cc-sidepanel: emoji picker, 475 glyphs from celest-chat's table
The panel had no picker before this. 8 category tabs plus a recents tab, table lifted verbatim from celest-chat's 07-emoji.js so the house row (💜🌙🐝🔫) carries over. Offline by construction: no library, no fetch, the panel's CSP would block a set anyway. Inserts at the caret rather than appending, and the pad stays open so glyphs can be stacked. Recents (last 24) live in localStorage under `ccsidepanel-emoji-recent`; the pad opens on that tab once it has entries. `#emoji-pad[hidden] { display: none }` is spelled out because an ID selector outranks the UA's `[hidden]` rule — without it the pad never closes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,40 @@
|
||||
#input-row { display: flex; gap: 6px; }
|
||||
#text { flex: 1; resize: none; height: 44px; }
|
||||
#send { cursor: pointer; background: #6b4fd6; border-color: #6b4fd6; padding: 0 14px; }
|
||||
#emoji-btn { cursor: pointer; padding: 0 9px; font-size: 15px; line-height: 1; }
|
||||
|
||||
#emoji-pad {
|
||||
background: #1b1826; border: 1px solid #3a3548; border-radius: 6px;
|
||||
padding: 6px; margin-bottom: 6px;
|
||||
}
|
||||
/* An ID selector outranks the UA's [hidden]{display:none}, so `hidden` alone
|
||||
never closes this pad — it has to be spelled out here. */
|
||||
#emoji-pad[hidden] { display: none; }
|
||||
|
||||
#emoji-cats {
|
||||
display: flex; gap: 2px; margin-bottom: 5px;
|
||||
border-bottom: 1px solid #2c2836; padding-bottom: 5px;
|
||||
}
|
||||
#emoji-cats button {
|
||||
flex: 1; border: 0; background: transparent; cursor: pointer;
|
||||
font-size: 14px; line-height: 1; padding: 4px 0; border-radius: 4px;
|
||||
}
|
||||
#emoji-cats button:hover { background: #2f2a40; }
|
||||
#emoji-cats button.on { background: #3a3155; box-shadow: inset 0 -2px 0 #a78bfa; }
|
||||
|
||||
#emoji-lbl {
|
||||
font-size: 10px; opacity: .45; margin-bottom: 4px;
|
||||
text-transform: lowercase; letter-spacing: .04em;
|
||||
}
|
||||
#emoji-grid {
|
||||
display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px;
|
||||
max-height: 168px; overflow-y: auto;
|
||||
}
|
||||
#emoji-grid button {
|
||||
border: 0; background: transparent; padding: 4px 0;
|
||||
font-size: 17px; line-height: 1.1; cursor: pointer; border-radius: 4px;
|
||||
}
|
||||
#emoji-grid button:hover { background: #2f2a40; }
|
||||
#status { font-size: 11px; opacity: .6; margin-bottom: 6px; }
|
||||
#ctx {
|
||||
font-size: 11px; color: #9d8fd0; margin-bottom: 6px;
|
||||
@@ -93,7 +127,13 @@
|
||||
<div id="ctx"></div>
|
||||
<div id="status">—</div>
|
||||
<div id="screen">connecting…</div>
|
||||
<div id="emoji-pad" hidden>
|
||||
<div id="emoji-cats"></div>
|
||||
<div id="emoji-lbl"></div>
|
||||
<div id="emoji-grid"></div>
|
||||
</div>
|
||||
<div id="input-row">
|
||||
<button id="emoji-btn" title="emoji (Esc to close)">🙂</button>
|
||||
<textarea id="text" placeholder="type, enter to send"></textarea>
|
||||
<button id="send">send</button>
|
||||
</div>
|
||||
|
||||
@@ -211,6 +211,88 @@ async function sendText() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Emoji pad ───────────────────────────────────────────────────────────────
|
||||
// Category table lifted verbatim from celest-chat's 07-emoji.js — same house,
|
||||
// same taste, and the "house" row only means something in this room. Offline by
|
||||
// construction: no library, no fetch, the panel's CSP would block a set anyway.
|
||||
const EMOJI = [
|
||||
["🐝","house", "💜 🌙 🐝 🔫 ✨ 🕯️ 🐙 🌻 🖤 🩵 🌸 🔥 ⚡ 🎯 🧠 🪄 🛠️ 🧻 📬 🚪 🏠 ♾️ 🌌 🫖 🦁 🐺 💧 🌳 🗼 🎰"],
|
||||
["😀","faces", "😀 😃 😄 😁 😆 😅 🤣 😂 🙂 🙃 😉 😊 😇 🥰 😍 🤩 😘 😗 😚 😙 🥲 😋 😛 😜 🤪 😝 🤗 🤭 🤫 🤔 🤨 😐 😑 😶 😏 😒 🙄 😬 😮💨 🤥 😌 😔 😪 🤤 😴 🥱 😵💫 🤐 🥴 🤢 🤮 🤧 🥵 🥶 😎 🤓 🧐 😕 😟 🙁 😮 😯 😲 😳 🥺 😦 😧 😨 😰 😥 😢 😭 😱 😖 😣 😞 😓 😩 😫 😤 😡 😠 🤬 😈 💀 🤡"],
|
||||
["🫶","hands", "👋 🤚 🖐️ ✋ 🖖 👌 🤌 🤏 ✌️ 🤞 🫰 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍 👎 ✊ 👊 🤛 🤜 👏 🙌 🫶 👐 🤲 🤝 🙏 ✍️ 💅 🤳 💪 🦾 🦵 🦶 👀 👁️ 🧠 🫀 🫁 🦷 👅 👄"],
|
||||
["🐈","animals","🐶 🐱 🐭 🐹 🐰 🦊 🐻 🐼 🐨 🐯 🦁 🐮 🐷 🐸 🐵 🙈 🙉 🙊 🐔 🐧 🐦 🐤 🦆 🦉 🦇 🐺 🐗 🐴 🦄 🐝 🪲 🦋 🐌 🐞 🐜 🕷️ 🦂 🐢 🐍 🦎 🐙 🦑 🦐 🦀 🐡 🐠 🐟 🐬 🐳 🐋 🦈 🐊 🐅 🦓 🦍 🐘 🦛 🐫 🦒 🦘 🐈 🐕 🕊️"],
|
||||
["🍜","food", "🍏 🍎 🍐 🍊 🍋 🍌 🍉 🍇 🍓 🫐 🍈 🍒 🍑 🥭 🍍 🥥 🥝 🍅 🥑 🥦 🥕 🌽 🌶️ 🥔 🍠 🥐 🍞 🥖 🧀 🥚 🍳 🥞 🧇 🥓 🍔 🍟 🍕 🌭 🥪 🌮 🌯 🥙 🍜 🍲 🍛 🍣 🍱 🥟 🍤 🍙 🍚 🍥 🥠 🍦 🍰 🎂 🍫 🍬 🍭 🍮 ☕ 🍵 🧋 🥤 🍺 🍻 🥂 🍷 🥃"],
|
||||
["🚀","things", "⌨️ 🖥️ 🖱️ 💻 📱 🕹️ 💾 💿 📀 🔌 🔋 🪫 📷 🎥 📺 📻 🎙️ 🎚️ 🎛️ ⏱️ ⏰ 🕰️ 🔍 🔎 🔒 🔓 🔑 🗝️ 🔨 🪓 ⛏️ 🛠️ 🗡️ ⚔️ 🔧 🪛 🔩 ⚙️ 🧰 🧲 💣 🧨 🪤 🧪 🧬 🔬 🔭 📡 💉 💊 🚀 🛸 ✈️ 🚁 🚗 🏍️ 🚲 ⛵ 🛰️"],
|
||||
["🌍","nature", "🌍 🌎 🌏 🌑 🌒 🌓 🌔 🌕 🌖 🌗 🌘 🌙 🌚 🌛 🌜 ☀️ 🌝 🌞 ⭐ 🌟 💫 ✨ ☄️ 🪐 🌌 ☁️ ⛅ 🌤️ 🌥️ 🌦️ 🌧️ ⛈️ 🌩️ 🌨️ ❄️ ☃️ 🌬️ 🌪️ 🌫️ 🌊 💧 🔥 🌈 🌸 🌺 🌻 🌼 🌷 🌹 🥀 🌿 🍀 🍁 🍂 🌾 🌵 🌲 🌳 🌴"],
|
||||
["🔣","symbols","❤️ 🧡 💛 💚 💙 💜 🖤 🤍 🤎 💔 ❣️ 💕 💞 💓 💗 💖 💘 💝 ✅ ❌ ⭕ 🚫 ⛔ ⚠️ 🔴 🟠 🟡 🟢 🔵 🟣 ⚫ ⚪ 🔺 🔻 🔶 🔷 ⬛ ⬜ ▶️ ⏸️ ⏹️ 🔁 🔂 🔀 ➕ ➖ ✖️ ➗ ♾️ ❓ ❗ 💯 🔔 🔕 ⏳ ⌛ 🎯 🏆 🥇 🎁 🎉"],
|
||||
];
|
||||
const RECENT_KEY = "ccsidepanel-emoji-recent", RECENT_MAX = 24;
|
||||
|
||||
const emojiPad = document.getElementById("emoji-pad");
|
||||
const emojiBtn = document.getElementById("emoji-btn");
|
||||
const emojiCats = document.getElementById("emoji-cats");
|
||||
const emojiGrid = document.getElementById("emoji-grid");
|
||||
const emojiLbl = document.getElementById("emoji-lbl");
|
||||
|
||||
function recents() {
|
||||
try { return JSON.parse(localStorage.getItem(RECENT_KEY)) || []; } catch (e) { return []; }
|
||||
}
|
||||
function remember(ch) {
|
||||
const list = [ch, ...recents().filter(x => x !== ch)].slice(0, RECENT_MAX);
|
||||
try { localStorage.setItem(RECENT_KEY, JSON.stringify(list)); } catch (e) {}
|
||||
}
|
||||
|
||||
function insertEmoji(ch) {
|
||||
// Insert at the caret, not at the end — appending mangles a half-typed line.
|
||||
const start = textEl.selectionStart ?? textEl.value.length;
|
||||
const end = textEl.selectionEnd ?? textEl.value.length;
|
||||
textEl.value = textEl.value.slice(0, start) + ch + textEl.value.slice(end);
|
||||
const caret = start + ch.length;
|
||||
textEl.focus();
|
||||
textEl.setSelectionRange(caret, caret);
|
||||
}
|
||||
|
||||
function paintEmoji(list, label) {
|
||||
emojiGrid.innerHTML = "";
|
||||
emojiLbl.textContent = list.length ? label : `${label} · nothing yet`;
|
||||
for (const ch of list) {
|
||||
const b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.textContent = ch;
|
||||
b.addEventListener("click", () => { insertEmoji(ch); remember(ch); }); // pad stays open
|
||||
emojiGrid.appendChild(b);
|
||||
}
|
||||
}
|
||||
|
||||
function showCat(i) {
|
||||
[...emojiCats.children].forEach((b, k) => b.classList.toggle("on", k === i));
|
||||
if (i === 0) return paintEmoji(recents(), "recent");
|
||||
const [, name, set] = EMOJI[i - 1];
|
||||
paintEmoji(set.split(" ").filter(Boolean), name);
|
||||
}
|
||||
|
||||
// tab 0 is recents, then one per category
|
||||
[["🕘", "recent"], ...EMOJI.map(c => [c[0], c[1]])].forEach(([icon, name], i) => {
|
||||
const b = document.createElement("button");
|
||||
b.type = "button";
|
||||
b.textContent = icon;
|
||||
b.title = name;
|
||||
b.addEventListener("click", () => showCat(i));
|
||||
emojiCats.appendChild(b);
|
||||
});
|
||||
|
||||
function toggleEmoji(show) {
|
||||
emojiPad.hidden = show === undefined ? !emojiPad.hidden : !show;
|
||||
if (!emojiPad.hidden) { showCat(recents().length ? 0 : 1); textEl.focus(); }
|
||||
}
|
||||
|
||||
emojiBtn.addEventListener("click", (e) => { e.stopPropagation(); toggleEmoji(); });
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!emojiPad.hidden && !emojiPad.contains(e.target)) toggleEmoji(false);
|
||||
});
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && !emojiPad.hidden) toggleEmoji(false);
|
||||
});
|
||||
|
||||
sendBtn.addEventListener("click", sendText);
|
||||
textEl.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendText(); }
|
||||
|
||||
Reference in New Issue
Block a user