fix: character limit for hyperlinks is reduced and full is highlighted #2

merged
opened by scanash.com targeting main
Changed files
+143 -27
src
view
com
+30 -24
src/view/com/composer/state/composer.ts
···
import {type SelfLabel} from '#/lib/moderation'
import {insertMentionAt} from '#/lib/strings/mention-manip'
-
import {shortenLinks} from '#/lib/strings/rich-text-manip'
+
import {
+
parseMarkdownLinks,
+
shortenLinks,
+
} from '#/lib/strings/rich-text-manip'
import {
isBskyPostUrl,
postUriToRelativePath,
···
| {type: 'embed_update_image'; image: ComposerImage}
| {type: 'embed_remove_image'; image: ComposerImage}
| {
-
type: 'embed_add_video'
-
asset: ImagePickerAsset
-
abortController: AbortController
-
}
+
type: 'embed_add_video'
+
asset: ImagePickerAsset
+
abortController: AbortController
+
}
| {type: 'embed_remove_video'}
| {type: 'embed_update_video'; videoAction: VideoAction}
| {type: 'embed_add_uri'; uri: string}
···
| {type: 'update_postgate'; postgate: AppBskyFeedPostgate.Record}
| {type: 'update_threadgate'; threadgate: ThreadgateAllowUISetting[]}
| {
-
type: 'update_post'
-
postId: string
-
postAction: PostAction
-
}
+
type: 'update_post'
+
postId: string
+
postAction: PostAction
+
}
| {
-
type: 'add_post'
-
}
+
type: 'add_post'
+
}
| {
-
type: 'remove_post'
-
postId: string
-
}
+
type: 'remove_post'
+
postId: string
+
}
| {
-
type: 'focus_post'
-
postId: string
-
}
+
type: 'focus_post'
+
postId: string
+
}
export const MAX_IMAGES = 4
···
initImageUris: ComposerOpts['imageUris']
initQuoteUri: string | undefined
initInteractionSettings:
-
| BskyPreferences['postInteractionSettings']
-
| undefined
+
| BskyPreferences['postInteractionSettings']
+
| undefined
}): ComposerState {
let media: ImagesMedia | undefined
if (initImageUris?.length) {
···
? initText
: initMention
? insertMentionAt(
-
`@${initMention}`,
-
initMention.length + 1,
-
`${initMention}`,
-
)
+
`@${initMention}`,
+
initMention.length + 1,
+
`${initMention}`,
+
)
: '',
})
···
}
function getShortenedLength(rt: RichText) {
-
return shortenLinks(rt).graphemeLength
+
const {text} = parseMarkdownLinks(rt.text)
+
const newRt = new RichText({text})
+
newRt.detectFacetsWithoutResolution()
+
return shortenLinks(newRt).graphemeLength
}
+50 -1
src/view/com/composer/text-input/TextInput.tsx
···
type TextInputSelectionChangeEventData,
View,
} from 'react-native'
-
import {AppBskyRichtextFacet, RichText} from '@atproto/api'
+
import {AppBskyRichtextFacet, RichText, UnicodeString} from '@atproto/api'
import PasteInput, {
type PastedFile,
type PasteInputRef, // @ts-expect-error no types when installing from github
···
const newRt = new RichText({text: newText})
newRt.detectFacetsWithoutResolution()
+
+
const markdownFacets: AppBskyRichtextFacet.Main[] = []
+
const regex = /\[([^\]]+)\]\s*\(([^)]+)\)/g
+
let match
+
while ((match = regex.exec(newText)) !== null) {
+
const [fullMatch, _linkText, linkUrl] = match
+
const matchStart = match.index
+
const matchEnd = matchStart + fullMatch.length
+
const prefix = newText.slice(0, matchStart)
+
const matchStr = newText.slice(matchStart, matchEnd)
+
const byteStart = new UnicodeString(prefix).length
+
const byteEnd = byteStart + new UnicodeString(matchStr).length
+
+
let validUrl = linkUrl
+
if (
+
!validUrl.startsWith('http://') &&
+
!validUrl.startsWith('https://') &&
+
!validUrl.startsWith('mailto:')
+
) {
+
validUrl = `https://${validUrl}`
+
}
+
+
markdownFacets.push({
+
index: {byteStart, byteEnd},
+
features: [
+
{$type: 'app.bsky.richtext.facet#link', uri: validUrl},
+
],
+
})
+
}
+
+
if (markdownFacets.length > 0) {
+
+
const nonOverlapping = (newRt.facets || []).filter(f => {
+
return !markdownFacets.some(mf => {
+
return (
+
(f.index.byteStart >= mf.index.byteStart &&
+
f.index.byteStart < mf.index.byteEnd) ||
+
(f.index.byteEnd > mf.index.byteStart &&
+
f.index.byteEnd <= mf.index.byteEnd) ||
+
(mf.index.byteStart >= f.index.byteStart &&
+
mf.index.byteStart < f.index.byteEnd)
+
)
+
})
+
})
+
newRt.facets = [...nonOverlapping, ...markdownFacets].sort(
+
(a, b) => a.index.byteStart - b.index.byteStart,
+
)
+
}
+
setRichText(newRt)
// NOTE: BinaryFiddler
+49 -1
src/view/com/composer/text-input/TextInput.web.tsx
···
} from 'react'
import {StyleSheet, View} from 'react-native'
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated'
-
import {AppBskyRichtextFacet, RichText} from '@atproto/api'
+
import {AppBskyRichtextFacet, RichText, UnicodeString} from '@atproto/api'
import {Trans} from '@lingui/macro'
import {Document} from '@tiptap/extension-document'
import Hardbreak from '@tiptap/extension-hard-break'
···
const newRt = new RichText({text: newText})
newRt.detectFacetsWithoutResolution()
+
+
const markdownFacets: AppBskyRichtextFacet.Main[] = []
+
const regex = /\[([^\]]+)\]\s*\(([^)]+)\)/g
+
let match
+
while ((match = regex.exec(newText)) !== null) {
+
const [fullMatch, _linkText, linkUrl] = match
+
const matchStart = match.index
+
const matchEnd = matchStart + fullMatch.length
+
const prefix = newText.slice(0, matchStart)
+
const matchStr = newText.slice(matchStart, matchEnd)
+
const byteStart = new UnicodeString(prefix).length
+
const byteEnd = byteStart + new UnicodeString(matchStr).length
+
+
let validUrl = linkUrl
+
if (
+
!validUrl.startsWith('http://') &&
+
!validUrl.startsWith('https://') &&
+
!validUrl.startsWith('mailto:')
+
) {
+
validUrl = `https://${validUrl}`
+
}
+
+
markdownFacets.push({
+
index: {byteStart, byteEnd},
+
features: [
+
{ $type: 'app.bsky.richtext.facet#link', uri: validUrl },
+
],
+
})
+
}
+
+
if (markdownFacets.length > 0) {
+
const nonOverlapping = (newRt.facets || []).filter(f => {
+
return !markdownFacets.some(mf => {
+
return (
+
(f.index.byteStart >= mf.index.byteStart &&
+
f.index.byteStart < mf.index.byteEnd) ||
+
(f.index.byteEnd > mf.index.byteStart &&
+
f.index.byteEnd <= mf.index.byteEnd) ||
+
(mf.index.byteStart >= f.index.byteStart &&
+
mf.index.byteStart < f.index.byteEnd)
+
)
+
})
+
})
+
newRt.facets = [...nonOverlapping, ...markdownFacets].sort(
+
(a, b) => a.index.byteStart - b.index.byteStart,
+
)
+
}
+
setRichText(newRt)
const nextDetectedUris = new Map<string, LinkFacetMatch>()
+14 -1
src/view/com/composer/text-input/web/LinkDecorator.ts
···
if (node.isText && node.text) {
const textContent = node.textContent
-
// links
+
// markdown links [text](url)
+
const markdownRegex = /\[([^\]]+)\]\s*\(([^)]+)\)/g
+
let markdownMatch
+
while ((markdownMatch = markdownRegex.exec(textContent)) !== null) {
+
const from = markdownMatch.index
+
const to = from + markdownMatch[0].length
+
decorations.push(
+
Decoration.inline(pos + from, pos + to, {
+
class: 'autolink',
+
}),
+
)
+
}
+
+
// regular links
iterateUris(textContent, (from, to) => {
decorations.push(
Decoration.inline(pos + from, pos + to, {