Fixed issue with bunker. Made the modal more beautiful.
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
* Two-file architecture:
|
* Two-file architecture:
|
||||||
* 1. Load nostr.bundle.js (official nostr-tools bundle)
|
* 1. Load nostr.bundle.js (official nostr-tools bundle)
|
||||||
* 2. Load nostr-lite.js (this file - NOSTR_LOGIN_LITE library with CSS-only themes)
|
* 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
|
// Verify dependencies are loaded
|
||||||
@@ -44,7 +44,7 @@ const THEME_CSS = {
|
|||||||
--nl-primary-color: #000000;
|
--nl-primary-color: #000000;
|
||||||
--nl-secondary-color: #ffffff;
|
--nl-secondary-color: #ffffff;
|
||||||
--nl-accent-color: #ff0000;
|
--nl-accent-color: #ff0000;
|
||||||
--nl-muted-color: #666666;
|
--nl-muted-color: #CCCCCC;
|
||||||
--nl-font-family: "Courier New", Courier, monospace;
|
--nl-font-family: "Courier New", Courier, monospace;
|
||||||
--nl-border-radius: 15px;
|
--nl-border-radius: 15px;
|
||||||
--nl-border-width: 3px;
|
--nl-border-width: 3px;
|
||||||
@@ -401,7 +401,7 @@ class Modal {
|
|||||||
closeButton.style.cssText = `
|
closeButton.style.cssText = `
|
||||||
background: var(--nl-secondary-color);
|
background: var(--nl-secondary-color);
|
||||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||||
border-radius: var(--nl-border-radius);
|
border-radius: 4px;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--nl-primary-color);
|
color: var(--nl-primary-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -585,21 +585,13 @@ class Modal {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const iconDiv = document.createElement('div');
|
const iconDiv = document.createElement('div');
|
||||||
// Replace emoji icons with text-based ones
|
// Remove the icon entirely - no emojis or text-based icons
|
||||||
const iconMap = {
|
iconDiv.textContent = '';
|
||||||
'🔌': '[EXT]',
|
|
||||||
'🔑': '[KEY]',
|
|
||||||
'🌱': '[SEED]',
|
|
||||||
'🌐': '[NET]',
|
|
||||||
'👁️': '[VIEW]',
|
|
||||||
'📱': '[SMS]'
|
|
||||||
};
|
|
||||||
iconDiv.textContent = iconMap[option.icon] || option.icon;
|
|
||||||
iconDiv.style.cssText = `
|
iconDiv.style.cssText = `
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
width: 50px;
|
width: 0px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--nl-primary-color);
|
color: var(--nl-primary-color);
|
||||||
font-family: var(--nl-font-family, 'Courier New', monospace);
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
@@ -1494,10 +1486,6 @@ class Modal {
|
|||||||
_showConnectScreen() {
|
_showConnectScreen() {
|
||||||
this.modalBody.innerHTML = '';
|
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');
|
const description = document.createElement('p');
|
||||||
description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.';
|
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;';
|
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||||
@@ -1522,12 +1510,67 @@ class Modal {
|
|||||||
box-sizing: border-box;
|
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');
|
const connectButton = document.createElement('button');
|
||||||
connectButton.textContent = 'Connect to Bunker';
|
connectButton.textContent = 'Connect to Bunker';
|
||||||
connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value);
|
connectButton.disabled = true;
|
||||||
connectButton.style.cssText = this._getButtonStyle();
|
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');
|
const backButton = document.createElement('button');
|
||||||
backButton.textContent = 'Back';
|
backButton.textContent = 'Back';
|
||||||
@@ -1536,14 +1579,49 @@ class Modal {
|
|||||||
|
|
||||||
formGroup.appendChild(label);
|
formGroup.appendChild(label);
|
||||||
formGroup.appendChild(pubkeyInput);
|
formGroup.appendChild(pubkeyInput);
|
||||||
|
formGroup.appendChild(formatHint);
|
||||||
|
|
||||||
this.modalBody.appendChild(title);
|
|
||||||
this.modalBody.appendChild(description);
|
this.modalBody.appendChild(description);
|
||||||
this.modalBody.appendChild(formGroup);
|
this.modalBody.appendChild(formGroup);
|
||||||
this.modalBody.appendChild(connectButton);
|
this.modalBody.appendChild(connectButton);
|
||||||
this.modalBody.appendChild(backButton);
|
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) {
|
_handleNip46Connect(bunkerPubkey) {
|
||||||
if (!bunkerPubkey || !bunkerPubkey.length) {
|
if (!bunkerPubkey || !bunkerPubkey.length) {
|
||||||
this._showError('Bunker pubkey is required');
|
this._showError('Bunker pubkey is required');
|
||||||
@@ -1613,9 +1691,9 @@ class Modal {
|
|||||||
const localSecretKey = window.NostrTools.generateSecretKey();
|
const localSecretKey = window.NostrTools.generateSecretKey();
|
||||||
console.log('Generated local client keypair for NIP-46 session');
|
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...');
|
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) => {
|
onauth: (url) => {
|
||||||
console.log('Received auth URL from bunker:', url);
|
console.log('Received auth URL from bunker:', url);
|
||||||
// Open auth URL in popup or redirect
|
// Open auth URL in popup or redirect
|
||||||
@@ -1695,12 +1773,8 @@ class Modal {
|
|||||||
_showSeedPhraseScreen() {
|
_showSeedPhraseScreen() {
|
||||||
this.modalBody.innerHTML = '';
|
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');
|
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;';
|
description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
|
||||||
|
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
@@ -1723,10 +1797,40 @@ class Modal {
|
|||||||
const formatHint = document.createElement('div');
|
const formatHint = document.createElement('div');
|
||||||
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
|
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 = () => {
|
textarea.oninput = () => {
|
||||||
const value = textarea.value.trim();
|
const value = textarea.value.trim();
|
||||||
if (!value) {
|
if (!value) {
|
||||||
formatHint.textContent = '';
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1735,35 +1839,46 @@ class Modal {
|
|||||||
const wordCount = value.split(/\s+/).length;
|
const wordCount = value.split(/\s+/).length;
|
||||||
formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`;
|
formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`;
|
||||||
formatHint.style.color = '#059669';
|
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 {
|
} else {
|
||||||
formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words';
|
formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words';
|
||||||
formatHint.style.color = '#dc2626';
|
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');
|
const backButton = document.createElement('button');
|
||||||
backButton.textContent = 'Back';
|
backButton.textContent = 'Back';
|
||||||
backButton.onclick = () => this._renderLoginOptions();
|
backButton.onclick = () => this._renderLoginOptions();
|
||||||
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
|
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
|
||||||
|
|
||||||
this.modalBody.appendChild(title);
|
|
||||||
this.modalBody.appendChild(description);
|
this.modalBody.appendChild(description);
|
||||||
this.modalBody.appendChild(textarea);
|
this.modalBody.appendChild(textarea);
|
||||||
this.modalBody.appendChild(formatHint);
|
this.modalBody.appendChild(formatHint);
|
||||||
this.modalBody.appendChild(generateButton);
|
|
||||||
this.modalBody.appendChild(importButton);
|
this.modalBody.appendChild(importButton);
|
||||||
this.modalBody.appendChild(backButton);
|
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) {
|
_generateNewSeedPhrase(textarea, formatHint) {
|
||||||
@@ -1779,12 +1894,12 @@ class Modal {
|
|||||||
// Set the generated mnemonic in the textarea
|
// Set the generated mnemonic in the textarea
|
||||||
textarea.value = mnemonic;
|
textarea.value = mnemonic;
|
||||||
|
|
||||||
// Trigger validation to show it's valid
|
// Trigger the oninput event to properly validate and enable the button
|
||||||
const wordCount = mnemonic.split(/\s+/).length;
|
if (textarea.oninput) {
|
||||||
formatHint.textContent = `✅ Generated valid ${wordCount}-word mnemonic`;
|
textarea.oninput();
|
||||||
formatHint.style.color = '#059669';
|
}
|
||||||
|
|
||||||
console.log('Generated new seed phrase:', wordCount, 'words');
|
console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to generate seed phrase:', error);
|
console.error('Failed to generate seed phrase:', error);
|
||||||
@@ -1866,15 +1981,10 @@ class Modal {
|
|||||||
_showAccountSelection(accounts) {
|
_showAccountSelection(accounts) {
|
||||||
this.modalBody.innerHTML = '';
|
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');
|
const description = document.createElement('p');
|
||||||
description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`;
|
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;';
|
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||||
|
|
||||||
this.modalBody.appendChild(title);
|
|
||||||
this.modalBody.appendChild(description);
|
this.modalBody.appendChild(description);
|
||||||
|
|
||||||
// Create table for account selection
|
// Create table for account selection
|
||||||
@@ -1892,8 +2002,7 @@ class Modal {
|
|||||||
thead.innerHTML = `
|
thead.innerHTML = `
|
||||||
<tr style="background: #f3f4f6;">
|
<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: 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;">Use</th>
|
||||||
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Action</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
table.appendChild(thead);
|
table.appendChild(thead);
|
||||||
@@ -1908,31 +2017,26 @@ class Modal {
|
|||||||
indexCell.textContent = account.index;
|
indexCell.textContent = account.index;
|
||||||
indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;';
|
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');
|
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');
|
const selectButton = document.createElement('button');
|
||||||
selectButton.textContent = 'Use';
|
selectButton.textContent = truncatedNpub;
|
||||||
selectButton.onclick = () => this._selectAccount(account);
|
selectButton.onclick = () => this._selectAccount(account);
|
||||||
selectButton.style.cssText = `
|
selectButton.style.cssText = `
|
||||||
padding: 4px 12px;
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
background: var(--nl-secondary-color);
|
background: var(--nl-secondary-color);
|
||||||
color: var(--nl-primary-color);
|
color: var(--nl-primary-color);
|
||||||
border: 1px solid var(--nl-primary-color);
|
border: 1px solid var(--nl-primary-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: var(--nl-font-family, 'Courier New', monospace);
|
font-family: 'Courier New', monospace;
|
||||||
|
text-align: center;
|
||||||
`;
|
`;
|
||||||
selectButton.onmouseover = () => {
|
selectButton.onmouseover = () => {
|
||||||
selectButton.style.borderColor = 'var(--nl-accent-color)';
|
selectButton.style.borderColor = 'var(--nl-accent-color)';
|
||||||
@@ -1944,7 +2048,6 @@ class Modal {
|
|||||||
actionCell.appendChild(selectButton);
|
actionCell.appendChild(selectButton);
|
||||||
|
|
||||||
row.appendChild(indexCell);
|
row.appendChild(indexCell);
|
||||||
row.appendChild(pubkeyCell);
|
|
||||||
row.appendChild(actionCell);
|
row.appendChild(actionCell);
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
});
|
});
|
||||||
|
|||||||
237
lite/ui/modal.js
237
lite/ui/modal.js
@@ -104,7 +104,7 @@ class Modal {
|
|||||||
closeButton.style.cssText = `
|
closeButton.style.cssText = `
|
||||||
background: var(--nl-secondary-color);
|
background: var(--nl-secondary-color);
|
||||||
border: var(--nl-border-width) solid var(--nl-primary-color);
|
border: var(--nl-border-width) solid var(--nl-primary-color);
|
||||||
border-radius: var(--nl-border-radius);
|
border-radius: 4px;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
color: var(--nl-primary-color);
|
color: var(--nl-primary-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -288,21 +288,13 @@ class Modal {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const iconDiv = document.createElement('div');
|
const iconDiv = document.createElement('div');
|
||||||
// Replace emoji icons with text-based ones
|
// Remove the icon entirely - no emojis or text-based icons
|
||||||
const iconMap = {
|
iconDiv.textContent = '';
|
||||||
'🔌': '[EXT]',
|
|
||||||
'🔑': '[KEY]',
|
|
||||||
'🌱': '[SEED]',
|
|
||||||
'🌐': '[NET]',
|
|
||||||
'👁️': '[VIEW]',
|
|
||||||
'📱': '[SMS]'
|
|
||||||
};
|
|
||||||
iconDiv.textContent = iconMap[option.icon] || option.icon;
|
|
||||||
iconDiv.style.cssText = `
|
iconDiv.style.cssText = `
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
width: 50px;
|
width: 0px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--nl-primary-color);
|
color: var(--nl-primary-color);
|
||||||
font-family: var(--nl-font-family, 'Courier New', monospace);
|
font-family: var(--nl-font-family, 'Courier New', monospace);
|
||||||
@@ -1197,10 +1189,6 @@ class Modal {
|
|||||||
_showConnectScreen() {
|
_showConnectScreen() {
|
||||||
this.modalBody.innerHTML = '';
|
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');
|
const description = document.createElement('p');
|
||||||
description.textContent = 'Connect to a remote signer (bunker) server to use its keys for signing.';
|
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;';
|
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||||
@@ -1225,12 +1213,67 @@ class Modal {
|
|||||||
box-sizing: border-box;
|
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');
|
const connectButton = document.createElement('button');
|
||||||
connectButton.textContent = 'Connect to Bunker';
|
connectButton.textContent = 'Connect to Bunker';
|
||||||
connectButton.onclick = () => this._handleNip46Connect(pubkeyInput.value);
|
connectButton.disabled = true;
|
||||||
connectButton.style.cssText = this._getButtonStyle();
|
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');
|
const backButton = document.createElement('button');
|
||||||
backButton.textContent = 'Back';
|
backButton.textContent = 'Back';
|
||||||
@@ -1239,14 +1282,49 @@ class Modal {
|
|||||||
|
|
||||||
formGroup.appendChild(label);
|
formGroup.appendChild(label);
|
||||||
formGroup.appendChild(pubkeyInput);
|
formGroup.appendChild(pubkeyInput);
|
||||||
|
formGroup.appendChild(formatHint);
|
||||||
|
|
||||||
this.modalBody.appendChild(title);
|
|
||||||
this.modalBody.appendChild(description);
|
this.modalBody.appendChild(description);
|
||||||
this.modalBody.appendChild(formGroup);
|
this.modalBody.appendChild(formGroup);
|
||||||
this.modalBody.appendChild(connectButton);
|
this.modalBody.appendChild(connectButton);
|
||||||
this.modalBody.appendChild(backButton);
|
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) {
|
_handleNip46Connect(bunkerPubkey) {
|
||||||
if (!bunkerPubkey || !bunkerPubkey.length) {
|
if (!bunkerPubkey || !bunkerPubkey.length) {
|
||||||
this._showError('Bunker pubkey is required');
|
this._showError('Bunker pubkey is required');
|
||||||
@@ -1316,9 +1394,9 @@ class Modal {
|
|||||||
const localSecretKey = window.NostrTools.generateSecretKey();
|
const localSecretKey = window.NostrTools.generateSecretKey();
|
||||||
console.log('Generated local client keypair for NIP-46 session');
|
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...');
|
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) => {
|
onauth: (url) => {
|
||||||
console.log('Received auth URL from bunker:', url);
|
console.log('Received auth URL from bunker:', url);
|
||||||
// Open auth URL in popup or redirect
|
// Open auth URL in popup or redirect
|
||||||
@@ -1398,12 +1476,8 @@ class Modal {
|
|||||||
_showSeedPhraseScreen() {
|
_showSeedPhraseScreen() {
|
||||||
this.modalBody.innerHTML = '';
|
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');
|
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;';
|
description.style.cssText = 'margin-bottom: 12px; color: #6b7280; font-size: 14px;';
|
||||||
|
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
@@ -1426,10 +1500,40 @@ class Modal {
|
|||||||
const formatHint = document.createElement('div');
|
const formatHint = document.createElement('div');
|
||||||
formatHint.style.cssText = 'margin-bottom: 16px; font-size: 12px; color: #6b7280; min-height: 16px;';
|
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 = () => {
|
textarea.oninput = () => {
|
||||||
const value = textarea.value.trim();
|
const value = textarea.value.trim();
|
||||||
if (!value) {
|
if (!value) {
|
||||||
formatHint.textContent = '';
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1438,35 +1542,46 @@ class Modal {
|
|||||||
const wordCount = value.split(/\s+/).length;
|
const wordCount = value.split(/\s+/).length;
|
||||||
formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`;
|
formatHint.textContent = `✅ Valid ${wordCount}-word mnemonic detected`;
|
||||||
formatHint.style.color = '#059669';
|
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 {
|
} else {
|
||||||
formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words';
|
formatHint.textContent = '❌ Invalid mnemonic - must be 12 or 24 valid BIP-39 words';
|
||||||
formatHint.style.color = '#dc2626';
|
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');
|
const backButton = document.createElement('button');
|
||||||
backButton.textContent = 'Back';
|
backButton.textContent = 'Back';
|
||||||
backButton.onclick = () => this._renderLoginOptions();
|
backButton.onclick = () => this._renderLoginOptions();
|
||||||
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
|
backButton.style.cssText = this._getButtonStyle('secondary') + 'margin-top: 12px;';
|
||||||
|
|
||||||
this.modalBody.appendChild(title);
|
|
||||||
this.modalBody.appendChild(description);
|
this.modalBody.appendChild(description);
|
||||||
this.modalBody.appendChild(textarea);
|
this.modalBody.appendChild(textarea);
|
||||||
this.modalBody.appendChild(formatHint);
|
this.modalBody.appendChild(formatHint);
|
||||||
this.modalBody.appendChild(generateButton);
|
|
||||||
this.modalBody.appendChild(importButton);
|
this.modalBody.appendChild(importButton);
|
||||||
this.modalBody.appendChild(backButton);
|
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) {
|
_generateNewSeedPhrase(textarea, formatHint) {
|
||||||
@@ -1482,12 +1597,12 @@ class Modal {
|
|||||||
// Set the generated mnemonic in the textarea
|
// Set the generated mnemonic in the textarea
|
||||||
textarea.value = mnemonic;
|
textarea.value = mnemonic;
|
||||||
|
|
||||||
// Trigger validation to show it's valid
|
// Trigger the oninput event to properly validate and enable the button
|
||||||
const wordCount = mnemonic.split(/\s+/).length;
|
if (textarea.oninput) {
|
||||||
formatHint.textContent = `✅ Generated valid ${wordCount}-word mnemonic`;
|
textarea.oninput();
|
||||||
formatHint.style.color = '#059669';
|
}
|
||||||
|
|
||||||
console.log('Generated new seed phrase:', wordCount, 'words');
|
console.log('Generated new seed phrase:', mnemonic.split(/\s+/).length, 'words');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to generate seed phrase:', error);
|
console.error('Failed to generate seed phrase:', error);
|
||||||
@@ -1569,15 +1684,10 @@ class Modal {
|
|||||||
_showAccountSelection(accounts) {
|
_showAccountSelection(accounts) {
|
||||||
this.modalBody.innerHTML = '';
|
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');
|
const description = document.createElement('p');
|
||||||
description.textContent = `Select which account to use (${accounts.length} accounts derived from seed phrase):`;
|
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;';
|
description.style.cssText = 'margin-bottom: 20px; color: #6b7280; font-size: 14px;';
|
||||||
|
|
||||||
this.modalBody.appendChild(title);
|
|
||||||
this.modalBody.appendChild(description);
|
this.modalBody.appendChild(description);
|
||||||
|
|
||||||
// Create table for account selection
|
// Create table for account selection
|
||||||
@@ -1595,8 +1705,7 @@ class Modal {
|
|||||||
thead.innerHTML = `
|
thead.innerHTML = `
|
||||||
<tr style="background: #f3f4f6;">
|
<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: 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;">Use</th>
|
||||||
<th style="padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;">Action</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
table.appendChild(thead);
|
table.appendChild(thead);
|
||||||
@@ -1611,31 +1720,26 @@ class Modal {
|
|||||||
indexCell.textContent = account.index;
|
indexCell.textContent = account.index;
|
||||||
indexCell.style.cssText = 'padding: 8px; text-align: center; border: 1px solid #d1d5db; font-weight: bold;';
|
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');
|
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');
|
const selectButton = document.createElement('button');
|
||||||
selectButton.textContent = 'Use';
|
selectButton.textContent = truncatedNpub;
|
||||||
selectButton.onclick = () => this._selectAccount(account);
|
selectButton.onclick = () => this._selectAccount(account);
|
||||||
selectButton.style.cssText = `
|
selectButton.style.cssText = `
|
||||||
padding: 4px 12px;
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
background: var(--nl-secondary-color);
|
background: var(--nl-secondary-color);
|
||||||
color: var(--nl-primary-color);
|
color: var(--nl-primary-color);
|
||||||
border: 1px solid var(--nl-primary-color);
|
border: 1px solid var(--nl-primary-color);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: var(--nl-font-family, 'Courier New', monospace);
|
font-family: 'Courier New', monospace;
|
||||||
|
text-align: center;
|
||||||
`;
|
`;
|
||||||
selectButton.onmouseover = () => {
|
selectButton.onmouseover = () => {
|
||||||
selectButton.style.borderColor = 'var(--nl-accent-color)';
|
selectButton.style.borderColor = 'var(--nl-accent-color)';
|
||||||
@@ -1647,7 +1751,6 @@ class Modal {
|
|||||||
actionCell.appendChild(selectButton);
|
actionCell.appendChild(selectButton);
|
||||||
|
|
||||||
row.appendChild(indexCell);
|
row.appendChild(indexCell);
|
||||||
row.appendChild(pubkeyCell);
|
|
||||||
row.appendChild(actionCell);
|
row.appendChild(actionCell);
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user