@devchitchat/chat 4.0.4 → 4.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -141,9 +141,7 @@ export default function CallIsland(root) {
|
|
|
141
141
|
data-emoji="${escHtml(r.emoji)}" data-msg-id="${escHtml(msgId)}"
|
|
142
142
|
type="button" title="${r.count} reaction${r.count !== 1 ? 's' : ''}">
|
|
143
143
|
${r.emoji} <span class="reaction-count">${r.count}</span>
|
|
144
|
-
</button>`).join('')
|
|
145
|
-
`<button class="reaction-add" data-msg-id="${escHtml(msgId)}" type="button"
|
|
146
|
-
title="Add reaction" aria-label="Add reaction">+</button>`
|
|
144
|
+
</button>`).join('')
|
|
147
145
|
}
|
|
148
146
|
|
|
149
147
|
// ── Hydrate seed message attachments ──────────────────────────────────────
|
|
@@ -157,18 +155,32 @@ export default function CallIsland(root) {
|
|
|
157
155
|
if (article.dataset.hydrated) continue
|
|
158
156
|
article.dataset.hydrated = '1'
|
|
159
157
|
|
|
160
|
-
// Add dm-trigger to non-self sender handles
|
|
158
|
+
// Add dm-trigger to non-self sender handles
|
|
161
159
|
const handle = article.querySelector('.message-handle[data-user-id]')
|
|
162
160
|
if (handle && handle.dataset.userId !== userId) {
|
|
163
161
|
handle.classList.add('dm-trigger')
|
|
164
162
|
handle.title = 'Send a direct message'
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
}
|
|
164
|
+
// Add hover action toolbar (react for all messages; … only for own)
|
|
165
|
+
if (!article.querySelector('.message-hover-actions')) {
|
|
166
|
+
const toolbar = document.createElement('div')
|
|
167
|
+
toolbar.className = 'message-hover-actions'
|
|
168
|
+
const reactBtn = document.createElement('button')
|
|
169
|
+
reactBtn.className = 'btn-react btn-icon'
|
|
170
|
+
reactBtn.type = 'button'
|
|
171
|
+
reactBtn.title = 'Add reaction'
|
|
172
|
+
reactBtn.setAttribute('aria-label', 'Add reaction')
|
|
173
|
+
reactBtn.textContent = '🙂'
|
|
174
|
+
toolbar.appendChild(reactBtn)
|
|
175
|
+
if (article.dataset.userId === userId) {
|
|
176
|
+
const actionsBtn = document.createElement('button')
|
|
177
|
+
actionsBtn.className = 'btn-msg-actions btn-icon'
|
|
178
|
+
actionsBtn.type = 'button'
|
|
179
|
+
actionsBtn.title = 'Message actions'
|
|
180
|
+
actionsBtn.textContent = '…'
|
|
181
|
+
toolbar.appendChild(actionsBtn)
|
|
182
|
+
}
|
|
183
|
+
article.appendChild(toolbar)
|
|
172
184
|
}
|
|
173
185
|
|
|
174
186
|
// Apply inline rendering (URLs, @mentions) to server-rendered message text.
|
|
@@ -201,6 +213,15 @@ export default function CallIsland(root) {
|
|
|
201
213
|
// Date separator before this article if date changed
|
|
202
214
|
const ts = parseInt(article.querySelector('time')?.getAttribute('datetime') ?? '0', 10)
|
|
203
215
|
if (ts) {
|
|
216
|
+
// Re-format time in the browser's local timezone (SSR bakes UTC time)
|
|
217
|
+
const timeEl = article.querySelector('.message-time')
|
|
218
|
+
if (timeEl) {
|
|
219
|
+
const localTime = new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
220
|
+
const editedSpan = timeEl.querySelector('.message-edited')
|
|
221
|
+
timeEl.textContent = localTime
|
|
222
|
+
if (editedSpan) timeEl.appendChild(editedSpan)
|
|
223
|
+
}
|
|
224
|
+
|
|
204
225
|
const dateKey = utcDateKey(ts)
|
|
205
226
|
if (prevDateKey && dateKey !== prevDateKey) {
|
|
206
227
|
article.before(makeDateSeparator(dateKey))
|
|
@@ -897,7 +918,7 @@ export default function CallIsland(root) {
|
|
|
897
918
|
if (activeReactionContextMenu && !activeReactionContextMenu.contains(e.target)) closeReactionContextMenu()
|
|
898
919
|
// Close emoji picker on click-outside
|
|
899
920
|
if (emojiPickerEl && emojiPickerEl.parentNode && !emojiPickerEl.contains(e.target)) {
|
|
900
|
-
const isReactionAddBtn = e.target.closest('.reaction-add')
|
|
921
|
+
const isReactionAddBtn = e.target.closest('.reaction-add') || e.target.closest('.btn-react')
|
|
901
922
|
if (!isReactionAddBtn) closeEmojiPicker()
|
|
902
923
|
}
|
|
903
924
|
}, { capture: true })
|
|
@@ -924,12 +945,14 @@ export default function CallIsland(root) {
|
|
|
924
945
|
}
|
|
925
946
|
})
|
|
926
947
|
|
|
927
|
-
// Delegated click on .reaction-add — open emoji picker
|
|
948
|
+
// Delegated click on .reaction-add or .btn-react — open emoji picker
|
|
928
949
|
messages.addEventListener('click', e => {
|
|
929
|
-
const
|
|
950
|
+
const addBtn = e.target.closest('.reaction-add')
|
|
951
|
+
const reactBtn = e.target.closest('.btn-react')
|
|
952
|
+
const btn = addBtn ?? reactBtn
|
|
930
953
|
if (!btn) return
|
|
931
954
|
e.stopPropagation()
|
|
932
|
-
const msgId = btn.dataset.msgId
|
|
955
|
+
const msgId = addBtn?.dataset.msgId ?? btn.closest('article.message')?.dataset.msgId
|
|
933
956
|
if (!msgId) return
|
|
934
957
|
// Toggle: close if already open for this message
|
|
935
958
|
if (emojiPickerEl && emojiPickerEl.parentNode && emojiPickerTarget === msgId) {
|
|
@@ -1690,7 +1713,15 @@ export default function CallIsland(root) {
|
|
|
1690
1713
|
// Re-initialise chat state for the new channel without touching RTC.
|
|
1691
1714
|
document.addEventListener('chatpanel:navigated', (e) => {
|
|
1692
1715
|
const { channelId: newId, name, topic, kind, seedSeq: newSeedSeq, seedFirstSeq: newFirstSeq, seedHasMore: newHasMore } = e.detail
|
|
1693
|
-
|
|
1716
|
+
|
|
1717
|
+
// Morph strips dynamically-added content (reactions, attachments, timestamps, etc.)
|
|
1718
|
+
// but may preserve data-hydrated="1". Always clear and re-hydrate.
|
|
1719
|
+
for (const a of messages.querySelectorAll('article.message[data-hydrated]')) {
|
|
1720
|
+
delete a.dataset.hydrated
|
|
1721
|
+
}
|
|
1722
|
+
hydrateSeedMessages()
|
|
1723
|
+
|
|
1724
|
+
if (newId === channelId) return // same channel — re-hydrate only, no WS channel change
|
|
1694
1725
|
|
|
1695
1726
|
// Leave old channel subscription on the server
|
|
1696
1727
|
ws.send({ t: 'channel.leave', body: { channel_id: channelId } })
|
|
@@ -1716,9 +1747,6 @@ export default function CallIsland(root) {
|
|
|
1716
1747
|
const textarea = root.querySelector('#message-input')
|
|
1717
1748
|
if (textarea) textarea.placeholder = `Message in ${name}`
|
|
1718
1749
|
|
|
1719
|
-
// Hydrate attachments + dm-trigger on the freshly morphed seed articles
|
|
1720
|
-
hydrateSeedMessages()
|
|
1721
|
-
|
|
1722
1750
|
closePicker()
|
|
1723
1751
|
|
|
1724
1752
|
// Join new channel — server responds with channel.joined + rtc.call_state.
|
|
@@ -134,7 +134,7 @@ export function makeMessageEl({ msg_id, seq, user_id, user_display_name, ts, tex
|
|
|
134
134
|
const isSelf = userId != null && user_id === userId
|
|
135
135
|
const attachmentHtml = (attachments ?? []).map(a => renderAttachment(a)).join('')
|
|
136
136
|
const editedHtml = edited_at ? '<span class="message-edited">(edited)</span>' : ''
|
|
137
|
-
const actionsHtml = isSelf ? '<button class="btn-msg-actions btn-icon" type="button" title="Message actions">…</button>' : ''
|
|
137
|
+
const actionsHtml = `<div class="message-hover-actions"><button class="btn-react btn-icon" type="button" title="Add reaction" aria-label="Add reaction">🙂</button>${isSelf ? '<button class="btn-msg-actions btn-icon" type="button" title="Message actions">…</button>' : ''}</div>`
|
|
138
138
|
const textHtml = rendered_text ?? (text ? renderText(text, { userHandle }) : '')
|
|
139
139
|
article.innerHTML = `
|
|
140
140
|
<span class="message-handle${isSelf ? '' : ' dm-trigger'}" data-user-id="${escHtml(user_id)}" title="${isSelf ? '' : 'Send a direct message'}">${escHtml(user_display_name ?? user_id)}</span>
|
|
@@ -989,10 +989,11 @@ mark { background: color-mix(in srgb, var(--accent) 30%, transparent); color: va
|
|
|
989
989
|
}
|
|
990
990
|
.btn-back-mobile:hover { color: var(--text-primary); }
|
|
991
991
|
|
|
992
|
-
/*
|
|
993
|
-
.
|
|
992
|
+
/* Hover toolbar: no hover on touch — always visible, tucked inside message */
|
|
993
|
+
.message-hover-actions {
|
|
994
994
|
opacity: 1;
|
|
995
995
|
pointer-events: auto;
|
|
996
|
+
top: 2px;
|
|
996
997
|
}
|
|
997
998
|
}
|
|
998
999
|
|
|
@@ -1767,19 +1768,43 @@ form.inline { display: inline; }
|
|
|
1767
1768
|
font-size: 14px;
|
|
1768
1769
|
}
|
|
1769
1770
|
|
|
1770
|
-
/*
|
|
1771
|
-
.
|
|
1771
|
+
/* Hover action toolbar — floats above the message on hover (Discord-style) */
|
|
1772
|
+
article.message { position: relative; }
|
|
1773
|
+
.message-hover-actions {
|
|
1772
1774
|
position: absolute;
|
|
1773
|
-
top:
|
|
1775
|
+
top: -20px;
|
|
1774
1776
|
right: 4px;
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
+
display: flex;
|
|
1778
|
+
gap: 1px;
|
|
1779
|
+
align-items: center;
|
|
1777
1780
|
opacity: 0;
|
|
1778
1781
|
pointer-events: none;
|
|
1779
1782
|
transition: opacity 0.1s;
|
|
1783
|
+
background: var(--bg-sidebar);
|
|
1784
|
+
border: 1px solid var(--border);
|
|
1785
|
+
border-radius: var(--radius-md);
|
|
1786
|
+
padding: 2px;
|
|
1787
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
1788
|
+
z-index: 10;
|
|
1789
|
+
}
|
|
1790
|
+
article.message:hover .message-hover-actions { opacity: 1; pointer-events: auto; }
|
|
1791
|
+
.btn-react,
|
|
1792
|
+
.btn-msg-actions {
|
|
1793
|
+
background: none;
|
|
1794
|
+
border: none;
|
|
1795
|
+
cursor: pointer;
|
|
1796
|
+
padding: 3px 7px;
|
|
1797
|
+
border-radius: calc(var(--radius-md) - 2px);
|
|
1798
|
+
font-size: 15px;
|
|
1799
|
+
line-height: 1;
|
|
1800
|
+
color: var(--text-secondary);
|
|
1801
|
+
transition: background var(--transition), color var(--transition);
|
|
1802
|
+
}
|
|
1803
|
+
.btn-react:hover,
|
|
1804
|
+
.btn-msg-actions:hover {
|
|
1805
|
+
background: color-mix(in srgb, var(--accent) 15%, transparent);
|
|
1806
|
+
color: var(--text-primary);
|
|
1780
1807
|
}
|
|
1781
|
-
article.message:hover .btn-msg-actions { opacity: 1; pointer-events: auto; }
|
|
1782
|
-
article.message { position: relative; }
|
|
1783
1808
|
|
|
1784
1809
|
/* Context menu */
|
|
1785
1810
|
.msg-context-menu {
|