Bluesky app fork with some witchin' additions 馃挮
at main 126 lines 3.2 kB view raw
1import {AppBskyRichtextFacet,type RichText,UnicodeString} from '@atproto/api' 2 3import {toShortUrl} from './url-helpers' 4 5export function restoreLinks( 6 text: string, 7 facets?: AppBskyRichtextFacet.Main[], 8): string { 9 if (!facets?.length) { 10 return text 11 } 12 13 const rt = new UnicodeString(text) 14 const parts: string[] = [] 15 let lastIndex = 0 16 17 const sortedFacets = [...facets].sort( 18 (a, b) => a.index.byteStart - b.index.byteStart, 19 ) 20 21 for (const facet of sortedFacets) { 22 const isLink = facet.features.find(AppBskyRichtextFacet.isLink) 23 if (!isLink) { 24 continue 25 } 26 27 parts.push(rt.slice(lastIndex, facet.index.byteStart)) 28 parts.push(isLink.uri) 29 lastIndex = facet.index.byteEnd 30 } 31 32 parts.push(rt.slice(lastIndex)) 33 34 return parts.join('') 35} 36 37export function shortenLinks(rt: RichText): RichText { 38 if (!rt.facets?.length) { 39 return rt 40 } 41 rt = rt.clone() 42 // enumerate the link facets 43 if (rt.facets) { 44 for (const facet of rt.facets) { 45 const isLink = !!facet.features.find(AppBskyRichtextFacet.isLink) 46 if (!isLink) { 47 continue 48 } 49 50 // extract and shorten the URL 51 const {byteStart, byteEnd} = facet.index 52 const url = rt.unicodeText.slice(byteStart, byteEnd) 53 const shortened = new UnicodeString(toShortUrl(url)) 54 55 // insert the shorten URL 56 rt.insert(byteStart, shortened.utf16) 57 // update the facet to cover the new shortened URL 58 facet.index.byteStart = byteStart 59 facet.index.byteEnd = byteStart + shortened.length 60 // remove the old URL 61 rt.delete(byteStart + shortened.length, byteEnd + shortened.length) 62 } 63 } 64 return rt 65} 66 67// filter out any mention facets that didn't map to a user 68export function stripInvalidMentions(rt: RichText): RichText { 69 if (!rt.facets?.length) { 70 return rt 71 } 72 rt = rt.clone() 73 if (rt.facets) { 74 rt.facets = rt.facets?.filter(facet => { 75 const mention = facet.features.find(AppBskyRichtextFacet.isMention) 76 if (mention && !mention.did) { 77 return false 78 } 79 return true 80 }) 81 } 82 return rt 83} 84 85export function parseMarkdownLinks(text: string): { 86 text: string 87 facets: AppBskyRichtextFacet.Main[] 88} { 89 const regex = /\[([^\]]+)\]\(([^)]+)\)/g 90 let match 91 let newText = '' 92 let lastIndex = 0 93 const facets: AppBskyRichtextFacet.Main[] = [] 94 95 while ((match = regex.exec(text)) !== null) { 96 const [fullMatch, linkText, linkUrl] = match 97 const matchStart = match.index 98 newText += text.slice(lastIndex, matchStart) 99 const startByte = new UnicodeString(newText).length 100 newText += linkText 101 const endByte = new UnicodeString(newText).length 102 let validUrl = linkUrl 103 if (!validUrl.startsWith('http://') && !validUrl.startsWith('https://') && !validUrl.startsWith('mailto:')) { 104 validUrl = `https://${validUrl}` 105 } 106 107 facets.push({ 108 index: { 109 byteStart: startByte, 110 byteEnd: endByte, 111 }, 112 features: [ 113 { 114 $type: 'app.bsky.richtext.facet#link', 115 uri: validUrl, 116 }, 117 ], 118 }) 119 120 lastIndex = matchStart + fullMatch.length 121 } 122 123 newText += text.slice(lastIndex) 124 125 return {text: newText, facets} 126}