refactor: ♻️ move and refactor code to the package
Moved and refactored most of the banner creation code to the @marknow/banner package.
This commit is contained in:
@@ -226,6 +226,7 @@ module.exports = {
|
||||
'duotone',
|
||||
'tsconfig',
|
||||
'workspace',
|
||||
'woff',
|
||||
],
|
||||
minLength: 4,
|
||||
}],
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/src/lib/components.d.ts
|
||||
/src/lib/imports.d.ts
|
||||
/.eslint-auto-import.json
|
||||
*.woff
|
||||
*.woff2
|
||||
|
||||
@@ -1,33 +1,12 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import satori from 'satori';
|
||||
import { html as satoriHtml } from 'satori-html';
|
||||
import Banner from './Banner.html?raw';
|
||||
import font400 from '$lib/assets/Mona-Sans-Regular.woff?url';
|
||||
import font600 from '$lib/assets/Mona-Sans-SemiBold.woff?url';
|
||||
import newBanner from '@marknow/banners';
|
||||
|
||||
export const GET = (async ({ fetch }): Promise<Response> => {
|
||||
const html = satoriHtml(Banner);
|
||||
|
||||
const banner = await satori(html,
|
||||
{
|
||||
width: 1000,
|
||||
height: 180,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Mona Sans',
|
||||
weight: 400,
|
||||
style: 'normal',
|
||||
data: await (await fetch(font400)).arrayBuffer(),
|
||||
},
|
||||
{
|
||||
name: 'Mona Sans',
|
||||
weight: 600,
|
||||
style: 'normal',
|
||||
data: await (await fetch(font600)).arrayBuffer(),
|
||||
}],
|
||||
export const GET = (async (): Promise<Response> => {
|
||||
const banner = await newBanner({
|
||||
title: 'Hello world',
|
||||
});
|
||||
|
||||
return new Response(banner, {
|
||||
return new Response(`${banner.toString()}`, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-type': 'image/svg+xml',
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<div style="
|
||||
display: flex;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
width: 1000px;
|
||||
height: 180px;
|
||||
">
|
||||
<div style="
|
||||
box-shadow: 0 5px 12px #00000040;
|
||||
position: relative;
|
||||
font-family: 'Mona Sans';
|
||||
background-color: white;
|
||||
margin: auto;
|
||||
border-radius: 1em;
|
||||
padding: 1.2em 2.5em;
|
||||
display: flex;
|
||||
min-width: 98%;
|
||||
min-height: 20%;
|
||||
gap: 1em;
|
||||
">
|
||||
<div style="
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin: 1.5em 0;
|
||||
">
|
||||
<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>
|
||||
</div>
|
||||
<div style="
|
||||
align-items: center;
|
||||
display: flex;
|
||||
">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<h1 style="margin: 0; font-weight: 600;">Marknow</h1>
|
||||
<sub style="font-size: medium; font-weight: 400;">
|
||||
Create beautiful markdown for your projects with ease
|
||||
</sub>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +1,3 @@
|
||||
/dist
|
||||
*.woff
|
||||
*.woff2
|
||||
|
||||
29
packages/banners/src/fonts.js
Normal file
29
packages/banners/src/fonts.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
/**
|
||||
* @param {import('./types').Reader | undefined} reader
|
||||
* @typedef {import('satori').SatoriOptions['fonts'][0]} Font
|
||||
* @returns {Promise<{regular: Font, bold: Font}>}
|
||||
*/
|
||||
export async function getMonaSansFonts(reader) {
|
||||
reader ||= (await import('node:fs/promises')).readFile;
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
return {
|
||||
regular: {
|
||||
name: 'Mona Sans',
|
||||
weight: 400,
|
||||
style: 'normal',
|
||||
data: await reader(join(__dirname, './assets/Mona-Sans-Regular.woff')),
|
||||
},
|
||||
bold: {
|
||||
name: 'Mona Sans',
|
||||
weight: 600,
|
||||
style: 'normal',
|
||||
data: await reader(join(__dirname, './assets/Mona-Sans-SemiBold.woff')),
|
||||
},
|
||||
};
|
||||
}
|
||||
85
packages/banners/src/html.js
Normal file
85
packages/banners/src/html.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/* 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.
|
||||
*
|
||||
* @param {'vertical' | 'horizontal'} layout
|
||||
* @param {{width: number, height: number}} dimensions
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
export function generateBannerHtml(layout, dimensions) {
|
||||
/** @type {boolean} */
|
||||
const horizontal = layout === 'horizontal';
|
||||
|
||||
return `
|
||||
<div style="
|
||||
display: flex;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
width: ${dimensions.width}px;
|
||||
height: ${dimensions.height}px;
|
||||
">
|
||||
<div style="
|
||||
box-shadow: 0 5px 12px #00000040;
|
||||
position: relative;
|
||||
font-family: 'Mona Sans';
|
||||
background-color: white;
|
||||
margin: auto;
|
||||
border-radius: 1em;
|
||||
padding: ${horizontal ? '1.2' : '2.5'}em 2.5em;
|
||||
display: flex;
|
||||
${horizontal
|
||||
? 'flex-direction: row;'
|
||||
: 'flex-direction: column;'
|
||||
}
|
||||
align-items: center;
|
||||
min-width: 98%;
|
||||
min-height: 20%;
|
||||
gap: 1em;
|
||||
">
|
||||
<div style="
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin: ${horizontal ? '1.5' : '0'}em 0;
|
||||
">
|
||||
<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>
|
||||
</div>
|
||||
<div style="
|
||||
align-items: center;
|
||||
display: flex;
|
||||
">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<h1 style="
|
||||
margin: ${horizontal ? '0' : '0 0 1em 0'};
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 50em;
|
||||
${horizontal ? 'text-align: start;' : 'text-align: center;'}
|
||||
">
|
||||
%%MARKNOW-PLACEHOLDER-TITLE%%
|
||||
</h1>
|
||||
<sub style="
|
||||
font-size: medium;
|
||||
font-weight: 400;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 50em;
|
||||
${horizontal ? 'text-align: start;' : 'text-align: center;'}
|
||||
">
|
||||
%%MARKNOW-PLACEHOLDER-SUBTILE%%
|
||||
</sub>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
3
packages/banners/src/index.d.ts
vendored
Normal file
3
packages/banners/src/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { BannerOptions, Banner } from "./types";
|
||||
|
||||
export default async function banner(options: BannerOptions): Promise<Banner>;
|
||||
43
packages/banners/src/index.js
Normal file
43
packages/banners/src/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { html as htmlToVNodes } from 'satori-html';
|
||||
import satori from 'satori';
|
||||
import { generateBannerHtml } from './html';
|
||||
import { getMonaSansFonts } from './fonts';
|
||||
|
||||
/**
|
||||
* @param {import('./types').BannerOptions} options
|
||||
* @returns {Promise<import('./types').Banner>}
|
||||
*/
|
||||
export default async function banner({
|
||||
title,
|
||||
subtitle = '',
|
||||
layout = 'horizontal',
|
||||
config,
|
||||
}) {
|
||||
const dimensions = {
|
||||
width: 1000,
|
||||
height: layout === 'horizontal' ? 180 : 680,
|
||||
};
|
||||
|
||||
const bannerFonts = await getMonaSansFonts(config?.reader);
|
||||
|
||||
const html = generateBannerHtml(layout, dimensions)
|
||||
.replace('%%MARKNOW-PLACEHOLDER-TITLE%%', title)
|
||||
.replace('%%MARKNOW-PLACEHOLDER-SUBTILE%%', subtitle);
|
||||
|
||||
const vNodes = htmlToVNodes(html);
|
||||
|
||||
const svg = await satori(vNodes, {
|
||||
...dimensions,
|
||||
fonts: [
|
||||
bannerFonts.bold,
|
||||
bannerFonts.regular,
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
html,
|
||||
vNodes,
|
||||
svg,
|
||||
toString() { return svg; },
|
||||
};
|
||||
}
|
||||
46
packages/banners/src/types.d.ts
vendored
Normal file
46
packages/banners/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { Abortable } from "node:events";
|
||||
import type { OpenMode, PathLike } from "node:fs";
|
||||
import type { FileHandle } from "node:fs/promises";
|
||||
|
||||
export type Reader = (
|
||||
path: PathLike | FileHandle,
|
||||
) => Promise<Buffer | ArrayBuffer>
|
||||
|
||||
/**
|
||||
* Options object for creating a banner.
|
||||
*
|
||||
* @package `@marknow/banners`
|
||||
*/
|
||||
export interface BannerOptions {
|
||||
title: string,
|
||||
subtitle?: string,
|
||||
layout?: 'horizontal' | 'vertical' = 'horizontal',
|
||||
config?: {
|
||||
reader?: Reader,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface Banner {
|
||||
toString(): string,
|
||||
html: string,
|
||||
svg: string,
|
||||
vNodes: VNode,
|
||||
}
|
||||
|
||||
/**
|
||||
* **Copied from the satori-html package,**
|
||||
* React-element-like objects / VDOM object used in satori.
|
||||
*
|
||||
* @package `satori-html`
|
||||
*/
|
||||
export interface VNode {
|
||||
type: string;
|
||||
props: {
|
||||
style?: Record<string, any>;
|
||||
children?: string | VNode | VNode[];
|
||||
[prop: string]: any;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user