544a170ead2c | atagen | 2026-10-20 11:47 UTC | 1 | <script lang="ts"> |
445bf199a9cf | atagen | 2026-10-25 02:56 UTC | 2 | import EmojiText from './EmojiText.svelte'; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 3 | import { onMount, tick } from 'svelte'; |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 4 | import { mediaUrl, messageMediaUrl } from './api'; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 5 | import Emoji from './Emoji.svelte'; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 6 | import { loadEmoji, searchEmoji } from './emoji'; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 7 | import type { EmojiGroup } from './emoji'; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 8 | import type { ImagePack, PackImage, QuickEntry, QuickSeparator } from './types'; |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 9 | import { hasUsage } from './packs'; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 10 | import { isSeparator, normaliseQuick, sameQuick } from './quick-reactions-model'; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 11 | import { flipAfter, reorder } from './reorder'; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 12 | import { moveBy, moveToSlot } from './reorder-model'; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 13 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 14 | export let packs: ImagePack[]; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 15 | export let quick: QuickEntry[]; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 16 | export let onPick: (key: string, shortcode: string | null) => void; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 17 | export let onQuickChange: (list: QuickEntry[]) => void; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 18 | export let onClose: () => void; |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 19 | export let embedded = false; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 20 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 21 | let root: HTMLDivElement; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 22 | let input: HTMLInputElement; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 23 | let row: HTMLDivElement; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 24 | let query = ''; |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 25 | let customising = embedded; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 26 | let groups: EmojiGroup[] | null = null; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 27 | let loadFailed = false; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 28 | let draftSeparator: QuickSeparator | null = null; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 29 | const separatorIds = new WeakMap<QuickSeparator, number>(); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 30 | let separatorCount = 0; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 31 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 32 | onMount(() => { |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 33 | if (!embedded) input.focus(); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 34 | loadEmoji().then( |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 35 | (g) => (groups = g), |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 36 | () => (loadFailed = true) |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 37 | ); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 38 | }); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 39 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 40 | $: q = query.trim().toLowerCase().replace(/^:|:$/g, ''); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 41 | $: filteredPacks = packs |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 42 | .map((pack) => ({ pack, images: pack.images.filter((i) => imageMatches(i, q)) })) |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 43 | .filter((p) => p.images.length > 0); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 44 | $: filteredGroups = searchEmoji(groups ?? [], q); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 45 | $: working = draftSeparator ? [...quick, draftSeparator] : quick; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 46 | $: canSeparate = working.length > 0 && !isSeparator(working[working.length - 1]); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 47 | $: nothingFound = q !== '' && groups !== null && filteredPacks.length === 0 && filteredGroups.length === 0; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 48 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 49 | function imageMatches(image: PackImage, needle: string): boolean { |
132eeceeb362 | atagen | 2026-10-25 03:34 UTC | 50 | if (!hasUsage(image, 'Emoticon')) return false; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 51 | if (!needle) return true; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 52 | return image.shortcode.toLowerCase().includes(needle) || (image.body ?? '').toLowerCase().includes(needle); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 53 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 54 | |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 55 | function packName(pack: ImagePack): string { |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 56 | return pack.display_name ?? 'Emoji pack'; |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 57 | } |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 58 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 59 | function isQuick(key: string): boolean { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 60 | return quick.some((r) => !isSeparator(r) && r.key === key); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 61 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 62 | |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 63 | function commit(list: QuickEntry[]): void { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 64 | const next = normaliseQuick(list); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 65 | draftSeparator = null; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 66 | if (!sameQuick(next, quick)) onQuickChange(next); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 67 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 68 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 69 | function choose(key: string, shortcode: string | null): void { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 70 | if (!customising) { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 71 | onPick(key, shortcode); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 72 | return; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 73 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 74 | if (isQuick(key)) commit(working.filter((r) => isSeparator(r) || r.key !== key)); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 75 | else commit([...working, { key, shortcode }]); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 76 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 77 | |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 78 | function entryKey(entry: QuickEntry): string { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 79 | if (!isSeparator(entry)) return entry.key; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 80 | let id = separatorIds.get(entry); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 81 | if (id === undefined) separatorIds.set(entry, (id = separatorCount++)); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 82 | return `separator:${id}`; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 83 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 84 | |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 85 | function entryLabel(entry: QuickEntry): string { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 86 | if (isSeparator(entry)) return 'separator'; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 87 | return entry.shortcode ? `:${entry.shortcode}:` : entry.key; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 88 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 89 | |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 90 | function remove(index: number): void { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 91 | commit(working.filter((_, i) => i !== index)); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 92 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 93 | |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 94 | function moveItem(from: number, slot: number): void { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 95 | const next = moveToSlot(working, from, slot); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 96 | if (next !== working) commit(next); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 97 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 98 | |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 99 | async function onChipKeydown(e: KeyboardEvent, index: number, entry: QuickEntry): Promise<void> { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 100 | if (!(e.altKey || e.shiftKey) || (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')) return; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 101 | e.preventDefault(); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 102 | const next = moveBy(working, index, e.key === 'ArrowLeft' ? -1 : 1); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 103 | if (next === working) return; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 104 | const id = entryKey(entry); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 105 | void flipAfter(row, () => commit(next)); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 106 | await tick(); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 107 | const grabs = row.querySelectorAll<HTMLElement>('.grab'); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 108 | (row.querySelector<HTMLElement>(`[data-reorder="${CSS.escape(id)}"] .grab`) ?? grabs[Math.min(index, grabs.length - 1)])?.focus(); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 109 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 110 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 111 | function onKeydown(e: KeyboardEvent): void { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 112 | if (e.key !== 'Escape') return; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 113 | e.stopPropagation(); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 114 | onClose(); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 115 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 116 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 117 | function closeOutside(e: PointerEvent): void { |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 118 | if (!embedded && !root.contains(e.target as Node)) onClose(); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 119 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 120 | </script> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 121 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 122 | <svelte:window on:pointerdown={closeOutside} /> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 123 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 124 | <div |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 125 | class="picker" |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 126 | class:embedded |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 127 | role="dialog" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 128 | tabindex="-1" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 129 | aria-label={customising ? 'Customise quick reactions' : 'Pick a reaction'} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 130 | bind:this={root} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 131 | on:keydown={onKeydown} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 132 | > |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 133 | <input |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 134 | class="search" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 135 | type="search" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 136 | placeholder="Search emoji" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 137 | aria-label="Search emoji" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 138 | autocomplete="off" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 139 | bind:this={input} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 140 | bind:value={query} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 141 | /> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 142 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 143 | {#if customising} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 144 | <div class="quick" role="group" aria-label="Quick reactions"> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 145 | <div class="quick-head"> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 146 | <span>Quick reactions</span> |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 147 | <button type="button" class="add-sep" disabled={!canSeparate} on:click={() => (draftSeparator = { separator: true })}> |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 148 | Add separator |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 149 | </button> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 150 | </div> |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 151 | <div class="quick-row" bind:this={row} use:reorder={{ onMove: moveItem }}> |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 152 | {#each working as entry, i (entryKey(entry))} |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 153 | <span class="quick-item" class:separator={isSeparator(entry)} data-reorder={entryKey(entry)}> |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 154 | <button |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 155 | type="button" |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 156 | class="grab" |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 157 | data-reorder-handle |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 158 | aria-label={`Move ${entryLabel(entry)}, ${i + 1} of ${working.length}`} |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 159 | on:keydown={(e) => onChipKeydown(e, i, entry)} |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 160 | > |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 161 | {#if isSeparator(entry)} |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 162 | <span class="bar"></span> |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 163 | {:else if entry.key.startsWith('mxc://')} |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 164 | <img src={mediaUrl(entry.key, 64) ?? ''} alt="" draggable="false" /> |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 165 | {:else} |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 166 | <Emoji emoji={entry.key} size={18} /> |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 167 | {/if} |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 168 | </button> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 169 | <button |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 170 | type="button" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 171 | class="remove" |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 172 | aria-label={`Remove ${entryLabel(entry)} from quick reactions`} |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 173 | on:click={() => remove(i)} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 174 | > |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 175 | × |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 176 | </button> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 177 | </span> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 178 | {:else} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 179 | <span class="muted">None. Pick emoji below to add them.</span> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 180 | {/each} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 181 | </div> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 182 | </div> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 183 | {/if} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 184 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 185 | <div class="scroll" aria-busy={groups === null && !loadFailed}> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 186 | {#each filteredPacks as { pack, images } (pack.id)} |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 187 | <section class="group" aria-label={packName(pack)}> |
445bf199a9cf | atagen | 2026-10-25 02:56 UTC | 188 | <h3><EmojiText text={packName(pack)} /></h3> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 189 | <div class="grid"> |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 190 | {#each images as image (image.reaction_key + image.shortcode)} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 191 | <button |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 192 | type="button" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 193 | class="cell" |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 194 | class:chosen={customising && isQuick(image.reaction_key)} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 195 | title={`:${image.shortcode}:`} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 196 | aria-label={`:${image.shortcode}:`} |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 197 | aria-pressed={customising ? isQuick(image.reaction_key) : undefined} |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 198 | on:click={() => choose(image.reaction_key, image.shortcode)} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 199 | > |
ecb8598b38af | atagen | 2026-10-22 09:19 UTC | 200 | <img src={messageMediaUrl(image.media_id, 'Thumbnail')} alt="" loading="lazy" /> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 201 | </button> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 202 | {/each} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 203 | </div> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 204 | </section> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 205 | {/each} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 206 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 207 | {#if loadFailed} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 208 | <p class="status" role="alert">Could not load the emoji list.</p> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 209 | {:else if groups === null} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 210 | <p class="status" role="status">Loading emoji…</p> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 211 | {:else} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 212 | {#each filteredGroups as g (g.name)} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 213 | <section class="group" aria-label={g.name}> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 214 | <h3>{g.name}</h3> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 215 | <div class="grid"> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 216 | {#each g.emojis as e (e.emoji)} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 217 | <button |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 218 | type="button" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 219 | class="cell" |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 220 | class:chosen={customising && isQuick(e.emoji)} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 221 | title={e.label} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 222 | aria-label={e.label} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 223 | aria-pressed={customising ? isQuick(e.emoji) : undefined} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 224 | on:click={() => choose(e.emoji, e.shortcodes[0] ?? null)} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 225 | > |
d07bca5da4ae | atagen | 2026-10-26 03:48 UTC | 226 | <Emoji emoji={e.emoji} size={24} lazy /> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 227 | </button> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 228 | {/each} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 229 | </div> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 230 | </section> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 231 | {/each} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 232 | {/if} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 233 | {#if nothingFound} |
445bf199a9cf | atagen | 2026-10-25 02:56 UTC | 234 | <p class="status" role="status">No emoji match "<EmojiText text={query.trim()} />".</p> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 235 | {/if} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 236 | </div> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 237 | |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 238 | {#if !embedded} |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 239 | <div class="foot"> |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 240 | <button type="button" class="mode" aria-pressed={customising} on:click={() => (customising = !customising)}> |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 241 | {customising ? 'Done' : 'Customise quick reactions'} |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 242 | </button> |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 243 | </div> |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 244 | {/if} |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 245 | </div> |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 246 | |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 247 | <style> |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 248 | .picker.embedded { width: 100%; height: 340px; box-shadow: none; } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 249 | .picker { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 250 | outline: none; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 251 | display: flex; flex-direction: column; gap: 6px; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 252 | width: 320px; height: 380px; max-width: 100%; max-height: 100%; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 253 | padding: 8px; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 254 | background: var(--panel); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 255 | border: 1px solid var(--border); |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 256 | border-radius: var(--r-md); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 257 | box-shadow: 0 6px 24px rgba(0, 0, 0, 0.5); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 258 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 259 | .search { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 260 | background: var(--bg-soft); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 261 | border: 1px solid var(--border); |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 262 | border-radius: var(--r-md); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 263 | color: var(--text); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 264 | padding: 6px 10px; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 265 | font-size: 13px; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 266 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 267 | .search:focus-visible { outline: none; border-color: var(--accent); } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 268 | .muted { color: var(--muted); font-size: 12px; } |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 269 | .quick { border: 1px solid var(--border); border-radius: var(--r-md); padding: 6px; background: var(--bg-soft); } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 270 | .quick-head { display: flex; align-items: center; justify-content: space-between; font-size: 12px; font-weight: 650; margin-bottom: 4px; } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 271 | .add-sep { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 272 | background: transparent; border: 1px solid var(--border); border-radius: var(--r-sm); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 273 | color: var(--text); font-size: 11px; font-weight: 400; padding: 1px 6px; cursor: pointer; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 274 | transition: border-color var(--pop); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 275 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 276 | .add-sep:hover:not(:disabled) { border-color: var(--accent); } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 277 | .add-sep:disabled { opacity: 0.4; cursor: default; } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 278 | .quick-row { position: relative; display: flex; flex-wrap: wrap; gap: 4px; min-height: 28px; align-items: center; } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 279 | .quick-item { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 280 | display: inline-flex; align-items: center; gap: 2px; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 281 | padding: 1px 2px 1px 0; |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 282 | border: 1px solid var(--border); border-radius: var(--r-sm); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 283 | font-size: 16px; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 284 | user-select: none; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 285 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 286 | .quick-item.separator { border-style: dashed; } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 287 | .quick-item:has(.grab:focus-visible) { border-color: var(--accent); } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 288 | .quick-item:global([data-dragging]) { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 289 | z-index: 2; background: var(--bg-soft); border-color: var(--accent); |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 290 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 291 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 292 | .quick-item img { width: 18px; height: 18px; object-fit: contain; } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 293 | .grab { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 294 | display: inline-flex; align-items: center; height: 20px; padding: 0 2px 0 6px; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 295 | background: transparent; border: none; color: inherit; font: inherit; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 296 | cursor: grab; touch-action: none; -webkit-touch-callout: none; user-select: none; |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 297 | } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 298 | .grab:focus-visible { outline: none; } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 299 | .quick-item:global([data-dragging]) .grab { cursor: grabbing; } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 300 | .separator .grab { padding: 0 6px; } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 301 | .bar { width: 1px; height: 14px; background: var(--muted); } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 302 | @media (pointer: coarse) { |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 303 | .grab { height: 30px; } |
5059ed753f8b | atagen | 2026-10-27 07:20 UTC | 304 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 305 | .remove { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 306 | background: transparent; border: none; color: var(--muted); |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 307 | cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 5px; border-radius: var(--r-sm); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 308 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 309 | .remove:hover { color: var(--danger); } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 310 | .scroll { flex: 1; min-height: 0; overflow-y: auto; } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 311 | .group h3 { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 312 | margin: 6px 2px 2px; font-size: 11px; font-weight: 650; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 313 | text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 314 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 315 | .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(32px, 1fr)); gap: 2px; } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 316 | .cell { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 317 | display: grid; place-items: center; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 318 | height: 32px; padding: 0; |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 319 | background: transparent; border: 1px solid transparent; border-radius: var(--r-sm); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 320 | font-size: 20px; cursor: pointer; color: var(--text); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 321 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 322 | .cell img { width: 24px; height: 24px; object-fit: contain; } |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 323 | .cell:hover:not(:disabled), .cell:focus-visible { background: var(--hover); border-color: var(--border); outline: none; } |
df3c0f2b3da4 | atagen | 2026-10-22 13:07 UTC | 324 | .cell.chosen { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 325 | .cell:disabled { opacity: 0.3; cursor: default; } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 326 | .status { color: var(--muted); font-size: 13px; text-align: center; margin: 16px 0; } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 327 | .foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 328 | .mode { |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 329 | margin-left: auto; |
05a48e9e7942 | atagen | 2026-10-24 09:32 UTC | 330 | background: transparent; border: 1px solid var(--border); border-radius: var(--r-sm); |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 331 | color: var(--text); font-size: 12px; padding: 3px 8px; cursor: pointer; |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 332 | } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 333 | .mode:hover, .mode[aria-pressed='true'] { border-color: var(--accent); } |
544a170ead2c | atagen | 2026-10-20 11:47 UTC | 334 | </style> |