|
|
|
|
@ -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'; |
|
|
|
|
@ -29,20 +29,6 @@
@@ -29,20 +29,6 @@
|
|
|
|
|
let fileInput: HTMLInputElement | null = $state(null); |
|
|
|
|
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; |
|
|
|
|
// Automatically publish the emoji set after upload |
|
|
|
|
await publishEmojiSet(file, fileUrl, shortcode); |
|
|
|
|
|
|
|
|
|
// 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: '' |
|
|
|
|
}; |
|
|
|
|
// 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> |
|
|
|
|
|