Still don't have dm working because I can't decrypt at primal.
This commit is contained in:
50
examples/README.md
Normal file
50
examples/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# NOSTR_LOGIN_LITE Examples
|
||||
|
||||
This directory contains examples and tests for NOSTR_LOGIN_LITE using the local bundle setup.
|
||||
|
||||
## Files
|
||||
|
||||
### 🔬 comprehensive-test.html
|
||||
**The main diagnostic and testing tool**
|
||||
- Comprehensive test suite with extensive debugging output
|
||||
- Tests all library functions, dependencies, crypto, storage, etc.
|
||||
- Results displayed on webpage for easy copying and debugging
|
||||
- Run this when you need to diagnose issues or verify functionality
|
||||
|
||||
### 📱 simple-demo.html
|
||||
Basic demonstration of NOSTR_LOGIN_LITE integration
|
||||
- Minimal setup example
|
||||
- Good starting point for new implementations
|
||||
|
||||
### 🎨 modal-login-demo.html
|
||||
Demonstrates modal-based login flow
|
||||
- Shows how to trigger and handle the login modal
|
||||
- Example of auth event handling
|
||||
|
||||
### 👤 login-and-profile.html
|
||||
Login and user profile demonstration
|
||||
- Shows authentication flow
|
||||
- Displays user profile information after login
|
||||
|
||||
### 🔗 nip46-bunker-demo.html
|
||||
NIP-46 remote signing demonstration
|
||||
- Shows how to connect to remote signers/bunkers
|
||||
- Advanced use case example
|
||||
|
||||
## Usage
|
||||
|
||||
1. Start a local web server (e.g., `python -m http.server 5501` or Live Server in VS Code)
|
||||
2. Navigate to any HTML file
|
||||
3. For comprehensive testing and debugging, use `comprehensive-test.html`
|
||||
|
||||
All examples use the local bundle setup with two files:
|
||||
1. `../lite/nostr.bundle.js` - Official nostr-tools bundle
|
||||
2. `../lite/nostr-lite.js` - NOSTR_LOGIN_LITE library with embedded NIP-46 extension
|
||||
|
||||
## Architecture Update (2025-09-13)
|
||||
|
||||
The library has been simplified from a three-file to a two-file architecture:
|
||||
- ✅ **Before:** `nostr.bundle.js` + `nip46-extension.js` + `nostr-lite.js`
|
||||
- ✅ **Now:** `nostr.bundle.js` + `nostr-lite.js` (with embedded NIP-46)
|
||||
|
||||
All functionality remains identical - NIP-46 remote signing, all auth methods, and full compatibility are preserved.
|
||||
1134
examples/comprehensive-test.html
Normal file
1134
examples/comprehensive-test.html
Normal file
File diff suppressed because it is too large
Load Diff
459
examples/login-and-profile.html
Normal file
459
examples/login-and-profile.html
Normal file
@@ -0,0 +1,459 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🔐 NOSTR_LOGIN_LITE - All Login Methods Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
background: linear-gradient(45deg, #fff, #007bff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status.logged-out {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.status.logged-in {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
border: 1px solid rgba(76, 175, 80, 0.5);
|
||||
}
|
||||
|
||||
.status.loading {
|
||||
background: rgba(255, 193, 7, 0.2);
|
||||
border: 1px solid rgba(255, 193, 7, 0.5);
|
||||
}
|
||||
|
||||
.button {
|
||||
background: linear-gradient(45deg, #007bff, #0056b3);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-block;
|
||||
margin: 10px 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.4);
|
||||
}
|
||||
|
||||
.button.secondary {
|
||||
background: linear-gradient(45deg, #6c757d, #495057);
|
||||
}
|
||||
|
||||
.button.secondary:hover {
|
||||
box-shadow: 0 4px 15px rgba(108, 117, 125, 0.4);
|
||||
}
|
||||
|
||||
.button.danger {
|
||||
background: linear-gradient(45deg, #dc3545, #c82333);
|
||||
}
|
||||
|
||||
.button.danger:hover {
|
||||
box-shadow: 0 4px 15px rgba(220, 53, 69, 0.4);
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.profile-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
margin: 0 auto 15px;
|
||||
display: block;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.profile-about {
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.profile-pubkey {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.console-output {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.console-entry {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.console-timestamp {
|
||||
color: #ccc;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.info-section h3 {
|
||||
margin-top: 0;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.methods-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.methods-list li {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin: 10px 0;
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔐 NOSTR_LOGIN_LITE - All Login Methods Test</h1>
|
||||
|
||||
<div id="status" class="status logged-out">
|
||||
⏳ Initializing NOSTR_LOGIN_LITE...
|
||||
</div>
|
||||
|
||||
<div id="login-section">
|
||||
<button id="launch-modal" class="button">🚀 Launch Login Modal</button>
|
||||
<p style="font-size: 14px; opacity: 0.8; margin-top: 10px;">
|
||||
Click to open the NOSTR_LOGIN_LITE modal and test all available login methods:
|
||||
</p>
|
||||
|
||||
<div class="info-section">
|
||||
<h3>Available Login Methods</h3>
|
||||
<ul class="methods-list">
|
||||
<li><strong>Browser Extension:</strong> Alby, nos2x, etc. (if installed)</li>
|
||||
<li><strong>Local Key:</strong> Generate new keys or import existing private key/nsec</li>
|
||||
<li><strong>Read Only:</strong> Access public content without authentication</li>
|
||||
<li><strong>Nostr Connect (NIP-46):</strong> Connect to remote signing services</li>
|
||||
<li><strong>DM/OTP:</strong> Secure local accounts with one-time passwords</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="profile-section" style="display: none;">
|
||||
<div class="profile-card">
|
||||
<div class="profile-header">
|
||||
<img id="profile-picture" class="profile-avatar" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iNDAiIGN5PSI0MCIgcj0iNDAiIGZpbGw9IiNmNmY2ZjYiLz4KPHBhdGggZD0iTTQwIDQ1QzQ1IDQ1IDUwIDQyIDUwIDQwUzQ1IDQxIDQwIDQ1WiIgc3Ryb2tlPSIjYmJkIiBzdHJva2Utd2lkdGg9IjIiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPgo8Y2lyY2xlIGN4PSIzNSIgY3k9IjM1IiByPSIyIiBmaWxsPSIjYmJkIi8+CjxjaXJjbGUgY3g9IjQ1IiBjeT0iMzUiIHI9IjIiIGZpbGw9IiNiYmQiLz4KPGRlZnM+Cj08L2RlZnM+Cjwvc3ZnPgo=" alt="Profile Picture">
|
||||
<div id="profile-name" class="profile-name">Loading...</div>
|
||||
<div id="profile-about" class="profile-about"></div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Public Key:</strong>
|
||||
<div id="profile-pubkey" class="profile-pubkey"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="refresh-profile" class="button secondary">🔄 Refresh Profile</button>
|
||||
<button id="logout" class="button danger">🚪 Logout</button>
|
||||
</div>
|
||||
|
||||
<div class="console-output" id="console-display">
|
||||
<div class="console-entry">
|
||||
<span class="console-timestamp">[Console]</span> Ready for testing
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Load the official nostr-tools bundle first -->
|
||||
<script src="../lite/nostr.bundle.js"></script>
|
||||
|
||||
<!-- Load NOSTR_LOGIN_LITE main library (now includes NIP-46 extension) -->
|
||||
<script src="../lite/nostr-lite.js"></script>
|
||||
|
||||
<script>
|
||||
// Console logging helper
|
||||
function log(level, message) {
|
||||
const consoleDiv = document.getElementById('console-display');
|
||||
const entry = document.createElement('div');
|
||||
entry.className = 'console-entry';
|
||||
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const prefix = level === 'ERROR' ? '[ERROR]' :
|
||||
level === 'SUCCESS' ? '[SUCCESS]' :
|
||||
level === 'WARNING' ? '[WARNING]' : '[INFO]';
|
||||
|
||||
entry.innerHTML = `<span class="console-timestamp">[${timestamp}] ${prefix}</span> ${message}`;
|
||||
consoleDiv.appendChild(entry);
|
||||
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
||||
}
|
||||
|
||||
// Global variables
|
||||
let nlLite = null;
|
||||
let userPubkey = null;
|
||||
let relayUrl = 'wss://relay.laantungir.net';
|
||||
|
||||
// Initialize NOSTR_LOGIN_LITE
|
||||
async function initializeApp() {
|
||||
log('INFO', 'Initializing NOSTR_LOGIN_LITE...');
|
||||
|
||||
try {
|
||||
await window.NOSTR_LOGIN_LITE.init({
|
||||
theme: 'dark',
|
||||
darkMode: false,
|
||||
relays: [relayUrl, 'wss://relay.damus.io'],
|
||||
methods: {
|
||||
extension: true,
|
||||
local: true,
|
||||
readonly: true,
|
||||
remote: true, // Enables "Nostr Connect" (NIP-46)
|
||||
otp: true // Enables "DM/OTP"
|
||||
},
|
||||
debug: true
|
||||
});
|
||||
|
||||
nlLite = window.NOSTR_LOGIN_LITE;
|
||||
log('SUCCESS', 'NOSTR_LOGIN_LITE initialized successfully');
|
||||
document.getElementById('status').innerHTML = '✅ Ready - Click "Launch Login Modal" to test all login methods';
|
||||
document.getElementById('status').className = 'status logged-in';
|
||||
|
||||
} catch (error) {
|
||||
log('ERROR', `Initialization failed: ${error.message}`);
|
||||
document.getElementById('status').innerHTML = '❌ Failed to initialize NOSTR_LOGIN_LITE';
|
||||
document.getElementById('status').className = 'status logged-out';
|
||||
}
|
||||
}
|
||||
|
||||
// Launch the login modal
|
||||
async function launchLoginModal() {
|
||||
log('INFO', 'Launching NOSTR_LOGIN_LITE modal...');
|
||||
document.getElementById('status').innerHTML = '🔄 Opening login modal...';
|
||||
document.getElementById('status').className = 'status loading';
|
||||
|
||||
try {
|
||||
// Launch the modal
|
||||
await nlLite.launch('login');
|
||||
log('SUCCESS', 'Login modal launched successfully');
|
||||
|
||||
} catch (error) {
|
||||
log('ERROR', `Failed to launch modal: ${error.message}`);
|
||||
document.getElementById('status').innerHTML = '❌ Failed to launch modal';
|
||||
document.getElementById('status').className = 'status logged-out';
|
||||
}
|
||||
}
|
||||
|
||||
// Handle authentication events
|
||||
function handleAuthEvent(event) {
|
||||
const { type, pubkey, method, error } = event.detail;
|
||||
|
||||
log('INFO', `Auth event received: type=${type}, method=${method}`);
|
||||
|
||||
if (type === 'login' && pubkey) {
|
||||
userPubkey = pubkey;
|
||||
log('SUCCESS', `Login successful! Method: ${method}, Pubkey: ${pubkey}`);
|
||||
|
||||
document.getElementById('status').innerHTML = `✅ Logged in via ${method}!`;
|
||||
document.getElementById('status').className = 'status logged-in';
|
||||
|
||||
// Show profile section
|
||||
document.getElementById('login-section').style.display = 'none';
|
||||
document.getElementById('profile-section').style.display = 'block';
|
||||
|
||||
// Load profile
|
||||
loadUserProfile();
|
||||
|
||||
} else if (type === 'logout') {
|
||||
log('INFO', 'User logged out');
|
||||
userPubkey = null;
|
||||
|
||||
document.getElementById('status').innerHTML = '✅ Ready - Click "Launch Login Modal" to test all login methods';
|
||||
document.getElementById('status').className = 'status logged-in';
|
||||
|
||||
// Show login section
|
||||
document.getElementById('login-section').style.display = 'block';
|
||||
document.getElementById('profile-section').style.display = 'none';
|
||||
|
||||
} else if (error) {
|
||||
log('ERROR', `Authentication error: ${error}`);
|
||||
document.getElementById('status').innerHTML = '❌ Authentication failed';
|
||||
document.getElementById('status').className = 'status logged-out';
|
||||
}
|
||||
}
|
||||
|
||||
// Load user profile
|
||||
async function loadUserProfile() {
|
||||
if (!userPubkey) return;
|
||||
|
||||
log('INFO', `Loading profile for: ${userPubkey}`);
|
||||
document.getElementById('profile-name').textContent = 'Loading profile...';
|
||||
document.getElementById('profile-pubkey').textContent = userPubkey;
|
||||
|
||||
try {
|
||||
// Simple WebSocket connection to get profile
|
||||
const ws = new WebSocket(relayUrl);
|
||||
|
||||
ws.onopen = () => {
|
||||
log('SUCCESS', 'WebSocket connected, requesting profile...');
|
||||
const req = JSON.stringify([
|
||||
'REQ',
|
||||
'profile',
|
||||
{
|
||||
kinds: [0],
|
||||
authors: [userPubkey],
|
||||
limit: 1
|
||||
}
|
||||
]);
|
||||
ws.send(req);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
const [type, subscriptionId, eventData] = message;
|
||||
|
||||
if (type === 'EVENT' && eventData && eventData.kind === 0) {
|
||||
log('SUCCESS', 'Profile event received');
|
||||
const profile = JSON.parse(eventData.content);
|
||||
displayProfile(profile);
|
||||
ws.close();
|
||||
} else if (type === 'EOSE') {
|
||||
log('INFO', 'End of subscription');
|
||||
ws.close();
|
||||
}
|
||||
} catch (parseError) {
|
||||
log('ERROR', `Failed to parse WebSocket message: ${parseError.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
log('ERROR', `WebSocket error: ${error.message || 'Connection failed'}`);
|
||||
document.getElementById('profile-name').textContent = 'Error loading profile';
|
||||
};
|
||||
|
||||
// Timeout after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (ws.readyState !== WebSocket.CLOSED) {
|
||||
ws.close();
|
||||
if (document.getElementById('profile-name').textContent === 'Loading profile...') {
|
||||
document.getElementById('profile-name').textContent = 'Profile timeout';
|
||||
document.getElementById('profile-about').textContent = 'Could not load profile from relay.';
|
||||
log('WARNING', 'Profile request timed out');
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
} catch (error) {
|
||||
log('ERROR', `Profile loading failed: ${error.message}`);
|
||||
document.getElementById('profile-name').textContent = 'Error loading profile';
|
||||
document.getElementById('profile-about').textContent = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
// Display profile data
|
||||
function displayProfile(profile) {
|
||||
const name = profile.name || profile.display_name || profile.displayName || 'Anonymous User';
|
||||
const about = profile.about || '';
|
||||
const picture = profile.picture || '';
|
||||
|
||||
document.getElementById('profile-name').textContent = name;
|
||||
document.getElementById('profile-about').textContent = about;
|
||||
|
||||
if (picture) {
|
||||
document.getElementById('profile-picture').src = picture;
|
||||
}
|
||||
|
||||
log('SUCCESS', `Profile displayed: ${name}`);
|
||||
}
|
||||
|
||||
// Logout function
|
||||
async function logout() {
|
||||
log('INFO', 'Logging out...');
|
||||
try {
|
||||
await nlLite.logout();
|
||||
log('SUCCESS', 'Logged out successfully');
|
||||
} catch (error) {
|
||||
log('ERROR', `Logout failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up event listeners
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Button event listeners
|
||||
document.getElementById('launch-modal').addEventListener('click', launchLoginModal);
|
||||
document.getElementById('refresh-profile').addEventListener('click', loadUserProfile);
|
||||
document.getElementById('logout').addEventListener('click', logout);
|
||||
|
||||
// Listen for authentication events
|
||||
window.addEventListener('nlAuth', handleAuthEvent);
|
||||
|
||||
// Initialize the app
|
||||
setTimeout(initializeApp, 100);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,121 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Minimal Nostr Login Test (Remote)</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="user-info" style="display: none;">
|
||||
<div id="user-name"></div>
|
||||
<img id="user-picture" style="display: none;">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Load nostr-login script with configuration from your website
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://laantungir.net/nostr-login/unpkg.js';
|
||||
|
||||
// Configure with Laan theme and Welcome Login start screen
|
||||
script.setAttribute('data-theme', 'laan');
|
||||
script.setAttribute('data-start-screen', 'welcome-login');
|
||||
script.setAttribute('data-methods', 'connect,extension,local,readOnly');
|
||||
script.setAttribute('data-no-banner', 'false');
|
||||
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Wait for script to load, then set up event listeners
|
||||
script.onload = () => {
|
||||
setupEventListeners();
|
||||
};
|
||||
|
||||
function setupEventListeners() {
|
||||
// Listen for authentication events
|
||||
document.addEventListener('nlAuth', (e) => {
|
||||
const { type, pubkey, name } = e.detail;
|
||||
|
||||
if (type === 'login' || type === 'signup') {
|
||||
console.log('User logged in:', e.detail);
|
||||
showUserInfo();
|
||||
// Check for profile updates periodically
|
||||
startProfilePolling();
|
||||
} else if (type === 'logout') {
|
||||
console.log('User logged out');
|
||||
hideUserInfo();
|
||||
stopProfilePolling();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let profileInterval;
|
||||
|
||||
function startProfilePolling() {
|
||||
// Check immediately
|
||||
updateUserProfile();
|
||||
|
||||
// Then check every 2 seconds for profile updates
|
||||
profileInterval = setInterval(updateUserProfile, 2000);
|
||||
|
||||
// Stop checking after 15 seconds (profile should be loaded by then)
|
||||
setTimeout(stopProfilePolling, 15000);
|
||||
}
|
||||
|
||||
function stopProfilePolling() {
|
||||
if (profileInterval) {
|
||||
clearInterval(profileInterval);
|
||||
profileInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateUserProfile() {
|
||||
try {
|
||||
// Try to get user info from localStorage (current user)
|
||||
const userInfo = JSON.parse(localStorage.getItem('__nostrlogin_nip46') || 'null');
|
||||
if (userInfo) {
|
||||
updateUserDisplay(userInfo);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle errors to avoid interference
|
||||
}
|
||||
}
|
||||
|
||||
function showUserInfo() {
|
||||
const userInfoDiv = document.getElementById('user-info');
|
||||
userInfoDiv.style.display = 'block';
|
||||
updateUserProfile();
|
||||
}
|
||||
|
||||
function updateUserDisplay(userInfo) {
|
||||
const userNameDiv = document.getElementById('user-name');
|
||||
const userPicture = document.getElementById('user-picture');
|
||||
|
||||
// Display name with proper fallback hierarchy
|
||||
const displayName = userInfo.name ||
|
||||
(userInfo.nip05 && userInfo.nip05.split('@')[0]) ||
|
||||
(userInfo.pubkey && userInfo.pubkey.substring(0, 16) + '...') ||
|
||||
'Unknown';
|
||||
|
||||
userNameDiv.textContent = displayName;
|
||||
|
||||
// Update profile picture if available
|
||||
if (userInfo.picture) {
|
||||
userPicture.src = userInfo.picture;
|
||||
userPicture.style.display = 'block';
|
||||
userPicture.style.width = '200px';
|
||||
// userPicture.style.height = '200px';
|
||||
}
|
||||
}
|
||||
|
||||
function hideUserInfo() {
|
||||
const userInfoDiv = document.getElementById('user-info');
|
||||
const userNameDiv = document.getElementById('user-name');
|
||||
const userPicture = document.getElementById('user-picture');
|
||||
|
||||
userInfoDiv.style.display = 'none';
|
||||
userNameDiv.textContent = '';
|
||||
userPicture.src = '';
|
||||
userPicture.style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,121 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Minimal Nostr Login Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="user-info" style="display: none;">
|
||||
<div id="user-name"></div>
|
||||
<img id="user-picture" style="display: none;">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Load nostr-login script with configuration
|
||||
const script = document.createElement('script');
|
||||
script.src = '../packages/auth/dist/unpkg.js';
|
||||
|
||||
// Configure with Laan theme and Welcome Login start screen
|
||||
script.setAttribute('data-theme', 'laan');
|
||||
script.setAttribute('data-start-screen', 'welcome-login');
|
||||
script.setAttribute('data-methods', 'connect,extension,local,readOnly');
|
||||
script.setAttribute('data-no-banner', 'false');
|
||||
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Wait for script to load, then set up event listeners
|
||||
script.onload = () => {
|
||||
setupEventListeners();
|
||||
};
|
||||
|
||||
function setupEventListeners() {
|
||||
// Listen for authentication events
|
||||
document.addEventListener('nlAuth', (e) => {
|
||||
const { type, pubkey, name } = e.detail;
|
||||
|
||||
if (type === 'login' || type === 'signup') {
|
||||
console.log('User logged in:', e.detail);
|
||||
showUserInfo();
|
||||
// Check for profile updates periodically
|
||||
startProfilePolling();
|
||||
} else if (type === 'logout') {
|
||||
console.log('User logged out');
|
||||
hideUserInfo();
|
||||
stopProfilePolling();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let profileInterval;
|
||||
|
||||
function startProfilePolling() {
|
||||
// Check immediately
|
||||
updateUserProfile();
|
||||
|
||||
// Then check every 2 seconds for profile updates
|
||||
profileInterval = setInterval(updateUserProfile, 2000);
|
||||
|
||||
// Stop checking after 15 seconds (profile should be loaded by then)
|
||||
setTimeout(stopProfilePolling, 15000);
|
||||
}
|
||||
|
||||
function stopProfilePolling() {
|
||||
if (profileInterval) {
|
||||
clearInterval(profileInterval);
|
||||
profileInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateUserProfile() {
|
||||
try {
|
||||
// Try to get user info from localStorage (current user)
|
||||
const userInfo = JSON.parse(localStorage.getItem('__nostrlogin_nip46') || 'null');
|
||||
if (userInfo) {
|
||||
updateUserDisplay(userInfo);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle errors to avoid interference
|
||||
}
|
||||
}
|
||||
|
||||
function showUserInfo() {
|
||||
const userInfoDiv = document.getElementById('user-info');
|
||||
userInfoDiv.style.display = 'block';
|
||||
updateUserProfile();
|
||||
}
|
||||
|
||||
function updateUserDisplay(userInfo) {
|
||||
const userNameDiv = document.getElementById('user-name');
|
||||
const userPicture = document.getElementById('user-picture');
|
||||
|
||||
// Display name with proper fallback hierarchy
|
||||
const displayName = userInfo.name ||
|
||||
(userInfo.nip05 && userInfo.nip05.split('@')[0]) ||
|
||||
(userInfo.pubkey && userInfo.pubkey.substring(0, 16) + '...') ||
|
||||
'Unknown';
|
||||
|
||||
userNameDiv.textContent = displayName;
|
||||
|
||||
// Update profile picture if available
|
||||
if (userInfo.picture) {
|
||||
userPicture.src = userInfo.picture;
|
||||
userPicture.style.display = 'block';
|
||||
userPicture.style.width = '200px';
|
||||
// userPicture.style.height = '200px';
|
||||
}
|
||||
}
|
||||
|
||||
function hideUserInfo() {
|
||||
const userInfoDiv = document.getElementById('user-info');
|
||||
const userNameDiv = document.getElementById('user-name');
|
||||
const userPicture = document.getElementById('user-picture');
|
||||
|
||||
userInfoDiv.style.display = 'none';
|
||||
userNameDiv.textContent = '';
|
||||
userPicture.src = '';
|
||||
userPicture.style.display = 'none';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
411
examples/modal-login-demo.html
Normal file
411
examples/modal-login-demo.html
Normal file
@@ -0,0 +1,411 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🔐 NOSTR_LOGIN_LITE - Full Modal Login Demo</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
background: linear-gradient(45deg, #fff, #007bff);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
margin-top: 0;
|
||||
font-size: 24px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: linear-gradient(45deg, #007bff, #0056b3);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: inline-block;
|
||||
margin: 10px 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.4);
|
||||
}
|
||||
|
||||
.button.secondary {
|
||||
background: linear-gradient(45deg, #6c757d, #495057);
|
||||
}
|
||||
|
||||
.button.secondary:hover {
|
||||
box-shadow: 0 4px 15px rgba(108, 117, 125, 0.4);
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status.success { background: rgba(76, 175, 80, 0.2); color: #81c784; }
|
||||
.status.error { background: rgba(244, 67, 54, 0.2); color: #ef5350; }
|
||||
.status.warning { background: rgba(255, 193, 7, 0.2); color: #ffd54f; }
|
||||
.status.info { background: rgba(33, 150, 243, 0.2); color: #64b5f6; }
|
||||
|
||||
.console-output {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.console-entry {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.console-timestamp {
|
||||
color: #ccc;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-item .icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.feature-item h3 {
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.feature-item p {
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔐 NOSTR_LOGIN_LITE Full Modal Login Demo</h1>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2>📚 Available Login Methods</h2>
|
||||
<p>This demo showcases all login methods provided by NOSTR_LOGIN_LITE:</p>
|
||||
|
||||
<div class="feature-list">
|
||||
<div class="feature-item">
|
||||
<div class="icon">📱</div>
|
||||
<h3>Extension Login</h3>
|
||||
<p>Use browser extensions like Alby, nos2x, or other Nostr-compatible extensions</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="icon">💾</div>
|
||||
<h3>Local Account</h3>
|
||||
<p>Create and manage local Nostr keypairs stored in browser storage</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="icon">👁️</div>
|
||||
<h3>Read-Only Account</h3>
|
||||
<p>Access public content without authentication (limited functionality)</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="icon">🔗</div>
|
||||
<h3>NIP-46 Remote</h3>
|
||||
<p>Connect to remote signers for secure key management</p>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="icon">🔐</div>
|
||||
<h3>OTP Backup</h3>
|
||||
<p>Secure local accounts with time-based one-time passwords</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Library Status -->
|
||||
<div class="demo-section">
|
||||
<h2>⚙️ Library Status</h2>
|
||||
<div id="dep-status" class="status info">Loading nostr-tools...</div>
|
||||
<div id="lib-status" class="status info">Loading NOSTR_LOGIN_LITE...</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal Authentication -->
|
||||
<div class="demo-section">
|
||||
<h2>🎯 Launch Full Login Modal</h2>
|
||||
<p>Click the button below to launch the complete authentication modal with all available login options:</p>
|
||||
<button id="launch-auth" class="button">🚀 Launch Authentication Modal</button>
|
||||
<button onclick="location.reload()" class="button secondary">🔄 Reload Page</button>
|
||||
<div id="auth-status" class="status" style="margin-top: 15px;">Ready to authenticate...</div>
|
||||
<div style="font-size: 14px; opacity: 0.8; margin-top: 10px;">
|
||||
The modal will show all available login methods based on your browser setup and library configuration.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Info Display (shown after login) -->
|
||||
<div id="user-info" class="demo-section" style="display: none;">
|
||||
<h2>👤 User Information</h2>
|
||||
<div id="user-details">
|
||||
<strong>Public Key:</strong> <span id="user-pubkey">Loading...</span><br>
|
||||
<strong>Login Method:</strong> <span id="user-method">Loading...</span><br>
|
||||
<strong>Account Type:</strong> <span id="user-type">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Console Log -->
|
||||
<div class="console-output" id="console-display">
|
||||
<div class="console-entry">
|
||||
<span class="console-timestamp">[Demo]</span> Modal Login Demo initialized
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Load the official nostr-tools bundle first -->
|
||||
<script src="../lite/nostr.bundle.js"></script>
|
||||
|
||||
<!-- Load NOSTR_LOGIN_LITE main library (now includes NIP-46 extension) -->
|
||||
<script src="../lite/nostr-lite.js"></script>
|
||||
|
||||
<script>
|
||||
// Console logging helper
|
||||
function addConsoleEntry(message, type = 'info') {
|
||||
const consoleDiv = document.getElementById('console-display');
|
||||
const entry = document.createElement('div');
|
||||
entry.className = 'console-entry';
|
||||
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const prefix = type === 'error' ? '[ERROR]' :
|
||||
type === 'success' ? '[SUCCESS]' :
|
||||
type === 'warning' ? '[WARNING]' : '[INFO]';
|
||||
|
||||
entry.innerHTML = `<span class="console-timestamp">[${timestamp}] ${prefix}</span> ${message}`;
|
||||
consoleDiv.appendChild(entry);
|
||||
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
||||
}
|
||||
|
||||
// Global state
|
||||
let authInitialized = false;
|
||||
|
||||
// Event listeners for authentication events
|
||||
window.addEventListener('nlAuth', (event) => {
|
||||
addConsoleEntry(`Authentication event: ${event.detail.type}`, 'success');
|
||||
if (event.detail.pubkey) {
|
||||
addConsoleEntry(`User authenticated: ${event.detail.pubkey}`, 'success');
|
||||
displayUserInfo(event.detail);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('nlLogout', (event) => {
|
||||
addConsoleEntry('User logged out', 'warning');
|
||||
hideUserInfo();
|
||||
});
|
||||
|
||||
window.addEventListener('nlAuthUrl', (event) => {
|
||||
addConsoleEntry(`Auth URL generated: ${event.detail.url}`, 'info');
|
||||
});
|
||||
|
||||
window.addEventListener('nlError', (event) => {
|
||||
addConsoleEntry(`Authentication error: ${event.detail.message}`, 'error');
|
||||
});
|
||||
|
||||
// Library load checking with retry
|
||||
function checkLibraryLoaded() {
|
||||
let attempts = 0;
|
||||
const maxAttempts = 50; // 5 seconds
|
||||
|
||||
const check = () => {
|
||||
if (window.NostrTools) {
|
||||
document.getElementById('dep-status').textContent = '✓ nostr-tools loaded successfully!';
|
||||
document.getElementById('dep-status').className = 'status success';
|
||||
}
|
||||
|
||||
if (window.NOSTR_LOGIN_LITE) {
|
||||
document.getElementById('lib-status').textContent = '✓ NOSTR_LOGIN_LITE loaded successfully!';
|
||||
document.getElementById('lib-status').className = 'status success';
|
||||
enableModalLaunch();
|
||||
} else {
|
||||
attempts++;
|
||||
if (attempts < maxAttempts) {
|
||||
setTimeout(check, 100);
|
||||
} else {
|
||||
document.getElementById('lib-status').textContent = '✗ Failed to load NOSTR_LOGIN_LITE';
|
||||
document.getElementById('lib-status').className = 'status error';
|
||||
addConsoleEntry('Bundle might have JavaScript errors - check browser console', 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
check();
|
||||
}
|
||||
|
||||
// Enable the modal launch button
|
||||
function enableModalLaunch() {
|
||||
const launchBtn = document.getElementById('launch-auth');
|
||||
launchBtn.disabled = false;
|
||||
launchBtn.textContent = '🚀 Launch Authentication Modal';
|
||||
addConsoleEntry('Full modal authentication ready', 'success');
|
||||
}
|
||||
|
||||
// Launch authentication modal
|
||||
async function launchAuthModal() {
|
||||
const launchBtn = document.getElementById('launch-auth');
|
||||
const status = document.getElementById('auth-status');
|
||||
|
||||
try {
|
||||
status.textContent = '🔄 Initializing authentication...';
|
||||
status.className = 'status warning';
|
||||
launchBtn.disabled = true;
|
||||
|
||||
// Initialize NOSTR_LOGIN_LITE with all methods enabled
|
||||
const options = {
|
||||
theme: 'dark',
|
||||
darkMode: false,
|
||||
relays: ['wss://relay.damus.io', 'wss://relay.nostr.band', 'wss://nos.lol'],
|
||||
methods: {
|
||||
extension: true,
|
||||
local: true,
|
||||
readonly: true,
|
||||
remote: true,
|
||||
otp: true
|
||||
},
|
||||
debug: true
|
||||
};
|
||||
|
||||
addConsoleEntry('Initializing NOSTR_LOGIN_LITE with full configuration', 'info');
|
||||
|
||||
if (!authInitialized) {
|
||||
await window.NOSTR_LOGIN_LITE.init(options);
|
||||
authInitialized = true;
|
||||
}
|
||||
|
||||
addConsoleEntry('Launching full authentication modal', 'info');
|
||||
status.textContent = '🎨 Opening authentication modal...';
|
||||
|
||||
// Launch the modal - this will show all available methods
|
||||
window.NOSTR_LOGIN_LITE.launch('login');
|
||||
|
||||
status.textContent = '✅ Authentication modal launched!';
|
||||
status.className = 'status success';
|
||||
|
||||
addConsoleEntry('Modal launched successfully - all login methods available', 'success');
|
||||
|
||||
// Re-enable button after a delay
|
||||
setTimeout(() => {
|
||||
launchBtn.disabled = false;
|
||||
launchBtn.textContent = '🔄 Launch Again';
|
||||
status.textContent = 'Ready to launch modal again...';
|
||||
status.className = 'status info';
|
||||
}, 3000);
|
||||
|
||||
} catch (error) {
|
||||
addConsoleEntry(`Modal launch failed: ${error.message}`, 'error');
|
||||
status.textContent = '❌ Failed to launch modal';
|
||||
status.className = 'status error';
|
||||
launchBtn.disabled = false;
|
||||
launchBtn.textContent = '🚀 Try Again';
|
||||
}
|
||||
}
|
||||
|
||||
// Display user information after successful authentication
|
||||
function displayUserInfo(details) {
|
||||
document.getElementById('user-info').style.display = 'block';
|
||||
document.getElementById('user-pubkey').textContent = details.pubkey || 'Unknown';
|
||||
document.getElementById('user-method').textContent = details.method || 'Unknown';
|
||||
document.getElementById('user-type').textContent = getAccountType(details.method);
|
||||
|
||||
const status = document.getElementById('auth-status');
|
||||
status.textContent = '✅ Successfully authenticated!';
|
||||
status.className = 'status success';
|
||||
}
|
||||
|
||||
// Hide user info on logout
|
||||
function hideUserInfo() {
|
||||
document.getElementById('user-info').style.display = 'none';
|
||||
|
||||
const status = document.getElementById('auth-status');
|
||||
status.textContent = '👋 User logged out';
|
||||
status.className = 'status warning';
|
||||
}
|
||||
|
||||
// Helper function to get readable account type
|
||||
function getAccountType(method) {
|
||||
const types = {
|
||||
extension: 'Browser Extension',
|
||||
local: 'Local Account',
|
||||
readonly: 'Read-Only Account',
|
||||
remote: 'NIP-46 Remote',
|
||||
otp: 'OTP Secured Local'
|
||||
};
|
||||
return types[method] || 'Unknown';
|
||||
}
|
||||
|
||||
// Initialize everything when DOM loads
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
addConsoleEntry('Demo page loaded, initializing libraries...', 'info');
|
||||
|
||||
// Check if libraries are loaded
|
||||
checkLibraryLoaded();
|
||||
|
||||
// Set up the modal launch button
|
||||
document.getElementById('launch-auth').addEventListener('click', launchAuthModal);
|
||||
});
|
||||
|
||||
// Debug logging
|
||||
console.log('NOSTR_LOGIN_LITE Modal Demo loaded');
|
||||
console.log('Available login methods will be shown in modal');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,838 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="" class="has-dark-text" dir="rtl">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>من الجميل في نوستر هي قدرة…</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://blossom.npubpro.com/9d9f5663fcc8ec87f256d3959fcd763ae234774d7cfcaf85a5ad10e3adea63e3.css"
|
||||
/>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/* The script for calculating the color contrast was taken from
|
||||
https://gomakethings.com/dynamically-changing-the-text-color-based-on-background-color-contrast-with-vanilla-js/ */
|
||||
var accentColor = getComputedStyle(
|
||||
document.documentElement,
|
||||
).getPropertyValue('--background-color')
|
||||
accentColor = accentColor.trim().slice(1)
|
||||
var r = parseInt(accentColor.substr(0, 2), 16)
|
||||
var g = parseInt(accentColor.substr(2, 2), 16)
|
||||
var b = parseInt(accentColor.substr(4, 2), 16)
|
||||
var yiq = (r * 299 + g * 587 + b * 114) / 1000
|
||||
var textColor = yiq >= 128 ? 'dark' : 'light'
|
||||
|
||||
document.documentElement.className = `has-${textColor}-text`
|
||||
</script>
|
||||
|
||||
<meta property="og:title" content="من الجميل في نوستر هي قدرة…" />
|
||||
<meta name="twitter:title" content="من الجميل في نوستر هي قدرة…" />
|
||||
|
||||
<meta
|
||||
name="description"
|
||||
content="من الجميل في نوستر هي قدرة التطبيقات على التخاطر.
|
||||
|
||||
إذا بتفضلوا استخدام مساحة مثل الDiscord، ضيفوا صفحتنا على flotilla.social للتواصل بصيغة التشات.
|
||||
|
||||
اضغط إضافة مساحة Add Space،
|
||||
ثم الدخول على مساحة Join a space،
|
||||
وضيفوا ريلاي relay.nostrarabia.com
|
||||
|
||||
نراكم. 🫡
|
||||
|
||||
#nostrarabia…"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="من الجميل في نوستر هي قدرة التطبيقات على التخاطر.
|
||||
|
||||
إذا بتفضلوا استخدام مساحة مثل الDiscord، ضيفوا صفحتنا على flotilla.social للتواصل بصيغة التشات.
|
||||
|
||||
اضغط إضافة مساحة Add Space،
|
||||
ثم الدخول على مساحة Join a space،
|
||||
وضيفوا ريلاي relay.nostrarabia.com
|
||||
|
||||
نراكم. 🫡
|
||||
|
||||
#nostrarabia…"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="من الجميل في نوستر هي قدرة التطبيقات على التخاطر.
|
||||
|
||||
إذا بتفضلوا استخدام مساحة مثل الDiscord، ضيفوا صفحتنا على flotilla.social للتواصل بصيغة التشات.
|
||||
|
||||
اضغط إضافة مساحة Add Space،
|
||||
ثم الدخول على مساحة Join a space،
|
||||
وضيفوا ريلاي relay.nostrarabia.com
|
||||
|
||||
نراكم. 🫡
|
||||
|
||||
#nostrarabia…"
|
||||
/>
|
||||
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://image.nostr.build/4afea744d3f72f4f7af1f7d632a8594159ef65f49b48cb00890c3904c57e1822.jpg"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://image.nostr.build/4afea744d3f72f4f7af1f7d632a8594159ef65f49b48cb00890c3904c57e1822.jpg"
|
||||
/>
|
||||
<meta name="twitter:image:alt" content="من الجميل في نوستر هي قدرة…" />
|
||||
|
||||
<link
|
||||
rel="canonical"
|
||||
href="https://nostrarabia.npub.pro/post/note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn/"
|
||||
/>
|
||||
<link
|
||||
rel="og:url"
|
||||
href="https://nostrarabia.npub.pro/post/note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn/"
|
||||
/>
|
||||
<meta property="og:site_name" content="نوستر" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@nostrprotocol" />
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
<!--
|
||||
***********************
|
||||
Powered by npub.pro
|
||||
***********************
|
||||
-->
|
||||
|
||||
<meta
|
||||
name="nostr:site"
|
||||
content="naddr1qqxkummnw3shywnzxg6kyv34qythwumn8ghj7un9d3shjtnwwp6kyurjduhxxmmdqgsgzt8ltta9m8pmgzptstrsxew06tf8nsn64yfrzwu0e07kt3q2a6crqsqqqaesldfhew"
|
||||
/>
|
||||
<meta name="author" content="نوستر بالعربي" />
|
||||
<meta
|
||||
name="nostr:author"
|
||||
content="npub1syk07kh6tkwrksyzhqk8qdjul5kj08p842gjxyacljlavhzq4m4slmdu3p"
|
||||
/>
|
||||
<meta
|
||||
name="nostr:id"
|
||||
content="note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn"
|
||||
/>
|
||||
<meta
|
||||
name="nostr:event_id"
|
||||
content="note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn"
|
||||
/>
|
||||
<link
|
||||
rel="manifest"
|
||||
href="https://nostrarabia.npub.pro/manifest.webmanifest"
|
||||
/>
|
||||
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.5.1.min.js"
|
||||
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
|
||||
<link
|
||||
rel="preload"
|
||||
as="style"
|
||||
href="https://cdn.jsdelivr.net/npm/venobox@2.1.8/dist/venobox.min.css"
|
||||
/>
|
||||
|
||||
<link
|
||||
rel="icon"
|
||||
href="https://image.nostr.build/4afea744d3f72f4f7af1f7d632a8594159ef65f49b48cb00890c3904c57e1822.jpg"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="https://image.nostr.build/4afea744d3f72f4f7af1f7d632a8594159ef65f49b48cb00890c3904c57e1822.jpg"
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
|
||||
<script>
|
||||
document.documentElement.setAttribute('dir', 'rtl')
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.np-oembed-video-link {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.np-oembed-video-link img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.np-oembed-video-link:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAJeUlEQVR4nO1daYwUVRD+9oBlV9kFJESjixxq8GAl3lEU1EXiwQ+RQwUPvIi3xgMVD7wxCgYTDwQFlxANakw0Jho1iqJoBEQMIuqqYHA9dleRBXZhZExpTeyU1T09M/1ed8/0l7xkd2be1a/7Vb2qr6qBBAkSJEiQoHjRA8AwABMA3AFgMYDlANYAaAbQDqCLSzt/Rt99xL+9g+sO47YS5IhKAIcDmAbgLQDbAaQDKjsBrAAwE0AjgKpkdXSU8wVqArAlwAXIVqiv5wCczGMoefTnu/VHi4uQdik0hgeBf8ZUctgfwDMAdvi8WM0AXgXwMIBLAZwA4BAAAwH0BtCNS2/+jL4bwb+lOq9xG376Ilk0H8B+KAEMAvA8gFSWi7IRwAIA5wHYJ8D+qa3zASz08VSmWCmgBS460N17LYAOjwuwmWUIyZIyS+Mi5WEOgFaPcW0DMKOYFIBTAHztMeG1fNdWhzjGah7DWo9xruebJdbqK91Zf7lMcDVfhApEB2UAxgD4xGXMu/iJ6o6YYYDHpDYBmIjo4xweqzYHOpjui5jgVAC/uxzKZgPoifigFsCjPHY5H7IKjEbEcb6LKksy5DDEF4cD+EaZF811MiKKa1zkxcsAeiH+6MkquyZXbkLEcJ/LFnU5ig9Xumxh9yIiuEoZXCeAsShejAGwVZn3DWEPbJKyTZFAH47ix9EAflO2rwvD1KZ2KJpHA0oHDTxnKeita19kEW1TTAyl8GRoT4o0CbXzWcyaXeojxRB3JkoXZyiC/hNbJ/rZijArRm0qCOWGTP9GMYoFl7PTF0x3GiO8qAh58kYaAZmfvxIdfsPmhQT/HR7XK1YKI6b7O5WDX5DmkNMAfAngAwBnIb44UnHA3WbC07dNdEKyJChUKAbJd5m6E0fMEXPZGrTWJW04mwK22vZ1MXOnADwNoB/ihToALWIui4Jq/ADlEQzan+G2IGkufwC4MWaOoUnKFj84iIafFQ2vMuDz3iPLgqQdSsR4xAcrxfjpaS/4RC7NIyaMhn1EH9sVjc5Z3mKqT9QxXqEY1RfS4EzR4FpDDL/eiumhG4DrFFuRcwt4gre7qIKu1Tox7gcKaUxyl8gjaAK9RD+/O77ryxfejcvVzgtHCxhFXCjGuyHfm3qUwpuqgTmtJC2EuMQQAK97bGNfR1S+VPN8nGM9KZ+GmkQj82AOtcriu6ExC3/qbQBDES0sEGOk/3NCd4WFTnxaU+gp+vrTx/huUu68TNnBhzNSFqKAk5QbLqct9niFa2uS3rm76G+Lz3r9WJV0I+O1sg+cSHtholzheB2XSwMzRGU6i5jEbqK/jhzrHwjgDY9tjFTo0xEupAi4PZfKS0VlYqGbRI1i+8mXfNCc5fxyMMLBFDGWd3LRCjpF5SBDAtz6TDsKGTLzBcUR3uoRiUWHs1khcMX6K4dfXzGPw0RFuuNMo4cy2EKxF2+1bvLlVwBTLZO9vxdj8EUImSgqUeSSaVSJPjsDbJt8Nu97bGPrmEFjA/IcNS4fR5RxvzCrsWmxrQQNki/feSzMa0FZY3PgI0z3U2mxqHQJzKObco4wJaum8TnH6/xClgMTmCr6I80rKz4UlSh40jQqFcOhSezNDiNJ2MiUnwFcbMCQeqLoh9zVWfGFqGRDTawQfaZgB0cAWOaxja0K2ELRINr/3E+lH0QlG5FC5aLPv2APZWyU/CGLfCFeQaEYKNolmZYVMiKVvHk2LkraUWgrsQ2yFtyjkDmcqvjYgD2jRNbOii5RyZYfOy1KWOjP5D9tUcimZ129D2tBdol+bcWqawfjpR5nFusLEsaWBeVEXQ67IM/kkx6eyZ+Y6W59ywpDqIe5IJUALlOCb+TZpDYsoR6G2gvlzqyw0OdIVj29rMMHha32LgvhYAiFbtTNYF8DFKa6s6xnrjGicDAMw3Ria0Gq2XTiZprvMJxoJi/TSRjGRRva3RjF/O0899DF2RMRNC6GYX6H4hSrCqhdog+96bE9UQ7GYxFh8/uhITiooJyQqwNQMR/3UGNbmMRm87wjn9Chfr138m4tiI/qE1tFn/mS8kg7u0KJEs6ULt6GbUd+7auYYnzvAu9ZopAGvSAjfKixYZEcLsqX5EC4q1CmXR7oEH2SsS8X/0aTh38jCmEMi/IR6BkMF5V/tLDX/in69BOhVc03j5Z3JM1t3hyBQJ9yNr04x5aTItFduUCmD4ibRX/Z9vhxHj6MXZwgmZgnUcDJhVJJwRNyNkK5bKOwIEOysBRX5krTtICFYoyUszhnNFoMR4BCnK5TgnrmuOSqSrOV+tqIJdUEXzO525ANLdIBO1DConvz5xWcusMtt+4OZiKaYopEJmAHnAvdRkgblNC1PkwwWO2xPb3BW1icQtruL6TBesXGZCrLQptCLEh7qLFkm4o6JohxdwbBk54vGv3MkArc5rEAmbKVrbFxeDFLmRIW/VQQDQ9WBOnZCB6tHgtBauySmL1K4jwxh1SQb1yQPpKfDNiCfo2ANTYo1DH7MWffRy6+YGmNpYzPQWKFaP8Xdo7F8c03j4m5dJh4uqeLTnZyxucg3ZstfB6ZHePky0cpJv9bTHRUpSTnCjqBWVkEAjQLQS8lpO5Lk7a0RsWiSsI2wb94SVFG6Mk3ikcUwUuhx6WOa5Tr8pCNjiuVGJJUkacWz4YxytHgY5v5V+qVc8M2TjhQajhG8cdYTaScAaXTTlKN4382uC5O3BPaa4G0ZPzHl3Ay/gvCHtiVijDrjHmqVz8yQwvsuR4RwT3K4HZy+u1i1KZSynzvRsRwtUvWhFcczqY4o9YlsiqSrzzKYLLLS8G+5YhXxNgc0qzMiwT4uYg4Rrv4NlKGg/JNoI4NhdoW1RqmNpUPbXK5izm9hZMMhxVD6Adl7M+Q2akz5UNL9Fqrr15dE8FXr5azBvVpllevRjUDqm+SmFdS5HXMzKgJcYw1nGBMEhKk1da4odD267vdIpcyppclfIdWWHoaiDI71yMZjdOPXzSv73ZiAJOO3chuaYeLuImfnP4By7YpPAbJtdXOUU0x8+MXRJyYp1CM0i7le44+msUhzCM5onUQ87e6c+nDnzXwb6Zyndc9wtk0VXZuQLlNYod6Jo5t9Hmx0gbLBh6L6fySsUA5JxteqJCuTZbNnJdxZEzJFFZQweSJaRzxtD3ABdjJ7JaZ7I4OO24klqhimTCemS+LOLnBajZntPG+38V/N/N3y1goT+dYkoZi1ZQSJEiQIEECwt/UKk4KqlBwIwAAAABJRU5ErkJggg==');
|
||||
background-size: cover;
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
:root {
|
||||
--ghost-accent-color: #139ad4;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.npubpro.com/maptalks.css" />
|
||||
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="نوستر"
|
||||
href="https://nostrarabia.npub.pro/rss/"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300..800&display=swap"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300..800&display=swap"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
as="script"
|
||||
href="https://blossom.npubpro.com/64f7c3c5de348a7d1a5c7d1519abfa33fec8c5442c583fda441d25cd7b5990cf.js"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
as="style"
|
||||
href="https://blossom.npubpro.com/8c397e35e20c7e4c6e163f0d17511294a2fba1c1a13823b91e2c220c3c38e83b.css"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
as="style"
|
||||
href="https://blossom.npubpro.com/9d9f5663fcc8ec87f256d3959fcd763ae234774d7cfcaf85a5ad10e3adea63e3.css"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
as="script"
|
||||
href="https://blossom.npubpro.com/b8292724b60caed6133f097c3f0427163e93e87724da84861192e1322d4146f5.js"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
as="script"
|
||||
href="https://blossom.npubpro.com/b866f2b384af7668032c0c2fd523717520537bf812de9870cf1a960b11087333.js"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
as="style"
|
||||
href="https://blossom.npubpro.com/eb267981d379bed63595b7ebd1dd6cb775c912a6f9b0f16c2faee5050afbf381.css"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body
|
||||
class="post-template tag-nostrarabia is-head-left-logo has-parallax-feed is-dropdown-loaded"
|
||||
style="overflow: initial"
|
||||
>
|
||||
<div class="gh-site">
|
||||
<header id="gh-head" class="gh-head gh-outer">
|
||||
<div class="gh-head-inner gh-inner">
|
||||
<div class="gh-head-brand">
|
||||
<div class="gh-head-brand-wrapper">
|
||||
<a
|
||||
class="gh-head-logo"
|
||||
href="https://nostrarabia.npub.pro"
|
||||
>
|
||||
<img
|
||||
src="https://image.nostr.build/4afea744d3f72f4f7af1f7d632a8594159ef65f49b48cb00890c3904c57e1822.jpg"
|
||||
alt="نوستر"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<button
|
||||
class="gh-search gh-icon-btn"
|
||||
aria-label="Search this site"
|
||||
data-ghost-search=""
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.5 17.5L12.5 12.5L17.5 17.5ZM14.1667 8.33333C14.1667 9.09938 14.0158 9.85792 13.7226 10.5657C13.4295 11.2734 12.9998 11.9164 12.4581 12.4581C11.9164 12.9998 11.2734 13.4295 10.5657 13.7226C9.85792 14.0158 9.09938 14.1667 8.33333 14.1667C7.56729 14.1667 6.80875 14.0158 6.10101 13.7226C5.39328 13.4295 4.75022 12.9998 4.20854 12.4581C3.66687 11.9164 3.23719 11.2734 2.94404 10.5657C2.65088 9.85792 2.5 9.09938 2.5 8.33333C2.5 6.78624 3.11458 5.30251 4.20854 4.20854C5.30251 3.11458 6.78624 2.5 8.33333 2.5C9.88043 2.5 11.3642 3.11458 12.4581 4.20854C13.5521 5.30251 14.1667 6.78624 14.1667 8.33333Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="gh-burger"></button>
|
||||
</div>
|
||||
|
||||
<nav class="gh-head-menu">
|
||||
<ul class="nav">
|
||||
<li class="nav-alreysyh">
|
||||
<a href="https://nostrarabia.npub.pro/"
|
||||
>الرئيسية</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-nwstr-arby">
|
||||
<a
|
||||
href="https://nostrarabia.npub.pro/tag/nostrarabia/"
|
||||
>نوستر عربي</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-talm-albytkwyn">
|
||||
<a
|
||||
href="https://nostrarabia.npub.pro/tag/learnbitcoin/"
|
||||
>تعلّم البيتكوين</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-talm-alaqtsad-alnmsawy">
|
||||
<a
|
||||
href="https://nostrarabia.npub.pro/tag/Learnaustrianeconomics/"
|
||||
>تعلّم الاقتصاد النمساوي</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-alaadhaa">
|
||||
<a
|
||||
href="https://nostrarabia.npub.pro/tag/community/"
|
||||
>الأعضاء</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-akhbar">
|
||||
<a href="https://nostrarabia.npub.pro/tag/news/"
|
||||
>أخبار</a
|
||||
>
|
||||
</li>
|
||||
|
||||
<button
|
||||
class="nav-more-toggle gh-icon-btn"
|
||||
aria-label="More"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 32 32"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M21.333 16c0-1.473 1.194-2.667 2.667-2.667v0c1.473 0 2.667 1.194 2.667 2.667v0c0 1.473-1.194 2.667-2.667 2.667v0c-1.473 0-2.667-1.194-2.667-2.667v0zM13.333 16c0-1.473 1.194-2.667 2.667-2.667v0c1.473 0 2.667 1.194 2.667 2.667v0c0 1.473-1.194 2.667-2.667 2.667v0c-1.473 0-2.667-1.194-2.667-2.667v0zM5.333 16c0-1.473 1.194-2.667 2.667-2.667v0c1.473 0 2.667 1.194 2.667 2.667v0c0 1.473-1.194 2.667-2.667 2.667v0c-1.473 0-2.667-1.194-2.667-2.667v0z"
|
||||
></path>
|
||||
</svg>
|
||||
<div class="gh-dropdown">
|
||||
<li class="nav-mymz">
|
||||
<a
|
||||
href="https://nostrarabia.npub.pro/tag/memes/"
|
||||
>ميمز</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-adwat">
|
||||
<a
|
||||
href="https://nostrarabia.npub.pro/tag/tools/"
|
||||
>أدوات</a
|
||||
>
|
||||
</li>
|
||||
<li class="nav-idamna">
|
||||
<a
|
||||
href="https://nostrarabia.npub.pro/tag/supportus/"
|
||||
>إدعمنا</a
|
||||
>
|
||||
</li>
|
||||
</div>
|
||||
</button>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div class="gh-head-actions">
|
||||
<button
|
||||
class="gh-search gh-icon-btn"
|
||||
aria-label="Search this site"
|
||||
data-ghost-search=""
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M17.5 17.5L12.5 12.5L17.5 17.5ZM14.1667 8.33333C14.1667 9.09938 14.0158 9.85792 13.7226 10.5657C13.4295 11.2734 12.9998 11.9164 12.4581 12.4581C11.9164 12.9998 11.2734 13.4295 10.5657 13.7226C9.85792 14.0158 9.09938 14.1667 8.33333 14.1667C7.56729 14.1667 6.80875 14.0158 6.10101 13.7226C5.39328 13.4295 4.75022 12.9998 4.20854 12.4581C3.66687 11.9164 3.23719 11.2734 2.94404 10.5657C2.65088 9.85792 2.5 9.09938 2.5 8.33333C2.5 6.78624 3.11458 5.30251 4.20854 4.20854C5.30251 3.11458 6.78624 2.5 8.33333 2.5C9.88043 2.5 11.3642 3.11458 12.4581 4.20854C13.5521 5.30251 14.1667 6.78624 14.1667 8.33333Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="gh-main gh-outer">
|
||||
<div class="gh-inner">
|
||||
<article class="gh-article post tag-nostrarabia no-image">
|
||||
<section class="gh-content gh-canvas">
|
||||
<np-content
|
||||
event="note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn"
|
||||
><p>
|
||||
من الجميل في نوستر هي قدرة التطبيقات على
|
||||
التخاطر.<br /><br />إذا بتفضلوا استخدام
|
||||
مساحة مثل الDiscord، ضيفوا صفحتنا على
|
||||
flotilla.social للتواصل بصيغة التشات.
|
||||
<br /><br />اضغط إضافة مساحة Add Space،<br />ثم
|
||||
الدخول على مساحة Join a space،<br />وضيفوا
|
||||
ريلاي relay.nostrarabia.com<br /><br />نراكم.
|
||||
🫡<br /><br /><a href="/tag/nostrarabia/"
|
||||
>#nostrarabia</a
|
||||
>
|
||||
</p>
|
||||
</np-content>
|
||||
<style>
|
||||
np-map {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
np-content p {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
np-content h1 {
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
np-content h2 {
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
np-content h3 {
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
np-content h4 {
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
np-content h5 {
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
np-content h6 {
|
||||
margin-top: 4rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
</style>
|
||||
<np-content-cta
|
||||
data-cta-list="zap,like,share"
|
||||
data-cta-main="zap"
|
||||
data-button-color="#139ad4"
|
||||
data-text-button-color="#fff"
|
||||
data-event-addr="note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn"
|
||||
data-event-id="note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn"
|
||||
data-author-npub="npub1syk07kh6tkwrksyzhqk8qdjul5kj08p842gjxyacljlavhzq4m4slmdu3p"
|
||||
id="np-content-cta"
|
||||
></np-content-cta>
|
||||
<div
|
||||
style="display: none"
|
||||
id="zap-button"
|
||||
data-anon=""
|
||||
data-npub="npub1syk07kh6tkwrksyzhqk8qdjul5kj08p842gjxyacljlavhzq4m4slmdu3p"
|
||||
data-note-id="note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn"
|
||||
data-relays="wss://purplepag.es/,wss://user.kindpag.es/,wss://relay.nos.social/,wss://relay.primal.net/,wss://relay.damus.io/,wss://relay.nostrarabia.com/"
|
||||
data-button-color="#139ad4"
|
||||
data-amount=""
|
||||
></div>
|
||||
<np-content-comments
|
||||
data-id="note1dplcmt2885l9tsy6c4xenm3w7hrg92af42sz76p0ypd7ft0muvpqg80sqn"
|
||||
data-relays="wss://purplepag.es/,wss://user.kindpag.es/,wss://relay.nos.social/,wss://relay.primal.net/,wss://relay.damus.io/,wss://relay.nostrarabia.com/"
|
||||
data-client="nostrarabia.npub.pro"
|
||||
></np-content-comments
|
||||
><np-content-dm
|
||||
data-peer-npub="npub1syk07kh6tkwrksyzhqk8qdjul5kj08p842gjxyacljlavhzq4m4slmdu3p"
|
||||
data-relays="wss://purplepag.es/,wss://user.kindpag.es/,wss://relay.nos.social/,wss://relay.primal.net/,wss://relay.damus.io/,wss://relay.nostrarabia.com/"
|
||||
></np-content-dm>
|
||||
<header class="gh-article-header">
|
||||
<h1 class="gh-article-title">
|
||||
من الجميل في نوستر هي قدرة…
|
||||
</h1>
|
||||
</header>
|
||||
<aside class="gh-article-meta">
|
||||
<div class="gh-article-meta-inner">
|
||||
<figure class="gh-author-image">
|
||||
<img
|
||||
src="https://mir-s3-cdn-cf.behance.net/projects/404/01ffa529646977.55fca90cee60d.jpg"
|
||||
alt="نوستر بالعربي"
|
||||
/>
|
||||
</figure>
|
||||
<div class="gh-article-meta-wrapper">
|
||||
<h4 class="gh-author-name">
|
||||
<a
|
||||
href="/author/npub1syk07kh6tkwrksyzhqk8qdjul5kj08p842gjxyacljlavhzq4m4slmdu3p/"
|
||||
>نوستر بالعربي</a
|
||||
>
|
||||
</h4>
|
||||
<time
|
||||
class="gh-article-date"
|
||||
datetime="2024-12-01"
|
||||
>Dec 01, 2024</time
|
||||
>
|
||||
</div>
|
||||
<!-- <a
|
||||
class="gh-article-tag"
|
||||
href="/tag/nostrarabia/"
|
||||
style="--tag-color:"
|
||||
>nostrarabia</a
|
||||
> -->
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<footer class="gh-article-footer gh-canvas">
|
||||
<nav class="gh-navigation">
|
||||
<div class="gh-navigation-previous">
|
||||
<a
|
||||
class="gh-navigation-link"
|
||||
href="/post/1733010811046/"
|
||||
>← Previous</a
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="gh-navigation-middle"></div>
|
||||
|
||||
<div class="gh-navigation-next">
|
||||
<a
|
||||
class="gh-navigation-link"
|
||||
href="/post/1733278250434/"
|
||||
>Next →</a
|
||||
>
|
||||
</div>
|
||||
</nav>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="gh-foot gh-outer">
|
||||
<div class="gh-foot-inner gh-inner">
|
||||
<nav class="gh-foot-menu"></nav>
|
||||
|
||||
<div class="gh-copyright">
|
||||
نوستر © 2024. Powered by
|
||||
<a
|
||||
href="https://npub.pro/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Npub.pro</a
|
||||
>. Theme by
|
||||
<a
|
||||
href="https://ghost.org/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Ghost</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="pswp__bg"></div>
|
||||
|
||||
<div class="pswp__scroll-wrap">
|
||||
<div class="pswp__container">
|
||||
<div class="pswp__item"></div>
|
||||
<div class="pswp__item"></div>
|
||||
<div class="pswp__item"></div>
|
||||
</div>
|
||||
|
||||
<div class="pswp__ui pswp__ui--hidden">
|
||||
<div class="pswp__top-bar">
|
||||
<div class="pswp__counter"></div>
|
||||
|
||||
<button
|
||||
class="pswp__button pswp__button--close"
|
||||
title="Close (Esc)"
|
||||
></button>
|
||||
<button
|
||||
class="pswp__button pswp__button--share"
|
||||
title="Share"
|
||||
></button>
|
||||
<button
|
||||
class="pswp__button pswp__button--fs"
|
||||
title="Toggle fullscreen"
|
||||
></button>
|
||||
<button
|
||||
class="pswp__button pswp__button--zoom"
|
||||
title="Zoom in/out"
|
||||
></button>
|
||||
|
||||
<div class="pswp__preloader">
|
||||
<div class="pswp__preloader__icn">
|
||||
<div class="pswp__preloader__cut">
|
||||
<div class="pswp__preloader__donut"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap"
|
||||
>
|
||||
<div class="pswp__share-tooltip"></div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="pswp__button pswp__button--arrow--left"
|
||||
title="Previous (arrow left)"
|
||||
></button>
|
||||
<button
|
||||
class="pswp__button pswp__button--arrow--right"
|
||||
title="Next (arrow right)"
|
||||
></button>
|
||||
|
||||
<div class="pswp__caption">
|
||||
<div class="pswp__caption__center"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://blossom.npubpro.com/64f7c3c5de348a7d1a5c7d1519abfa33fec8c5442c583fda441d25cd7b5990cf.js"></script>
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/venobox@2.1.8/dist/venobox.min.css"
|
||||
type="text/css"
|
||||
media="screen"
|
||||
/>
|
||||
<script>
|
||||
;(() => {
|
||||
const script = document.createElement('script')
|
||||
script.async = true
|
||||
script.type = 'text/javascript'
|
||||
script.src =
|
||||
'https://cdn.jsdelivr.net/npm/venobox@2.1.8/dist/venobox.min.js'
|
||||
script.onload = () => {
|
||||
new VenoBox({
|
||||
selector: '.vbx-media',
|
||||
spinColor: '#139ad4',
|
||||
overlayColor: '#139ad4',
|
||||
})
|
||||
}
|
||||
document.body.appendChild(script)
|
||||
})()
|
||||
</script>
|
||||
<script
|
||||
async=""
|
||||
type="text/javascript"
|
||||
src="https://cdn.jsdelivr.net/npm/venobox@2.1.8/dist/venobox.min.js"
|
||||
></script>
|
||||
|
||||
<script
|
||||
type="text/javascript"
|
||||
async=""
|
||||
src="https://cdn.npubpro.com/zapthreads.iife.0.6.0.js"
|
||||
></script>
|
||||
<script
|
||||
type="text/javascript"
|
||||
async=""
|
||||
src="https://cdn.npubpro.com/nostr-site-zapthreads.1.0.2.js"
|
||||
></script>
|
||||
|
||||
<script
|
||||
type="text/javascript"
|
||||
async=""
|
||||
src="https://cdn.npubpro.com/content-cta.iife.1.0.22.js"
|
||||
></script>
|
||||
|
||||
<script
|
||||
async=""
|
||||
src="../packages/auth/dist/unpkg.js"
|
||||
data-perms="sign_event:1,sign_event:7,sign_event:3,sign_event:9734,sign_event:10003,sign_event:9802,nip04_encrypt,nip04_decrypt"
|
||||
data-start-screen="local-signup"
|
||||
data-signup-relays="wss://relay.primal.net/,wss://relay.damus.io/,wss://relay.nostrarabia.com/"
|
||||
></script>
|
||||
<script>
|
||||
;(async () => {
|
||||
if (!window.nostrSite)
|
||||
await new Promise((ok) =>
|
||||
document.addEventListener('npLoad', ok),
|
||||
)
|
||||
const ep = window.nostrSite.plugins.register('nostr-login')
|
||||
document.addEventListener('nlAuth', async (e) => {
|
||||
console.log('nlAuth', e)
|
||||
ep.dispatch('auth', {
|
||||
type: e.detail.type,
|
||||
pubkey: e.detail.pubkey,
|
||||
})
|
||||
|
||||
if (
|
||||
e.detail.type === 'login' ||
|
||||
e.detail.type === 'signup'
|
||||
) {
|
||||
window.__nlAuthed = true
|
||||
} else {
|
||||
window.__nlAuthed = false
|
||||
}
|
||||
|
||||
const npub = window.nostrSite.nostrTools.nip19.npubEncode(
|
||||
await window.nostr.getPublicKey(),
|
||||
)
|
||||
const zapButton = document.querySelector('#zap-button')
|
||||
if (zapButton) {
|
||||
if (window.__nlAuthed)
|
||||
zapButton.setAttribute('data-anon', '')
|
||||
else zapButton.setAttribute('data-anon', 'true')
|
||||
}
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.npubpro.com/nostr-zap.0.22.2.js"></script>
|
||||
<div></div>
|
||||
<np-content-cta-selection></np-content-cta-selection>
|
||||
<script>
|
||||
;(async () => {
|
||||
if (!window.nostrSite)
|
||||
await new Promise((ok) =>
|
||||
document.addEventListener('npLoad', ok),
|
||||
)
|
||||
const ep = window.nostrSite.plugins.register('nostr-zap')
|
||||
console.log('nostr-zap ep', ep)
|
||||
ep.subscribe('action-zap', (amount) => {
|
||||
const button = document.querySelector('#zap-button')
|
||||
button.setAttribute('data-amount', amount || '')
|
||||
button.dispatchEvent(new Event('click'))
|
||||
})
|
||||
document.addEventListener('nostr-zap-published', (e) => {
|
||||
console.log('nostr-zap on zap published', e)
|
||||
ep.dispatch('event-published', e.detail)
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
|
||||
<script
|
||||
async=""
|
||||
src="https://unpkg.com/nostr-site-search@1.0.12/dist/index.js"
|
||||
></script>
|
||||
<script>
|
||||
document.addEventListener('np-search-goto', (e) => {
|
||||
console.log('np-search-goto', e)
|
||||
window.location.href = e.detail
|
||||
})
|
||||
</script>
|
||||
<script
|
||||
async=""
|
||||
src="https://cdn.npubpro.com/embeds.iife.1.0.4.js"
|
||||
></script>
|
||||
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.npubpro.com/maptalks.min.js"
|
||||
></script>
|
||||
<script>
|
||||
const container = document.querySelector('np-map')
|
||||
console.log('map', container)
|
||||
if (container) {
|
||||
const coords = container
|
||||
.getAttribute('coords')
|
||||
.split(',')
|
||||
.map((c) => Number(c))
|
||||
console.log('coords', coords)
|
||||
const div = document.createElement('div')
|
||||
div.style.width = '100%'
|
||||
div.style.height = '300px'
|
||||
container.append(div)
|
||||
const map = new maptalks.Map(div, {
|
||||
center: coords,
|
||||
zoom: 15,
|
||||
zoomControl: true, // add zoom control
|
||||
scaleControl: true, // add scale control
|
||||
baseLayer: new maptalks.TileLayer('base', {
|
||||
// urlTemplate: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
|
||||
urlTemplate:
|
||||
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
subdomains: ['a', 'b', 'c'], // "d"
|
||||
attribution:
|
||||
'© <a href="http://osm.org">OpenStreetMap</a>',
|
||||
}),
|
||||
})
|
||||
|
||||
const point = new maptalks.Marker(coords, {
|
||||
visible: true,
|
||||
editable: false,
|
||||
cursor: 'pointer',
|
||||
draggable: false,
|
||||
// symbol : {
|
||||
// 'textFaceName' : 'sans-serif',
|
||||
// 'textName' : 'MapTalks',
|
||||
// 'textFill' : '#34495e',
|
||||
// 'textHorizontalAlignment' : 'right',
|
||||
// 'textSize' : 40
|
||||
// }
|
||||
})
|
||||
point.on('click touchend', (e) => console.log(e))
|
||||
|
||||
new maptalks.VectorLayer('vector', point).addTo(map)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="https://cdn.npubpro.com/index.js"
|
||||
onload="window.nostrSite.startTab();"
|
||||
></script>
|
||||
|
||||
<style>
|
||||
#pwa-toast {
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 16px;
|
||||
padding: 12px;
|
||||
border: 1px solid #8885;
|
||||
border-radius: 4px;
|
||||
z-index: 1;
|
||||
text-align: left;
|
||||
box-shadow: 3px 4px 5px 0 #8885;
|
||||
display: grid;
|
||||
background-color: #fff;
|
||||
}
|
||||
#pwa-toast .message {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
#pwa-toast .buttons {
|
||||
display: flex;
|
||||
}
|
||||
#pwa-toast button {
|
||||
border: 1px solid #8885;
|
||||
outline: none;
|
||||
margin-right: 5px;
|
||||
border-radius: 2px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
#pwa-toast.show {
|
||||
visibility: visible;
|
||||
}
|
||||
button#pwa-refresh {
|
||||
display: none;
|
||||
}
|
||||
#pwa-toast.show.refresh button#pwa-refresh {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<iframe
|
||||
width="0"
|
||||
height="0"
|
||||
border="0"
|
||||
id="__nostr-login-worker-iframe-3-use-nsec-app"
|
||||
style="display: none"
|
||||
src="https://3.use.nsec.app/iframe"
|
||||
></iframe
|
||||
><deepl-input-controller></deepl-input-controller>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,343 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nostr Login Interactive Test</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
.controls { background: #f5f5f5; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
|
||||
.control-group { margin-bottom: 15px; }
|
||||
.control-group label { display: inline-block; width: 150px; font-weight: bold; }
|
||||
.control-group input, .control-group select { margin-left: 10px; padding: 5px; }
|
||||
.checkbox-group { display: flex; gap: 10px; flex-wrap: wrap; }
|
||||
.checkbox-group label { width: auto; margin-right: 15px; }
|
||||
.current-config { background: #e8f4fd; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
|
||||
.login-area { background: #fff; border: 2px solid #ddd; padding: 20px; border-radius: 8px; }
|
||||
button { padding: 10px 15px; margin: 5px; cursor: pointer; }
|
||||
.apply-btn { background: #007cba; color: white; border: none; border-radius: 5px; }
|
||||
.reset-btn { background: #999; color: white; border: none; border-radius: 5px; }
|
||||
.note { background: #fff3cd; padding: 10px; border-radius: 5px; margin-top: 10px; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Nostr Login Interactive Test</h1>
|
||||
|
||||
<!-- Controls Section -->
|
||||
<div class="controls">
|
||||
<h3>Configuration Options</h3>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Theme:</label>
|
||||
<select id="themeSelect">
|
||||
<option value="default">Default</option>
|
||||
<option value="ocean">Ocean</option>
|
||||
<option value="lemonade">Lemonade</option>
|
||||
<option value="purple">Purple</option>
|
||||
<option value="laan">Laan</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Dark Mode:</label>
|
||||
<input type="checkbox" id="darkModeCheck">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Start Screen:</label>
|
||||
<select id="startScreenSelect">
|
||||
<option value="">Default</option>
|
||||
<option value="welcome">Welcome</option>
|
||||
<option value="welcome-login">Welcome Login</option>
|
||||
<option value="welcome-signup">Welcome Signup</option>
|
||||
<option value="signup">Signup</option>
|
||||
<option value="local-signup">Local Signup</option>
|
||||
<option value="login">Login</option>
|
||||
<option value="connect">Connect</option>
|
||||
<option value="login-bunker-url">Login Bunker URL</option>
|
||||
<option value="login-read-only">Login Read Only</option>
|
||||
<option value="switch-account">Switch Account</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Auth Methods:</label>
|
||||
<div class="checkbox-group">
|
||||
<label><input type="checkbox" value="connect" checked> Connect (NIP-46)</label>
|
||||
<label><input type="checkbox" value="extension" checked> Extension</label>
|
||||
<label><input type="checkbox" value="readOnly" checked> Read Only</label>
|
||||
<label><input type="checkbox" value="local" checked> Local</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>No Banner:</label>
|
||||
<input type="checkbox" id="noBannerCheck">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Bunkers:</label>
|
||||
<input type="text" id="bunkersInput" placeholder="e.g., nsec.app,highlighter.com" style="width: 300px;">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Permissions:</label>
|
||||
<input type="text" id="permsInput" placeholder="e.g., sign_event:1,nip04_encrypt" style="width: 300px;">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Title:</label>
|
||||
<input type="text" id="titleInput" placeholder="Custom welcome title" style="width: 300px;">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>Description:</label>
|
||||
<input type="text" id="descriptionInput" placeholder="Custom welcome description" style="width: 300px;">
|
||||
</div>
|
||||
|
||||
<button class="apply-btn" onclick="applyConfig()">Apply Configuration</button>
|
||||
<button class="reset-btn" onclick="resetConfig()">Reset to Defaults</button>
|
||||
|
||||
<div class="note">
|
||||
<strong>Note:</strong> Configuration changes will be applied by reloading the page with new settings.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Configuration Display -->
|
||||
<div class="current-config">
|
||||
<h4>Current Configuration:</h4>
|
||||
<pre id="configDisplay"></pre>
|
||||
</div>
|
||||
|
||||
<!-- Login/User Section -->
|
||||
<div class="login-area">
|
||||
<!-- Login Section -->
|
||||
<div id="loginSection">
|
||||
<h3>Test Login</h3>
|
||||
<button id="loginBtn">Login with Nostr</button>
|
||||
<button onclick="launchSpecificScreen()">Launch with Start Screen</button>
|
||||
</div>
|
||||
|
||||
<!-- User Info Section (hidden initially) -->
|
||||
<div id="userSection" style="display: none;">
|
||||
<h3>Welcome!</h3>
|
||||
<p><strong>Your Public Key (hex):</strong></p>
|
||||
<div id="pubkeyHex" style="word-break: break-all; background: #f0f0f0; padding: 10px; border-radius: 4px;"></div>
|
||||
<br>
|
||||
<button id="logoutBtn">Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic script loading with configuration -->
|
||||
<script>
|
||||
let currentConfig = {};
|
||||
let scriptLoaded = false;
|
||||
|
||||
// Load nostr-login with current configuration
|
||||
function loadNostrLogin() {
|
||||
if (scriptLoaded) return;
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = '../packages/auth/dist/unpkg.js';
|
||||
|
||||
// Apply configuration as data attributes
|
||||
const config = getStoredConfig();
|
||||
if (config.theme) script.setAttribute('data-theme', config.theme);
|
||||
if (config.darkMode !== undefined) script.setAttribute('data-dark-mode', config.darkMode);
|
||||
if (config.startScreen) script.setAttribute('data-start-screen', config.startScreen);
|
||||
if (config.noBanner !== undefined) script.setAttribute('data-no-banner', config.noBanner);
|
||||
if (config.bunkers) script.setAttribute('data-bunkers', config.bunkers);
|
||||
if (config.perms) script.setAttribute('data-perms', config.perms);
|
||||
if (config.title) script.setAttribute('data-title', config.title);
|
||||
if (config.description) script.setAttribute('data-description', config.description);
|
||||
if (config.methods && config.methods.length > 0) {
|
||||
script.setAttribute('data-methods', config.methods.join(','));
|
||||
}
|
||||
|
||||
document.head.appendChild(script);
|
||||
scriptLoaded = true;
|
||||
|
||||
// Wait for script to load, then set up event listeners
|
||||
script.onload = () => {
|
||||
setupEventListeners();
|
||||
};
|
||||
}
|
||||
|
||||
// Get stored configuration from localStorage
|
||||
function getStoredConfig() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('nostrLoginConfig') || '{}');
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Store configuration in localStorage
|
||||
function storeConfig(config) {
|
||||
localStorage.setItem('nostrLoginConfig', JSON.stringify(config));
|
||||
}
|
||||
|
||||
// Listen for nostr-login auth events
|
||||
function setupEventListeners() {
|
||||
document.addEventListener('nlAuth', async (e) => {
|
||||
console.log('nlAuth event:', e.detail);
|
||||
if (e.detail.type === 'login' || e.detail.type === 'signup') {
|
||||
await showUserInfo();
|
||||
} else if (e.detail.type === 'logout') {
|
||||
showLoginSection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Get and display user info
|
||||
async function showUserInfo() {
|
||||
try {
|
||||
const pubkey = await window.nostr.getPublicKey();
|
||||
console.log('Got pubkey:', pubkey);
|
||||
|
||||
// Display pubkey
|
||||
document.getElementById('pubkeyHex').textContent = pubkey;
|
||||
|
||||
// Hide login section, show user section
|
||||
document.getElementById('loginSection').style.display = 'none';
|
||||
document.getElementById('userSection').style.display = 'block';
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to get pubkey:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Show login section
|
||||
function showLoginSection() {
|
||||
document.getElementById('loginSection').style.display = 'block';
|
||||
document.getElementById('userSection').style.display = 'none';
|
||||
document.getElementById('pubkeyHex').textContent = '';
|
||||
}
|
||||
|
||||
// Apply configuration
|
||||
function applyConfig() {
|
||||
// Collect all settings
|
||||
const config = {
|
||||
theme: document.getElementById('themeSelect').value,
|
||||
darkMode: document.getElementById('darkModeCheck').checked,
|
||||
startScreen: document.getElementById('startScreenSelect').value,
|
||||
noBanner: document.getElementById('noBannerCheck').checked,
|
||||
bunkers: document.getElementById('bunkersInput').value,
|
||||
perms: document.getElementById('permsInput').value,
|
||||
title: document.getElementById('titleInput').value,
|
||||
description: document.getElementById('descriptionInput').value,
|
||||
methods: Array.from(document.querySelectorAll('input[type="checkbox"][value]'))
|
||||
.filter(cb => cb.checked)
|
||||
.map(cb => cb.value)
|
||||
};
|
||||
|
||||
// Remove empty values
|
||||
Object.keys(config).forEach(key => {
|
||||
if (config[key] === '' || (Array.isArray(config[key]) && config[key].length === 0)) {
|
||||
delete config[key];
|
||||
}
|
||||
});
|
||||
|
||||
currentConfig = config;
|
||||
storeConfig(config);
|
||||
updateConfigDisplay();
|
||||
|
||||
console.log('Applied config:', config);
|
||||
|
||||
// Reload page to apply new configuration
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// Reset configuration
|
||||
function resetConfig() {
|
||||
document.getElementById('themeSelect').value = 'default';
|
||||
document.getElementById('darkModeCheck').checked = false;
|
||||
document.getElementById('startScreenSelect').value = '';
|
||||
document.getElementById('noBannerCheck').checked = false;
|
||||
document.getElementById('bunkersInput').value = '';
|
||||
document.getElementById('permsInput').value = '';
|
||||
document.getElementById('titleInput').value = '';
|
||||
document.getElementById('descriptionInput').value = '';
|
||||
|
||||
// Reset auth methods to all checked
|
||||
document.querySelectorAll('input[type="checkbox"][value]').forEach(cb => cb.checked = true);
|
||||
|
||||
currentConfig = {};
|
||||
localStorage.removeItem('nostrLoginConfig');
|
||||
updateConfigDisplay();
|
||||
}
|
||||
|
||||
// Update config display
|
||||
function updateConfigDisplay() {
|
||||
document.getElementById('configDisplay').textContent = JSON.stringify(currentConfig, null, 2);
|
||||
}
|
||||
|
||||
// Launch with specific screen
|
||||
function launchSpecificScreen() {
|
||||
const startScreen = document.getElementById('startScreenSelect').value || 'welcome';
|
||||
document.dispatchEvent(new CustomEvent('nlLaunch', { detail: startScreen }));
|
||||
}
|
||||
|
||||
// Load configuration from storage and update UI
|
||||
function loadConfigIntoUI() {
|
||||
const config = getStoredConfig();
|
||||
currentConfig = config;
|
||||
|
||||
// Set default to laan theme for testing
|
||||
if (config.theme) document.getElementById('themeSelect').value = config.theme;
|
||||
else document.getElementById('themeSelect').value = 'laan';
|
||||
|
||||
if (config.darkMode !== undefined) document.getElementById('darkModeCheck').checked = config.darkMode;
|
||||
if (config.startScreen) document.getElementById('startScreenSelect').value = config.startScreen;
|
||||
if (config.noBanner !== undefined) document.getElementById('noBannerCheck').checked = config.noBanner;
|
||||
if (config.bunkers) document.getElementById('bunkersInput').value = config.bunkers;
|
||||
if (config.perms) document.getElementById('permsInput').value = config.perms;
|
||||
if (config.title) document.getElementById('titleInput').value = config.title;
|
||||
if (config.description) document.getElementById('descriptionInput').value = config.description;
|
||||
|
||||
// Load auth methods
|
||||
document.querySelectorAll('input[type="checkbox"][value]').forEach(cb => {
|
||||
cb.checked = !config.methods || config.methods.includes(cb.value);
|
||||
});
|
||||
|
||||
updateConfigDisplay();
|
||||
}
|
||||
|
||||
// Handle login button click
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Load configuration into UI
|
||||
loadConfigIntoUI();
|
||||
|
||||
// Load nostr-login with current config
|
||||
loadNostrLogin();
|
||||
|
||||
document.getElementById('loginBtn').addEventListener('click', () => {
|
||||
// Trigger nostr-login modal
|
||||
document.dispatchEvent(new CustomEvent('nlLaunch', { detail: 'welcome' }));
|
||||
});
|
||||
|
||||
// Handle logout button click
|
||||
document.getElementById('logoutBtn').addEventListener('click', () => {
|
||||
// Trigger logout
|
||||
document.dispatchEvent(new Event('nlLogout'));
|
||||
});
|
||||
|
||||
// Update config display when inputs change
|
||||
document.querySelectorAll('input, select').forEach(input => {
|
||||
input.addEventListener('change', () => {
|
||||
// Auto-apply dark mode for immediate feedback
|
||||
if (input.id === 'darkModeCheck') {
|
||||
document.dispatchEvent(new CustomEvent('nlDarkMode', { detail: input.checked }));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,135 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Input and Output Fields</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
}
|
||||
input, button, textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px; /* Adds space between the buttons */
|
||||
}
|
||||
.button-container button {
|
||||
flex: 1 1 calc(50% - 10px); /* Makes each button take up half the width minus the gap */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
<script src='https://www.unpkg.com/nostr-login@latest/dist/unpkg.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Try Nostr-Login</h1>
|
||||
<textarea id="inputField" rows="4" placeholder="Paste your json formatted event here"></textarea>
|
||||
<label>Output:</label>
|
||||
<textarea id="outputField" rows="4" placeholder="Output will be shown here" readonly></textarea>
|
||||
<button onclick="fillExampleData()">Fill with example event</button>
|
||||
<button onclick="SignEventFn()">Sign Event</button>
|
||||
<div class="button-container">
|
||||
<button onclick="cryptWithNIP04('encrypt')">Encrypt with NIP-04</button>
|
||||
<button onclick="cryptWithNIP04('decrypt')">Decrypt with NIP-04</button>
|
||||
<button onclick="cryptWithNIP44('encrypt')">Encrypt with NIP-44</button>
|
||||
<button onclick="cryptWithNIP44('decrypt')">Decrypt with NIP-44</button>
|
||||
</div>
|
||||
<button onclick="switchOutputInput()">Switch Output Input</button>
|
||||
</div>
|
||||
|
||||
<form id="testpw" action="/usage.html" method="POST">
|
||||
<input type="text" name="name" value="npub">
|
||||
<input type="password" name="password" value="a;h123UIFBEKSDBF,SA">
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
<script>
|
||||
const form = document.querySelector("#testpw");
|
||||
form.onsubmit = (e) => {
|
||||
e.preventDefault();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
// Example Nostr event as json string
|
||||
var ExampleData = `{ "content": "hello world", "created_at": 1731313613, "id": "", "kind": 1, "pubkey": "568ad8bf00ed530eb44614e4b363271f36f6b645700630470c51f98e7e58fbf0", "tags": [] }`
|
||||
|
||||
// Function to get public key
|
||||
async function getPublicKey() {
|
||||
return await window.nostr.getPublicKey();
|
||||
}
|
||||
|
||||
// Function to encrypt with NIP-04
|
||||
async function cryptWithNIP04(encryptType) {
|
||||
try {
|
||||
var publicKey = await getPublicKey();
|
||||
var data = document.getElementById('inputField').value;
|
||||
var result;
|
||||
if (encryptType === "encrypt") {
|
||||
result = await window.nostr.nip04.encrypt(publicKey, data);
|
||||
} else if (encryptType === "decrypt") {
|
||||
result = await window.nostr.nip04.decrypt(publicKey, data);
|
||||
}
|
||||
document.getElementById('outputField').value = result;
|
||||
} catch (error) {
|
||||
console.error("Error in cryptWithNIP04:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to encrypt with NIP-44
|
||||
async function cryptWithNIP44(encryptType) {
|
||||
try {
|
||||
var publicKey = await getPublicKey();
|
||||
var data = document.getElementById('inputField').value;
|
||||
var result;
|
||||
if (encryptType === "encrypt") {
|
||||
result = await window.nostr.nip44.encrypt(publicKey, data);
|
||||
} else if (encryptType === "decrypt") {
|
||||
result = await window.nostr.nip44.decrypt(publicKey, data);
|
||||
}
|
||||
document.getElementById('outputField').value = result;
|
||||
} catch (error) {
|
||||
console.error("Error in cryptWithNIP44:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Sign nostr event
|
||||
async function SignEventFn() {
|
||||
try {
|
||||
var input = document.getElementById('inputField').value;
|
||||
// Convert the text from input to json object
|
||||
var json_data = JSON.parse(input);
|
||||
// Sign the event object using the plugin signEvent method
|
||||
var signedEvent = await window.nostr.signEvent(json_data);
|
||||
document.getElementById('outputField').value = JSON.stringify(signedEvent, null, 2);
|
||||
} catch (error) {
|
||||
console.error("Error in SignEventFn:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill example data for easy testing
|
||||
function fillExampleData() {
|
||||
document.getElementById('outputField').value = '';
|
||||
document.getElementById('inputField').value = ExampleData;
|
||||
}
|
||||
|
||||
// Switch input and output fields
|
||||
function switchOutputInput() {
|
||||
var tempOutput = document.getElementById('outputField').value;
|
||||
document.getElementById('inputField').value = tempOutput;
|
||||
document.getElementById('outputField').value = '';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user