forked from nostr/nostrweb
settings: move remaining settings code
with this change everything related to user settings is now in settings.ts module.
parent
efda7737c8
commit
43754149a9
137
src/main.js
137
src/main.js
|
@ -1,4 +1,4 @@
|
||||||
import {generatePrivateKey, getPublicKey, nip19, signEvent} from 'nostr-tools';
|
import {nip19, signEvent} from 'nostr-tools';
|
||||||
import {zeroLeadingBitsCount} from './utils/crypto';
|
import {zeroLeadingBitsCount} from './utils/crypto';
|
||||||
import {elem, elemCanvas, elemShrink, parseTextContent, updateElemHeight} from './utils/dom';
|
import {elem, elemCanvas, elemShrink, parseTextContent, updateElemHeight} from './utils/dom';
|
||||||
import {bounce, dateTime, formatTime} from './utils/time';
|
import {bounce, dateTime, formatTime} from './utils/time';
|
||||||
|
@ -8,7 +8,7 @@ import {sub24hFeed, subNote, subProfile} from './subscriptions'
|
||||||
import {publish} from './relays';
|
import {publish} from './relays';
|
||||||
import {getReplyTo, hasEventTag, isMention, sortByCreatedAt, sortEventCreatedAt, validatePow} from './events';
|
import {getReplyTo, hasEventTag, isMention, sortByCreatedAt, sortEventCreatedAt, validatePow} from './events';
|
||||||
import {clearView, getViewContent, getViewElem, setViewElem, view} from './view';
|
import {clearView, getViewContent, getViewElem, setViewElem, view} from './view';
|
||||||
import {config} from './settings';
|
import {closeSettingsView, config, toggleSettingsView} from './settings';
|
||||||
// curl -H 'accept: application/nostr+json' https://relay.nostr.ch/
|
// curl -H 'accept: application/nostr+json' https://relay.nostr.ch/
|
||||||
|
|
||||||
function onEvent(evt, relay) {
|
function onEvent(evt, relay) {
|
||||||
|
@ -608,7 +608,6 @@ window.addEventListener('popstate', (event) => {
|
||||||
route(location.pathname);
|
route(location.pathname);
|
||||||
});
|
});
|
||||||
|
|
||||||
const settingsView = document.querySelector('#settings');
|
|
||||||
const publishView = document.querySelector('#newNote');
|
const publishView = document.querySelector('#newNote');
|
||||||
|
|
||||||
document.body.addEventListener('click', (e) => {
|
document.body.addEventListener('click', (e) => {
|
||||||
|
@ -618,9 +617,7 @@ document.body.addEventListener('click', (e) => {
|
||||||
if (a) {
|
if (a) {
|
||||||
if ('nav' in a.dataset) {
|
if ('nav' in a.dataset) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!settingsView.hidden) {
|
closeSettingsView();
|
||||||
settingsView.hidden = true;
|
|
||||||
}
|
|
||||||
if (!publishView.hidden) {
|
if (!publishView.hidden) {
|
||||||
publishView.hidden = true;
|
publishView.hidden = true;
|
||||||
}
|
}
|
||||||
|
@ -646,7 +643,7 @@ document.body.addEventListener('click', (e) => {
|
||||||
upvote(id, pubkey);
|
upvote(id, pubkey);
|
||||||
break;
|
break;
|
||||||
case 'settings':
|
case 'settings':
|
||||||
settingsView.hidden = !settingsView.hidden;
|
toggleSettingsView();
|
||||||
break;
|
break;
|
||||||
case 'new-note':
|
case 'new-note':
|
||||||
if (publishView.hidden) {
|
if (publishView.hidden) {
|
||||||
|
@ -682,129 +679,3 @@ document.body.addEventListener('click', (e) => {
|
||||||
// hideNewMessage(true);
|
// hideNewMessage(true);
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// settings
|
|
||||||
const settingsForm = document.querySelector('form[name="settings"]');
|
|
||||||
const privateKeyInput = settingsForm.querySelector('#privatekey');
|
|
||||||
const pubKeyInput = settingsForm.querySelector('#pubkey');
|
|
||||||
const statusMessage = settingsForm.querySelector('#keystatus');
|
|
||||||
const generateBtn = settingsForm.querySelector('button[name="generate"]');
|
|
||||||
const importBtn = settingsForm.querySelector('button[name="import"]');
|
|
||||||
const privateTgl = settingsForm.querySelector('button[name="privatekey-toggle"]');
|
|
||||||
|
|
||||||
generateBtn.addEventListener('click', () => {
|
|
||||||
const privatekey = generatePrivateKey();
|
|
||||||
const pubkey = getPublicKey(privatekey);
|
|
||||||
if (validKeys(privatekey, pubkey)) {
|
|
||||||
privateKeyInput.value = privatekey;
|
|
||||||
pubKeyInput.value = pubkey;
|
|
||||||
statusMessage.textContent = 'private-key created!';
|
|
||||||
statusMessage.hidden = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
importBtn.addEventListener('click', () => {
|
|
||||||
const privatekey = privateKeyInput.value;
|
|
||||||
const pubkeyInput = pubKeyInput.value;
|
|
||||||
if (validKeys(privatekey, pubkeyInput)) {
|
|
||||||
localStorage.setItem('private_key', privatekey);
|
|
||||||
localStorage.setItem('pub_key', pubkeyInput);
|
|
||||||
statusMessage.textContent = 'stored private and public key locally!';
|
|
||||||
statusMessage.hidden = false;
|
|
||||||
config.pubkey = pubkeyInput;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
settingsForm.addEventListener('input', () => validKeys(privateKeyInput.value, pubKeyInput.value));
|
|
||||||
privateKeyInput.addEventListener('paste', (event) => {
|
|
||||||
if (pubKeyInput.value || !event.clipboardData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (privateKeyInput.value === '' || ( // either privatekey field is empty
|
|
||||||
privateKeyInput.selectionStart === 0 // or the whole text is selected and replaced with the clipboard
|
|
||||||
&& privateKeyInput.selectionEnd === privateKeyInput.value.length
|
|
||||||
)) { // only generate the pubkey if no data other than the text from clipboard will be used
|
|
||||||
try {
|
|
||||||
pubKeyInput.value = getPublicKey(event.clipboardData.getData('text'));
|
|
||||||
} catch(err) {} // settings form will call validKeys on input and display the error
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function validKeys(privatekey, pubkey) {
|
|
||||||
try {
|
|
||||||
if (getPublicKey(privatekey) === pubkey) {
|
|
||||||
statusMessage.hidden = true;
|
|
||||||
statusMessage.textContent = 'public-key corresponds to private-key';
|
|
||||||
importBtn.removeAttribute('disabled');
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
statusMessage.textContent = 'private-key does not correspond to public-key!'
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
statusMessage.textContent = `not a valid private-key: ${e.message || e}`;
|
|
||||||
}
|
|
||||||
statusMessage.hidden = false;
|
|
||||||
importBtn.setAttribute('disabled', true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
privateTgl.addEventListener('click', () => {
|
|
||||||
privateKeyInput.type = privateKeyInput.type === 'text' ? 'password' : 'text';
|
|
||||||
});
|
|
||||||
|
|
||||||
privateKeyInput.value = localStorage.getItem('private_key');
|
|
||||||
pubKeyInput.value = localStorage.getItem('pub_key');
|
|
||||||
|
|
||||||
// profile
|
|
||||||
const profileForm = document.querySelector('form[name="profile"]');
|
|
||||||
const profileSubmit = profileForm.querySelector('button[type="submit"]');
|
|
||||||
const profileStatus = document.querySelector('#profilestatus');
|
|
||||||
const onProfileError = err => {
|
|
||||||
profileStatus.hidden = false;
|
|
||||||
profileStatus.textContent = err.message
|
|
||||||
};
|
|
||||||
profileForm.addEventListener('input', (e) => {
|
|
||||||
if (e.target.nodeName === 'TEXTAREA') {
|
|
||||||
updateElemHeight(e.target);
|
|
||||||
}
|
|
||||||
const form = new FormData(profileForm);
|
|
||||||
const name = form.get('name');
|
|
||||||
const about = form.get('about');
|
|
||||||
const picture = form.get('picture');
|
|
||||||
profileSubmit.disabled = !(name || about || picture);
|
|
||||||
});
|
|
||||||
|
|
||||||
profileForm.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const form = new FormData(profileForm);
|
|
||||||
const newProfile = await powEvent({
|
|
||||||
kind: 0,
|
|
||||||
pubkey: config.pubkey,
|
|
||||||
content: JSON.stringify(Object.fromEntries(form)),
|
|
||||||
tags: [],
|
|
||||||
created_at: Math.floor(Date.now() * 0.001),
|
|
||||||
}, {
|
|
||||||
difficulty: config.difficulty,
|
|
||||||
statusElem: profileStatus,
|
|
||||||
timeout: config.timeout,
|
|
||||||
}).catch(console.warn);
|
|
||||||
if (!newProfile) {
|
|
||||||
profileStatus.textContent = 'publishing profile data canceled';
|
|
||||||
profileStatus.hidden = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const privatekey = localStorage.getItem('private_key');
|
|
||||||
const sig = signEvent(newProfile, privatekey);
|
|
||||||
// TODO: validateEvent
|
|
||||||
if (sig) {
|
|
||||||
publish({...newProfile, sig}, (relay, error) => {
|
|
||||||
if (error) {
|
|
||||||
return console.error(error, relay);
|
|
||||||
}
|
|
||||||
console.info(`publish request sent to ${relay}`);
|
|
||||||
profileStatus.textContent = 'profile metadata successfully published';
|
|
||||||
profileStatus.hidden = false;
|
|
||||||
profileSubmit.disabled = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
149
src/settings.ts
149
src/settings.ts
|
@ -1,4 +1,13 @@
|
||||||
import {generatePrivateKey, getPublicKey} from 'nostr-tools';
|
import {generatePrivateKey, getPublicKey, signEvent} from 'nostr-tools';
|
||||||
|
import {updateElemHeight} from './utils/dom';
|
||||||
|
import {powEvent} from './system';
|
||||||
|
import {publish} from './relays';
|
||||||
|
|
||||||
|
const settingsView = document.querySelector('#settings') as HTMLElement;
|
||||||
|
|
||||||
|
export const closeSettingsView = () => settingsView.hidden = true;
|
||||||
|
|
||||||
|
export const toggleSettingsView = () => settingsView.hidden = !settingsView.hidden;
|
||||||
|
|
||||||
let pubkey: string = '';
|
let pubkey: string = '';
|
||||||
|
|
||||||
|
@ -90,3 +99,141 @@ miningTimeoutInput.addEventListener('input', (e) => {
|
||||||
});
|
});
|
||||||
timeout = getNumberFromStorage('mining_timeout', 5);
|
timeout = getNumberFromStorage('mining_timeout', 5);
|
||||||
miningTimeoutInput.valueAsNumber = timeout;
|
miningTimeoutInput.valueAsNumber = timeout;
|
||||||
|
|
||||||
|
|
||||||
|
// settings
|
||||||
|
const settingsForm = document.querySelector('form[name="settings"]') as HTMLFormElement;
|
||||||
|
const privateKeyInput = settingsForm.querySelector('#privatekey') as HTMLInputElement;
|
||||||
|
const pubKeyInput = settingsForm.querySelector('#pubkey') as HTMLInputElement;
|
||||||
|
const statusMessage = settingsForm.querySelector('#keystatus') as HTMLElement;
|
||||||
|
const generateBtn = settingsForm.querySelector('button[name="generate"]') as HTMLButtonElement;
|
||||||
|
const importBtn = settingsForm.querySelector('button[name="import"]') as HTMLButtonElement;
|
||||||
|
const privateTgl = settingsForm.querySelector('button[name="privatekey-toggle"]') as HTMLButtonElement;
|
||||||
|
|
||||||
|
const validKeys = (
|
||||||
|
privatekey: string,
|
||||||
|
pubkey: string,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
if (getPublicKey(privatekey) === pubkey) {
|
||||||
|
statusMessage.hidden = true;
|
||||||
|
statusMessage.textContent = 'public-key corresponds to private-key';
|
||||||
|
importBtn.removeAttribute('disabled');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
statusMessage.textContent = 'private-key does not correspond to public-key!'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
statusMessage.textContent = `not a valid private-key: ${e.message || e}`;
|
||||||
|
}
|
||||||
|
statusMessage.hidden = false;
|
||||||
|
importBtn.disabled = true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
generateBtn.addEventListener('click', () => {
|
||||||
|
const privatekey = generatePrivateKey();
|
||||||
|
const pubkey = getPublicKey(privatekey);
|
||||||
|
if (validKeys(privatekey, pubkey)) {
|
||||||
|
privateKeyInput.value = privatekey;
|
||||||
|
pubKeyInput.value = pubkey;
|
||||||
|
statusMessage.textContent = 'private-key created!';
|
||||||
|
statusMessage.hidden = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
importBtn.addEventListener('click', () => {
|
||||||
|
const privatekey = privateKeyInput.value;
|
||||||
|
const pubkeyInput = pubKeyInput.value;
|
||||||
|
if (validKeys(privatekey, pubkeyInput)) {
|
||||||
|
localStorage.setItem('private_key', privatekey);
|
||||||
|
localStorage.setItem('pub_key', pubkeyInput);
|
||||||
|
statusMessage.textContent = 'stored private and public key locally!';
|
||||||
|
statusMessage.hidden = false;
|
||||||
|
config.pubkey = pubkeyInput;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
settingsForm.addEventListener('input', () => validKeys(privateKeyInput.value, pubKeyInput.value));
|
||||||
|
|
||||||
|
privateKeyInput.addEventListener('paste', (event) => {
|
||||||
|
if (pubKeyInput.value || !event.clipboardData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (privateKeyInput.value === '' || ( // either privatekey field is empty
|
||||||
|
privateKeyInput.selectionStart === 0 // or the whole text is selected and replaced with the clipboard
|
||||||
|
&& privateKeyInput.selectionEnd === privateKeyInput.value.length
|
||||||
|
)) { // only generate the pubkey if no data other than the text from clipboard will be used
|
||||||
|
try {
|
||||||
|
pubKeyInput.value = getPublicKey(event.clipboardData.getData('text'));
|
||||||
|
} catch(err) {} // settings form will call validKeys on input and display the error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
privateTgl.addEventListener('click', () => {
|
||||||
|
privateKeyInput.type = privateKeyInput.type === 'text' ? 'password' : 'text';
|
||||||
|
});
|
||||||
|
|
||||||
|
privateKeyInput.value = localStorage.getItem('private_key') || '';
|
||||||
|
pubKeyInput.value = localStorage.getItem('pub_key') || '';
|
||||||
|
|
||||||
|
// profile
|
||||||
|
const profileForm = document.querySelector('form[name="profile"]') as HTMLFormElement;
|
||||||
|
const profileSubmit = profileForm.querySelector('button[type="submit"]') as HTMLButtonElement;
|
||||||
|
const profileStatus = document.querySelector('#profilestatus') as HTMLElement;
|
||||||
|
// const onProfileError = err => {
|
||||||
|
// profileStatus.hidden = false;
|
||||||
|
// profileStatus.textContent = err.message
|
||||||
|
// };
|
||||||
|
profileForm.addEventListener('input', (e) => {
|
||||||
|
if (e.target instanceof HTMLElement) {
|
||||||
|
if (e.target?.nodeName === 'TEXTAREA') {
|
||||||
|
updateElemHeight(e.target as HTMLTextAreaElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const form = new FormData(profileForm);
|
||||||
|
const name = form.get('name');
|
||||||
|
const about = form.get('about');
|
||||||
|
const picture = form.get('picture');
|
||||||
|
profileSubmit.disabled = !(name || about || picture);
|
||||||
|
});
|
||||||
|
|
||||||
|
profileForm.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const form = new FormData(profileForm);
|
||||||
|
const newProfile = await powEvent({
|
||||||
|
kind: 0,
|
||||||
|
pubkey: config.pubkey,
|
||||||
|
content: JSON.stringify(Object.fromEntries(form)),
|
||||||
|
tags: [],
|
||||||
|
created_at: Math.floor(Date.now() * 0.001)
|
||||||
|
}, {
|
||||||
|
difficulty: config.difficulty,
|
||||||
|
statusElem: profileStatus,
|
||||||
|
timeout: config.timeout,
|
||||||
|
}).catch(console.warn);
|
||||||
|
if (!newProfile) {
|
||||||
|
profileStatus.textContent = 'publishing profile data canceled';
|
||||||
|
profileStatus.hidden = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const privatekey = localStorage.getItem('private_key');
|
||||||
|
if (!privatekey) {
|
||||||
|
profileStatus.textContent = 'no private key to sign';
|
||||||
|
profileStatus.hidden = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sig = signEvent(newProfile, privatekey);
|
||||||
|
// TODO: validateEvent
|
||||||
|
if (sig) {
|
||||||
|
publish({...newProfile, sig}, (relay, error) => {
|
||||||
|
if (error) {
|
||||||
|
return console.error(error, relay);
|
||||||
|
}
|
||||||
|
console.info(`publish request sent to ${relay}`);
|
||||||
|
profileStatus.textContent = 'profile metadata successfully published';
|
||||||
|
profileStatus.hidden = false;
|
||||||
|
profileSubmit.disabled = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue