react-native-enriched 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +49 -658
  2. package/ReactNativeEnriched.podspec +1 -1
  3. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +11 -6
  4. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +5 -0
  5. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +2 -0
  6. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +3 -0
  7. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +1 -1
  8. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +2 -2
  9. package/android/src/main/new_arch/CMakeLists.txt +13 -9
  10. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +2 -2
  11. package/ios/EnrichedTextInputView.mm +56 -53
  12. package/ios/internals/EnrichedTextInputViewShadowNode.mm +4 -1
  13. package/ios/styles/BlockQuoteStyle.mm +18 -23
  14. package/ios/styles/BoldStyle.mm +5 -2
  15. package/ios/styles/HeadingStyleBase.mm +37 -5
  16. package/ios/styles/InlineCodeStyle.mm +12 -8
  17. package/ios/styles/ItalicStyle.mm +5 -5
  18. package/ios/styles/MentionStyle.mm +3 -2
  19. package/ios/styles/OrderedListStyle.mm +18 -23
  20. package/ios/styles/StrikethroughStyle.mm +5 -2
  21. package/ios/styles/UnderlineStyle.mm +5 -2
  22. package/ios/styles/UnorderedListStyle.mm +18 -23
  23. package/ios/utils/ColorExtension.h +1 -0
  24. package/ios/utils/ColorExtension.mm +9 -0
  25. package/ios/utils/OccurenceUtils.h +6 -0
  26. package/ios/utils/OccurenceUtils.mm +31 -0
  27. package/ios/utils/ParagraphAttributesUtils.h +6 -0
  28. package/ios/utils/ParagraphAttributesUtils.mm +67 -0
  29. package/ios/utils/StyleHeaders.h +2 -0
  30. package/ios/utils/TextInsertionUtils.mm +2 -2
  31. package/ios/utils/ZeroWidthSpaceUtils.mm +16 -10
  32. package/package.json +4 -4
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
11
11
  s.authors = package["author"]
12
12
 
13
13
  s.platforms = { :ios => min_ios_version_supported }
14
- s.source = { :git => "https://github.com/software-mansion-labs/react-native-rich-text-editor.git", :tag => "#{s.version}" }
14
+ s.source = { :git => "https://github.com/software-mansion/react-native-enriched.git", :tag => "#{s.version}" }
15
15
 
16
16
  s.source_files = "ios/**/*.{h,m,mm,cpp}"
17
17
  s.private_header_files = "ios/**/*.h"
@@ -51,7 +51,10 @@ class EnrichedTextInputView : AppCompatEditText {
51
51
  val paragraphStyles: ParagraphStyles? = ParagraphStyles(this)
52
52
  val listStyles: ListStyles? = ListStyles(this)
53
53
  val parametrizedStyles: ParametrizedStyles? = ParametrizedStyles(this)
54
- var isSettingValue: Boolean = false
54
+ // Sometimes setting up style triggers many changes in sequence
55
+ // Eg. removing conflicting styles -> changing text -> applying spans
56
+ // In such scenario we want to prevent from handling side effects (eg. onTextChanged)
57
+ var isDuringTransaction: Boolean = false
55
58
  var isRemovingMany: Boolean = false
56
59
 
57
60
  val mentionHandler: MentionHandler? = MentionHandler(this)
@@ -204,7 +207,9 @@ class EnrichedTextInputView : AppCompatEditText {
204
207
  }
205
208
  }
206
209
 
207
- val finalText = currentText.mergeSpannables(start, end, item?.text.toString())
210
+ // Currently, we do not support pasting images
211
+ if (item?.text == null) return
212
+ val finalText = currentText.mergeSpannables(start, end, item.text.toString())
208
213
  setValue(finalText)
209
214
  parametrizedStyles?.detectAllLinks()
210
215
  }
@@ -231,7 +236,7 @@ class EnrichedTextInputView : AppCompatEditText {
231
236
 
232
237
  fun setValue(value: CharSequence?) {
233
238
  if (value == null) return
234
- isSettingValue = true
239
+ isDuringTransaction = true
235
240
 
236
241
  val newText = parseText(value)
237
242
  setText(newText)
@@ -242,7 +247,7 @@ class EnrichedTextInputView : AppCompatEditText {
242
247
  // Scroll to the last line of text
243
248
  setSelection(text?.length ?: 0)
244
249
 
245
- isSettingValue = false
250
+ isDuringTransaction = false
246
251
  }
247
252
 
248
253
  fun setAutoFocus(autoFocus: Boolean) {
@@ -450,13 +455,13 @@ class EnrichedTextInputView : AppCompatEditText {
450
455
  val end = selection?.end ?: 0
451
456
  val lengthBefore = text?.length ?: 0
452
457
 
453
- isSettingValue = true
458
+ isDuringTransaction = true
454
459
  val targetRange = getTargetRange(name)
455
460
  val removed = removeStyle(style, targetRange.first, targetRange.second)
456
461
  if (removed) {
457
462
  spanState?.setStart(style, null)
458
463
  }
459
- isSettingValue = false
464
+ isDuringTransaction = false
460
465
 
461
466
  val lengthAfter = text?.length ?: 0
462
467
  val charactersRemoved = lengthBefore - lengthAfter
@@ -8,6 +8,11 @@ class MentionHandler(private val view: EnrichedTextInputView) {
8
8
  private var previousText: String? = null
9
9
  private var previousIndicator: String? = null
10
10
 
11
+ fun reset() {
12
+ previousText = null
13
+ previousIndicator = null
14
+ }
15
+
11
16
  fun endMention() {
12
17
  val indicator = previousIndicator
13
18
  if (indicator == null) return
@@ -154,6 +154,8 @@ class HtmlStyle {
154
154
  }
155
155
 
156
156
  private fun withOpacity(color: Int, alpha: Int): Int {
157
+ // Do not apply opacity to transparent color
158
+ if (Color.alpha(color) == 0) return color
157
159
  val a = alpha.coerceIn(0, 255)
158
160
  return (color and 0x00FFFFFF) or (a shl 24)
159
161
  }
@@ -201,6 +201,7 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
201
201
  }
202
202
 
203
203
  val start = mentionStart ?: return
204
+ view.isDuringTransaction = true
204
205
  spannable.replace(start, selectionEnd, text)
205
206
 
206
207
  val span = EnrichedMentionSpan(text, indicator, attributes, view.htmlStyle)
@@ -213,6 +214,8 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
213
214
  spannable.insert(safeEnd, " ")
214
215
  }
215
216
 
217
+ view.isDuringTransaction = false
218
+ view.mentionHandler?.reset()
216
219
  view.selection.validateStyles()
217
220
  }
218
221
 
@@ -39,7 +39,7 @@ class EnrichedSelection(private val view: EnrichedTextInputView) {
39
39
  val finalStart = newStart.coerceAtMost(newEnd).coerceAtLeast(0).coerceAtMost(textLength)
40
40
  val finalEnd = newEnd.coerceAtLeast(newStart).coerceAtLeast(0).coerceAtMost(textLength)
41
41
 
42
- if (isZeroWidthSelection(finalStart, finalEnd) && !view.isSettingValue) {
42
+ if (isZeroWidthSelection(finalStart, finalEnd) && !view.isDuringTransaction) {
43
43
  view.setSelection(finalStart + 1)
44
44
  shouldValidateStyles = false
45
45
  }
@@ -18,14 +18,14 @@ class EnrichedTextWatcher(private val view: EnrichedTextInputView) : TextWatcher
18
18
  override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
19
19
  endCursorPosition = start + count
20
20
  view.layoutManager.measureSize(s ?: "")
21
- view.isRemovingMany = !view.isSettingValue && before > count + 1
21
+ view.isRemovingMany = !view.isDuringTransaction && before > count + 1
22
22
  }
23
23
 
24
24
  override fun afterTextChanged(s: Editable?) {
25
25
  if (s == null) return
26
26
  emitEvents(s)
27
27
 
28
- if (view.isSettingValue) return
28
+ if (view.isDuringTransaction) return
29
29
  applyStyles(s)
30
30
  }
31
31
 
@@ -39,15 +39,19 @@ target_link_libraries(
39
39
  ReactAndroid::reactnative
40
40
  )
41
41
 
42
- target_compile_options(
43
- ${LIB_TARGET_NAME}
44
- PRIVATE
45
- -DLOG_TAG=\"ReactNative\"
46
- -fexceptions
47
- -frtti
48
- -Wall
49
- -std=c++20
50
- )
42
+ if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 80)
43
+ target_compile_reactnative_options(${LIB_TARGET_NAME} PRIVATE)
44
+ else()
45
+ target_compile_options(
46
+ ${LIB_TARGET_NAME}
47
+ PRIVATE
48
+ -DLOG_TAG=\"ReactNative\"
49
+ -fexceptions
50
+ -frtti
51
+ -Wall
52
+ -std=c++20
53
+ )
54
+ endif()
51
55
 
52
56
  target_include_directories(
53
57
  ${CMAKE_PROJECT_NAME}
@@ -11,7 +11,7 @@ namespace facebook::react {
11
11
  class EnrichedTextInputMeasurementManager {
12
12
  public:
13
13
  EnrichedTextInputMeasurementManager(
14
- const ContextContainer::Shared& contextContainer)
14
+ const std::shared_ptr<const ContextContainer>& contextContainer)
15
15
  : contextContainer_(contextContainer) {}
16
16
 
17
17
  Size measure(
@@ -20,7 +20,7 @@ namespace facebook::react {
20
20
  LayoutConstraints layoutConstraints) const;
21
21
 
22
22
  private:
23
- const ContextContainer::Shared contextContainer_;
23
+ const std::shared_ptr<const ContextContainer> contextContainer_;
24
24
  };
25
25
 
26
26
  } // namespace facebook::react
@@ -14,6 +14,7 @@
14
14
  #import "WordsUtils.h"
15
15
  #import "LayoutManagerExtension.h"
16
16
  #import "ZeroWidthSpaceUtils.h"
17
+ #import "ParagraphAttributesUtils.h"
17
18
 
18
19
  using namespace facebook::react;
19
20
 
@@ -422,7 +423,7 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
422
423
  }
423
424
 
424
425
  // editable
425
- if(newViewProps.editable != oldViewProps.editable) {
426
+ if(newViewProps.editable != textView.editable) {
426
427
  textView.editable = newViewProps.editable;
427
428
  }
428
429
 
@@ -564,7 +565,7 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
564
565
  unichar lastChar = [currentStr.string characterAtIndex:currentStr.length-1];
565
566
  if([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
566
567
  [currentStr appendAttributedString:
567
- [[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
568
+ [[NSAttributedString alloc] initWithString:@"I" attributes:defaultTypingAttributes]
568
569
  ];
569
570
  }
570
571
  }
@@ -977,6 +978,22 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
977
978
  [mentionStyleClass manageMentionTypingAttributes];
978
979
  [mentionStyleClass manageMentionEditing];
979
980
  }
981
+
982
+ // typing attributes for empty lines selection reset
983
+ NSString *currentString = [textView.textStorage.string copy];
984
+ if(textView.selectedRange.length == 0 && [_recentlyEmittedString isEqualToString:currentString]) {
985
+ // no string change means only a selection changed with no character changes
986
+ NSRange paragraphRange = [textView.textStorage.string paragraphRangeForRange:textView.selectedRange];
987
+ if(
988
+ paragraphRange.length == 0 ||
989
+ (paragraphRange.length == 1 &&
990
+ [[NSCharacterSet newlineCharacterSet] characterIsMember:[textView.textStorage.string characterAtIndex:paragraphRange.location]])
991
+ ) {
992
+ // user changed selection to an empty line (or empty line with a newline)
993
+ // typing attributes need to be reset
994
+ textView.typingAttributes = defaultTypingAttributes;
995
+ }
996
+ }
980
997
  }
981
998
 
982
999
  - (void)handleWordModificationBasedChanges:(NSString*)word inRange:(NSRange)range {
@@ -995,15 +1012,15 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
995
1012
  return;
996
1013
  }
997
1014
 
1015
+ // zero width space adding or removal
1016
+ [ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
1017
+
998
1018
  // emptying input typing attributes management
999
1019
  if(textView.textStorage.string.length == 0 && _recentlyEmittedString.length > 0) {
1000
1020
  // reset typing attribtues
1001
1021
  textView.typingAttributes = defaultTypingAttributes;
1002
1022
  }
1003
1023
 
1004
- // zero width space removal
1005
- [ZeroWidthSpaceUtils handleZeroWidthSpacesInInput:self];
1006
-
1007
1024
  // inline code on newlines fix
1008
1025
  InlineCodeStyle *codeStyle = stylesDict[@([InlineCodeStyle getStyleType])];
1009
1026
  if(codeStyle != nullptr) {
@@ -1016,6 +1033,16 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
1016
1033
  [bqStyle manageBlockquoteColor];
1017
1034
  }
1018
1035
 
1036
+ // improper headings fix
1037
+ H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
1038
+ H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
1039
+ H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
1040
+ if(h1Style != nullptr && h2Style != nullptr && h3Style != nullptr) {
1041
+ [h1Style handleImproperHeadings];
1042
+ [h2Style handleImproperHeadings];
1043
+ [h3Style handleImproperHeadings];
1044
+ }
1045
+
1019
1046
  // placholder management
1020
1047
  if(!_placeholderLabel.hidden && textView.textStorage.string.length > 0) {
1021
1048
  [self setPlaceholderLabelShown:NO];
@@ -1102,61 +1129,37 @@ Class<RCTComponentViewProtocol> EnrichedTextInputViewCls(void) {
1102
1129
  - (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
1103
1130
  recentlyChangedRange = NSMakeRange(range.location, text.length);
1104
1131
 
1105
- BOOL rejectTextChanges = NO;
1106
-
1107
1132
  UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
1108
- if(uStyle != nullptr) {
1109
- // removing first line list fix
1110
- BOOL removedFirstLineList = [uStyle handleBackspaceInRange:range replacementText:text];
1111
-
1112
- // creating unordered list from "- "
1113
- BOOL addedShortcutList = [uStyle tryHandlingListShorcutInRange:range replacementText:text];
1114
-
1115
- // any of these changes make us reject text changes
1116
- rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
1117
- }
1118
-
1119
1133
  OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])];
1120
- if(oStyle != nullptr) {
1121
- // removing first line list fix
1122
- BOOL removedFirstLineList = [oStyle handleBackspaceInRange:range replacementText:text];
1123
-
1124
- // creating ordered list from "1."
1125
- BOOL addedShortcutList = [oStyle tryHandlingListShorcutInRange:range replacementText:text];
1126
-
1127
- // any of these changes make us reject text changes
1128
- rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
1129
- }
1130
-
1131
1134
  BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])];
1132
- if(bqStyle != nullptr) {
1133
- // removing first line quote fix
1134
- BOOL removedFirstLineQuote = [bqStyle handleBackspaceInRange:range replacementText:text];
1135
- rejectTextChanges = rejectTextChanges || removedFirstLineQuote;
1136
- }
1137
-
1138
1135
  LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
1139
- if(linkStyle != nullptr) {
1140
- // persisting link attributes fix
1141
- BOOL fixedLeadingAttributes = [linkStyle handleLeadingLinkReplacement:range replacementText:text];
1142
- rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
1143
- }
1144
-
1145
1136
  MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
1146
- if(mentionStyle != nullptr) {
1147
- // persisting mention attributes fix
1148
- BOOL fixedLeadingAttributes = [mentionStyle handleLeadingMentionReplacement:range replacementText:text];
1149
- rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
1150
- }
1151
-
1152
- // zero width space removal fix
1153
- rejectTextChanges = rejectTextChanges || [ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self];
1154
-
1155
- if(rejectTextChanges) {
1137
+ H1Style *h1Style = stylesDict[@([H1Style getStyleType])];
1138
+ H2Style *h2Style = stylesDict[@([H2Style getStyleType])];
1139
+ H3Style *h3Style = stylesDict[@([H3Style getStyleType])];
1140
+
1141
+ // some of the changes these checks do could interfere with later checks and cause a crash
1142
+ // so here I rely on short circuiting evaluation of the logical expression
1143
+ // either way it's not possible to have two of them come off at the same time
1144
+ if(
1145
+ [uStyle handleBackspaceInRange:range replacementText:text] ||
1146
+ [uStyle tryHandlingListShorcutInRange:range replacementText:text] ||
1147
+ [oStyle handleBackspaceInRange:range replacementText:text] ||
1148
+ [oStyle tryHandlingListShorcutInRange:range replacementText:text] ||
1149
+ [bqStyle handleBackspaceInRange:range replacementText:text] ||
1150
+ [linkStyle handleLeadingLinkReplacement:range replacementText:text] ||
1151
+ [mentionStyle handleLeadingMentionReplacement:range replacementText:text] ||
1152
+ [h1Style handleNewlinesInRange:range replacementText:text] ||
1153
+ [h2Style handleNewlinesInRange:range replacementText:text] ||
1154
+ [h3Style handleNewlinesInRange:range replacementText:text] ||
1155
+ [ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self] ||
1156
+ [ParagraphAttributesUtils handleBackspaceInRange:range replacementText:text input:self]
1157
+ ) {
1156
1158
  [self anyTextMayHaveBeenModified];
1159
+ return NO;
1157
1160
  }
1158
-
1159
- return !rejectTextChanges;
1161
+
1162
+ return YES;
1160
1163
  }
1161
1164
 
1162
1165
  - (void)textViewDidChangeSelection:(UITextView *)textView {
@@ -55,7 +55,10 @@ Size EnrichedTextInputViewShadowNode::measureContent(const LayoutContext& layout
55
55
  });
56
56
  }
57
57
 
58
- return {estimatedSize.width, estimatedSize.height};
58
+ return {
59
+ estimatedSize.width,
60
+ MIN(estimatedSize.height, layoutConstraints.maximumSize.height)
61
+ };
59
62
  }
60
63
  } else {
61
64
  // on the very first call there is no componentView that we can query for the component height
@@ -118,16 +118,22 @@
118
118
  [self removeAttributes:_input->textView.selectedRange];
119
119
  }
120
120
 
121
- // removing first quote line by backspacing doesn't remove typing attributes because it doesn't run textViewDidChange
122
- // so we try guessing that a point should be deleted here
123
121
  - (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text {
124
- if([self detectStyle:_input->textView.selectedRange] &&
125
- NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0)) &&
126
- [text isEqualToString:@""]
127
- ) {
122
+ if([self detectStyle:_input->textView.selectedRange] && text.length == 0) {
123
+ // backspace while the style is active
124
+
128
125
  NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange];
129
- [self removeAttributes:paragraphRange];
130
- return YES;
126
+
127
+ if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) {
128
+ // a backspace on the very first input's line quote
129
+ // it doesn't run textVieDidChange so we need to manually remove attributes
130
+ [self removeAttributes:paragraphRange];
131
+ return YES;
132
+ } else if(range.location == paragraphRange.location - 1) {
133
+ // same case in other lines; here, the removed range location will be exactly 1 less than paragraph range location
134
+ [self removeAttributes:paragraphRange];
135
+ return YES;
136
+ }
131
137
  }
132
138
  return NO;
133
139
  }
@@ -145,22 +151,11 @@
145
151
  }
146
152
  ];
147
153
  } else {
148
- NSInteger searchLocation = range.location;
149
- if(searchLocation == _input->textView.textStorage.length) {
150
- NSParagraphStyle *pStyle = _input->textView.typingAttributes[NSParagraphStyleAttributeName];
151
- return [self styleCondition:pStyle :NSMakeRange(0, 0)];
152
- }
153
-
154
- NSRange paragraphRange = NSMakeRange(0, 0);
155
- NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
156
- NSParagraphStyle *paragraph = [_input->textView.textStorage
157
- attribute:NSParagraphStyleAttributeName
158
- atIndex:searchLocation
159
- longestEffectiveRange: &paragraphRange
160
- inRange:inputRange
154
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input atIndex:range.location checkPrevious:YES
155
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
156
+ return [self styleCondition:value :range];
157
+ }
161
158
  ];
162
-
163
- return [self styleCondition:paragraph :NSMakeRange(0, 0)];
164
159
  }
165
160
  }
166
161
 
@@ -98,8 +98,11 @@
98
98
  }
99
99
  ];
100
100
  } else {
101
- UIFont *currentFontAttr = (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
102
- return [self styleCondition:currentFontAttr :range];
101
+ return [OccurenceUtils detect:NSFontAttributeName withInput:_input atIndex:range.location checkPrevious:NO
102
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
103
+ return [self styleCondition:value :range];
104
+ }
105
+ ];
103
106
  }
104
107
  }
105
108
 
@@ -2,6 +2,7 @@
2
2
  #import "EnrichedTextInputView.h"
3
3
  #import "FontExtension.h"
4
4
  #import "OccurenceUtils.h"
5
+ #import "TextInsertionUtils.h"
5
6
 
6
7
  @implementation HeadingStyleBase
7
8
 
@@ -116,11 +117,11 @@
116
117
  }
117
118
  ];
118
119
  } else {
119
- UIFont *currentFontAttr = (UIFont *)[self typedInput]->textView.typingAttributes[NSFontAttributeName];
120
- if(currentFontAttr == nullptr) {
121
- return false;
122
- }
123
- return currentFontAttr.pointSize == [self getHeadingFontSize];
120
+ return [OccurenceUtils detect:NSFontAttributeName withInput:[self typedInput] atIndex:range.location checkPrevious:YES
121
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
122
+ return [self styleCondition:value :range];
123
+ }
124
+ ];
124
125
  }
125
126
  }
126
127
 
@@ -140,5 +141,36 @@
140
141
  ];
141
142
  }
142
143
 
144
+ // used to make sure headings dont persist after a newline is placed
145
+ - (BOOL)handleNewlinesInRange:(NSRange)range replacementText:(NSString *)text {
146
+ // in a heading and a new text ends with a newline
147
+ if(
148
+ [self detectStyle:[self typedInput]->textView.selectedRange] &&
149
+ text.length > 0 &&
150
+ [[NSCharacterSet newlineCharacterSet] characterIsMember: [text characterAtIndex:text.length-1]]
151
+ ) {
152
+ // do the replacement manually
153
+ [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:[self typedInput] withSelection:YES];
154
+ // remove the attribtues at the new selection
155
+ [self removeAttributes:[self typedInput]->textView.selectedRange];
156
+ return YES;
157
+ }
158
+ return NO;
159
+ }
160
+
161
+ // backspacing a line after a heading "into" a heading will not result in the text attaining heading attributes
162
+ // so, we do it manually
163
+ - (void)handleImproperHeadings {
164
+ NSArray *occurences = [self findAllOccurences:NSMakeRange(0, [self typedInput]->textView.textStorage.string.length)];
165
+ for(StylePair *pair in occurences) {
166
+ NSRange occurenceRange = [pair.rangeValue rangeValue];
167
+ NSRange paragraphRange = [[self typedInput]->textView.textStorage.string paragraphRangeForRange:occurenceRange];
168
+ if(!NSEqualRanges(occurenceRange, paragraphRange)) {
169
+ // we have a heading but it does not span its whole paragraph - let's fix it
170
+ [self addAttributes:paragraphRange];
171
+ }
172
+ }
173
+ }
174
+
143
175
  @end
144
176
 
@@ -3,6 +3,7 @@
3
3
  #import "FontExtension.h"
4
4
  #import "OccurenceUtils.h"
5
5
  #import "ParagraphsUtils.h"
6
+ #import "ColorExtension.h"
6
7
 
7
8
  @implementation InlineCodeStyle {
8
9
  EnrichedTextInputView *_input;
@@ -33,7 +34,7 @@
33
34
  NSRange currentRange = [value rangeValue];
34
35
  [_input->textView.textStorage beginEditing];
35
36
 
36
- [_input->textView.textStorage addAttribute:NSBackgroundColorAttributeName value:[[_input->config inlineCodeBgColor] colorWithAlphaComponent:0.4] range:currentRange];
37
+ [_input->textView.textStorage addAttribute:NSBackgroundColorAttributeName value:[[_input->config inlineCodeBgColor] colorWithAlphaIfNotTransparent:0.4] range:currentRange];
37
38
  [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
38
39
  [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
39
40
  [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
@@ -41,7 +42,7 @@
41
42
  usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
42
43
  UIFont *font = (UIFont *)value;
43
44
  if(font != nullptr) {
44
- UIFont *newFont = [[_input->config monospacedFont] withFontTraits:font];
45
+ UIFont *newFont = [[[_input->config monospacedFont] withFontTraits:font] setSize:font.pointSize];
45
46
  [_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
46
47
  }
47
48
  }
@@ -53,13 +54,13 @@
53
54
 
54
55
  - (void)addTypingAttributes {
55
56
  NSMutableDictionary *newTypingAttrs = [_input->textView.typingAttributes mutableCopy];
56
- newTypingAttrs[NSBackgroundColorAttributeName] = [[_input->config inlineCodeBgColor] colorWithAlphaComponent:0.4];
57
+ newTypingAttrs[NSBackgroundColorAttributeName] = [[_input->config inlineCodeBgColor] colorWithAlphaIfNotTransparent:0.4];
57
58
  newTypingAttrs[NSForegroundColorAttributeName] = [_input->config inlineCodeFgColor];
58
59
  newTypingAttrs[NSUnderlineColorAttributeName] = [_input->config inlineCodeFgColor];
59
60
  newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config inlineCodeFgColor];
60
61
  UIFont* currentFont = (UIFont *)newTypingAttrs[NSFontAttributeName];
61
62
  if(currentFont != nullptr) {
62
- newTypingAttrs[NSFontAttributeName] = [[_input->config monospacedFont] withFontTraits:currentFont];
63
+ newTypingAttrs[NSFontAttributeName] = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize];
63
64
  }
64
65
  _input->textView.typingAttributes = newTypingAttrs;
65
66
  }
@@ -75,7 +76,7 @@
75
76
  usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
76
77
  UIFont *font = (UIFont *)value;
77
78
  if(font != nullptr) {
78
- UIFont *newFont = [[_input->config primaryFont] withFontTraits:font];
79
+ UIFont *newFont = [[[_input->config primaryFont] withFontTraits:font] setSize:font.pointSize];
79
80
  [_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
80
81
  }
81
82
  }
@@ -92,7 +93,7 @@
92
93
  newTypingAttrs[NSStrikethroughColorAttributeName] = [_input->config primaryColor];
93
94
  UIFont* currentFont = (UIFont *)newTypingAttrs[NSFontAttributeName];
94
95
  if(currentFont != nullptr) {
95
- newTypingAttrs[NSFontAttributeName] = [[_input->config primaryFont] withFontTraits:currentFont];
96
+ newTypingAttrs[NSFontAttributeName] = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize];
96
97
  }
97
98
  _input->textView.typingAttributes = newTypingAttrs;
98
99
  }
@@ -139,8 +140,11 @@
139
140
 
140
141
  return detected;
141
142
  } else {
142
- UIColor *currentBgColorAttr = (UIColor *)_input->textView.typingAttributes[NSBackgroundColorAttributeName];
143
- return [self styleCondition:currentBgColorAttr :range];
143
+ return [OccurenceUtils detect:NSBackgroundColorAttributeName withInput:_input atIndex:range.location checkPrevious:NO
144
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
145
+ return [self styleCondition:value :range];
146
+ }
147
+ ];
144
148
  }
145
149
  }
146
150
 
@@ -83,11 +83,11 @@
83
83
  }
84
84
  ];
85
85
  } else {
86
- UIFont *currentFontAttr = (UIFont *)_input->textView.typingAttributes[NSFontAttributeName];
87
- if(currentFontAttr == nullptr) {
88
- return false;
89
- }
90
- return [currentFontAttr isItalic];
86
+ return [OccurenceUtils detect:NSFontAttributeName withInput:_input atIndex:range.location checkPrevious:NO
87
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
88
+ return [self styleCondition:value :range];
89
+ }
90
+ ];
91
91
  }
92
92
  }
93
93
 
@@ -4,6 +4,7 @@
4
4
  #import "TextInsertionUtils.h"
5
5
  #import "WordsUtils.h"
6
6
  #import "UIView+React.h"
7
+ #import "ColorExtension.h"
7
8
 
8
9
  // custom NSAttributedStringKey to differentiate from links
9
10
  static NSString *const MentionAttributeName = @"MentionAttributeName";
@@ -154,7 +155,7 @@ static NSString *const MentionAttributeName = @"MentionAttributeName";
154
155
  NSForegroundColorAttributeName: styleProps.color,
155
156
  NSUnderlineColorAttributeName: styleProps.color,
156
157
  NSStrikethroughColorAttributeName: styleProps.color,
157
- NSBackgroundColorAttributeName: [styleProps.backgroundColor colorWithAlphaComponent:0.4],
158
+ NSBackgroundColorAttributeName: [styleProps.backgroundColor colorWithAlphaIfNotTransparent:0.4],
158
159
  } mutableCopy];
159
160
 
160
161
  if(styleProps.decorationLine == DecorationUnderline) {
@@ -186,7 +187,7 @@ static NSString *const MentionAttributeName = @"MentionAttributeName";
186
187
  NSForegroundColorAttributeName: styleProps.color,
187
188
  NSUnderlineColorAttributeName: styleProps.color,
188
189
  NSStrikethroughColorAttributeName: styleProps.color,
189
- NSBackgroundColorAttributeName: [styleProps.backgroundColor colorWithAlphaComponent:0.4],
190
+ NSBackgroundColorAttributeName: [styleProps.backgroundColor colorWithAlphaIfNotTransparent:0.4],
190
191
  } mutableCopy];
191
192
 
192
193
  if(styleProps.decorationLine == DecorationUnderline) {