Compare commits

..

5 Commits

Author SHA1 Message Date
Samuel Newman bae356a2d2 Update yarn.lock 2026-01-30 15:41:59 +02:00
vineyardbovines faa9473032 yarn??? 2026-01-30 15:41:35 +02:00
vineyardbovines 743f2ea497 lockfile 2026-01-30 15:41:35 +02:00
vineyardbovines e1a0fb80f9 install expo metro runtime 2026-01-30 15:41:35 +02:00
vineyardbovines 3ba894a851 add expo metro runtime for web reloading 2026-01-30 15:41:35 +02:00
12 changed files with 72 additions and 84 deletions
+1
View File
@@ -1,3 +1,4 @@
import '@expo/metro-runtime'
import '#/platform/markBundleStartTime'
import '#/platform/polyfills'
+1
View File
@@ -235,6 +235,7 @@
"@babel/runtime": "^7.26.0",
"@eslint/js": "^9.39.2",
"@expo/config-plugins": "~54.0.1",
"@expo/metro-runtime": "~6.1.2",
"@lingui/cli": "^4.14.1",
"@lingui/macro": "^4.14.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
@@ -303,7 +303,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
additionalCauses={additionalPostAlerts}
/>
{richText?.text ? (
<View style={[a.mb_2xs]}>
<>
<RichText
enableTags
value={richText}
@@ -318,7 +318,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({
onPress={onPressShowMore}
/>
)}
</View>
</>
) : undefined}
{post.embed && (
<View style={[a.pb_xs]}>
@@ -343,7 +343,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
additionalCauses={additionalPostAlerts}
/>
{richText?.text ? (
<View style={[a.mb_2xs]}>
<>
<RichText
enableTags
value={richText}
@@ -358,7 +358,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({
onPress={onPressShowMore}
/>
)}
</View>
</>
) : null}
{post.embed && (
<View style={[a.pb_xs]}>
+16 -17
View File
@@ -270,29 +270,27 @@ async function moveIfNecessary(from: string) {
* On web, converts blob URLs to data URIs immediately to prevent revocation issues.
*/
async function copyToCache(from: string): Promise<string> {
// Handle web blob URLs - convert to data URI immediately before they can be revoked
if (IS_WEB && from.startsWith('blob:')) {
try {
const response = await fetch(from)
const blob = await response.blob()
return await blobToDataUri(blob)
} catch (e) {
// If fetch fails, the blob URL was likely already revoked
// Return as-is and let downstream code handle the error
return from
}
}
// Data URIs don't need any conversion
if (from.startsWith('data:')) {
return from
}
if (IS_WEB) {
// Web: convert blob URLs to data URIs before they can be revoked
if (from.startsWith('blob:')) {
try {
const response = await fetch(from)
const blob = await response.blob()
return await blobToDataUri(blob)
} catch (e) {
// Blob URL was likely revoked, return as-is for downstream error handling
return from
}
}
// Other URLs on web don't need conversion
return from
}
const cacheDir = IS_WEB && getImageCacheDirectory()
// Native: copy to cache directory to survive OS temp file cleanup
const cacheDir = getImageCacheDirectory()
// On web (non-blob URLs) or if already in cache dir, no need to copy
if (!cacheDir || from.startsWith(cacheDir)) {
return from
}
@@ -300,6 +298,7 @@ async function copyToCache(from: string): Promise<string> {
const to = joinPath(cacheDir, nanoid(36))
await makeDirectoryAsync(cacheDir, {intermediates: true})
// Normalize the source path for expo-file-system
let normalizedFrom = from
if (!from.startsWith('file://') && from.startsWith('/')) {
normalizedFrom = `file://${from}`
+34 -24
View File
@@ -1,16 +1,13 @@
/**
* Type converters for Draft API - convert between ComposerState and server Draft types.
*/
import {type AppBskyDraftDefs, AtUri, RichText} from '@atproto/api'
import {type AppBskyDraftDefs, RichText} from '@atproto/api'
import {nanoid} from 'nanoid/non-secure'
import {resolveLink} from '#/lib/api/resolve'
import {getImageDim} from '#/lib/media/manip'
import {mimeToExt} from '#/lib/media/video/util'
import {type ComposerImage} from '#/state/gallery'
import {type Gif} from '#/state/queries/tenor'
import {threadgateAllowUISettingToAllowRecordValue} from '#/state/queries/threadgate/util'
import {createPublicAgent} from '#/state/session/agent'
import {
type ComposerState,
type EmbedDraft,
@@ -64,12 +61,33 @@ export async function composerStateToDraft(state: ComposerState): Promise<{
}),
)
// Convert threadgate settings to server format
const threadgateAllow: AppBskyDraftDefs.Draft['threadgateAllow'] = []
for (const setting of state.thread.threadgate) {
if (setting.type === 'mention') {
threadgateAllow.push({
$type: 'app.bsky.feed.threadgate#mentionRule' as const,
})
} else if (setting.type === 'following') {
threadgateAllow.push({
$type: 'app.bsky.feed.threadgate#followingRule' as const,
})
} else if (setting.type === 'followers') {
threadgateAllow.push({
$type: 'app.bsky.feed.threadgate#followerRule' as const,
})
} else if (setting.type === 'list') {
threadgateAllow.push({
$type: 'app.bsky.feed.threadgate#listRule' as const,
list: setting.list,
})
}
}
const draft: AppBskyDraftDefs.Draft = {
$type: 'app.bsky.draft.defs#draft',
posts,
threadgateAllow: threadgateAllowUISettingToAllowRecordValue(
state.thread.threadgate,
),
threadgateAllow: threadgateAllow.length > 0 ? threadgateAllow : undefined,
postgateEmbeddingRules:
state.thread.postgate.embeddingRules &&
state.thread.postgate.embeddingRules.length > 0
@@ -122,21 +140,15 @@ async function postDraftToServerPost(
// Add quote record embed
if (post.embed.quote) {
const resolved = await resolveLink(
createPublicAgent(),
post.embed.quote.uri,
)
if (resolved && resolved.type === 'record') {
draftPost.embedRecords = [
{
$type: 'app.bsky.draft.defs#draftEmbedRecord',
record: {
uri: resolved.record.uri,
cid: resolved.record.cid,
},
draftPost.embedRecords = [
{
$type: 'app.bsky.draft.defs#draftEmbedRecord',
record: {
uri: post.embed.quote.uri,
cid: '', // We don't have the CID at draft time
},
]
}
},
]
}
// Add external link embed (only if no media, otherwise it's ignored)
@@ -524,9 +536,7 @@ export async function draftToComposerPosts(
// Restore quote embed
if (post.embedRecords && post.embedRecords.length > 0) {
const record = post.embedRecords[0]
const urip = new AtUri(record.record.uri)
const url = `https://bsky.app/profile/${urip.host}/post/${urip.rkey}`
embed.quote = {type: 'link', uri: url}
embed.quote = {type: 'link', uri: record.record.uri}
}
// Restore link embed (only if not a GIF)
@@ -55,7 +55,7 @@ export async function loadDraft(draft: AppBskyDraftDefs.Draft): Promise<{
const url = await storage.loadMediaFromLocal(img.localRef.path)
loadedMedia.set(img.localRef.path, url)
} catch (e) {
logger.debug('Failed to load draft image', {
logger.warn('Failed to load draft image', {
path: img.localRef.path,
error: e,
})
@@ -69,7 +69,7 @@ export async function loadDraft(draft: AppBskyDraftDefs.Draft): Promise<{
const url = await storage.loadMediaFromLocal(vid.localRef.path)
loadedMedia.set(vid.localRef.path, url)
} catch (e) {
logger.debug('Failed to load draft video', {
logger.warn('Failed to load draft video', {
path: vid.localRef.path,
error: e,
})
-3
View File
@@ -670,9 +670,6 @@ export function createComposerState({
}
}
}
} else if (initMention) {
// highlight the mention
initRichText.detectFacetsWithoutResolution()
}
return {
@@ -241,7 +241,7 @@ export function TextInput({
}
},
},
content: generateJSON(richTextToHTML(richtext), extensions, {
content: generateJSON(richtext.text.toString(), extensions, {
preserveWhitespace: 'full',
}),
autofocus: 'end',
@@ -382,36 +382,6 @@ export function TextInput({
)
}
/**
* Helper function to initialise the editor with RichText, which expects HTML
*
* All the extensions are able to initialise themselves from plain text, *except*
* for the Mention extension - we need to manually convert it into a `<span>` element
*
* It also escapes HTML characters
*/
function richTextToHTML(richtext: RichText): string {
let html = ''
for (const segment of richtext.segments()) {
if (segment.mention) {
html += `<span data-type="mention" data-id="${escapeHTML(segment.mention.did)}"></span>`
} else {
html += escapeHTML(segment.text)
}
}
return html
}
function escapeHTML(str: string): string {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
}
function editorJsonToText(
json: JSONContent,
isLastDocumentChild: boolean = false,
+1 -1
View File
@@ -199,7 +199,7 @@ function PostInner({
style={[a.pb_xs]}
/>
{richText.text ? (
<View style={[a.mb_2xs]}>
<View>
<RichText
enableTags
testID="postText"
+2 -2
View File
@@ -466,7 +466,7 @@ let PostContent = ({
additionalCauses={additionalPostAlerts}
/>
{richText.text ? (
<View style={[a.mb_2xs]}>
<>
<RichText
enableTags
testID="postText"
@@ -479,7 +479,7 @@ let PostContent = ({
{limitLines && (
<ShowMoreTextButton style={[a.text_md]} onPress={onPressShowMore} />
)}
</View>
</>
) : undefined}
{postEmbed ? (
<View style={[a.pb_xs]}>
+10
View File
@@ -4124,6 +4124,16 @@
postcss "~8.4.32"
resolve-from "^5.0.0"
"@expo/metro-runtime@~6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@expo/metro-runtime/-/metro-runtime-6.1.2.tgz#5a4ff117df6115f9c9d4dcc561065e16d69c078b"
integrity sha512-nvM+Qv45QH7pmYvP8JB1G8JpScrWND3KrMA6ZKe62cwwNiX/BjHU28Ear0v/4bQWXlOY0mv6B8CDIm8JxXde9g==
dependencies:
anser "^1.4.9"
pretty-format "^29.7.0"
stacktrace-parser "^0.1.10"
whatwg-fetch "^3.0.0"
"@expo/metro@~54.1.0":
version "54.1.0"
resolved "https://registry.yarnpkg.com/@expo/metro/-/metro-54.1.0.tgz#27765ef2c342c39086a2f5c9f932a375dc2ccad3"