tg stickers v3
This commit is contained in:
@@ -497,9 +497,13 @@ if (window.__dmLoaded) {
|
|||||||
if (oldestMsgId === null || m.id < oldestMsgId) oldestMsgId = m.id;
|
if (oldestMsgId === null || m.id < oldestMsgId) oldestMsgId = m.id;
|
||||||
|
|
||||||
if (prepend) {
|
if (prepend) {
|
||||||
thread.insertBefore(buildMessageBubble(m), thread.firstChild);
|
const bubble = buildMessageBubble(m);
|
||||||
|
thread.insertBefore(bubble, thread.firstChild);
|
||||||
|
playDmEmojiVideos(bubble);
|
||||||
} else {
|
} else {
|
||||||
thread.appendChild(buildMessageBubble(m));
|
const bubble = buildMessageBubble(m);
|
||||||
|
thread.appendChild(bubble);
|
||||||
|
playDmEmojiVideos(bubble);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +535,9 @@ if (window.__dmLoaded) {
|
|||||||
renderedIds.add(m.id);
|
renderedIds.add(m.id);
|
||||||
threadMessages.push(m);
|
threadMessages.push(m);
|
||||||
if (m.id > latestMsgId) latestMsgId = m.id;
|
if (m.id > latestMsgId) latestMsgId = m.id;
|
||||||
thread.appendChild(buildMessageBubble(m));
|
const bubble = buildMessageBubble(m);
|
||||||
|
thread.appendChild(bubble);
|
||||||
|
playDmEmojiVideos(bubble);
|
||||||
appended = true;
|
appended = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,10 +588,22 @@ if (window.__dmLoaded) {
|
|||||||
return text.replace(/:([a-z0-9_]+):/g, (match, name) => {
|
return text.replace(/:([a-z0-9_]+):/g, (match, name) => {
|
||||||
const url = emojis[name];
|
const url = emojis[name];
|
||||||
if (!url) return match;
|
if (!url) return match;
|
||||||
|
if (url.endsWith('.webm')) {
|
||||||
|
return `<video src="${url}" class="emoji" title="${match}" autoplay loop muted playsinline></video>`;
|
||||||
|
}
|
||||||
return `<img src="${url}" class="emoji" alt="${match}" title="${match}" loading="lazy">`;
|
return `<img src="${url}" class="emoji" alt="${match}" title="${match}" loading="lazy">`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function playDmEmojiVideos(el) {
|
||||||
|
if (!el) return;
|
||||||
|
el.querySelectorAll('video.emoji').forEach(v => {
|
||||||
|
v.play().catch(() => {
|
||||||
|
v.addEventListener('canplay', () => v.play().catch(() => {}), { once: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderDmContent(plaintext) {
|
function renderDmContent(plaintext) {
|
||||||
if (!plaintext) return '';
|
if (!plaintext) return '';
|
||||||
|
|
||||||
@@ -1748,15 +1766,37 @@ if (window.__dmLoaded) {
|
|||||||
window.addEventListener('resize', () => { if (autocomplete.style.display !== 'none') positionAC(); });
|
window.addEventListener('resize', () => { if (autocomplete.style.display !== 'none') positionAC(); });
|
||||||
// ─────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function renderEmojisIntoPicker(emojis) {
|
// ── Helper: img or video element for a given emoji URL ────────────────
|
||||||
|
function makeDmEmojiEl(url, name) {
|
||||||
|
let el;
|
||||||
|
if (url && url.endsWith('.webm')) {
|
||||||
|
el = document.createElement('video');
|
||||||
|
el.src = url;
|
||||||
|
el.autoplay = true;
|
||||||
|
el.loop = true;
|
||||||
|
el.muted = true;
|
||||||
|
el.playsInline = true;
|
||||||
|
} else {
|
||||||
|
el = document.createElement('img');
|
||||||
|
el.src = url;
|
||||||
|
el.loading = 'lazy';
|
||||||
|
}
|
||||||
|
el.title = `:${name}:`;
|
||||||
|
el.onerror = () => { el.style.display = 'none'; };
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDmPickerContent(emojis, packs) {
|
||||||
picker.innerHTML = '';
|
picker.innerHTML = '';
|
||||||
|
picker.classList.remove('has-tabs');
|
||||||
|
|
||||||
|
const hasPacks = packs && packs.length > 0;
|
||||||
|
|
||||||
|
if (!hasPacks) {
|
||||||
|
// Flat list — old behaviour
|
||||||
Object.entries(emojis).forEach(([name, url]) => {
|
Object.entries(emojis).forEach(([name, url]) => {
|
||||||
const img = document.createElement('img');
|
const el = makeDmEmojiEl(url, name);
|
||||||
img.src = url;
|
el.addEventListener('click', (ev) => {
|
||||||
img.title = `:${name}:`;
|
|
||||||
img.loading = 'lazy';
|
|
||||||
img.onerror = () => { img.style.display = 'none'; };
|
|
||||||
img.addEventListener('click', (ev) => {
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const pos = textarea.selectionStart ?? textarea.value.length;
|
const pos = textarea.selectionStart ?? textarea.value.length;
|
||||||
const val = textarea.value;
|
const val = textarea.value;
|
||||||
@@ -1764,23 +1804,89 @@ if (window.__dmLoaded) {
|
|||||||
textarea.focus();
|
textarea.focus();
|
||||||
textarea.selectionStart = textarea.selectionEnd = pos + name.length + 4;
|
textarea.selectionStart = textarea.selectionEnd = pos + name.length + 4;
|
||||||
});
|
});
|
||||||
picker.appendChild(img);
|
picker.appendChild(el);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
function populatePicker() {
|
|
||||||
if (picker.querySelector('img')) return; // already populated
|
|
||||||
|
|
||||||
// Prefer CommentSystem static cache (filled on any item page in this session)
|
|
||||||
const cached = (typeof CommentSystem !== 'undefined' && CommentSystem.emojiCache)
|
|
||||||
|| window.commentSystem?.customEmojis;
|
|
||||||
|
|
||||||
if (cached && Object.keys(cached).length) {
|
|
||||||
renderEmojisIntoPicker(cached);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No cache — fetch directly (happens when /messages is the first page visited)
|
// ── Tabbed picker ──
|
||||||
|
picker.classList.add('has-tabs');
|
||||||
|
|
||||||
|
const tabBar = document.createElement('div');
|
||||||
|
tabBar.className = 'emoji-picker-tabs';
|
||||||
|
|
||||||
|
const gridArea = document.createElement('div');
|
||||||
|
gridArea.className = 'emoji-picker-grid';
|
||||||
|
|
||||||
|
let activeTabId = null;
|
||||||
|
|
||||||
|
const showTab = (packId) => {
|
||||||
|
activeTabId = packId;
|
||||||
|
tabBar.querySelectorAll('.ep-tab').forEach(t =>
|
||||||
|
t.classList.toggle('active', t.dataset.packId === String(packId ?? 'null'))
|
||||||
|
);
|
||||||
|
gridArea.innerHTML = '';
|
||||||
|
const pack = packs.find(p => String(p.id ?? null) === String(packId ?? null)) || packs[0];
|
||||||
|
if (!pack) return;
|
||||||
|
pack.emojis.forEach(({ name, url }) => {
|
||||||
|
const el = makeDmEmojiEl(url, name);
|
||||||
|
el.addEventListener('click', (ev) => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const pos = textarea.selectionStart ?? textarea.value.length;
|
||||||
|
const val = textarea.value;
|
||||||
|
textarea.value = val.slice(0, pos) + `:${name}:` + val.slice(pos);
|
||||||
|
textarea.focus();
|
||||||
|
const newPos = pos + name.length + 2;
|
||||||
|
textarea.setSelectionRange(newPos, newPos);
|
||||||
|
});
|
||||||
|
gridArea.appendChild(el);
|
||||||
|
});
|
||||||
|
// Autoplay any webm stickers in the grid
|
||||||
|
gridArea.querySelectorAll('video').forEach(v =>
|
||||||
|
v.play().catch(() => v.addEventListener('canplay', () => v.play().catch(() => {}), { once: true }))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
packs.forEach((pack) => {
|
||||||
|
const tab = document.createElement('button');
|
||||||
|
tab.className = 'ep-tab';
|
||||||
|
tab.dataset.packId = String(pack.id ?? null);
|
||||||
|
tab.title = pack.name || 'Emojis';
|
||||||
|
tab.type = 'button';
|
||||||
|
// Always use static img for tab icon (thumb_url or first non-video emoji)
|
||||||
|
if (pack.emojis && pack.emojis.length > 0) {
|
||||||
|
const iconUrl = pack.thumb_url
|
||||||
|
|| (pack.emojis.find(e => !e.url.endsWith('.webm')) || pack.emojis[0]).url;
|
||||||
|
const icon = document.createElement('img');
|
||||||
|
icon.src = iconUrl;
|
||||||
|
icon.alt = pack.name;
|
||||||
|
icon.loading = 'lazy';
|
||||||
|
tab.appendChild(icon);
|
||||||
|
} else {
|
||||||
|
tab.textContent = '☺';
|
||||||
|
}
|
||||||
|
tab.addEventListener('mousedown', e => e.preventDefault());
|
||||||
|
tab.addEventListener('click', () => showTab(pack.id ?? null));
|
||||||
|
tabBar.appendChild(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
picker.appendChild(tabBar);
|
||||||
|
picker.appendChild(gridArea);
|
||||||
|
showTab(packs[0]?.id ?? null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function populatePicker() {
|
||||||
|
// Only use cache if BOTH emojis and packs are present — otherwise fetch fresh
|
||||||
|
// so the tab bar is always built from the full /api/v2/emojis response.
|
||||||
|
const cachedPacks = (typeof CommentSystem !== 'undefined' && CommentSystem.emojiPacks) || null;
|
||||||
|
const cachedEmojis = (typeof CommentSystem !== 'undefined' && CommentSystem.emojiCache) || null;
|
||||||
|
|
||||||
|
if (cachedEmojis && Object.keys(cachedEmojis).length && cachedPacks && cachedPacks.length > 0) {
|
||||||
|
buildDmPickerContent(cachedEmojis, cachedPacks);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch fresh — ensures packs are always included
|
||||||
picker.innerHTML = '<div style="padding:8px;color:#aaa;font-size:0.82em;">Loading…</div>';
|
picker.innerHTML = '<div style="padding:8px;color:#aaa;font-size:0.82em;">Loading…</div>';
|
||||||
fetch('/api/v2/emojis')
|
fetch('/api/v2/emojis')
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
@@ -1788,9 +1894,11 @@ if (window.__dmLoaded) {
|
|||||||
if (data.success && data.emojis?.length) {
|
if (data.success && data.emojis?.length) {
|
||||||
const map = {};
|
const map = {};
|
||||||
data.emojis.forEach(e => { map[e.name] = e.url; });
|
data.emojis.forEach(e => { map[e.name] = e.url; });
|
||||||
// Populate CommentSystem cache for future use
|
if (typeof CommentSystem !== 'undefined') {
|
||||||
if (typeof CommentSystem !== 'undefined') CommentSystem.emojiCache = map;
|
CommentSystem.emojiCache = map;
|
||||||
renderEmojisIntoPicker(map);
|
CommentSystem.emojiPacks = data.packs || [];
|
||||||
|
}
|
||||||
|
buildDmPickerContent(map, data.packs || []);
|
||||||
} else {
|
} else {
|
||||||
picker.innerHTML = '<div style="padding:8px;color:#aaa;font-size:0.82em;">No emojis yet</div>';
|
picker.innerHTML = '<div style="padding:8px;color:#aaa;font-size:0.82em;">No emojis yet</div>';
|
||||||
}
|
}
|
||||||
@@ -1807,6 +1915,9 @@ if (window.__dmLoaded) {
|
|||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
populatePicker();
|
populatePicker();
|
||||||
picker.style.display = '';
|
picker.style.display = '';
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() =>
|
||||||
|
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
|
||||||
|
));
|
||||||
closeHandler = (ev) => {
|
closeHandler = (ev) => {
|
||||||
if (!picker.contains(ev.target) && ev.target !== trigger) {
|
if (!picker.contains(ev.target) && ev.target !== trigger) {
|
||||||
picker.style.display = 'none';
|
picker.style.display = 'none';
|
||||||
@@ -1821,8 +1932,10 @@ if (window.__dmLoaded) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure the emoji picker is updated when emojis are ready
|
// Refresh picker when emojis are ready (e.g. first page load)
|
||||||
window.addEventListener('f0ck:emojis_ready', populatePicker);
|
window.addEventListener('f0ck:emojis_ready', () => {
|
||||||
|
if (picker.style.display !== 'none') populatePicker();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupSendForm() {
|
function setupSendForm() {
|
||||||
@@ -1908,7 +2021,9 @@ if (window.__dmLoaded) {
|
|||||||
renderedIds.add(res.id);
|
renderedIds.add(res.id);
|
||||||
if (res.id > latestMsgId) latestMsgId = res.id;
|
if (res.id > latestMsgId) latestMsgId = res.id;
|
||||||
const msg = { id: res.id, sender_id: myId, plaintext: text, created_at: res.created_at || new Date().toISOString() };
|
const msg = { id: res.id, sender_id: myId, plaintext: text, created_at: res.created_at || new Date().toISOString() };
|
||||||
thread.appendChild(buildMessageBubble(msg));
|
const sentBubble = buildMessageBubble(msg);
|
||||||
|
thread.appendChild(sentBubble);
|
||||||
|
playDmEmojiVideos(sentBubble);
|
||||||
snapToBottom(thread, true); // Force scroll for your own sent message
|
snapToBottom(thread, true); // Force scroll for your own sent message
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -2429,7 +2544,9 @@ if (window.__dmLoaded) {
|
|||||||
// Re-render everyone
|
// Re-render everyone
|
||||||
for (const m of threadMessages) {
|
for (const m of threadMessages) {
|
||||||
renderedIds.add(m.id);
|
renderedIds.add(m.id);
|
||||||
thread.appendChild(buildMessageBubble(m));
|
const bubble = buildMessageBubble(m);
|
||||||
|
thread.appendChild(bubble);
|
||||||
|
playDmEmojiVideos(bubble);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAtBottom) snapToBottom(thread, true);
|
if (isAtBottom) snapToBottom(thread, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user