You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Hold for payment 2024-02-13] [$500] Private Note - Code block paste in private notes does not work (works fine in native apps) #29806
changed the title [-]Private Note - Code block paste in private notes does not work (works fine in native apps)[/-][+][$500] Private Note - Code block paste in private notes does not work (works fine in native apps)[/+]on Oct 17, 2023
Please re-state the problem that we are trying to solve in this issue.
Code block paste doesn't work properly in private notes text input
What is the root cause of that problem?
When user copies code block message we set the code in 'html' prop of the clipboard and the plain text (found by parsing it with ExpensiMark htmToText function) to 'text' prop of the clipboard in here when canSetHtml is true( for web only, that's why it works fine for native apps).
So when the user pastes in PrivatesNotesEditPage text input the plain text is copied by default as we have not implemented a code that pastes the html whenever the clipboard has html code inside it as we did in our Composer component.
What changes do you think we should make in order to solve the problem?
We need to take the code inside Composer component that handles paste of html code and reimplement it in a reusable code (custom hook or HOC) and then use it both in here privates notes and in composer.
So for instance we can use a custom hook like this and use it both in Private notes and Composer
function useCodePaste(textInputRef) {
const navigation = useNavigation();
/**
* Set pasted text to clipboard
* @param {String} text
*/
const paste = useCallback((text) => {
try {
document.execCommand('insertText', false, text);
// Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view.
textInputRef.current.blur();
textInputRef.current.focus();
// eslint-disable-next-line no-empty
} catch (e) {}
}, []);
/**
* Manually place the pasted HTML into Composer
*
* @param {String} html - pasted HTML
*/
const handlePastedHTML = useCallback(
(html) => {
const parser = new ExpensiMark();
paste(parser.htmlToMarkdown(html));
},
[paste],
);
/**
* Paste the plaintext content into Composer.
*
* @param {ClipboardEvent} event
*/
const handlePastePlainText = useCallback(
(event) => {
const plainText = event.clipboardData.getData('text/plain');
paste(plainText);
},
[paste],
);
const handlePaste = useCallback(
(event) => {
const isFocused = textInputRef.current.isFocused();
if (!isFocused) {
return;
}
if (textInputRef.current !== event.target) {
// To make sure the text input does not capture paste events from other inputs, we check where the event originated
// If it did originate in another input, we return early to prevent the text input from handling the paste
const isTargetInput = event.target.nodeName === 'INPUT' || event.target.nodeName === 'TEXTAREA' || event.target.contentEditable === 'true';
if (isTargetInput) {
return;
}
textInputRef.current.focus();
}
event.preventDefault();
const {types} = event.clipboardData;
const TEXT_HTML = 'text/html';
// If paste contains HTML
if (types.includes(TEXT_HTML)) {
const pastedHTML = event.clipboardData.getData(TEXT_HTML);
const domparser = new DOMParser();
const embeddedImages = domparser.parseFromString(pastedHTML, TEXT_HTML).images;
// Exclude parsing img tags in the HTML, as fetching the image via fetch triggers a connect-src Content-Security-Policy error.
if (embeddedImages.length > 0 && embeddedImages[0].src) {
// If HTML has emoji, then treat this as plain text.
if (embeddedImages[0].dataset && embeddedImages[0].dataset.stringifyType === 'emoji') {
handlePastePlainText(event);
return;
}
}
handlePastedHTML(pastedHTML);
return;
}
handlePastePlainText(event);
},
[handlePastedHTML, handlePastePlainText],
);
useEffect(() => {
// we need to handle listeners on navigation focus/blur as Composer is not unmounting
// when navigating away to different report
const unsubscribeFocus = navigation.addListener('focus', () => document.addEventListener('paste', handlePaste));
const unsubscribeBlur = navigation.addListener('blur', () => document.removeEventListener('paste', handlePaste));
if (textInputRef.current) {
document.addEventListener('paste', handlePaste);
}
return () => {
unsubscribeFocus();
unsubscribeBlur();
document.removeEventListener('paste', handlePaste);
// eslint-disable-next-line es/no-optional-chaining
if (!textInputRef.current) {
return;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}
And in both PrivateNotesEditPage and Composer we call the custom hook passing the appropriate textInputRef parameter like this (or we can also return the ref from the custom hook and assign it to the ref of our text inputs):
const {files} = event.clipboardData;
// If paste contains files, then trigger file management
if (files.length > 0) {
// Prevent the default so we do not post the file name into the text box
onPasteFile(event.clipboardData.files[0]);
return;
}
},
[onPasteFile, checkComposerVisibility],
);
useCodePaste(textInput);
(Optional) We can use also HOC (whichever is appropriate I am just trying to explain the basic idea).
*** We can also use HOC 👍 ***
Here is the result:
10.New.Expensify.1.mp4
What alternative solutions did you explore? (Optional)
Activity
[-]Private Note - Code block paste in private notes does not work (works fine in native apps)[/-][+][$500] Private Note - Code block paste in private notes does not work (works fine in native apps)[/+]melvin-bot commentedon Oct 17, 2023
Triggered auto assignment to @garrettmknight (
Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details.melvin-bot commentedon Oct 17, 2023
Job added to Upwork: https://www.upwork.com/jobs/~013e368c7fef7e59bc
melvin-bot commentedon Oct 17, 2023
Bug0 Triage Checklist (Main S/O)
Platformsin OP are ✅)melvin-bot commentedon Oct 17, 2023
Triggered auto assignment to Contributor-plus team member for initial proposal review - @ArekChr (
External)FitseTLT commentedon Oct 17, 2023
Proposal
Please re-state the problem that we are trying to solve in this issue.
Code block paste doesn't work properly in private notes text input
What is the root cause of that problem?
When user copies code block message we set the code in 'html' prop of the clipboard and the plain text (found by parsing it with ExpensiMark
htmToTextfunction) to 'text' prop of the clipboard in here whencanSetHtmlis true( for web only, that's why it works fine for native apps).App/src/pages/home/report/ContextMenu/ContextMenuActions.js
Lines 284 to 292 in 7b546b0
So when the user pastes in
PrivatesNotesEditPagetext input the plain text is copied by default as we have not implemented a code that pastes the html whenever the clipboard has html code inside it as we did in our Composer component.What changes do you think we should make in order to solve the problem?
We need to take the code inside Composer component that handles paste of html code and reimplement it in a reusable code (custom hook or HOC) and then use it both in here privates notes and in composer.
So for instance we can use a custom hook like this and use it both in Private notes and Composer
And in both PrivateNotesEditPage and Composer we call the custom hook passing the appropriate textInputRef parameter like this (or we can also return the ref from the custom hook and assign it to the ref of our text inputs):
In Composer we change
App/src/components/Composer/index.js
Lines 298 to 330 in 098157c
to
(Optional) We can use also HOC (whichever is appropriate I am just trying to explain the basic idea).
*** We can also use HOC 👍 ***
Here is the result:
10.New.Expensify.1.mp4
What alternative solutions did you explore? (Optional)
neonbhai commentedon Oct 17, 2023
Cannot reproduce on latest main
FitseTLT commentedon Oct 19, 2023
Updated comment
And also I still can reproduce it in the latest main :-
3.New.Expensify.2.mp4
127 remaining items
garrettmknight commentedon Feb 21, 2024
@Santhosh-Sellavel please fill out the checklist when you get a chance and request payment. Dropping to weekly till then.
garrettmknight commentedon Mar 1, 2024
Just waiting on checklist and payment.
garrettmknight commentedon Mar 11, 2024
@Santhosh-Sellavel can you request payment when you get a chance? The checklist was completed in the original issue.
Santhosh-Sellavel commentedon Mar 11, 2024
Feel free to close this one, I'll request later
Santhosh-Sellavel commentedon May 22, 2024
Requested on ND
JmillsExpensify commentedon May 31, 2024
$500 approved for @Santhosh-Sellavel