diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 0682156..54ea248 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -227,6 +227,10 @@ module.exports = { 'tsconfig', 'workspace', 'woff', + 'marknow', + 'lored', + 'guz013', + 'xml', ], minLength: 4, }], diff --git a/packages/banners/src/fonts.js b/packages/banners/src/fonts.js index a2ba759..86c25ff 100644 --- a/packages/banners/src/fonts.js +++ b/packages/banners/src/fonts.js @@ -2,9 +2,14 @@ import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; /** - * @param {import('./types').Reader | undefined} reader + * @param {import('./types').Reader | undefined} [reader=import('node:fs').readFile] + * The function to be used as reader of the local files. + * * @typedef {import('satori').SatoriOptions['fonts'][0]} Font * @returns {Promise<{subtitle: Font, title: Font}>} + * + * @module \@marknow/banners + * @access protected */ export async function getMonaSansFonts(reader) { reader ||= (await import('node:fs/promises')).readFile; diff --git a/packages/banners/src/html.js b/packages/banners/src/html.js index aa653ff..8118b2a 100644 --- a/packages/banners/src/html.js +++ b/packages/banners/src/html.js @@ -1,11 +1,10 @@ /* eslint-disable complexity */ /* eslint-disable @typescript-eslint/indent */ /** - * Returns the html string of the banner to be used by satori. - * Use the params to customize and complete it. + * Constructor of the HTML banner converted to VNodes to be used in satori. + * Use the properties to customize and complete it. * * @typedef {import('satori').SatoriOptions['fonts'][0]} Font - * * @typedef {{ * dimensions: { width: number, height: number }, * fonts: { title: Font, subtitle: Font }, @@ -14,8 +13,12 @@ * rtl: boolean, * }} Props * @param {Props} properties + * Properties to be applied on the html. * - * @return {string} + * @returns {string} + * + * @module \@marknow/banners + * @access protected */ export default function html({ dimensions, diff --git a/packages/banners/src/icons.js b/packages/banners/src/icons.js index 9ff2234..1dc3be3 100644 --- a/packages/banners/src/icons.js +++ b/packages/banners/src/icons.js @@ -1,8 +1,20 @@ /** - * @param {string} icon - * @param {import('./types').Fetcher | undefined} fetcher +/** + * Utility function used to get a SVG icon from Iconify OR from an url passed as the icon. * - * @return {Promise} + * If the `icon` parameter is not a valid icon name or url, + * it returns the `icon` itself. + * + * @param {string} icon + * The icon's name or url endpoint. + * + * @param {import('./types').Fetcher | undefined} [fetcher=globalThis.fetch] + * Fetch function to be used. + * + * @returns {Promise} + * + * @module \@marknow/banners + * @access protected */ export async function getIcon(icon, fetcher = fetch) { if (!isIconName(icon) && !isValidUrl(icon)) { @@ -19,6 +31,20 @@ export async function getIcon(icon, fetcher = fetch) { return svg; } +/** + * Utility function used to set the icons SVG width and height to the specified dimensions. + * + * @param {string} svg + * The svg string. + * + * @param {{width?: string | number, height?: string | number}} dimensions + * The dimensions values, if type number it is converted to pixels. + * + * @returns {string} + * + * @module \@marknow/banners + * @access protected + */ export function setIconDimensions(svg, { width, height }) { width = typeof width === 'number' ? `width="${width}px"` @@ -38,10 +64,14 @@ export function setIconDimensions(svg, { width, height }) { } /** - * Checks if a given string is a valid [Iconify](https://iconify.design/)/[Icônes](https://icones.js.org/)-like icon name. + * 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} + * @param {string} string The string to be checked. + * @returns {boolean} + * + * @module \@marknow/banners + * @access package */ function isIconName(string) { try { @@ -54,8 +84,13 @@ function isIconName(string) { } /** - * @param {string} string - * @return {boolean} + * Checks if string is a valid URL. + * + * @param {string} string The string to be checked. + * @returns {boolean} + * + * @module \@marknow/banners + * @access package */ function isValidUrl(string) { try { diff --git a/packages/banners/src/index.d.ts b/packages/banners/src/index.d.ts index 01e68c9..060446a 100644 --- a/packages/banners/src/index.d.ts +++ b/packages/banners/src/index.d.ts @@ -1,7 +1,50 @@ -/** - * @param {import('./types').BannerOptions} options - * @returns {Promise} - */ -export default function banner({ title, subtitle, icon, layout, config, }: import('./types').BannerOptions): Promise; +/** + +/** + * The banner constructor function. Use the options to customize the + * appearance of the resulting banner. + * + * @param {import('./types').BannerOptions} options + * Options object for customizing the banner appearance. + * + * @returns {Promise} + * + * @example + * import newBanner from '@marknow/banners'; + * + * export async function GET({ fetch }) { + * const banner = await newBanner({ + * title: 'Hello world', + * subtitle: 'This is a example api endpoint.' + * icon: 'material-symbols:api' + * fonts: { + * title: { + * data: await (await fetch('/Mona-Sans-SemiBold.woff')).arrayBuffer(), + * name: 'Mona Sans', + * weight: 600, + * }, + * subtitle: { + * data: await (await fetch('/Mona-Sans-Regular.woff')).arrayBuffer(), + * name: 'Mona Sans', + * weight: 400, + * }, + * }, + * libConfig: { + * fetcher: fetch, + * }, + * }); + * + * return new Response(banner.toString(), { + * status: 200, + * headers: { + * 'Content-type': 'image/svg+xml', + * }, + * }); + * } + * + * @module \@marknow/banners + * @access public + */ +export default function banner({ title, subtitle, icon, layout, config, }: import('./types').BannerOptions): Promise; export type { BannerOptions, Banner } from './types' diff --git a/packages/banners/src/index.js b/packages/banners/src/index.js index 8c0367e..e100ede 100644 --- a/packages/banners/src/index.js +++ b/packages/banners/src/index.js @@ -5,8 +5,49 @@ import { getMonaSansFonts } from './fonts'; import { getIcon, setIconDimensions } from './icons'; /** + * The banner constructor function. Use the options to customize the + * appearance of the resulting banner. + * * @param {import('./types').BannerOptions} options + * Options object for customizing the banner appearance. + * * @returns {Promise} + * + * @example + * import newBanner from '@marknow/banners'; + * + * export async function GET({ fetch }) { + * const banner = await newBanner({ + * title: 'Hello world', + * subtitle: 'This is a example api endpoint.' + * icon: 'material-symbols:api' + * fonts: { + * title: { + * data: await (await fetch('/Mona-Sans-SemiBold.woff')).arrayBuffer(), + * name: 'Mona Sans', + * weight: 600, + * }, + * subtitle: { + * data: await (await fetch('/Mona-Sans-Regular.woff')).arrayBuffer(), + * name: 'Mona Sans', + * weight: 400, + * }, + * }, + * libConfig: { + * fetcher: fetch, + * }, + * }); + * + * return new Response(banner.toString(), { + * status: 200, + * headers: { + * 'Content-type': 'image/svg+xml', + * }, + * }); + * } + * + * @module \@marknow/banners + * @access public */ export default async function banner({ title, @@ -60,7 +101,17 @@ export default async function banner({ }; } -/** @type {(string: string, maxChar: number) => string} */ +/** + * Small utility function used to truncate long texts on the banner + * + * @param {string} string - Text string to be truncated. + * @param {number} maxChar - Maximum number of characters. + * + * @returns {string} + * + * @module \@marknow/banners + * @access package + */ function truncateText(string, maxChar) { return string.length > maxChar ? `${string.slice(0, maxChar)}...` : string; } diff --git a/packages/banners/src/types.d.ts b/packages/banners/src/types.d.ts index e34a475..d20a321 100644 --- a/packages/banners/src/types.d.ts +++ b/packages/banners/src/types.d.ts @@ -3,6 +3,170 @@ import type { OpenMode, PathLike } from "node:fs"; import type { FileHandle } from "node:fs/promises"; import type { SatoriOptions } from "satori/wasm"; +/** + * Options object for customizing the banner appearance. + * + * @module \@marknow/banners + * @access public + */ +export interface BannerOptions { + /** + * **(REQUIRED)** Title to be displayed in the banner. + */ + title: string, + + /** + * (Optional) Set the text direction right-to-left. + */ + rtl?: boolean = false, + + /** + * (Optional) {@link https://iconify.design/ Iconify}/{@link https://icones.js.org/ Icônes}-like + * icon name OR url for svg file of the icon to be used in the banner. + */ + icon?: string = '', + + /** + * (Optional) Subtitle/legend to be displayed in the banner + * bellow the title. + */ + subtitle?: string = '', + + /** + * (Optional) Customize the colors of the banner. + * + * Customize the background color and foreground (title, subtitle + * and icon) colors. + */ + colors?: Colors = { background: '#ffffff', foreground: '#000000' }, + + /** + * (Optional) Customize the layout of the banner and position of elements. + */ + layout?: 'horizontal' | 'vertical' = 'horizontal', + + /** + * (Optional) Customize the fonts used on the banner. + * + * Changes the default font file used for the title and subtitle. + * Defaults is {@link https://github.com/github/mona-sans Github's Mona Sans} + * semi-bold and regular respectively. + * + * @see {@link https://github.com/vercel/satori#fonts} + */ + fonts?: { + title: Font, + subtitle: Font, + } + + /** + * (Optional) Customize the behavior of the package. + * + * Provide functions or polyfills to be used by the package + * for better compatibility or customization of the banner + * creation. + */ + libConfig?: { + /** + * (Optional) Fetch function used by the package to retrieve + * icons from {@link https://iconify.design/ Iconify} and custom + * icons provided as url. + * + * Default function used is the + * {@link https://developer.mozilla.org/en-US/docs/Web/API/fetch globalThis.fetch} + * function. Compatible with modern browsers, Node.js (version 18 and greater), + * Deno and Bun. + * + * @see {@link BannerOptions.icon} + * + * @param {RequestInfo} input - The request url/info. + * @param {RequestInit} - Request options. + * @returns {Promise} + */ + fetcher?: Fetcher = globalThis.fetch, + + /** + * (Optional) The function used to read the font files and return a Buffer or + * ArrayBuffer from them. + * + * Default function used is the {@link https://nodejs.org/api/fs.html#fsreadfilepath-options-callback fs.readFile} + * from the Node file system promises api ({@link https://nodejs.org/api/fs.html#file-system node:fs/promises}). + * Compatible with Node.js (version 10 and greater), Deno and Bun. + * + * @param {PathLike | FileHandle} path - The path to the font files. + * @returns {Promise} + */ + reader?: Reader = import ('node:fs/promises').readFile, + + /** + * (Optional) The function used to get the icon svg file from {@link https://iconify.design/ Iconify} + * or URL endpoint passed. + * + * @see {@link BannerOptions.icon} + * + * @param {string} icon - Icon name or URL. + * @returns {string | Promise} + */ + iconHandler?: (icon: string) => string | Promise + } +} + +/** + * The resulting banner object. + * + * Has a `toString()` function to be used in string literals + * that returns the svg string of the banner. + * + * @module \@marknow/banners + * @access public + */ +export interface Banner { + /** + * The resulting svg of the banner. + * @readonly + */ + svg: string, + /** + * The raw html used to create the banner. + * @readonly + */ + html: string, + /** + * The used icon's svg. + * @readonly + */ + icon: string, + /** + * React-element-like objects / VDOM used to create the banner. + * @readonly + */ + vNodes: VNode, + /** + * Returns the {@link Banner.svg svg string} of the banner. + * Useful when using the banner object directly on a string. + * + * @example + * import newBanner from '@marknow/banners'; + * + * const banner = await newBanner({ ... }); + * + * // Prints the resulting svg instead of the banner object itself. + * console.log(`Banner svg:\n${banner}`) + * + * @readonly + */ + toString(): string, +}; + +/** + * Font object for the banner passed to the `satori` package. + * + * @see {@link BannerOptions.fonts} + * @see {@link https://github.com/vercel/satori#fonts} + * + * @module \@marknow/banners > satori + * @access protected + */ export type Font = SatoriOptions['fonts'][0]; export type Colors = { @@ -10,54 +174,43 @@ export type Colors = { background: string; } +/** + * "Global Fetch"-like function used by the package to retrieve + * icons from [Iconify](https://iconify.design/) and custom + * icons provided as url. + * + * @param {RequestInfo} input - The request url/info. + * @param {RequestInit} - Request options. + * @returns {Promise} + * + * @module \@marknow/banners + * @access protected + */ export type Fetcher = ( input: RequestInfo | URL, init?: RequestInit ) => Promise; +/** + * "Node.js' `fs.readFile`"-like function used to read the font files + * and return a Buffer or ArrayBuffer from them. + * + * @param {RequestInfo} input - The request url/info. + * @param {RequestInit} - Request options. + * @returns {Promise} + * + * @module \@marknow/banners + * @access protected + */ export type Reader = ( path: PathLike | FileHandle, ) => Promise /** - * Options object for creating a banner. - * - * @package `@marknow/banners` - */ -export interface BannerOptions { - icon?: string, - title: string, - subtitle?: string, - layout?: 'horizontal' | 'vertical', - fonts?: { - title: Font, - subtitle: Font, - } - colors?: Colors, - rtl?: boolean, - libConfig?: { - fetcher?: Fetcher = fetch, - reader?: Reader, - iconHandler?: (icon: string) => string | Promise - } -} - -/** - * - */ -export interface Banner { - toString(): string, - html: string, - svg: string, - icon: string, - vNodes: VNode, -}; - -/** - * **Copied from the satori-html package,** * React-element-like objects / VDOM object used in satori. * - * @package `satori-html` + * @module \@marknow/banners > satori-html + * @access protected */ export interface VNode { type: string;