Skip to content

Commit

Permalink
chore: resolve merge conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
BartoszGrajdek committed Jun 3, 2024
2 parents 064a46f + 6541a6e commit fea12c7
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 32 deletions.
21 changes: 12 additions & 9 deletions ios/MarkdownTextInputDecoratorView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ @implementation MarkdownTextInputDecoratorView {
#else
__weak RCTBaseTextInputView *_textInput;
#endif /* RCT_NEW_ARCH_ENABLED */
__weak UIView<RCTBackedTextInputViewProtocol> *_backedTextInputView;
__weak RCTBackedTextFieldDelegateAdapter *_adapter;
__weak RCTUITextView *_textView;
}
Expand Down Expand Up @@ -51,24 +52,24 @@ - (void)didMoveToWindow {
#ifdef RCT_NEW_ARCH_ENABLED
react_native_assert([view isKindOfClass:[RCTTextInputComponentView class]] && "Previous sibling component is not an instance of RCTTextInputComponentView.");
_textInput = (RCTTextInputComponentView *)view;
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = [_textInput valueForKey:@"_backedTextInputView"];
_backedTextInputView = [_textInput valueForKey:@"_backedTextInputView"];
#else
react_native_assert([view isKindOfClass:[RCTBaseTextInputView class]] && "Previous sibling component is not an instance of RCTBaseTextInputView.");
_textInput = (RCTBaseTextInputView *)view;
UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = _textInput.backedTextInputView;
_backedTextInputView = _textInput.backedTextInputView;
#endif /* RCT_NEW_ARCH_ENABLED */

_markdownUtils = [[RCTMarkdownUtils alloc] init];
react_native_assert(_markdownStyle != nil);
[_markdownUtils setMarkdownStyle:_markdownStyle];

[_textInput setMarkdownUtils:_markdownUtils];
if ([backedTextInputView isKindOfClass:[RCTUITextField class]]) {
RCTUITextField *textField = (RCTUITextField *)backedTextInputView;
if ([_backedTextInputView isKindOfClass:[RCTUITextField class]]) {
RCTUITextField *textField = (RCTUITextField *)_backedTextInputView;
_adapter = [textField valueForKey:@"textInputDelegateAdapter"];
[_adapter setMarkdownUtils:_markdownUtils];
} else if ([backedTextInputView isKindOfClass:[RCTUITextView class]]) {
_textView = (RCTUITextView *)backedTextInputView;
} else if ([_backedTextInputView isKindOfClass:[RCTUITextView class]]) {
_textView = (RCTUITextView *)_backedTextInputView;
[_textView setMarkdownUtils:_markdownUtils];
NSLayoutManager *layoutManager = _textView.layoutManager; // switching to TextKit 1 compatibility mode
layoutManager.allowsNonContiguousLayout = NO; // workaround for onScroll issue
Expand Down Expand Up @@ -100,11 +101,13 @@ - (void)setMarkdownStyle:(RCTMarkdownStyle *)markdownStyle
{
_markdownStyle = markdownStyle;
[_markdownUtils setMarkdownStyle:markdownStyle];

// apply new styles
#ifdef RCT_NEW_ARCH_ENABLED
[_textInput textInputDidChange]; // apply new styles
[_textInput _setAttributedString:_backedTextInputView.attributedText];
#else
[_textInput setAttributedText:_textInput.attributedText]; // apply new styles
#endif
[_textInput setAttributedText:_textInput.attributedText];
#endif /* RCT_NEW_ARCH_ENABLED */
}

@end
2 changes: 2 additions & 0 deletions ios/RCTBaseTextInputView+Markdown.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ NS_ASSUME_NONNULL_BEGIN

- (void)markdown_setAttributedText:(NSAttributedString *)attributedText;

- (BOOL)markdown_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText;

- (void)markdown_updateLocalData;

@end
Expand Down
19 changes: 19 additions & 0 deletions ios/RCTBaseTextInputView+Markdown.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ - (void)markdown_setAttributedText:(NSAttributedString *)attributedText
[self markdown_setAttributedText:attributedText];
}

- (BOOL)markdown_textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText
{
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
if (markdownUtils != nil) {
return [newText isEqualToAttributedString:oldText];
}

return [self markdown_textOf:newText equals:oldText];
}

- (void)markdown_updateLocalData
{
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
Expand Down Expand Up @@ -69,6 +79,15 @@ + (void)load
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}

{
// swizzle textOf
SEL originalSelector = @selector(textOf:equals:);
SEL swizzledSelector = @selector(markdown_textOf:equals:);
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

Expand Down
45 changes: 38 additions & 7 deletions ios/RCTLiveMarkdownModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,54 @@
@implementation RCTLiveMarkdownModule {
BOOL installed_;
std::shared_ptr<livemarkdown::MarkdownCommitHook> commitHook_;
__weak RCTSurfacePresenter *surfacePresenter_;
}

RCT_EXPORT_MODULE(@"LiveMarkdownModule")

- (NSNumber *)install {
if (!installed_) {
installed_ = YES;

RCTBridge *bridge = self.bridge;
RCTSurfacePresenter *surfacePresenter = bridge.surfacePresenter;
RCTScheduler *scheduler = [surfacePresenter scheduler];
if (!installed_ && surfacePresenter_ != nil) {
RCTScheduler *scheduler = [surfacePresenter_ scheduler];

commitHook_ =
std::make_shared<livemarkdown::MarkdownCommitHook>(scheduler.uiManager);
std::make_shared<livemarkdown::MarkdownCommitHook>(scheduler.uiManager);
installed_ = YES;
}
return @1;
}

- (void)handleJavaScriptDidLoadNotification:(NSNotification *)notification
{
surfacePresenter_ = self.bridge.surfacePresenter;
[self install];
}

- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleJavaScriptDidLoadNotification:)
name:RCTJavaScriptDidLoadNotification
object:nil];

// only within the first loading `self.bridge.surfacePresenter` exists
// during the reload `self.bridge.surfacePresenter` is null
if (self.bridge.surfacePresenter) {
surfacePresenter_ = self.bridge.surfacePresenter;
}
}

/*
* Taken from RCTNativeAnimatedTurboModule:
* This selector is invoked via BridgelessTurboModuleSetup.
*/
- (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
{
surfacePresenter_ = surfacePresenter;
}


- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeLiveMarkdownModuleSpecJSI>(
Expand All @@ -40,6 +70,7 @@ - (NSNumber *)install {

- (void)invalidate {
MarkdownShadowFamilyRegistry::reset();
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super invalidate];
}

Expand Down
45 changes: 45 additions & 0 deletions ios/RCTMarkdownUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
}
}];

RCTApplyBaselineOffset(attributedString);

[attributedString endEditing];

_prevInputString = inputString;
Expand All @@ -147,4 +149,47 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
}
}

static void RCTApplyBaselineOffset(NSMutableAttributedString *attributedText)
{
__block CGFloat maximumLineHeight = 0;

[attributedText enumerateAttribute:NSParagraphStyleAttributeName
inRange:NSMakeRange(0, attributedText.length)
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(NSParagraphStyle *paragraphStyle, __unused NSRange range, __unused BOOL *stop) {
if (!paragraphStyle) {
return;
}

maximumLineHeight = MAX(paragraphStyle.maximumLineHeight, maximumLineHeight);
}];

if (maximumLineHeight == 0) {
// `lineHeight` was not specified, nothing to do.
return;
}

__block CGFloat maximumFontLineHeight = 0;

[attributedText enumerateAttribute:NSFontAttributeName
inRange:NSMakeRange(0, attributedText.length)
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) {
if (!font) {
return;
}

maximumFontLineHeight = MAX(font.lineHeight, maximumFontLineHeight);
}];

if (maximumLineHeight < maximumFontLineHeight) {
return;
}

CGFloat baseLineOffset = (maximumLineHeight - maximumFontLineHeight) / 2.0;
[attributedText addAttribute:NSBaselineOffsetAttributeName
value:@(baseLineOffset)
range:NSMakeRange(0, attributedText.length)];
}

@end
4 changes: 3 additions & 1 deletion ios/RCTTextInputComponentView+Markdown.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ NS_ASSUME_NONNULL_BEGIN

- (void)markdown__setAttributedString:(NSAttributedString *)attributedString;

- (void)textInputDidChange;
- (BOOL)markdown__textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText;

- (void)_setAttributedString:(NSAttributedString *)attributedString;

@end

Expand Down
35 changes: 29 additions & 6 deletions ios/RCTTextInputComponentView+Markdown.mm
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,39 @@ - (void)markdown__setAttributedString:(NSAttributedString *)attributedString
[self markdown__setAttributedString:attributedString];
}

- (BOOL)markdown__textOf:(NSAttributedString *)newText equals:(NSAttributedString *)oldText
{
RCTMarkdownUtils *markdownUtils = [self getMarkdownUtils];
if (markdownUtils != nil) {
return [newText isEqualToAttributedString:oldText];
}

return [self markdown__textOf:newText equals:oldText];
}

+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [self class];
SEL originalSelector = @selector(_setAttributedString:);
SEL swizzledSelector = @selector(markdown__setAttributedString:);
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
{
// swizzle _setAttributedString
Class cls = [self class];
SEL originalSelector = @selector(_setAttributedString:);
SEL swizzledSelector = @selector(markdown__setAttributedString:);
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}

{
// swizzle _textOf
Class cls = [self class];
SEL originalSelector = @selector(_textOf:equals:);
SEL swizzledSelector = @selector(markdown__textOf:equals:);
Method originalMethod = class_getInstanceMethod(cls, originalSelector);
Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@expensify/react-native-live-markdown",
"version": "0.1.73",
"version": "0.1.77",
"description": "Drop-in replacement for React Native's TextInput component with Markdown formatting.",
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand Down
3 changes: 2 additions & 1 deletion src/MarkdownTextInput.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,8 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
}

const text = processedValue !== undefined ? processedValue : '';
parseText(divRef.current, text, processedMarkdownStyle, text.length);

parseText(divRef.current, text, processedMarkdownStyle, contentSelection.current?.end);
updateTextColor(divRef.current, value);
},
[multiline, processedMarkdownStyle, processedValue],
Expand Down
6 changes: 2 additions & 4 deletions src/web/cursorUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ function setCursorPosition(target: HTMLElement, start: number, end: number | nul

const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(range);
selection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
}

if (scrollIntoView) scrollCursorIntoView(target as HTMLInputElement);
Expand All @@ -107,8 +106,7 @@ function moveCursorToEnd(target: HTMLElement) {
if (selection) {
range.setStart(target, target.childNodes.length);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
selection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
}
}

Expand Down
7 changes: 4 additions & 3 deletions src/web/parserUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,13 @@ function handlePreBlockBackground(target: HTMLElement, markdownStyle: PartialMar
});
}

function parseText(target: HTMLElement, text: string, curosrPositionIndex: number | null, markdownStyle: PartialMarkdownStyle = {}, alwaysMoveCursorToTheEnd = false) {
function parseText(target: HTMLElement, text: string, cursorPositionIndex: number | null, markdownStyle: PartialMarkdownStyle = {}, alwaysMoveCursorToTheEnd = false) {
const targetElement = target;

let cursorPosition: number | null = curosrPositionIndex;
// in case the cursorPositionIndex is larger than text length, cursorPosition will be null, i.e: move the caret to the end
let cursorPosition: number | null = cursorPositionIndex && cursorPositionIndex <= text.length ? cursorPositionIndex : null;
const isFocused = document.activeElement === target;
if (isFocused && curosrPositionIndex === null) {
if (isFocused && cursorPositionIndex === null) {
const selection = CursorUtils.getCurrentCursorPosition(target);
cursorPosition = selection ? selection.end : null;
}
Expand Down

0 comments on commit fea12c7

Please sign in to comment.