chore: update deps and add TypeScript null safety checks

This commit is contained in:
2026-01-04 06:44:41 +00:00
parent d786a7463b
commit 619fcdb9ae
26 changed files with 3232 additions and 1581 deletions

View File

@@ -108,12 +108,12 @@ const trapFocus = (event: KeyboardEvent) => {
if (event.shiftKey) {
if (document.activeElement === firstElement) {
event.preventDefault()
lastElement.focus()
lastElement?.focus()
}
} else {
if (document.activeElement === lastElement) {
event.preventDefault()
firstElement.focus()
firstElement?.focus()
}
}
}

View File

@@ -241,7 +241,7 @@ onMounted(() => {
const getFocusedMessage = (): ExtendedMessage | UnsentMessage | null => {
const messages = allMessages.value
if (focusedMessageIndex.value >= 0 && focusedMessageIndex.value < messages.length) {
return messages[focusedMessageIndex.value]
return messages[focusedMessageIndex.value] ?? null
}
return null
}

View File

@@ -207,8 +207,8 @@ const switchCamera = async () => {
// Determine if this is likely a front camera
const currentCamera = availableCameras.value[currentCameraIndex.value]
isFrontCamera.value = currentCamera.label.toLowerCase().includes('front') ||
currentCamera.label.toLowerCase().includes('user')
isFrontCamera.value = currentCamera?.label.toLowerCase().includes('front') ||
currentCamera?.label.toLowerCase().includes('user') || false
try {
await startCamera()

View File

@@ -281,7 +281,10 @@ const performDelete = async () => {
// Switch to first available channel if we were on the deleted channel
if (appStore.currentChannelId === props.channel.id && appStore.channels.length > 0) {
await appStore.setCurrentChannel(appStore.channels[0].id)
const firstChannel = appStore.channels[0]
if (firstChannel) {
await appStore.setCurrentChannel(firstChannel.id)
}
}
} catch (error) {
// For delete, we can't do offline fallback easily since it affects server state

View File

@@ -141,20 +141,24 @@ const uploadFiles = async () => {
try {
// For single file, use the filename as message content
// For multiple files, show count
const messageContent = selectedFiles.value.length === 1
? selectedFiles.value[0].name
const messageContent = selectedFiles.value.length === 1
? selectedFiles.value[0]?.name || 'Uploaded file'
: `Uploaded ${selectedFiles.value.length} files`
// Create a message first to attach files to
const message = await apiService.createMessage(appStore.currentChannelId, messageContent)
// Upload the first file (backend uses single file per message)
const file = selectedFiles.value[0]
if (!file) {
throw new Error('No file selected')
}
try {
const uploadedFile = await apiService.uploadFile(appStore.currentChannelId, message.id, file)
uploadProgress.value[0] = 100
// Immediately update the local message with file metadata
const updatedMessage = {
...message,
@@ -165,12 +169,12 @@ const uploadFiles = async () => {
originalName: uploadedFile.original_name,
fileCreatedAt: uploadedFile.created_at
}
// Update the message in the store
appStore.updateMessage(message.id, updatedMessage)
toastStore.success('File uploaded successfully!')
} catch (fileError) {
console.error(`Failed to upload ${file.name}:`, fileError)
toastStore.error(`Failed to upload ${file.name}`)

View File

@@ -161,11 +161,17 @@ const handleAlphanumericNavigation = (char: string, currentIndex: number) => {
focusChannel(nextMatch)
} else {
// Wrap around to the first match
focusChannel(matchingIndices[0])
const firstMatch = matchingIndices[0]
if (firstMatch !== undefined) {
focusChannel(firstMatch)
}
}
} else {
// New character: jump to first match
focusChannel(matchingIndices[0])
const firstMatch = matchingIndices[0]
if (firstMatch !== undefined) {
focusChannel(firstMatch)
}
}
}

View File

@@ -174,14 +174,20 @@ export function useAudio() {
console.log(`playWater called - global: ${globalWaterSounds.length}, reactive: ${waterSounds.value.length} water sounds available`)
if (globalWaterSounds.length > 0) {
const randomIndex = Math.floor(Math.random() * globalWaterSounds.length)
await playSoundBuffer(globalWaterSounds[randomIndex])
const buffer = globalWaterSounds[randomIndex]
if (buffer) {
await playSoundBuffer(buffer)
}
} else {
console.warn('Water sounds not loaded - trying to load them now')
if (globalAudioContext) {
await loadAllSounds()
if (globalWaterSounds.length > 0) {
const randomIndex = Math.floor(Math.random() * globalWaterSounds.length)
await playSoundBuffer(globalWaterSounds[randomIndex])
const buffer = globalWaterSounds[randomIndex]
if (buffer) {
await playSoundBuffer(buffer)
}
}
}
}
@@ -190,7 +196,10 @@ export function useAudio() {
const playSent = async () => {
if (globalSentSounds.length > 0) {
const randomIndex = Math.floor(Math.random() * globalSentSounds.length)
await playSoundBuffer(globalSentSounds[randomIndex])
const buffer = globalSentSounds[randomIndex]
if (buffer) {
await playSoundBuffer(buffer)
}
} else {
console.warn('Sent sounds not loaded')
}
@@ -303,7 +312,7 @@ export function useAudio() {
// Select default voice (prefer English voices)
if (!selectedVoice.value && voices.length > 0) {
const englishVoice = voices.find(voice => voice.lang.startsWith('en'))
selectedVoice.value = englishVoice || voices[0]
selectedVoice.value = englishVoice || voices[0] || null
}
}

View File

@@ -136,8 +136,15 @@ export function useWebSocket() {
const channels = [...appStore.channels]
const channelIndex = channels.findIndex(c => c.id === channelId)
if (channelIndex !== -1) {
channels[channelIndex] = { ...channels[channelIndex], name: data.name }
appStore.setChannels(channels)
const existingChannel = channels[channelIndex]
if (existingChannel) {
channels[channelIndex] = {
id: existingChannel.id,
name: data.name,
created_at: existingChannel.created_at
}
appStore.setChannels(channels)
}
}
}

View File

@@ -73,11 +73,16 @@ export const useAppStore = defineStore('app', () => {
}
const channelMessages = messages.value[message.channel_id]
if (!channelMessages) return
const existingIndex = channelMessages.findIndex(m => m.id === message.id)
if (existingIndex !== -1) {
// Upsert: update existing to avoid duplicates from WebSocket vs sync
channelMessages[existingIndex] = { ...channelMessages[existingIndex], ...message }
const existingMessage = channelMessages[existingIndex]
if (existingMessage) {
channelMessages[existingIndex] = { ...existingMessage, ...message }
}
} else {
channelMessages.push(message)
}
@@ -93,9 +98,14 @@ export const useAppStore = defineStore('app', () => {
const updateMessage = (messageId: number, updates: Partial<ExtendedMessage>) => {
for (const channelId in messages.value) {
const channelMessages = messages.value[parseInt(channelId)]
if (!channelMessages) continue
const messageIndex = channelMessages.findIndex(m => m.id === messageId)
if (messageIndex !== -1) {
channelMessages[messageIndex] = { ...channelMessages[messageIndex], ...updates }
const existingMessage = channelMessages[messageIndex]
if (existingMessage) {
channelMessages[messageIndex] = { ...existingMessage, ...updates }
}
break
}
}
@@ -108,6 +118,8 @@ export const useAppStore = defineStore('app', () => {
const removeMessage = (messageId: number) => {
for (const channelId in messages.value) {
const channelMessages = messages.value[parseInt(channelId)]
if (!channelMessages) continue
const messageIndex = channelMessages.findIndex(m => m.id === messageId)
if (messageIndex !== -1) {
channelMessages.splice(messageIndex, 1)
@@ -127,16 +139,23 @@ export const useAppStore = defineStore('app', () => {
}
const message = sourceMessages[messageIndex]
if (!message) {
console.warn(`Message ${messageId} not found at index ${messageIndex}`)
return
}
sourceMessages.splice(messageIndex, 1)
// Update message's channel_id and add to target channel
const updatedMessage = { ...message, channel_id: targetChannelId }
if (!messages.value[targetChannelId]) {
messages.value[targetChannelId] = []
}
const targetMessages = messages.value[targetChannelId]
if (!targetMessages) return
targetMessages.push(updatedMessage)
// Keep chronological order in target channel

View File

@@ -443,9 +443,14 @@ const announceLastMessage = (position: number) => {
}
const message = messages[messageIndex]
if (!message) {
toastStore.info('No message is available in this position')
return
}
const timeStr = formatTimestampForScreenReader(message.created_at)
const announcement = `${message.content}; sent ${timeStr}`
toastStore.info(announcement)
// Also speak if TTS is enabled
@@ -608,7 +613,10 @@ onMounted(async () => {
// 5. Auto-select first channel if none selected and we have channels
if (!appStore.currentChannelId && appStore.channels.length > 0) {
await selectChannel(appStore.channels[0].id)
const firstChannel = appStore.channels[0]
if (firstChannel) {
await selectChannel(firstChannel.id)
}
}
// 6. Auto-focus message input on page load