Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions apps/example/src/screens/playground/PlaygroundScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
EnrichedMarkdownText,
type EnrichedMarkdownTextInputInstance,
type StyleState,
type PastedImage,
} from 'react-native-enriched-markdown';
import { FormattingToolbar } from '../../components/FormattingToolbar';

Expand Down Expand Up @@ -75,6 +76,15 @@ export default function PlaygroundScreen() {
Alert.alert('Markdown', md ?? '(empty)', [{ text: 'OK' }]);
}, []);

const handlePasteImages = useCallback((images: PastedImage[]) => {
for (const img of images) {
inputRef.current?.insertImage(img.uri, {
width: Math.min(img.width, 80),
height: Math.min(img.height, 80),
});
}
}, []);

return (
<KeyboardAvoidingView
style={styles.container}
Expand Down Expand Up @@ -139,13 +149,12 @@ export default function PlaygroundScreen() {
<View style={styles.buttonRow}>
<TouchableOpacity
style={styles.button}
onPress={async () => {
const current = (await inputRef.current?.getMarkdown()) ?? '';
const md = current
? `${current}\n\n![logo](${BLOCK_IMAGE_URI})`
: `![logo](${BLOCK_IMAGE_URI})`;
inputRef.current?.setValue(md);
setMarkdown(md);
onPress={() => {
inputRef.current?.insertImage(BLOCK_IMAGE_URI, {
alt: 'logo',
width: 80,
height: 80,
});
}}
testID="insert-image-button"
>
Expand All @@ -154,9 +163,9 @@ export default function PlaygroundScreen() {
<TouchableOpacity
style={styles.button}
onPress={() => {
const md = `Enriched Markdown is a library for ![icon](${INLINE_IMAGE_URI}) React Native.`;
inputRef.current?.setValue(md);
setMarkdown(md);
inputRef.current?.insertImage(INLINE_IMAGE_URI, {
alt: 'icon',
});
}}
testID="insert-inline-image-button"
>
Expand All @@ -178,6 +187,7 @@ export default function PlaygroundScreen() {
onChangeState={setState}
onChangeMarkdown={setMarkdown}
onChangeSelection={(sel) => setHasSelection(sel.start !== sel.end)}
onPasteImages={handlePasteImages}
/>
<FormattingToolbar
state={state}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ NS_ASSUME_NONNULL_BEGIN

+ (instancetype)attachmentForURL:(NSString *)imageURL config:(StyleConfig *)config isInline:(BOOL)isInline;

+ (instancetype)inputAttachmentForURL:(NSString *)imageURL
isInline:(BOOL)isInline
inlineSize:(CGFloat)inlineSize
blockWidth:(CGFloat)blockWidth
blockHeight:(CGFloat)blockHeight
borderRadius:(CGFloat)borderRadius;

- (void)setAssociatedTextView:(ENRMPlatformTextView *)textView;

+ (void)clearAttachmentRegistry;

+ (NSCache<NSString *, RCTUIImage *> *)originalImageCache;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ @interface ENRMImageAttachment ()
@property (nonatomic, assign) BOOL isInline;
@property (nonatomic, assign) CGFloat cachedHeight;
@property (nonatomic, assign) CGFloat cachedBorderRadius;
@property (nonatomic, assign) CGFloat explicitBlockWidth;
@property (nonatomic, weak) NSTextContainer *textContainer;
@property (nonatomic, weak) ENRMPlatformTextView *textView;
@property (nonatomic, strong) RCTUIImage *originalImage;
Expand Down Expand Up @@ -76,6 +77,50 @@ + (instancetype)attachmentForURL:(NSString *)imageURL config:(StyleConfig *)conf
return attachment;
}

+ (instancetype)inputAttachmentForURL:(NSString *)imageURL
isInline:(BOOL)isInline
inlineSize:(CGFloat)inlineSize
blockWidth:(CGFloat)blockWidth
blockHeight:(CGFloat)blockHeight
borderRadius:(CGFloat)borderRadius
{
NSString *key = [NSString stringWithFormat:@"input_%@_%d_%.2f_%.2f_%.2f_%.2f", imageURL, isInline, inlineSize,
blockWidth, blockHeight, borderRadius];
ENRMImageAttachment *existing = [[self attachmentRegistry] objectForKey:key];
if (existing) {
return existing;
}
ENRMImageAttachment *attachment = [[self alloc] initWithImageURL:imageURL
isInline:isInline
inlineSize:inlineSize
blockWidth:blockWidth
blockHeight:blockHeight
borderRadius:borderRadius];
[[self attachmentRegistry] setObject:attachment forKey:key];
return attachment;
}

- (instancetype)initWithImageURL:(NSString *)imageURL
isInline:(BOOL)isInline
inlineSize:(CGFloat)inlineSize
blockWidth:(CGFloat)blockWidth
blockHeight:(CGFloat)blockHeight
borderRadius:(CGFloat)borderRadius
{
self = [super init];
if (self) {
_imageURL = imageURL;
_isInline = isInline;
_cachedHeight = isInline ? inlineSize : blockHeight;
_cachedBorderRadius = borderRadius;
_explicitBlockWidth = isInline ? 0 : blockWidth;

[self setupPlaceholder];
[self startDownloadingImage];
}
return self;
}

+ (void)clearAttachmentRegistry
{
[[self attachmentRegistry] removeAllObjects];
Expand Down Expand Up @@ -103,7 +148,14 @@ - (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer
characterIndex:(NSUInteger)characterIndex
{
CGFloat height = self.cachedHeight;
CGFloat width = self.isInline ? height : (lineFragment.size.width > 0 ? lineFragment.size.width : height);
CGFloat width;
if (self.isInline) {
width = height;
} else if (self.explicitBlockWidth > 0) {
width = self.explicitBlockWidth;
} else {
width = lineFragment.size.width > 0 ? lineFragment.size.width : height;
}

if (self.isInline) {
UIFont *appliedFont = nil;
Expand Down Expand Up @@ -269,6 +321,11 @@ - (void)refreshDisplay
}
}

- (void)setAssociatedTextView:(ENRMPlatformTextView *)textView
{
self.textView = textView;
}

- (ENRMPlatformTextView *)fetchAssociatedTextView
{
if (self.textView)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol ENRMEditAdjusting <NSObject>

- (void)adjustForEditAtLocation:(NSUInteger)location
deletedLength:(NSUInteger)deletedLength
insertedLength:(NSUInteger)insertedLength;
- (void)clearAll;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#pragma once

#import "ENRMEditAdjusting.h"
#import "ENRMFormattingRange.h"

NS_ASSUME_NONNULL_BEGIN

@interface ENRMFormattingStore : NSObject
@interface ENRMFormattingStore : NSObject <ENRMEditAdjusting>

@property (nonatomic, readonly) NSArray<ENRMFormattingRange *> *allRanges;

Expand Down
41 changes: 41 additions & 0 deletions packages/react-native-enriched-markdown/ios/input/ENRMImageStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#import "ENRMEditAdjusting.h"
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ENRMImageEntry : NSObject

@property (nonatomic, assign) NSUInteger position;
@property (nonatomic, copy) NSString *url;
@property (nonatomic, copy) NSString *alt;
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat height;
@property (nonatomic, assign) BOOL isInline;

+ (instancetype)entryWithPosition:(NSUInteger)position
url:(NSString *)url
alt:(NSString *)alt
width:(CGFloat)width
height:(CGFloat)height
isInline:(BOOL)isInline;

@end

@interface ENRMImageStore : NSObject <ENRMEditAdjusting>

@property (nonatomic, readonly) NSArray<ENRMImageEntry *> *allEntries;

- (void)addEntry:(ENRMImageEntry *)entry;
- (void)removeEntryAtPosition:(NSUInteger)position;
- (nullable ENRMImageEntry *)entryAtPosition:(NSUInteger)position;
- (void)clearAll;

- (void)adjustForEditAtLocation:(NSUInteger)editLocation
deletedLength:(NSUInteger)deletedLength
insertedLength:(NSUInteger)insertedLength;

@end

NS_ASSUME_NONNULL_END
112 changes: 112 additions & 0 deletions packages/react-native-enriched-markdown/ios/input/ENRMImageStore.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#import "ENRMImageStore.h"

@implementation ENRMImageEntry

+ (instancetype)entryWithPosition:(NSUInteger)position
url:(NSString *)url
alt:(NSString *)alt
width:(CGFloat)width
height:(CGFloat)height
isInline:(BOOL)isInline
{
ENRMImageEntry *entry = [[ENRMImageEntry alloc] init];
entry.position = position;
entry.url = url;
entry.alt = alt;
entry.width = width;
entry.height = height;
entry.isInline = isInline;
return entry;
}

@end

@implementation ENRMImageStore {
NSMutableArray<ENRMImageEntry *> *_entries;
}

- (instancetype)init
{
if (self = [super init]) {
_entries = [NSMutableArray array];
}
return self;
}

- (NSArray<ENRMImageEntry *> *)allEntries
{
return [_entries copy];
}

- (void)addEntry:(ENRMImageEntry *)entry
{
NSUInteger insertAt = 0;
for (NSUInteger i = 0; i < _entries.count; i++) {
if (_entries[i].position == entry.position) {
_entries[i] = entry;
return;
}
if (_entries[i].position > entry.position)
break;
insertAt = i + 1;
}
[_entries insertObject:entry atIndex:insertAt];
}

- (void)removeEntryAtPosition:(NSUInteger)position
{
for (NSUInteger i = 0; i < _entries.count; i++) {
if (_entries[i].position == position) {
[_entries removeObjectAtIndex:i];
return;
}
}
}

- (nullable ENRMImageEntry *)entryAtPosition:(NSUInteger)position
{
for (ENRMImageEntry *entry in _entries) {
if (entry.position == position) {
return entry;
}
}
return nil;
}

- (void)clearAll
{
[_entries removeAllObjects];
}

- (void)adjustForEditAtLocation:(NSUInteger)editLocation
deletedLength:(NSUInteger)deletedLength
insertedLength:(NSUInteger)insertedLength
{
if (deletedLength == 0 && insertedLength == 0)
return;

NSUInteger deleteEnd = editLocation + deletedLength;
NSMutableIndexSet *indexesToRemove = [NSMutableIndexSet indexSet];

for (NSUInteger i = 0; i < _entries.count; i++) {
ENRMImageEntry *entry = _entries[i];
NSUInteger pos = entry.position;

if (deletedLength > 0) {
if (pos >= editLocation && pos < deleteEnd) {
[indexesToRemove addIndex:i];
} else if (pos >= deleteEnd) {
entry.position = pos - deletedLength + insertedLength;
}
} else {
if (pos >= editLocation) {
entry.position = pos + insertedLength;
}
}
}

[indexesToRemove enumerateIndexesWithOptions:NSEnumerationReverse
usingBlock:^(NSUInteger idx, BOOL *stop) { [_entries removeObjectAtIndex:idx]; }];
}

@end
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
#pragma once

#import "ENRMFormattingRange.h"
#import "ENRMInputStyledRange.h"
#import "ENRMParseResult.h"
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ENRMParseResult : NSObject
@property (nonatomic, strong, readonly) NSString *plainText;
@property (nonatomic, strong, readonly) NSArray<ENRMFormattingRange *> *formattingRanges;
@end

@interface ENRMInputParser : NSObject

- (ENRMParseResult *)parseToPlainTextAndRanges:(NSString *)markdown;
Expand Down
Loading
Loading