chore: update deps and add TypeScript null safety checks
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user