Browse Source

fix emoji picker

master
Silberengel 1 month ago
parent
commit
e7ea2505d4
  1. 365
      src/lib/components/content/EmojiPicker.svelte
  2. 5
      src/lib/components/content/MarkdownRenderer.svelte
  3. 2
      src/lib/components/write/CreateEventForm.svelte
  4. 2
      src/lib/modules/comments/CommentForm.svelte
  5. 20
      src/lib/services/nostr/nip30-emoji.ts

365
src/lib/components/content/EmojiPicker.svelte

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
import emojiData from 'unicode-emoji-json/data-ordered-emoji.json';
import emojiNames from 'unicode-emoji-json/data-by-emoji.json';
import EmojiDrawer from './EmojiDrawer.svelte';
import { loadAllEmojiPacks, getAllCustomEmojis } from '../../services/nostr/nip30-emoji.js';
import { loadAllEmojiPacks, getAllCustomEmojis, invalidateEmojiSetCache, forceReloadAllEmojiPacks } from '../../services/nostr/nip30-emoji.js';
import { signAndPublish } from '../../services/nostr/auth-handler.js';
import { relayManager } from '../../services/nostr/relay-manager.js';
import { uploadFileToServer } from '../../services/nostr/file-upload.js';
@ -30,20 +30,6 @@ @@ -30,20 +30,6 @@
let shortcodeInput: HTMLInputElement | null = $state(null);
let showUploadForm = $state(false);
// Metadata form state (like GIF picker)
let showMetadataForm = $state(false);
let pendingUpload: { file: File; fileUrl: string; shortcode: string } | null = $state(null);
let metadataForm = $state({
title: '',
summary: '',
alt: '',
dim: '',
blurhash: '',
thumb: '',
image: '',
content: ''
});
// Check if user is logged in
let isLoggedIn = $derived(sessionManager.isLoggedIn());
@ -237,21 +223,12 @@ @@ -237,21 +223,12 @@
}
}
// Store pending upload for metadata form
pendingUpload = { file, fileUrl, shortcode };
showMetadataForm = true;
// Reset metadata form and prefill with shortcode and image URL
metadataForm = {
title: shortcode, // Prefill with shortcode
summary: '',
alt: `:${shortcode}: emoji`,
dim: '',
blurhash: '',
thumb: '',
image: fileUrl, // Prefill with the uploaded emoji URL
content: ''
};
// Automatically publish the emoji set after upload
await publishEmojiSet(file, fileUrl, shortcode);
// Reset form
if (fileInput) fileInput.value = '';
if (shortcodeInput) shortcodeInput.value = '';
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const fileName = files && files.length > 0 ? files[0].name : 'file';
@ -262,17 +239,13 @@ @@ -262,17 +239,13 @@
}
}
async function publishWithMetadata() {
if (!pendingUpload) return;
const { file, fileUrl, shortcode } = pendingUpload;
async function publishEmojiSet(file: File, fileUrl: string, shortcode: string) {
const session = sessionManager.getSession();
if (!session) {
uploadError = 'Please log in to publish emojis';
return;
}
uploading = true;
uploadError = null;
try {
@ -335,15 +308,13 @@ @@ -335,15 +308,13 @@
if (result.success.length > 0) {
console.log(`[EmojiPicker] Published emoji set event for ${file.name} (shortcode: :${finalShortcode}:) to ${result.success.length} relay(s)`);
showMetadataForm = false;
pendingUpload = null;
showUploadForm = false;
// Reset form
if (fileInput) fileInput.value = '';
if (shortcodeInput) shortcodeInput.value = '';
// Invalidate cache for this user's emoji set and force reload
invalidateEmojiSetCache(session.pubkey);
forceReloadAllEmojiPacks();
// Reload custom emojis
// Reload custom emojis to show the newly published emoji
await loadCustomEmojis();
} else {
const errorMsg = result.failed.length > 0
@ -355,16 +326,10 @@ @@ -355,16 +326,10 @@
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`[EmojiPicker] Error publishing ${file.name}:`, error);
uploadError = errorMessage;
} finally {
uploading = false;
throw error; // Re-throw so handleEmojiUpload can handle it
}
}
function cancelMetadataForm() {
showMetadataForm = false;
pendingUpload = null;
uploading = false;
}
function triggerEmojiUpload() {
if (showUploadForm) {
@ -485,115 +450,6 @@ @@ -485,115 +450,6 @@
{/snippet}
</EmojiDrawer>
<!-- Metadata Form Modal (like GIF picker) -->
{#if showMetadataForm && pendingUpload}
<div
class="metadata-modal-backdrop"
onclick={cancelMetadataForm}
onkeydown={(e) => e.key === 'Escape' && cancelMetadataForm()}
role="button"
tabindex="0"
aria-label="Close metadata form"
>
<div
class="metadata-modal"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.stopPropagation()}
role="dialog"
aria-modal="true"
aria-labelledby="emoji-metadata-modal-title"
tabindex="-1"
>
<div class="metadata-modal-header">
<h3 id="emoji-metadata-modal-title">Add Metadata for {pendingUpload.file.name}</h3>
<button class="metadata-modal-close" onclick={cancelMetadataForm}>×</button>
</div>
<div class="metadata-modal-content">
<div class="metadata-form-group">
<label for="emoji-metadata-title">Title/Shortcode (t tag)</label>
<input
id="emoji-metadata-title"
type="text"
bind:value={metadataForm.title}
placeholder="e.g., myemoji"
class="metadata-input"
/>
</div>
<div class="metadata-form-group">
<label for="emoji-metadata-summary">Summary</label>
<textarea
id="emoji-metadata-summary"
bind:value={metadataForm.summary}
placeholder="Brief description of the emoji"
class="metadata-textarea"
rows="2"
></textarea>
</div>
<div class="metadata-form-group">
<label for="emoji-metadata-alt">Alt Text</label>
<textarea
id="emoji-metadata-alt"
bind:value={metadataForm.alt}
placeholder="Accessibility description"
class="metadata-textarea"
rows="2"
></textarea>
</div>
<div class="metadata-form-group">
<label for="emoji-metadata-dim">Dimensions (dim tag, e.g., 128x128)</label>
<input
id="emoji-metadata-dim"
type="text"
bind:value={metadataForm.dim}
placeholder="widthxheight"
class="metadata-input"
/>
</div>
<div class="metadata-form-group">
<label for="emoji-metadata-blurhash">Blurhash</label>
<input
id="emoji-metadata-blurhash"
type="text"
bind:value={metadataForm.blurhash}
placeholder="Blurhash string"
class="metadata-input"
/>
</div>
<div class="metadata-form-group">
<label for="emoji-metadata-thumb">Thumbnail URL</label>
<input
id="emoji-metadata-thumb"
type="url"
bind:value={metadataForm.thumb}
placeholder="https://..."
class="metadata-input"
/>
</div>
<div class="metadata-form-group">
<label for="emoji-metadata-image">Image URL</label>
<input
id="emoji-metadata-image"
type="url"
bind:value={metadataForm.image}
placeholder="https://..."
class="metadata-input"
/>
</div>
<div class="metadata-form-actions">
<button class="metadata-button cancel" onclick={cancelMetadataForm} disabled={uploading}>
Cancel
</button>
<button class="metadata-button publish" onclick={publishWithMetadata} disabled={uploading}>
{uploading ? 'Publishing...' : 'Publish'}
</button>
</div>
{#if uploadError}
<div class="metadata-error">{uploadError}</div>
{/if}
</div>
</div>
</div>
{/if}
<style>
.emoji-picker-wrapper {
@ -790,199 +646,4 @@ @@ -790,199 +646,4 @@
:global(.dark) .emoji-upload-error {
color: var(--fog-dark-error, #ef4444);
}
/* Metadata Form Modal (like GIF picker) */
.metadata-modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.metadata-modal {
background: var(--fog-post, #ffffff);
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.5rem;
max-width: 600px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
}
:global(.dark) .metadata-modal {
background: var(--fog-dark-post, #1f2937);
border-color: var(--fog-dark-border, #374151);
}
.metadata-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--fog-border, #e5e7eb);
}
:global(.dark) .metadata-modal-header {
border-bottom-color: var(--fog-dark-border, #374151);
}
.metadata-modal-header h3 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--fog-text, #1f2937);
}
:global(.dark) .metadata-modal-header h3 {
color: var(--fog-dark-text, #f9fafb);
}
.metadata-modal-close {
background: transparent;
border: none;
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
color: var(--fog-text-light, #9ca3af);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
transition: all 0.2s;
}
.metadata-modal-close:hover {
background: var(--fog-highlight, #f3f4f6);
color: var(--fog-text, #1f2937);
}
:global(.dark) .metadata-modal-close {
color: var(--fog-dark-text-light, #6b7280);
}
:global(.dark) .metadata-modal-close:hover {
background: var(--fog-dark-highlight, #475569);
color: var(--fog-dark-text, #f9fafb);
}
.metadata-modal-content {
padding: 1.5rem;
}
.metadata-form-group {
margin-bottom: 1rem;
}
.metadata-form-group label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--fog-text, #1f2937);
}
:global(.dark) .metadata-form-group label {
color: var(--fog-dark-text, #f9fafb);
}
.metadata-input,
.metadata-textarea {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.375rem;
background: var(--fog-post, #ffffff);
color: var(--fog-text, #1f2937);
font-size: 0.875rem;
font-family: inherit;
}
:global(.dark) .metadata-input,
:global(.dark) .metadata-textarea {
background: var(--fog-dark-post, #334155);
border-color: var(--fog-dark-border, #475569);
color: var(--fog-dark-text, #f9fafb);
}
.metadata-textarea {
resize: vertical;
min-height: 60px;
}
.metadata-form-actions {
display: flex;
gap: 0.75rem;
justify-content: flex-end;
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid var(--fog-border, #e5e7eb);
}
:global(.dark) .metadata-form-actions {
border-top-color: var(--fog-dark-border, #374151);
}
.metadata-button {
padding: 0.5rem 1.5rem;
border: 1px solid var(--fog-border, #e5e7eb);
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.metadata-button.cancel {
background: var(--fog-highlight, #f3f4f6);
color: var(--fog-text, #1f2937);
}
:global(.dark) .metadata-button.cancel {
background: var(--fog-dark-highlight, #475569);
border-color: var(--fog-dark-border, #475569);
color: var(--fog-dark-text, #f9fafb);
}
.metadata-button.cancel:hover {
background: var(--fog-accent, #64748b);
color: white;
border-color: var(--fog-accent, #64748b);
}
.metadata-button.publish {
background: var(--fog-accent, #64748b);
color: white;
border-color: var(--fog-accent, #64748b);
}
.metadata-button.publish:hover:not(:disabled) {
opacity: 0.9;
}
.metadata-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.metadata-error {
margin-top: 1rem;
padding: 0.75rem;
background: var(--fog-error-bg, #fee2e2);
border: 1px solid var(--fog-error, #dc2626);
border-radius: 0.375rem;
color: var(--fog-error, #dc2626);
font-size: 0.875rem;
}
:global(.dark) .metadata-error {
background: var(--fog-dark-error-bg, #7f1d1d);
border-color: var(--fog-dark-error, #ef4444);
color: var(--fog-dark-error, #ef4444);
}
</style>

5
src/lib/components/content/MarkdownRenderer.svelte

@ -212,7 +212,7 @@ @@ -212,7 +212,7 @@
const escapedUrl = escapeHtml(url);
const escapedShortcode = escapeHtml(shortcode);
// Replace with img tag, preserving the shortcode as alt text
const imgTag = `<img src="${escapedUrl}" alt="${escapedShortcode}" class="emoji-inline" style="display: inline-block; width: 1.2em; height: 1.2em; vertical-align: middle;" />`;
const imgTag = `<img src="${escapedUrl}" alt="${escapedShortcode}" class="emoji-inline" style="display: inline-block; width: 1.6em; height: 1.6em; vertical-align: middle; object-fit: contain;" />`;
processed = processed.replaceAll(shortcode, imgTag);
}
@ -755,6 +755,9 @@ @@ -755,6 +755,9 @@
display: inline-block;
margin: 0;
vertical-align: middle;
width: 1.6em;
height: 1.6em;
object-fit: contain;
/* Emojis should be in full color, no grayscale filter */
}

2
src/lib/components/write/CreateEventForm.svelte

@ -1074,7 +1074,7 @@ @@ -1074,7 +1074,7 @@
} as NostrEvent;
})()}
<MediaAttachments event={previewEvent} />
<MarkdownRenderer content={previewContent} />
<MarkdownRenderer content={previewContent} event={previewEvent} />
{:else}
<p class="text-muted">No content to preview</p>
{/if}

2
src/lib/modules/comments/CommentForm.svelte

@ -613,7 +613,7 @@ @@ -613,7 +613,7 @@
} as NostrEvent;
})()}
<MediaAttachments event={previewEvent} />
<MarkdownRenderer content={previewContent} />
<MarkdownRenderer content={previewContent} event={previewEvent} />
{:else}
<p class="text-muted">No content to preview</p>
{/if}

20
src/lib/services/nostr/nip30-emoji.ts

@ -44,6 +44,26 @@ export function getAllCustomEmojis(): Array<{ shortcode: string; url: string }> @@ -44,6 +44,26 @@ export function getAllCustomEmojis(): Array<{ shortcode: string; url: string }>
let allEmojiPacksLoaded = false;
let loadingEmojiPacks = false;
/**
* Invalidate cache for a specific pubkey's emoji set
* This forces a fresh fetch on the next load
*/
export function invalidateEmojiSetCache(pubkey: string): void {
emojiSetCache.delete(pubkey);
// Also clear any shortcodes that might have come from this pubkey
// We'll rebuild the shortcode cache on next load
allEmojiPacksLoaded = false;
}
/**
* Force reload all emoji packs (clears cache and reloads)
*/
export function forceReloadAllEmojiPacks(): void {
emojiSetCache.clear();
shortcodeCache.clear();
allEmojiPacksLoaded = false;
}
/**
* Parse a kind 10030 emoji set event or kind 30030 emoji pack
*/

Loading…
Cancel
Save