Browse Source

moved inline styles into the styles folder

master
silberengel 7 months ago
parent
commit
fcdf6dfe62
  1. 45
      src/lib/components/Notifications.svelte
  2. 214
      src/styles/notifications.css

45
src/lib/components/Notifications.svelte

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
<script lang="ts">
import "../../styles/notifications.css";
import { onMount } from "svelte";
import { Heading, P } from "flowbite-svelte";
import type { NDKEvent } from "$lib/utils/nostrUtils";
@ -147,9 +148,9 @@ @@ -147,9 +148,9 @@
}
// ALWAYS highlight the message in blue
element.classList.add('ring-2', 'ring-blue-500');
element.classList.add('message-highlight', 'ring-2', 'ring-blue-500');
setTimeout(() => {
element.classList.remove('ring-2', 'ring-blue-500');
element.classList.remove('message-highlight', 'ring-2', 'ring-blue-500');
}, 2000);
}
}
@ -712,7 +713,7 @@ @@ -712,7 +713,7 @@
{#each ["to-me", "from-me", "public-messages"] as mode}
{@const modeLabel = mode === "to-me" ? "To Me" : mode === "from-me" ? "From Me" : "Public Messages"}
<button
class="px-3 py-1 text-sm font-medium rounded-md transition-colors {notificationMode === mode ? 'bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-sm' : 'text-gray-700 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100'}"
class="mode-toggle-button px-3 py-1 text-sm font-medium rounded-md {notificationMode === mode ? 'active' : 'inactive'}"
onclick={() => notificationMode = mode as "to-me" | "from-me" | "public-messages"}
>
{modeLabel}
@ -724,7 +725,7 @@ @@ -724,7 +725,7 @@
{#if loading}
<div class="flex items-center justify-center py-8 min-h-96">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
<div class="notifications-loading-spinner rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
<span class="ml-2 text-gray-600 dark:text-gray-400">
Loading {notificationMode === "public-messages" ? "public messages" : "notifications"}...
</span>
@ -741,7 +742,7 @@ @@ -741,7 +742,7 @@
{:else}
<div class="max-h-[72rem] overflow-y-auto">
{#if filteredByUser}
<div class="mb-4 p-3 bg-blue-50 dark:bg-blue-900 border border-blue-200 dark:border-blue-700 rounded-lg">
<div class="filter-indicator mb-4 p-3 rounded-lg">
<div class="flex items-center justify-between">
<span class="text-sm text-blue-700 dark:text-blue-300">
Filtered by user: {@render userBadge(filteredByUser, authorProfiles.get(filteredByUser)?.displayName || authorProfiles.get(filteredByUser)?.name)}
@ -759,7 +760,7 @@ @@ -759,7 +760,7 @@
{#each filteredMessages.slice(0, 100) as message}
{@const authorProfile = authorProfiles.get(message.pubkey)}
{@const isFromUser = message.pubkey === $userStore.pubkey}
<div class="p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm hover:shadow-md transition-all" data-event-id="{message.id}">
<div class="message-container p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm" data-event-id="{message.id}">
<div class="flex items-start gap-3 {isFromUser ? 'flex-row-reverse' : ''}">
<!-- Author Profile Picture and Name -->
<div class="flex-shrink-0 relative">
@ -772,7 +773,7 @@ @@ -772,7 +773,7 @@
onerror={(e) => (e.target as HTMLImageElement).style.display = 'none'}
/>
{:else}
<div class="w-10 h-10 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center border border-gray-200 dark:border-gray-600">
<div class="profile-picture-fallback w-10 h-10 rounded-full flex items-center justify-center border border-gray-200 dark:border-gray-600">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">
{(authorProfile?.displayName || authorProfile?.name || message.pubkey.slice(0, 1)).toUpperCase()}
</span>
@ -788,7 +789,7 @@ @@ -788,7 +789,7 @@
<div class="mt-2 flex flex-col gap-1">
<!-- Reply button -->
<button
class="w-6 h-6 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white rounded-full flex items-center justify-center text-xs shadow-sm transition-colors"
class="reply-button w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs shadow-sm transition-colors"
onclick={(e) => {
e.preventDefault();
e.stopPropagation();
@ -801,7 +802,7 @@ @@ -801,7 +802,7 @@
</button>
<!-- Filter button -->
<button
class="w-6 h-6 bg-gray-400 hover:bg-gray-500 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-700 dark:text-gray-300 rounded-full flex items-center justify-center text-xs shadow-sm transition-colors {filteredByUser === message.pubkey ? 'ring-2 ring-gray-300 dark:ring-gray-400 bg-gray-500 dark:bg-gray-500' : ''}"
class="filter-button w-6 h-6 bg-gray-400 hover:bg-gray-500 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-700 dark:text-gray-300 rounded-full flex items-center justify-center text-xs shadow-sm {filteredByUser === message.pubkey ? 'filter-button-active ring-2 ring-gray-300 dark:ring-gray-400' : ''}"
onclick={() => filterByUser(message.pubkey)}
title="Filter by this user"
aria-label="Filter by this user"
@ -815,7 +816,7 @@ @@ -815,7 +816,7 @@
</div>
<!-- Message Content -->
<div class="flex-1 min-w-0 {isFromUser ? 'text-right' : ''}">
<div class="message-content flex-1 {isFromUser ? 'text-right' : ''}">
<div class="flex items-center gap-2 mb-2 {isFromUser ? 'justify-end' : ''}">
<span class="text-xs font-medium text-primary-600 dark:text-primary-400 bg-primary-100 dark:bg-primary-900 px-2 py-1 rounded">
{isFromUser ? 'Your Message' : 'Public Message'}
@ -877,7 +878,7 @@ @@ -877,7 +878,7 @@
<div class="max-h-[72rem] overflow-y-auto space-y-4">
{#each notifications.slice(0, 100) as notification}
{@const authorProfile = authorProfiles.get(notification.pubkey)}
<div class="p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm hover:shadow-md transition-all">
<div class="message-container p-4 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
<div class="flex items-start gap-3">
<!-- Author Profile Picture and Name -->
<div class="flex-shrink-0">
@ -890,7 +891,7 @@ @@ -890,7 +891,7 @@
onerror={(e) => (e.target as HTMLImageElement).style.display = 'none'}
/>
{:else}
<div class="w-10 h-10 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center border border-gray-200 dark:border-gray-600">
<div class="profile-picture-fallback w-10 h-10 rounded-full flex items-center justify-center border border-gray-200 dark:border-gray-600">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">
{(authorProfile?.displayName || authorProfile?.name || notification.pubkey.slice(0, 1)).toUpperCase()}
</span>
@ -950,7 +951,7 @@ @@ -950,7 +951,7 @@
<!-- New Message Modal -->
<Modal bind:open={showNewMessageModal} size="lg" class="w-full">
<div class="p-6">
<div class="modal-content p-6">
<div class="mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">
{replyToMessage ? 'Reply to Message' : 'New Public Message'}
@ -959,7 +960,7 @@ @@ -959,7 +960,7 @@
<!-- Quoted Content Display -->
{#if quotedContent}
<div class="mb-4 p-3 bg-gray-100 dark:bg-gray-800 border-l-4 border-gray-400 dark:border-gray-500 rounded-r-lg">
<div class="quoted-content mb-4 p-3 rounded-r-lg">
<div class="text-sm text-gray-600 dark:text-gray-400 mb-1">Replying to:</div>
<div class="text-sm text-gray-800 dark:text-gray-200">
{#await parseContent(quotedContent) then parsedContent}
@ -990,7 +991,7 @@ @@ -990,7 +991,7 @@
</div>
{#if selectedRecipients.length === 0}
<div class="p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded-lg">
<div class="recipient-selection p-3 rounded-lg">
<p class="text-sm text-yellow-700 dark:text-yellow-300">
No recipients selected. Click "Edit Recipients" to add recipients.
</p>
@ -1041,7 +1042,7 @@ @@ -1041,7 +1042,7 @@
id="new-message-content"
bind:value={newMessageContent}
placeholder="Type your message here..."
class="w-full p-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 resize-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
class="message-textarea w-full p-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 resize-none"
rows="6"
onkeydown={(e) => {
if (e.key === 'Enter' && !e.shiftKey && !isComposingMessage && selectedRecipients.length > 0 && newMessageContent.trim()) {
@ -1065,7 +1066,7 @@ @@ -1065,7 +1066,7 @@
color="primary"
onclick={sendNewMessage}
disabled={isComposingMessage || selectedRecipients.length === 0 || !newMessageContent.trim()}
class="flex items-center gap-2"
class="flex items-center gap-2 {isComposingMessage || selectedRecipients.length === 0 || !newMessageContent.trim() ? 'button-disabled' : ''}"
>
{#if isComposingMessage}
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
@ -1078,7 +1079,7 @@ @@ -1078,7 +1079,7 @@
<!-- Recipient Selection Modal -->
<Modal bind:open={showRecipientModal} size="lg" class="w-full">
<div class="p-6">
<div class="modal-content p-6">
<div class="mb-4">
<h3 class="text-lg font-semibold text-gray-900 dark:text-gray-100">Select Recipients</h3>
</div>
@ -1090,7 +1091,7 @@ @@ -1090,7 +1091,7 @@
placeholder="Search display name, name, NIP-05, or npub..."
bind:value={recipientSearch}
bind:this={recipientSearchInput}
class="w-full rounded-lg border border-gray-300 bg-gray-50 text-gray-900 text-sm focus:border-primary-500 focus:ring-primary-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-primary-500 dark:focus:ring-primary-500 p-2.5 {recipientLoading ? 'pr-10' : ''}"
class="search-input w-full rounded-lg border border-gray-300 bg-gray-50 text-gray-900 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 p-2.5 {recipientLoading ? 'pr-10' : ''}"
/>
{#if recipientLoading}
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
@ -1100,14 +1101,14 @@ @@ -1100,14 +1101,14 @@
</div>
{#if recipientResults.length > 0}
<div class="max-h-64 overflow-y-auto">
<div class="recipient-results">
<ul class="space-y-2">
{#each recipientResults as profile}
{@const isAlreadySelected = selectedRecipients.some(r => r.pubkey === profile.pubkey)}
<button
onclick={() => selectRecipient(profile)}
disabled={isAlreadySelected}
class="w-full flex items-center gap-3 p-3 text-left bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-colors {isAlreadySelected ? 'opacity-50 cursor-not-allowed' : ''}"
class="recipient-selection-button w-full flex items-center gap-3 p-3 text-left bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 {isAlreadySelected ? 'opacity-50 cursor-not-allowed' : ''}"
>
{#if profile.picture}
<img
@ -1150,7 +1151,7 @@ @@ -1150,7 +1151,7 @@
</div>
{#if recipientCommunityStatus[profile.pubkey || ""]}
<div
class="flex-shrink-0 w-4 h-4 bg-yellow-100 dark:bg-yellow-900 rounded-full flex items-center justify-center"
class="community-status-indicator flex-shrink-0 w-4 h-4 rounded-full flex items-center justify-center"
title="Has posted to the community"
>
<svg

214
src/styles/notifications.css

@ -0,0 +1,214 @@ @@ -0,0 +1,214 @@
/* Notifications Component Styles */
/* Loading spinner animation */
.notifications-loading-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* Message highlighting for jump-to functionality */
.message-highlight {
transition: all 0.2s ease-in-out;
}
.message-highlight.ring-2 {
box-shadow: 0 0 0 2px rgb(59 130 246);
}
/* Modal content styling */
.modal-content {
max-height: 80vh;
overflow-y: auto;
}
/* Recipient search results */
.recipient-results {
max-height: 16rem;
overflow-y: auto;
}
/* Message content area */
.message-content {
min-width: 0;
word-wrap: break-word;
}
/* Profile picture fallback */
.profile-picture-fallback {
background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);
}
.dark .profile-picture-fallback {
background: linear-gradient(135deg, #4b5563 0%, #374151 100%);
}
/* Filter button states */
.filter-button-active {
background-color: rgb(107 114 128);
color: rgb(243 244 246);
}
.dark .filter-button-active {
background-color: rgb(107 114 128);
color: rgb(243 244 246);
}
/* Reply button hover states */
.reply-button:hover {
background-color: rgb(37 99 235);
}
.dark .reply-button:hover {
background-color: rgb(29 78 216);
}
/* Community status indicator */
.community-status-indicator {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
}
.dark .community-status-indicator {
background: linear-gradient(135deg, #78350f 0%, #92400e 100%);
}
/* Quoted content styling */
.quoted-content {
border-left: 4px solid rgb(156 163 175);
background-color: rgb(249 250 251);
}
.dark .quoted-content {
border-left-color: rgb(107 114 128);
background-color: rgb(31 41 55);
}
/* Recipient selection styling */
.recipient-selection {
background-color: rgb(243 244 246);
border: 1px solid rgb(229 231 235);
}
.dark .recipient-selection {
background-color: rgb(55 65 81);
border-color: rgb(75 85 99);
}
/* Message container hover effects */
.message-container {
transition: all 0.2s ease-in-out;
}
.message-container:hover {
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
}
.dark .message-container:hover {
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.2);
}
/* Filter indicator styling */
.filter-indicator {
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
border: 1px solid rgb(147 197 253);
}
.dark .filter-indicator {
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
border-color: rgb(59 130 246);
}
/* Textarea focus states */
.message-textarea:focus {
outline: none;
border-color: transparent;
box-shadow: 0 0 0 2px rgb(59 130 246);
}
/* Button disabled states */
.button-disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Search input focus states */
.search-input:focus {
border-color: rgb(59 130 246);
box-shadow: 0 0 0 2px rgb(59 130 246 / 0.2);
}
.dark .search-input:focus {
border-color: rgb(59 130 246);
box-shadow: 0 0 0 2px rgb(59 130 246 / 0.3);
}
/* Transition utilities */
.transition-colors {
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, text-decoration-color 0.15s ease-in-out, fill 0.15s ease-in-out, stroke 0.15s ease-in-out;
}
.transition-all {
transition: all 0.15s ease-in-out;
}
/* Mode toggle button states */
.mode-toggle-button {
transition: all 0.15s ease-in-out;
}
.mode-toggle-button.active {
background-color: rgb(255 255 255);
color: rgb(17 24 39);
box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
}
.dark .mode-toggle-button.active {
background-color: rgb(31 41 55);
color: rgb(243 244 246);
}
.mode-toggle-button.inactive {
color: rgb(55 65 81);
}
.dark .mode-toggle-button.inactive {
color: rgb(156 163 175);
}
.mode-toggle-button.inactive:hover {
color: rgb(17 24 39);
}
.dark .mode-toggle-button.inactive:hover {
color: rgb(243 244 246);
}
/* Filter button transitions */
.filter-button {
transition: all 0.15s ease-in-out;
}
/* Recipient selection button transitions */
.recipient-selection-button {
transition: all 0.15s ease-in-out;
}
.recipient-selection-button:hover {
background-color: rgb(249 250 251);
}
.dark .recipient-selection-button:hover {
background-color: rgb(55 65 81);
}
.recipient-selection-button:focus {
outline: none;
box-shadow: 0 0 0 2px rgb(59 130 246);
}
Loading…
Cancel
Save