@expensify/react-native-live-markdown 0.1.3

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 (80) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +139 -0
  3. package/RNLiveMarkdown.podspec +44 -0
  4. package/android/build.gradle +143 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/AndroidManifestNew.xml +2 -0
  8. package/android/src/main/cpp/CMakeLists.txt +23 -0
  9. package/android/src/main/cpp/MarkdownUtils.cpp +44 -0
  10. package/android/src/main/cpp/MarkdownUtils.h +41 -0
  11. package/android/src/main/cpp/OnLoad.cpp +8 -0
  12. package/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java +24 -0
  13. package/android/src/main/java/com/expensify/livemarkdown/MarkdownBackgroundColorSpan.java +11 -0
  14. package/android/src/main/java/com/expensify/livemarkdown/MarkdownBlockquoteSpan.java +47 -0
  15. package/android/src/main/java/com/expensify/livemarkdown/MarkdownBoldSpan.java +10 -0
  16. package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java +39 -0
  17. package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java +9 -0
  18. package/android/src/main/java/com/expensify/livemarkdown/MarkdownForegroundColorSpan.java +11 -0
  19. package/android/src/main/java/com/expensify/livemarkdown/MarkdownItalicSpan.java +10 -0
  20. package/android/src/main/java/com/expensify/livemarkdown/MarkdownLineHeightSpan.java +18 -0
  21. package/android/src/main/java/com/expensify/livemarkdown/MarkdownSpan.java +7 -0
  22. package/android/src/main/java/com/expensify/livemarkdown/MarkdownStrikethroughSpan.java +5 -0
  23. package/android/src/main/java/com/expensify/livemarkdown/MarkdownStyle.java +186 -0
  24. package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextInputDecoratorView.java +85 -0
  25. package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextInputDecoratorViewManager.java +31 -0
  26. package/android/src/main/java/com/expensify/livemarkdown/MarkdownTextWatcher.java +38 -0
  27. package/android/src/main/java/com/expensify/livemarkdown/MarkdownUnderlineSpan.java +5 -0
  28. package/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +176 -0
  29. package/android/src/newarch/MarkdownTextInputDecoratorViewManagerSpec.java +24 -0
  30. package/android/src/oldarch/MarkdownTextInputDecoratorViewManagerSpec.java +10 -0
  31. package/ios/MarkdownLayoutManager.h +12 -0
  32. package/ios/MarkdownLayoutManager.mm +35 -0
  33. package/ios/MarkdownTextInputDecoratorComponentView.h +12 -0
  34. package/ios/MarkdownTextInputDecoratorComponentView.mm +55 -0
  35. package/ios/MarkdownTextInputDecoratorView.h +12 -0
  36. package/ios/MarkdownTextInputDecoratorView.mm +106 -0
  37. package/ios/MarkdownTextInputDecoratorViewManager.h +8 -0
  38. package/ios/MarkdownTextInputDecoratorViewManager.mm +23 -0
  39. package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.h +14 -0
  40. package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.m +43 -0
  41. package/ios/RCTBaseTextInputView+Markdown.h +16 -0
  42. package/ios/RCTBaseTextInputView+Markdown.m +66 -0
  43. package/ios/RCTMarkdownStyle.h +35 -0
  44. package/ios/RCTMarkdownStyle.mm +81 -0
  45. package/ios/RCTMarkdownUtils.h +18 -0
  46. package/ios/RCTMarkdownUtils.mm +134 -0
  47. package/ios/RCTTextInputComponentView+Markdown.h +21 -0
  48. package/ios/RCTTextInputComponentView+Markdown.mm +51 -0
  49. package/ios/RCTUITextView+Markdown.h +15 -0
  50. package/ios/RCTUITextView+Markdown.mm +42 -0
  51. package/lib/commonjs/MarkdownTextInput.js +103 -0
  52. package/lib/commonjs/MarkdownTextInput.js.map +1 -0
  53. package/lib/commonjs/MarkdownTextInput.web.js +21 -0
  54. package/lib/commonjs/MarkdownTextInput.web.js.map +1 -0
  55. package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js +10 -0
  56. package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -0
  57. package/lib/commonjs/index.js +14 -0
  58. package/lib/commonjs/index.js.map +1 -0
  59. package/lib/module/MarkdownTextInput.js +96 -0
  60. package/lib/module/MarkdownTextInput.js.map +1 -0
  61. package/lib/module/MarkdownTextInput.web.js +15 -0
  62. package/lib/module/MarkdownTextInput.web.js.map +1 -0
  63. package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js +3 -0
  64. package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -0
  65. package/lib/module/index.js +2 -0
  66. package/lib/module/index.js.map +1 -0
  67. package/lib/typescript/src/MarkdownTextInput.d.ts +15 -0
  68. package/lib/typescript/src/MarkdownTextInput.d.ts.map +1 -0
  69. package/lib/typescript/src/MarkdownTextInput.web.d.ts +9 -0
  70. package/lib/typescript/src/MarkdownTextInput.web.d.ts.map +1 -0
  71. package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts +46 -0
  72. package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts.map +1 -0
  73. package/lib/typescript/src/index.d.ts +3 -0
  74. package/lib/typescript/src/index.d.ts.map +1 -0
  75. package/package.json +159 -0
  76. package/parser/react-native-live-markdown-parser.js +34 -0
  77. package/src/MarkdownTextInput.tsx +129 -0
  78. package/src/MarkdownTextInput.web.tsx +24 -0
  79. package/src/MarkdownTextInputDecoratorViewNativeComponent.ts +48 -0
  80. package/src/index.tsx +2 -0
@@ -0,0 +1,5 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.text.style.StrikethroughSpan;
4
+
5
+ public class MarkdownStrikethroughSpan extends StrikethroughSpan implements MarkdownSpan {}
@@ -0,0 +1,186 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.content.Context;
4
+
5
+ import androidx.annotation.ColorInt;
6
+ import androidx.annotation.NonNull;
7
+
8
+ import com.facebook.react.bridge.ColorPropConverter;
9
+ import com.facebook.react.bridge.Dynamic;
10
+ import com.facebook.react.bridge.JSApplicationCausedNativeException;
11
+ import com.facebook.react.bridge.ReadableMap;
12
+
13
+ import java.util.Objects;
14
+
15
+ public class MarkdownStyle {
16
+ @ColorInt
17
+ private final int mSyntaxColor;
18
+
19
+ @ColorInt
20
+ private final int mLinkColor;
21
+
22
+ private final float mH1FontSize;
23
+
24
+ @ColorInt
25
+ private final int mBlockquoteBorderColor;
26
+
27
+ private final float mBlockquoteBorderWidth;
28
+
29
+ private final float mBlockquoteMarginLeft;
30
+
31
+ private final float mBlockquotePaddingLeft;
32
+
33
+ private final String mCodeFontFamily;
34
+
35
+ @ColorInt
36
+ private final int mCodeColor;
37
+
38
+ @ColorInt
39
+ private final int mCodeBackgroundColor;
40
+
41
+ private final String mPreFontFamily;
42
+
43
+ @ColorInt
44
+ private final int mPreColor;
45
+
46
+ @ColorInt
47
+ private final int mPreBackgroundColor;
48
+
49
+ @ColorInt
50
+ private final int mMentionHereColor;
51
+
52
+ @ColorInt
53
+ private final int mMentionHereBackgroundColor;
54
+
55
+ @ColorInt
56
+ private final int mMentionUserColor;
57
+
58
+ @ColorInt
59
+ private final int mMentionUserBackgroundColor;
60
+
61
+ public MarkdownStyle(@NonNull ReadableMap map, @NonNull Context context) {
62
+ mSyntaxColor = parseColor(map, "syntax", "color", context);
63
+ mLinkColor = parseColor(map, "link", "color", context);
64
+ mH1FontSize = parseFloat(map, "h1", "fontSize");
65
+ mBlockquoteBorderColor = parseColor(map, "blockquote", "borderColor", context);
66
+ mBlockquoteBorderWidth = parseFloat(map, "blockquote", "borderWidth");
67
+ mBlockquoteMarginLeft = parseFloat(map, "blockquote", "marginLeft");
68
+ mBlockquotePaddingLeft = parseFloat(map, "blockquote", "paddingLeft");
69
+ mCodeFontFamily = parseString(map, "code", "fontFamily");
70
+ mCodeColor = parseColor(map, "code", "color", context);
71
+ mCodeBackgroundColor = parseColor(map, "code", "backgroundColor", context);
72
+ mPreFontFamily = parseString(map, "pre", "fontFamily");
73
+ mPreColor = parseColor(map, "pre", "color", context);
74
+ mPreBackgroundColor = parseColor(map, "pre", "backgroundColor", context);
75
+ mMentionHereColor = parseColor(map, "mentionHere", "color", context);
76
+ mMentionHereBackgroundColor = parseColor(map, "mentionHere", "backgroundColor", context);
77
+ mMentionUserColor = parseColor(map, "mentionUser", "color", context);
78
+ mMentionUserBackgroundColor = parseColor(map, "mentionUser", "backgroundColor", context);
79
+ }
80
+
81
+ private static int parseColor(@NonNull ReadableMap map, @NonNull String key, @NonNull String prop, @NonNull Context context) {
82
+ ReadableMap style = map.getMap(key);
83
+ Objects.requireNonNull(style);
84
+ Dynamic value = style.getDynamic(prop);
85
+ switch (value.getType()) {
86
+ case Number:
87
+ return ColorPropConverter.getColor(value.asDouble(), context);
88
+ case Map:
89
+ return ColorPropConverter.getColor(value.asMap(), context);
90
+ default:
91
+ throw new JSApplicationCausedNativeException("ColorValue: the value must be a number or Object.");
92
+ }
93
+ }
94
+
95
+ private static float parseFloat(@NonNull ReadableMap map, @NonNull String key, @NonNull String prop) {
96
+ ReadableMap style = map.getMap(key);
97
+ Objects.requireNonNull(style);
98
+ double value = style.getDouble(prop);
99
+ return (float) value;
100
+ }
101
+
102
+ private static String parseString(@NonNull ReadableMap map, @NonNull String key, @NonNull String prop) {
103
+ ReadableMap style = map.getMap(key);
104
+ Objects.requireNonNull(style);
105
+ return style.getString(prop);
106
+ }
107
+
108
+ @ColorInt
109
+ public int getSyntaxColor() {
110
+ return mSyntaxColor;
111
+ }
112
+
113
+ @ColorInt
114
+ public int getLinkColor() {
115
+ return mLinkColor;
116
+ }
117
+
118
+ public float getH1FontSize() {
119
+ return mH1FontSize;
120
+ }
121
+
122
+ @ColorInt
123
+ public int getBlockquoteBorderColor() {
124
+ return mBlockquoteBorderColor;
125
+ }
126
+
127
+ public float getBlockquoteBorderWidth() {
128
+ return mBlockquoteBorderWidth;
129
+ }
130
+
131
+ public float getBlockquoteMarginLeft() {
132
+ return mBlockquoteMarginLeft;
133
+ }
134
+
135
+ public float getBlockquotePaddingLeft() {
136
+ return mBlockquotePaddingLeft;
137
+ }
138
+
139
+ public String getCodeFontFamily() {
140
+ return mCodeFontFamily;
141
+ }
142
+
143
+ @ColorInt
144
+ public int getCodeColor() {
145
+ return mCodeColor;
146
+ }
147
+
148
+ @ColorInt
149
+ public int getCodeBackgroundColor() {
150
+ return mCodeBackgroundColor;
151
+ }
152
+
153
+ public String getPreFontFamily() {
154
+ return mPreFontFamily;
155
+ }
156
+
157
+ @ColorInt
158
+ public int getPreColor() {
159
+ return mPreColor;
160
+ }
161
+
162
+ @ColorInt
163
+ public int getPreBackgroundColor() {
164
+ return mPreBackgroundColor;
165
+ }
166
+
167
+ @ColorInt
168
+ public int getMentionHereColor() {
169
+ return mMentionHereColor;
170
+ }
171
+
172
+ @ColorInt
173
+ public int getMentionHereBackgroundColor() {
174
+ return mMentionHereBackgroundColor;
175
+ }
176
+
177
+ @ColorInt
178
+ public int getMentionUserColor() {
179
+ return mMentionUserColor;
180
+ }
181
+
182
+ @ColorInt
183
+ public int getMentionUserBackgroundColor() {
184
+ return mMentionUserBackgroundColor;
185
+ }
186
+ }
@@ -0,0 +1,85 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import androidx.annotation.Nullable;
4
+
5
+ import android.content.Context;
6
+ import android.content.res.AssetManager;
7
+ import android.text.TextWatcher;
8
+ import android.util.AttributeSet;
9
+
10
+ import android.view.View;
11
+ import android.view.ViewGroup;
12
+ import android.view.ViewParent;
13
+
14
+ import com.facebook.react.views.textinput.ReactEditText;
15
+
16
+ public class MarkdownTextInputDecoratorView extends View {
17
+
18
+ public MarkdownTextInputDecoratorView(Context context) {
19
+ super(context);
20
+ }
21
+
22
+ public MarkdownTextInputDecoratorView(Context context, @Nullable AttributeSet attrs) {
23
+ super(context, attrs);
24
+ }
25
+
26
+ public MarkdownTextInputDecoratorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
27
+ super(context, attrs, defStyleAttr);
28
+ }
29
+
30
+ private MarkdownStyle mMarkdownStyle;
31
+
32
+ private MarkdownUtils mMarkdownUtils;
33
+
34
+ private ReactEditText mReactEditText;
35
+
36
+ private TextWatcher mTextWatcher;
37
+
38
+ @Override
39
+ protected void onAttachedToWindow() {
40
+ super.onAttachedToWindow();
41
+
42
+ View previousSibling = null;
43
+ final ViewParent parent = this.getParent();
44
+ if (parent instanceof ViewGroup) {
45
+ final ViewGroup viewGroup = (ViewGroup) parent;
46
+ for (int i = 1; i < viewGroup.getChildCount(); i++) {
47
+ if (viewGroup.getChildAt(i) == this) {
48
+ previousSibling = viewGroup.getChildAt(i - 1);
49
+ break;
50
+ }
51
+ }
52
+ }
53
+
54
+ if (previousSibling instanceof ReactEditText) {
55
+ AssetManager assetManager = getContext().getAssets();
56
+ MarkdownUtils.maybeInitializeRuntime(assetManager);
57
+ mMarkdownUtils = new MarkdownUtils(assetManager);
58
+ mMarkdownUtils.setMarkdownStyle(mMarkdownStyle);
59
+ mReactEditText = (ReactEditText) previousSibling;
60
+ mTextWatcher = new MarkdownTextWatcher(mMarkdownUtils);
61
+ mReactEditText.addTextChangedListener(mTextWatcher);
62
+ }
63
+ }
64
+
65
+ @Override
66
+ protected void onDetachedFromWindow() {
67
+ super.onDetachedFromWindow();
68
+ if (mReactEditText != null) {
69
+ mReactEditText.removeTextChangedListener(mTextWatcher);
70
+ mReactEditText = null;
71
+ mTextWatcher = null;
72
+ mMarkdownUtils = null;
73
+ }
74
+ }
75
+
76
+ protected void setMarkdownStyle(MarkdownStyle markdownStyle) {
77
+ mMarkdownStyle = markdownStyle;
78
+ if (mMarkdownUtils != null) {
79
+ mMarkdownUtils.setMarkdownStyle(mMarkdownStyle);
80
+ }
81
+ if (mReactEditText != null) {
82
+ mReactEditText.setText(mReactEditText.getText()); // trigger update
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,31 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import androidx.annotation.NonNull;
4
+
5
+ import com.facebook.react.bridge.ReadableMap;
6
+ import com.facebook.react.module.annotations.ReactModule;
7
+ import com.facebook.react.uimanager.ThemedReactContext;
8
+ import com.facebook.react.uimanager.annotations.ReactProp;
9
+
10
+ @ReactModule(name = MarkdownTextInputDecoratorViewManager.NAME)
11
+ public class MarkdownTextInputDecoratorViewManager extends MarkdownTextInputDecoratorViewManagerSpec<MarkdownTextInputDecoratorView> {
12
+
13
+ public static final String NAME = "MarkdownTextInputDecoratorView";
14
+
15
+ @Override
16
+ public String getName() {
17
+ return NAME;
18
+ }
19
+
20
+ @Override
21
+ public MarkdownTextInputDecoratorView createViewInstance(ThemedReactContext context) {
22
+ return new MarkdownTextInputDecoratorView(context);
23
+ }
24
+
25
+ @Override
26
+ @ReactProp(name = "markdownStyle")
27
+ public void setMarkdownStyle(@NonNull MarkdownTextInputDecoratorView view, @NonNull ReadableMap value) {
28
+ MarkdownStyle markdownStyle = new MarkdownStyle(value, view.getContext());
29
+ view.setMarkdownStyle(markdownStyle);
30
+ }
31
+ }
@@ -0,0 +1,38 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.text.Editable;
4
+ import android.text.SpannableStringBuilder;
5
+ import android.text.TextWatcher;
6
+
7
+ import androidx.annotation.NonNull;
8
+
9
+ public class MarkdownTextWatcher implements TextWatcher {
10
+ private final MarkdownUtils mMarkdownUtils;
11
+
12
+ private boolean mShouldSkip = false;
13
+
14
+ public MarkdownTextWatcher(@NonNull MarkdownUtils markdownUtils) {
15
+ mMarkdownUtils = markdownUtils;
16
+ }
17
+
18
+ @Override
19
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
20
+
21
+ }
22
+
23
+ @Override
24
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
25
+ if (mShouldSkip) {
26
+ return;
27
+ }
28
+ if (s instanceof SpannableStringBuilder) {
29
+ mMarkdownUtils.applyMarkdownFormatting((SpannableStringBuilder) s);
30
+ mShouldSkip = true;
31
+ }
32
+ }
33
+
34
+ @Override
35
+ public void afterTextChanged(Editable editable) {
36
+ mShouldSkip = false;
37
+ }
38
+ }
@@ -0,0 +1,5 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.text.style.UnderlineSpan;
4
+
5
+ public class MarkdownUnderlineSpan extends UnderlineSpan implements MarkdownSpan {}
@@ -0,0 +1,176 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import static com.facebook.infer.annotation.ThreadConfined.UI;
4
+
5
+ import android.content.res.AssetManager;
6
+ import android.text.SpannableStringBuilder;
7
+ import android.text.Spanned;
8
+
9
+ import androidx.annotation.NonNull;
10
+
11
+ import com.facebook.infer.annotation.Assertions;
12
+ import com.facebook.infer.annotation.ThreadConfined;
13
+ import com.facebook.react.bridge.UiThreadUtil;
14
+ import com.facebook.react.views.text.CustomLineHeightSpan;
15
+ import com.facebook.soloader.SoLoader;
16
+
17
+ import org.json.JSONArray;
18
+ import org.json.JSONException;
19
+
20
+ import java.io.IOException;
21
+ import java.io.InputStream;
22
+ import java.util.Objects;
23
+
24
+ public class MarkdownUtils {
25
+ static {
26
+ SoLoader.loadLibrary("livemarkdown");
27
+ }
28
+
29
+ private static boolean IS_RUNTIME_INITIALIZED = false;
30
+
31
+ @ThreadConfined(UI)
32
+ public static void maybeInitializeRuntime(AssetManager assetManager) {
33
+ UiThreadUtil.assertOnUiThread();
34
+ if (IS_RUNTIME_INITIALIZED) {
35
+ return;
36
+ }
37
+ try {
38
+ InputStream inputStream = assetManager.open("react-native-live-markdown-parser.js");
39
+ byte[] buffer = new byte[inputStream.available()];
40
+ inputStream.read(buffer);
41
+ inputStream.close();
42
+ String code = new String(buffer);
43
+ nativeInitializeRuntime(code);
44
+ IS_RUNTIME_INITIALIZED = true;
45
+ } catch (IOException e) {
46
+ throw new RuntimeException("Failed to initialize Markdown runtime");
47
+ }
48
+ }
49
+
50
+ private static native void nativeInitializeRuntime(String code);
51
+
52
+ @ThreadConfined(UI)
53
+ private static String parseMarkdown(String input) {
54
+ UiThreadUtil.assertOnUiThread();
55
+ return nativeParseMarkdown(input);
56
+ }
57
+
58
+ private static native String nativeParseMarkdown(String input);
59
+
60
+ public MarkdownUtils(@NonNull AssetManager assetManager) {
61
+ mAssetManager = assetManager;
62
+ }
63
+
64
+ private final @NonNull AssetManager mAssetManager;
65
+
66
+ private String mPrevInput;
67
+
68
+ private String mPrevOutput;
69
+
70
+ private MarkdownStyle mMarkdownStyle;
71
+
72
+ public void setMarkdownStyle(@NonNull MarkdownStyle markdownStyle) {
73
+ mMarkdownStyle = markdownStyle;
74
+ }
75
+
76
+ public void applyMarkdownFormatting(SpannableStringBuilder ssb) {
77
+ Objects.requireNonNull(mMarkdownStyle, "mMarkdownStyle is null");
78
+
79
+ removeSpans(ssb);
80
+
81
+ String input = ssb.toString();
82
+ String output;
83
+ if (input.equals(mPrevInput)) {
84
+ output = mPrevOutput;
85
+ } else {
86
+ output = parseMarkdown(input);
87
+ mPrevInput = input;
88
+ mPrevOutput = output;
89
+ }
90
+
91
+ try {
92
+ JSONArray ranges = new JSONArray(output);
93
+ for (int i = 0; i < ranges.length(); i++) {
94
+ JSONArray range = ranges.getJSONArray(i);
95
+ String type = range.getString(0);
96
+ int start = range.getInt(1);
97
+ int end = start + range.getInt(2);
98
+ applyRange(ssb, type, start, end);
99
+ }
100
+ } catch (JSONException e) {
101
+ // Do nothing
102
+ }
103
+ }
104
+
105
+ private void applyRange(SpannableStringBuilder ssb, String type, int start, int end) {
106
+ switch (type) {
107
+ case "bold":
108
+ setSpan(ssb, new MarkdownBoldSpan(), start, end);
109
+ break;
110
+ case "italic":
111
+ setSpan(ssb, new MarkdownItalicSpan(), start, end);
112
+ break;
113
+ case "strikethrough":
114
+ setSpan(ssb, new MarkdownStrikethroughSpan(), start, end);
115
+ break;
116
+ case "mention-here":
117
+ setSpan(ssb, new MarkdownForegroundColorSpan(mMarkdownStyle.getMentionHereColor()), start, end);
118
+ setSpan(ssb, new MarkdownBackgroundColorSpan(mMarkdownStyle.getMentionHereBackgroundColor()), start, end);
119
+ break;
120
+ case "mention-user":
121
+ // TODO: change mention color when it mentions current user
122
+ setSpan(ssb, new MarkdownForegroundColorSpan(mMarkdownStyle.getMentionUserColor()), start, end);
123
+ setSpan(ssb, new MarkdownBackgroundColorSpan(mMarkdownStyle.getMentionUserBackgroundColor()), start, end);
124
+ break;
125
+ case "syntax":
126
+ setSpan(ssb, new MarkdownForegroundColorSpan(mMarkdownStyle.getSyntaxColor()), start, end);
127
+ break;
128
+ case "link":
129
+ setSpan(ssb, new MarkdownUnderlineSpan(), start, end);
130
+ setSpan(ssb, new MarkdownForegroundColorSpan(mMarkdownStyle.getLinkColor()), start, end);
131
+ break;
132
+ case "code":
133
+ setSpan(ssb, new MarkdownFontFamilySpan(mMarkdownStyle.getCodeFontFamily(), mAssetManager), start, end);
134
+ setSpan(ssb, new MarkdownForegroundColorSpan(mMarkdownStyle.getCodeColor()), start, end);
135
+ setSpan(ssb, new MarkdownBackgroundColorSpan(mMarkdownStyle.getCodeBackgroundColor()), start, end);
136
+ break;
137
+ case "pre":
138
+ setSpan(ssb, new MarkdownFontFamilySpan(mMarkdownStyle.getPreFontFamily(), mAssetManager), start, end);
139
+ setSpan(ssb, new MarkdownForegroundColorSpan(mMarkdownStyle.getPreColor()), start, end);
140
+ setSpan(ssb, new MarkdownBackgroundColorSpan(mMarkdownStyle.getPreBackgroundColor()), start, end);
141
+ break;
142
+ case "h1":
143
+ setSpan(ssb, new MarkdownBoldSpan(), start, end);
144
+ CustomLineHeightSpan[] spans = ssb.getSpans(0, ssb.length(), CustomLineHeightSpan.class);
145
+ if (spans.length >= 1) {
146
+ int lineHeight = spans[0].getLineHeight();
147
+ setSpan(ssb, new MarkdownLineHeightSpan(lineHeight * 1.5f), start, end);
148
+ }
149
+ // NOTE: size span must be set after line height span to avoid height jumps
150
+ setSpan(ssb, new MarkdownFontSizeSpan(mMarkdownStyle.getH1FontSize()), start, end);
151
+ break;
152
+ case "blockquote":
153
+ MarkdownBlockquoteSpan span = new MarkdownBlockquoteSpan(
154
+ mMarkdownStyle.getBlockquoteBorderColor(),
155
+ mMarkdownStyle.getBlockquoteBorderWidth(),
156
+ mMarkdownStyle.getBlockquoteMarginLeft(),
157
+ mMarkdownStyle.getBlockquotePaddingLeft());
158
+ setSpan(ssb, span, start, end);
159
+ break;
160
+ default:
161
+ throw new IllegalStateException("Unsupported type: " + type);
162
+ }
163
+ }
164
+
165
+ private void setSpan(SpannableStringBuilder ssb, MarkdownSpan span, int start, int end) {
166
+ ssb.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
167
+ }
168
+
169
+ private void removeSpans(SpannableStringBuilder ssb) {
170
+ // We shouldn't use `removeSpans()` because it also removes SpellcheckSpan, SuggestionSpan etc.
171
+ MarkdownSpan[] spans = ssb.getSpans(0, ssb.length(), MarkdownSpan.class);
172
+ for (MarkdownSpan span : spans) {
173
+ ssb.removeSpan(span);
174
+ }
175
+ }
176
+ }
@@ -0,0 +1,24 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.view.View;
4
+
5
+ import androidx.annotation.Nullable;
6
+
7
+ import com.facebook.react.uimanager.SimpleViewManager;
8
+ import com.facebook.react.uimanager.ViewManagerDelegate;
9
+ import com.facebook.react.viewmanagers.MarkdownTextInputDecoratorViewManagerDelegate;
10
+ import com.facebook.react.viewmanagers.MarkdownTextInputDecoratorViewManagerInterface;
11
+
12
+ public abstract class MarkdownTextInputDecoratorViewManagerSpec<T extends View> extends SimpleViewManager<T> implements MarkdownTextInputDecoratorViewManagerInterface<T> {
13
+ private final ViewManagerDelegate<T> mDelegate;
14
+
15
+ public MarkdownTextInputDecoratorViewManagerSpec() {
16
+ mDelegate = new MarkdownTextInputDecoratorViewManagerDelegate(this);
17
+ }
18
+
19
+ @Nullable
20
+ @Override
21
+ protected ViewManagerDelegate<T> getDelegate() {
22
+ return mDelegate;
23
+ }
24
+ }
@@ -0,0 +1,10 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import android.view.View;
4
+
5
+ import com.facebook.react.bridge.ReadableMap;
6
+ import com.facebook.react.uimanager.SimpleViewManager;
7
+
8
+ public abstract class MarkdownTextInputDecoratorViewManagerSpec<T extends View> extends SimpleViewManager<T> {
9
+ public abstract void setMarkdownStyle(T view, ReadableMap value);
10
+ }
@@ -0,0 +1,12 @@
1
+ #import <UIKit/UIKit.h>
2
+ #import <RNLiveMarkdown/RCTMarkdownUtils.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ @interface MarkdownLayoutManager : NSLayoutManager
7
+
8
+ @property(nonatomic) RCTMarkdownUtils *markdownUtils;
9
+
10
+ @end
11
+
12
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,35 @@
1
+ #import <RNLiveMarkdown/MarkdownLayoutManager.h>
2
+
3
+ @implementation MarkdownLayoutManager
4
+
5
+ - (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {
6
+ [super drawBackgroundForGlyphRange:glyphsToShow atPoint:origin];
7
+
8
+ [self enumerateLineFragmentsForGlyphRange:glyphsToShow usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
9
+ __block BOOL isBlockquote = NO;
10
+ RCTMarkdownUtils *markdownUtils = [self valueForKey:@"markdownUtils"];
11
+ [markdownUtils.blockquoteRanges enumerateObjectsUsingBlock:^(NSValue *item, NSUInteger idx, BOOL * _Nonnull stop) {
12
+ NSRange range = [item rangeValue];
13
+ NSUInteger start = range.location;
14
+ NSUInteger end = start + range.length;
15
+ NSUInteger location = glyphRange.location;
16
+ if (location >= start && location < end) {
17
+ isBlockquote = YES;
18
+ *stop = YES;
19
+ }
20
+ }];
21
+ if (isBlockquote) {
22
+ CGFloat paddingLeft = markdownUtils.backedTextInputView.textContainerInset.left;
23
+ CGFloat paddingTop = markdownUtils.backedTextInputView.textContainerInset.top;
24
+ CGFloat x = paddingLeft + markdownUtils.markdownStyle.blockquoteMarginLeft;
25
+ CGFloat y = paddingTop + rect.origin.y;
26
+ CGFloat width = markdownUtils.markdownStyle.blockquoteBorderWidth;
27
+ CGFloat height = rect.size.height;
28
+ CGRect lineRect = CGRectMake(x, y, width, height);
29
+ [markdownUtils.markdownStyle.blockquoteBorderColor setFill];
30
+ UIRectFill(lineRect);
31
+ }
32
+ }];
33
+ }
34
+
35
+ @end
@@ -0,0 +1,12 @@
1
+ // This guard prevent this file to be compiled in the old architecture.
2
+ #ifdef RCT_NEW_ARCH_ENABLED
3
+ #import <React/RCTViewComponentView.h>
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface MarkdownTextInputDecoratorComponentView : RCTViewComponentView
8
+ @end
9
+
10
+ NS_ASSUME_NONNULL_END
11
+
12
+ #endif /* RCT_NEW_ARCH_ENABLED */
@@ -0,0 +1,55 @@
1
+ // This guard prevent this file to be compiled in the old architecture.
2
+ #ifdef RCT_NEW_ARCH_ENABLED
3
+ #import <react/renderer/components/RNLiveMarkdownSpec/ComponentDescriptors.h>
4
+ #import <react/renderer/components/RNLiveMarkdownSpec/Props.h>
5
+
6
+ #import <RNLiveMarkdown/MarkdownTextInputDecoratorComponentView.h>
7
+ #import <RNLiveMarkdown/MarkdownTextInputDecoratorView.h>
8
+ #import <RNLiveMarkdown/RCTMarkdownStyle.h>
9
+
10
+ #import "RCTFabricComponentsPlugins.h"
11
+
12
+ using namespace facebook::react;
13
+
14
+ @implementation MarkdownTextInputDecoratorComponentView {
15
+ MarkdownTextInputDecoratorView *_view;
16
+ }
17
+
18
+ + (ComponentDescriptorProvider)componentDescriptorProvider
19
+ {
20
+ return concreteComponentDescriptorProvider<MarkdownTextInputDecoratorViewComponentDescriptor>();
21
+ }
22
+
23
+ - (instancetype)initWithFrame:(CGRect)frame
24
+ {
25
+ if (self = [super initWithFrame:frame]) {
26
+ static const auto defaultProps = std::make_shared<const MarkdownTextInputDecoratorViewProps>();
27
+ _props = defaultProps;
28
+
29
+ _view = [[MarkdownTextInputDecoratorView alloc] init];
30
+
31
+ self.contentView = _view;
32
+ }
33
+
34
+ return self;
35
+ }
36
+
37
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
38
+ {
39
+ const auto &oldViewProps = *std::static_pointer_cast<MarkdownTextInputDecoratorViewProps const>(_props);
40
+ const auto &newViewProps = *std::static_pointer_cast<MarkdownTextInputDecoratorViewProps const>(props);
41
+
42
+ // TODO: if (oldViewProps.markdownStyle != newViewProps.markdownStyle)
43
+ RCTMarkdownStyle *markdownStyle = [[RCTMarkdownStyle alloc] initWithStruct:newViewProps.markdownStyle];
44
+ [_view setMarkdownStyle:markdownStyle];
45
+
46
+ [super updateProps:props oldProps:oldProps];
47
+ }
48
+
49
+ Class<RCTComponentViewProtocol> MarkdownTextInputDecoratorViewCls(void)
50
+ {
51
+ return MarkdownTextInputDecoratorComponentView.class;
52
+ }
53
+
54
+ @end
55
+ #endif