Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add web implementation #32

Merged
merged 12 commits into from
Feb 7, 2024
Merged
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
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ PODS:
- React-jsi (= 0.72.9)
- React-logger (= 0.72.9)
- React-perflogger (= 0.72.9)
- RNLiveMarkdown (0.1.0):
- RNLiveMarkdown (0.1.3):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- SocketRocket (0.6.1)
Expand Down Expand Up @@ -709,7 +709,7 @@ SPEC CHECKSUMS:
React-runtimescheduler: a7b1442e155c6f131d8bdfaac47abdc303f50788
React-utils: a3ffbc321572ee91911d7bc30965abe9aa4e16af
ReactCommon: 180205f326d59f52e12fa724f5278fcf8fb6afc3
RNLiveMarkdown: e76d6cd583fe59179100055b2e58f444b807ac66
RNLiveMarkdown: 79f23328b984e15502395a44260917e999b07e08
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
Yoga: eddf2bbe4a896454c248a8f23b4355891eb720a6
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
Expand Down
24 changes: 23 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,15 @@ function getReactNativeVersion() {
return `${major}.${minor}.${patch}`;
}

function getRandomColor() {
return `#${Math.floor(Math.random() * 16777215)
.toString(16)
.padStart(6, '0')}`;
}

export default function App() {
const [value, setValue] = React.useState(DEFAULT_TEXT);
const [markdownStyle, setMarkdownStyle] = React.useState({});

// TODO: use MarkdownTextInput ref instead of TextInput ref
const ref = React.useRef<TextInput>(null);
Expand Down Expand Up @@ -89,6 +96,8 @@ export default function App() {
onChangeText={setValue}
style={styles.input}
ref={ref}
markdownStyle={markdownStyle}
placeholder="Type here..."
/>
{/* <Text>TextInput singleline</Text>
<TextInput
Expand Down Expand Up @@ -126,12 +135,25 @@ export default function App() {
/>
<Button
title="Reset"
onPress={() => setValue(DEFAULT_TEXT)}
onPress={() => {
setValue(DEFAULT_TEXT);
setMarkdownStyle({});
}}
/>
<Button
title="Clear"
onPress={() => setValue('')}
/>
<Button
title="Change style"
onPress={() =>
setMarkdownStyle({
link: {
color: getRandomColor(),
},
})
}
/>
</View>
);
}
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"lint:parser": "eslint parser --ext .js,.ts,.tsx",
"lint:web": "eslint WebExample --ext .js,.ts,.tsx",
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
"prepare": "bob build",
"prepare": "bob build && mkdir -p lib/parser && cp parser/react-native-live-markdown-parser.js lib/parser/react-native-live-markdown-parser.js",
"release": "release-it"
},
"keywords": [
Expand Down Expand Up @@ -85,11 +85,13 @@
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-tsdoc": "^0.2.17",
"jest": "^28.1.1",
"jest-environment-jsdom": "^29.7.0",
"pod-install": "^0.1.0",
"prettier": "^2.0.5",
"react": "18.2.0",
"react-native": "0.72.9",
"react-native-builder-bob": "^0.20.0",
"react-native-web": "^0.19.10",
"release-it": "^15.0.0",
"turbo": "^1.10.7",
"typescript": "^5.3.3"
Expand All @@ -113,7 +115,8 @@
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
]
],
"testEnvironment": "jsdom"
},
"commitlint": {
"extends": [
Expand Down
36 changes: 18 additions & 18 deletions parser/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,43 +173,43 @@ test('codeblock', () => {
describe('blockquote', () => {
test('with single space', () => {
expect('> Hello world!').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 14],
['syntax', 0, 1],
]);
});

test('with multiple spaces', () => {
expect('> Hello world!').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 19],
['syntax', 0, 1],
]);
});

test('without space', () => {
expect('>Hello world!').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 13],
['syntax', 0, 1],
]);
});
});

test('multiple blockquotes', () => {
expect('> Hello\n> beautiful\n> world').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 7],
['syntax', 8, 1],
['syntax', 0, 1],
['blockquote', 8, 11],
['syntax', 20, 1],
['syntax', 8, 1],
['blockquote', 20, 7],
['syntax', 20, 1],
]);
});

test('separate blockquotes', () => {
expect('> Lorem ipsum\ndolor\n> sit amet').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 13],
['syntax', 20, 1],
['syntax', 0, 1],
['blockquote', 20, 10],
['syntax', 20, 1],
]);
});

Expand All @@ -223,14 +223,14 @@ test('h1', () => {
test('nested bold and italic', () => {
expect('*_Hello_*, _*world*_!').toBeParsedAs([
['syntax', 0, 1],
['syntax', 1, 1],
['bold', 1, 7],
['syntax', 1, 1],
['italic', 2, 5],
['syntax', 7, 1],
['syntax', 8, 1],
['syntax', 11, 1],
['syntax', 12, 1],
['italic', 12, 7],
['syntax', 12, 1],
['bold', 13, 5],
['syntax', 18, 1],
['syntax', 19, 1],
Expand All @@ -240,26 +240,26 @@ test('nested bold and italic', () => {
describe('nested h1 in blockquote', () => {
test('without spaces', () => {
expect('># Hello world').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 14],
['syntax', 0, 1],
['syntax', 1, 2],
['h1', 3, 11],
]);
});

test('with single space', () => {
expect('> # Hello world').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 15],
['syntax', 0, 1],
['syntax', 2, 2],
['h1', 4, 11],
]);
});

test('with multiple spaces after #', () => {
expect('># Hello world').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 17],
['syntax', 0, 1],
['syntax', 1, 2],
['h1', 3, 14],
]);
Expand All @@ -270,22 +270,22 @@ describe('trailing whitespace', () => {
describe('after blockquote', () => {
test('nothing', () => {
expect('> Hello world').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 13],
['syntax', 0, 1],
]);
});

test('single space', () => {
expect('> Hello world ').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 14],
['syntax', 0, 1],
]);
});

test('newline', () => {
expect('> Hello world\n').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 13],
['syntax', 0, 1],
]);
});
});
Expand Down Expand Up @@ -321,12 +321,12 @@ describe('trailing whitespace', () => {

test('multiple blockquotes', () => {
expect('> # Hello\n> # world').toBeParsedAs([
['syntax', 0, 1],
['blockquote', 0, 9],
['syntax', 0, 1],
['syntax', 2, 2],
['h1', 4, 5],
['syntax', 10, 1],
['blockquote', 10, 9],
['syntax', 10, 1],
['syntax', 12, 2],
['h1', 14, 5],
]);
Expand Down
15 changes: 14 additions & 1 deletion parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,21 @@ function parseTreeToTextAndRanges(tree: StackItem): [string, Range[]] {
return [text, ranges];
}

// getTagPriority returns a priority for a tag, higher priority means the tag should be processed first
function getTagPriority(tag: string) {
switch (tag) {
case 'blockquote':
return 2;
case 'h1':
return 1;
default:
return 0;
}
}

function sortRanges(ranges: Range[]) {
return ranges.sort((a, b) => a[1] - b[1]); // sort by location to properly handle bold+italic
// sort ranges by start position, then by length, then by tag hierarchy
return ranges.sort((a, b) => a[1] - b[1] || b[2] - a[2] || getTagPriority(b[0]) - getTagPriority(a[0]) || 0);
}

function parseExpensiMarkToRanges(markdown: string): Range[] {
Expand Down
14 changes: 7 additions & 7 deletions parser/react-native-live-markdown-parser.js

Large diffs are not rendered by default.

78 changes: 9 additions & 69 deletions src/MarkdownTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,16 @@
import {StyleSheet, TextInput, processColor} from 'react-native';
import React from 'react';
import {Platform, StyleSheet, TextInput, processColor} from 'react-native';
import type {TextInputProps} from 'react-native';

import type * as MarkdownTextInputDecoractorView from './MarkdownTextInputDecoratorViewNativeComponent';
import MarkdownTextInputDecoratorViewNativeComponent from './MarkdownTextInputDecoratorViewNativeComponent';
import type * as MarkdownTextInputDecoratorViewNativeComponentTypes from './MarkdownTextInputDecoratorViewNativeComponent';
import * as StyleUtils from './styleUtils';
import type * as StyleUtilsTypes from './styleUtils';

type MarkdownStyle = MarkdownTextInputDecoractorView.MarkdownStyle;

const FONT_FAMILY_MONOSPACE = Platform.select({
ios: 'Courier',
default: 'monospace',
});

function makeDefaultMarkdownStyle(): MarkdownStyle {
return {
syntax: {
color: 'gray',
},
link: {
color: 'blue',
},
h1: {
fontSize: 25,
},
blockquote: {
borderColor: 'gray',
borderWidth: 6,
marginLeft: 6,
paddingLeft: 6,
},
code: {
fontFamily: FONT_FAMILY_MONOSPACE,
color: 'black',
backgroundColor: 'lightgray',
},
pre: {
fontFamily: FONT_FAMILY_MONOSPACE,
color: 'black',
backgroundColor: 'lightgray',
},
mentionHere: {
color: 'green',
backgroundColor: 'lime',
},
mentionUser: {
color: 'blue',
backgroundColor: 'cyan',
},
};
}
type PartialMarkdownStyle = StyleUtilsTypes.PartialMarkdownStyle;
type MarkdownStyle = MarkdownTextInputDecoratorViewNativeComponentTypes.MarkdownStyle;

type PartialMarkdownStyle = Partial<{
[K in keyof MarkdownStyle]: Partial<MarkdownStyle[K]>;
}>;

function mergeMarkdownStyleWithDefault(input: PartialMarkdownStyle | undefined): MarkdownStyle {
const output = makeDefaultMarkdownStyle();

if (input !== undefined) {
Object.keys(input).forEach((key) => {
if (!(key in output)) {
return;
}
Object.assign(output[key as keyof MarkdownStyle], input[key as keyof MarkdownStyle]);
});
}

return output;
interface MarkdownTextInputProps extends TextInputProps {
markdownStyle?: PartialMarkdownStyle;
}

function processColorsInMarkdownStyle(input: MarkdownStyle): MarkdownStyle {
Expand All @@ -87,11 +31,7 @@ function processColorsInMarkdownStyle(input: MarkdownStyle): MarkdownStyle {
}

function processMarkdownStyle(input: PartialMarkdownStyle | undefined): MarkdownStyle {
return processColorsInMarkdownStyle(mergeMarkdownStyleWithDefault(input));
}

interface MarkdownTextInputProps extends TextInputProps {
markdownStyle?: PartialMarkdownStyle;
return processColorsInMarkdownStyle(StyleUtils.mergeMarkdownStyleWithDefault(input));
}

const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>((props, ref) => {
Expand Down
Loading
Loading