From 046e11de31a9e6ddda32811b1efab52f9c221616 Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 16 Apr 2024 14:29:32 -0700 Subject: Automatically add a link card for URLs in the composer (#3566) * automatically add a link card for urls in the composer simplify was paste check use a set simplify the cross platform reuse web implementation remove log pasting in the middle of a block of text proper regex dont re-add immediately after paste and remove don't use `byteIndex` lfg automatically add link card * `mayBePaste` * remove accidentally pasted url from comment --- .../com/composer/text-input/text-input-util.ts | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/view/com/composer/text-input/text-input-util.ts (limited to 'src/view/com/composer/text-input/text-input-util.ts') diff --git a/src/view/com/composer/text-input/text-input-util.ts b/src/view/com/composer/text-input/text-input-util.ts new file mode 100644 index 000000000..8119e429c --- /dev/null +++ b/src/view/com/composer/text-input/text-input-util.ts @@ -0,0 +1,59 @@ +export function addLinkCardIfNecessary({ + uri, + newText, + cursorLocation, + mayBePaste, + onNewLink, + prevAddedLinks, +}: { + uri: string + newText: string + cursorLocation: number + mayBePaste: boolean + onNewLink: (uri: string) => void + prevAddedLinks: Set +}) { + // It would be cool if we could just use facet.index.byteEnd, but you know... *upside down smiley* + const lastCharacterPosition = findIndexInText(uri, newText) + uri.length + + // If the text being added is not from a paste, then we should only check if the cursor is one + // position ahead of the last character. However, if it is a paste we need to check both if it's + // the same position _or_ one position ahead. That is because iOS will add a space after a paste if + // pasting into the middle of a sentence! + const cursorLocationIsOkay = + cursorLocation === lastCharacterPosition + 1 || mayBePaste + + // Checking previouslyAddedLinks keeps a card from getting added over and over i.e. + // Link card added -> Remove link card -> Press back space -> Press space -> Link card added -> and so on + + // We use the isValidUrl regex below because we don't want to add embeds only if the url is valid, i.e. + // http://facebook is a valid url, but that doesn't mean we want to embed it. We should only embed if + // the url is a valid url _and_ domain. new URL() won't work for this check. + const shouldCheck = + cursorLocationIsOkay && !prevAddedLinks.has(uri) && isValidUrlAndDomain(uri) + + if (shouldCheck) { + onNewLink(uri) + prevAddedLinks.add(uri) + } +} + +// https://stackoverflow.com/questions/8667070/javascript-regular-expression-to-validate-url +// question credit Muhammad Imran Tariq https://stackoverflow.com/users/420613/muhammad-imran-tariq +// answer credit Christian David https://stackoverflow.com/users/967956/christian-david +function isValidUrlAndDomain(value: string) { + return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( + value, + ) +} + +export function findIndexInText(term: string, text: string) { + // This should find patterns like: + // HELLO SENTENCE http://google.com/ HELLO + // HELLO SENTENCE http://google.com HELLO + // http://google.com/ HELLO. + // http://google.com/. + const pattern = new RegExp(`\\b(${term})(?![/w])`, 'i') + const match = pattern.exec(text) + return match ? match.index : -1 +} -- cgit 1.4.1