fav tab
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
html[theme='f0ck'] {
|
html[theme='f0ck'] {
|
||||||
--accent: #9f0;
|
--accent: #9f0;
|
||||||
--accent-rgb: 153, 255, 0;
|
--accent-rgb: 153, 255, 0;
|
||||||
|
--emoji-fav-color: #f5c518;
|
||||||
--bg: #111;
|
--bg: #111;
|
||||||
--black: #000;
|
--black: #000;
|
||||||
--white: #fff;
|
--white: #fff;
|
||||||
@@ -5727,6 +5728,11 @@ body[type='login'] {
|
|||||||
border-bottom: 2px solid var(--accent, #fff);
|
border-bottom: 2px solid var(--accent, #fff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ep-tab-favs.active {
|
||||||
|
border-bottom-color: var(--emoji-fav-color, #f5c518);
|
||||||
|
background: rgba(245,197,24,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.ep-tab img,
|
.ep-tab img,
|
||||||
.ep-tab video {
|
.ep-tab video {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
@@ -5759,6 +5765,38 @@ body[type='login'] {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sticker wrapper — holds img/video + optional fav badge */
|
||||||
|
.ep-sticker-wrap {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ep-sticker-wrap:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fav star badge — top-right corner of sticker */
|
||||||
|
.ep-fav-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--emoji-fav-color, #f5c518);
|
||||||
|
pointer-events: none;
|
||||||
|
line-height: 1;
|
||||||
|
filter: drop-shadow(0 0 2px rgba(0,0,0,0.7));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide badge in the favorites tab itself — everything there is already a fav */
|
||||||
|
.emoji-picker-grid.favs-active .ep-fav-badge {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.emoji-picker-grid img:hover,
|
.emoji-picker-grid img:hover,
|
||||||
.emoji-picker-grid video:hover {
|
.emoji-picker-grid video:hover {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
@@ -8255,7 +8293,6 @@ video.autoplay-gif {
|
|||||||
border: none;
|
border: none;
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 4px;
|
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4060,25 +4060,81 @@ class CommentSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper: create img or video element for a given emoji URL
|
// Helper: create img or video element for a given emoji URL
|
||||||
|
// ── Favorites (singleton utils + context menu) ──────────────
|
||||||
|
if (!window._emojiPickerFavUtils) {
|
||||||
|
window._emojiPickerFavUtils = {
|
||||||
|
KEY: 'f0ck_emoji_favs',
|
||||||
|
get() { try { return JSON.parse(localStorage.getItem(this.KEY) || '[]'); } catch(e) { return []; } },
|
||||||
|
has(name) { return this.get().some(f => f.name === name); },
|
||||||
|
toggle(name, url) {
|
||||||
|
const favs = this.get();
|
||||||
|
const idx = favs.findIndex(f => f.name === name);
|
||||||
|
if (idx >= 0) favs.splice(idx, 1); else favs.push({ name, url });
|
||||||
|
localStorage.setItem(this.KEY, JSON.stringify(favs));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!window._favContextMenu) {
|
||||||
|
const menu = document.createElement('div');
|
||||||
|
menu.style.cssText = 'position:fixed;z-index:99999;background:var(--dropdown-bg,#222);border:1px solid var(--nav-border-color,#444);border-radius:7px;padding:4px 0;box-shadow:0 4px 20px rgba(0,0,0,0.5);display:none;min-width:180px;';
|
||||||
|
document.body.appendChild(menu);
|
||||||
|
window._favContextMenu = {
|
||||||
|
el: menu,
|
||||||
|
show(x, y, name, url, onToggle) {
|
||||||
|
const isFav = window._emojiPickerFavUtils.has(name);
|
||||||
|
menu.innerHTML = '';
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.style.cssText = 'padding:9px 16px;cursor:pointer;font-size:0.875em;color:var(--white,#fff);display:flex;align-items:center;gap:8px;border-radius:4px;margin:2px 4px;';
|
||||||
|
item.innerHTML = '<span style="font-size:1.1em;">' + (isFav ? '★' : '☆') + '</span>' + (isFav ? 'Remove from Favorites' : 'Add to Favorites');
|
||||||
|
item.addEventListener('mouseenter', () => { item.style.background = 'rgba(255,255,255,0.09)'; });
|
||||||
|
item.addEventListener('mouseleave', () => { item.style.background = ''; });
|
||||||
|
item.addEventListener('click', (e) => { e.stopPropagation(); window._emojiPickerFavUtils.toggle(name, url); menu.style.display = 'none'; if (onToggle) onToggle(); });
|
||||||
|
menu.appendChild(item);
|
||||||
|
menu.style.left = x + 'px'; menu.style.top = y + 'px'; menu.style.display = 'block';
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const r = menu.getBoundingClientRect();
|
||||||
|
if (r.right > window.innerWidth) menu.style.left = (x - r.width) + 'px';
|
||||||
|
if (r.bottom > window.innerHeight) menu.style.top = (y - r.height) + 'px';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
hide() { menu.style.display = 'none'; }
|
||||||
|
};
|
||||||
|
document.addEventListener('click', () => window._favContextMenu.hide());
|
||||||
|
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') window._favContextMenu.hide(); });
|
||||||
|
}
|
||||||
|
|
||||||
const makeEmojiEl = (url, name) => {
|
const makeEmojiEl = (url, name) => {
|
||||||
const isVideo = url && url.endsWith('.webm');
|
const isVideo = url && url.endsWith('.webm');
|
||||||
let el;
|
// media element (img or video)
|
||||||
|
let media;
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
el = document.createElement('video');
|
media = document.createElement('video');
|
||||||
el.src = url;
|
media.src = url;
|
||||||
el.autoplay = true;
|
media.autoplay = true;
|
||||||
el.loop = true;
|
media.loop = true;
|
||||||
el.muted = true;
|
media.muted = true;
|
||||||
el.playsInline = true;
|
media.playsInline = true;
|
||||||
} else {
|
} else {
|
||||||
el = document.createElement('img');
|
media = document.createElement('img');
|
||||||
el.src = url;
|
media.src = url;
|
||||||
el.loading = 'lazy';
|
media.loading = 'lazy';
|
||||||
}
|
}
|
||||||
el.title = `:${name}:`;
|
media.title = `:${name}:`;
|
||||||
el.onerror = () => { el.style.display = 'none'; };
|
media.onerror = () => { media.style.display = 'none'; };
|
||||||
|
media._stickerSrc = url;
|
||||||
|
media._stickerIsVideo = isVideo;
|
||||||
|
// Wrapper div — needed to position the fav badge overlay
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'ep-sticker-wrap';
|
||||||
el._stickerSrc = url;
|
el._stickerSrc = url;
|
||||||
el._stickerIsVideo = isVideo;
|
el._stickerIsVideo = isVideo;
|
||||||
|
el.appendChild(media);
|
||||||
|
// Fav star badge
|
||||||
|
if (window._emojiPickerFavUtils?.has(name)) {
|
||||||
|
const badge = document.createElement('i');
|
||||||
|
badge.className = 'fa-solid fa-star ep-fav-badge';
|
||||||
|
el.appendChild(badge);
|
||||||
|
}
|
||||||
// Hold-to-preview
|
// Hold-to-preview
|
||||||
let holdTimer = null;
|
let holdTimer = null;
|
||||||
let holdFired = false;
|
let holdFired = false;
|
||||||
@@ -4094,7 +4150,6 @@ class CommentSystem {
|
|||||||
clearTimeout(holdTimer);
|
clearTimeout(holdTimer);
|
||||||
if (holdFired) {
|
if (holdFired) {
|
||||||
holdFired = false;
|
holdFired = false;
|
||||||
// Suppress the click that fires on mouseup after a preview gesture
|
|
||||||
el.addEventListener('click', (e) => {
|
el.addEventListener('click', (e) => {
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -4102,16 +4157,23 @@ class CommentSystem {
|
|||||||
}
|
}
|
||||||
window.stickerPreview?.hide();
|
window.stickerPreview?.hide();
|
||||||
};
|
};
|
||||||
el.addEventListener('mousedown', startHold);
|
media.addEventListener('mousedown', startHold);
|
||||||
el.addEventListener('mouseup', endHold);
|
media.addEventListener('mouseup', endHold);
|
||||||
el.addEventListener('mouseleave', cancelHold);
|
media.addEventListener('mouseleave', cancelHold);
|
||||||
// Instant switch: if already previewing and mouse enters a new emoji, show it immediately
|
media.addEventListener('mouseenter', () => {
|
||||||
el.addEventListener('mouseenter', () => {
|
|
||||||
if (window.stickerPreview?.isShowing) window.stickerPreview.show(url, isVideo);
|
if (window.stickerPreview?.isShowing) window.stickerPreview.show(url, isVideo);
|
||||||
});
|
});
|
||||||
el.addEventListener('touchstart', startHold, { passive: true });
|
media.addEventListener('touchstart', startHold, { passive: true });
|
||||||
el.addEventListener('touchend', endHold);
|
media.addEventListener('touchend', endHold);
|
||||||
el.addEventListener('touchcancel', endHold);
|
media.addEventListener('touchcancel', endHold);
|
||||||
|
// Right-click → favorites context menu
|
||||||
|
media.addEventListener('contextmenu', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
window._favContextMenu?.show(e.clientX, e.clientY, name, url, () => {
|
||||||
|
showTab(activeTabId); // refresh to update star badges
|
||||||
|
});
|
||||||
|
});
|
||||||
return el;
|
return el;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -4153,6 +4215,34 @@ class CommentSystem {
|
|||||||
tabBar.querySelectorAll('.ep-tab').forEach(t => t.classList.toggle('active', t.dataset.packId === String(packId ?? 'null')));
|
tabBar.querySelectorAll('.ep-tab').forEach(t => t.classList.toggle('active', t.dataset.packId === String(packId ?? 'null')));
|
||||||
|
|
||||||
gridArea.innerHTML = '';
|
gridArea.innerHTML = '';
|
||||||
|
gridArea.classList.toggle('favs-active', packId === '__favs__');
|
||||||
|
|
||||||
|
// ── Favorites tab ──
|
||||||
|
if (packId === '__favs__') {
|
||||||
|
const favs = window._emojiPickerFavUtils?.get() || [];
|
||||||
|
if (!favs.length) {
|
||||||
|
const empty = document.createElement('div');
|
||||||
|
empty.style.cssText = 'grid-column:1/-1;text-align:center;padding:28px 12px;opacity:0.45;font-size:0.82em;line-height:1.5;';
|
||||||
|
empty.textContent = 'No favorites yet. Right-click any sticker to add it here.';
|
||||||
|
gridArea.appendChild(empty);
|
||||||
|
} else {
|
||||||
|
favs.forEach(({ name, url }) => {
|
||||||
|
const el = makeEmojiEl(url, name);
|
||||||
|
el.onclick = (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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const pack = packs.find(p => String(p.id ?? null) === String(packId ?? null)) || packs[0];
|
const pack = packs.find(p => String(p.id ?? null) === String(packId ?? null)) || packs[0];
|
||||||
if (!pack) return;
|
if (!pack) return;
|
||||||
|
|
||||||
@@ -4181,7 +4271,17 @@ class CommentSystem {
|
|||||||
}, { passive: true });
|
}, { passive: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build tab buttons
|
// Build tab buttons — Favorites first
|
||||||
|
const favTab = document.createElement('button');
|
||||||
|
favTab.className = 'ep-tab ep-tab-favs';
|
||||||
|
favTab.dataset.packId = '__favs__';
|
||||||
|
favTab.title = 'Favorites';
|
||||||
|
favTab.type = 'button';
|
||||||
|
favTab.innerHTML = '<i class="fa-solid fa-star" style="color:var(--emoji-fav-color,#f5c518);font-size:1.1em;"></i>';
|
||||||
|
favTab.addEventListener('mousedown', e => e.preventDefault());
|
||||||
|
favTab.addEventListener('click', () => showTab('__favs__'));
|
||||||
|
tabBar.appendChild(favTab);
|
||||||
|
|
||||||
packs.forEach((pack, i) => {
|
packs.forEach((pack, i) => {
|
||||||
const tab = document.createElement('button');
|
const tab = document.createElement('button');
|
||||||
tab.className = 'ep-tab';
|
tab.className = 'ep-tab';
|
||||||
@@ -4226,26 +4326,12 @@ class CommentSystem {
|
|||||||
const isVisible = picker.style.display !== 'none';
|
const isVisible = picker.style.display !== 'none';
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
picker.style.display = 'none';
|
picker.style.display = 'none';
|
||||||
if (closeHandler) {
|
|
||||||
document.removeEventListener('click', closeHandler);
|
|
||||||
closeHandler = null;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Rebuild content only if the cache has been updated since last build
|
|
||||||
buildPickerContent();
|
buildPickerContent();
|
||||||
picker.style.display = ''; // Reset to CSS default (flex)
|
picker.style.display = '';
|
||||||
requestAnimationFrame(() => requestAnimationFrame(() =>
|
requestAnimationFrame(() => requestAnimationFrame(() =>
|
||||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
|
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
|
||||||
));
|
));
|
||||||
closeHandler = (ev) => {
|
|
||||||
const isSubmit = ev.target.closest?.('.submit-comment');
|
|
||||||
if (!picker.contains(ev.target) && ev.target !== trigger && !isSubmit) {
|
|
||||||
picker.style.display = 'none';
|
|
||||||
document.removeEventListener('click', closeHandler);
|
|
||||||
closeHandler = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setTimeout(() => document.addEventListener('click', closeHandler), 0);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -4311,17 +4397,6 @@ class CommentSystem {
|
|||||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
|
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' })
|
||||||
));
|
));
|
||||||
|
|
||||||
// Set up close handler
|
|
||||||
// Set up close handler
|
|
||||||
closeHandler = (ev) => {
|
|
||||||
const isSubmit = ev.target.closest?.('.submit-comment');
|
|
||||||
if (!picker.contains(ev.target) && ev.target !== trigger && !isSubmit) {
|
|
||||||
picker.style.display = 'none';
|
|
||||||
document.removeEventListener('click', closeHandler);
|
|
||||||
closeHandler = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setTimeout(() => document.addEventListener('click', closeHandler), 0);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user