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

Inline images feature #462

Merged
merged 116 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
f853061
Add refactor changes
Skalakid Jun 25, 2024
c4108c1
Fix parserUtils
Skalakid Jun 25, 2024
38693c4
Add tree building when creating markdown HTML structure
Skalakid Jun 26, 2024
6a1eaf1
Refactor function and type names
Skalakid Jun 26, 2024
75d6afd
Fix TS errors
Skalakid Jun 26, 2024
db68b78
Update parser structure
Skalakid Jun 26, 2024
31711ab
Move BrowserUtils into an object
Skalakid Jun 26, 2024
cab8ca7
Move utils to separate folder
Skalakid Jun 26, 2024
2f829cc
Add block utils
Skalakid Jun 26, 2024
b9b2984
Move functions from above the web component to utils
Skalakid Jun 26, 2024
3fbc456
Fix unit tests
Skalakid Jun 27, 2024
d74e0e4
Fix cursor positioning bugs
Skalakid Jun 28, 2024
220395b
Remove scrollCursorIntoView function
Skalakid Jun 28, 2024
4d95382
Rename variable names
Skalakid Jun 28, 2024
907c73b
Replace textContent with ref value
Skalakid Jun 28, 2024
1017ebc
Merge branch 'main' into @Skalakid/web-parser-refactor
Skalakid Jun 28, 2024
1f1fefd
Merge branch 'main' into @Skalakid/web-parser-refactor
Skalakid Jul 1, 2024
ff41dd7
Fix crashes and cursor positioning in E/App
Skalakid Jul 1, 2024
d1d8a49
Fix copying and pasting text with markdown
Skalakid Jul 1, 2024
153ead8
Fix pasting text starting with newlines
Skalakid Jul 1, 2024
ea6aad8
Fix errors when replacing text
Skalakid Jul 2, 2024
87953bb
Fix HTML injestions and pasted text parsing
Skalakid Jul 2, 2024
3d30621
Fix paste text trimming condiftion
Skalakid Jul 2, 2024
471f53a
Fix cutting text
Skalakid Jul 2, 2024
92a224c
Change handlePaste logic to fix newlines in pasted text
Skalakid Jul 2, 2024
c14add4
Fix cursor positioning when undoing/redoing previously pasted text
Skalakid Jul 2, 2024
e9d8ec3
Fix text coloring
Skalakid Jul 2, 2024
e24d259
Add review changes
Skalakid Jul 3, 2024
092775d
Move updateTextColor function
Skalakid Jul 3, 2024
a0c1dfa
Fix cursor positioning when changing text and styles at the same time
Skalakid Jul 3, 2024
d04ad32
Fix newlines on FireFox
Skalakid Jul 3, 2024
63de148
Fix cursor position value update when entering newline inside codeblock
Skalakid Jul 3, 2024
f06d10f
Fix removing characters when cursor is at the beginning of the line o…
Skalakid Jul 3, 2024
34eabe2
Fix writing in empty line on Firefox
Skalakid Jul 3, 2024
a52e3f5
Fix cursor position value on Cmd+A on FireFox
Skalakid Jul 3, 2024
b5515cf
Fix getting value in e2e tests
Skalakid Jul 4, 2024
5a043a3
Fix input e2e tests
Skalakid Jul 4, 2024
5a1dae2
Fix style e2e tests
Skalakid Jul 4, 2024
60f4f4b
Fix text manipulation e2e tests
Skalakid Jul 4, 2024
55e78b4
Update checkCursorPosition function in e2e tests
Skalakid Jul 4, 2024
8a0a7be
Update checkCursorPosition function in e2e tests
Skalakid Jul 4, 2024
96a9b5c
Fix cursor position after redoing pasted text
Skalakid Jul 5, 2024
8c85473
Merge branch '@Skalakid/web-parser-refactor' of github.com:Expensify/…
Skalakid Jul 5, 2024
401febe
Merge branch 'main' into @Skalakid/web-parser-refactor
Skalakid Jul 5, 2024
862e74a
Fix e2e tests on CI/CD
Skalakid Jul 5, 2024
0580f45
Uncomment undo test
Skalakid Jul 5, 2024
6682c9d
Fix TS errors
Skalakid Jul 5, 2024
8165fb9
Change line merging funciton
Skalakid Jul 5, 2024
9e2b943
Fix selection event sending on paste
Skalakid Jul 5, 2024
0f5b86a
Merge branch 'main' into @Skalakid/web-parser-refactor
Skalakid Jul 8, 2024
5f369ec
Fix scrolling cursor into view on Safari browser
Skalakid Jul 8, 2024
7cdaf9a
Merge branch 'main' into @Skalakid/web-parser-refactor
Skalakid Jul 9, 2024
6bada78
Enhance cursor positioning on input
Skalakid Jul 9, 2024
d9c097d
Merge branch 'main' into @Skalakid/web-parser-refactor
Skalakid Jul 10, 2024
309bccf
Fix diacritics after CMD+A
Skalakid Jul 10, 2024
90d2ea3
Fix autocorrect cursor positioning
Skalakid Jul 10, 2024
111fd6b
Fix deleting codeBlock lines with CMD+backspace
Skalakid Jul 10, 2024
d14f4ba
Fix text color on undo/redo
Skalakid Jul 10, 2024
82d58ca
Fix removing last letter from the line
Skalakid Jul 10, 2024
2b9e56a
Fix cursor position when replacing text with the same text
Skalakid Jul 11, 2024
5861a21
Change parseInnerHTMLToText function
Skalakid Jul 15, 2024
68ae9be
Fix input behavior when interracting with display: block element
Skalakid Jul 15, 2024
0bc882a
Fix getTreeNodeByIndex function
Skalakid Jul 16, 2024
cc8e307
Fix replacing whole content of the input
Skalakid Jul 16, 2024
3da3989
Fix set cursor position when content changes
Skalakid Jul 16, 2024
ccb251e
Fix removing selection on paste
Skalakid Jul 16, 2024
e759170
Fix set cursor position on paste
Skalakid Jul 16, 2024
4db4441
Fix dissapearing cursor bug
Skalakid Jul 16, 2024
0480cb8
Fix pasting text into empty input
Skalakid Jul 16, 2024
5faac50
Fix newline deletion
Skalakid Jul 17, 2024
14d0eed
Improve parseInnerHTMLToText function and fix pasted text correct val…
Skalakid Jul 18, 2024
0c204f5
Remove buildTree function
Skalakid Jul 18, 2024
de12aca
Fix tests
Skalakid Jul 18, 2024
8648205
Replacing text by text cursor position
Skalakid Jul 19, 2024
eb1502a
Fix cursor positioning on custom text pasting (E/App)
Skalakid Jul 19, 2024
3486303
Add function comments
Skalakid Jul 19, 2024
a1bf047
fix: windows emoji picker selection
BartoszGrajdek Aug 2, 2024
b7db466
Add inline image preview
Skalakid Aug 13, 2024
7f3d720
Update inline image ranges after parsing
Skalakid Aug 14, 2024
cdccda0
Fix text and other markdown align when inline image markdown is present
Skalakid Aug 14, 2024
7556a7f
Add image preview scaling
Skalakid Aug 14, 2024
2070261
Update project structure
Skalakid Aug 14, 2024
b67fd7b
Fix bug with duplicating block content
Skalakid Aug 21, 2024
fadc909
Add element swapping to fix image flickering issue
Skalakid Aug 21, 2024
991f98f
Merge branch 'main' into @Skalakid/inline-images-feature
Skalakid Aug 21, 2024
c5fb730
Fix inline images in blockquotes
Skalakid Aug 21, 2024
32293e8
Add ability to customize inline image style
Skalakid Aug 21, 2024
e1f47b3
Add customable loading indicators for image previews
Skalakid Aug 23, 2024
5d12fda
Fix loading expired image requests
Skalakid Aug 27, 2024
dbb2a9c
Limit element search only for only in the current input
Skalakid Aug 27, 2024
b08bc5a
Fix image loading indicator animation
Skalakid Aug 29, 2024
ade3501
Clean up the code
Skalakid Aug 29, 2024
ee19f1f
Fix input behavior on text behind the inline image
Skalakid Aug 30, 2024
c6c70c1
Merge branch 'main' into @Skalakid/inline-images-feature
Skalakid Aug 30, 2024
1992975
Fix tests
Skalakid Aug 30, 2024
8f526b0
Update parser
Skalakid Aug 30, 2024
7aa794c
Fix tests
Skalakid Aug 30, 2024
49acf31
Clean up the code
Skalakid Aug 30, 2024
41f10bf
Change EXAMPLE_CONTENT
Skalakid Aug 30, 2024
dbb9d57
Merge branch 'main' into @Skalakid/inline-images-feature
Skalakid Sep 2, 2024
940f11f
Update default example input value
Skalakid Sep 2, 2024
e0a1c7b
Enhance inline image prieves structure
Skalakid Sep 2, 2024
4901270
Fix disappearing cursor when navigating by keyboard arrows to the end…
Skalakid Sep 2, 2024
ed5e9c5
Fix cursor position on pasting inline image preview
Skalakid Sep 2, 2024
be39222
Fix changing content structure on image drag and drop
Skalakid Sep 2, 2024
d005eae
Fix inline image preview on error
Skalakid Sep 5, 2024
20b6a32
Merge branch 'main' into @Skalakid/inline-images-feature
Skalakid Sep 9, 2024
9fd782d
Fix image position when deleting new line
Skalakid Sep 9, 2024
76c119e
Add review changes
Skalakid Sep 9, 2024
b16b974
Fix image position when removing new line before heading markdown
Skalakid Sep 10, 2024
4cc4869
Fix input value parsing when removing lines when preview is at the end
Skalakid Sep 10, 2024
581681b
Fix selection
Skalakid Sep 10, 2024
30f8a63
Remove inline image preview when input is single line
Skalakid Sep 10, 2024
dcfb0b0
Add animation state preserving between rerenders
Skalakid Sep 11, 2024
d9bb184
Merge branch 'main' into @Skalakid/inline-images-feature
Skalakid Sep 11, 2024
5206134
Merge branch 'main' into @Skalakid/inline-images-feature
Skalakid Sep 12, 2024
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
2 changes: 1 addition & 1 deletion WebExample/__tests__/styles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ test.describe('markdown content styling', () => {

test('blockquote', async ({page, browserName}) => {
const blockquoteStyle =
'border-color: gray; border-width: 6px; margin-left: 6px; padding-left: 6px; border-left-style: solid; display: inline-block; max-width: 100%; box-sizing: border-box;';
'border-color: gray; border-width: 6px; margin-left: 6px; padding-left: 6px; border-left-style: solid; display: inline-block; max-width: 100%; box-sizing: border-box; overflow-wrap: anywhere;';
tomekzaw marked this conversation as resolved.
Show resolved Hide resolved

// Firefox border properties are serialized slightly differently
const browserStyle = browserName === 'firefox' ? blockquoteStyle.replace('border-left-style: solid', 'border-left: 6px solid gray') : blockquoteStyle;
Expand Down
1 change: 1 addition & 0 deletions example/src/testConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const EXAMPLE_CONTENT = [
'@here',
'@someone@swmansion.com',
'#mention-report',
'![demo image](https://picsum.photos/id/1069/200/300)',
].join('\n');

const INPUT_ID = 'MarkdownInput_Example';
Expand Down
12 changes: 12 additions & 0 deletions parser/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ describe('trailing whitespace', () => {
describe('inline image', () => {
test('with alt text', () => {
expect('![test](https://example.com/image.png)').toBeParsedAs([
{type: 'inline-image', start: 0, length: 38},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 1},
{type: 'syntax', start: 6, length: 1},
Expand All @@ -412,6 +413,7 @@ describe('inline image', () => {

test('without alt text', () => {
expect('![](https://example.com/image.png)').toBeParsedAs([
{type: 'inline-image', start: 0, length: 34},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 1},
{type: 'syntax', start: 2, length: 1},
Expand All @@ -423,6 +425,7 @@ describe('inline image', () => {

test('with same alt text as src', () => {
expect('![https://example.com/image.png](https://example.com/image.png)').toBeParsedAs([
{type: 'inline-image', start: 0, length: 63},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 1},
{type: 'syntax', start: 31, length: 1},
Expand All @@ -434,12 +437,14 @@ describe('inline image', () => {

test('text containing images', () => {
expect('An image of a banana: ![banana](https://example.com/banana.png) an image of a developer: ![dev](https://example.com/developer.png)').toBeParsedAs([
{type: 'inline-image', start: 22, length: 41},
{type: 'syntax', start: 22, length: 1},
{type: 'syntax', start: 23, length: 1},
{type: 'syntax', start: 30, length: 1},
{type: 'syntax', start: 31, length: 1},
{type: 'link', start: 32, length: 30},
{type: 'syntax', start: 62, length: 1},
{type: 'inline-image', start: 89, length: 41},
{type: 'syntax', start: 89, length: 1},
{type: 'syntax', start: 90, length: 1},
{type: 'syntax', start: 94, length: 1},
Expand All @@ -451,6 +456,7 @@ describe('inline image', () => {

test('with alt text containing markdown', () => {
expect('![# fake-heading *bold* _italic_ ~strike~ [:-)]](https://example.com/image.png)').toBeParsedAs([
{type: 'inline-image', start: 0, length: 79},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 1},
{type: 'syntax', start: 47, length: 1},
Expand All @@ -462,6 +468,7 @@ describe('inline image', () => {

test('text containing image and autolink', () => {
expect('An image of a banana: ![banana](https://example.com/banana.png) an autolink: example.com').toBeParsedAs([
{type: 'inline-image', start: 22, length: 41},
{type: 'syntax', start: 22, length: 1},
{type: 'syntax', start: 23, length: 1},
{type: 'syntax', start: 30, length: 1},
Expand All @@ -482,6 +489,7 @@ describe('inline image', () => {

test('trying to inject additional attributes', () => {
expect('![test" onerror="alert(\'xss\')](https://example.com/image.png)').toBeParsedAs([
{type: 'inline-image', start: 0, length: 61},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 1},
{type: 'syntax', start: 29, length: 1},
Expand All @@ -493,6 +501,7 @@ describe('inline image', () => {

test('inline code in alt', () => {
expect('![`code`](https://example.com/image.png)').toBeParsedAs([
{type: 'inline-image', start: 0, length: 40},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 1},
{type: 'syntax', start: 8, length: 1},
Expand All @@ -504,6 +513,7 @@ describe('inline image', () => {

test('blockquote in alt', () => {
expect('![```test```](https://example.com/image.png)').toBeParsedAs([
{type: 'inline-image', start: 0, length: 44},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 1},
{type: 'syntax', start: 12, length: 1},
Expand All @@ -515,6 +525,7 @@ describe('inline image', () => {

test('image without alt text', () => {
expect('!(https://example.com/image.png)').toBeParsedAs([
{type: 'inline-image', start: 0, length: 32},
{type: 'syntax', start: 0, length: 1},
{type: 'syntax', start: 1, length: 1},
{type: 'link', start: 2, length: 29},
Expand All @@ -526,6 +537,7 @@ describe('inline image', () => {
expect('# ![](example.com)').toBeParsedAs([
{type: 'syntax', start: 0, length: 2},
{type: 'h1', start: 2, length: 16},
{type: 'inline-image', start: 2, length: 16},
{type: 'syntax', start: 2, length: 1},
{type: 'syntax', start: 3, length: 1},
{type: 'syntax', start: 4, length: 1},
Expand Down
20 changes: 19 additions & 1 deletion parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@
import ExpensiMark from 'expensify-common/dist/ExpensiMark';
import {unescapeText} from './utils';

type MarkdownType = 'bold' | 'italic' | 'strikethrough' | 'emoji' | 'mention-here' | 'mention-user' | 'mention-report' | 'link' | 'code' | 'pre' | 'blockquote' | 'h1' | 'syntax';
type MarkdownType =
| 'bold'
| 'italic'
| 'strikethrough'
| 'emoji'
| 'mention-here'
| 'mention-user'
| 'mention-report'
| 'link'
| 'code'
| 'pre'
| 'blockquote'
| 'h1'
| 'syntax'
| 'inline-image';
type MarkdownRange = {
type: MarkdownType;
start: number;
Expand Down Expand Up @@ -182,6 +196,10 @@ function parseTreeToTextAndRanges(tree: StackItem): [string, MarkdownRange[]] {
const rawLink = node.tag.match(/data-raw-href="([^"]*)"/);
const linkString = rawLink ? unescapeText(rawLink[1]!) : src;

const start = text.length;
const length = 3 + (hasAlt ? 2 + unescapeText(alt?.[1] || '').length : 0) + linkString.length;
ranges.push({type: 'inline-image', start, length});

appendSyntax('!');
if (hasAlt) {
appendSyntax('[');
Expand Down
30 changes: 15 additions & 15 deletions parser/react-native-live-markdown-parser.js

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions src/MarkdownTextInput.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(
if (text === null) {
return {text: divRef.current.value, cursorPosition: null};
}
const parsedText = updateInputStructure(target, text, cursorPosition, customMarkdownStyles, false, shouldForceDOMUpdate);
const parsedText = updateInputStructure(target, text, cursorPosition, multiline, customMarkdownStyles, false, shouldForceDOMUpdate);
divRef.current.value = parsedText.text;

if (history.current && shouldAddToHistory) {
Expand All @@ -158,7 +158,7 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(

return parsedText;
},
[],
[multiline],
);

const processedMarkdownStyle = useMemo(() => {
Expand Down Expand Up @@ -287,7 +287,9 @@ const MarkdownTextInput = React.forwardRef<TextInput, MarkdownTextInputProps>(

updateTextColor(divRef.current, e.target.textContent ?? '');
const previousText = divRef.current.value;
const parsedText = normalizeValue(inputType === 'pasteText' ? pasteContent.current || '' : parseInnerHTMLToText(e.target as MarkdownTextInputElement));
const parsedText = normalizeValue(
inputType === 'pasteText' ? pasteContent.current || '' : parseInnerHTMLToText(e.target as MarkdownTextInputElement, inputType, contentSelection.current.start),
);

if (pasteContent.current) {
pasteContent.current = null;
Expand Down
21 changes: 21 additions & 0 deletions src/MarkdownTextInputDecoratorViewNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ interface MarkdownStyle {
color: ColorValue;
backgroundColor: ColorValue;
};
inlineImage: {
maxWidth: Float;
maxHeight: Float;
marginTop: Float;
marginBottom: Float;
};
loadingIndicatorContainer?: {
backgroundColor?: ColorValue;
borderWidth?: Float;
borderColor?: ColorValue;
borderRadius?: Float;
width?: Float;
height?: Float;
};
loadingIndicator?: {
primaryColor?: ColorValue;
secondaryColor?: ColorValue;
width?: Float;
height?: Float;
borderWidth?: Float;
};
}

interface NativeProps extends ViewProps {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/webParser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const toBeParsedAsHTML = function (actual: string, expectedHTML: string) {
let expected = expectedHTML;
const markdownRanges = global.parseExpensiMarkToRanges(actual);

const actualDOM = parseRangesToHTMLNodes(actual, markdownRanges, {}, true).dom;
const actualDOM = parseRangesToHTMLNodes(actual, markdownRanges, true, {}, true).dom;
const actualHTML = actualDOM.innerHTML;

if (actualHTML === expected) {
Expand Down
16 changes: 15 additions & 1 deletion src/commonTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
type MarkdownType = 'bold' | 'italic' | 'strikethrough' | 'emoji' | 'mention-here' | 'mention-user' | 'mention-report' | 'link' | 'code' | 'pre' | 'blockquote' | 'h1' | 'syntax';
type MarkdownType =
| 'bold'
| 'italic'
| 'strikethrough'
| 'emoji'
| 'mention-here'
| 'mention-user'
| 'mention-report'
| 'link'
| 'code'
| 'pre'
| 'blockquote'
| 'h1'
| 'syntax'
| 'inline-image';

interface MarkdownRange {
type: MarkdownType;
Expand Down
22 changes: 20 additions & 2 deletions src/styleUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ function makeDefaultMarkdownStyle(): MarkdownStyle {
color: 'red',
backgroundColor: 'pink',
},
inlineImage: {
maxWidth: 150,
maxHeight: 150,
marginTop: 5,
marginBottom: 0,
},
loadingIndicator: {
primaryColor: 'gray',
secondaryColor: 'lightgray',
},
};
}

Expand All @@ -65,13 +75,21 @@ function mergeMarkdownStyleWithDefault(input: PartialMarkdownStyle | undefined):
if (!(key in output)) {
return;
}
Object.assign(output[key as keyof MarkdownStyle], input[key as keyof MarkdownStyle]);

const outputValue = output[key as keyof MarkdownStyle];
if (outputValue) {
Object.assign(outputValue, input[key as keyof MarkdownStyle]);
}
});
}

return output;
}

function parseStringWithUnitToNumber(value: string | null): number {
return value ? parseInt(value.replace('px', ''), 10) : 0;
}

export type {PartialMarkdownStyle};

export {mergeMarkdownStyleWithDefault};
export {mergeMarkdownStyleWithDefault, parseStringWithUnitToNumber};
17 changes: 17 additions & 0 deletions src/web/MarkdownTextInput.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,20 @@
display: block; /* For Firefox */
content: attr(placeholder);
}

@keyframes react-native-live-markdown-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

.react-native-live-markdown-input-multiline [contenteditable='false'] {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
Loading
Loading