feat(banners): custom icon support

This commit is contained in:
Guz013
2023-06-23 16:04:38 -03:00
parent 4452ac2d24
commit 1bf5c1ff60
5 changed files with 89 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ export const GET = (async ({ fetch }): Promise<Response> => {
const banner = await newBanner({
title: 'Hello world',
subtitle: 'This is a test!',
icon: 'https://raw.githubusercontent.com/LoredDev/.github/main/assets/designs/dots-icon-dark.svg',
colors: {
background: '#000000',
foreground: '#ffffff',
@@ -23,7 +24,9 @@ export const GET = (async ({ fetch }): Promise<Response> => {
style: 'normal',
},
},
rtl: true,
libConfig: {
fetcher: fetch,
},
});
return new Response(`${banner.toString()}`, {

View File

@@ -63,16 +63,10 @@ export function generateBannerHtml({
align-items: center;
display: flex;
margin: ${horizontal ? '1.5' : '0'}em 0;
width: 3.5em;
height: 3.5em;
">
<svg xmlns="http://www.w3.org/2000/svg" width="3.5em" height="3.5em" viewBox="0 0 24 24">
<g fill="currentColor">
<path
d="M15.75 2a.75.75 0 0 0-1.5 0v20a.75.75 0 0 0 1.5 0v-2.006c2.636-.027 4.104-.191 5.078-1.166C22 17.657 22 15.771 22 12c0-3.771 0-5.657-1.172-6.828c-.974-.975-2.442-1.139-5.078-1.166V2Z" />
<path fill-rule="evenodd"
d="M10 20c-3.771 0-5.657 0-6.828-1.172C2 17.657 2 15.771 2 12c0-3.771 0-5.657 1.172-6.828C4.343 4 6.229 4 10 4h3v16h-3ZM6.818 7.787c.3-.037.666-.037 1.066-.037h2.232c.4 0 .766 0 1.066.037c.329.041.68.137.98.405c.052.046.1.094.146.146c.268.3.364.651.405.98c.037.3.037.666.037 1.066v.041a.75.75 0 0 1-1.5 0c0-.455-.001-.726-.026-.922c-.024-.195-.228-.227-.228-.227c-.195-.025-.466-.026-.921-.026H9.75v5.5H11a.75.75 0 0 1 0 1.5H7a.75.75 0 0 1 0-1.5h1.25v-5.5h-.325c-.455 0-.726.001-.922.026c0 0-.203.032-.227.227c-.025.196-.026.467-.026.922a.75.75 0 0 1-1.5 0v-.041c0-.4 0-.766.037-1.066c.041-.329.137-.68.405-.98c.046-.052.094-.1.146-.146c.3-.268.651-.364.98-.405Z"
clip-rule="evenodd" />
</g>
</svg>
%%MARKNOW-PLACEHOLDER-ICON%%
</div>
<div style="
align-items: center;

View File

@@ -0,0 +1,62 @@
/**
* @param {string} icon
* @param {import('./types').Fetcher | undefined} fetcher
*
* @return {Promise<string>}
*/
export async function getIcon(icon, fetcher = fetch) {
if ((!isIconName(icon) && !isValidUrl(icon))) {
return icon;
}
else if (isValidUrl(icon)) {
const svg = (await fetcher(icon)).text();
return setIconDimensions(await svg);
}
const [collection, iconName] = icon.split(':');
const svg = (await fetcher(`https://api.iconify.design/${collection}/${iconName}.svg`)).text();
return setIconDimensions(await svg);
}
/**
* Checks if a given string is a valid [Iconify](https://iconify.design/)/[Icônes](https://icones.js.org/)-like icon name.
*
* @param {string} iconName
* @return {boolean}
*/
function isIconName(iconName) {
try {
const [collection, icon] = iconName.split(':');
return Boolean(collection) && Boolean(icon);
}
catch (_) {
return false;
}
}
/**
* @param {string} string
* @return {boolean}
*/
function isValidUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'https:' || url.protocol === 'http:';
}
catch (_) {
return false;
}
}
/**
* @param {string} svg
* @return {string}
*/
function setIconDimensions(svg) {
return svg
.replace(/width="([^"]*)"/, 'width="3.5em"')
.replace(/height="([^"]*)"/, '');
}

View File

@@ -2,6 +2,7 @@ import { html as htmlToVNodes } from 'satori-html';
import satori from 'satori';
import { generateBannerHtml } from './html';
import { getMonaSansFonts } from './fonts';
import { getIcon } from './icons';
/**
* @param {import('./types').BannerOptions} options
@@ -10,15 +11,18 @@ import { getMonaSansFonts } from './fonts';
export default async function banner({
title,
subtitle = '',
icon = '',
layout = 'horizontal',
colors = {
background: '#ffffff',
foreground: '#000000',
},
layout = 'horizontal',
libConfig: config = {},
font: customFonts,
rtl = false,
libConfig: config,
}) {
config.iconHandler ||= icon => getIcon(icon, config?.fetcher);
const dimensions = {
width: 1000,
height: 180,
@@ -38,9 +42,12 @@ export default async function banner({
const htmlTemplate = generateBannerHtml({ layout, dimensions, fonts: bannerFonts, colors, rtl });
const iconSvg = await config.iconHandler(icon);
const html = htmlTemplate
.replace('%%MARKNOW-PLACEHOLDER-TITLE%%', title)
.replace('%%MARKNOW-PLACEHOLDER-SUBTILE%%', subtitle);
.replace('%%MARKNOW-PLACEHOLDER-SUBTILE%%', subtitle)
.replace('%%MARKNOW-PLACEHOLDER-ICON%%', iconSvg);
const vNodes = htmlToVNodes(html);
@@ -56,6 +63,7 @@ export default async function banner({
html,
vNodes,
svg,
icon: iconSvg,
toString() { return svg; },
};
}

View File

@@ -10,6 +10,10 @@ export type Colors = {
background: string;
}
export type Fetcher = (
input: RequestInfo | URL,
init?: RequestInit
) => Promise<Response>;
export type Reader = (
path: PathLike | FileHandle,
@@ -21,6 +25,7 @@ export type Reader = (
* @package `@marknow/banners`
*/
export interface BannerOptions {
icon?: string,
title: string,
subtitle?: string,
layout?: 'horizontal' | 'vertical',
@@ -31,7 +36,9 @@ export interface BannerOptions {
colors?: Colors,
rtl?: boolean,
libConfig?: {
fetcher?: Fetcher = fetch,
reader?: Reader,
iconHandler?: (icon: string) => string | Promise<string>
}
}
@@ -42,8 +49,9 @@ export interface Banner {
toString(): string,
html: string,
svg: string,
icon: string,
vNodes: VNode,
}
};
/**
* **Copied from the satori-html package,**