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

[No QA][TS migration] Migrate 'Clipboard' lib to TypeScript #28789

Merged
merged 18 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
18 changes: 0 additions & 18 deletions src/libs/Clipboard/index.native.js

This file was deleted.

19 changes: 19 additions & 0 deletions src/libs/Clipboard/index.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import RNCClipboard from '@react-native-community/clipboard';
import {SetString, Clipboard} from './types';

/**
* Sets a string on the Clipboard object via @react-native-community/clipboard
*/
const setString: SetString = (text) => {
RNCClipboard.setString(text);
};

const clipboard: Clipboard = {
setString,

// We don't want to set HTML on native platforms so noop them.
canSetHtml: () => false,
setHtml: () => {},
teneeto marked this conversation as resolved.
Show resolved Hide resolved
};

export default clipboard;
86 changes: 56 additions & 30 deletions src/libs/Clipboard/index.js → src/libs/Clipboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
// on Web/desktop this import will be replaced with `react-native-web`
import {Clipboard} from 'react-native-web';
import lodashGet from 'lodash/get';
import {Clipboard as RNWClipboard} from 'react-native-web';
import CONST from '../../CONST';
import * as Browser from '../Browser';
import {SetString, Clipboard} from './types';

const canSetHtml = () => lodashGet(navigator, 'clipboard.write');
type ComposerSelection = {
start: number;
end: number;
direction: 'forward' | 'backward' | 'none';
};

type AnchorSelection = {
anchorOffset: number;
focusOffset: number;
anchorNode: Node;
focusNode: Node;
};

type Nullable<T> = {[K in keyof T]: T[K] | null};
teneeto marked this conversation as resolved.
Show resolved Hide resolved
type OriginalSelection = ComposerSelection | Partial<Nullable<AnchorSelection>>;

/*
* @param {this: void} object The object to query.
teneeto marked this conversation as resolved.
Show resolved Hide resolved
*/

const canSetHtml =
() =>
(...args: ClipboardItems) =>
navigator?.clipboard?.write([...args]);

/**
* Deprecated method to write the content as HTML to clipboard.
* @param {String} html HTML representation
* @param {String} text Plain text representation
* @param HTML representation
teneeto marked this conversation as resolved.
Show resolved Hide resolved
* @param Plain text representation
teneeto marked this conversation as resolved.
Show resolved Hide resolved
*/
function setHTMLSync(html, text) {
function setHTMLSync(html: string, text: string) {
const node = document.createElement('span');
node.textContent = html;
node.style.all = 'unset';
Expand All @@ -22,16 +45,16 @@ function setHTMLSync(html, text) {
node.addEventListener('copy', (e) => {
e.stopPropagation();
e.preventDefault();
e.clipboardData.clearData();
e.clipboardData.setData('text/html', html);
e.clipboardData.setData('text/plain', text);
e.clipboardData?.clearData();
e.clipboardData?.setData('text/html', html);
e.clipboardData?.setData('text/plain', text);
});
document.body.appendChild(node);

const selection = window.getSelection();
const firstAnchorChild = selection.anchorNode && selection.anchorNode.firstChild;
const selection = window?.getSelection();
const firstAnchorChild = selection?.anchorNode?.firstChild;
const isComposer = firstAnchorChild instanceof HTMLTextAreaElement;
let originalSelection = null;
let originalSelection: OriginalSelection | null = null;
if (isComposer) {
originalSelection = {
start: firstAnchorChild.selectionStart,
Expand All @@ -40,17 +63,17 @@ function setHTMLSync(html, text) {
};
} else {
originalSelection = {
anchorNode: selection.anchorNode,
anchorOffset: selection.anchorOffset,
focusNode: selection.focusNode,
focusOffset: selection.focusOffset,
anchorNode: selection?.anchorNode,
anchorOffset: selection?.anchorOffset,
focusNode: selection?.focusNode,
focusOffset: selection?.focusOffset,
};
}

selection.removeAllRanges();
selection?.removeAllRanges();
const range = document.createRange();
range.selectNodeContents(node);
selection.addRange(range);
selection?.addRange(range);

teneeto marked this conversation as resolved.
Show resolved Hide resolved
try {
document.execCommand('copy');
Expand All @@ -59,23 +82,25 @@ function setHTMLSync(html, text) {
// See https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#the-copy-command for more details.
}

selection.removeAllRanges();
selection?.removeAllRanges();

if (isComposer) {
firstAnchorChild.setSelectionRange(originalSelection.start, originalSelection.end, originalSelection.direction);
const composerSelection = originalSelection as ComposerSelection;
teneeto marked this conversation as resolved.
Show resolved Hide resolved
firstAnchorChild.setSelectionRange(composerSelection.start, composerSelection.end, composerSelection.direction);
} else {
selection.setBaseAndExtent(originalSelection.anchorNode, originalSelection.anchorOffset, originalSelection.focusNode, originalSelection.focusOffset);
const anchorSelection = originalSelection as AnchorSelection;
selection?.setBaseAndExtent(anchorSelection.anchorNode, anchorSelection.anchorOffset, anchorSelection.focusNode, anchorSelection.focusOffset);
}

document.body.removeChild(node);
}

/**
* Writes the content as HTML if the web client supports it.
* @param {String} html HTML representation
* @param {String} text Plain text representation
* @param HTML representation
teneeto marked this conversation as resolved.
Show resolved Hide resolved
* @param Plain text representation
teneeto marked this conversation as resolved.
Show resolved Hide resolved
*/
const setHtml = (html, text) => {
const setHtml = (html: string, text: string) => {
if (!html || !text) {
return;
}
Expand All @@ -92,9 +117,10 @@ const setHtml = (html, text) => {
setHTMLSync(html, text);
} else {
navigator.clipboard.write([
// eslint-disable-next-line no-undef
new ClipboardItem({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/html': new Blob([html], {type: 'text/html'}),
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/plain': new Blob([text], {type: 'text/plain'}),
teneeto marked this conversation as resolved.
Show resolved Hide resolved
}),
]);
Expand All @@ -103,15 +129,15 @@ const setHtml = (html, text) => {

/**
* Sets a string on the Clipboard object via react-native-web
*
* @param {String} text
*/
const setString = (text) => {
Clipboard.setString(text);
const setString: SetString = (text) => {
RNWClipboard.setString(text);
};

export default {
const clipboard: Clipboard = {
setString,
canSetHtml,
setHtml,
};

export default clipboard;
9 changes: 9 additions & 0 deletions src/libs/Clipboard/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type SetString = (text: string) => void;

type Clipboard = {
setString: SetString;
canSetHtml: () => void;
setHtml: (html: string, text: string) => void;
teneeto marked this conversation as resolved.
Show resolved Hide resolved
};

export type {SetString, Clipboard};
7 changes: 7 additions & 0 deletions src/types/modules/react-native-web.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
declare module 'react-native-web' {
type SetString = (text: string) => void;

const Clipboard: {
setString: SetString;
};
}
teneeto marked this conversation as resolved.
Show resolved Hide resolved
Loading