From 41bd007aec85c4c2476064f5bdb0c46d3dff97e5 Mon Sep 17 00:00:00 2001 From: IvanIhnatsiuk Date: Tue, 11 Nov 2025 21:46:18 +0100 Subject: [PATCH 1/6] feat: add heading styles --- .../enriched/EnrichedTextInputView.kt | 89 ++--- .../enriched/EnrichedTextInputViewManager.kt | 12 + .../enriched/spans/EnrichedH4Span.kt | 17 + .../enriched/spans/EnrichedH5Span.kt | 17 + .../enriched/spans/EnrichedH6Span.kt | 17 + .../swmansion/enriched/spans/EnrichedSpans.kt | 116 +++---- .../swmansion/enriched/styles/HtmlStyle.kt | 21 ++ .../enriched/utils/EnrichedParser.java | 54 +++- .../enriched/utils/EnrichedSpanState.kt | 67 ++-- apps/example/src/App.tsx | 19 +- apps/example/src/components/Toolbar.tsx | 27 ++ ios/EnrichedTextInputView.mm | 304 +++++++++--------- ios/config/InputConfig.h | 12 + ios/config/InputConfig.mm | 60 ++++ ios/inputParser/InputParser.mm | 127 +++----- ios/styles/BoldStyle.mm | 29 +- ios/styles/H4Style.mm | 10 + ios/styles/H5Style.mm | 10 + ios/styles/H6Style.mm | 10 + ios/styles/HeadingStyleBase.mm | 2 +- ios/utils/StyleHeaders.h | 11 +- ios/utils/StyleTypeEnum.h | 3 + src/EnrichedTextInput.tsx | 35 +- src/EnrichedTextInputNativeComponent.ts | 32 +- src/normalizeHtmlStyle.ts | 12 + 25 files changed, 711 insertions(+), 402 deletions(-) create mode 100644 android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt create mode 100644 android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt create mode 100644 android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt create mode 100644 ios/styles/H4Style.mm create mode 100644 ios/styles/H5Style.mm create mode 100644 ios/styles/H6Style.mm diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt index 3d8ca8179..5cc963f0c 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt @@ -475,6 +475,9 @@ class EnrichedTextInputView : AppCompatEditText { EnrichedSpans.H1 -> paragraphStyles?.toggleStyle(EnrichedSpans.H1) EnrichedSpans.H2 -> paragraphStyles?.toggleStyle(EnrichedSpans.H2) EnrichedSpans.H3 -> paragraphStyles?.toggleStyle(EnrichedSpans.H3) + EnrichedSpans.H4 -> paragraphStyles?.toggleStyle(EnrichedSpans.H4) + EnrichedSpans.H5 -> paragraphStyles?.toggleStyle(EnrichedSpans.H5) + EnrichedSpans.H6 -> paragraphStyles?.toggleStyle(EnrichedSpans.H6) EnrichedSpans.CODE_BLOCK -> paragraphStyles?.toggleStyle(EnrichedSpans.CODE_BLOCK) EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.toggleStyle(EnrichedSpans.BLOCK_QUOTE) EnrichedSpans.ORDERED_LIST -> listStyles?.toggleStyle(EnrichedSpans.ORDERED_LIST) @@ -485,54 +488,54 @@ class EnrichedTextInputView : AppCompatEditText { layoutManager.invalidateLayout() } - private fun removeStyle( - name: String, - start: Int, - end: Int, - ): Boolean { - val removed = - when (name) { - EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end) - EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end) - EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end) - EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end) - EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end) - EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end) - EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end) - EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end) - EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end) - EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end) - EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end) - EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end) - EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end) - EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end) - EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end) - else -> false - } + private fun removeStyle(name: String, start: Int, end: Int): Boolean { + val removed = when (name) { + EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end) + EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end) + EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end) + EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end) + EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end) + EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end) + EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end) + EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end) + EnrichedSpans.H4 -> paragraphStyles?.removeStyle(EnrichedSpans.H4, start, end) + EnrichedSpans.H5 -> paragraphStyles?.removeStyle(EnrichedSpans.H5, start, end) + EnrichedSpans.H6 -> paragraphStyles?.removeStyle(EnrichedSpans.H6, start, end) + EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end) + EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end) + EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end) + EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end) + EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end) + EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end) + EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end) + else -> false + } return removed == true } private fun getTargetRange(name: String): Pair { - val result = - when (name) { - EnrichedSpans.BOLD -> inlineStyles?.getStyleRange() - EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange() - EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange() - EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange() - EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange() - EnrichedSpans.H1 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H2 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H3 -> paragraphStyles?.getStyleRange() - EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange() - EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange() - EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange() - EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange() - EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange() - EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange() - EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange() - else -> Pair(0, 0) - } + val result = when (name) { + EnrichedSpans.BOLD -> inlineStyles?.getStyleRange() + EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange() + EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange() + EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange() + EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange() + EnrichedSpans.H1 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H2 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H3 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H4 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H5 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H6 -> paragraphStyles?.getStyleRange() + EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange() + EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange() + EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange() + EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange() + EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange() + EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange() + EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange() + else -> Pair(0, 0) + } return result ?: Pair(0, 0) } diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt index cb4ee3e61..84553e665 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt @@ -294,6 +294,18 @@ class EnrichedTextInputViewManager : view?.verifyAndToggleStyle(EnrichedSpans.H3) } + override fun toggleH4(view: EnrichedTextInputView?) { + view?.verifyAndToggleStyle(EnrichedSpans.H4) + } + + override fun toggleH5(view: EnrichedTextInputView?) { + view?.verifyAndToggleStyle(EnrichedSpans.H5) + } + + override fun toggleH6(view: EnrichedTextInputView?) { + view?.verifyAndToggleStyle(EnrichedSpans.H6) + } + override fun toggleCodeBlock(view: EnrichedTextInputView?) { view?.verifyAndToggleStyle(EnrichedSpans.CODE_BLOCK) } diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt new file mode 100644 index 000000000..76f649476 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt @@ -0,0 +1,17 @@ +package com.swmansion.enriched.spans + +import android.graphics.Typeface +import android.text.TextPaint +import android.text.style.AbsoluteSizeSpan +import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan +import com.swmansion.enriched.styles.HtmlStyle + +class EnrichedH4Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h4FontSize), EnrichedHeadingSpan { + override fun updateDrawState(tp: TextPaint) { + super.updateDrawState(tp) + val bold = htmlStyle.h4Bold + if (bold) { + tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD) + } + } +} diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt new file mode 100644 index 000000000..437bddf14 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt @@ -0,0 +1,17 @@ +package com.swmansion.enriched.spans + +import android.graphics.Typeface +import android.text.TextPaint +import android.text.style.AbsoluteSizeSpan +import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan +import com.swmansion.enriched.styles.HtmlStyle + +class EnrichedH5Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h5FontSize), EnrichedHeadingSpan { + override fun updateDrawState(tp: TextPaint) { + super.updateDrawState(tp) + val bold = htmlStyle.h5Bold + if (bold) { + tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD) + } + } +} diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt new file mode 100644 index 000000000..22c89e7d0 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt @@ -0,0 +1,17 @@ +package com.swmansion.enriched.spans + +import android.graphics.Typeface +import android.text.TextPaint +import android.text.style.AbsoluteSizeSpan +import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan +import com.swmansion.enriched.styles.HtmlStyle + +class EnrichedH6Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h6FontSize), EnrichedHeadingSpan { + override fun updateDrawState(tp: TextPaint) { + super.updateDrawState(tp) + val bold = htmlStyle.h6Bold + if (bold) { + tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD) + } + } +} diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt index 800d2ef0b..09616ca39 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt @@ -39,6 +39,9 @@ object EnrichedSpans { const val H1 = "h1" const val H2 = "h2" const val H3 = "h3" + const val H4 = "h4" + const val H5 = "h5" + const val H6 = "h6" const val BLOCK_QUOTE = "block_quote" const val CODE_BLOCK = "code_block" @@ -60,14 +63,16 @@ object EnrichedSpans { INLINE_CODE to BaseSpanConfig(EnrichedInlineCodeSpan::class.java), ) - val paragraphSpans: Map = - mapOf( - H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false), - H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false), - H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false), - BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true), - CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true), - ) + val paragraphSpans: Map = mapOf( + H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false), + H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false), + H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false), + H4 to ParagraphSpanConfig(EnrichedH4Span::class.java, false), + H5 to ParagraphSpanConfig(EnrichedH5Span::class.java, false), + H6 to ParagraphSpanConfig(EnrichedH6Span::class.java, false), + BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true), + CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true), + ) val listSpans: Map = mapOf( @@ -94,6 +99,9 @@ object EnrichedSpans { if (htmlStyle.h1Bold) blockingStyles.add(H1) if (htmlStyle.h2Bold) blockingStyles.add(H2) if (htmlStyle.h3Bold) blockingStyles.add(H3) + if (htmlStyle.h4Bold) blockingStyles.add(H4) + if (htmlStyle.h5Bold) blockingStyles.add(H5) + if (htmlStyle.h6Bold) blockingStyles.add(H6) StylesMergingConfig(blockingStyles = blockingStyles.toTypedArray()) } @@ -123,79 +131,57 @@ object EnrichedSpans { } H1 -> { - val conflictingStyles = mutableListOf(H2, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) + val conflictingStyles = mutableListOf(H2, H3, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) if (htmlStyle.h1Bold) conflictingStyles.add(BOLD) StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } H2 -> { - val conflictingStyles = mutableListOf(H1, H3, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) + val conflictingStyles = mutableListOf(H1, H3, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) if (htmlStyle.h2Bold) conflictingStyles.add(BOLD) StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } H3 -> { - val conflictingStyles = mutableListOf(H1, H2, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) + val conflictingStyles = mutableListOf(H1, H2, H4, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) if (htmlStyle.h3Bold) conflictingStyles.add(BOLD) StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } - - BLOCK_QUOTE -> { - StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST), - ) - } - - CODE_BLOCK -> { - StylesMergingConfig( - conflictingStyles = - arrayOf( - H1, - H2, - H3, - BOLD, - ITALIC, - UNDERLINE, - STRIKETHROUGH, - UNORDERED_LIST, - ORDERED_LIST, - BLOCK_QUOTE, - INLINE_CODE, - ), - ) - } - - UNORDERED_LIST -> { - StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), - ) - } - - ORDERED_LIST -> { - StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), - ) - } - - LINK -> { - StylesMergingConfig( - blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION), - ) - } - - IMAGE -> { - StylesMergingConfig() + H4 -> { + val conflictingStyles = mutableListOf(H1, H2, H3, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) + if (htmlStyle.h4Bold) conflictingStyles.add(BOLD) + StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } - - MENTION -> { - StylesMergingConfig( - blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, LINK), - ) + H5 -> { + val conflictingStyles = mutableListOf(H1, H2, H3, H4, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) + if (htmlStyle.h5Bold) conflictingStyles.add(BOLD) + StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } - - else -> { - null + H6 -> { + val conflictingStyles = mutableListOf(H1, H2, H3, H4, H5, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) + if (htmlStyle.h6Bold) conflictingStyles.add(BOLD) + StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } + BLOCK_QUOTE -> StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST) + ) + CODE_BLOCK -> StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, UNORDERED_LIST, ORDERED_LIST, BLOCK_QUOTE, INLINE_CODE) + ) + UNORDERED_LIST -> StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE) + ) + ORDERED_LIST -> StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), + ) + LINK -> StylesMergingConfig( + blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION) + ) + IMAGE -> StylesMergingConfig() + MENTION -> StylesMergingConfig( + blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, LINK) + ) + else -> null } fun isTypeContinuous(type: Class<*>): Boolean = paragraphSpans.values.find { it.clazz == type }?.isContinuous == true diff --git a/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt b/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt index ed0909151..66043b932 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt @@ -28,6 +28,15 @@ class HtmlStyle { var h3FontSize: Int = 56 var h3Bold: Boolean = false + var h4FontSize: Int = 48 + var h4Bold: Boolean = false + + var h5FontSize: Int = 40 + var h5Bold: Boolean = false + + var h6FontSize: Int = 32 + var h6Bold: Boolean = false + var blockquoteColor: Int? = null var blockquoteBorderColor: Int = Color.BLACK var blockquoteStripeWidth: Int = 2 @@ -77,6 +86,18 @@ class HtmlStyle { h3FontSize = parseFloat(h3Style, "fontSize").toInt() h3Bold = h3Style?.getBoolean("bold") == true + val h4Style = style.getMap("h4") + h4FontSize = parseFloat(h4Style, "fontSize").toInt() + h4Bold = h4Style?.getBoolean("bold") == true + + val h5Style = style.getMap("h5") + h5FontSize = parseFloat(h5Style, "fontSize").toInt() + h5Bold = h5Style?.getBoolean("bold") == true + + val h6Style = style.getMap("h6") + h6FontSize = parseFloat(h6Style, "fontSize").toInt() + h6Bold = h6Style?.getBoolean("bold") == true + val blockquoteStyle = style.getMap("blockquote") blockquoteColor = parseOptionalColor(blockquoteStyle, "color") blockquoteBorderColor = parseColor(blockquoteStyle, "borderColor") diff --git a/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java b/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java index f158ab7cc..1a2e1cb55 100644 --- a/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +++ b/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java @@ -15,6 +15,9 @@ import com.swmansion.enriched.spans.EnrichedH1Span; import com.swmansion.enriched.spans.EnrichedH2Span; import com.swmansion.enriched.spans.EnrichedH3Span; +import com.swmansion.enriched.spans.EnrichedH4Span; +import com.swmansion.enriched.spans.EnrichedH5Span; +import com.swmansion.enriched.spans.EnrichedH6Span; import com.swmansion.enriched.spans.EnrichedImageSpan; import com.swmansion.enriched.spans.EnrichedInlineCodeSpan; import com.swmansion.enriched.spans.EnrichedItalicSpan; @@ -159,6 +162,12 @@ private static String getBlockTag(EnrichedParagraphSpan[] spans) { return "h2"; } else if (span instanceof EnrichedH3Span) { return "h3"; + } else if (span instanceof EnrichedH4Span) { + return "h4"; + } else if (span instanceof EnrichedH5Span) { + return "h5"; + } else if (span instanceof EnrichedH6Span) { + return "h6"; } } @@ -477,6 +486,12 @@ private void handleStartTag(String tag, Attributes attributes) { startHeading(mSpannableStringBuilder, 2); } else if (tag.equalsIgnoreCase("h3")) { startHeading(mSpannableStringBuilder, 3); + } else if (tag.equalsIgnoreCase("h4")) { + startHeading(mSpannableStringBuilder, 4); + } else if (tag.equalsIgnoreCase("h5")) { + startHeading(mSpannableStringBuilder, 5); + } else if (tag.equalsIgnoreCase("h6")) { + startHeading(mSpannableStringBuilder, 6); } else if (tag.equalsIgnoreCase("img")) { startImg(mSpannableStringBuilder, attributes, mImageGetter); } else if (tag.equalsIgnoreCase("code")) { @@ -515,6 +530,12 @@ private void handleEndTag(String tag) { endHeading(mSpannableStringBuilder, mStyle, 2); } else if (tag.equalsIgnoreCase("h3")) { endHeading(mSpannableStringBuilder, mStyle, 3); + } else if (tag.equalsIgnoreCase("h4")) { + endHeading(mSpannableStringBuilder, mStyle, 4); + } else if (tag.equalsIgnoreCase("h5")) { + endHeading(mSpannableStringBuilder, mStyle, 5); + } else if (tag.equalsIgnoreCase("h6")) { + endHeading(mSpannableStringBuilder, mStyle, 6); } else if (tag.equalsIgnoreCase("code")) { end(mSpannableStringBuilder, Code.class, new EnrichedInlineCodeSpan(mStyle)); } else if (tag.equalsIgnoreCase("mention")) { @@ -618,6 +639,15 @@ private void startHeading(Editable text, int level) { case 3: start(text, new H3()); break; + case 4: + start(text, new H4()); + break; + case 5: + start(text, new H5()); + break; + case 6: + start(text, new H6()); + break; default: throw new IllegalArgumentException("Unsupported heading level: " + level); } @@ -639,6 +669,18 @@ private static void endHeading(Editable text, HtmlStyle style, int level) { H3 lastH3 = getLast(text, H3.class); setParagraphSpanFromMark(text, lastH3, new EnrichedH3Span(style)); break; + case 4: + H4 lastH4 = getLast(text, H4.class); + setParagraphSpanFromMark(text, lastH4, new EnrichedH4Span(style)); + break; + case 5: + H5 lastH5 = getLast(text, H5.class); + setParagraphSpanFromMark(text, lastH5, new EnrichedH5Span(style)); + break; + case 6: + H6 lastH6 = getLast(text, H6.class); + setParagraphSpanFromMark(text, lastH6, new EnrichedH6Span(style)); + break; default: throw new IllegalArgumentException("Unsupported heading level: " + level); } @@ -819,7 +861,17 @@ private static class H2 {} private static class H3 {} - private static class Bold {} + private static class H4 { + } + + private static class H5 { + } + + private static class H6 { + } + + private static class Bold { + } private static class Italic {} diff --git a/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt b/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt index e1871455b..da24a797f 100644 --- a/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +++ b/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt @@ -29,6 +29,12 @@ class EnrichedSpanState( private set var h3Start: Int? = null private set + var h4Start: Int? = null + private set + var h5Start: Int? = null + private set + var h6Start: Int? = null + private set var codeBlockStart: Int? = null private set var blockQuoteStart: Int? = null @@ -84,6 +90,21 @@ class EnrichedSpanState( emitStateChangeEvent() } + fun setH4Start(start: Int?) { + this.h4Start = start + emitStateChangeEvent() + } + + fun setH5Start(start: Int?) { + this.h5Start = start + emitStateChangeEvent() + } + + fun setH6Start(start: Int?) { + this.h6Start = start + emitStateChangeEvent() + } + fun setCodeBlockStart(start: Int?) { this.codeBlockStart = start emitStateChangeEvent() @@ -120,25 +141,27 @@ class EnrichedSpanState( } fun getStart(name: String): Int? { - val start = - when (name) { - EnrichedSpans.BOLD -> boldStart - EnrichedSpans.ITALIC -> italicStart - EnrichedSpans.UNDERLINE -> underlineStart - EnrichedSpans.STRIKETHROUGH -> strikethroughStart - EnrichedSpans.INLINE_CODE -> inlineCodeStart - EnrichedSpans.H1 -> h1Start - EnrichedSpans.H2 -> h2Start - EnrichedSpans.H3 -> h3Start - EnrichedSpans.CODE_BLOCK -> codeBlockStart - EnrichedSpans.BLOCK_QUOTE -> blockQuoteStart - EnrichedSpans.ORDERED_LIST -> orderedListStart - EnrichedSpans.UNORDERED_LIST -> unorderedListStart - EnrichedSpans.LINK -> linkStart - EnrichedSpans.IMAGE -> imageStart - EnrichedSpans.MENTION -> mentionStart - else -> null - } + val start = when (name) { + EnrichedSpans.BOLD -> boldStart + EnrichedSpans.ITALIC -> italicStart + EnrichedSpans.UNDERLINE -> underlineStart + EnrichedSpans.STRIKETHROUGH -> strikethroughStart + EnrichedSpans.INLINE_CODE -> inlineCodeStart + EnrichedSpans.H1 -> h1Start + EnrichedSpans.H2 -> h2Start + EnrichedSpans.H3 -> h3Start + EnrichedSpans.H4 -> h4Start + EnrichedSpans.H5 -> h5Start + EnrichedSpans.H6 -> h6Start + EnrichedSpans.CODE_BLOCK -> codeBlockStart + EnrichedSpans.BLOCK_QUOTE -> blockQuoteStart + EnrichedSpans.ORDERED_LIST -> orderedListStart + EnrichedSpans.UNORDERED_LIST -> unorderedListStart + EnrichedSpans.LINK -> linkStart + EnrichedSpans.IMAGE -> imageStart + EnrichedSpans.MENTION -> mentionStart + else -> null + } return start } @@ -156,6 +179,9 @@ class EnrichedSpanState( EnrichedSpans.H1 -> setH1Start(start) EnrichedSpans.H2 -> setH2Start(start) EnrichedSpans.H3 -> setH3Start(start) + EnrichedSpans.H4 -> setH4Start(start) + EnrichedSpans.H5 -> setH5Start(start) + EnrichedSpans.H6 -> setH6Start(start) EnrichedSpans.CODE_BLOCK -> setCodeBlockStart(start) EnrichedSpans.BLOCK_QUOTE -> setBlockQuoteStart(start) EnrichedSpans.ORDERED_LIST -> setOrderedListStart(start) @@ -176,6 +202,9 @@ class EnrichedSpanState( payload.putBoolean("isH1", h1Start != null) payload.putBoolean("isH2", h2Start != null) payload.putBoolean("isH3", h3Start != null) + payload.putBoolean("isH4", h4Start != null) + payload.putBoolean("isH5", h5Start != null) + payload.putBoolean("isH6", h6Start != null) payload.putBoolean("isCodeBlock", codeBlockStart != null) payload.putBoolean("isBlockQuote", blockQuoteStart != null) payload.putBoolean("isOrderedList", orderedListStart != null) diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index d36ac903a..81bdfeebf 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -46,6 +46,9 @@ const DEFAULT_STYLE: StylesState = { isH1: false, isH2: false, isH3: false, + isH4: false, + isH5: false, + isH6: false, isBlockQuote: false, isCodeBlock: false, isOrderedList: false, @@ -370,14 +373,26 @@ export default function App() { const htmlStyle: HtmlStyle = { h1: { - fontSize: 40, + fontSize: 72, bold: true, }, h2: { - fontSize: 32, + fontSize: 60, bold: true, }, h3: { + fontSize: 50, + bold: true, + }, + h4: { + fontSize: 40, + bold: true, + }, + h5: { + fontSize: 30, + bold: true, + }, + h6: { fontSize: 24, bold: true, }, diff --git a/apps/example/src/components/Toolbar.tsx b/apps/example/src/components/Toolbar.tsx index 28c99b48a..6fad4c527 100644 --- a/apps/example/src/components/Toolbar.tsx +++ b/apps/example/src/components/Toolbar.tsx @@ -39,6 +39,18 @@ const STYLE_ITEMS = [ name: 'heading-3', text: 'H3', }, + { + name: 'heading-4', + text: 'H4', + }, + { + name: 'heading-5', + text: 'H5', + }, + { + name: 'heading-6', + text: 'H6', + }, { name: 'quote', icon: 'quote-right', @@ -114,6 +126,15 @@ export const Toolbar: FC = ({ case 'heading-3': editorRef.current?.toggleH3(); break; + case 'heading-4': + editorRef.current?.toggleH4(); + break; + case 'heading-5': + editorRef.current?.toggleH5(); + break; + case 'heading-6': + editorRef.current?.toggleH6(); + break; case 'code-block': editorRef?.current?.toggleCodeBlock(); break; @@ -156,6 +177,12 @@ export const Toolbar: FC = ({ return stylesState.isH2; case 'heading-3': return stylesState.isH3; + case 'heading-4': + return stylesState.isH4; + case 'heading-5': + return stylesState.isH5; + case 'heading-6': + return stylesState.isH6; case 'code-block': return stylesState.isCodeBlock; case 'quote': diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index a46f4f20d..996e56452 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -86,27 +86,23 @@ - (void)setDefaults { stylesDict = @{ @([BoldStyle getStyleType]) : [[BoldStyle alloc] initWithInput:self], - @([ItalicStyle getStyleType]) : [[ItalicStyle alloc] initWithInput:self], - @([UnderlineStyle getStyleType]) : - [[UnderlineStyle alloc] initWithInput:self], - @([StrikethroughStyle getStyleType]) : - [[StrikethroughStyle alloc] initWithInput:self], - @([InlineCodeStyle getStyleType]) : - [[InlineCodeStyle alloc] initWithInput:self], - @([LinkStyle getStyleType]) : [[LinkStyle alloc] initWithInput:self], - @([MentionStyle getStyleType]) : [[MentionStyle alloc] initWithInput:self], - @([H1Style getStyleType]) : [[H1Style alloc] initWithInput:self], - @([H2Style getStyleType]) : [[H2Style alloc] initWithInput:self], - @([H3Style getStyleType]) : [[H3Style alloc] initWithInput:self], - @([UnorderedListStyle getStyleType]) : - [[UnorderedListStyle alloc] initWithInput:self], - @([OrderedListStyle getStyleType]) : - [[OrderedListStyle alloc] initWithInput:self], - @([BlockQuoteStyle getStyleType]) : - [[BlockQuoteStyle alloc] initWithInput:self], - @([CodeBlockStyle getStyleType]) : - [[CodeBlockStyle alloc] initWithInput:self], - @([ImageStyle getStyleType]) : [[ImageStyle alloc] initWithInput:self] + @([ItalicStyle getStyleType]): [[ItalicStyle alloc] initWithInput:self], + @([UnderlineStyle getStyleType]): [[UnderlineStyle alloc] initWithInput:self], + @([StrikethroughStyle getStyleType]): [[StrikethroughStyle alloc] initWithInput:self], + @([InlineCodeStyle getStyleType]): [[InlineCodeStyle alloc] initWithInput:self], + @([LinkStyle getStyleType]): [[LinkStyle alloc] initWithInput:self], + @([MentionStyle getStyleType]): [[MentionStyle alloc] initWithInput:self], + @([H1Style getStyleType]): [[H1Style alloc] initWithInput:self], + @([H2Style getStyleType]): [[H2Style alloc] initWithInput:self], + @([H3Style getStyleType]): [[H3Style alloc] initWithInput:self], + @([H4Style getStyleType]): [[H4Style alloc] initWithInput:self], + @([H5Style getStyleType]): [[H5Style alloc] initWithInput:self], + @([H6Style getStyleType]): [[H6Style alloc] initWithInput:self], + @([UnorderedListStyle getStyleType]): [[UnorderedListStyle alloc] initWithInput:self], + @([OrderedListStyle getStyleType]): [[OrderedListStyle alloc] initWithInput:self], + @([BlockQuoteStyle getStyleType]): [[BlockQuoteStyle alloc] initWithInput:self], + @([CodeBlockStyle getStyleType]): [[CodeBlockStyle alloc] initWithInput:self], + @([ImageStyle getStyleType]): [[ImageStyle alloc] initWithInput:self] }; conflictingStyles = @{ @@ -114,77 +110,42 @@ - (void)setDefaults { @([ItalicStyle getStyleType]) : @[], @([UnderlineStyle getStyleType]) : @[], @([StrikethroughStyle getStyleType]) : @[], - @([InlineCodeStyle getStyleType]) : - @[ @([LinkStyle getStyleType]), @([MentionStyle getStyleType]) ], - @([LinkStyle getStyleType]) : @[ - @([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]), - @([MentionStyle getStyleType]) - ], - @([MentionStyle getStyleType]) : - @[ @([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]) ], - @([H1Style getStyleType]) : @[ - @([H2Style getStyleType]), @([H3Style getStyleType]), - @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), - @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType]) - ], - @([H2Style getStyleType]) : @[ - @([H1Style getStyleType]), @([H3Style getStyleType]), - @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), - @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType]) - ], - @([H3Style getStyleType]) : @[ - @([H1Style getStyleType]), @([H2Style getStyleType]), - @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), - @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType]) - ], - @([UnorderedListStyle getStyleType]) : @[ - @([H1Style getStyleType]), @([H2Style getStyleType]), - @([H3Style getStyleType]), @([OrderedListStyle getStyleType]), - @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType]) - ], - @([OrderedListStyle getStyleType]) : @[ - @([H1Style getStyleType]), @([H2Style getStyleType]), - @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), - @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType]) - ], - @([BlockQuoteStyle getStyleType]) : @[ - @([H1Style getStyleType]), @([H2Style getStyleType]), - @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), - @([OrderedListStyle getStyleType]), @([CodeBlockStyle getStyleType]) - ], - @([CodeBlockStyle getStyleType]) : @[ - @([H1Style getStyleType]), @([H2Style getStyleType]), - @([H3Style getStyleType]), @([BoldStyle getStyleType]), - @([ItalicStyle getStyleType]), @([UnderlineStyle getStyleType]), - @([StrikethroughStyle getStyleType]), - @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), - @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType]), - @([MentionStyle getStyleType]), @([LinkStyle getStyleType]) - ], - @([ImageStyle getStyleType]) : - @[ @([LinkStyle getStyleType]), @([MentionStyle getStyleType]) ] + @([InlineCodeStyle getStyleType]) : @[@([LinkStyle getStyleType]), @([MentionStyle getStyleType])], + @([LinkStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]), @([MentionStyle getStyleType])], + @([MentionStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType])], + @([H1Style getStyleType]): @[@([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], + @([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], + @([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], + @([H4Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], + @([H5Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], + @([H6Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], + @([UnorderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], + @([OrderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], + @([BlockQuoteStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType])], + @([CodeBlockStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), + @([BoldStyle getStyleType]), @([ItalicStyle getStyleType]), @([UnderlineStyle getStyleType]), @([StrikethroughStyle getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType]), @([MentionStyle getStyleType]), @([LinkStyle getStyleType])], + @([ImageStyle getStyleType]) : @[@([LinkStyle getStyleType]), @([MentionStyle getStyleType])] }; blockingStyles = @{ - @([BoldStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ], - @([ItalicStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ], - @([UnderlineStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ], - @([StrikethroughStyle getStyleType]) : - @[ @([CodeBlockStyle getStyleType]) ], - @([InlineCodeStyle getStyleType]) : - @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ], - @([LinkStyle getStyleType]) : - @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ], - @([MentionStyle getStyleType]) : - @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ], - @([H1Style getStyleType]) : @[], - @([H2Style getStyleType]) : @[], - @([H3Style getStyleType]) : @[], - @([UnorderedListStyle getStyleType]) : @[], - @([OrderedListStyle getStyleType]) : @[], - @([BlockQuoteStyle getStyleType]) : @[], - @([CodeBlockStyle getStyleType]) : @[], - @([ImageStyle getStyleType]) : @[ @([InlineCodeStyle getStyleType]) ] + @([BoldStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([ItalicStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([UnderlineStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([StrikethroughStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([InlineCodeStyle getStyleType]) : @[@([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType])], + @([LinkStyle getStyleType]): @[@([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType])], + @([MentionStyle getStyleType]): @[@([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType])], + @([H1Style getStyleType]): @[], + @([H2Style getStyleType]): @[], + @([H3Style getStyleType]): @[], + @([H4Style getStyleType]): @[], + @([H5Style getStyleType]): @[], + @([H6Style getStyleType]): @[], + @([UnorderedListStyle getStyleType]): @[], + @([OrderedListStyle getStyleType]): @[], + @([BlockQuoteStyle getStyleType]): @[], + @([CodeBlockStyle getStyleType]): @[], + @([ImageStyle getStyleType]) : @[@([InlineCodeStyle getStyleType])] }; parser = [[InputParser alloc] initWithInput:self]; @@ -316,13 +277,40 @@ - (void)updateProps:(Props::Shared const &)props [newConfig setH3Bold:newViewProps.htmlStyle.h3.bold]; stylePropChanged = YES; } - - if (newViewProps.htmlStyle.blockquote.borderColor != - oldViewProps.htmlStyle.blockquote.borderColor) { - if (isColorMeaningful(newViewProps.htmlStyle.blockquote.borderColor)) { - [newConfig setBlockquoteBorderColor:RCTUIColorFromSharedColor( - newViewProps.htmlStyle.blockquote - .borderColor)]; + + if(newViewProps.htmlStyle.h4.fontSize != oldViewProps.htmlStyle.h4.fontSize) { + [newConfig setH4FontSize:newViewProps.htmlStyle.h4.fontSize]; + stylePropChanged = YES; + } + + if(newViewProps.htmlStyle.h4.bold != oldViewProps.htmlStyle.h4.bold) { + [newConfig setH4Bold:newViewProps.htmlStyle.h4.bold]; + stylePropChanged = YES; + } + + if(newViewProps.htmlStyle.h5.fontSize != oldViewProps.htmlStyle.h5.fontSize) { + [newConfig setH5FontSize:newViewProps.htmlStyle.h5.fontSize]; + stylePropChanged = YES; + } + + if(newViewProps.htmlStyle.h5.bold != oldViewProps.htmlStyle.h5.bold) { + [newConfig setH5Bold:newViewProps.htmlStyle.h5.bold]; + stylePropChanged = YES; + } + + if(newViewProps.htmlStyle.h6.fontSize != oldViewProps.htmlStyle.h6.fontSize) { + [newConfig setH6FontSize:newViewProps.htmlStyle.h6.fontSize]; + stylePropChanged = YES; + } + + if(newViewProps.htmlStyle.h6.bold != oldViewProps.htmlStyle.h6.bold) { + [newConfig setH6Bold:newViewProps.htmlStyle.h6.bold]; + stylePropChanged = YES; + } + + if(newViewProps.htmlStyle.blockquote.borderColor != oldViewProps.htmlStyle.blockquote.borderColor) { + if(isColorMeaningful(newViewProps.htmlStyle.blockquote.borderColor)) { + [newConfig setBlockquoteBorderColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.blockquote.borderColor)]; stylePropChanged = YES; } } @@ -875,30 +863,24 @@ - (void)tryUpdatingActiveStyles { _activeStyles = newActiveStyles; emitter->onChangeState({ - .isBold = [_activeStyles containsObject:@([BoldStyle getStyleType])], - .isItalic = - [_activeStyles containsObject:@([ItalicStyle getStyleType])], - .isUnderline = - [_activeStyles containsObject:@([UnderlineStyle getStyleType])], - .isStrikeThrough = - [_activeStyles containsObject:@([StrikethroughStyle getStyleType])], - .isInlineCode = - [_activeStyles containsObject:@([InlineCodeStyle getStyleType])], - .isLink = [_activeStyles containsObject:@([LinkStyle getStyleType])], - .isMention = - [_activeStyles containsObject:@([MentionStyle getStyleType])], - .isH1 = [_activeStyles containsObject:@([H1Style getStyleType])], - .isH2 = [_activeStyles containsObject:@([H2Style getStyleType])], - .isH3 = [_activeStyles containsObject:@([H3Style getStyleType])], - .isUnorderedList = - [_activeStyles containsObject:@([UnorderedListStyle getStyleType])], - .isOrderedList = - [_activeStyles containsObject:@([OrderedListStyle getStyleType])], - .isBlockQuote = - [_activeStyles containsObject:@([BlockQuoteStyle getStyleType])], - .isCodeBlock = - [_activeStyles containsObject:@([CodeBlockStyle getStyleType])], - .isImage = [_activeStyles containsObject:@([ImageStyle getStyleType])], + .isBold = [_activeStyles containsObject: @([BoldStyle getStyleType])], + .isItalic = [_activeStyles containsObject: @([ItalicStyle getStyleType])], + .isUnderline = [_activeStyles containsObject: @([UnderlineStyle getStyleType])], + .isStrikeThrough = [_activeStyles containsObject: @([StrikethroughStyle getStyleType])], + .isInlineCode = [_activeStyles containsObject: @([InlineCodeStyle getStyleType])], + .isLink = [_activeStyles containsObject: @([LinkStyle getStyleType])], + .isMention = [_activeStyles containsObject: @([MentionStyle getStyleType])], + .isH1 = [_activeStyles containsObject: @([H1Style getStyleType])], + .isH2 = [_activeStyles containsObject: @([H2Style getStyleType])], + .isH3 = [_activeStyles containsObject: @([H3Style getStyleType])], + .isH4 = [_activeStyles containsObject: @([H4Style getStyleType])], + .isH5 = [_activeStyles containsObject: @([H5Style getStyleType])], + .isH6 = [_activeStyles containsObject: @([H6Style getStyleType])], + .isUnorderedList = [_activeStyles containsObject: @([UnorderedListStyle getStyleType])], + .isOrderedList = [_activeStyles containsObject: @([OrderedListStyle getStyleType])], + .isBlockQuote = [_activeStyles containsObject: @([BlockQuoteStyle getStyleType])], + .isCodeBlock = [_activeStyles containsObject: @([CodeBlockStyle getStyleType])], + .isImage = [_activeStyles containsObject: @([ImageStyle getStyleType])], }); } } @@ -964,7 +946,13 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args { [self toggleParagraphStyle:[H2Style getStyleType]]; } else if ([commandName isEqualToString:@"toggleH3"]) { [self toggleParagraphStyle:[H3Style getStyleType]]; - } else if ([commandName isEqualToString:@"toggleUnorderedList"]) { + } else if([commandName isEqualToString:@"toggleH4"]) { + [self toggleParagraphStyle:[H4Style getStyleType]]; + } else if([commandName isEqualToString:@"toggleH5"]) { + [self toggleParagraphStyle:[H5Style getStyleType]]; + } else if([commandName isEqualToString:@"toggleH6"]) { + [self toggleParagraphStyle:[H6Style getStyleType]]; + } else if([commandName isEqualToString:@"toggleUnorderedList"]) { [self toggleParagraphStyle:[UnorderedListStyle getStyleType]]; } else if ([commandName isEqualToString:@"toggleOrderedList"]) { [self toggleParagraphStyle:[OrderedListStyle getStyleType]]; @@ -1378,10 +1366,21 @@ - (void)anyTextMayHaveBeenModified { H1Style *h1Style = stylesDict[@([H1Style getStyleType])]; H2Style *h2Style = stylesDict[@([H2Style getStyleType])]; H3Style *h3Style = stylesDict[@([H3Style getStyleType])]; - if (h1Style != nullptr && h2Style != nullptr && h3Style != nullptr) { + H4Style *h4Style = stylesDict[@([H4Style getStyleType])]; + H5Style *h5Style = stylesDict[@([H5Style getStyleType])]; + H6Style *h6Style = stylesDict[@([H6Style getStyleType])]; + + bool shouldHandleImproperHeadings = + h1Style != nullptr && h2Style != nullptr && h3Style != nullptr && + h4Style != nullptr && h5Style != nullptr && h6Style != nullptr; + + if(shouldHandleImproperHeadings) { [h1Style handleImproperHeadings]; [h2Style handleImproperHeadings]; [h3Style handleImproperHeadings]; + [h4Style handleImproperHeadings]; + [h5Style handleImproperHeadings]; + [h6Style handleImproperHeadings]; } // mentions management: removal and editing @@ -1532,37 +1531,36 @@ - (bool)textView:(UITextView *)textView H1Style *h1Style = stylesDict[@([H1Style getStyleType])]; H2Style *h2Style = stylesDict[@([H2Style getStyleType])]; H3Style *h3Style = stylesDict[@([H3Style getStyleType])]; - - // some of the changes these checks do could interfere with later checks and - // cause a crash so here I rely on short circuiting evaluation of the logical - // expression either way it's not possible to have two of them come off at the - // same time - if ([uStyle handleBackspaceInRange:range replacementText:text] || - [uStyle tryHandlingListShorcutInRange:range replacementText:text] || - [oStyle handleBackspaceInRange:range replacementText:text] || - [oStyle tryHandlingListShorcutInRange:range replacementText:text] || - [bqStyle handleBackspaceInRange:range replacementText:text] || - [cbStyle handleBackspaceInRange:range replacementText:text] || - [linkStyle handleLeadingLinkReplacement:range replacementText:text] || - [mentionStyle handleLeadingMentionReplacement:range - replacementText:text] || - [h1Style handleNewlinesInRange:range replacementText:text] || - [h2Style handleNewlinesInRange:range replacementText:text] || - [h3Style handleNewlinesInRange:range replacementText:text] || - [ZeroWidthSpaceUtils handleBackspaceInRange:range - replacementText:text - input:self] || - [ParagraphAttributesUtils handleBackspaceInRange:range - replacementText:text - input:self] || - // CRITICAL: This callback HAS TO be always evaluated last. - // - // This function is the "Generic Fallback": if no specific style claims - // the backspace action to change its state, only then do we proceed to - // physically delete the newline and merge paragraphs. - [ParagraphAttributesUtils handleParagraphStylesMergeOnBackspace:range - replacementText:text - input:self]) { + H4Style *h4Style = stylesDict[@([H4Style getStyleType])]; + H5Style *h5Style = stylesDict[@([H5Style getStyleType])]; + H6Style *h6Style = stylesDict[@([H6Style getStyleType])]; + + // some of the changes these checks do could interfere with later checks and cause a crash + // so here I rely on short circuiting evaluation of the logical expression + // either way it's not possible to have two of them come off at the same time + if( + [uStyle handleBackspaceInRange:range replacementText:text] || + [uStyle tryHandlingListShorcutInRange:range replacementText:text] || + [oStyle handleBackspaceInRange:range replacementText:text] || + [oStyle tryHandlingListShorcutInRange:range replacementText:text] || + [bqStyle handleBackspaceInRange:range replacementText:text] || + [cbStyle handleBackspaceInRange:range replacementText:text] || + [linkStyle handleLeadingLinkReplacement:range replacementText:text] || + [mentionStyle handleLeadingMentionReplacement:range replacementText:text] || + [h1Style handleNewlinesInRange:range replacementText:text] || + [h2Style handleNewlinesInRange:range replacementText:text] || + [h3Style handleNewlinesInRange:range replacementText:text] || + [h4Style handleNewlinesInRange:range replacementText:text] || + [h5Style handleNewlinesInRange:range replacementText:text] || + [h6Style handleNewlinesInRange:range replacementText:text] || + [ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self] || + [ParagraphAttributesUtils handleBackspaceInRange:range replacementText:text input:self] || + // CRITICAL: This callback HAS TO be always evaluated last. + // + // This function is the "Generic Fallback": if no specific style claims the backspace action + // to change its state, only then do we proceed to physically delete the newline and merge paragraphs. + [ParagraphAttributesUtils handleNewlineBackspaceInRange:range replacementText:text input:self] + ) { [self anyTextMayHaveBeenModified]; return NO; } diff --git a/ios/config/InputConfig.h b/ios/config/InputConfig.h index e562517f1..dbf404308 100644 --- a/ios/config/InputConfig.h +++ b/ios/config/InputConfig.h @@ -29,6 +29,18 @@ - (void)setH3FontSize:(CGFloat)newValue; - (BOOL)h3Bold; - (void)setH3Bold:(BOOL)newValue; +- (CGFloat)h4FontSize; +- (void)setH4FontSize:(CGFloat)newValue; +- (BOOL)h4Bold; +- (void)setH4Bold:(BOOL)newValue; +- (CGFloat)h5FontSize; +- (void)setH5FontSize:(CGFloat)newValue; +- (BOOL)h5Bold; +- (void)setH5Bold:(BOOL)newValue; +- (CGFloat)h6FontSize; +- (void)setH6FontSize:(CGFloat)newValue; +- (BOOL)h6Bold; +- (void)setH6Bold:(BOOL)newValue; - (UIColor *)blockquoteBorderColor; - (void)setBlockquoteBorderColor:(UIColor *)newValue; - (CGFloat)blockquoteBorderWidth; diff --git a/ios/config/InputConfig.mm b/ios/config/InputConfig.mm index bccf74612..cef866943 100644 --- a/ios/config/InputConfig.mm +++ b/ios/config/InputConfig.mm @@ -17,6 +17,12 @@ @implementation InputConfig { BOOL _h2Bold; CGFloat _h3FontSize; BOOL _h3Bold; + CGFloat _h4FontSize; + BOOL _h4Bold; + CGFloat _h5FontSize; + BOOL _h5Bold; + CGFloat _h6FontSize; + BOOL _h6Bold; UIColor *_blockquoteBorderColor; CGFloat _blockquoteBorderWidth; CGFloat _blockquoteGapWidth; @@ -66,6 +72,12 @@ - (id)copyWithZone:(NSZone *)zone { copy->_h2Bold = _h2Bold; copy->_h3FontSize = _h3FontSize; copy->_h3Bold = _h3Bold; + copy->_h4FontSize = _h4FontSize; + copy->_h4Bold = _h4Bold; + copy->_h5FontSize = _h5FontSize; + copy->_h5Bold = _h5Bold; + copy->_h6FontSize = _h6FontSize; + copy->_h6Bold = _h6Bold; copy->_blockquoteBorderColor = [_blockquoteBorderColor copy]; copy->_blockquoteBorderWidth = _blockquoteBorderWidth; copy->_blockquoteGapWidth = _blockquoteGapWidth; @@ -222,6 +234,54 @@ - (void)setH3Bold:(BOOL)newValue { _h3Bold = newValue; } +- (CGFloat)h4FontSize { + return _h4FontSize; +} + +- (void)setH4FontSize:(CGFloat)newValue { + _h4FontSize = newValue; +} + +- (BOOL)h4Bold { + return _h4Bold; +} + +- (void)setH4Bold:(BOOL)newValue { + _h4Bold = newValue; +} + +- (CGFloat)h5FontSize { + return _h5FontSize; +} + +- (void)setH5FontSize:(CGFloat)newValue { + _h5FontSize = newValue; +} + +- (BOOL)h5Bold { + return _h5Bold; +} + +- (void)setH5Bold:(BOOL)newValue { + _h5Bold = newValue; +} + +- (CGFloat)h6FontSize { + return _h6FontSize; +} + +- (void)setH6FontSize:(CGFloat)newValue { + _h6FontSize = newValue; +} + +- (BOOL)h6Bold { + return _h6Bold; +} + +- (void)setH6Bold:(BOOL)newValue { + _h6Bold = newValue; +} + - (UIColor *)blockquoteBorderColor { return _blockquoteBorderColor; } diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm index c7543dde9..09d0f0939 100644 --- a/ios/inputParser/InputParser.mm +++ b/ios/inputParser/InputParser.mm @@ -217,17 +217,17 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { } // don't add the

tag if some paragraph styles are present - if ([currentActiveStyles - containsObject:@([UnorderedListStyle getStyleType])] || - [currentActiveStyles - containsObject:@([OrderedListStyle getStyleType])] || - [currentActiveStyles containsObject:@([H1Style getStyleType])] || - [currentActiveStyles containsObject:@([H2Style getStyleType])] || - [currentActiveStyles containsObject:@([H3Style getStyleType])] || - [currentActiveStyles - containsObject:@([BlockQuoteStyle getStyleType])] || - [currentActiveStyles - containsObject:@([CodeBlockStyle getStyleType])]) { + if([currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])] || + [currentActiveStyles containsObject:@([OrderedListStyle getStyleType])] || + [currentActiveStyles containsObject:@([H1Style getStyleType])] || + [currentActiveStyles containsObject:@([H2Style getStyleType])] || + [currentActiveStyles containsObject:@([H3Style getStyleType])] || + [currentActiveStyles containsObject:@([H4Style getStyleType])] || + [currentActiveStyles containsObject:@([H5Style getStyleType])] || + [currentActiveStyles containsObject:@([H6Style getStyleType])] || + [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] || + [currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])] + ) { [result appendString:@"\n"]; } else { [result appendString:@"\n

"]; @@ -373,13 +373,15 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { } else if ([previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) { [result appendString:@"\n"]; - } else if ([previousActiveStyles - containsObject:@([H1Style getStyleType])] || - [previousActiveStyles - containsObject:@([H2Style getStyleType])] || - [previousActiveStyles - containsObject:@([H3Style getStyleType])]) { - // do nothing, heading closing tag has already ben appended + } else if( + [previousActiveStyles containsObject:@([H1Style getStyleType])] || + [previousActiveStyles containsObject:@([H2Style getStyleType])] || + [previousActiveStyles containsObject:@([H3Style getStyleType])] || + [previousActiveStyles containsObject:@([H4Style getStyleType])] || + [previousActiveStyles containsObject:@([H5Style getStyleType])] || + [previousActiveStyles containsObject:@([H6Style getStyleType])] + ) { + // do nothing, heading closing tag has already been appended } else { [result appendString:@"

"]; } @@ -513,8 +515,13 @@ - (NSString *)tagContentForStyle:(NSNumber *)style return @"h2"; } else if ([style isEqualToNumber:@([H3Style getStyleType])]) { return @"h3"; - } else if ([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || - [style isEqualToNumber:@([OrderedListStyle getStyleType])]) { + } else if([style isEqualToNumber:@([H4Style getStyleType])]) { + return @"h4"; + } else if([style isEqualToNumber:@([H5Style getStyleType])]) { + return @"h5"; + } else if([style isEqualToNumber:@([H6Style getStyleType])]) { + return @"h6"; + } else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || [style isEqualToNumber:@([OrderedListStyle getStyleType])]) { return @"li"; } else if ([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] || [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) { @@ -724,64 +731,24 @@ - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html { trailing:YES]; // line opening tags - fixedHtml = [self stringByAddingNewlinesToTag:@"

" - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

  • " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " - inString:fixedHtml - leading:YES - trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " - inString:fixedHtml - leading:YES - trailing:NO]; - + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

  • " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
    " inString:fixedHtml leading:YES trailing:NO]; + // line closing tags - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
  • " - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:NO - trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" - inString:fixedHtml - leading:NO - trailing:YES]; - - // this is more like a hack but for some reason the last
    in - //
    and are not properly changed into zero width - // space so we do that manually here - fixedHtml = [fixedHtml - stringByReplacingOccurrencesOfString:@"
    \n
    " - withString:@"

    \u200B

    \n"]; - fixedHtml = [fixedHtml - stringByReplacingOccurrencesOfString:@"
    \n" - withString:@"

    \u200B

    \n"]; - - // replace "
    " at the end with "
    \n" if input is not empty to properly - // handle last
    in html - if ([fixedHtml hasSuffix:@"
    "] && fixedHtml.length != 4) { - fixedHtml = [fixedHtml stringByAppendingString:@"\n"]; - } + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
  • " inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; } return fixedHtml; @@ -1198,6 +1165,12 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml { [styleArr addObject:@([H2Style getStyleType])]; } else if ([tagName isEqualToString:@"h3"]) { [styleArr addObject:@([H3Style getStyleType])]; + } else if([tagName isEqualToString:@"h4"]) { + [styleArr addObject:@([H4Style getStyleType])]; + } else if([tagName isEqualToString:@"h5"]) { + [styleArr addObject:@([H5Style getStyleType])]; + } else if([tagName isEqualToString:@"h6"]) { + [styleArr addObject:@([H6Style getStyleType])]; } } else if ([tagName isEqualToString:@"ul"]) { [styleArr addObject:@([UnorderedListStyle getStyleType])]; diff --git a/ios/styles/BoldStyle.mm b/ios/styles/BoldStyle.mm index aae3b400b..c69f96eb6 100644 --- a/ios/styles/BoldStyle.mm +++ b/ios/styles/BoldStyle.mm @@ -92,18 +92,18 @@ - (void)removeTypingAttributes { } - (BOOL)boldHeadingConflictsInRange:(NSRange)range type:(StyleType)type { - if (type == H1) { - if (![_input->config h1Bold]) { - return NO; - } - } else if (type == H2) { - if (![_input->config h2Bold]) { - return NO; - } - } else if (type == H3) { - if (![_input->config h3Bold]) { - return NO; - } + if(type == H1) { + if(![_input->config h1Bold]) { return NO; } + } else if(type == H2) { + if(![_input->config h2Bold]) { return NO; } + } else if(type == H3) { + if(![_input->config h3Bold]) { return NO; } + } else if(type == H4) { + if(![_input->config h4Bold]) { return NO; } + } else if(type == H5) { + if(![_input->config h5Bold]) { return NO; } + } else if(type == H6) { + if(![_input->config h6Bold]) { return NO; } } id headingStyle = _input->stylesDict[@(type)]; @@ -113,10 +113,7 @@ - (BOOL)boldHeadingConflictsInRange:(NSRange)range type:(StyleType)type { - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range { UIFont *font = (UIFont *)value; - return font != nullptr && [font isBold] && - ![self boldHeadingConflictsInRange:range type:H1] && - ![self boldHeadingConflictsInRange:range type:H2] && - ![self boldHeadingConflictsInRange:range type:H3]; + return font != nullptr && [font isBold] && ![self boldHeadingConflictsInRange:range type:H1] && ![self boldHeadingConflictsInRange:range type:H2] && ![self boldHeadingConflictsInRange:range type:H3] && ![self boldHeadingConflictsInRange:range type:H4] && ![self boldHeadingConflictsInRange:range type:H5] && ![self boldHeadingConflictsInRange:range type:H6]; } - (BOOL)detectStyle:(NSRange)range { diff --git a/ios/styles/H4Style.mm b/ios/styles/H4Style.mm new file mode 100644 index 000000000..2c76eb37a --- /dev/null +++ b/ios/styles/H4Style.mm @@ -0,0 +1,10 @@ +#import "StyleHeaders.h" +#import "EnrichedTextInputView.h" + +@implementation H4Style ++ (StyleType)getStyleType { return H4; } +- (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h4FontSize]; } +- (BOOL)isHeadingBold { + return [((EnrichedTextInputView *)input)->config h4Bold]; +} +@end diff --git a/ios/styles/H5Style.mm b/ios/styles/H5Style.mm new file mode 100644 index 000000000..b47a69120 --- /dev/null +++ b/ios/styles/H5Style.mm @@ -0,0 +1,10 @@ +#import "StyleHeaders.h" +#import "EnrichedTextInputView.h" + +@implementation H5Style ++ (StyleType)getStyleType { return H5; } +- (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h5FontSize]; } +- (BOOL)isHeadingBold { + return [((EnrichedTextInputView *)input)->config h5Bold]; +} +@end diff --git a/ios/styles/H6Style.mm b/ios/styles/H6Style.mm new file mode 100644 index 000000000..f224e9dca --- /dev/null +++ b/ios/styles/H6Style.mm @@ -0,0 +1,10 @@ +#import "StyleHeaders.h" +#import "EnrichedTextInputView.h" + +@implementation H6Style ++ (StyleType)getStyleType { return H6; } +- (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h6FontSize]; } +- (BOOL)isHeadingBold { + return [((EnrichedTextInputView *)input)->config h6Bold]; +} +@end diff --git a/ios/styles/HeadingStyleBase.mm b/ios/styles/HeadingStyleBase.mm index 6e86672f3..7727af8c7 100644 --- a/ios/styles/HeadingStyleBase.mm +++ b/ios/styles/HeadingStyleBase.mm @@ -6,7 +6,7 @@ @implementation HeadingStyleBase -// mock values since H1/2/3Style classes anyway are used +// mock values since H1/2/3/4/5/6Style classes anyway are used + (StyleType)getStyleType { return None; } diff --git a/ios/utils/StyleHeaders.h b/ios/utils/StyleHeaders.h index 6384f3852..dc848eff0 100644 --- a/ios/utils/StyleHeaders.h +++ b/ios/utils/StyleHeaders.h @@ -69,7 +69,16 @@ @interface H3Style : HeadingStyleBase @end -@interface UnorderedListStyle : NSObject +@interface H4Style : HeadingStyleBase +@end + +@interface H5Style : HeadingStyleBase +@end + +@interface H6Style : HeadingStyleBase +@end + +@interface UnorderedListStyle : NSObject - (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text; - (BOOL)tryHandlingListShorcutInRange:(NSRange)range replacementText:(NSString *)text; diff --git a/ios/utils/StyleTypeEnum.h b/ios/utils/StyleTypeEnum.h index d20419ddb..b19fcb087 100644 --- a/ios/utils/StyleTypeEnum.h +++ b/ios/utils/StyleTypeEnum.h @@ -10,6 +10,9 @@ typedef NS_ENUM(NSInteger, StyleType) { H1, H2, H3, + H4, + H5, + H6, Link, Mention, Image, diff --git a/src/EnrichedTextInput.tsx b/src/EnrichedTextInput.tsx index 826b8e6d9..795830d92 100644 --- a/src/EnrichedTextInput.tsx +++ b/src/EnrichedTextInput.tsx @@ -51,6 +51,9 @@ export interface EnrichedTextInputInstance extends NativeMethods { toggleH1: () => void; toggleH2: () => void; toggleH3: () => void; + toggleH4: () => void; + toggleH5: () => void; + toggleH6: () => void; toggleCodeBlock: () => void; toggleBlockQuote: () => void; toggleOrderedList: () => void; @@ -70,19 +73,18 @@ export interface OnChangeMentionEvent { text: string; } +type HeadingStyle = { + fontSize?: number; + bold?: boolean; +}; + export interface HtmlStyle { - h1?: { - fontSize?: number; - bold?: boolean; - }; - h2?: { - fontSize?: number; - bold?: boolean; - }; - h3?: { - fontSize?: number; - bold?: boolean; - }; + h1?: HeadingStyle; + h2?: HeadingStyle; + h3?: HeadingStyle; + h4?: HeadingStyle; + h5?: HeadingStyle; + h6?: HeadingStyle; blockquote?: { borderColor?: ColorValue; borderWidth?: number; @@ -282,6 +284,15 @@ export const EnrichedTextInput = ({ toggleH3: () => { Commands.toggleH3(nullthrows(nativeRef.current)); }, + toggleH4: () => { + Commands.toggleH4(nullthrows(nativeRef.current)); + }, + toggleH5: () => { + Commands.toggleH5(nullthrows(nativeRef.current)); + }, + toggleH6: () => { + Commands.toggleH6(nullthrows(nativeRef.current)); + }, toggleCodeBlock: () => { Commands.toggleCodeBlock(nullthrows(nativeRef.current)); }, diff --git a/src/EnrichedTextInputNativeComponent.ts b/src/EnrichedTextInputNativeComponent.ts index 7d1a8e932..d50d90dae 100644 --- a/src/EnrichedTextInputNativeComponent.ts +++ b/src/EnrichedTextInputNativeComponent.ts @@ -25,6 +25,9 @@ export interface OnChangeStateEvent { isH1: boolean; isH2: boolean; isH3: boolean; + isH4: boolean; + isH5: boolean; + isH6: boolean; isCodeBlock: boolean; isBlockQuote: boolean; isOrderedList: boolean; @@ -75,19 +78,18 @@ export interface MentionStyleProperties { textDecorationLine?: 'underline' | 'none'; } +type Heading = { + fontSize?: Float; + bold?: boolean; +}; + export interface HtmlStyleInternal { - h1?: { - fontSize?: Float; - bold?: boolean; - }; - h2?: { - fontSize?: Float; - bold?: boolean; - }; - h3?: { - fontSize?: Float; - bold?: boolean; - }; + h1?: Heading; + h2?: Heading; + h3?: Heading; + h4?: Heading; + h5?: Heading; + h6?: Heading; blockquote?: { borderColor?: ColorValue; borderWidth?: Float; @@ -187,6 +189,9 @@ interface NativeCommands { toggleH1: (viewRef: React.ElementRef) => void; toggleH2: (viewRef: React.ElementRef) => void; toggleH3: (viewRef: React.ElementRef) => void; + toggleH4: (viewRef: React.ElementRef) => void; + toggleH5: (viewRef: React.ElementRef) => void; + toggleH6: (viewRef: React.ElementRef) => void; toggleCodeBlock: (viewRef: React.ElementRef) => void; toggleBlockQuote: (viewRef: React.ElementRef) => void; toggleOrderedList: (viewRef: React.ElementRef) => void; @@ -237,6 +242,9 @@ export const Commands: NativeCommands = codegenNativeCommands({ 'toggleH1', 'toggleH2', 'toggleH3', + 'toggleH4', + 'toggleH5', + 'toggleH6', 'toggleCodeBlock', 'toggleBlockQuote', 'toggleOrderedList', diff --git a/src/normalizeHtmlStyle.ts b/src/normalizeHtmlStyle.ts index 245ed2e4f..03a1d6b21 100644 --- a/src/normalizeHtmlStyle.ts +++ b/src/normalizeHtmlStyle.ts @@ -18,6 +18,18 @@ const defaultStyle: Required = { fontSize: 20, bold: false, }, + h4: { + fontSize: 16, + bold: false, + }, + h5: { + fontSize: 14, + bold: false, + }, + h6: { + fontSize: 12, + bold: false, + }, blockquote: { borderColor: 'darkgray', borderWidth: 4, From 328ed061d1f240e02e65ce9b756587858dca5097 Mon Sep 17 00:00:00 2001 From: IvanIhnatsiuk Date: Tue, 6 Jan 2026 22:20:24 +0100 Subject: [PATCH 2/6] fix: rebase with latest main --- .../enriched/spans/EnrichedH4Span.kt | 4 + .../enriched/spans/EnrichedH5Span.kt | 4 + .../enriched/spans/EnrichedH6Span.kt | 4 + ios/EnrichedTextInputView.mm | 330 +++++++++++------- ios/inputParser/InputParser.mm | 142 +++++--- ios/styles/BoldStyle.mm | 44 ++- ios/styles/H4Style.mm | 10 +- ios/styles/H5Style.mm | 10 +- ios/styles/H6Style.mm | 10 +- ios/utils/StyleHeaders.h | 2 +- 10 files changed, 375 insertions(+), 185 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt index 76f649476..d33ae85d7 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt @@ -7,6 +7,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle class EnrichedH4Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h4FontSize), EnrichedHeadingSpan { + override val dependsOnHtmlStyle: Boolean = true + override fun updateDrawState(tp: TextPaint) { super.updateDrawState(tp) val bold = htmlStyle.h4Bold @@ -14,4 +16,6 @@ class EnrichedH4Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlSt tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD) } } + + override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH4Span = EnrichedH4Span(htmlStyle) } diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt index 437bddf14..f92c28c7c 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt @@ -7,6 +7,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle class EnrichedH5Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h5FontSize), EnrichedHeadingSpan { + override val dependsOnHtmlStyle: Boolean = true + override fun updateDrawState(tp: TextPaint) { super.updateDrawState(tp) val bold = htmlStyle.h5Bold @@ -14,4 +16,6 @@ class EnrichedH5Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlSt tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD) } } + + override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH5Span = EnrichedH5Span(htmlStyle) } diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt index 22c89e7d0..999afc340 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt @@ -7,6 +7,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle class EnrichedH6Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h6FontSize), EnrichedHeadingSpan { + override val dependsOnHtmlStyle: Boolean = true + override fun updateDrawState(tp: TextPaint) { super.updateDrawState(tp) val bold = htmlStyle.h6Bold @@ -14,4 +16,6 @@ class EnrichedH6Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlSt tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD) } } + + override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH6Span = EnrichedH6Span(htmlStyle) } diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 996e56452..181116514 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -86,23 +86,30 @@ - (void)setDefaults { stylesDict = @{ @([BoldStyle getStyleType]) : [[BoldStyle alloc] initWithInput:self], - @([ItalicStyle getStyleType]): [[ItalicStyle alloc] initWithInput:self], - @([UnderlineStyle getStyleType]): [[UnderlineStyle alloc] initWithInput:self], - @([StrikethroughStyle getStyleType]): [[StrikethroughStyle alloc] initWithInput:self], - @([InlineCodeStyle getStyleType]): [[InlineCodeStyle alloc] initWithInput:self], - @([LinkStyle getStyleType]): [[LinkStyle alloc] initWithInput:self], - @([MentionStyle getStyleType]): [[MentionStyle alloc] initWithInput:self], - @([H1Style getStyleType]): [[H1Style alloc] initWithInput:self], - @([H2Style getStyleType]): [[H2Style alloc] initWithInput:self], - @([H3Style getStyleType]): [[H3Style alloc] initWithInput:self], - @([H4Style getStyleType]): [[H4Style alloc] initWithInput:self], - @([H5Style getStyleType]): [[H5Style alloc] initWithInput:self], - @([H6Style getStyleType]): [[H6Style alloc] initWithInput:self], - @([UnorderedListStyle getStyleType]): [[UnorderedListStyle alloc] initWithInput:self], - @([OrderedListStyle getStyleType]): [[OrderedListStyle alloc] initWithInput:self], - @([BlockQuoteStyle getStyleType]): [[BlockQuoteStyle alloc] initWithInput:self], - @([CodeBlockStyle getStyleType]): [[CodeBlockStyle alloc] initWithInput:self], - @([ImageStyle getStyleType]): [[ImageStyle alloc] initWithInput:self] + @([ItalicStyle getStyleType]) : [[ItalicStyle alloc] initWithInput:self], + @([UnderlineStyle getStyleType]) : + [[UnderlineStyle alloc] initWithInput:self], + @([StrikethroughStyle getStyleType]) : + [[StrikethroughStyle alloc] initWithInput:self], + @([InlineCodeStyle getStyleType]) : + [[InlineCodeStyle alloc] initWithInput:self], + @([LinkStyle getStyleType]) : [[LinkStyle alloc] initWithInput:self], + @([MentionStyle getStyleType]) : [[MentionStyle alloc] initWithInput:self], + @([H1Style getStyleType]) : [[H1Style alloc] initWithInput:self], + @([H2Style getStyleType]) : [[H2Style alloc] initWithInput:self], + @([H3Style getStyleType]) : [[H3Style alloc] initWithInput:self], + @([H4Style getStyleType]) : [[H4Style alloc] initWithInput:self], + @([H5Style getStyleType]) : [[H5Style alloc] initWithInput:self], + @([H6Style getStyleType]) : [[H6Style alloc] initWithInput:self], + @([UnorderedListStyle getStyleType]) : + [[UnorderedListStyle alloc] initWithInput:self], + @([OrderedListStyle getStyleType]) : + [[OrderedListStyle alloc] initWithInput:self], + @([BlockQuoteStyle getStyleType]) : + [[BlockQuoteStyle alloc] initWithInput:self], + @([CodeBlockStyle getStyleType]) : + [[CodeBlockStyle alloc] initWithInput:self], + @([ImageStyle getStyleType]) : [[ImageStyle alloc] initWithInput:self] }; conflictingStyles = @{ @@ -110,42 +117,105 @@ - (void)setDefaults { @([ItalicStyle getStyleType]) : @[], @([UnderlineStyle getStyleType]) : @[], @([StrikethroughStyle getStyleType]) : @[], - @([InlineCodeStyle getStyleType]) : @[@([LinkStyle getStyleType]), @([MentionStyle getStyleType])], - @([LinkStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]), @([MentionStyle getStyleType])], - @([MentionStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType])], - @([H1Style getStyleType]): @[@([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([H4Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([H5Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([H6Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([UnorderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([OrderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([BlockQuoteStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType])], - @([CodeBlockStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), - @([BoldStyle getStyleType]), @([ItalicStyle getStyleType]), @([UnderlineStyle getStyleType]), @([StrikethroughStyle getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType]), @([MentionStyle getStyleType]), @([LinkStyle getStyleType])], - @([ImageStyle getStyleType]) : @[@([LinkStyle getStyleType]), @([MentionStyle getStyleType])] + @([InlineCodeStyle getStyleType]) : + @[ @([LinkStyle getStyleType]), @([MentionStyle getStyleType]) ], + @([LinkStyle getStyleType]) : @[ + @([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]), + @([MentionStyle getStyleType]) + ], + @([MentionStyle getStyleType]) : + @[ @([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]) ], + @([H1Style getStyleType]) : @[ + @([H2Style getStyleType]), @([H3Style getStyleType]), + @([H4Style getStyleType]), @([H5Style getStyleType]), + @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + ], + @([H2Style getStyleType]) : @[ + @([H1Style getStyleType]), @([H3Style getStyleType]), + @([H4Style getStyleType]), @([H5Style getStyleType]), + @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + ], + @([H3Style getStyleType]) : @[ + @([H1Style getStyleType]), @([H2Style getStyleType]), + @([H4Style getStyleType]), @([H5Style getStyleType]), + @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + ], + @([H4Style getStyleType]) : @[ + @([H1Style getStyleType]), @([H2Style getStyleType]), + @([H3Style getStyleType]), @([H5Style getStyleType]), + @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + ], + @([H5Style getStyleType]) : @[ + @([H1Style getStyleType]), @([H2Style getStyleType]), + @([H3Style getStyleType]), @([H4Style getStyleType]), + @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + ], + @([H6Style getStyleType]) : @[ + @([H1Style getStyleType]), @([H2Style getStyleType]), + @([H3Style getStyleType]), @([H4Style getStyleType]), + @([H5Style getStyleType]), @([UnorderedListStyle getStyleType]), + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + ], + @([UnorderedListStyle getStyleType]) : @[ + @([H1Style getStyleType]), @([H2Style getStyleType]), + @([H3Style getStyleType]), @([H4Style getStyleType]), + @([H5Style getStyleType]), @([H6Style getStyleType]), + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + ], + @([OrderedListStyle getStyleType]) : @[ + @([H1Style getStyleType]), @([H2Style getStyleType]), + @([H3Style getStyleType]), @([H4Style getStyleType]), + @([H5Style getStyleType]), @([H6Style getStyleType]), + @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + ], + @([BlockQuoteStyle getStyleType]) : @[ + @([H1Style getStyleType]), @([H2Style getStyleType]), + @([H3Style getStyleType]), @([H4Style getStyleType]), + @([H5Style getStyleType]), @([H6Style getStyleType]), + @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]) + ], + @([CodeBlockStyle getStyleType]) : @[ + @([H1Style getStyleType]), @([H2Style getStyleType]), + @([H3Style getStyleType]), @([H4Style getStyleType]), + @([H5Style getStyleType]), @([H6Style getStyleType]), + @([BoldStyle getStyleType]), @([ItalicStyle getStyleType]), + @([UnderlineStyle getStyleType]), @([StrikethroughStyle getStyleType]), + @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), + @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType]), + @([MentionStyle getStyleType]), @([LinkStyle getStyleType]) + ], + @([ImageStyle getStyleType]) : + @[ @([LinkStyle getStyleType]), @([MentionStyle getStyleType]) ] }; blockingStyles = @{ - @([BoldStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], - @([ItalicStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], - @([UnderlineStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], - @([StrikethroughStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], - @([InlineCodeStyle getStyleType]) : @[@([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType])], - @([LinkStyle getStyleType]): @[@([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType])], - @([MentionStyle getStyleType]): @[@([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType])], - @([H1Style getStyleType]): @[], - @([H2Style getStyleType]): @[], - @([H3Style getStyleType]): @[], - @([H4Style getStyleType]): @[], - @([H5Style getStyleType]): @[], - @([H6Style getStyleType]): @[], - @([UnorderedListStyle getStyleType]): @[], - @([OrderedListStyle getStyleType]): @[], - @([BlockQuoteStyle getStyleType]): @[], - @([CodeBlockStyle getStyleType]): @[], - @([ImageStyle getStyleType]) : @[@([InlineCodeStyle getStyleType])] + @([BoldStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ], + @([ItalicStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ], + @([UnderlineStyle getStyleType]) : @[ @([CodeBlockStyle getStyleType]) ], + @([StrikethroughStyle getStyleType]) : + @[ @([CodeBlockStyle getStyleType]) ], + @([InlineCodeStyle getStyleType]) : + @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ], + @([LinkStyle getStyleType]) : + @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ], + @([MentionStyle getStyleType]) : + @[ @([CodeBlockStyle getStyleType]), @([ImageStyle getStyleType]) ], + @([H1Style getStyleType]) : @[], + @([H2Style getStyleType]) : @[], + @([H3Style getStyleType]) : @[], + @([H4Style getStyleType]) : @[], + @([H5Style getStyleType]) : @[], + @([H6Style getStyleType]) : @[], + @([UnorderedListStyle getStyleType]) : @[], + @([OrderedListStyle getStyleType]) : @[], + @([BlockQuoteStyle getStyleType]) : @[], + @([CodeBlockStyle getStyleType]) : @[], + @([ImageStyle getStyleType]) : @[ @([InlineCodeStyle getStyleType]) ] }; parser = [[InputParser alloc] initWithInput:self]; @@ -277,40 +347,46 @@ - (void)updateProps:(Props::Shared const &)props [newConfig setH3Bold:newViewProps.htmlStyle.h3.bold]; stylePropChanged = YES; } - - if(newViewProps.htmlStyle.h4.fontSize != oldViewProps.htmlStyle.h4.fontSize) { + + if (newViewProps.htmlStyle.h4.fontSize != + oldViewProps.htmlStyle.h4.fontSize) { [newConfig setH4FontSize:newViewProps.htmlStyle.h4.fontSize]; stylePropChanged = YES; } - - if(newViewProps.htmlStyle.h4.bold != oldViewProps.htmlStyle.h4.bold) { + + if (newViewProps.htmlStyle.h4.bold != oldViewProps.htmlStyle.h4.bold) { [newConfig setH4Bold:newViewProps.htmlStyle.h4.bold]; stylePropChanged = YES; } - - if(newViewProps.htmlStyle.h5.fontSize != oldViewProps.htmlStyle.h5.fontSize) { + + if (newViewProps.htmlStyle.h5.fontSize != + oldViewProps.htmlStyle.h5.fontSize) { [newConfig setH5FontSize:newViewProps.htmlStyle.h5.fontSize]; stylePropChanged = YES; } - - if(newViewProps.htmlStyle.h5.bold != oldViewProps.htmlStyle.h5.bold) { + + if (newViewProps.htmlStyle.h5.bold != oldViewProps.htmlStyle.h5.bold) { [newConfig setH5Bold:newViewProps.htmlStyle.h5.bold]; stylePropChanged = YES; } - - if(newViewProps.htmlStyle.h6.fontSize != oldViewProps.htmlStyle.h6.fontSize) { + + if (newViewProps.htmlStyle.h6.fontSize != + oldViewProps.htmlStyle.h6.fontSize) { [newConfig setH6FontSize:newViewProps.htmlStyle.h6.fontSize]; stylePropChanged = YES; } - - if(newViewProps.htmlStyle.h6.bold != oldViewProps.htmlStyle.h6.bold) { + + if (newViewProps.htmlStyle.h6.bold != oldViewProps.htmlStyle.h6.bold) { [newConfig setH6Bold:newViewProps.htmlStyle.h6.bold]; stylePropChanged = YES; } - - if(newViewProps.htmlStyle.blockquote.borderColor != oldViewProps.htmlStyle.blockquote.borderColor) { - if(isColorMeaningful(newViewProps.htmlStyle.blockquote.borderColor)) { - [newConfig setBlockquoteBorderColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.blockquote.borderColor)]; + + if (newViewProps.htmlStyle.blockquote.borderColor != + oldViewProps.htmlStyle.blockquote.borderColor) { + if (isColorMeaningful(newViewProps.htmlStyle.blockquote.borderColor)) { + [newConfig setBlockquoteBorderColor:RCTUIColorFromSharedColor( + newViewProps.htmlStyle.blockquote + .borderColor)]; stylePropChanged = YES; } } @@ -863,24 +939,33 @@ - (void)tryUpdatingActiveStyles { _activeStyles = newActiveStyles; emitter->onChangeState({ - .isBold = [_activeStyles containsObject: @([BoldStyle getStyleType])], - .isItalic = [_activeStyles containsObject: @([ItalicStyle getStyleType])], - .isUnderline = [_activeStyles containsObject: @([UnderlineStyle getStyleType])], - .isStrikeThrough = [_activeStyles containsObject: @([StrikethroughStyle getStyleType])], - .isInlineCode = [_activeStyles containsObject: @([InlineCodeStyle getStyleType])], - .isLink = [_activeStyles containsObject: @([LinkStyle getStyleType])], - .isMention = [_activeStyles containsObject: @([MentionStyle getStyleType])], - .isH1 = [_activeStyles containsObject: @([H1Style getStyleType])], - .isH2 = [_activeStyles containsObject: @([H2Style getStyleType])], - .isH3 = [_activeStyles containsObject: @([H3Style getStyleType])], - .isH4 = [_activeStyles containsObject: @([H4Style getStyleType])], - .isH5 = [_activeStyles containsObject: @([H5Style getStyleType])], - .isH6 = [_activeStyles containsObject: @([H6Style getStyleType])], - .isUnorderedList = [_activeStyles containsObject: @([UnorderedListStyle getStyleType])], - .isOrderedList = [_activeStyles containsObject: @([OrderedListStyle getStyleType])], - .isBlockQuote = [_activeStyles containsObject: @([BlockQuoteStyle getStyleType])], - .isCodeBlock = [_activeStyles containsObject: @([CodeBlockStyle getStyleType])], - .isImage = [_activeStyles containsObject: @([ImageStyle getStyleType])], + .isBold = [_activeStyles containsObject:@([BoldStyle getStyleType])], + .isItalic = + [_activeStyles containsObject:@([ItalicStyle getStyleType])], + .isUnderline = + [_activeStyles containsObject:@([UnderlineStyle getStyleType])], + .isStrikeThrough = + [_activeStyles containsObject:@([StrikethroughStyle getStyleType])], + .isInlineCode = + [_activeStyles containsObject:@([InlineCodeStyle getStyleType])], + .isLink = [_activeStyles containsObject:@([LinkStyle getStyleType])], + .isMention = + [_activeStyles containsObject:@([MentionStyle getStyleType])], + .isH1 = [_activeStyles containsObject:@([H1Style getStyleType])], + .isH2 = [_activeStyles containsObject:@([H2Style getStyleType])], + .isH3 = [_activeStyles containsObject:@([H3Style getStyleType])], + .isH4 = [_activeStyles containsObject:@([H4Style getStyleType])], + .isH5 = [_activeStyles containsObject:@([H5Style getStyleType])], + .isH6 = [_activeStyles containsObject:@([H6Style getStyleType])], + .isUnorderedList = + [_activeStyles containsObject:@([UnorderedListStyle getStyleType])], + .isOrderedList = + [_activeStyles containsObject:@([OrderedListStyle getStyleType])], + .isBlockQuote = + [_activeStyles containsObject:@([BlockQuoteStyle getStyleType])], + .isCodeBlock = + [_activeStyles containsObject:@([CodeBlockStyle getStyleType])], + .isImage = [_activeStyles containsObject:@([ImageStyle getStyleType])], }); } } @@ -946,13 +1031,13 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args { [self toggleParagraphStyle:[H2Style getStyleType]]; } else if ([commandName isEqualToString:@"toggleH3"]) { [self toggleParagraphStyle:[H3Style getStyleType]]; - } else if([commandName isEqualToString:@"toggleH4"]) { + } else if ([commandName isEqualToString:@"toggleH4"]) { [self toggleParagraphStyle:[H4Style getStyleType]]; - } else if([commandName isEqualToString:@"toggleH5"]) { + } else if ([commandName isEqualToString:@"toggleH5"]) { [self toggleParagraphStyle:[H5Style getStyleType]]; - } else if([commandName isEqualToString:@"toggleH6"]) { + } else if ([commandName isEqualToString:@"toggleH6"]) { [self toggleParagraphStyle:[H6Style getStyleType]]; - } else if([commandName isEqualToString:@"toggleUnorderedList"]) { + } else if ([commandName isEqualToString:@"toggleUnorderedList"]) { [self toggleParagraphStyle:[UnorderedListStyle getStyleType]]; } else if ([commandName isEqualToString:@"toggleOrderedList"]) { [self toggleParagraphStyle:[OrderedListStyle getStyleType]]; @@ -1369,12 +1454,12 @@ - (void)anyTextMayHaveBeenModified { H4Style *h4Style = stylesDict[@([H4Style getStyleType])]; H5Style *h5Style = stylesDict[@([H5Style getStyleType])]; H6Style *h6Style = stylesDict[@([H6Style getStyleType])]; - + bool shouldHandleImproperHeadings = - h1Style != nullptr && h2Style != nullptr && h3Style != nullptr && - h4Style != nullptr && h5Style != nullptr && h6Style != nullptr; + h1Style != nullptr && h2Style != nullptr && h3Style != nullptr && + h4Style != nullptr && h5Style != nullptr && h6Style != nullptr; - if(shouldHandleImproperHeadings) { + if (shouldHandleImproperHeadings) { [h1Style handleImproperHeadings]; [h2Style handleImproperHeadings]; [h3Style handleImproperHeadings]; @@ -1534,33 +1619,40 @@ - (bool)textView:(UITextView *)textView H4Style *h4Style = stylesDict[@([H4Style getStyleType])]; H5Style *h5Style = stylesDict[@([H5Style getStyleType])]; H6Style *h6Style = stylesDict[@([H6Style getStyleType])]; - - // some of the changes these checks do could interfere with later checks and cause a crash - // so here I rely on short circuiting evaluation of the logical expression - // either way it's not possible to have two of them come off at the same time - if( - [uStyle handleBackspaceInRange:range replacementText:text] || - [uStyle tryHandlingListShorcutInRange:range replacementText:text] || - [oStyle handleBackspaceInRange:range replacementText:text] || - [oStyle tryHandlingListShorcutInRange:range replacementText:text] || - [bqStyle handleBackspaceInRange:range replacementText:text] || - [cbStyle handleBackspaceInRange:range replacementText:text] || - [linkStyle handleLeadingLinkReplacement:range replacementText:text] || - [mentionStyle handleLeadingMentionReplacement:range replacementText:text] || - [h1Style handleNewlinesInRange:range replacementText:text] || - [h2Style handleNewlinesInRange:range replacementText:text] || - [h3Style handleNewlinesInRange:range replacementText:text] || - [h4Style handleNewlinesInRange:range replacementText:text] || - [h5Style handleNewlinesInRange:range replacementText:text] || - [h6Style handleNewlinesInRange:range replacementText:text] || - [ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self] || - [ParagraphAttributesUtils handleBackspaceInRange:range replacementText:text input:self] || - // CRITICAL: This callback HAS TO be always evaluated last. - // - // This function is the "Generic Fallback": if no specific style claims the backspace action - // to change its state, only then do we proceed to physically delete the newline and merge paragraphs. - [ParagraphAttributesUtils handleNewlineBackspaceInRange:range replacementText:text input:self] - ) { + + // some of the changes these checks do could interfere with later checks and + // cause a crash so here I rely on short circuiting evaluation of the logical + // expression either way it's not possible to have two of them come off at the + // same time + if ([uStyle handleBackspaceInRange:range replacementText:text] || + [uStyle tryHandlingListShorcutInRange:range replacementText:text] || + [oStyle handleBackspaceInRange:range replacementText:text] || + [oStyle tryHandlingListShorcutInRange:range replacementText:text] || + [bqStyle handleBackspaceInRange:range replacementText:text] || + [cbStyle handleBackspaceInRange:range replacementText:text] || + [linkStyle handleLeadingLinkReplacement:range replacementText:text] || + [mentionStyle handleLeadingMentionReplacement:range + replacementText:text] || + [h1Style handleNewlinesInRange:range replacementText:text] || + [h2Style handleNewlinesInRange:range replacementText:text] || + [h3Style handleNewlinesInRange:range replacementText:text] || + [h4Style handleNewlinesInRange:range replacementText:text] || + [h5Style handleNewlinesInRange:range replacementText:text] || + [h6Style handleNewlinesInRange:range replacementText:text] || + [ZeroWidthSpaceUtils handleBackspaceInRange:range + replacementText:text + input:self] || + [ParagraphAttributesUtils handleBackspaceInRange:range + replacementText:text + input:self] || + // CRITICAL: This callback HAS TO be always evaluated last. + // + // This function is the "Generic Fallback": if no specific style claims + // the backspace action to change its state, only then do we proceed to + // physically delete the newline and merge paragraphs. + [ParagraphAttributesUtils handleParagraphStylesMergeOnBackspace:range + replacementText:text + input:self]) { [self anyTextMayHaveBeenModified]; return NO; } diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm index 09d0f0939..0ab87e7c8 100644 --- a/ios/inputParser/InputParser.mm +++ b/ios/inputParser/InputParser.mm @@ -217,17 +217,20 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { } // don't add the

    tag if some paragraph styles are present - if([currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])] || - [currentActiveStyles containsObject:@([OrderedListStyle getStyleType])] || - [currentActiveStyles containsObject:@([H1Style getStyleType])] || - [currentActiveStyles containsObject:@([H2Style getStyleType])] || - [currentActiveStyles containsObject:@([H3Style getStyleType])] || - [currentActiveStyles containsObject:@([H4Style getStyleType])] || - [currentActiveStyles containsObject:@([H5Style getStyleType])] || - [currentActiveStyles containsObject:@([H6Style getStyleType])] || - [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] || - [currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])] - ) { + if ([currentActiveStyles + containsObject:@([UnorderedListStyle getStyleType])] || + [currentActiveStyles + containsObject:@([OrderedListStyle getStyleType])] || + [currentActiveStyles containsObject:@([H1Style getStyleType])] || + [currentActiveStyles containsObject:@([H2Style getStyleType])] || + [currentActiveStyles containsObject:@([H3Style getStyleType])] || + [currentActiveStyles containsObject:@([H4Style getStyleType])] || + [currentActiveStyles containsObject:@([H5Style getStyleType])] || + [currentActiveStyles containsObject:@([H6Style getStyleType])] || + [currentActiveStyles + containsObject:@([BlockQuoteStyle getStyleType])] || + [currentActiveStyles + containsObject:@([CodeBlockStyle getStyleType])]) { [result appendString:@"\n"]; } else { [result appendString:@"\n

    "]; @@ -373,14 +376,18 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { } else if ([previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) { [result appendString:@"\n"]; - } else if( - [previousActiveStyles containsObject:@([H1Style getStyleType])] || - [previousActiveStyles containsObject:@([H2Style getStyleType])] || - [previousActiveStyles containsObject:@([H3Style getStyleType])] || - [previousActiveStyles containsObject:@([H4Style getStyleType])] || - [previousActiveStyles containsObject:@([H5Style getStyleType])] || - [previousActiveStyles containsObject:@([H6Style getStyleType])] - ) { + } else if ([previousActiveStyles + containsObject:@([H1Style getStyleType])] || + [previousActiveStyles + containsObject:@([H2Style getStyleType])] || + [previousActiveStyles + containsObject:@([H3Style getStyleType])] || + [previousActiveStyles + containsObject:@([H4Style getStyleType])] || + [previousActiveStyles + containsObject:@([H5Style getStyleType])] || + [previousActiveStyles + containsObject:@([H6Style getStyleType])]) { // do nothing, heading closing tag has already been appended } else { [result appendString:@"

    "]; @@ -515,13 +522,14 @@ - (NSString *)tagContentForStyle:(NSNumber *)style return @"h2"; } else if ([style isEqualToNumber:@([H3Style getStyleType])]) { return @"h3"; - } else if([style isEqualToNumber:@([H4Style getStyleType])]) { + } else if ([style isEqualToNumber:@([H4Style getStyleType])]) { return @"h4"; - } else if([style isEqualToNumber:@([H5Style getStyleType])]) { + } else if ([style isEqualToNumber:@([H5Style getStyleType])]) { return @"h5"; - } else if([style isEqualToNumber:@([H6Style getStyleType])]) { + } else if ([style isEqualToNumber:@([H6Style getStyleType])]) { return @"h6"; - } else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || [style isEqualToNumber:@([OrderedListStyle getStyleType])]) { + } else if ([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || + [style isEqualToNumber:@([OrderedListStyle getStyleType])]) { return @"li"; } else if ([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] || [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) { @@ -731,24 +739,72 @@ - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html { trailing:YES]; // line opening tags - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

  • " inString:fixedHtml leading:YES trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:YES trailing:NO]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
    " inString:fixedHtml leading:YES trailing:NO]; - + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

  • " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " + inString:fixedHtml + leading:YES + trailing:NO]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
    " + inString:fixedHtml + leading:YES + trailing:NO]; + // line closing tags - fixedHtml = [self stringByAddingNewlinesToTag:@"

    " inString:fixedHtml leading:NO trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
  • " inString:fixedHtml leading:NO trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"
    " inString:fixedHtml leading:NO trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; - fixedHtml = [self stringByAddingNewlinesToTag:@"" inString:fixedHtml leading:NO trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"

    " + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"
  • " + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; + fixedHtml = [self stringByAddingNewlinesToTag:@"" + inString:fixedHtml + leading:NO + trailing:YES]; } return fixedHtml; @@ -1165,11 +1221,11 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml { [styleArr addObject:@([H2Style getStyleType])]; } else if ([tagName isEqualToString:@"h3"]) { [styleArr addObject:@([H3Style getStyleType])]; - } else if([tagName isEqualToString:@"h4"]) { + } else if ([tagName isEqualToString:@"h4"]) { [styleArr addObject:@([H4Style getStyleType])]; - } else if([tagName isEqualToString:@"h5"]) { + } else if ([tagName isEqualToString:@"h5"]) { [styleArr addObject:@([H5Style getStyleType])]; - } else if([tagName isEqualToString:@"h6"]) { + } else if ([tagName isEqualToString:@"h6"]) { [styleArr addObject:@([H6Style getStyleType])]; } } else if ([tagName isEqualToString:@"ul"]) { diff --git a/ios/styles/BoldStyle.mm b/ios/styles/BoldStyle.mm index c69f96eb6..1afe0d609 100644 --- a/ios/styles/BoldStyle.mm +++ b/ios/styles/BoldStyle.mm @@ -92,18 +92,30 @@ - (void)removeTypingAttributes { } - (BOOL)boldHeadingConflictsInRange:(NSRange)range type:(StyleType)type { - if(type == H1) { - if(![_input->config h1Bold]) { return NO; } - } else if(type == H2) { - if(![_input->config h2Bold]) { return NO; } - } else if(type == H3) { - if(![_input->config h3Bold]) { return NO; } - } else if(type == H4) { - if(![_input->config h4Bold]) { return NO; } - } else if(type == H5) { - if(![_input->config h5Bold]) { return NO; } - } else if(type == H6) { - if(![_input->config h6Bold]) { return NO; } + if (type == H1) { + if (![_input->config h1Bold]) { + return NO; + } + } else if (type == H2) { + if (![_input->config h2Bold]) { + return NO; + } + } else if (type == H3) { + if (![_input->config h3Bold]) { + return NO; + } + } else if (type == H4) { + if (![_input->config h4Bold]) { + return NO; + } + } else if (type == H5) { + if (![_input->config h5Bold]) { + return NO; + } + } else if (type == H6) { + if (![_input->config h6Bold]) { + return NO; + } } id headingStyle = _input->stylesDict[@(type)]; @@ -113,7 +125,13 @@ - (BOOL)boldHeadingConflictsInRange:(NSRange)range type:(StyleType)type { - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range { UIFont *font = (UIFont *)value; - return font != nullptr && [font isBold] && ![self boldHeadingConflictsInRange:range type:H1] && ![self boldHeadingConflictsInRange:range type:H2] && ![self boldHeadingConflictsInRange:range type:H3] && ![self boldHeadingConflictsInRange:range type:H4] && ![self boldHeadingConflictsInRange:range type:H5] && ![self boldHeadingConflictsInRange:range type:H6]; + return font != nullptr && [font isBold] && + ![self boldHeadingConflictsInRange:range type:H1] && + ![self boldHeadingConflictsInRange:range type:H2] && + ![self boldHeadingConflictsInRange:range type:H3] && + ![self boldHeadingConflictsInRange:range type:H4] && + ![self boldHeadingConflictsInRange:range type:H5] && + ![self boldHeadingConflictsInRange:range type:H6]; } - (BOOL)detectStyle:(NSRange)range { diff --git a/ios/styles/H4Style.mm b/ios/styles/H4Style.mm index 2c76eb37a..a12047f40 100644 --- a/ios/styles/H4Style.mm +++ b/ios/styles/H4Style.mm @@ -1,9 +1,13 @@ -#import "StyleHeaders.h" #import "EnrichedTextInputView.h" +#import "StyleHeaders.h" @implementation H4Style -+ (StyleType)getStyleType { return H4; } -- (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h4FontSize]; } ++ (StyleType)getStyleType { + return H4; +} +- (CGFloat)getHeadingFontSize { + return [((EnrichedTextInputView *)input)->config h4FontSize]; +} - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h4Bold]; } diff --git a/ios/styles/H5Style.mm b/ios/styles/H5Style.mm index b47a69120..8c56b003a 100644 --- a/ios/styles/H5Style.mm +++ b/ios/styles/H5Style.mm @@ -1,9 +1,13 @@ -#import "StyleHeaders.h" #import "EnrichedTextInputView.h" +#import "StyleHeaders.h" @implementation H5Style -+ (StyleType)getStyleType { return H5; } -- (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h5FontSize]; } ++ (StyleType)getStyleType { + return H5; +} +- (CGFloat)getHeadingFontSize { + return [((EnrichedTextInputView *)input)->config h5FontSize]; +} - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h5Bold]; } diff --git a/ios/styles/H6Style.mm b/ios/styles/H6Style.mm index f224e9dca..e94fdfe3c 100644 --- a/ios/styles/H6Style.mm +++ b/ios/styles/H6Style.mm @@ -1,9 +1,13 @@ -#import "StyleHeaders.h" #import "EnrichedTextInputView.h" +#import "StyleHeaders.h" @implementation H6Style -+ (StyleType)getStyleType { return H6; } -- (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h6FontSize]; } ++ (StyleType)getStyleType { + return H6; +} +- (CGFloat)getHeadingFontSize { + return [((EnrichedTextInputView *)input)->config h6FontSize]; +} - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h6Bold]; } diff --git a/ios/utils/StyleHeaders.h b/ios/utils/StyleHeaders.h index dc848eff0..7a9f1ac8d 100644 --- a/ios/utils/StyleHeaders.h +++ b/ios/utils/StyleHeaders.h @@ -78,7 +78,7 @@ @interface H6Style : HeadingStyleBase @end -@interface UnorderedListStyle : NSObject +@interface UnorderedListStyle : NSObject - (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text; - (BOOL)tryHandlingListShorcutInRange:(NSRange)range replacementText:(NSString *)text; From 002b92ca8bdca0d67585bdb1cf5e86f61bb11308 Mon Sep 17 00:00:00 2001 From: IvanIhnatsiuk Date: Tue, 6 Jan 2026 22:21:06 +0100 Subject: [PATCH 3/6] fix: android lint --- .../enriched/EnrichedTextInputView.kt | 92 ++++++++-------- .../enriched/spans/EnrichedH4Span.kt | 7 +- .../enriched/spans/EnrichedH5Span.kt | 5 +- .../enriched/spans/EnrichedH6Span.kt | 5 +- .../swmansion/enriched/spans/EnrichedSpans.kt | 104 +++++++++++++----- .../enriched/utils/EnrichedParser.java | 12 +- .../enriched/utils/EnrichedSpanState.kt | 43 ++++---- 7 files changed, 162 insertions(+), 106 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt index 5cc963f0c..a9a5e0735 100644 --- a/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt @@ -488,54 +488,60 @@ class EnrichedTextInputView : AppCompatEditText { layoutManager.invalidateLayout() } - private fun removeStyle(name: String, start: Int, end: Int): Boolean { - val removed = when (name) { - EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end) - EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end) - EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end) - EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end) - EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end) - EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end) - EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end) - EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end) - EnrichedSpans.H4 -> paragraphStyles?.removeStyle(EnrichedSpans.H4, start, end) - EnrichedSpans.H5 -> paragraphStyles?.removeStyle(EnrichedSpans.H5, start, end) - EnrichedSpans.H6 -> paragraphStyles?.removeStyle(EnrichedSpans.H6, start, end) - EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end) - EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end) - EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end) - EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end) - EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end) - EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end) - EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end) - else -> false - } + private fun removeStyle( + name: String, + start: Int, + end: Int, + ): Boolean { + val removed = + when (name) { + EnrichedSpans.BOLD -> inlineStyles?.removeStyle(EnrichedSpans.BOLD, start, end) + EnrichedSpans.ITALIC -> inlineStyles?.removeStyle(EnrichedSpans.ITALIC, start, end) + EnrichedSpans.UNDERLINE -> inlineStyles?.removeStyle(EnrichedSpans.UNDERLINE, start, end) + EnrichedSpans.STRIKETHROUGH -> inlineStyles?.removeStyle(EnrichedSpans.STRIKETHROUGH, start, end) + EnrichedSpans.INLINE_CODE -> inlineStyles?.removeStyle(EnrichedSpans.INLINE_CODE, start, end) + EnrichedSpans.H1 -> paragraphStyles?.removeStyle(EnrichedSpans.H1, start, end) + EnrichedSpans.H2 -> paragraphStyles?.removeStyle(EnrichedSpans.H2, start, end) + EnrichedSpans.H3 -> paragraphStyles?.removeStyle(EnrichedSpans.H3, start, end) + EnrichedSpans.H4 -> paragraphStyles?.removeStyle(EnrichedSpans.H4, start, end) + EnrichedSpans.H5 -> paragraphStyles?.removeStyle(EnrichedSpans.H5, start, end) + EnrichedSpans.H6 -> paragraphStyles?.removeStyle(EnrichedSpans.H6, start, end) + EnrichedSpans.CODE_BLOCK -> paragraphStyles?.removeStyle(EnrichedSpans.CODE_BLOCK, start, end) + EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.removeStyle(EnrichedSpans.BLOCK_QUOTE, start, end) + EnrichedSpans.ORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.ORDERED_LIST, start, end) + EnrichedSpans.UNORDERED_LIST -> listStyles?.removeStyle(EnrichedSpans.UNORDERED_LIST, start, end) + EnrichedSpans.LINK -> parametrizedStyles?.removeStyle(EnrichedSpans.LINK, start, end) + EnrichedSpans.IMAGE -> parametrizedStyles?.removeStyle(EnrichedSpans.IMAGE, start, end) + EnrichedSpans.MENTION -> parametrizedStyles?.removeStyle(EnrichedSpans.MENTION, start, end) + else -> false + } return removed == true } private fun getTargetRange(name: String): Pair { - val result = when (name) { - EnrichedSpans.BOLD -> inlineStyles?.getStyleRange() - EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange() - EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange() - EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange() - EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange() - EnrichedSpans.H1 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H2 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H3 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H4 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H5 -> paragraphStyles?.getStyleRange() - EnrichedSpans.H6 -> paragraphStyles?.getStyleRange() - EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange() - EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange() - EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange() - EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange() - EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange() - EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange() - EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange() - else -> Pair(0, 0) - } + val result = + when (name) { + EnrichedSpans.BOLD -> inlineStyles?.getStyleRange() + EnrichedSpans.ITALIC -> inlineStyles?.getStyleRange() + EnrichedSpans.UNDERLINE -> inlineStyles?.getStyleRange() + EnrichedSpans.STRIKETHROUGH -> inlineStyles?.getStyleRange() + EnrichedSpans.INLINE_CODE -> inlineStyles?.getStyleRange() + EnrichedSpans.H1 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H2 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H3 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H4 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H5 -> paragraphStyles?.getStyleRange() + EnrichedSpans.H6 -> paragraphStyles?.getStyleRange() + EnrichedSpans.CODE_BLOCK -> paragraphStyles?.getStyleRange() + EnrichedSpans.BLOCK_QUOTE -> paragraphStyles?.getStyleRange() + EnrichedSpans.ORDERED_LIST -> listStyles?.getStyleRange() + EnrichedSpans.UNORDERED_LIST -> listStyles?.getStyleRange() + EnrichedSpans.LINK -> parametrizedStyles?.getStyleRange() + EnrichedSpans.IMAGE -> parametrizedStyles?.getStyleRange() + EnrichedSpans.MENTION -> parametrizedStyles?.getStyleRange() + else -> Pair(0, 0) + } return result ?: Pair(0, 0) } diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt index d33ae85d7..7d01fd7d4 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt @@ -6,9 +6,12 @@ import android.text.style.AbsoluteSizeSpan import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedH4Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h4FontSize), EnrichedHeadingSpan { +class EnrichedH4Span( + private val htmlStyle: HtmlStyle, +) : AbsoluteSizeSpan(htmlStyle.h4FontSize), + EnrichedHeadingSpan { override val dependsOnHtmlStyle: Boolean = true - + override fun updateDrawState(tp: TextPaint) { super.updateDrawState(tp) val bold = htmlStyle.h4Bold diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt index f92c28c7c..80abf6137 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt @@ -6,7 +6,10 @@ import android.text.style.AbsoluteSizeSpan import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedH5Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h5FontSize), EnrichedHeadingSpan { +class EnrichedH5Span( + private val htmlStyle: HtmlStyle, +) : AbsoluteSizeSpan(htmlStyle.h5FontSize), + EnrichedHeadingSpan { override val dependsOnHtmlStyle: Boolean = true override fun updateDrawState(tp: TextPaint) { diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt index 999afc340..85a4f7cfb 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt @@ -6,7 +6,10 @@ import android.text.style.AbsoluteSizeSpan import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan import com.swmansion.enriched.styles.HtmlStyle -class EnrichedH6Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h6FontSize), EnrichedHeadingSpan { +class EnrichedH6Span( + private val htmlStyle: HtmlStyle, +) : AbsoluteSizeSpan(htmlStyle.h6FontSize), + EnrichedHeadingSpan { override val dependsOnHtmlStyle: Boolean = true override fun updateDrawState(tp: TextPaint) { diff --git a/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt b/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt index 09616ca39..3589a73f7 100644 --- a/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +++ b/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt @@ -63,16 +63,17 @@ object EnrichedSpans { INLINE_CODE to BaseSpanConfig(EnrichedInlineCodeSpan::class.java), ) - val paragraphSpans: Map = mapOf( - H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false), - H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false), - H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false), - H4 to ParagraphSpanConfig(EnrichedH4Span::class.java, false), - H5 to ParagraphSpanConfig(EnrichedH5Span::class.java, false), - H6 to ParagraphSpanConfig(EnrichedH6Span::class.java, false), - BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true), - CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true), - ) + val paragraphSpans: Map = + mapOf( + H1 to ParagraphSpanConfig(EnrichedH1Span::class.java, false), + H2 to ParagraphSpanConfig(EnrichedH2Span::class.java, false), + H3 to ParagraphSpanConfig(EnrichedH3Span::class.java, false), + H4 to ParagraphSpanConfig(EnrichedH4Span::class.java, false), + H5 to ParagraphSpanConfig(EnrichedH5Span::class.java, false), + H6 to ParagraphSpanConfig(EnrichedH6Span::class.java, false), + BLOCK_QUOTE to ParagraphSpanConfig(EnrichedBlockQuoteSpan::class.java, true), + CODE_BLOCK to ParagraphSpanConfig(EnrichedCodeBlockSpan::class.java, true), + ) val listSpans: Map = mapOf( @@ -147,41 +148,84 @@ object EnrichedSpans { if (htmlStyle.h3Bold) conflictingStyles.add(BOLD) StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } + H4 -> { val conflictingStyles = mutableListOf(H1, H2, H3, H5, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) if (htmlStyle.h4Bold) conflictingStyles.add(BOLD) StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } + H5 -> { val conflictingStyles = mutableListOf(H1, H2, H3, H4, H6, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) if (htmlStyle.h5Bold) conflictingStyles.add(BOLD) StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } + H6 -> { val conflictingStyles = mutableListOf(H1, H2, H3, H4, H5, ORDERED_LIST, UNORDERED_LIST, BLOCK_QUOTE, CODE_BLOCK) if (htmlStyle.h6Bold) conflictingStyles.add(BOLD) StylesMergingConfig(conflictingStyles = conflictingStyles.toTypedArray()) } - BLOCK_QUOTE -> StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST) - ) - CODE_BLOCK -> StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, BOLD, ITALIC, UNDERLINE, STRIKETHROUGH, UNORDERED_LIST, ORDERED_LIST, BLOCK_QUOTE, INLINE_CODE) - ) - UNORDERED_LIST -> StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE) - ) - ORDERED_LIST -> StylesMergingConfig( - conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), - ) - LINK -> StylesMergingConfig( - blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION) - ) - IMAGE -> StylesMergingConfig() - MENTION -> StylesMergingConfig( - blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, LINK) - ) - else -> null + + BLOCK_QUOTE -> { + StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, CODE_BLOCK, ORDERED_LIST, UNORDERED_LIST), + ) + } + + CODE_BLOCK -> { + StylesMergingConfig( + conflictingStyles = + arrayOf( + H1, + H2, + H3, + H4, + H5, + H6, + BOLD, + ITALIC, + UNDERLINE, + STRIKETHROUGH, + UNORDERED_LIST, + ORDERED_LIST, + BLOCK_QUOTE, + INLINE_CODE, + ), + ) + } + + UNORDERED_LIST -> { + StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, ORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), + ) + } + + ORDERED_LIST -> { + StylesMergingConfig( + conflictingStyles = arrayOf(H1, H2, H3, H4, H5, H6, UNORDERED_LIST, CODE_BLOCK, BLOCK_QUOTE), + ) + } + + LINK -> { + StylesMergingConfig( + blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, MENTION), + ) + } + + IMAGE -> { + StylesMergingConfig() + } + + MENTION -> { + StylesMergingConfig( + blockingStyles = arrayOf(INLINE_CODE, CODE_BLOCK, LINK), + ) + } + + else -> { + null + } } fun isTypeContinuous(type: Class<*>): Boolean = paragraphSpans.values.find { it.clazz == type }?.isContinuous == true diff --git a/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java b/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java index 1a2e1cb55..82c89efd6 100644 --- a/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +++ b/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java @@ -861,17 +861,13 @@ private static class H2 {} private static class H3 {} - private static class H4 { - } + private static class H4 {} - private static class H5 { - } + private static class H5 {} - private static class H6 { - } + private static class H6 {} - private static class Bold { - } + private static class Bold {} private static class Italic {} diff --git a/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt b/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt index da24a797f..f22acca8f 100644 --- a/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +++ b/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt @@ -141,27 +141,28 @@ class EnrichedSpanState( } fun getStart(name: String): Int? { - val start = when (name) { - EnrichedSpans.BOLD -> boldStart - EnrichedSpans.ITALIC -> italicStart - EnrichedSpans.UNDERLINE -> underlineStart - EnrichedSpans.STRIKETHROUGH -> strikethroughStart - EnrichedSpans.INLINE_CODE -> inlineCodeStart - EnrichedSpans.H1 -> h1Start - EnrichedSpans.H2 -> h2Start - EnrichedSpans.H3 -> h3Start - EnrichedSpans.H4 -> h4Start - EnrichedSpans.H5 -> h5Start - EnrichedSpans.H6 -> h6Start - EnrichedSpans.CODE_BLOCK -> codeBlockStart - EnrichedSpans.BLOCK_QUOTE -> blockQuoteStart - EnrichedSpans.ORDERED_LIST -> orderedListStart - EnrichedSpans.UNORDERED_LIST -> unorderedListStart - EnrichedSpans.LINK -> linkStart - EnrichedSpans.IMAGE -> imageStart - EnrichedSpans.MENTION -> mentionStart - else -> null - } + val start = + when (name) { + EnrichedSpans.BOLD -> boldStart + EnrichedSpans.ITALIC -> italicStart + EnrichedSpans.UNDERLINE -> underlineStart + EnrichedSpans.STRIKETHROUGH -> strikethroughStart + EnrichedSpans.INLINE_CODE -> inlineCodeStart + EnrichedSpans.H1 -> h1Start + EnrichedSpans.H2 -> h2Start + EnrichedSpans.H3 -> h3Start + EnrichedSpans.H4 -> h4Start + EnrichedSpans.H5 -> h5Start + EnrichedSpans.H6 -> h6Start + EnrichedSpans.CODE_BLOCK -> codeBlockStart + EnrichedSpans.BLOCK_QUOTE -> blockQuoteStart + EnrichedSpans.ORDERED_LIST -> orderedListStart + EnrichedSpans.UNORDERED_LIST -> unorderedListStart + EnrichedSpans.LINK -> linkStart + EnrichedSpans.IMAGE -> imageStart + EnrichedSpans.MENTION -> mentionStart + else -> null + } return start } From f5b4597eba1623ca60c4cadfb34e21a2bcd9266b Mon Sep 17 00:00:00 2001 From: Igor Furgala Date: Wed, 7 Jan 2026 11:59:35 +0100 Subject: [PATCH 4/6] fix: handle html style update - android --- .../java/com/swmansion/enriched/styles/HtmlStyle.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt b/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt index 66043b932..cb36ad877 100644 --- a/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt @@ -268,6 +268,12 @@ class HtmlStyle { h2Bold == other.h2Bold && h3FontSize == other.h3FontSize && h3Bold == other.h3Bold && + h4FontSize == other.h4FontSize && + h4Bold == other.h4Bold && + h5FontSize == other.h5FontSize && + h5Bold == other.h5Bold && + h6FontSize == other.h6FontSize && + h6Bold == other.h6Bold && blockquoteColor == other.blockquoteColor && blockquoteBorderColor == other.blockquoteBorderColor && @@ -304,6 +310,12 @@ class HtmlStyle { result = 31 * result + h2Bold.hashCode() result = 31 * result + h3FontSize.hashCode() result = 31 * result + h3Bold.hashCode() + result = 31 * result + h4FontSize.hashCode() + result = 31 * result + h4Bold.hashCode() + result = 31 * result + h5FontSize.hashCode() + result = 31 * result + h5Bold.hashCode() + result = 31 * result + h6FontSize.hashCode() + result = 31 * result + h6Bold.hashCode() result = 31 * result + (blockquoteColor ?: 0) result = 31 * result + blockquoteBorderColor.hashCode() From ee00cca8ed8959f657320efecb28d3573f0a325d Mon Sep 17 00:00:00 2001 From: Igor Furgala Date: Wed, 7 Jan 2026 12:10:59 +0100 Subject: [PATCH 5/6] chore: update heading docs --- README.md | 4 +--- docs/API_REFERENCE.md | 49 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index f2e49eaf0..a7df24c1f 100644 --- a/README.md +++ b/README.md @@ -144,9 +144,7 @@ Supported styles: - underline - strikethrough - inline code -- H1 heading -- H2 heading -- H3 heading +- H1, H2, H3, H4, H5 and H6 headings - codeblock - blockquote - ordered list diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index f8f73f4ee..0c75f9e0c 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -156,6 +156,9 @@ interface OnChangeStateEvent { isH1: boolean; isH2: boolean; isH3: boolean; + isH4: boolean; + isH5: boolean; + isH6: boolean; isCodeBlock: boolean; isBlockQuote: boolean; isOrderedList: boolean; @@ -461,7 +464,7 @@ Toggles codeblock formatting at the current selection. toggleH1: () => void; ``` -Toggles heading 1 (h1) style at the current selection. +Toggles heading 1 (H1) style at the current selection. ### `.toggleH2()` @@ -469,7 +472,7 @@ Toggles heading 1 (h1) style at the current selection. toggleH2: () => void; ``` -Toggles heading 2 (h2) style at the current selection. +Toggles heading 2 (H2) style at the current selection. ### `.toggleH3()` @@ -477,7 +480,31 @@ Toggles heading 2 (h2) style at the current selection. toggleH3: () => void; ``` -Toggles heading 3 (h3) style at the current selection. +Toggles heading 3 (H3) style at the current selection. + +### `.toggleH4()` + +```ts +toggleH4: () => void; +``` + +Toggles heading 4 (H4) style at the current selection. + +### `.toggleH5()` + +```ts +toggleH5: () => void; +``` + +Toggles heading 5 (H5) style at the current selection. + +### `.toggleH6()` + +```ts +toggleH6: () => void; +``` + +Toggles heading 6 (H6) style at the current selection. ### `.toggleInlineCode()` @@ -545,6 +572,18 @@ interface HtmlStyle { fontSize?: number; bold?: boolean; }; + h4?: { + fontSize?: number; + bold?: boolean; + }; + h5?: { + fontSize?: number; + bold?: boolean; + }; + h6?: { + fontSize?: number; + bold?: boolean; + }; blockquote?: { borderColor?: ColorValue; borderWidth?: number; @@ -586,9 +625,9 @@ interface MentionStyleProperties { } ``` -### h1/h2/h3 (headings) +### h1/h2/h3/h4/h5/h6 (headings) -- `fontSize` is the size of the heading's font, defaults to `32`/`24`/`20` for h1/h2/h3. +- `fontSize` is the size of the heading's font. Defaults to `32` for `H1`, `24` for `H2`, `20` for `H3`, `16` for `H4`, `14` for `H5`, `12` for `H6`. - `bold` defines whether the heading should be bolded, defaults to `false`. > [!NOTE] From b9b1d8b3000f7cd2534db020eddf71f7abdddeb9 Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Wed, 7 Jan 2026 14:16:12 +0100 Subject: [PATCH 6/6] fix: iOS missing/wrongly deleted code --- ios/EnrichedTextInputView.mm | 35 +++++++++++++++++++++------------- ios/inputParser/InputParser.mm | 25 +++++++++++++++++++++--- ios/styles/H4Style.mm | 3 +++ ios/styles/H5Style.mm | 3 +++ ios/styles/H6Style.mm | 3 +++ 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 2b8da4eda..13bf2b0e8 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -129,55 +129,64 @@ - (void)setDefaults { @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), - @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([H2Style getStyleType]) : @[ @([H1Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), - @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([H3Style getStyleType]) : @[ @([H1Style getStyleType]), @([H2Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), - @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([H4Style getStyleType]) : @[ @([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), - @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([H5Style getStyleType]) : @[ @([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H6Style getStyleType]), @([UnorderedListStyle getStyleType]), - @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([H6Style getStyleType]) : @[ @([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([UnorderedListStyle getStyleType]), - @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([UnorderedListStyle getStyleType]) : @[ @([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), - @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([OrderedListStyle getStyleType]) : @[ @([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), - @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]) + @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([BlockQuoteStyle getStyleType]) : @[ @([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([H4Style getStyleType]), @([H5Style getStyleType]), @([H6Style getStyleType]), - @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]) + @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), + @([CodeBlockStyle getStyleType]) ], @([CodeBlockStyle getStyleType]) : @[ @([H1Style getStyleType]), @([H2Style getStyleType]), @@ -1454,11 +1463,11 @@ - (void)anyTextMayHaveBeenModified { H5Style *h5Style = stylesDict[@([H5Style getStyleType])]; H6Style *h6Style = stylesDict[@([H6Style getStyleType])]; - bool shouldHandleImproperHeadings = - h1Style != nullptr && h2Style != nullptr && h3Style != nullptr && - h4Style != nullptr && h5Style != nullptr && h6Style != nullptr; + bool headingStylesDefined = h1Style != nullptr && h2Style != nullptr && + h3Style != nullptr && h4Style != nullptr && + h5Style != nullptr && h6Style != nullptr; - if (shouldHandleImproperHeadings) { + if (headingStylesDefined) { [h1Style handleImproperHeadings]; [h2Style handleImproperHeadings]; [h3Style handleImproperHeadings]; diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm index 0ab87e7c8..4ca1d7d43 100644 --- a/ios/inputParser/InputParser.mm +++ b/ios/inputParser/InputParser.mm @@ -137,6 +137,9 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { [previousActiveStyles containsObject:@([H1Style getStyleType])] || [previousActiveStyles containsObject:@([H2Style getStyleType])] || [previousActiveStyles containsObject:@([H3Style getStyleType])] || + [previousActiveStyles containsObject:@([H4Style getStyleType])] || + [previousActiveStyles containsObject:@([H5Style getStyleType])] || + [previousActiveStyles containsObject:@([H6Style getStyleType])] || [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] || [previousActiveStyles @@ -805,6 +808,22 @@ - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html { inString:fixedHtml leading:NO trailing:YES]; + + // this is more like a hack but for some reason the last
    in + //
    and are not properly changed into zero width + // space so we do that manually here + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"
    \n
    " + withString:@"

    \u200B

    \n"]; + fixedHtml = [fixedHtml + stringByReplacingOccurrencesOfString:@"
    \n" + withString:@"

    \u200B

    \n"]; + + // replace "
    " at the end with "
    \n" if input is not empty to properly + // handle last
    in html + if ([fixedHtml hasSuffix:@"
    "] && fixedHtml.length != 4) { + fixedHtml = [fixedHtml stringByAppendingString:@"\n"]; + } } return fixedHtml; @@ -833,9 +852,9 @@ - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html { * you MUST add it to the `textTags` set below. */ - (NSString *)stripExtraWhiteSpacesAndNewlines:(NSString *)html { - NSSet *textTags = - [NSSet setWithObjects:@"p", @"h1", @"h2", @"h3", @"li", @"b", @"a", @"s", - @"mention", @"code", @"u", @"i", nil]; + NSSet *textTags = [NSSet setWithObjects:@"p", @"h1", @"h2", @"h3", @"h4", + @"h5", @"h6", @"li", @"b", @"a", @"s", + @"mention", @"code", @"u", @"i", nil]; NSMutableString *output = [NSMutableString stringWithCapacity:html.length]; NSMutableString *currentTagBuffer = [NSMutableString string]; diff --git a/ios/styles/H4Style.mm b/ios/styles/H4Style.mm index a12047f40..0fef5cc7d 100644 --- a/ios/styles/H4Style.mm +++ b/ios/styles/H4Style.mm @@ -5,6 +5,9 @@ @implementation H4Style + (StyleType)getStyleType { return H4; } ++ (BOOL)isParagraphStyle { + return YES; +} - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h4FontSize]; } diff --git a/ios/styles/H5Style.mm b/ios/styles/H5Style.mm index 8c56b003a..60a06abaf 100644 --- a/ios/styles/H5Style.mm +++ b/ios/styles/H5Style.mm @@ -5,6 +5,9 @@ @implementation H5Style + (StyleType)getStyleType { return H5; } ++ (BOOL)isParagraphStyle { + return YES; +} - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h5FontSize]; } diff --git a/ios/styles/H6Style.mm b/ios/styles/H6Style.mm index e94fdfe3c..80b46f810 100644 --- a/ios/styles/H6Style.mm +++ b/ios/styles/H6Style.mm @@ -5,6 +5,9 @@ @implementation H6Style + (StyleType)getStyleType { return H6; } ++ (BOOL)isParagraphStyle { + return YES; +} - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h6FontSize]; }