Fixed issue with bunker. Made the modal more beautiful.

This commit is contained in:
Your Name
2025-09-19 12:24:13 -04:00
parent c747f1f315
commit ca75df8bb4
2 changed files with 342 additions and 136 deletions

View File

@@ -8,7 +8,7 @@
* Two-file architecture:
* 1. Load nostr.bundle.js (official nostr-tools bundle)
* 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes)
* Generated on: 2025-09-16T22:12:00.192Z
* Generated on: 2025-09-19T16:21:34.133Z
*/
// Verify dependencies are loaded
@@ -44,7 +44,7 @@ const THEME_CSS = {
--nl-primary-color: #000000;
--nl-secondary-color: #ffffff;
--nl-accent-color: #ff0000;
--nl-muted-color: #666666;
--nl-muted-color: #CCCCCC;
--nl-font-family: "Courier New", Courier, monospace;
--nl-border-radius: 15px;
--nl-border-width: 3px;
@@ -401,7 +401,7 @@ class Modal {
closeButton.style.cssText = `
background: var(--nl-secondary-color);
border: var(--nl-border-width) solid var(--nl-primary-color);
border-radius: var(--nl-border-radius);
border-radius: 4px;
font-size: 28px;
color: var(--nl-primary-color);
cursor: pointer;
@@ -585,21 +585,13 @@ class Modal {
};
const iconDiv = document.createElement('div');
// Replace emoji icons with text-based ones
const iconMap = {
'🔌': '[EXT]',
'🔑': '[KEY]',
'🌱': '[SEED]',
'🌐': '[NET]',
'👁️': '[VIEW]',
'📱': '[SMS]'
};
iconDiv.textContent = iconMap[option.icon] || option.icon;
// Remove the icon entirely - no emojis or text-based icons
iconDiv.textContent = '';
iconDiv.style.cssText = `
font-size: 16px;
font-weight: bold;
margin-right: 16px;
width: 50px;
width: 0px;
text-align: center;
color: var(--nl-primary-color);
font-family: var(--nl-font-family, 'Courier New', monospace);
@@ -1494,10 +1486,6 @@ class Modal {
_showConnectScreen() {
this.modalBody.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Connect to NIP-46 Remote Signer';
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const description = document.createElement('p');
description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.';
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
@@ -1522,12 +1510,67 @@ class Modal {
box-sizing: border-box;
`;
// Users will enter the complete bunker connection string with relay info
// Add real-time bunker key validation
const formatHint = document.createElement('div');
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
const connectButton = document.createElement('button');
connectButton.textContent = 'Connect to Bunker';
connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value);
connectButton.style.cssText = this._getButtonStyle();
connectButton.disabled = true;
connectButton.onclick = () => {
if (!connectButton.disabled) {
this._handleNip46Connect(pubkeyInput.value);
}
};
// Set initial disabled state
connectButton.style.cssText = `
display: block;
width: 100%;
padding: 12px;
border: var(--nl-border-width) solid var(--nl-muted-color);
border-radius: var(--nl-border-radius);
font-size: 16px;
font-weight: 500;
cursor: not-allowed;
transition: all 0.2s;
font-family: var(--nl-font-family, 'Courier New', monospace);
background: var(--nl-secondary-color);
color: var(--nl-muted-color);
margin-bottom: 12px;
`;
pubkeyInput.oninput = () => {
const value = pubkeyInput.value.trim();
if (!value) {
formatHint.textContent = '';
// Disable button
connectButton.disabled = true;
connectButton.style.borderColor = 'var(--nl-muted-color)';
connectButton.style.color = 'var(--nl-muted-color)';
connectButton.style.cursor = 'not-allowed';
return;
}
const isValid = this._validateBunkerKey(value);
if (isValid) {
formatHint.textContent = '✅ Valid bunker connection format detected';
formatHint.style.color = '#059669';
// Enable button
connectButton.disabled = false;
connectButton.style.borderColor = 'var(--nl-primary-color)';
connectButton.style.color = 'var(--nl-primary-color)';
connectButton.style.cursor = 'pointer';
} else {
formatHint.textContent = '❌ Invalid format - must be bunker://, npub, or 64-char hex';
formatHint.style.color = '#dc2626';
// Disable button
connectButton.disabled = true;
connectButton.style.borderColor = 'var(--nl-muted-color)';
connectButton.style.color = 'var(--nl-muted-color)';
connectButton.style.cursor = 'not-allowed';
}
};
const backButton = document.createElement('button');
backButton.textContent = 'Back';
@@ -1536,14 +1579,49 @@ class Modal {
formGroup.appendChild(label);
formGroup.appendChild(pubkeyInput);
formGroup.appendChild(formatHint);
this.modalBody.appendChild(title);
this.modalBody.appendChild(description);
this.modalBody.appendChild(formGroup);
this.modalBody.appendChild(connectButton);
this.modalBody.appendChild(backButton);
}
_validateBunkerKey(bunkerKey) {
try {
const trimmed = bunkerKey.trim();
// Check for bunker:// format
if (trimmed.startsWith('bunker://')) {
// Should have format: bunker://pubkey or bunker://pubkey?param=value
const match = trimmed.match(/^bunker:\/\/([0-9a-fA-F]{64})(\?.*)?$/);
return !!match;
}
// Check for npub format
if (trimmed.startsWith('npub1') && trimmed.length === 63) {
try {
if (window.NostrTools?.nip19) {
const decoded = window.NostrTools.nip19.decode(trimmed);
return decoded.type === 'npub';
}
} catch {
return false;
}
}
// Check for hex format (64 characters, valid hex)
if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) {
return true;
}
return false;
} catch (error) {
console.log('Bunker key validation failed:', error.message);
return false;
}
}
_handleNip46Connect(bunkerPubkey) {
if (!bunkerPubkey || !bunkerPubkey.length) {
this._showError('Bunker pubkey is required');
@@ -1613,9 +1691,9 @@ class Modal {
const localSecretKey = window.NostrTools.generateSecretKey();
console.log('Generated local client keypair for NIP-46 session');
// Use nostr-tools BunkerSigner constructor
// Use nostr-tools BunkerSigner factory method (not constructor - it's private)
console.log('Creating nip46 BunkerSigner...');
const signer = new window.NostrTools.nip46.BunkerSigner(localSecretKey, bunkerPointer, {
const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, {
onauth: (url) => {
console.log('Received auth URL from bunker:', url);
// Open auth URL in popup or redirect
@@ -1695,12 +1773,8 @@ class Modal {
_showSeedPhraseScreen() {
this.modalBody.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Import from Seed Phrase';
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const description = document.createElement('p');
description.textContent = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts:';
description.innerHTML = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts, or <span id="generate-new" style="text-decoration: underline; cursor: pointer; color: var(--nl-primary-color);">generate new</span>.';
description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
const textarea = document.createElement('textarea');
@@ -1723,10 +1797,40 @@ class Modal {
const formatHint = document.createElement('div');
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
const importButton = document.createElement('button');
importButton.textContent = 'Import Accounts';
importButton.disabled = true;
importButton.onclick = () => {
if (!importButton.disabled) {
this._importFromSeedPhrase(textarea.value);
}
};
// Set initial disabled state
importButton.style.cssText = `
display: block;
width: 100%;
padding: 12px;
border: var(--nl-border-width) solid var(--nl-muted-color);
border-radius: var(--nl-border-radius);
font-size: 16px;
font-weight: 500;
cursor: not-allowed;
transition: all 0.2s;
font-family: var(--nl-font-family, 'Courier New', monospace);
background: var(--nl-secondary-color);
color: var(--nl-muted-color);
`;
textarea.oninput = () => {
const value = textarea.value.trim();
if (!value) {
formatHint.textContent = '';
// Disable button
importButton.disabled = true;
importButton.style.borderColor = 'var(--nl-muted-color)';
importButton.style.color = 'var(--nl-muted-color)';
importButton.style.cursor = 'not-allowed';
return;
}
@@ -1735,35 +1839,46 @@ class Modal {
const wordCount = value.split(/\s+/).length;
formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`;
formatHint.style.color = '#059669';
// Enable button
importButton.disabled = false;
importButton.style.borderColor = 'var(--nl-primary-color)';
importButton.style.color = 'var(--nl-primary-color)';
importButton.style.cursor = 'pointer';
} else {
formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words';
formatHint.style.color = '#dc2626';
// Disable button
importButton.disabled = true;
importButton.style.borderColor = 'var(--nl-muted-color)';
importButton.style.color = 'var(--nl-muted-color)';
importButton.style.cursor = 'not-allowed';
}
};
// Generate new seed phrase button
const generateButton = document.createElement('button');
generateButton.textContent = 'Generate New Seed Phrase';
generateButton.onclick = () => this._generateNewSeedPhrase(textarea, formatHint);
generateButton.style.cssText = this._getButtonStyle() + 'margin-bottom: 12px;';
const importButton = document.createElement('button');
importButton.textContent = 'Import Accounts';
importButton.onclick = () => this._importFromSeedPhrase(textarea.value);
importButton.style.cssText = this._getButtonStyle();
const backButton = document.createElement('button');
backButton.textContent = 'Back';
backButton.onclick = () => this._renderLoginOptions();
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
this.modalBody.appendChild(title);
this.modalBody.appendChild(description);
this.modalBody.appendChild(textarea);
this.modalBody.appendChild(formatHint);
this.modalBody.appendChild(generateButton);
this.modalBody.appendChild(importButton);
this.modalBody.appendChild(backButton);
// Add click handler for the "generate new" link
const generateLink = document.getElementById('generate-new');
if (generateLink) {
generateLink.addEventListener('mouseenter', () => {
generateLink.style.color = 'var(--nl-accent-color)';
});
generateLink.addEventListener('mouseleave', () => {
generateLink.style.color = 'var(--nl-primary-color)';
});
generateLink.addEventListener('click', () => {
this._generateNewSeedPhrase(textarea, formatHint);
});
}
}
_generateNewSeedPhrase(textarea, formatHint) {
@@ -1779,12 +1894,12 @@ class Modal {
// Set the generated mnemonic in the textarea
textarea.value = mnemonic;
// Trigger validation to show it's valid
const wordCount = mnemonic.split(/\s+/).length;
formatHint.textContent = `✅ Generated valid ${wordCount}-word mnemonic`;
formatHint.style.color = '#059669';
// Trigger the oninput event to properly validate and enable the button
if (textarea.oninput) {
textarea.oninput();
}
console.log('Generated new seed phrase:', wordCount, 'words');
console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words');
} catch (error) {
console.error('Failed to generate seed phrase:', error);
@@ -1866,15 +1981,10 @@ class Modal {
_showAccountSelection(accounts) {
this.modalBody.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Select Account';
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const description = document.createElement('p');
description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`;
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
this.modalBody.appendChild(title);
this.modalBody.appendChild(description);
// Create table for account selection
@@ -1892,8 +2002,7 @@ class Modal {
thead.innerHTML = `
<tr style="background: #f3f4f6;">
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">#</th>
<th style="padding: 8px; text-align: left; border: 1px solid #d1d5db; font-weight: bold;">Public Key (npub)</th>
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Action</th>
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Use</th>
</tr>
`;
table.appendChild(thead);
@@ -1908,31 +2017,26 @@ class Modal {
indexCell.textContent = account.index;
indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;';
const pubkeyCell = document.createElement('td');
pubkeyCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db; font-family: monospace; word-break: break-all;';
// Show truncated npub for readability
const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`;
pubkeyCell.innerHTML = `
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 2px;">${truncatedNpub}</code><br>
<small style="color: #6b7280;">Full: ${account.npub}</small>
`;
const actionCell = document.createElement('td');
actionCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db;';
actionCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db;';
// Show truncated npub in the button
const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`;
const selectButton = document.createElement('button');
selectButton.textContent = 'Use';
selectButton.textContent = truncatedNpub;
selectButton.onclick = () => this._selectAccount(account);
selectButton.style.cssText = `
padding: 4px 12px;
width: 100%;
padding: 8px 12px;
font-size: 11px;
background: var(--nl-secondary-color);
color: var(--nl-primary-color);
border: 1px solid var(--nl-primary-color);
border-radius: 4px;
cursor: pointer;
font-family: var(--nl-font-family, 'Courier New', monospace);
font-family: 'Courier New', monospace;
text-align: center;
`;
selectButton.onmouseover = () => {
selectButton.style.borderColor = 'var(--nl-accent-color)';
@@ -1944,7 +2048,6 @@ class Modal {
actionCell.appendChild(selectButton);
row.appendChild(indexCell);
row.appendChild(pubkeyCell);
row.appendChild(actionCell);
tbody.appendChild(row);
});

View File

@@ -104,7 +104,7 @@ class Modal {
closeButton.style.cssText = `
background: var(--nl-secondary-color);
border: var(--nl-border-width) solid var(--nl-primary-color);
border-radius: var(--nl-border-radius);
border-radius: 4px;
font-size: 28px;
color: var(--nl-primary-color);
cursor: pointer;
@@ -288,21 +288,13 @@ class Modal {
};
const iconDiv = document.createElement('div');
// Replace emoji icons with text-based ones
const iconMap = {
'🔌': '[EXT]',
'🔑': '[KEY]',
'🌱': '[SEED]',
'🌐': '[NET]',
'👁️': '[VIEW]',
'📱': '[SMS]'
};
iconDiv.textContent = iconMap[option.icon] || option.icon;
// Remove the icon entirely - no emojis or text-based icons
iconDiv.textContent = '';
iconDiv.style.cssText = `
font-size: 16px;
font-weight: bold;
margin-right: 16px;
width: 50px;
width: 0px;
text-align: center;
color: var(--nl-primary-color);
font-family: var(--nl-font-family, 'Courier New', monospace);
@@ -1197,10 +1189,6 @@ class Modal {
_showConnectScreen() {
this.modalBody.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Connect to NIP-46 Remote Signer';
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const description = document.createElement('p');
description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.';
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
@@ -1225,12 +1213,67 @@ class Modal {
box-sizing: border-box;
`;
// Users will enter the complete bunker connection string with relay info
// Add real-time bunker key validation
const formatHint = document.createElement('div');
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
const connectButton = document.createElement('button');
connectButton.textContent = 'Connect to Bunker';
connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value);
connectButton.style.cssText = this._getButtonStyle();
connectButton.disabled = true;
connectButton.onclick = () => {
if (!connectButton.disabled) {
this._handleNip46Connect(pubkeyInput.value);
}
};
// Set initial disabled state
connectButton.style.cssText = `
display: block;
width: 100%;
padding: 12px;
border: var(--nl-border-width) solid var(--nl-muted-color);
border-radius: var(--nl-border-radius);
font-size: 16px;
font-weight: 500;
cursor: not-allowed;
transition: all 0.2s;
font-family: var(--nl-font-family, 'Courier New', monospace);
background: var(--nl-secondary-color);
color: var(--nl-muted-color);
margin-bottom: 12px;
`;
pubkeyInput.oninput = () => {
const value = pubkeyInput.value.trim();
if (!value) {
formatHint.textContent = '';
// Disable button
connectButton.disabled = true;
connectButton.style.borderColor = 'var(--nl-muted-color)';
connectButton.style.color = 'var(--nl-muted-color)';
connectButton.style.cursor = 'not-allowed';
return;
}
const isValid = this._validateBunkerKey(value);
if (isValid) {
formatHint.textContent = '✅ Valid bunker connection format detected';
formatHint.style.color = '#059669';
// Enable button
connectButton.disabled = false;
connectButton.style.borderColor = 'var(--nl-primary-color)';
connectButton.style.color = 'var(--nl-primary-color)';
connectButton.style.cursor = 'pointer';
} else {
formatHint.textContent = '❌ Invalid format - must be bunker://, npub, or 64-char hex';
formatHint.style.color = '#dc2626';
// Disable button
connectButton.disabled = true;
connectButton.style.borderColor = 'var(--nl-muted-color)';
connectButton.style.color = 'var(--nl-muted-color)';
connectButton.style.cursor = 'not-allowed';
}
};
const backButton = document.createElement('button');
backButton.textContent = 'Back';
@@ -1239,14 +1282,49 @@ class Modal {
formGroup.appendChild(label);
formGroup.appendChild(pubkeyInput);
formGroup.appendChild(formatHint);
this.modalBody.appendChild(title);
this.modalBody.appendChild(description);
this.modalBody.appendChild(formGroup);
this.modalBody.appendChild(connectButton);
this.modalBody.appendChild(backButton);
}
_validateBunkerKey(bunkerKey) {
try {
const trimmed = bunkerKey.trim();
// Check for bunker:// format
if (trimmed.startsWith('bunker://')) {
// Should have format: bunker://pubkey or bunker://pubkey?param=value
const match = trimmed.match(/^bunker:\/\/([0-9a-fA-F]{64})(\?.*)?$/);
return !!match;
}
// Check for npub format
if (trimmed.startsWith('npub1') && trimmed.length === 63) {
try {
if (window.NostrTools?.nip19) {
const decoded = window.NostrTools.nip19.decode(trimmed);
return decoded.type === 'npub';
}
} catch {
return false;
}
}
// Check for hex format (64 characters, valid hex)
if (trimmed.length === 64 && /^[a-fA-F0-9]{64}$/.test(trimmed)) {
return true;
}
return false;
} catch (error) {
console.log('Bunker key validation failed:', error.message);
return false;
}
}
_handleNip46Connect(bunkerPubkey) {
if (!bunkerPubkey || !bunkerPubkey.length) {
this._showError('Bunker pubkey is required');
@@ -1316,9 +1394,9 @@ class Modal {
const localSecretKey = window.NostrTools.generateSecretKey();
console.log('Generated local client keypair for NIP-46 session');
// Use nostr-tools BunkerSigner constructor
// Use nostr-tools BunkerSigner factory method (not constructor - it's private)
console.log('Creating nip46 BunkerSigner...');
const signer = new window.NostrTools.nip46.BunkerSigner(localSecretKey, bunkerPointer, {
const signer = window.NostrTools.nip46.BunkerSigner.fromBunker(localSecretKey, bunkerPointer, {
onauth: (url) => {
console.log('Received auth URL from bunker:', url);
// Open auth URL in popup or redirect
@@ -1398,12 +1476,8 @@ class Modal {
_showSeedPhraseScreen() {
this.modalBody.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Import from Seed Phrase';
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const description = document.createElement('p');
description.textContent = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts:';
description.innerHTML = 'Enter your 12 or 24-word mnemonic seed phrase to derive Nostr accounts, or <span id="generate-new" style="text-decoration: underline; cursor: pointer; color: var(--nl-primary-color);">generate new</span>.';
description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
const textarea = document.createElement('textarea');
@@ -1426,10 +1500,40 @@ class Modal {
const formatHint = document.createElement('div');
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
const importButton = document.createElement('button');
importButton.textContent = 'Import Accounts';
importButton.disabled = true;
importButton.onclick = () => {
if (!importButton.disabled) {
this._importFromSeedPhrase(textarea.value);
}
};
// Set initial disabled state
importButton.style.cssText = `
display: block;
width: 100%;
padding: 12px;
border: var(--nl-border-width) solid var(--nl-muted-color);
border-radius: var(--nl-border-radius);
font-size: 16px;
font-weight: 500;
cursor: not-allowed;
transition: all 0.2s;
font-family: var(--nl-font-family, 'Courier New', monospace);
background: var(--nl-secondary-color);
color: var(--nl-muted-color);
`;
textarea.oninput = () => {
const value = textarea.value.trim();
if (!value) {
formatHint.textContent = '';
// Disable button
importButton.disabled = true;
importButton.style.borderColor = 'var(--nl-muted-color)';
importButton.style.color = 'var(--nl-muted-color)';
importButton.style.cursor = 'not-allowed';
return;
}
@@ -1438,35 +1542,46 @@ class Modal {
const wordCount = value.split(/\s+/).length;
formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`;
formatHint.style.color = '#059669';
// Enable button
importButton.disabled = false;
importButton.style.borderColor = 'var(--nl-primary-color)';
importButton.style.color = 'var(--nl-primary-color)';
importButton.style.cursor = 'pointer';
} else {
formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words';
formatHint.style.color = '#dc2626';
// Disable button
importButton.disabled = true;
importButton.style.borderColor = 'var(--nl-muted-color)';
importButton.style.color = 'var(--nl-muted-color)';
importButton.style.cursor = 'not-allowed';
}
};
// Generate new seed phrase button
const generateButton = document.createElement('button');
generateButton.textContent = 'Generate New Seed Phrase';
generateButton.onclick = () => this._generateNewSeedPhrase(textarea, formatHint);
generateButton.style.cssText = this._getButtonStyle() + 'margin-bottom: 12px;';
const importButton = document.createElement('button');
importButton.textContent = 'Import Accounts';
importButton.onclick = () => this._importFromSeedPhrase(textarea.value);
importButton.style.cssText = this._getButtonStyle();
const backButton = document.createElement('button');
backButton.textContent = 'Back';
backButton.onclick = () => this._renderLoginOptions();
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
this.modalBody.appendChild(title);
this.modalBody.appendChild(description);
this.modalBody.appendChild(textarea);
this.modalBody.appendChild(formatHint);
this.modalBody.appendChild(generateButton);
this.modalBody.appendChild(importButton);
this.modalBody.appendChild(backButton);
// Add click handler for the "generate new" link
const generateLink = document.getElementById('generate-new');
if (generateLink) {
generateLink.addEventListener('mouseenter', () => {
generateLink.style.color = 'var(--nl-accent-color)';
});
generateLink.addEventListener('mouseleave', () => {
generateLink.style.color = 'var(--nl-primary-color)';
});
generateLink.addEventListener('click', () => {
this._generateNewSeedPhrase(textarea, formatHint);
});
}
}
_generateNewSeedPhrase(textarea, formatHint) {
@@ -1482,12 +1597,12 @@ class Modal {
// Set the generated mnemonic in the textarea
textarea.value = mnemonic;
// Trigger validation to show it's valid
const wordCount = mnemonic.split(/\s+/).length;
formatHint.textContent = `✅ Generated valid ${wordCount}-word mnemonic`;
formatHint.style.color = '#059669';
// Trigger the oninput event to properly validate and enable the button
if (textarea.oninput) {
textarea.oninput();
}
console.log('Generated new seed phrase:', wordCount, 'words');
console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words');
} catch (error) {
console.error('Failed to generate seed phrase:', error);
@@ -1569,15 +1684,10 @@ class Modal {
_showAccountSelection(accounts) {
this.modalBody.innerHTML = '';
const title = document.createElement('h3');
title.textContent = 'Select Account';
title.style.cssText = 'margin: 0 0 16px 0; font-size: 18px; font-weight: 600;';
const description = document.createElement('p');
description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`;
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
this.modalBody.appendChild(title);
this.modalBody.appendChild(description);
// Create table for account selection
@@ -1595,8 +1705,7 @@ class Modal {
thead.innerHTML = `
<tr style="background: #f3f4f6;">
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">#</th>
<th style="padding: 8px; text-align: left; border: 1px solid #d1d5db; font-weight: bold;">Public Key (npub)</th>
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Action</th>
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Use</th>
</tr>
`;
table.appendChild(thead);
@@ -1611,31 +1720,26 @@ class Modal {
indexCell.textContent = account.index;
indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;';
const pubkeyCell = document.createElement('td');
pubkeyCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db; font-family: monospace; word-break: break-all;';
// Show truncated npub for readability
const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`;
pubkeyCell.innerHTML = `
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 2px;">${truncatedNpub}</code><br>
<small style="color: #6b7280;">Full: ${account.npub}</small>
`;
const actionCell = document.createElement('td');
actionCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db;';
actionCell.style.cssText = 'padding: 8px; border: 1px solid #d1d5db;';
// Show truncated npub in the button
const truncatedNpub = `${account.npub.slice(0, 12)}...${account.npub.slice(-8)}`;
const selectButton = document.createElement('button');
selectButton.textContent = 'Use';
selectButton.textContent = truncatedNpub;
selectButton.onclick = () => this._selectAccount(account);
selectButton.style.cssText = `
padding: 4px 12px;
width: 100%;
padding: 8px 12px;
font-size: 11px;
background: var(--nl-secondary-color);
color: var(--nl-primary-color);
border: 1px solid var(--nl-primary-color);
border-radius: 4px;
cursor: pointer;
font-family: var(--nl-font-family, 'Courier New', monospace);
font-family: 'Courier New', monospace;
text-align: center;
`;
selectButton.onmouseover = () => {
selectButton.style.borderColor = 'var(--nl-accent-color)';
@@ -1647,7 +1751,6 @@ class Modal {
actionCell.appendChild(selectButton);
row.appendChild(indexCell);
row.appendChild(pubkeyCell);
row.appendChild(actionCell);
tbody.appendChild(row);
});