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 {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…
Cancel
Save