diff options
Diffstat (limited to 'src/view/com/composer/text-input/text-input-util.ts')
-rw-r--r-- | src/view/com/composer/text-input/text-input-util.ts | 123 |
1 files changed, 78 insertions, 45 deletions
diff --git a/src/view/com/composer/text-input/text-input-util.ts b/src/view/com/composer/text-input/text-input-util.ts index 8119e429c..cbe8ef6af 100644 --- a/src/view/com/composer/text-input/text-input-util.ts +++ b/src/view/com/composer/text-input/text-input-util.ts @@ -1,41 +1,85 @@ -export function addLinkCardIfNecessary({ - uri, - newText, - cursorLocation, - mayBePaste, - onNewLink, - prevAddedLinks, -}: { - uri: string - newText: string - cursorLocation: number - mayBePaste: boolean - onNewLink: (uri: string) => void - prevAddedLinks: Set<string> -}) { - // 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 +import {AppBskyRichtextFacet, RichText} from '@atproto/api' - // 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 +export type LinkFacetMatch = { + rt: RichText + facet: AppBskyRichtextFacet.Main +} - // 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) +export function suggestLinkCardUri( + mayBePaste: boolean, + nextDetectedUris: Map<string, LinkFacetMatch>, + prevDetectedUris: Map<string, LinkFacetMatch>, + pastSuggestedUris: Set<string>, +): string | undefined { + const suggestedUris = new Set<string>() + for (const [uri, nextMatch] of nextDetectedUris) { + if (!isValidUrlAndDomain(uri)) { + continue + } + if (pastSuggestedUris.has(uri)) { + // Don't suggest already added or already dismissed link cards. + continue + } + if (mayBePaste) { + // Immediately add the pasted link without waiting to type more. + suggestedUris.add(uri) + continue + } + const prevMatch = prevDetectedUris.get(uri) + if (!prevMatch) { + // If the same exact link wasn't already detected during the last keystroke, + // it means you're probably still typing it. Disregard until it stabilizes. + continue + } + const prevTextAfterUri = prevMatch.rt.unicodeText.slice( + prevMatch.facet.index.byteEnd, + ) + const nextTextAfterUri = nextMatch.rt.unicodeText.slice( + nextMatch.facet.index.byteEnd, + ) + if (prevTextAfterUri === nextTextAfterUri) { + // The text you're editing is before the link, e.g. + // "abc google.com" -> "abcd google.com". + // This is a good time to add the link. + suggestedUris.add(uri) + continue + } + if (/^\s/m.test(nextTextAfterUri)) { + // The link is followed by a space, e.g. + // "google.com" -> "google.com " or + // "google.com." -> "google.com ". + // This is a clear indicator we can linkify it. + suggestedUris.add(uri) + continue + } + if ( + /^[)]?[.,:;!?)](\s|$)/m.test(prevTextAfterUri) && + /^[)]?[.,:;!?)]\s/m.test(nextTextAfterUri) + ) { + // The link was *already* being followed by punctuation, + // and now it's followed both by punctuation and a space. + // This means you're typing after punctuation, e.g. + // "google.com." -> "google.com. " or + // "google.com.foo" -> "google.com. foo". + // This means you're not typing the link anymore, so we can linkify it. + suggestedUris.add(uri) + continue + } + } + for (const uri of pastSuggestedUris) { + if (!nextDetectedUris.has(uri)) { + // If a link is no longer detected, it's eligible for suggestions next time. + pastSuggestedUris.delete(uri) + } + } - if (shouldCheck) { - onNewLink(uri) - prevAddedLinks.add(uri) + let suggestedUri: string | undefined + if (suggestedUris.size > 0) { + suggestedUri = Array.from(suggestedUris)[0] + pastSuggestedUris.add(suggestedUri) } + + return suggestedUri } // https://stackoverflow.com/questions/8667070/javascript-regular-expression-to-validate-url @@ -46,14 +90,3 @@ function isValidUrlAndDomain(value: string) { 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 -} |