Bluesky app fork with some witchin' additions 馃挮 witchsky.app
bluesky fork client
120
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a876aae44ea07494ebea9727350aa060b81f317b 92 lines 3.6 kB view raw
1import {type AppBskyRichtextFacet, type RichText} from '@atproto/api' 2 3export type LinkFacetMatch = { 4 rt: RichText 5 facet: AppBskyRichtextFacet.Main 6} 7 8export function suggestLinkCardUri( 9 suggestLinkImmediately: boolean, 10 nextDetectedUris: Map<string, LinkFacetMatch>, 11 prevDetectedUris: Map<string, LinkFacetMatch>, 12 pastSuggestedUris: Set<string>, 13): string | undefined { 14 const suggestedUris = new Set<string>() 15 for (const [uri, nextMatch] of nextDetectedUris) { 16 if (!isValidUrlAndDomain(uri)) { 17 continue 18 } 19 if (pastSuggestedUris.has(uri)) { 20 // Don't suggest already added or already dismissed link cards. 21 continue 22 } 23 if (suggestLinkImmediately) { 24 // Immediately add the pasted or intent-prefilled link without waiting to type more. 25 suggestedUris.add(uri) 26 continue 27 } 28 const prevMatch = prevDetectedUris.get(uri) 29 if (!prevMatch) { 30 // If the same exact link wasn't already detected during the last keystroke, 31 // it means you're probably still typing it. Disregard until it stabilizes. 32 continue 33 } 34 const prevTextAfterUri = prevMatch.rt.unicodeText.slice( 35 prevMatch.facet.index.byteEnd, 36 ) 37 const nextTextAfterUri = nextMatch.rt.unicodeText.slice( 38 nextMatch.facet.index.byteEnd, 39 ) 40 if (prevTextAfterUri === nextTextAfterUri) { 41 // The text you're editing is before the link, e.g. 42 // "abc google.com" -> "abcd google.com". 43 // This is a good time to add the link. 44 suggestedUris.add(uri) 45 continue 46 } 47 if (/^\s/m.test(nextTextAfterUri)) { 48 // The link is followed by a space, e.g. 49 // "google.com" -> "google.com " or 50 // "google.com." -> "google.com ". 51 // This is a clear indicator we can linkify it. 52 suggestedUris.add(uri) 53 continue 54 } 55 if ( 56 /^[)]?[.,:;!?)](\s|$)/m.test(prevTextAfterUri) && 57 /^[)]?[.,:;!?)]\s/m.test(nextTextAfterUri) 58 ) { 59 // The link was *already* being followed by punctuation, 60 // and now it's followed both by punctuation and a space. 61 // This means you're typing after punctuation, e.g. 62 // "google.com." -> "google.com. " or 63 // "google.com.foo" -> "google.com. foo". 64 // This means you're not typing the link anymore, so we can linkify it. 65 suggestedUris.add(uri) 66 continue 67 } 68 } 69 for (const uri of pastSuggestedUris) { 70 if (!nextDetectedUris.has(uri)) { 71 // If a link is no longer detected, it's eligible for suggestions next time. 72 pastSuggestedUris.delete(uri) 73 } 74 } 75 76 let suggestedUri: string | undefined 77 if (suggestedUris.size > 0) { 78 suggestedUri = Array.from(suggestedUris)[0] 79 pastSuggestedUris.add(suggestedUri) 80 } 81 82 return suggestedUri 83} 84 85// https://stackoverflow.com/questions/8667070/javascript-regular-expression-to-validate-url 86// question credit Muhammad Imran Tariq https://stackoverflow.com/users/420613/muhammad-imran-tariq 87// answer credit Christian David https://stackoverflow.com/users/967956/christian-david 88function isValidUrlAndDomain(value: string) { 89 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( 90 value, 91 ) 92}