Fix time display, fix file attachments not working properly after sending without refresh
This commit is contained in:
@@ -18,7 +18,16 @@ export const uploadFile = async (req: Request, res: Response) => {
|
||||
|
||||
const result = await FileService.uploadFile(channelId, messageId, filePath, fileType!, fileSize!, originalName!);
|
||||
logger.info(`File ${originalName} uploaded to message ${messageId} as ${filePath}`);
|
||||
res.json({ id: result.lastInsertRowid, channelId, messageId, filePath, fileType });
|
||||
res.json({
|
||||
id: result.lastInsertRowid,
|
||||
channel_id: parseInt(channelId),
|
||||
message_id: parseInt(messageId),
|
||||
file_path: filePath,
|
||||
file_type: fileType,
|
||||
file_size: fileSize,
|
||||
original_name: originalName,
|
||||
created_at: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@@ -11,11 +11,16 @@ export const uploadFile = async (channelId: string, messageId: string, filePath:
|
||||
const result2 = updateQuery.run({ fileId: fileId, messageId: messageId });
|
||||
|
||||
events.emit('file-uploaded', result.lastInsertRowid, channelId, messageId, filePath, fileType, fileSize, originalName);
|
||||
return result2; ''
|
||||
return result;
|
||||
}
|
||||
|
||||
export const getFiles = async (messageId: string) => {
|
||||
const query = db.prepare(`SELECT * FROM files WHERE messageId = $messageId`);
|
||||
// Get the file linked to this message via the fileId in the messages table
|
||||
const query = db.prepare(`
|
||||
SELECT files.* FROM files
|
||||
JOIN messages ON messages.fileId = files.id
|
||||
WHERE messages.id = $messageId
|
||||
`);
|
||||
const rows = query.all({ messageId: messageId });
|
||||
return rows;
|
||||
}
|
@@ -34,6 +34,7 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const fileExtension = computed(() => {
|
||||
if (!props.file.original_name) return ''
|
||||
return props.file.original_name.split('.').pop()?.toLowerCase() || ''
|
||||
})
|
||||
|
||||
|
@@ -20,8 +20,12 @@
|
||||
</div>
|
||||
|
||||
<div class="message__meta">
|
||||
<time v-if="!isUnsent && 'created_at' in message" class="message__time">
|
||||
{{ formatTime(message.created_at) }}
|
||||
<time
|
||||
v-if="!isUnsent && 'created_at' in message"
|
||||
class="message__time"
|
||||
:datetime="message.created_at"
|
||||
>
|
||||
{{ formatSmartTimestamp(message.created_at) }}
|
||||
</time>
|
||||
<span v-else class="message__status">Sending...</span>
|
||||
</div>
|
||||
@@ -33,6 +37,7 @@ import { computed } from 'vue'
|
||||
import { useAudio } from '@/composables/useAudio'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { formatSmartTimestamp, formatTimestampForScreenReader } from '@/utils/time'
|
||||
import FileAttachment from './FileAttachment.vue'
|
||||
import type { ExtendedMessage, UnsentMessage, FileAttachment as FileAttachmentType } from '@/types'
|
||||
|
||||
@@ -61,21 +66,30 @@ const hasFileAttachment = computed(() => {
|
||||
const fileAttachment = computed((): FileAttachmentType | null => {
|
||||
if (!hasFileAttachment.value || !('fileId' in props.message)) return null
|
||||
|
||||
// Check if we have the minimum required file metadata
|
||||
if (!props.message.filePath || !props.message.originalName) {
|
||||
console.warn('File attachment missing metadata:', {
|
||||
fileId: props.message.fileId,
|
||||
filePath: props.message.filePath,
|
||||
originalName: props.message.originalName,
|
||||
fileType: props.message.fileType
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
id: props.message.fileId!,
|
||||
channel_id: props.message.channel_id,
|
||||
message_id: props.message.id,
|
||||
file_path: props.message.filePath!,
|
||||
file_type: props.message.fileType!,
|
||||
file_size: props.message.fileSize!,
|
||||
file_type: props.message.fileType || 'application/octet-stream',
|
||||
file_size: props.message.fileSize || 0,
|
||||
original_name: props.message.originalName!,
|
||||
created_at: props.message.fileCreatedAt || props.message.created_at
|
||||
}
|
||||
})
|
||||
|
||||
const formatTime = (timestamp: string): string => {
|
||||
return new Date(timestamp).toLocaleTimeString()
|
||||
}
|
||||
// formatTime function removed - now using formatSmartTimestamp from utils
|
||||
|
||||
// Create comprehensive aria-label for screen readers
|
||||
const messageAriaLabel = computed(() => {
|
||||
@@ -95,8 +109,8 @@ const messageAriaLabel = computed(() => {
|
||||
|
||||
// Add timestamp
|
||||
if ('created_at' in props.message && props.message.created_at) {
|
||||
const time = formatTime(props.message.created_at)
|
||||
label += `. Sent at ${time}`
|
||||
const time = formatTimestampForScreenReader(props.message.created_at)
|
||||
label += `. Sent ${time}`
|
||||
}
|
||||
|
||||
// Add status for unsent messages
|
||||
|
@@ -58,7 +58,7 @@
|
||||
{{ result.content }}
|
||||
</div>
|
||||
<div class="result-time">
|
||||
{{ formatTime(result.created_at) }}
|
||||
{{ formatSmartTimestamp(result.created_at) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,6 +79,7 @@ import { ref, onMounted } from 'vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { apiService } from '@/services/api'
|
||||
import { formatSmartTimestamp } from '@/utils/time'
|
||||
import BaseInput from '@/components/base/BaseInput.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import type { Message, ExtendedMessage } from '@/types'
|
||||
@@ -140,16 +141,7 @@ const getChannelName = (channelId: number): string => {
|
||||
return channel?.name || `Channel ${channelId}`
|
||||
}
|
||||
|
||||
const formatTime = (timestamp: string): string => {
|
||||
if (!timestamp) return 'Unknown time'
|
||||
|
||||
const date = new Date(timestamp)
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'Invalid date'
|
||||
}
|
||||
|
||||
return date.toLocaleString()
|
||||
}
|
||||
// formatTime function removed - now using formatSmartTimestamp from utils
|
||||
|
||||
onMounted(() => {
|
||||
searchInput.value?.focus()
|
||||
|
94
frontend-vue/src/utils/time.ts
Normal file
94
frontend-vue/src/utils/time.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Smart timestamp formatting that shows appropriate level of detail based on message age
|
||||
*/
|
||||
export function formatSmartTimestamp(timestamp: string): string {
|
||||
const now = new Date()
|
||||
const date = new Date(timestamp)
|
||||
|
||||
// Handle invalid dates
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'Invalid date'
|
||||
}
|
||||
|
||||
const diffMs = now.getTime() - date.getTime()
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
||||
|
||||
// Same day (today)
|
||||
if (diffDays === 0) {
|
||||
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
// Yesterday
|
||||
if (diffDays === 1) {
|
||||
const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
return `Yesterday ${timeStr}`
|
||||
}
|
||||
|
||||
// This week (2-6 days ago)
|
||||
if (diffDays <= 6) {
|
||||
const dayStr = date.toLocaleDateString([], { weekday: 'short' })
|
||||
const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
return `${dayStr} ${timeStr}`
|
||||
}
|
||||
|
||||
// This year (more than a week ago)
|
||||
if (now.getFullYear() === date.getFullYear()) {
|
||||
return date.toLocaleDateString([], {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// Different year
|
||||
return date.toLocaleDateString([], {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Format timestamp for accessibility/screen readers with full context
|
||||
*/
|
||||
export function formatTimestampForScreenReader(timestamp: string): string {
|
||||
const date = new Date(timestamp)
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'Invalid date'
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const diffMs = now.getTime() - date.getTime()
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
||||
|
||||
// Same day
|
||||
if (diffDays === 0) {
|
||||
const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
return `today at ${timeStr}`
|
||||
}
|
||||
|
||||
// Yesterday
|
||||
if (diffDays === 1) {
|
||||
const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
return `yesterday at ${timeStr}`
|
||||
}
|
||||
|
||||
// This week
|
||||
if (diffDays <= 6) {
|
||||
const dayStr = date.toLocaleDateString([], { weekday: 'long' })
|
||||
const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
return `${dayStr} at ${timeStr}`
|
||||
}
|
||||
|
||||
// Older messages - use full date and time
|
||||
return date.toLocaleDateString([], {
|
||||
weekday: 'long',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
@@ -130,6 +130,7 @@ import { useOfflineSync } from '@/composables/useOfflineSync'
|
||||
import { useWebSocket } from '@/composables/useWebSocket'
|
||||
import { useKeyboardShortcuts } from '@/composables/useKeyboardShortcuts'
|
||||
import { useAudio } from '@/composables/useAudio'
|
||||
import { formatTimestampForScreenReader } from '@/utils/time'
|
||||
import { apiService } from '@/services/api'
|
||||
import { syncService } from '@/services/sync'
|
||||
|
||||
@@ -364,9 +365,7 @@ const handleSelectMessage = async (message: ExtendedMessage) => {
|
||||
}
|
||||
}
|
||||
|
||||
const formatTime = (timestamp: string): string => {
|
||||
return new Date(timestamp).toLocaleTimeString()
|
||||
}
|
||||
// formatTime function removed - now using formatTimestampForScreenReader from utils
|
||||
|
||||
const handleVoiceSent = () => {
|
||||
// Voice message was sent successfully
|
||||
@@ -396,8 +395,8 @@ const announceLastMessage = (position: number) => {
|
||||
}
|
||||
|
||||
const message = messages[messageIndex]
|
||||
const timeStr = formatTime(message.created_at)
|
||||
const announcement = `${message.content}; ${timeStr}`
|
||||
const timeStr = formatTimestampForScreenReader(message.created_at)
|
||||
const announcement = `${message.content}; sent ${timeStr}`
|
||||
|
||||
toastStore.info(announcement)
|
||||
|
||||
|
Reference in New Issue
Block a user