parent
10ebcbc917
commit
3483964353
@ -37,7 +37,7 @@
|
||||
<div style="margin-bottom: 10px;">
|
||||
<label for="serverPubkey">Server Pubkey or Search Term:</label><br>
|
||||
<div class="server-input-container">
|
||||
<input type="text" id="serverPubkey" placeholder="npub, username, or NIP-05 identifier" value="npub1thq3fzcw393c0tpy60sz0dvvjz4tjrgtrudxsa68sldkf78fznksgp34w8" class="server-input">
|
||||
<input type="text" id="serverPubkey" placeholder="npub, username, or NIP-05 identifier" class="server-input">
|
||||
<button id="searchServerBtn" class="server-search-button">Search</button>
|
||||
</div>
|
||||
<div id="serverSearchResult" class="server-search-result" style="display: none;">
|
||||
|
@ -28,25 +28,10 @@
|
||||
<div class="info-box">
|
||||
<p>View and manage your Nostr profile information. Connect with a NIP-07 extension or enter your keys manually.</p>
|
||||
</div>
|
||||
|
||||
<h2>Your Nostr Identity</h2>
|
||||
<div class="profile-section connection-status">
|
||||
<div id="connectionStatus" class="connection-status-indicator">
|
||||
Not connected to any extension
|
||||
</div>
|
||||
|
||||
<button id="connectButton" class="connect-button">Connect with Extension</button>
|
||||
|
||||
<div class="manual-entry">
|
||||
<details>
|
||||
<summary>Manual Key Entry</summary>
|
||||
<div class="manual-key-entry">
|
||||
<label for="manualPubkey">Public Key (hex or npub):</label>
|
||||
<input type="text" id="manualPubkey" placeholder="npub or hex pubkey">
|
||||
<button id="setManualPubkeyBtn">Set Key</button>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div id="connectionStatus" class="connection-status-indicator" hidden>
|
||||
Not connected to any extension
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="profileContainer" class="profile-container hidden">
|
||||
|
@ -46,6 +46,12 @@
|
||||
<input type="text" id="relayUrl" value="wss://relay.degmods.com" placeholder="wss://relay.example.com">
|
||||
<button id="connectRelayBtn" class="relay-connect-button">Connect</button>
|
||||
</div>
|
||||
<div class="filter-options">
|
||||
<label class="filter-checkbox">
|
||||
<input type="checkbox" id="showAllEvents" checked>
|
||||
Show all kind 21120 events (not just addressed to me)
|
||||
</label>
|
||||
</div>
|
||||
<div id="relayStatus" class="relay-status">Not connected</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -422,6 +422,29 @@ function handleCopyEvent(): void {
|
||||
initNostrLogin();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(): void {
|
||||
// Add event listener for "Enter" key on the serverPubkey input field
|
||||
const serverPubkeyInput = document.getElementById('serverPubkey') as HTMLInputElement;
|
||||
if (serverPubkeyInput) {
|
||||
serverPubkeyInput.addEventListener('keydown', async (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
await handleServerSearch();
|
||||
|
||||
// After search, check if we found a NIP-05 address that resolved to exactly one result
|
||||
const resultDiv = document.getElementById('serverSearchResult');
|
||||
if (resultDiv && resultDiv.querySelector('.search-results-list')) {
|
||||
const resultItems = resultDiv.querySelectorAll('.search-result-item');
|
||||
if (resultItems.length === 1) {
|
||||
const npub = resultItems[0].getAttribute('data-npub');
|
||||
if (npub) {
|
||||
serverPubkeyInput.value = npub;
|
||||
resultDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Set up the convert button click handler
|
||||
const convertButton = document.getElementById('convertButton');
|
||||
const searchButton = document.getElementById('searchServerBtn');
|
||||
|
@ -57,55 +57,19 @@ async function connectWithExtension(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Set manual pubkey
|
||||
function setManualPubkey(pubkeyInput: string): void {
|
||||
try {
|
||||
let hexPubkey: string;
|
||||
|
||||
// Handle npub format
|
||||
if (pubkeyInput.startsWith('npub')) {
|
||||
try {
|
||||
const decoded = nostrTools.nip19.decode(pubkeyInput);
|
||||
if (decoded.type === 'npub') {
|
||||
hexPubkey = decoded.data as string;
|
||||
} else {
|
||||
throw new Error('Invalid npub format');
|
||||
}
|
||||
} catch (error) {
|
||||
updateConnectionStatus(`Invalid npub format: ${error}`, false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Assume hex format
|
||||
if (!/^[0-9a-f]{64}$/i.test(pubkeyInput)) {
|
||||
updateConnectionStatus('Invalid hex pubkey format. Must be 64 hex characters.', false);
|
||||
return;
|
||||
}
|
||||
hexPubkey = pubkeyInput;
|
||||
}
|
||||
|
||||
currentPubkey = hexPubkey;
|
||||
updateConnectionStatus(`Using manually entered pubkey: ${hexPubkey.substring(0, 8)}...`, true);
|
||||
showProfile(hexPubkey);
|
||||
} catch (error) {
|
||||
console.error('Error setting manual pubkey:', error);
|
||||
updateConnectionStatus(`Error: ${error instanceof Error ? error.message : String(error)}`, false);
|
||||
}
|
||||
}
|
||||
// Manual pubkey functionality removed as users are automatically logged in
|
||||
|
||||
// Update connection status UI
|
||||
function updateConnectionStatus(message: string, isConnected: boolean): void {
|
||||
if (!connectionStatus) {return;}
|
||||
|
||||
// Just update the status message but keep it hidden
|
||||
connectionStatus.textContent = message;
|
||||
connectionStatus.className = `connection-status-indicator ${isConnected ? 'connected' : 'not-connected'}`;
|
||||
|
||||
// Always show the profile container regardless of connection state
|
||||
if (profileContainer) {
|
||||
if (isConnected) {
|
||||
profileContainer.classList.remove('hidden');
|
||||
} else {
|
||||
profileContainer.classList.add('hidden');
|
||||
}
|
||||
profileContainer.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,26 +337,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
statsResponsesReceived = document.getElementById('responsesReceived');
|
||||
statsRelaysConnected = document.getElementById('relaysConnected');
|
||||
|
||||
// Connect button
|
||||
const connectButton = document.getElementById('connectButton');
|
||||
if (connectButton) {
|
||||
connectButton.addEventListener('click', connectWithExtension);
|
||||
}
|
||||
|
||||
// Manual pubkey button
|
||||
const setManualPubkeyBtn = document.getElementById('setManualPubkeyBtn');
|
||||
const manualPubkeyInput = document.getElementById('manualPubkey') as HTMLInputElement;
|
||||
|
||||
if (setManualPubkeyBtn && manualPubkeyInput) {
|
||||
setManualPubkeyBtn.addEventListener('click', () => {
|
||||
const pubkeyValue = manualPubkeyInput.value.trim();
|
||||
if (pubkeyValue) {
|
||||
setManualPubkey(pubkeyValue);
|
||||
} else {
|
||||
alert('Please enter a pubkey');
|
||||
}
|
||||
});
|
||||
}
|
||||
// Auto-connect, no manual button needed
|
||||
|
||||
// Copy npub button
|
||||
const copyNpubBtn = document.getElementById('copyNpubBtn');
|
||||
@ -406,10 +351,23 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
refreshProfileBtn.addEventListener('click', refreshProfile);
|
||||
}
|
||||
|
||||
// Try to connect automatically if extension is available
|
||||
// Try to connect automatically
|
||||
if (window.nostr) {
|
||||
// If extension is available, try to connect with it
|
||||
connectWithExtension().catch(error => {
|
||||
console.error('Error auto-connecting:', error);
|
||||
console.error('Error auto-connecting with extension:', error);
|
||||
// Show profile container even if connection fails
|
||||
if (profileContainer) {
|
||||
profileContainer.classList.remove('hidden');
|
||||
updateProfileWithDefaults();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Even without an extension, show the profile container with default information
|
||||
console.log('No Nostr extension available, showing default profile');
|
||||
if (profileContainer) {
|
||||
profileContainer.classList.remove('hidden');
|
||||
updateProfileWithDefaults();
|
||||
}
|
||||
}
|
||||
});
|
@ -186,9 +186,7 @@ function updateRelayStatus(message: string, className: string): void {
|
||||
}
|
||||
|
||||
// Subscribe to events
|
||||
async function subscribeToEvents(options: {
|
||||
pubkeyFilter?: string;
|
||||
}): Promise<void> {
|
||||
async function subscribeToEvents(): Promise<void> {
|
||||
if (!relayPool || !activeRelayUrl) {
|
||||
console.error('Cannot subscribe: Relay pool or URL not set');
|
||||
return;
|
||||
@ -206,13 +204,9 @@ async function subscribeToEvents(options: {
|
||||
|
||||
console.log('Creating subscription for kind 21120 events');
|
||||
|
||||
// For now, we're going to fetch ALL kind 21120 events from the relay
|
||||
// to ensure we're getting data
|
||||
// Get the logged-in user's pubkey if available
|
||||
const loggedInPubkey = getLoggedInPubkey();
|
||||
|
||||
console.log('Creating subscription for kind 21120 events addressed to the user');
|
||||
|
||||
// Define the filter type properly
|
||||
interface NostrFilter {
|
||||
kinds: number[];
|
||||
@ -225,8 +219,12 @@ async function subscribeToEvents(options: {
|
||||
kinds: [21120], // HTTP Messages event kind
|
||||
};
|
||||
|
||||
// If the user is logged in, filter for events addressed to them
|
||||
if (loggedInPubkey) {
|
||||
// Get the status of the "Show all events" checkbox
|
||||
const showAllEventsCheckbox = document.getElementById('showAllEvents') as HTMLInputElement;
|
||||
const showAllEvents = showAllEventsCheckbox ? showAllEventsCheckbox.checked : false;
|
||||
|
||||
// If "Show all events" is not checked and the user is logged in, filter for events addressed to them
|
||||
if (!showAllEvents && loggedInPubkey) {
|
||||
let pubkeyHex = loggedInPubkey;
|
||||
|
||||
// Convert npub to hex if needed
|
||||
@ -245,34 +243,20 @@ async function subscribeToEvents(options: {
|
||||
filter['#p'] = [pubkeyHex];
|
||||
console.log(`Filtering for events addressed to user: ${pubkeyHex}`);
|
||||
} else {
|
||||
console.log('No user logged in, showing all kind 21120 events');
|
||||
// If "Show all events" is checked or no user is logged in
|
||||
console.log('Showing all kind 21120 events');
|
||||
|
||||
// Explicitly remove any p-tag filter that might have been set previously
|
||||
if (filter['#p']) {
|
||||
delete filter['#p'];
|
||||
}
|
||||
}
|
||||
|
||||
// Log the filter we're using
|
||||
console.log('Using filter:', JSON.stringify(filter));
|
||||
|
||||
// If a specific pubkey filter is provided in options, use it for p-tag filtering
|
||||
// This replaces the logged-in user's pubkey filter with the specified one
|
||||
if (options.pubkeyFilter) {
|
||||
let pubkey = options.pubkeyFilter;
|
||||
|
||||
// Convert npub to hex if needed
|
||||
if (pubkey.startsWith('npub')) {
|
||||
try {
|
||||
const hexPubkey = convertNpubToHex(pubkey);
|
||||
if (hexPubkey) {
|
||||
pubkey = hexPubkey;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to convert npub to hex:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the p-tag filter to the specified pubkey
|
||||
// This replaces any existing p-tag filter set by the logged-in user
|
||||
filter['#p'] = [pubkey];
|
||||
console.log(`Overriding p-tag filter to: ${pubkey}`);
|
||||
}
|
||||
// We're skipping the pubkeyFilter override on the server tab
|
||||
// since we always want to use the logged-in user's pubkey based on checkbox state
|
||||
|
||||
// Skip using nostr-tools SimplePool for subscription to avoid issues
|
||||
// Instead, create a direct WebSocket connection
|
||||
@ -514,24 +498,6 @@ function showEventDetails(eventId: string): void {
|
||||
|
||||
// Ensure event has an ID (should already be verified)
|
||||
const eventIdForDisplay = event.id ? event.id.substring(0, 8) : 'unknown';
|
||||
const fullEventId = event.id || 'unknown';
|
||||
|
||||
// Generate tags HTML with better formatting
|
||||
let tagsHtml = '';
|
||||
event.tags.forEach((tag: string[]) => {
|
||||
if (tag.length >= 2) {
|
||||
// For p and e tags, add additional explanation
|
||||
if (tag[0] === 'p') {
|
||||
tagsHtml += `<li><strong>p:</strong> ${tag[1]} (p-tag: recipient/target)</li>`;
|
||||
} else if (tag[0] === 'e') {
|
||||
tagsHtml += `<li><strong>e:</strong> ${tag[1]} (e-tag: reference to another event)</li>`;
|
||||
} else {
|
||||
tagsHtml += `<li><strong>${tag[0]}:</strong> ${tag[1]}</li>`;
|
||||
}
|
||||
} else if (tag.length === 1) {
|
||||
tagsHtml += `<li><strong>${tag[0]}</strong></li>`;
|
||||
}
|
||||
});
|
||||
|
||||
// Determine if this is a request or response
|
||||
const isRequest = event.tags.some(tag => tag[0] === 'p');
|
||||
@ -539,57 +505,60 @@ function showEventDetails(eventId: string): void {
|
||||
const eventTypeLabel = isRequest ? 'HTTP Request' : (isResponse ? 'HTTP Response' : 'Unknown Type');
|
||||
|
||||
// Get the decrypted or original content
|
||||
// Since we now encrypt just the HTTP request directly, this is just the HTTP content
|
||||
const httpContent = receivedEvent.decrypted ?
|
||||
(receivedEvent.decryptedContent || event.content) :
|
||||
event.content;
|
||||
|
||||
// Display the event details
|
||||
// Create raw JSON representation of the event
|
||||
const rawJson = JSON.stringify(event, null, 2);
|
||||
|
||||
// Display the event details with a tabbed interface
|
||||
eventDetails.innerHTML = `
|
||||
<div class="event-detail-header">
|
||||
<h3>${eventTypeLabel} (ID: ${eventIdForDisplay}...)</h3>
|
||||
<div class="event-timestamp">${new Date(event.created_at * 1000).toLocaleString()}</div>
|
||||
</div>
|
||||
|
||||
<div class="event-detail-metadata">
|
||||
<div class="event-detail-item">
|
||||
<strong>ID:</strong> <span class="metadata-value">${fullEventId}</span>
|
||||
</div>
|
||||
<div class="event-detail-item">
|
||||
<strong>From:</strong> <span class="metadata-value">${event.pubkey}</span>
|
||||
</div>
|
||||
<div class="event-detail-item">
|
||||
<strong>Created:</strong> <span class="metadata-value">${new Date(event.created_at * 1000).toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="event-detail-item">
|
||||
<strong>Tags:</strong>
|
||||
<ul class="event-tags">${tagsHtml}</ul>
|
||||
</div>
|
||||
<div class="event-detail-tabs">
|
||||
<button class="tab-btn" data-tab="raw-json">Raw JSON</button>
|
||||
<button class="tab-btn active" data-tab="http-content">HTTP Content</button>
|
||||
</div>
|
||||
|
||||
<div class="event-detail-content">
|
||||
<div class="content-row">
|
||||
<div class="event-metadata-column">
|
||||
<div class="content-header">
|
||||
<strong>Event Metadata</strong>
|
||||
</div>
|
||||
<div class="metadata-content">
|
||||
<div>ID: ${fullEventId}</div>
|
||||
<div>Pubkey: ${event.pubkey}</div>
|
||||
<div>Created: ${new Date(event.created_at * 1000).toLocaleString()}</div>
|
||||
${receivedEvent.decrypted ? '<div class="decrypted-badge">Decrypted</div>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="http-content-column">
|
||||
<div class="content-header">
|
||||
<strong>HTTP ${eventTypeLabel}</strong>
|
||||
</div>
|
||||
<pre class="http-content">${httpContent}</pre>
|
||||
</div>
|
||||
<div class="tab-content" id="raw-json">
|
||||
<pre class="json-content">${rawJson}</pre>
|
||||
</div>
|
||||
|
||||
<div class="tab-content active" id="http-content">
|
||||
<pre class="http-content">${httpContent}</pre>
|
||||
${!receivedEvent.decrypted ? '<div class="decryption-status">Attempting decryption...</div>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add event listeners for tab buttons
|
||||
const tabButtons = eventDetails.querySelectorAll('.tab-btn');
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
// Get the target tab
|
||||
const targetTabId = (button as HTMLElement).dataset.tab;
|
||||
if (!targetTabId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove active class from all buttons and content sections
|
||||
tabButtons.forEach(btn => btn.classList.remove('active'));
|
||||
// Use non-null assertion since we already checked eventDetails isn't null at the beginning
|
||||
eventDetails!.querySelectorAll('.tab-content').forEach(section => section.classList.remove('active'));
|
||||
|
||||
// Add active class to clicked button and corresponding content section
|
||||
button.classList.add('active');
|
||||
const targetSection = document.getElementById(targetTabId);
|
||||
if (targetSection) {
|
||||
targetSection.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-decrypt event using NIP-44
|
||||
@ -981,6 +950,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
eventDetails = document.getElementById('eventDetails');
|
||||
|
||||
const connectRelayBtn = document.getElementById('connectRelayBtn');
|
||||
const showAllEventsCheckbox = document.getElementById('showAllEvents') as HTMLInputElement;
|
||||
|
||||
// Tab functionality
|
||||
setupTabNavigation();
|
||||
@ -991,6 +961,34 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Raw text input setup
|
||||
setupRawTextInput();
|
||||
|
||||
// Setup event listener for the "Show all events" checkbox
|
||||
if (showAllEventsCheckbox) {
|
||||
showAllEventsCheckbox.addEventListener('change', async () => {
|
||||
// Only resubscribe if we're already connected to a relay
|
||||
if (relayPool && activeRelayUrl) {
|
||||
console.log(`Checkbox changed to: ${showAllEventsCheckbox.checked ? 'show all events' : 'only show events addressed to me'}`);
|
||||
|
||||
try {
|
||||
// No need to get the pubkey since we're not filtering based on it anymore
|
||||
|
||||
// Update status to indicate resubscription is in progress
|
||||
updateRelayStatus('Updating subscription...', 'connecting');
|
||||
|
||||
// Resubscribe with the current checkbox state
|
||||
await subscribeToEvents();
|
||||
|
||||
console.log(`Subscription updated with new filter settings`);
|
||||
updateRelayStatus('Subscription updated ✓', 'connected');
|
||||
} catch (error) {
|
||||
console.error("Failed to update subscription:", error);
|
||||
updateRelayStatus(`Subscription update failed: ${error instanceof Error ? error.message : String(error)}`, 'error');
|
||||
}
|
||||
} else {
|
||||
console.log("Checkbox changed but no active relay connection");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Connect to relay and automatically start subscription for logged-in user
|
||||
if (connectRelayBtn) {
|
||||
connectRelayBtn.addEventListener('click', async () => {
|
||||
@ -1013,7 +1011,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (success) {
|
||||
// Get the logged-in user's pubkey from localStorage as the default
|
||||
const userPubkey = getLoggedInPubkey();
|
||||
const pubkeyFilter = userPubkey || '';
|
||||
|
||||
// Log the pubkey we're using for subscription
|
||||
if (userPubkey) {
|
||||
@ -1027,9 +1024,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Update status to indicate subscription is in progress
|
||||
updateRelayStatus('Subscribing...', 'connecting');
|
||||
|
||||
await subscribeToEvents({
|
||||
pubkeyFilter
|
||||
});
|
||||
await subscribeToEvents();
|
||||
|
||||
// If no error was thrown, the subscription was successful
|
||||
console.log(`Subscription initiated to ${activeRelayUrl}`);
|
||||
|
@ -1,601 +0,0 @@
|
||||
/* Styles for the HTTP to Nostr converter */
|
||||
|
||||
/* CSS Variables for themes */
|
||||
:root {
|
||||
/* Light theme (default) */
|
||||
--bg-primary: #f8f9fa;
|
||||
--bg-secondary: #ffffff;
|
||||
--bg-tertiary: #f1f8ff;
|
||||
--bg-info: #e2f0fd;
|
||||
--text-primary: #212529;
|
||||
--text-secondary: #495057;
|
||||
--text-tertiary: #6c757d;
|
||||
--border-color: #dee2e6;
|
||||
--accent-color: #0d6efd;
|
||||
--button-primary: #0d6efd;
|
||||
--button-hover: #0b5ed7;
|
||||
--button-success: #28a745;
|
||||
--button-success-hover: #218838;
|
||||
--button-login: #6c3483;
|
||||
--button-login-hover: #5b2c6f;
|
||||
--info-border: #0d6efd;
|
||||
--code-bg: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Dark theme */
|
||||
[data-theme="dark"] {
|
||||
--bg-primary: #121212;
|
||||
--bg-secondary: #1e1e1e;
|
||||
--bg-tertiary: #252836;
|
||||
--bg-info: #1a2634;
|
||||
--text-primary: #e0e0e0;
|
||||
--text-secondary: #b0b0b0;
|
||||
--text-tertiary: #909090;
|
||||
--border-color: #333333;
|
||||
--accent-color: #3f87ff;
|
||||
--button-primary: #3f87ff;
|
||||
--button-hover: #2970e3;
|
||||
--button-success: #2a9745;
|
||||
--button-success-hover: #218838;
|
||||
--button-login: #7c44a3;
|
||||
--button-login-hover: #6c378f;
|
||||
--info-border: #3f87ff;
|
||||
--code-bg: #252525;
|
||||
}
|
||||
|
||||
/* General layout */
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Headings */
|
||||
h1 {
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--text-secondary);
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Top Navigation Bar */
|
||||
.top-nav {
|
||||
background-color: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
height: 50px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 70px; /* Added padding to account for fixed navbar */
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 20px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* Theme toggle button in the nav bar - aligned with icons */
|
||||
.theme-toggle-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
color: var(--text-primary);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: all 0.3s ease;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Ensure icons are properly sized */
|
||||
#themeIcon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* Navigation Links - Tab Style */
|
||||
.nav-link {
|
||||
text-decoration: none;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 25px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.nav-link.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Icon navigation links */
|
||||
.nav-icon {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
/* Legacy theme toggle styles for backward compatibility */
|
||||
.theme-toggle-container {
|
||||
display: none; /* Hide the old toggle container */
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: none; /* Hide the old toggle */
|
||||
}
|
||||
|
||||
.theme-toggle-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Info box */
|
||||
.info-box {
|
||||
background-color: var(--bg-info);
|
||||
border-left: 4px solid var(--info-border);
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
/* Form elements */
|
||||
input[type="text"], textarea {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 150px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* Login container */
|
||||
.login-container {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-status {
|
||||
margin-top: 10px;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
#nostr-login-container {
|
||||
margin: 10px auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
button {
|
||||
background-color: var(--button-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--button-hover);
|
||||
}
|
||||
|
||||
/* Server input section */
|
||||
.server-input-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.server-input {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.server-search-button {
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
.server-search-result {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* Output section */
|
||||
#output {
|
||||
margin-top: 30px;
|
||||
padding: 15px;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--code-bg);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* Publish container */
|
||||
.publish-container {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.publish-input-container {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.publish-input {
|
||||
flex-grow: 1;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.publish-button {
|
||||
background-color: var(--button-success);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.publish-button:hover {
|
||||
background-color: var(--button-success-hover);
|
||||
}
|
||||
|
||||
.publish-result {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* QR code container */
|
||||
.qr-container {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#qrCode {
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.qr-info {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--text-tertiary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-frame-container {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
background-color: white; /* QR codes need white background */
|
||||
}
|
||||
|
||||
.qr-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.qr-controls {
|
||||
margin: 15px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-controls button {
|
||||
margin: 0 5px;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
/* Search results styling */
|
||||
.search-results-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.result-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result-npub {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.result-nip05 {
|
||||
font-size: 12px;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.use-npub-btn {
|
||||
align-self: flex-end;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* Error and success messages */
|
||||
.error-message {
|
||||
color: #cc0000;
|
||||
padding: 10px;
|
||||
background-color: rgba(204, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
color: #008800;
|
||||
padding: 10px;
|
||||
background-color: rgba(0, 136, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding-top: 60px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
right: 5px;
|
||||
height: 45px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tab Interface Styles */
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
padding: 10px 20px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
margin-right: 10px;
|
||||
margin-bottom: -2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: var(--accent-color);
|
||||
border-bottom: 2px solid var(--accent-color);
|
||||
}
|
||||
|
||||
/* Tab content sections */
|
||||
#relay-connection-section,
|
||||
#qr-code-scanner-section,
|
||||
#raw-event-input-section {
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#relay-connection-section.active,
|
||||
#qr-code-scanner-section.active,
|
||||
#raw-event-input-section.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* QR Scanner Styles */
|
||||
.qr-scanner-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.qr-viewport {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
height: 300px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 3px solid var(--accent-color);
|
||||
border-radius: 8px;
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.qr-viewport video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.qr-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: 8px 15px;
|
||||
background-color: var(--button-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background-color: var(--button-hover);
|
||||
}
|
||||
|
||||
.action-button:disabled {
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-tertiary);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
margin-top: 10px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Raw Input Styles */
|
||||
.raw-input-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.raw-input-container textarea {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.raw-input-container button {
|
||||
align-self: flex-start;
|
||||
}
|
@ -525,11 +525,27 @@ footer {
|
||||
|
||||
.filter-options {
|
||||
margin: 15px 0;
|
||||
padding: 8px 10px;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filter-options label {
|
||||
margin-right: 15px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-checkbox input[type="checkbox"] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.key-input {
|
||||
@ -1001,11 +1017,73 @@ footer {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.event-detail-content {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Event detail tabs */
|
||||
.event-detail-tabs {
|
||||
display: flex;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.event-detail-tabs .tab-btn {
|
||||
padding: 8px 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
margin-right: 10px;
|
||||
margin-bottom: -2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.event-detail-tabs .tab-btn:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.event-detail-tabs .tab-btn.active {
|
||||
color: var(--accent-color);
|
||||
border-bottom: 2px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.event-detail-content .tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.event-detail-content .tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.json-content {
|
||||
margin-top: 10px;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--bg-tertiary);
|
||||
min-height: 200px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
border-left: 4px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.decryption-status {
|
||||
margin-top: 10px;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
background-color: #fff3cd;
|
||||
color: #856404;
|
||||
border: 1px solid #ffeeba;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.content-header {
|
||||
margin-bottom: 10px;
|
||||
color: var(--accent-color);
|
||||
|
Loading…
x
Reference in New Issue
Block a user