feat(banners-lib): basic custom icon fetching

This commit is contained in:
Guz013
2023-11-30 17:53:51 -03:00
parent 171c5ffd96
commit 9ad2ca0d3c
2 changed files with 81 additions and 2 deletions

View File

@@ -2,6 +2,7 @@
* @typedef {import('./index.js').BannerObject} BannerObject * @typedef {import('./index.js').BannerObject} BannerObject
*/ */
import getLocalLayout from './layouts.js'; import getLocalLayout from './layouts.js';
import { isValidIcon } from './utils.js';
/** /**
* @param {Readonly<string>} string - The string to be converted. * @param {Readonly<string>} string - The string to be converted.
@@ -29,13 +30,26 @@ function htmlToString(element, document) {
/** /**
* @typedef {{ * @typedef {{
* modify(query: string, callback: (el: Element | null) => T): T * modify(query: string, callback: (el: Element | null) => T): T,
* asyncModify(query: string, callback: (el: Element | null) => Promise<T>): Promise<T>,
* }} DOMHelper * }} DOMHelper
* @param {Element} element - The element to be manipulated. * @param {Element} element - The element to be manipulated.
* @returns {DOMHelper} * @returns {DOMHelper}
*/ */
function domHelper(element) { function domHelper(element) {
return { return {
/**
* @template T
* @param {string} query - The query selector to find the element.
* @param {(el: Element | null) => Promise<T>} callback - Callback to modify the element.
* @returns {Promise<T>} - The return value of the callback.
* @throws {Error} - Throws if the element is not found.
*/
async asyncModify(query, callback) {
const el = element.querySelector(query);
return callback(el);
},
/** /**
* @template T * @template T
* @param {string} query - The query selector to find the element. * @param {string} query - The query selector to find the element.
@@ -115,12 +129,36 @@ async function banner(object) {
/** @type {Document} */ /** @type {Document} */
// @ts-expect-error because Document is not compatible with Readonly<Document> // @ts-expect-error because Document is not compatible with Readonly<Document>
const doc = object.lib?.document ?? globalThis.document; const doc = object.lib?.document ?? globalThis.document;
/** @type {(info: URL | RequestInfo, init?: RequestInit) => Promise<Response>} */
// @ts-expect-error because fetch is Readonly in Banner object;
const lFetch = object.lib?.fetch ?? globalThis.fetch;
/** @type {Readonly<string>} */ /** @type {Readonly<string>} */
const svg = await getLocalLayout('horizontal', true); const svg = await getLocalLayout('horizontal');
const dom = stringToHtml(svg, doc); const dom = stringToHtml(svg, doc);
const helper = domHelper(dom); const helper = domHelper(dom);
await helper.asyncModify('[data-banner-class="icon"]', async (el) => {
if (!el || !object.icon || !isValidIcon(object.icon)) return;
const [ iconSet, iconName ] = object.icon.split(':');
const res = await lFetch(`https://api.iconify.design/${iconSet}/${iconName}.svg`);
const resSvg = stringToHtml(await res.text(), doc);
resSvg.setAttribute('x', '22');
resSvg.setAttribute('y', '33');
resSvg.setAttribute('width', '13');
resSvg.setAttribute('height', '13');
if (resSvg.children[0].getAttribute('fill') === 'currentColor')
resSvg.children[0].setAttribute('fill', '#000000');
// eslint-disable-next-line require-atomic-updates
el.innerHTML = htmlToString(resSvg, doc);
});
helper.modify('[data-banner-class="title"] > tspan', (el) => { helper.modify('[data-banner-class="title"] > tspan', (el) => {
if (!el) return; if (!el) return;
@@ -153,11 +191,13 @@ async function banner(object) {
*/ */
async function test() { async function test() {
const testBanner = await banner({ const testBanner = await banner({
icon: 'solar:4k-bold',
lib: { lib: {
// @ts-expect-error because Document is not DeepReadonly<Document> // @ts-expect-error because Document is not DeepReadonly<Document>
document: new Document(), document: new Document(),
fetch, fetch,
}, },
subtitle: 'this is a test with icon',
title: 'Hello, world', title: 'Hello, world',
}); });

View File

@@ -0,0 +1,39 @@
/**
* Checks if a given string is a URL.
*
* @param {Readonly<string>} string - The string to be checked.
* @returns {boolean}
*/
function isURL(string) {
try {
const url = new URL(string);
return url.protocol === 'http' || url.protocol === 'https';
}
catch {
return false;
}
}
/**
* Checks if a given string is a valid Iconify's icon name.
*
* @param {string} string - The string to be checked.
* @returns {boolean}
*/
function isValidIcon(string) {
if (string.includes('--')) return false;
// eslint-disable-next-line no-secrets/no-secrets
const VALID_CHARS = 'abcdefghijklmnopqrstuvwxyz1234567890-:';
if ([...string].some(l => !VALID_CHARS.includes(l)))
return false;
if (!string.includes(':')) return false;
return true;
}
export { isURL, isValidIcon };