-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into @jpiasecki/bump-to-76
- Loading branch information
Showing
41 changed files
with
1,211 additions
and
582 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
#include "MarkdownParser.h" | ||
#include "MarkdownGlobal.h" | ||
|
||
#include <fbjni/fbjni.h> | ||
|
||
using namespace facebook; | ||
|
||
namespace expensify { | ||
namespace livemarkdown { | ||
jni::local_ref<jni::JString> MarkdownParser::nativeParse( | ||
jni::alias_ref<jhybridobject> jThis, | ||
jni::alias_ref<jni::JString> text, | ||
const int parserId) { | ||
static std::mutex workletRuntimeMutex; // this needs to be global since the worklet runtime is also global | ||
const auto lock = std::lock_guard<std::mutex>(workletRuntimeMutex); | ||
|
||
const auto markdownRuntime = expensify::livemarkdown::getMarkdownRuntime(); | ||
jsi::Runtime &rt = markdownRuntime->getJSIRuntime(); | ||
|
||
const auto markdownWorklet = expensify::livemarkdown::getMarkdownWorklet(parserId); | ||
|
||
const auto input = jsi::String::createFromUtf8(rt, text->toStdString()); | ||
const auto output = markdownRuntime->runGuarded(markdownWorklet, input); | ||
|
||
const auto json = rt.global().getPropertyAsObject(rt, "JSON").getPropertyAsFunction(rt, "stringify").call(rt, output).asString(rt).utf8(rt); | ||
return jni::make_jstring(json); | ||
} | ||
|
||
void MarkdownParser::registerNatives() { | ||
registerHybrid({ | ||
makeNativeMethod("nativeParse", MarkdownParser::nativeParse)}); | ||
} | ||
|
||
} // namespace livemarkdown | ||
} // namespace expensify |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
android/src/main/java/com/expensify/livemarkdown/MarkdownFormatter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package com.expensify.livemarkdown; | ||
|
||
import android.content.res.AssetManager; | ||
import android.text.SpannableStringBuilder; | ||
import android.text.Spanned; | ||
|
||
import androidx.annotation.NonNull; | ||
|
||
import com.expensify.livemarkdown.spans.*; | ||
import com.facebook.react.views.text.internal.span.CustomLineHeightSpan; | ||
import com.facebook.systrace.Systrace; | ||
|
||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
public class MarkdownFormatter { | ||
private final @NonNull AssetManager mAssetManager; | ||
|
||
public MarkdownFormatter(@NonNull AssetManager assetManager) { | ||
mAssetManager = assetManager; | ||
} | ||
|
||
public void format(@NonNull SpannableStringBuilder ssb, @NonNull List<MarkdownRange> markdownRanges, @NonNull MarkdownStyle markdownStyle) { | ||
try { | ||
Systrace.beginSection(0, "format"); | ||
Objects.requireNonNull(markdownStyle, "mMarkdownStyle is null"); | ||
removeSpans(ssb); | ||
applyRanges(ssb, markdownRanges, markdownStyle); | ||
} finally { | ||
Systrace.endSection(0); | ||
} | ||
} | ||
|
||
private void removeSpans(@NonNull SpannableStringBuilder ssb) { | ||
try { | ||
Systrace.beginSection(0, "removeSpans"); | ||
// We shouldn't use `removeSpans()` because it also removes SpellcheckSpan, SuggestionSpan etc. | ||
MarkdownSpan[] spans = ssb.getSpans(0, ssb.length(), MarkdownSpan.class); | ||
for (MarkdownSpan span : spans) { | ||
ssb.removeSpan(span); | ||
} | ||
} finally { | ||
Systrace.endSection(0); | ||
} | ||
} | ||
|
||
private void applyRanges(@NonNull SpannableStringBuilder ssb, @NonNull List<MarkdownRange> markdownRanges, @NonNull MarkdownStyle markdownStyle) { | ||
try { | ||
Systrace.beginSection(0, "applyRanges"); | ||
for (MarkdownRange markdownRange : markdownRanges) { | ||
applyRange(ssb, markdownRange, markdownStyle); | ||
} | ||
} finally { | ||
Systrace.endSection(0); | ||
} | ||
} | ||
|
||
private void applyRange(@NonNull SpannableStringBuilder ssb, @NonNull MarkdownRange markdownRange, @NonNull MarkdownStyle markdownStyle) { | ||
String type = markdownRange.getType(); | ||
int start = markdownRange.getStart(); | ||
int end = markdownRange.getEnd(); | ||
switch (type) { | ||
case "bold": | ||
setSpan(ssb, new MarkdownBoldSpan(), start, end); | ||
break; | ||
case "italic": | ||
setSpan(ssb, new MarkdownItalicSpan(), start, end); | ||
break; | ||
case "strikethrough": | ||
setSpan(ssb, new MarkdownStrikethroughSpan(), start, end); | ||
break; | ||
case "emoji": | ||
setSpan(ssb, new MarkdownEmojiSpan(markdownStyle.getEmojiFontSize()), start, end); | ||
break; | ||
case "mention-here": | ||
setSpan(ssb, new MarkdownForegroundColorSpan(markdownStyle.getMentionHereColor()), start, end); | ||
setSpan(ssb, new MarkdownBackgroundColorSpan(markdownStyle.getMentionHereBackgroundColor()), start, end); | ||
break; | ||
case "mention-user": | ||
// TODO: change mention color when it mentions current user | ||
setSpan(ssb, new MarkdownForegroundColorSpan(markdownStyle.getMentionUserColor()), start, end); | ||
setSpan(ssb, new MarkdownBackgroundColorSpan(markdownStyle.getMentionUserBackgroundColor()), start, end); | ||
break; | ||
case "mention-report": | ||
setSpan(ssb, new MarkdownForegroundColorSpan(markdownStyle.getMentionReportColor()), start, end); | ||
setSpan(ssb, new MarkdownBackgroundColorSpan(markdownStyle.getMentionReportBackgroundColor()), start, end); | ||
break; | ||
case "syntax": | ||
setSpan(ssb, new MarkdownForegroundColorSpan(markdownStyle.getSyntaxColor()), start, end); | ||
break; | ||
case "link": | ||
setSpan(ssb, new MarkdownUnderlineSpan(), start, end); | ||
setSpan(ssb, new MarkdownForegroundColorSpan(markdownStyle.getLinkColor()), start, end); | ||
break; | ||
case "code": | ||
setSpan(ssb, new MarkdownFontFamilySpan(markdownStyle.getCodeFontFamily(), mAssetManager), start, end); | ||
setSpan(ssb, new MarkdownFontSizeSpan(markdownStyle.getCodeFontSize()), start, end); | ||
setSpan(ssb, new MarkdownForegroundColorSpan(markdownStyle.getCodeColor()), start, end); | ||
setSpan(ssb, new MarkdownBackgroundColorSpan(markdownStyle.getCodeBackgroundColor()), start, end); | ||
break; | ||
case "pre": | ||
setSpan(ssb, new MarkdownFontFamilySpan(markdownStyle.getPreFontFamily(), mAssetManager), start, end); | ||
setSpan(ssb, new MarkdownFontSizeSpan(markdownStyle.getPreFontSize()), start, end); | ||
setSpan(ssb, new MarkdownForegroundColorSpan(markdownStyle.getPreColor()), start, end); | ||
setSpan(ssb, new MarkdownBackgroundColorSpan(markdownStyle.getPreBackgroundColor()), start, end); | ||
break; | ||
case "h1": | ||
setSpan(ssb, new MarkdownBoldSpan(), start, end); | ||
CustomLineHeightSpan[] spans = ssb.getSpans(0, ssb.length(), CustomLineHeightSpan.class); | ||
if (spans.length >= 1) { | ||
int lineHeight = spans[0].getLineHeight(); | ||
setSpan(ssb, new MarkdownLineHeightSpan(lineHeight * 1.5f), start, end); | ||
} | ||
// NOTE: size span must be set after line height span to avoid height jumps | ||
setSpan(ssb, new MarkdownFontSizeSpan(markdownStyle.getH1FontSize()), start, end); | ||
break; | ||
case "blockquote": | ||
MarkdownBlockquoteSpan span = new MarkdownBlockquoteSpan( | ||
markdownStyle.getBlockquoteBorderColor(), | ||
markdownStyle.getBlockquoteBorderWidth(), | ||
markdownStyle.getBlockquoteMarginLeft(), | ||
markdownStyle.getBlockquotePaddingLeft(), | ||
markdownRange.getDepth()); | ||
setSpan(ssb, span, start, end); | ||
break; | ||
} | ||
} | ||
|
||
private void setSpan(@NonNull SpannableStringBuilder ssb, @NonNull MarkdownSpan span, int start, int end) { | ||
ssb.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
android/src/main/java/com/expensify/livemarkdown/MarkdownParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package com.expensify.livemarkdown; | ||
|
||
import androidx.annotation.NonNull; | ||
|
||
import com.facebook.react.bridge.ReactContext; | ||
import com.facebook.react.util.RNLog; | ||
import com.facebook.soloader.SoLoader; | ||
import com.facebook.systrace.Systrace; | ||
|
||
import org.json.JSONArray; | ||
import org.json.JSONException; | ||
import org.json.JSONObject; | ||
|
||
import java.util.Collections; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
|
||
public class MarkdownParser { | ||
static { | ||
SoLoader.loadLibrary("livemarkdown"); | ||
} | ||
|
||
private final @NonNull ReactContext mReactContext; | ||
private String mPrevText; | ||
private int mPrevParserId; | ||
private List<MarkdownRange> mPrevMarkdownRanges; | ||
|
||
public MarkdownParser(@NonNull ReactContext reactContext) { | ||
mReactContext = reactContext; | ||
} | ||
|
||
private native String nativeParse(@NonNull String text, int parserId); | ||
|
||
public synchronized List<MarkdownRange> parse(@NonNull String text, int parserId) { | ||
try { | ||
Systrace.beginSection(0, "parse"); | ||
|
||
if (text.equals(mPrevText) && parserId == mPrevParserId) { | ||
return mPrevMarkdownRanges; | ||
} | ||
|
||
String json; | ||
try { | ||
Systrace.beginSection(0, "nativeParse"); | ||
json = nativeParse(text, parserId); | ||
} catch (Exception e) { | ||
// Skip formatting, runGuarded will show the error in LogBox | ||
mPrevText = text; | ||
mPrevParserId = parserId; | ||
mPrevMarkdownRanges = Collections.emptyList(); | ||
return mPrevMarkdownRanges; | ||
} finally { | ||
Systrace.endSection(0); | ||
} | ||
|
||
List<MarkdownRange> markdownRanges = new LinkedList<>(); | ||
try { | ||
Systrace.beginSection(0, "markdownRanges"); | ||
JSONArray ranges = new JSONArray(json); | ||
for (int i = 0; i < ranges.length(); i++) { | ||
JSONObject range = ranges.getJSONObject(i); | ||
String type = range.getString("type"); | ||
int start = range.getInt("start"); | ||
int length = range.getInt("length"); | ||
int depth = range.optInt("depth", 1); | ||
if (length == 0 || start + length > text.length()) { | ||
continue; | ||
} | ||
markdownRanges.add(new MarkdownRange(type, start, length, depth)); | ||
} | ||
} catch (JSONException e) { | ||
RNLog.w(mReactContext, "[react-native-live-markdown] Incorrect schema of worklet parser output: " + e.getMessage()); | ||
mPrevText = text; | ||
mPrevParserId = parserId; | ||
mPrevMarkdownRanges = Collections.emptyList(); | ||
return mPrevMarkdownRanges; | ||
} finally { | ||
Systrace.endSection(0); | ||
} | ||
|
||
mPrevText = text; | ||
mPrevParserId = parserId; | ||
mPrevMarkdownRanges = markdownRanges; | ||
return mPrevMarkdownRanges; | ||
} finally { | ||
Systrace.endSection(0); | ||
} | ||
} | ||
} |
Oops, something went wrong.