* messages times
* sending message on Enter key * multiline message * new chat on contact * paste file from buffer on ctrl+v
This commit is contained in:
parent
596f2a1210
commit
3b55ecc693
3 changed files with 111 additions and 2 deletions
|
|
@ -40,6 +40,26 @@
|
||||||
|
|
||||||
[xmpp_Compose_input] {
|
[xmpp_Compose_input] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
max-height: 8rem;
|
||||||
|
resize: vertical;
|
||||||
|
overflow: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[xmpp_Msg_head] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[xmpp_Msg_time] {
|
||||||
|
font-size: .7rem;
|
||||||
|
opacity: .5;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* media */
|
/* media */
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,17 @@ $xmpp $mol_book2
|
||||||
body /
|
body /
|
||||||
<= Roster_list $mol_list
|
<= Roster_list $mol_list
|
||||||
rows <= roster_rows /
|
rows <= roster_rows /
|
||||||
|
<= New_chat_form $mol_form
|
||||||
|
form_fields /
|
||||||
|
<= New_chat_field $mol_form_field
|
||||||
|
name @ \New chat with
|
||||||
|
control <= New_chat_input $mol_string
|
||||||
|
hint \user@example.com
|
||||||
|
value? <=> new_chat_jid? \
|
||||||
|
buttons /
|
||||||
|
<= New_chat_button $mol_button_major
|
||||||
|
title @ \Start chat
|
||||||
|
click? <=> do_new_chat? null
|
||||||
<= Room_join_form $mol_form
|
<= Room_join_form $mol_form
|
||||||
form_fields /
|
form_fields /
|
||||||
<= Room_jid_field $mol_form_field
|
<= Room_jid_field $mol_form_field
|
||||||
|
|
@ -96,8 +107,13 @@ $xmpp $mol_book2
|
||||||
rows <= message_rows* /
|
rows <= message_rows* /
|
||||||
foot /
|
foot /
|
||||||
<= Compose_input* $mol_string
|
<= Compose_input* $mol_string
|
||||||
|
dom_name \textarea
|
||||||
hint @ \Type a message…
|
hint @ \Type a message…
|
||||||
value? <=> compose*? \
|
value? <=> compose*? \
|
||||||
|
event *
|
||||||
|
^
|
||||||
|
keydown? <=> compose_keydown*? null
|
||||||
|
paste? <=> compose_paste*? null
|
||||||
<= Attach_button* $mol_button_minor
|
<= Attach_button* $mol_button_minor
|
||||||
title @ \Attach
|
title @ \Attach
|
||||||
click? <=> do_attach*? null
|
click? <=> do_attach*? null
|
||||||
|
|
@ -130,10 +146,15 @@ $xmpp $mol_book2
|
||||||
src <= msg_avatar_uri* \
|
src <= msg_avatar_uri* \
|
||||||
alt \
|
alt \
|
||||||
<= Msg_content* $mol_view
|
<= Msg_content* $mol_view
|
||||||
|
sub /
|
||||||
|
<= Msg_head* $mol_view
|
||||||
sub /
|
sub /
|
||||||
<= Msg_from* $mol_view
|
<= Msg_from* $mol_view
|
||||||
sub /
|
sub /
|
||||||
<= msg_from* \
|
<= msg_from* \
|
||||||
|
<= Msg_time* $mol_view
|
||||||
|
sub /
|
||||||
|
<= msg_time* \
|
||||||
<= Msg_body* $mol_view
|
<= Msg_body* $mol_view
|
||||||
sub /
|
sub /
|
||||||
<= msg_body* \
|
<= msg_body* \
|
||||||
|
|
|
||||||
68
xmpp.view.ts
68
xmpp.view.ts
|
|
@ -702,6 +702,9 @@ private _handle_message(el: Element) {
|
||||||
@ $mol_mem
|
@ $mol_mem
|
||||||
room_nick(next?: string) { return next ?? '' }
|
room_nick(next?: string) { return next ?? '' }
|
||||||
|
|
||||||
|
@ $mol_mem
|
||||||
|
new_chat_jid(next?: string) { return next ?? '' }
|
||||||
|
|
||||||
@ $mol_mem_key
|
@ $mol_mem_key
|
||||||
compose(_jid: string, next?: string) { return next ?? '' }
|
compose(_jid: string, next?: string) { return next ?? '' }
|
||||||
|
|
||||||
|
|
@ -905,6 +908,16 @@ private _handle_message(el: Element) {
|
||||||
|
|
||||||
open_chat(jid: string) { this.$.$mol_state_arg.value('chat', jid) }
|
open_chat(jid: string) { this.$.$mol_state_arg.value('chat', jid) }
|
||||||
|
|
||||||
|
do_new_chat() {
|
||||||
|
const jid = this.new_chat_jid().trim()
|
||||||
|
if (!jid || !jid.includes('@')) {
|
||||||
|
this.error_text('Enter a valid JID like user@server')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.new_chat_jid('')
|
||||||
|
this.$.$mol_state_arg.value('chat', jid)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Rooms ─────────────────────────────────────────────────────────────
|
// ── Rooms ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
do_join_room() {
|
do_join_room() {
|
||||||
|
|
@ -999,6 +1012,23 @@ private _handle_message(el: Element) {
|
||||||
@ $mol_mem_key // ← FIX: make reactive
|
@ $mol_mem_key // ← FIX: make reactive
|
||||||
msg_link_uri(id: string) { return this._msg_by_id.get(id)?.body.trim() ?? '' }
|
msg_link_uri(id: string) { return this._msg_by_id.get(id)?.body.trim() ?? '' }
|
||||||
|
|
||||||
|
@ $mol_mem_key
|
||||||
|
msg_time(id: string) {
|
||||||
|
const msg = this._msg_by_id.get(id)
|
||||||
|
if (!msg) return ''
|
||||||
|
const d = new Date(msg.time)
|
||||||
|
const same_day = (() => {
|
||||||
|
const now = new Date()
|
||||||
|
return d.getFullYear() === now.getFullYear()
|
||||||
|
&& d.getMonth() === now.getMonth()
|
||||||
|
&& d.getDate() === now.getDate()
|
||||||
|
})()
|
||||||
|
const time = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||||
|
if (same_day) return time
|
||||||
|
const date = d.toLocaleDateString([], { day: '2-digit', month: '2-digit' })
|
||||||
|
return `${ date } ${ time }`
|
||||||
|
}
|
||||||
|
|
||||||
// XEP-0333 status for outgoing messages.
|
// XEP-0333 status for outgoing messages.
|
||||||
// ✓ — sent locally; ✓✓ — peer received; ✓✓ (filled) — peer displayed.
|
// ✓ — sent locally; ✓✓ — peer received; ✓✓ (filled) — peer displayed.
|
||||||
@ $mol_mem_key
|
@ $mol_mem_key
|
||||||
|
|
@ -1014,6 +1044,37 @@ private _handle_message(el: Element) {
|
||||||
return '✓'
|
return '✓'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Compose input event handlers ──────────────────────────────────────
|
||||||
|
|
||||||
|
// Enter sends; Shift+Enter inserts a newline (browser default).
|
||||||
|
compose_keydown(jid: string, e?: Event | null) {
|
||||||
|
const ke = e as KeyboardEvent | null | undefined
|
||||||
|
if (!ke) return null
|
||||||
|
if (ke.key === 'Enter' && !ke.shiftKey && !ke.ctrlKey && !ke.altKey && !ke.metaKey) {
|
||||||
|
ke.preventDefault()
|
||||||
|
this.do_send(jid)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paste image / file from clipboard → upload via XEP-0363 and send the link.
|
||||||
|
compose_paste(jid: string, e?: Event | null) {
|
||||||
|
const ce = e as ClipboardEvent | null | undefined
|
||||||
|
const items = ce?.clipboardData?.items
|
||||||
|
if (!items) return null
|
||||||
|
for (const item of Array.from(items)) {
|
||||||
|
if (item.kind === 'file') {
|
||||||
|
const file = item.getAsFile()
|
||||||
|
if (file) {
|
||||||
|
ce!.preventDefault()
|
||||||
|
void this._upload_and_send(jid, file).catch(err => this.error_text(String(err)))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// ── Send text ─────────────────────────────────────────────────────────
|
// ── Send text ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
do_send(jid: string) {
|
do_send(jid: string) {
|
||||||
|
|
@ -1240,6 +1301,12 @@ private _handle_message(el: Element) {
|
||||||
el.addEventListener('scroll', () => {
|
el.addEventListener('scroll', () => {
|
||||||
if (el.scrollTop < 200) this._load_more_history(jid)
|
if (el.scrollTop < 200) this._load_more_history(jid)
|
||||||
}, { passive: true })
|
}, { passive: true })
|
||||||
|
// Late-loading images push content; if user is near the bottom, follow.
|
||||||
|
el.addEventListener('load', e => {
|
||||||
|
if (!(e.target instanceof HTMLImageElement)) return
|
||||||
|
const near = el.scrollHeight - el.scrollTop - el.clientHeight < 200
|
||||||
|
if (near) el.scrollTop = el.scrollHeight
|
||||||
|
}, true)
|
||||||
} catch {
|
} catch {
|
||||||
this._scroll_setup.delete(jid)
|
this._scroll_setup.delete(jid)
|
||||||
}
|
}
|
||||||
|
|
@ -1299,6 +1366,7 @@ private _handle_message(el: Element) {
|
||||||
const id = `l${ Date.now() }_${ Math.random().toString(36).slice(2) }`
|
const id = `l${ Date.now() }_${ Math.random().toString(36).slice(2) }`
|
||||||
this._conn.send_message(jid, get, id)
|
this._conn.send_message(jid, get, id)
|
||||||
this._add_message({ id, from: this.my_jid().split('/')[0], to: jid, body: get, time: Date.now() })
|
this._add_message({ id, from: this.my_jid().split('/')[0], to: jid, body: get, time: Date.now() })
|
||||||
|
this._scroll_to_bottom(jid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue