Draft previews (#9803)
* Send deviceId and platform
* Add deviceId and deviceName to drafts, skip loading media for other devies
* WIP new preview
* show rich text in drafts list
(cherry picked from commit fb70d53d59)
* New draft preview UI
* Tighten up spacing in draft list
* Add i18n comments
---------
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import {type AppBskyDraftDefs, AtUri, RichText} from '@atproto/api'
|
||||
import {nanoid} from 'nanoid/non-secure'
|
||||
|
||||
import {resolveLink} from '#/lib/api/resolve'
|
||||
import {getDeviceName} from '#/lib/deviceName'
|
||||
import {getImageDim} from '#/lib/media/manip'
|
||||
import {mimeToExt} from '#/lib/media/video/util'
|
||||
import {type ComposerImage} from '#/state/gallery'
|
||||
@@ -17,8 +18,11 @@ import {
|
||||
type PostDraft,
|
||||
} from '#/view/com/composer/state/composer'
|
||||
import {type VideoState} from '#/view/com/composer/state/video'
|
||||
import {type AnalyticsContextType} from '#/analytics'
|
||||
import {getDeviceId} from '#/analytics/identifiers'
|
||||
import {logger} from './logger'
|
||||
import {type DraftPostDisplay, type DraftSummary} from './schema'
|
||||
import * as storage from './storage'
|
||||
|
||||
const TENOR_HOSTNAME = 'media.tenor.com'
|
||||
|
||||
@@ -66,6 +70,8 @@ export async function composerStateToDraft(state: ComposerState): Promise<{
|
||||
|
||||
const draft: AppBskyDraftDefs.Draft = {
|
||||
$type: 'app.bsky.draft.defs#draft',
|
||||
deviceId: getDeviceId(),
|
||||
deviceName: getDeviceName().slice(0, 100), // max length of 100 in lex
|
||||
posts,
|
||||
threadgateAllow: threadgateAllowUISettingToAllowRecordValue(
|
||||
state.thread.threadgate,
|
||||
@@ -265,16 +271,24 @@ function serializeGif(gifMedia: {
|
||||
* Convert server DraftView to DraftSummary for list display.
|
||||
* Also checks which media files exist locally.
|
||||
*/
|
||||
export function draftViewToSummary(
|
||||
view: AppBskyDraftDefs.DraftView,
|
||||
localMediaExists: (path: string) => boolean,
|
||||
): DraftSummary {
|
||||
const firstPost = view.draft.posts[0]
|
||||
const previewText = firstPost?.text?.slice(0, 100) || ''
|
||||
|
||||
let mediaCount = 0
|
||||
let hasMedia = false
|
||||
let hasMissingMedia = false
|
||||
export function draftViewToSummary({
|
||||
view,
|
||||
analytics,
|
||||
}: {
|
||||
view: AppBskyDraftDefs.DraftView
|
||||
analytics: AnalyticsContextType
|
||||
}): DraftSummary {
|
||||
const meta = {
|
||||
isOriginatingDevice: view.draft.deviceId === getDeviceId(),
|
||||
postCount: view.draft.posts.length,
|
||||
// minus anchor post
|
||||
replyCount: view.draft.posts.length - 1,
|
||||
hasMedia: false,
|
||||
hasMissingMedia: false,
|
||||
mediaCount: 0,
|
||||
hasQuotes: false,
|
||||
quoteCount: 0,
|
||||
}
|
||||
|
||||
const posts: DraftPostDisplay[] = view.draft.posts.map((post, index) => {
|
||||
const images: DraftPostDisplay['images'] = []
|
||||
@@ -284,11 +298,11 @@ export function draftViewToSummary(
|
||||
// Process images
|
||||
if (post.embedImages) {
|
||||
for (const img of post.embedImages) {
|
||||
mediaCount++
|
||||
hasMedia = true
|
||||
const exists = localMediaExists(img.localRef.path)
|
||||
meta.mediaCount++
|
||||
meta.hasMedia = true
|
||||
const exists = storage.mediaExists(img.localRef.path)
|
||||
if (!exists) {
|
||||
hasMissingMedia = true
|
||||
meta.hasMissingMedia = true
|
||||
}
|
||||
images.push({
|
||||
localPath: img.localRef.path,
|
||||
@@ -301,11 +315,11 @@ export function draftViewToSummary(
|
||||
// Process videos
|
||||
if (post.embedVideos) {
|
||||
for (const vid of post.embedVideos) {
|
||||
mediaCount++
|
||||
hasMedia = true
|
||||
const exists = localMediaExists(vid.localRef.path)
|
||||
meta.mediaCount++
|
||||
meta.hasMedia = true
|
||||
const exists = storage.mediaExists(vid.localRef.path)
|
||||
if (!exists) {
|
||||
hasMissingMedia = true
|
||||
meta.hasMissingMedia = true
|
||||
}
|
||||
videos.push({
|
||||
localPath: vid.localRef.path,
|
||||
@@ -320,13 +334,18 @@ export function draftViewToSummary(
|
||||
for (const ext of post.embedExternals) {
|
||||
const gifData = parseGifFromUrl(ext.uri)
|
||||
if (gifData) {
|
||||
mediaCount++
|
||||
hasMedia = true
|
||||
meta.mediaCount++
|
||||
meta.hasMedia = true
|
||||
gif = gifData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (post.embedRecords && post.embedRecords.length > 0) {
|
||||
meta.quoteCount += post.embedRecords.length
|
||||
meta.hasQuotes = true
|
||||
}
|
||||
|
||||
return {
|
||||
id: `post-${index}`,
|
||||
text: post.text || '',
|
||||
@@ -336,17 +355,17 @@ export function draftViewToSummary(
|
||||
}
|
||||
})
|
||||
|
||||
if (meta.isOriginatingDevice && meta.hasMissingMedia) {
|
||||
analytics.logger.warn(`Draft is missing media on originating device`, {})
|
||||
}
|
||||
|
||||
return {
|
||||
id: view.id,
|
||||
draft: view.draft,
|
||||
previewText,
|
||||
hasMedia,
|
||||
hasMissingMedia,
|
||||
mediaCount,
|
||||
postCount: view.draft.posts.length,
|
||||
createdAt: view.createdAt,
|
||||
updatedAt: view.updatedAt,
|
||||
draft: view.draft,
|
||||
posts,
|
||||
meta,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user