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'
OFF0 2 years ago
parent 67af695350
commit 334b09afa1
Signed by: offbyn
GPG Key ID: 94A2F643C51F37FA

@ -3,7 +3,7 @@ import {sub24hFeed, subNote, subProfile} from './subscriptions'
import {publish} from './relays';
import {getReplyTo, hasEventTag, isMention, sortByCreatedAt, sortEventCreatedAt, validatePow} from './events';
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/
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) {
const host = getHost(relay);
const user = userList.find(user => user.pubkey === evt.pubkey);
@ -450,20 +434,12 @@ function getMetadata(evt, relay) {
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', () => {
const reply_to = localStorage.getItem('reply_to');
if (reply_to && writeInput.value === '') {
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
writeForm.after(elemShrink());
writeForm.after(elemShrink(writeInput));
writeForm.remove();
localStorage.removeItem('reply_to');
}
@ -472,7 +448,7 @@ writeInput.addEventListener('focusout', () => {
});
function appendReplyForm(el) {
writeForm.before(elemShrink());
writeForm.before(elemShrink(writeInput));
writeInput.blur();
writeInput.style.removeProperty('height');
el.after(writeForm);

@ -18,11 +18,11 @@ type Attributes = {
* @param {Array<HTMLElement|string>} children
* @return HTMLElement
*/
export const elem = (
name: keyof HTMLElementTagNameMap,
attrs: Attributes = {},
children: Array<Node> | string = []
) => {
export const elem = <Name extends keyof HTMLElementTagNameMap>(
name: Extract<Name, keyof HTMLElementTagNameMap>,
attrs: Attributes = {}, // TODO optional
children: Array<Node> | string = [], // TODO optional
): HTMLElementTagNameMap[Name] => {
const {data, ...props} = attrs;
const el = document.createElement(name);
Object.assign(el, props);
@ -127,3 +127,44 @@ export const parseTextContent = (
{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 {elem, parseTextContent} from './dom';
export {elem, elemCanvas, elemShrink, parseTextContent} from './dom';
export {bounce, dateTime, formatTime} from './time';
export {getHost, getNoxyUrl, isHttpUrl, isWssUrl} from './url';

@ -2,7 +2,7 @@ import {elem} from './utils';
type Container = {
id: string;
view: HTMLSelectElement;
view: HTMLElement;
content: HTMLDivElement;
dom: {
[eventId: string]: HTMLElement

Loading…
Cancel
Save