Skip to content

Commit

Permalink
Introduce MarkdownRange class on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
tomekzaw committed Dec 4, 2024
1 parent c1a08de commit 1f6a112
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 32 deletions.
15 changes: 15 additions & 0 deletions apple/MarkdownRange.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MarkdownRange : NSObject

@property (nonatomic, strong) NSString *type;
@property (nonatomic) NSRange range;
@property (nonatomic) NSUInteger depth;

- (instancetype)initWithType:(NSString *)type range:(NSRange)range depth:(NSUInteger)depth;

NS_ASSUME_NONNULL_END

@end
15 changes: 15 additions & 0 deletions apple/MarkdownRange.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#import "MarkdownRange.h"

@implementation MarkdownRange

- (instancetype)initWithType:(NSString *)type range:(NSRange)range depth:(NSUInteger)depth {
self = [super init];
if (self) {
_type = type;
_range = range;
_depth = depth;
}
return self;
}

@end
66 changes: 38 additions & 28 deletions apple/RCTMarkdownUtils.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import <RNLiveMarkdown/RCTMarkdownUtils.h>
#import <RNLiveMarkdown/MarkdownGlobal.h>
#import <RNLiveMarkdown/MarkdownRange.h>
#import "react_native_assert.h"
#import <React/RCTAssert.h>
#import <React/RCTFont.h>
Expand Down Expand Up @@ -32,43 +33,25 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
jsi::Runtime &rt = markdownRuntime->getJSIRuntime();

auto markdownWorklet = expensify::livemarkdown::getMarkdownWorklet([_parserId intValue]);

NSMutableArray *markdownRanges = [[NSMutableArray alloc] init];

try {
const auto &text = jsi::String::createFromUtf8(rt, [inputString UTF8String]);
const auto &output = markdownRuntime->runGuarded(markdownWorklet, text);
const auto &ranges = output.asObject(rt).asArray(rt);

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:attributes];
[attributedString beginEditing];

// If the attributed string ends with underlined text, blurring the single-line input imprints the underline style across the whole string.
// It looks like a bug in iOS, as there is no underline style to be found in the attributed string, especially after formatting.
// This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem.
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleNone] range:NSMakeRange(0, attributedString.length)];

_blockquoteRangesAndLevels = [NSMutableArray new];

for (size_t i = 0, n = ranges.size(rt); i < n; ++i) {
const auto &item = ranges.getValueAtIndex(rt, i).asObject(rt);
const auto &type = item.getProperty(rt, "type").asString(rt).utf8(rt);
const auto &start = static_cast<int>(item.getProperty(rt, "start").asNumber());
const auto &length = static_cast<int>(item.getProperty(rt, "length").asNumber());
const auto &depth = item.hasProperty(rt, "depth") ? static_cast<int>(item.getProperty(rt, "depth").asNumber()) : 1;

[self applyRangeToAttributedString:attributedString type:type start:start length:length depth:depth];
NSRange range = NSMakeRange(start, length);
MarkdownRange *markdownRange = [[MarkdownRange alloc] initWithType:@(type.c_str()) range:range depth:depth];
[markdownRanges addObject:markdownRange];
}

RCTApplyBaselineOffset(attributedString);

[attributedString endEditing];

_prevInputString = inputString;
_prevAttributedString = attributedString;
_prevTextAttributes = attributes;
_prevMarkdownStyle = _markdownStyle;
_prevParserId = _parserId;

return attributedString;
} catch (const jsi::JSError &error) {
RCTLogWarn(@"[react-native-live-markdown] Incorrect schema of worklet parser output: %s", error.getMessage().c_str());
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:inputString attributes:attributes];
Expand All @@ -79,18 +62,45 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
_prevParserId = _parserId;
return attributedString;
}

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:inputString attributes:attributes];
[attributedString beginEditing];

// If the attributed string ends with underlined text, blurring the single-line input imprints the underline style across the whole string.
// It looks like a bug in iOS, as there is no underline style to be found in the attributed string, especially after formatting.
// This is a workaround that applies the NSUnderlineStyleNone to the string before iterating over ranges which resolves this problem.
[attributedString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleNone] range:NSMakeRange(0, attributedString.length)];

_blockquoteRangesAndLevels = [NSMutableArray new];

for (MarkdownRange *markdownRange in markdownRanges) {
[self applyRangeToAttributedString:attributedString
type:std::string([markdownRange.type UTF8String])
range:markdownRange.range
depth:markdownRange.depth];
}

RCTApplyBaselineOffset(attributedString);

[attributedString endEditing];

_prevInputString = inputString;
_prevAttributedString = attributedString;
_prevTextAttributes = attributes;
_prevMarkdownStyle = _markdownStyle;
_prevParserId = _parserId;

return attributedString;
}
}

- (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedString type:(const std::string)type start:(const int)start length:(const int)length depth:(const int)depth {
if (length == 0 || start + length > attributedString.length) {
- (void)applyRangeToAttributedString:(NSMutableAttributedString *)attributedString type:(const std::string)type range:(NSRange)range depth:(const int)depth {
if (range.length == 0 || range.location + range.length > attributedString.length) {
return;
}

NSRange range = NSMakeRange(start, length);

if (type == "bold" || type == "italic" || type == "code" || type == "pre" || type == "h1" || type == "emoji") {
UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:start effectiveRange:NULL];
UIFont *font = [attributedString attribute:NSFontAttributeName atIndex:range.location effectiveRange:NULL];
if (type == "bold") {
font = [RCTFont updateFont:font withWeight:@"bold"];
} else if (type == "italic") {
Expand Down
8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1497,7 +1497,7 @@ PODS:
- React-logger (= 0.75.3)
- React-perflogger (= 0.75.3)
- React-utils (= 0.75.3)
- RNLiveMarkdown (0.1.187):
- RNLiveMarkdown (0.1.188):
- DoubleConversion
- glog
- hermes-engine
Expand All @@ -1517,10 +1517,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNLiveMarkdown/newarch (= 0.1.187)
- RNLiveMarkdown/newarch (= 0.1.188)
- RNReanimated/worklets
- Yoga
- RNLiveMarkdown/newarch (0.1.187):
- RNLiveMarkdown/newarch (0.1.188):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -1897,7 +1897,7 @@ SPEC CHECKSUMS:
React-utils: f2afa6acd905ca2ce7bb8ffb4a22f7f8a12534e8
ReactCodegen: e35c23cdd36922f6d2990c6c1f1b022ade7ad74d
ReactCommon: 289214026502e6a93484f4a46bcc0efa4f3f2864
RNLiveMarkdown: ab4c8425068c97fa7cca6bbf376ed1d83e0df166
RNLiveMarkdown: c0d3ebfa32b4a6a33f1dbfc76ab9a06e516bfb1a
RNReanimated: ab6c33a61e90c4cbe5dbcbe65bd6c7cb3be167e6
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
Yoga: 1354c027ab07c7736f99a3bef16172d6f1b12b47
Expand Down

0 comments on commit 1f6a112

Please sign in to comment.