forked from nostr/nostrweb
refactor: type elem and enforce inferred generic type
typed elem so that it returns the exact type of the HTMLElement, and that name must be a key of HTMLElementTagNameMap. example: elem('form'); // returns HTMLFormElement elem('abc'); // not assignable to parameter of type 'keyof HTMLElementTagNameMap'
parent
2d46687e12
commit
489a260427
30
src/main.js
30
src/main.js
|
@ -3,7 +3,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 {bounce, dateTime, elem, formatTime, getHost, getNoxyUrl, isWssUrl, parseTextContent, zeroLeadingBitsCount} from './utils';
|
import {bounce, dateTime, elem, elemCanvas, elemShrink, formatTime, getHost, getNoxyUrl, isWssUrl, parseTextContent, zeroLeadingBitsCount} from './utils';
|
||||||
// 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) {
|
||||||
|
@ -415,22 +415,6 @@ function setMetadata(evt, relay, content) {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
const elemCanvas = (text) => {
|
|
||||||
const canvas = elem('canvas', {height: 80, width: 80, data: {pubkey: text}});
|
|
||||||
const context = canvas.getContext('2d');
|
|
||||||
const color = `#${text.slice(0, 6)}`;
|
|
||||||
context.fillStyle = color;
|
|
||||||
context.fillRect(0, 0, 80, 80);
|
|
||||||
context.fillStyle = '#111';
|
|
||||||
context.fillRect(0, 50, 80, 32);
|
|
||||||
context.font = 'bold 18px monospace';
|
|
||||||
if (color === '#000000') {
|
|
||||||
context.fillStyle = '#fff';
|
|
||||||
}
|
|
||||||
context.fillText(text.slice(0, 8), 2, 46);
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMetadata(evt, relay) {
|
function getMetadata(evt, relay) {
|
||||||
const host = getHost(relay);
|
const host = getHost(relay);
|
||||||
const user = userList.find(user => user.pubkey === evt.pubkey);
|
const user = userList.find(user => user.pubkey === evt.pubkey);
|
||||||
|
@ -450,20 +434,12 @@ function getMetadata(evt, relay) {
|
||||||
|
|
||||||
const writeForm = document.querySelector('#writeForm');
|
const writeForm = document.querySelector('#writeForm');
|
||||||
|
|
||||||
const elemShrink = () => {
|
|
||||||
const height = writeInput.style.height || writeInput.getBoundingClientRect().height;
|
|
||||||
const shrink = elem('div', {className: 'shrink-out'});
|
|
||||||
shrink.style.height = `${height}px`;
|
|
||||||
shrink.addEventListener('animationend', () => shrink.remove(), {once: true});
|
|
||||||
return shrink;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeInput.addEventListener('focusout', () => {
|
writeInput.addEventListener('focusout', () => {
|
||||||
const reply_to = localStorage.getItem('reply_to');
|
const reply_to = localStorage.getItem('reply_to');
|
||||||
if (reply_to && writeInput.value === '') {
|
if (reply_to && writeInput.value === '') {
|
||||||
writeInput.addEventListener('transitionend', (event) => {
|
writeInput.addEventListener('transitionend', (event) => {
|
||||||
if (!reply_to || reply_to === localStorage.getItem('reply_to') && !writeInput.style.height) { // should prob use some class or data-attr instead of relying on height
|
if (!reply_to || reply_to === localStorage.getItem('reply_to') && !writeInput.style.height) { // should prob use some class or data-attr instead of relying on height
|
||||||
writeForm.after(elemShrink());
|
writeForm.after(elemShrink(writeInput));
|
||||||
writeForm.remove();
|
writeForm.remove();
|
||||||
localStorage.removeItem('reply_to');
|
localStorage.removeItem('reply_to');
|
||||||
}
|
}
|
||||||
|
@ -472,7 +448,7 @@ writeInput.addEventListener('focusout', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function appendReplyForm(el) {
|
function appendReplyForm(el) {
|
||||||
writeForm.before(elemShrink());
|
writeForm.before(elemShrink(writeInput));
|
||||||
writeInput.blur();
|
writeInput.blur();
|
||||||
writeInput.style.removeProperty('height');
|
writeInput.style.removeProperty('height');
|
||||||
el.after(writeForm);
|
el.after(writeForm);
|
||||||
|
|
|
@ -18,11 +18,11 @@ type Attributes = {
|
||||||
* @param {Array<HTMLElement|string>} children
|
* @param {Array<HTMLElement|string>} children
|
||||||
* @return HTMLElement
|
* @return HTMLElement
|
||||||
*/
|
*/
|
||||||
export const elem = (
|
export const elem = <Name extends keyof HTMLElementTagNameMap>(
|
||||||
name: keyof HTMLElementTagNameMap,
|
name: Extract<Name, keyof HTMLElementTagNameMap>,
|
||||||
attrs: Attributes = {},
|
attrs: Attributes = {}, // TODO optional
|
||||||
children: Array<Node> | string = []
|
children: Array<Node> | string = [], // TODO optional
|
||||||
) => {
|
): HTMLElementTagNameMap[Name] => {
|
||||||
const {data, ...props} = attrs;
|
const {data, ...props} = attrs;
|
||||||
const el = document.createElement(name);
|
const el = document.createElement(name);
|
||||||
Object.assign(el, props);
|
Object.assign(el, props);
|
||||||
|
@ -127,3 +127,44 @@ export const parseTextContent = (
|
||||||
{firstLink}
|
{firstLink}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a small profile image
|
||||||
|
* @param text to pass pubkey
|
||||||
|
* @returns HTMLCanvasElement | null
|
||||||
|
*/
|
||||||
|
export const elemCanvas = (text: string) => {
|
||||||
|
const canvas = elem('canvas', {
|
||||||
|
height: 80,
|
||||||
|
width: 80,
|
||||||
|
data: {pubkey: text}
|
||||||
|
});
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
if (!context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const color = `#${text.slice(0, 6)}`;
|
||||||
|
context.fillStyle = color;
|
||||||
|
context.fillRect(0, 0, 80, 80);
|
||||||
|
context.fillStyle = '#111';
|
||||||
|
context.fillRect(0, 50, 80, 32);
|
||||||
|
context.font = 'bold 18px monospace';
|
||||||
|
if (color === '#000000') {
|
||||||
|
context.fillStyle = '#fff';
|
||||||
|
}
|
||||||
|
context.fillText(text.slice(0, 8), 2, 46);
|
||||||
|
return canvas;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a placeholder element that animates the height to 0
|
||||||
|
* @param element to get the initial height from
|
||||||
|
* @returns HTMLDivElement
|
||||||
|
*/
|
||||||
|
export const elemShrink = (el: HTMLElement) => {
|
||||||
|
const height = el.style.height || el.getBoundingClientRect().height;
|
||||||
|
const shrink = elem('div', {className: 'shrink-out'});
|
||||||
|
shrink.style.height = `${height}px`;
|
||||||
|
shrink.addEventListener('animationend', () => shrink.remove(), {once: true});
|
||||||
|
return shrink;
|
||||||
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export {zeroLeadingBitsCount} from './crypto';
|
export {zeroLeadingBitsCount} from './crypto';
|
||||||
export {elem, parseTextContent} from './dom';
|
export {elem, elemCanvas, elemShrink, parseTextContent} from './dom';
|
||||||
export {bounce, dateTime, formatTime} from './time';
|
export {bounce, dateTime, formatTime} from './time';
|
||||||
export {getHost, getNoxyUrl, isHttpUrl, isWssUrl} from './url';
|
export {getHost, getNoxyUrl, isHttpUrl, isWssUrl} from './url';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {elem} from './utils';
|
||||||
|
|
||||||
type Container = {
|
type Container = {
|
||||||
id: string;
|
id: string;
|
||||||
view: HTMLSelectElement;
|
view: HTMLElement;
|
||||||
content: HTMLDivElement;
|
content: HTMLDivElement;
|
||||||
dom: {
|
dom: {
|
||||||
[eventId: string]: HTMLElement
|
[eventId: string]: HTMLElement
|
||||||
|
|
Loading…
Reference in New Issue