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
12 changes: 11 additions & 1 deletion example/babel.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
const path = require('path');
const pak = require('../package.json');

/** @type {import('react-native-worklets/plugin').PluginOptions} */
const workletsPluginOptions = {
bundleMode: true,
strictGlobal: true,
importForwarding: {
moduleNames: ['expensify-common', '@expensify/react-native-live-markdown'],
relativePaths: ['react-native-live-markdown/src/'],
},
};

module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
Expand All @@ -13,6 +23,6 @@ module.exports = {
},
},
],
'react-native-worklets/plugin',
['react-native-worklets/plugin', workletsPluginOptions],
],
};
7 changes: 6 additions & 1 deletion example/metro.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const {bundleModeMetroConfig} = require('react-native-worklets/bundleMode');
const path = require('path');

const root = path.resolve(__dirname, '..');
Expand All @@ -22,4 +23,8 @@ const config = {
},
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);
module.exports = mergeConfig(
getDefaultConfig(__dirname),
bundleModeMetroConfig,
config,
);
8 changes: 0 additions & 8 deletions patches/html-entities+2.5.3.patch

This file was deleted.

31 changes: 31 additions & 0 deletions patches/metro+0.84.4.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
diff --git a/node_modules/metro/src/node-haste/DependencyGraph.js b/node_modules/metro/src/node-haste/DependencyGraph.js
index 8222d8c..e36770d 100644
--- a/node_modules/metro/src/node-haste/DependencyGraph.js
+++ b/node_modules/metro/src/node-haste/DependencyGraph.js
@@ -32,6 +32,11 @@ function getOrCreateMap(map, field) {
}
return subMap;
}
+
+const workletsDirPath = _path.default.join(
+ "react-native-worklets",
+ ".worklets",
+);
class DependencyGraph extends _events.default {
#packageCache;
#dependencyPlugin;
@@ -197,6 +202,14 @@ class DependencyGraph extends _events.default {
return (0, _nullthrows.default)(this._fileSystem).getAllFiles();
}
async getOrComputeSha1(mixedPath) {
+ if (mixedPath.includes(workletsDirPath)) {
+ const createHash = require("crypto").createHash;
+ return {
+ sha1: createHash("sha1")
+ .update(performance.now().toString())
+ .digest("hex"),
+ };
+ }
const result = await this._fileSystem.getOrComputeSha1(mixedPath);
if (!result || !result.sha1) {
throw new Error(`Failed to get the SHA-1 for: ${mixedPath}.
14 changes: 14 additions & 0 deletions patches/react-native++metro-runtime+0.84.4.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
diff --git a/node_modules/react-native/node_modules/metro-runtime/src/modules/HMRClient.js b/node_modules/react-native/node_modules/metro-runtime/src/modules/HMRClient.js
index 534ac87..5773985 100644
--- a/node_modules/react-native/node_modules/metro-runtime/src/modules/HMRClient.js
+++ b/node_modules/react-native/node_modules/metro-runtime/src/modules/HMRClient.js
@@ -3,6 +3,9 @@
const EventEmitter = require("./vendor/eventemitter3");
const HEARTBEAT_INTERVAL_MS = 20_000;
const inject = ({ module: [id, code], sourceURL }) => {
+ if (global.__workletsModuleProxy?.propagateModuleUpdate) {
+ global.__workletsModuleProxy.propagateModuleUpdate(code, sourceURL);
+ }
if (global.globalEvalWithSourceUrl) {
global.globalEvalWithSourceUrl(code, sourceURL);
} else {
6 changes: 0 additions & 6 deletions src/MarkdownTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,6 @@ const MarkdownTextInput = React.forwardRef<MarkdownTextInput, MarkdownTextInputP
throw new Error('[react-native-live-markdown] `parser` is undefined');
}

// eslint-disable-next-line no-underscore-dangle
const workletHash = (props.parser as {__workletHash?: number}).__workletHash;
if (workletHash === undefined) {
throw new Error('[react-native-live-markdown] `parser` is not a worklet');
}

const parserId = React.useMemo(() => {
return registerParser(props.parser);
}, [props.parser]);
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/parseExpensiMark.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {expect} from '@jest/globals';
import type {MarkdownRange} from '../commonTypes';
import parseExpensiMark from '../parseExpensiMark';
import parseExpensiMark from '../parse';

declare module 'expect' {
interface Matchers<R> {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/singleLineInputFix.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {expect} from '@jest/globals';
import {parseRangesToHTMLNodes} from '../web/utils/parserUtils';
import parseExpensiMark from '../parseExpensiMark';
import parseExpensiMark from '../parse';

/**
* Focused tests for the single-line input fix
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/webParser.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {expect} from '@jest/globals';
import {parseRangesToHTMLNodes} from '../web/utils/parserUtils';
import parseExpensiMark from '../parseExpensiMark';
import parseExpensiMark from '../parse';

declare module 'expect' {
interface Matchers<R> {
Expand Down
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export {default as MarkdownTextInput, getWorkletRuntime} from './MarkdownTextInput';
export type {MarkdownTextInputProps, MarkdownStyle} from './MarkdownTextInput';
export type {MarkdownType, MarkdownRange} from './commonTypes';
export {default as parseExpensiMark} from './parseExpensiMark';
export {default as parseExpensiMark} from './parse';
37 changes: 37 additions & 0 deletions src/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type {MarkdownRange} from './commonTypes';
import {groupRanges, sortRanges, excludeRangeTypesFromFormatting, getRangesToExcludeFormatting} from './rangeUtils';
import {parseMarkdownToHTML, parseHTMLToTokens, parseTokensToTree, parseTreeToTextAndRanges} from './parseExpensiMark';

const MAX_PARSABLE_LENGTH = 4000;

function parseExpensiMark(markdown: string): MarkdownRange[] {
'worklet';

if (markdown.length > MAX_PARSABLE_LENGTH) {
return [];
}
const html = parseMarkdownToHTML(markdown);
const tokens = parseHTMLToTokens(html);
const tree = parseTokensToTree(tokens);
const [text, ranges] = parseTreeToTextAndRanges(tree);
if (text !== markdown) {
console.error(
`[react-native-live-markdown] Parsing error: the processed text does not match the original Markdown input. This may be caused by incorrect parsing functions or invalid input Markdown.\nProcessed input: '${JSON.stringify(
text,
)}'\nOriginal input: '${JSON.stringify(markdown)}'`,
);
return [];
}
let markdownRanges = sortRanges(ranges);

// Prevent italic, bold and strikethrough formatting inside emojis and inline code blocks
const rangesToExclude = getRangesToExcludeFormatting(markdownRanges);
markdownRanges = excludeRangeTypesFromFormatting(markdownRanges, 'italic', rangesToExclude);
markdownRanges = excludeRangeTypesFromFormatting(markdownRanges, 'bold', rangesToExclude);
markdownRanges = excludeRangeTypesFromFormatting(markdownRanges, 'strikethrough', rangesToExclude);

const groupedRanges = groupRanges(markdownRanges);
return groupedRanges;
}

export default parseExpensiMark;
53 changes: 1 addition & 52 deletions src/parseExpensiMark.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
'worklet';

import {Platform} from 'react-native';
import {ExpensiMark} from 'expensify-common';
import {unescapeText} from 'expensify-common/utils';
import {decode} from 'html-entities';
import type {WorkletFunction} from 'react-native-worklets';
import {groupRanges, sortRanges, excludeRangeTypesFromFormatting, getRangesToExcludeFormatting} from './rangeUtils';
import type {MarkdownRange, MarkdownType} from './commonTypes';

function isWeb() {
return Platform.OS === 'web';
}

function isJest() {
return !!global.process.env.JEST_WORKER_ID;
}

// eslint-disable-next-line no-underscore-dangle
if (__DEV__ && !isWeb() && !isJest() && (decode as WorkletFunction<unknown[], unknown>).__workletHash === undefined) {
throw new Error(
"[react-native-live-markdown] `parseExpensiMark` requires `html-entities` package to be workletized. Please add `'worklet';` directive at the top of `node_modules/html-entities/lib/index.js` using patch-package. Make sure you've installed `html-entities` version 2.5.3 exactly as otherwise there is no `lib/` directory.",
);
}

const MAX_PARSABLE_LENGTH = 4000;

type Token = ['TEXT' | 'HTML', string];
type StackItem = {tag: string; children: Array<StackItem | string>};

Expand Down Expand Up @@ -238,32 +215,4 @@ function parseTreeToTextAndRanges(tree: StackItem): [string, MarkdownRange[]] {
return [text, ranges];
}

function parseExpensiMark(markdown: string): MarkdownRange[] {
if (markdown.length > MAX_PARSABLE_LENGTH) {
return [];
}
const html = parseMarkdownToHTML(markdown);
const tokens = parseHTMLToTokens(html);
const tree = parseTokensToTree(tokens);
const [text, ranges] = parseTreeToTextAndRanges(tree);
if (text !== markdown) {
console.error(
`[react-native-live-markdown] Parsing error: the processed text does not match the original Markdown input. This may be caused by incorrect parsing functions or invalid input Markdown.\nProcessed input: '${JSON.stringify(
text,
)}'\nOriginal input: '${JSON.stringify(markdown)}'`,
);
return [];
}
let markdownRanges = sortRanges(ranges);

// Prevent italic, bold and strikethrough formatting inside emojis and inline code blocks
const rangesToExclude = getRangesToExcludeFormatting(markdownRanges);
markdownRanges = excludeRangeTypesFromFormatting(markdownRanges, 'italic', rangesToExclude);
markdownRanges = excludeRangeTypesFromFormatting(markdownRanges, 'bold', rangesToExclude);
markdownRanges = excludeRangeTypesFromFormatting(markdownRanges, 'strikethrough', rangesToExclude);

const groupedRanges = groupRanges(markdownRanges);
return groupedRanges;
}

export default parseExpensiMark;
export {parseMarkdownToHTML, parseHTMLToTokens, parseTokensToTree, parseTreeToTextAndRanges};
2 changes: 0 additions & 2 deletions src/rangeUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'worklet';

import type {MarkdownRange, MarkdownType} from './commonTypes';

// getTagPriority returns a priority for a tag, higher priority means the tag should be processed first
Expand Down
Loading