fix: auto-open chat with conversation ID, show listing title and time in messages

This commit is contained in:
2026-02-10 07:58:16 +01:00
parent 73769d6af2
commit 8479fa2071
11 changed files with 73 additions and 22 deletions

View File

@@ -12,7 +12,7 @@ import { reputationService } from '../services/reputation.js'
class ChatWidget extends HTMLElement { class ChatWidget extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ['listing-id', 'recipient-name', 'seller-public-key'] return ['listing-id', 'recipient-name', 'seller-public-key', 'conversation-id']
} }
constructor() { constructor() {
@@ -33,6 +33,7 @@ class ChatWidget extends HTMLElement {
this.listingId = this.getAttribute('listing-id') this.listingId = this.getAttribute('listing-id')
this.recipientName = this.getAttribute('recipient-name') || 'Seller' this.recipientName = this.getAttribute('recipient-name') || 'Seller'
this.sellerPublicKey = this.getAttribute('seller-public-key') this.sellerPublicKey = this.getAttribute('seller-public-key')
this.conversationId = this.getAttribute('conversation-id')
this.render() this.render()
} }
@@ -45,6 +46,7 @@ class ChatWidget extends HTMLElement {
if (this._initialized) return if (this._initialized) return
this._initialized = true this._initialized = true
this.conversationId = this.getAttribute('conversation-id') || this.conversationId
await cryptoService.ready await cryptoService.ready
if (!cryptoService.getPublicKey()) { if (!cryptoService.getPublicKey()) {
@@ -68,26 +70,39 @@ class ChatWidget extends HTMLElement {
async initConversation() { async initConversation() {
try { try {
if (!this.sellerPublicKey) { if (this.conversationId) {
this.error = 'no-seller-key' this.conversation = await conversationsService.getConversation(this.conversationId)
this.loading = false if (!this.conversation) {
this.render() this.error = 'init-failed'
return this.loading = false
} this.render()
return
}
this.mySecretKey = await conversationsService.getMySecretKeyForConversation(this.conversation)
await this.loadMessages()
await this.loadDealState()
} else {
if (!this.sellerPublicKey) {
this.error = 'no-seller-key'
this.loading = false
this.render()
return
}
const pinStatus = keyPinningService.check(this.listingId, this.sellerPublicKey) const pinStatus = keyPinningService.check(this.listingId, this.sellerPublicKey)
if (pinStatus === 'changed') { if (pinStatus === 'changed') {
this.keyWarning = true this.keyWarning = true
this.loading = false this.loading = false
this.render() this.render()
this.setupEventListeners() this.setupEventListeners()
return return
} }
this.conversation = await conversationsService.startOrGetConversation(this.listingId, this.sellerPublicKey) this.conversation = await conversationsService.startOrGetConversation(this.listingId, this.sellerPublicKey)
this.mySecretKey = await conversationsService.getMySecretKeyForConversation(this.conversation) this.mySecretKey = await conversationsService.getMySecretKeyForConversation(this.conversation)
await this.loadMessages() await this.loadMessages()
await this.loadDealState() await this.loadDealState()
}
} catch (e) { } catch (e) {
console.error('Failed to init conversation:', e) console.error('Failed to init conversation:', e)
this.error = 'init-failed' this.error = 'init-failed'

View File

@@ -75,6 +75,16 @@ class PageListing extends HTMLElement {
this.render() this.render()
this.setupEventListeners() this.setupEventListeners()
this.updateMetaTags() this.updateMetaTags()
if (this.dataset.chat && auth.isLoggedIn()) {
const chatWidget = this.querySelector('chat-widget')
if (this.dataset.chat !== '1') {
chatWidget?.setAttribute('conversation-id', this.dataset.chat)
}
const dialog = this.querySelector('#contact-dialog')
dialog?.showModal()
chatWidget?.activate()
}
} }
updateMetaTags() { updateMetaTags() {

View File

@@ -51,6 +51,17 @@ class PageMessages extends HTMLElement {
const conversations = await conversationsService.getMyConversations() const conversations = await conversationsService.getMyConversations()
this.conversations = conversations || [] this.conversations = conversations || []
const missing = this.conversations.filter(c => typeof c.listing_id !== 'object')
if (missing.length > 0) {
await Promise.all(missing.map(async conv => {
try {
const listing = await directus.getListing(conv.listing_id)
if (listing) conv.listing_id = listing
} catch { /* listing may not be accessible */ }
}))
}
this.loading = false this.loading = false
this.updateContent() this.updateContent()
} catch (err) { } catch (err) {
@@ -139,11 +150,13 @@ class PageMessages extends HTMLElement {
const listingId = listing?.id || conv.listing_id const listingId = listing?.id || conv.listing_id
const imageId = listing?.images?.[0]?.directus_files_id?.id const imageId = listing?.images?.[0]?.directus_files_id?.id
const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 80) : '' const imageUrl = imageId ? directus.getThumbnailUrl(imageId, 80) : ''
const title = listing?.status === 'deleted' ? t('messages.listingRemoved') : (listing?.title || t('messages.unknownListing')) const title = listing?.status === 'deleted'
? t('messages.listingRemoved')
: (listing?.title || t('messages.listing'))
const dateStr = this.formatDate(conv.date_updated || conv.date_created) const dateStr = this.formatDate(conv.date_updated || conv.date_created)
return /* html */` return /* html */`
<a href="#/listing/${listingId}" class="conversation-item" data-conv-id="${conv.id}"> <a href="#/listing/${listingId}?chat=${conv.id}" class="conversation-item" data-conv-id="${conv.id}">
<div class="conversation-image"> <div class="conversation-image">
${imageUrl ${imageUrl
? `<img src="${imageUrl}" alt="" loading="lazy">` ? `<img src="${imageUrl}" alt="" loading="lazy">`
@@ -165,7 +178,7 @@ class PageMessages extends HTMLElement {
const diffMs = now - date const diffMs = now - date
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)) const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
if (diffDays === 0) return t('messages.today') if (diffDays === 0) return `${t('messages.today')}, ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
if (diffDays === 1) return t('messages.yesterday') if (diffDays === 1) return t('messages.yesterday')
if (diffDays < 7) return t('messages.daysAgo', { days: diffDays }) if (diffDays < 7) return t('messages.daysAgo', { days: diffDays })

View File

@@ -43,6 +43,12 @@ class ConversationsService {
} }
} }
conversations.sort((a, b) => {
const da = new Date(b.date_updated || b.date_created)
const db = new Date(a.date_updated || a.date_created)
return da - db
})
return conversations.map(conv => { return conversations.map(conv => {
const isP1 = allHashes.has(conv.participant_hash_1) const isP1 = allHashes.has(conv.participant_hash_1)
return { return {

View File

@@ -257,6 +257,7 @@
"loginHint": "Melde dich an, um deine Nachrichten zu sehen.", "loginHint": "Melde dich an, um deine Nachrichten zu sehen.",
"login": "Anmelden", "login": "Anmelden",
"unknownListing": "Unbekannte Anzeige", "unknownListing": "Unbekannte Anzeige",
"listing": "Anzeige",
"today": "Heute", "today": "Heute",
"yesterday": "Gestern", "yesterday": "Gestern",
"daysAgo": "Vor {{days}} Tagen", "daysAgo": "Vor {{days}} Tagen",

View File

@@ -257,6 +257,7 @@
"loginHint": "Log in to see your messages.", "loginHint": "Log in to see your messages.",
"login": "Login", "login": "Login",
"unknownListing": "Unknown listing", "unknownListing": "Unknown listing",
"listing": "Listing",
"today": "Today", "today": "Today",
"yesterday": "Yesterday", "yesterday": "Yesterday",
"daysAgo": "{{days}} days ago", "daysAgo": "{{days}} days ago",

View File

@@ -257,6 +257,7 @@
"loginHint": "Inicia sesión para ver tus mensajes.", "loginHint": "Inicia sesión para ver tus mensajes.",
"login": "Iniciar sesión", "login": "Iniciar sesión",
"unknownListing": "Anuncio desconocido", "unknownListing": "Anuncio desconocido",
"listing": "Anuncio",
"today": "Hoy", "today": "Hoy",
"yesterday": "Ayer", "yesterday": "Ayer",
"daysAgo": "Hace {{days}} días", "daysAgo": "Hace {{days}} días",

View File

@@ -257,6 +257,7 @@
"loginHint": "Connectez-vous pour voir vos messages.", "loginHint": "Connectez-vous pour voir vos messages.",
"login": "Connexion", "login": "Connexion",
"unknownListing": "Annonce inconnue", "unknownListing": "Annonce inconnue",
"listing": "Annonce",
"today": "Aujourd'hui", "today": "Aujourd'hui",
"yesterday": "Hier", "yesterday": "Hier",
"daysAgo": "Il y a {{days}} jours", "daysAgo": "Il y a {{days}} jours",

View File

@@ -257,6 +257,7 @@
"loginHint": "Accedi per vedere i tuoi messaggi.", "loginHint": "Accedi per vedere i tuoi messaggi.",
"login": "Accedi", "login": "Accedi",
"unknownListing": "Annuncio sconosciuto", "unknownListing": "Annuncio sconosciuto",
"listing": "Annuncio",
"today": "Oggi", "today": "Oggi",
"yesterday": "Ieri", "yesterday": "Ieri",
"daysAgo": "{{days}} giorni fa", "daysAgo": "{{days}} giorni fa",

View File

@@ -257,6 +257,7 @@
"loginHint": "Faça login para ver suas mensagens.", "loginHint": "Faça login para ver suas mensagens.",
"login": "Entrar", "login": "Entrar",
"unknownListing": "Anúncio desconhecido", "unknownListing": "Anúncio desconhecido",
"listing": "Anúncio",
"today": "Hoje", "today": "Hoje",
"yesterday": "Ontem", "yesterday": "Ontem",
"daysAgo": "{{days}} dias atrás", "daysAgo": "{{days}} dias atrás",

View File

@@ -257,6 +257,7 @@
"loginHint": "Войдите, чтобы увидеть свои сообщения.", "loginHint": "Войдите, чтобы увидеть свои сообщения.",
"login": "Войти", "login": "Войти",
"unknownListing": "Неизвестное объявление", "unknownListing": "Неизвестное объявление",
"listing": "Объявление",
"today": "Сегодня", "today": "Сегодня",
"yesterday": "Вчера", "yesterday": "Вчера",
"daysAgo": "{{days}} дн. назад", "daysAgo": "{{days}} дн. назад",