Shorten links in composer to reduce char usage (#1188)
* Modify toShortUrl() to always include the full domain * Shorten links in the composer to save on characters * Apply some limits to the link card suggester
This commit is contained in:
parent
5379561934
commit
819340dd3c
6 changed files with 123 additions and 26 deletions
|
@ -14,6 +14,7 @@ import {isNetworkError} from 'lib/strings/errors'
|
|||
import {LinkMeta} from '../link-meta/link-meta'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {ImageModel} from 'state/models/media/image'
|
||||
import {shortenLinks} from 'lib/strings/rich-text-manip'
|
||||
|
||||
export interface ExternalEmbedDraft {
|
||||
uri: string
|
||||
|
@ -92,7 +93,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
|||
| AppBskyEmbedRecordWithMedia.Main
|
||||
| undefined
|
||||
let reply
|
||||
const rt = new RichText(
|
||||
let rt = new RichText(
|
||||
{text: opts.rawText.trim()},
|
||||
{
|
||||
cleanNewlines: true,
|
||||
|
@ -101,6 +102,7 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
|
|||
|
||||
opts.onStateChange?.('Processing...')
|
||||
await rt.detectFacets(store.agent)
|
||||
rt = shortenLinks(rt)
|
||||
|
||||
// filter out any mention facets that didn't map to a user
|
||||
rt.facets = rt.facets?.filter(facet => {
|
||||
|
|
34
src/lib/strings/rich-text-manip.ts
Normal file
34
src/lib/strings/rich-text-manip.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import {RichText, UnicodeString} from '@atproto/api'
|
||||
import {toShortUrl} from './url-helpers'
|
||||
|
||||
export function shortenLinks(rt: RichText): RichText {
|
||||
if (!rt.facets?.length) {
|
||||
return rt
|
||||
}
|
||||
rt = rt.clone()
|
||||
// enumerate the link facets
|
||||
if (rt.facets) {
|
||||
for (const facet of rt.facets) {
|
||||
const isLink = !!facet.features.find(
|
||||
f => f.$type === 'app.bsky.richtext.facet#link',
|
||||
)
|
||||
if (!isLink) {
|
||||
continue
|
||||
}
|
||||
|
||||
// extract and shorten the URL
|
||||
const {byteStart, byteEnd} = facet.index
|
||||
const url = rt.unicodeText.slice(byteStart, byteEnd)
|
||||
const shortened = new UnicodeString(toShortUrl(url))
|
||||
|
||||
// insert the shorten URL
|
||||
rt.insert(byteStart, shortened.utf16)
|
||||
// update the facet to cover the new shortened URL
|
||||
facet.index.byteStart = byteStart
|
||||
facet.index.byteEnd = byteStart + shortened.length
|
||||
// remove the old URL
|
||||
rt.delete(byteStart + shortened.length, byteEnd + shortened.length)
|
||||
}
|
||||
}
|
||||
return rt
|
||||
}
|
|
@ -42,15 +42,12 @@ export function toShortUrl(url: string): string {
|
|||
if (urlp.protocol !== 'http:' && urlp.protocol !== 'https:') {
|
||||
return url
|
||||
}
|
||||
const shortened =
|
||||
urlp.host +
|
||||
(urlp.pathname === '/' ? '' : urlp.pathname) +
|
||||
urlp.search +
|
||||
urlp.hash
|
||||
if (shortened.length > 30) {
|
||||
return shortened.slice(0, 27) + '...'
|
||||
const path =
|
||||
(urlp.pathname === '/' ? '' : urlp.pathname) + urlp.search + urlp.hash
|
||||
if (path.length > 15) {
|
||||
return urlp.host + path.slice(0, 13) + '...'
|
||||
}
|
||||
return shortened ? shortened : url
|
||||
return urlp.host + path
|
||||
} catch (e) {
|
||||
return url
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ import {s, colors, gradients} from 'lib/styles'
|
|||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||
import {sanitizeHandle} from 'lib/strings/handles'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {shortenLinks} from 'lib/strings/rich-text-manip'
|
||||
import {toShortUrl} from 'lib/strings/url-helpers'
|
||||
import {SelectPhotoBtn} from './photos/SelectPhotoBtn'
|
||||
import {OpenCameraBtn} from './photos/OpenCameraBtn'
|
||||
import {usePalette} from 'lib/hooks/usePalette'
|
||||
|
@ -63,7 +65,9 @@ export const ComposePost = observer(function ComposePost({
|
|||
const [processingState, setProcessingState] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [richtext, setRichText] = useState(new RichText({text: ''}))
|
||||
const graphemeLength = useMemo(() => richtext.graphemeLength, [richtext])
|
||||
const graphemeLength = useMemo(() => {
|
||||
return shortenLinks(richtext).graphemeLength
|
||||
}, [richtext])
|
||||
const [quote, setQuote] = useState<ComposerOpts['quote'] | undefined>(
|
||||
initQuote,
|
||||
)
|
||||
|
@ -148,7 +152,7 @@ export const ComposePost = observer(function ComposePost({
|
|||
)
|
||||
|
||||
const onPressPublish = async (rt: RichText) => {
|
||||
if (isProcessing || rt.graphemeLength > MAX_GRAPHEME_LENGTH) {
|
||||
if (isProcessing || graphemeLength > MAX_GRAPHEME_LENGTH) {
|
||||
return
|
||||
}
|
||||
if (store.preferences.requireAltTextEnabled && gallery.needsAltText) {
|
||||
|
@ -352,20 +356,23 @@ export const ComposePost = observer(function ComposePost({
|
|||
</ScrollView>
|
||||
{!extLink && suggestedLinks.size > 0 ? (
|
||||
<View style={s.mb5}>
|
||||
{Array.from(suggestedLinks).map(url => (
|
||||
<TouchableOpacity
|
||||
key={`suggested-${url}`}
|
||||
testID="addLinkCardBtn"
|
||||
style={[pal.borderDark, styles.addExtLinkBtn]}
|
||||
onPress={() => onPressAddLinkCard(url)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Add link card"
|
||||
accessibilityHint={`Creates a card with a thumbnail. The card links to ${url}`}>
|
||||
<Text style={pal.text}>
|
||||
Add link card: <Text style={pal.link}>{url}</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
{Array.from(suggestedLinks)
|
||||
.slice(0, 3)
|
||||
.map(url => (
|
||||
<TouchableOpacity
|
||||
key={`suggested-${url}`}
|
||||
testID="addLinkCardBtn"
|
||||
style={[pal.borderDark, styles.addExtLinkBtn]}
|
||||
onPress={() => onPressAddLinkCard(url)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Add link card"
|
||||
accessibilityHint={`Creates a card with a thumbnail. The card links to ${url}`}>
|
||||
<Text style={pal.text}>
|
||||
Add link card:{' '}
|
||||
<Text style={pal.link}>{toShortUrl(url)}</Text>
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
<View style={[pal.border, styles.bottomBar]}>
|
||||
|
|
|
@ -107,6 +107,7 @@ export const TextInput = React.forwardRef(
|
|||
const json = editorProp.getJSON()
|
||||
|
||||
const newRt = new RichText({text: editorJsonToText(json).trim()})
|
||||
newRt.detectFacetsWithoutResolution()
|
||||
setRichText(newRt)
|
||||
|
||||
const newSuggestedLinks = new Set(editorJsonToLinks(json))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue