[android, ios] Parse bold/italic/code in a text field.
This commit is contained in:
parent
43945c114e
commit
72d47ff50d
|
@ -61,6 +61,9 @@ const viewConfig = {
|
||||||
maxFontSizeMultiplier: true,
|
maxFontSizeMultiplier: true,
|
||||||
disabled: true,
|
disabled: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
|
parseBasicMarkdown: true,
|
||||||
|
markdownCodeBackgroundColor: true,
|
||||||
|
markdownCodeForegroundColor: true,
|
||||||
selectionColor: true,
|
selectionColor: true,
|
||||||
adjustsFontSizeToFit: true,
|
adjustsFontSizeToFit: true,
|
||||||
minimumFontScale: true,
|
minimumFontScale: true,
|
||||||
|
@ -136,6 +139,18 @@ class TouchableText extends React.Component<Props, State> {
|
||||||
selectionColor: processColor(props.selectionColor),
|
selectionColor: processColor(props.selectionColor),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (props.markdownCodeBackgroundColor != null) {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
markdownCodeBackgroundColor: processColor(props.markdownCodeBackgroundColor),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (props.markdownCodeForegroundColor != null) {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
markdownCodeForegroundColor: processColor(props.markdownCodeForegroundColor),
|
||||||
|
};
|
||||||
|
}
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
if (Touchable.TOUCH_TARGET_DEBUG && props.onPress != null) {
|
if (Touchable.TOUCH_TARGET_DEBUG && props.onPress != null) {
|
||||||
props = {
|
props = {
|
||||||
|
|
|
@ -20,6 +20,10 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
@property (nonatomic, assign) BOOL adjustsFontSizeToFit;
|
@property (nonatomic, assign) BOOL adjustsFontSizeToFit;
|
||||||
@property (nonatomic, assign) CGFloat minimumFontScale;
|
@property (nonatomic, assign) CGFloat minimumFontScale;
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onTextLayout;
|
@property (nonatomic, copy) RCTDirectEventBlock onTextLayout;
|
||||||
|
@property (nonatomic, assign) BOOL parseBasicMarkdown;
|
||||||
|
@property (atomic, copy) UIColor* markdownCodeBackgroundColor;
|
||||||
|
@property (atomic, copy) UIColor* markdownCodeForegroundColor;
|
||||||
|
|
||||||
|
|
||||||
- (void)uiManagerWillPerformMounting;
|
- (void)uiManagerWillPerformMounting;
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,190 @@
|
||||||
[attributedText addAttribute:NSBaselineOffsetAttributeName
|
[attributedText addAttribute:NSBaselineOffsetAttributeName
|
||||||
value:@(baseLineOffset)
|
value:@(baseLineOffset)
|
||||||
range:NSMakeRange(0, attributedText.length)];
|
range:NSMakeRange(0, attributedText.length)];
|
||||||
|
|
||||||
|
if (!_parseBasicMarkdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIFontDescriptorSymbolicTraits traitZones[attributedText.string.length];
|
||||||
|
for (int i = 0; i < attributedText.string.length; i++) {
|
||||||
|
traitZones[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self parseMarkdownTag:@"```"
|
||||||
|
inText:attributedText
|
||||||
|
fontTraits:UIFontDescriptorTraitMonoSpace
|
||||||
|
fgColor:self.markdownCodeForegroundColor
|
||||||
|
bgColor:self.markdownCodeBackgroundColor
|
||||||
|
codeZones:traitZones];
|
||||||
|
|
||||||
|
[self parseMarkdownTag:@"`"
|
||||||
|
inText:attributedText
|
||||||
|
fontTraits:UIFontDescriptorTraitMonoSpace
|
||||||
|
fgColor:self.markdownCodeForegroundColor
|
||||||
|
bgColor:self.markdownCodeBackgroundColor
|
||||||
|
codeZones:traitZones];
|
||||||
|
|
||||||
|
[self parseMarkdownTag:@"*"
|
||||||
|
inText:attributedText
|
||||||
|
fontTraits:UIFontDescriptorTraitBold
|
||||||
|
fgColor:nil
|
||||||
|
bgColor:nil
|
||||||
|
codeZones:traitZones];
|
||||||
|
|
||||||
|
[self parseMarkdownTag:@"_"
|
||||||
|
inText:attributedText
|
||||||
|
fontTraits:UIFontDescriptorTraitItalic
|
||||||
|
fgColor:nil
|
||||||
|
bgColor:nil
|
||||||
|
codeZones:traitZones];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (UIFont *)fontWithTraits:(UIFontDescriptorSymbolicTraits)traits fromFont:(UIFont *)font
|
||||||
|
{
|
||||||
|
if (traits == UIFontDescriptorTraitMonoSpace) {
|
||||||
|
return [UIFont fontWithName:@"Menlo" size:font.pointSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
UIFontDescriptor *descriptor = [font.fontDescriptor fontDescriptorWithSymbolicTraits:font.fontDescriptor.symbolicTraits|traits];
|
||||||
|
if (!descriptor) {
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
return [UIFont fontWithDescriptor:descriptor size:font.pointSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(bool)isWhitespaceOrControlCharacter:(unichar)character {
|
||||||
|
return character == ' ' ||
|
||||||
|
character == '\n' ||
|
||||||
|
character == [@"\u200B" characterAtIndex:0] ||
|
||||||
|
character == '_' ||
|
||||||
|
character == '*' ||
|
||||||
|
character == '`';
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)applyFontAttributeToString:(NSMutableAttributedString *)attributedText
|
||||||
|
inRange:(NSRange)range
|
||||||
|
fontTraits:(UIFontDescriptorSymbolicTraits)fontTraits
|
||||||
|
allTraits:(UIFontDescriptorSymbolicTraits *)allTraits {
|
||||||
|
UIFont *font = [attributedText attribute:NSFontAttributeName atIndex:range.location effectiveRange:nil];
|
||||||
|
if (!font) {
|
||||||
|
font = [UIFont systemFontOfSize:18];
|
||||||
|
}
|
||||||
|
|
||||||
|
UIFontDescriptorSymbolicTraits prevTraits = allTraits[range.location];
|
||||||
|
NSInteger rangeStart = range.location;
|
||||||
|
for (NSUInteger i = range.location; i < range.location + range.length; i++) {
|
||||||
|
UIFontDescriptorSymbolicTraits currentTraits = allTraits[i];
|
||||||
|
if (currentTraits != prevTraits) {
|
||||||
|
NSRange subrange = NSMakeRange(rangeStart, i - rangeStart);
|
||||||
|
[self applyTraitsToSubrangeOfString:attributedText
|
||||||
|
subrange:subrange
|
||||||
|
prevTraits:prevTraits
|
||||||
|
font:font
|
||||||
|
incomingTraits:fontTraits];
|
||||||
|
|
||||||
|
prevTraits = currentTraits;
|
||||||
|
rangeStart = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSRange subrange = NSMakeRange(rangeStart, range.location + range.length - rangeStart);
|
||||||
|
|
||||||
|
[self applyTraitsToSubrangeOfString:attributedText
|
||||||
|
subrange:subrange
|
||||||
|
prevTraits:prevTraits
|
||||||
|
font:font
|
||||||
|
incomingTraits:fontTraits];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)applyTraitsToSubrangeOfString:(NSMutableAttributedString *)attributedText
|
||||||
|
subrange:(NSRange)subrange
|
||||||
|
prevTraits:(UIFontDescriptorSymbolicTraits)prevTraits
|
||||||
|
font:(UIFont *)font
|
||||||
|
incomingTraits:(UIFontDescriptorSymbolicTraits)incomingTraits
|
||||||
|
{
|
||||||
|
// don't override monospace, ever
|
||||||
|
UIFontDescriptorSymbolicTraits traitsToApply =
|
||||||
|
prevTraits & UIFontDescriptorTraitMonoSpace ? prevTraits : prevTraits|incomingTraits;
|
||||||
|
font = [self fontWithTraits:traitsToApply fromFont:font];
|
||||||
|
[attributedText addAttribute:NSFontAttributeName value:font range:subrange];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)parseMarkdownTag:(NSString *)tag
|
||||||
|
inText:(NSMutableAttributedString *)attributedText
|
||||||
|
fontTraits:(UIFontDescriptorSymbolicTraits)traits
|
||||||
|
fgColor:(UIColor *)fgColor
|
||||||
|
bgColor:(UIColor *)bgColor
|
||||||
|
codeZones:(UIFontDescriptorSymbolicTraits *)codeZones
|
||||||
|
{
|
||||||
|
NSInteger start = NSNotFound;
|
||||||
|
bool multilineCodeTag = [tag isEqualToString:@"```"];
|
||||||
|
|
||||||
|
for (NSInteger i = 0; i < attributedText.string.length - tag.length; i++) {
|
||||||
|
|
||||||
|
NSString *candidate = [attributedText.string substringWithRange:NSMakeRange(i, tag.length)];
|
||||||
|
|
||||||
|
bool isInCodeZone = codeZones[i] & UIFontDescriptorTraitMonoSpace;
|
||||||
|
|
||||||
|
if ([candidate isEqualToString:tag]) {
|
||||||
|
unichar nextCharacter = [attributedText.string characterAtIndex:i+tag.length];
|
||||||
|
bool followedByWhitespace = nextCharacter == ' ' || nextCharacter == '\n';
|
||||||
|
bool followedByControlCharacter = [self isWhitespaceOrControlCharacter:nextCharacter];
|
||||||
|
|
||||||
|
bool preceededByWhitespaceOrControlCharacter = i == 0 || [self isWhitespaceOrControlCharacter:[attributedText.string characterAtIndex:i - 1]];
|
||||||
|
|
||||||
|
if (start == NSNotFound && !isInCodeZone) {
|
||||||
|
if (attributedText.string.length - i > tag.length) {
|
||||||
|
if ((!followedByWhitespace || multilineCodeTag) && preceededByWhitespaceOrControlCharacter) {
|
||||||
|
start = i;
|
||||||
|
|
||||||
|
// the ``` tag needs to be outermost
|
||||||
|
if (multilineCodeTag) {
|
||||||
|
i += tag.length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(start != NSNotFound && !isInCodeZone) {
|
||||||
|
if (i - start < 2) {
|
||||||
|
// multilineCodeTag should be outermost
|
||||||
|
if(!followedByWhitespace && !multilineCodeTag) {
|
||||||
|
start = i;
|
||||||
|
} else {
|
||||||
|
start = NSNotFound;
|
||||||
|
}
|
||||||
|
} else if(followedByWhitespace || (!multilineCodeTag && followedByControlCharacter)) {
|
||||||
|
NSRange range = NSMakeRange(start, i-start+tag.length);
|
||||||
|
// iOS doesn't support merging font traits natively, so we are doing it manually here
|
||||||
|
[self applyFontAttributeToString:attributedText inRange:range fontTraits:traits allTraits:codeZones];
|
||||||
|
|
||||||
|
if (fgColor) {
|
||||||
|
[attributedText addAttribute:NSForegroundColorAttributeName value:fgColor range:range];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bgColor) {
|
||||||
|
[attributedText addAttribute:NSBackgroundColorAttributeName value:bgColor range:range];
|
||||||
|
}
|
||||||
|
|
||||||
|
// replacing control characters with 0-width spaces to hide them
|
||||||
|
NSString *replacement = @"";
|
||||||
|
for (int j = 0; j < tag.length; j++) {
|
||||||
|
replacement = [replacement stringByAppendingString:@"\u200B"];
|
||||||
|
}
|
||||||
|
[attributedText replaceCharactersInRange:NSMakeRange(i, tag.length) withString:replacement];
|
||||||
|
[attributedText replaceCharactersInRange:NSMakeRange(start, tag.length) withString:replacement];
|
||||||
|
|
||||||
|
for (NSInteger j = start; j < i + tag.length; j++) {
|
||||||
|
codeZones[j] |= traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = NSNotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ([candidate isEqualToString:@"\n"] && !multilineCodeTag) {
|
||||||
|
// resetting tags on line breaks (except ```)
|
||||||
|
start = NSNotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSAttributedString *)attributedTextWithMeasuredAttachmentsThatFitSize:(CGSize)size
|
- (NSAttributedString *)attributedTextWithMeasuredAttachmentsThatFitSize:(CGSize)size
|
||||||
|
|
|
@ -37,6 +37,11 @@ RCT_EXPORT_SHADOW_PROPERTY(onTextLayout, RCTDirectEventBlock)
|
||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(selectable, BOOL)
|
RCT_EXPORT_VIEW_PROPERTY(selectable, BOOL)
|
||||||
|
|
||||||
|
RCT_EXPORT_SHADOW_PROPERTY(parseBasicMarkdown, BOOL)
|
||||||
|
RCT_EXPORT_SHADOW_PROPERTY(markdownCodeBackgroundColor, UIColor)
|
||||||
|
RCT_EXPORT_SHADOW_PROPERTY(markdownCodeForegroundColor, UIColor)
|
||||||
|
|
||||||
|
|
||||||
- (void)setBridge:(RCTBridge *)bridge
|
- (void)setBridge:(RCTBridge *)bridge
|
||||||
{
|
{
|
||||||
[super setBridge:bridge];
|
[super setBridge:bridge];
|
||||||
|
|
|
@ -132,4 +132,9 @@ module.exports = {
|
||||||
* See https://facebook.github.io/react-native/docs/text.html#disabled
|
* See https://facebook.github.io/react-native/docs/text.html#disabled
|
||||||
*/
|
*/
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
|
||||||
|
parseBasicMarkdown: PropTypes.bool,
|
||||||
|
|
||||||
|
markdownCodeBackgroundColor: DeprecatedColorPropType,
|
||||||
|
markdownCodeForegroundColor: DeprecatedColorPropType,
|
||||||
};
|
};
|
||||||
|
|
|
@ -183,4 +183,9 @@ export type TextProps = $ReadOnly<{|
|
||||||
* See https://facebook.github.io/react-native/docs/text.html#supperhighlighting
|
* See https://facebook.github.io/react-native/docs/text.html#supperhighlighting
|
||||||
*/
|
*/
|
||||||
suppressHighlighting?: ?boolean,
|
suppressHighlighting?: ?boolean,
|
||||||
|
|
||||||
|
parseBasicMarkdown?: ?boolean,
|
||||||
|
|
||||||
|
markdownCodeBackgroundColor?: ?string,
|
||||||
|
markdownCodeForegroundColor?: ?string,
|
||||||
|}>;
|
|}>;
|
||||||
|
|
|
@ -41,6 +41,11 @@ import javax.annotation.Nullable;
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||||
|
|
||||||
|
// Android 6.x doesn't have `java.util.Supplier` so we provide one on our own.
|
||||||
|
interface Supplier<T> {
|
||||||
|
public T get();
|
||||||
|
}
|
||||||
|
|
||||||
private static final String INLINE_IMAGE_PLACEHOLDER = "I";
|
private static final String INLINE_IMAGE_PLACEHOLDER = "I";
|
||||||
public static final int UNSET = -1;
|
public static final int UNSET = -1;
|
||||||
|
|
||||||
|
@ -120,6 +125,13 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||||
}
|
}
|
||||||
int end = sb.length();
|
int end = sb.length();
|
||||||
if (end >= start) {
|
if (end >= start) {
|
||||||
|
// Markdown parsing has to be done before setting the font size
|
||||||
|
// because it uses `ReactAbsoluteTextSpan` to hide formatting characters.
|
||||||
|
// if text size is set to the whole string before that, it will
|
||||||
|
// override size for these hidden characters and they will be displayed.
|
||||||
|
// and colors.
|
||||||
|
addMarkdownParsing(textShadowNode, ops, sb);
|
||||||
|
|
||||||
if (textShadowNode.mIsColorSet) {
|
if (textShadowNode.mIsColorSet) {
|
||||||
ops.add(new SetSpanOperation(start, end, new ReactForegroundColorSpan(textShadowNode.mColor)));
|
ops.add(new SetSpanOperation(start, end, new ReactForegroundColorSpan(textShadowNode.mColor)));
|
||||||
}
|
}
|
||||||
|
@ -138,6 +150,8 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||||
new CustomLetterSpacingSpan(effectiveLetterSpacing)));
|
new CustomLetterSpacingSpan(effectiveLetterSpacing)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int effectiveFontSize = textAttributes.getEffectiveFontSize();
|
int effectiveFontSize = textAttributes.getEffectiveFontSize();
|
||||||
if (// `getEffectiveFontSize` always returns a value so don't need to check for anything like
|
if (// `getEffectiveFontSize` always returns a value so don't need to check for anything like
|
||||||
// `Float.NaN`.
|
// `Float.NaN`.
|
||||||
|
@ -192,6 +206,167 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isWhitespace(char character) {
|
||||||
|
if (character == '\u200b') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return String.valueOf(character).trim().length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isControlChar(char character) {
|
||||||
|
return character == '*' || character == '`' || character == '_' || character == '\u200b';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected static void parseMarkdownTag(String tag,
|
||||||
|
SpannableStringBuilder sb,
|
||||||
|
List<SetSpanOperation> ops,
|
||||||
|
// codeZones mark where do we format stuff as "code", we won't apply any formatting there.
|
||||||
|
boolean[] codeZones,
|
||||||
|
boolean isCode,
|
||||||
|
Supplier<ReactSpan>... spans) {
|
||||||
|
final String content = sb.toString();
|
||||||
|
|
||||||
|
int tagBeginIndex = -1;
|
||||||
|
|
||||||
|
final boolean isMultilineCodeTag = tag.compareTo("```") == 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < content.length() - tag.length(); i++) {
|
||||||
|
final String candidate = content.substring(i, i + tag.length());
|
||||||
|
|
||||||
|
final boolean followedByWhitespace = content.length() - i < tag.length() || isWhitespace(content.charAt(i + tag.length()));
|
||||||
|
final boolean followedByControlCharacter = content.length() - i < tag.length() || isControlChar(content.charAt(i + tag.length()));
|
||||||
|
final boolean preceededByWhitespace = i == 0 || isWhitespace(content.charAt(i - 1));
|
||||||
|
final boolean preceededByControlCharacter = i > 0 && isControlChar(content.charAt(i - 1));
|
||||||
|
|
||||||
|
if (candidate.compareTo(tag) == 0) {
|
||||||
|
if (tagBeginIndex < 0 && !codeZones[i] && (isMultilineCodeTag || !followedByWhitespace) && (preceededByWhitespace || preceededByControlCharacter) ) {
|
||||||
|
tagBeginIndex = i;
|
||||||
|
|
||||||
|
// make sure that ``` is the OUTERMOST tag as opposed to everything else
|
||||||
|
if (isMultilineCodeTag) {
|
||||||
|
i += tag.length() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (tagBeginIndex >= 0 && !codeZones[i]) {
|
||||||
|
if (i - tagBeginIndex < 2) {
|
||||||
|
if (!followedByWhitespace && !isMultilineCodeTag) {
|
||||||
|
tagBeginIndex = i;
|
||||||
|
} else {
|
||||||
|
tagBeginIndex = -1;
|
||||||
|
}
|
||||||
|
} else if (followedByWhitespace || (!isMultilineCodeTag && followedByControlCharacter)) {
|
||||||
|
hideCharacters(tagBeginIndex, tag.length(), sb);
|
||||||
|
hideCharacters(i, tag.length(), sb);
|
||||||
|
|
||||||
|
for (int j = 0; j < spans.length; j++) {
|
||||||
|
ops.add(new SetSpanOperation(tagBeginIndex, i + tag.length(), spans[j].get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCode) {
|
||||||
|
for (int j = tagBeginIndex; j <= i; j++) {
|
||||||
|
codeZones[j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tagBeginIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// line break stops all tags except ```
|
||||||
|
} else if (candidate.compareTo("\n") == 0 && !isMultilineCodeTag) {
|
||||||
|
tagBeginIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void addMarkdownParsing(ReactBaseTextShadowNode textShadowNode, List<SetSpanOperation> ops, SpannableStringBuilder sb) {
|
||||||
|
if (!textShadowNode.mParseBasicMarkdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb.toString().length() <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean[] codeZones = new boolean[sb.toString().length()];
|
||||||
|
|
||||||
|
parseMarkdownTag("```", sb, ops, codeZones, true,
|
||||||
|
new Supplier<ReactSpan>() {
|
||||||
|
public ReactSpan get() {
|
||||||
|
return new ReactForegroundColorSpan(textShadowNode.mMarkdownCodeForegroundColor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Supplier<ReactSpan>() {
|
||||||
|
public ReactSpan get() {
|
||||||
|
return new ReactBackgroundColorSpan(textShadowNode.mMarkdownCodeBackgroundColor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Supplier<ReactSpan>() {
|
||||||
|
public ReactSpan get() {
|
||||||
|
return new CustomStyleSpan(
|
||||||
|
textShadowNode.mFontStyle,
|
||||||
|
textShadowNode.mFontWeight,
|
||||||
|
"monospace",
|
||||||
|
textShadowNode.getThemedContext().getAssets());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
parseMarkdownTag("`", sb, ops, codeZones, true,
|
||||||
|
new Supplier<ReactSpan>() {
|
||||||
|
public ReactSpan get() {
|
||||||
|
return new ReactForegroundColorSpan(textShadowNode.mMarkdownCodeForegroundColor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Supplier<ReactSpan>() {
|
||||||
|
public ReactSpan get() {
|
||||||
|
return new ReactBackgroundColorSpan(textShadowNode.mMarkdownCodeBackgroundColor);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Supplier<ReactSpan>() {
|
||||||
|
public ReactSpan get() {
|
||||||
|
return new CustomStyleSpan(
|
||||||
|
textShadowNode.mFontStyle,
|
||||||
|
textShadowNode.mFontWeight,
|
||||||
|
"monospace",
|
||||||
|
textShadowNode.getThemedContext().getAssets());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
parseMarkdownTag("*", sb, ops, codeZones, false,
|
||||||
|
new Supplier<ReactSpan>() {
|
||||||
|
public ReactSpan get() {
|
||||||
|
return new CustomStyleSpan(
|
||||||
|
textShadowNode.mFontStyle,
|
||||||
|
Typeface.BOLD,
|
||||||
|
textShadowNode.mFontFamily,
|
||||||
|
textShadowNode.getThemedContext().getAssets());
|
||||||
|
}});
|
||||||
|
|
||||||
|
parseMarkdownTag("_", sb, ops, codeZones, false,
|
||||||
|
new Supplier<ReactSpan>() {
|
||||||
|
public ReactSpan get() {
|
||||||
|
return new CustomStyleSpan(
|
||||||
|
Typeface.ITALIC,
|
||||||
|
textShadowNode.mFontWeight,
|
||||||
|
textShadowNode.mFontFamily,
|
||||||
|
textShadowNode.getThemedContext().getAssets());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void hideCharacters(int index, int length, SpannableStringBuilder sb) {
|
||||||
|
// To keep internal consistency, we just replace control characters with
|
||||||
|
// zero-width spaces. They won't be visible at all, but the number of
|
||||||
|
// chars will stay the same
|
||||||
|
for (int i = index; i < index + length; i++) {
|
||||||
|
sb.replace(i, i + 1, "\u200b");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static Spannable spannedFromShadowNode(
|
protected static Spannable spannedFromShadowNode(
|
||||||
ReactBaseTextShadowNode textShadowNode, String text) {
|
ReactBaseTextShadowNode textShadowNode, String text) {
|
||||||
SpannableStringBuilder sb = new SpannableStringBuilder();
|
SpannableStringBuilder sb = new SpannableStringBuilder();
|
||||||
|
@ -214,6 +389,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||||
textShadowNode.mContainsImages = false;
|
textShadowNode.mContainsImages = false;
|
||||||
float heightOfTallestInlineImage = Float.NaN;
|
float heightOfTallestInlineImage = Float.NaN;
|
||||||
|
|
||||||
|
|
||||||
// While setting the Spans on the final text, we also check whether any of them are images.
|
// While setting the Spans on the final text, we also check whether any of them are images.
|
||||||
int priority = 0;
|
int priority = 0;
|
||||||
for (SetSpanOperation op : ops) {
|
for (SetSpanOperation op : ops) {
|
||||||
|
@ -306,6 +482,9 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||||
|
|
||||||
protected boolean mContainsImages = false;
|
protected boolean mContainsImages = false;
|
||||||
protected float mHeightOfTallestInlineImage = Float.NaN;
|
protected float mHeightOfTallestInlineImage = Float.NaN;
|
||||||
|
protected boolean mParseBasicMarkdown = false;
|
||||||
|
protected int mMarkdownCodeForegroundColor = Color.BLACK;
|
||||||
|
protected int mMarkdownCodeBackgroundColor = Color.WHITE;
|
||||||
|
|
||||||
public ReactBaseTextShadowNode() {
|
public ReactBaseTextShadowNode() {
|
||||||
mTextAttributes = new TextAttributes();
|
mTextAttributes = new TextAttributes();
|
||||||
|
@ -553,4 +732,25 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode {
|
||||||
}
|
}
|
||||||
markUpdated();
|
markUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "parseBasicMarkdown")
|
||||||
|
public void setParseBasicMarkdown(boolean parseBasicMarkdown) {
|
||||||
|
mParseBasicMarkdown = parseBasicMarkdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "markdownCodeBackgroundColor", defaultInt = Color.WHITE, customType = "Color")
|
||||||
|
public void setMarkdownCodeBackgroundColor(int markdownCodeBackgroundColor) {
|
||||||
|
if (markdownCodeBackgroundColor != mMarkdownCodeBackgroundColor) {
|
||||||
|
mMarkdownCodeBackgroundColor = markdownCodeBackgroundColor;
|
||||||
|
markUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "markdownCodeForegroundColor", defaultInt = Color.BLACK, customType = "Color")
|
||||||
|
public void setMarkdownCodeForegroundColor(int markdownCodeForegroundColor) {
|
||||||
|
if (markdownCodeForegroundColor != mMarkdownCodeForegroundColor) {
|
||||||
|
mMarkdownCodeForegroundColor = markdownCodeForegroundColor;
|
||||||
|
markUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue