style: format with new style
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ title: string; description?: string }>();
|
||||
defineProps<{ title: string; description?: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
tw="w-full h-full bg-black flex flex-col"
|
||||
style="background-image: url(https://fmhy.pages.dev/og.png)">
|
||||
<div tw="p-10 w-full min-h-0 grow flex flex-col items-center justify-between">
|
||||
style="background-image: url(https://fmhy.pages.dev/og.png)"
|
||||
>
|
||||
<div
|
||||
tw="p-10 w-full min-h-0 grow flex flex-col items-center justify-between"
|
||||
>
|
||||
<div tw="w-full flex justify-between items-center text-5xl font-medium">
|
||||
<div tw="flex items-center">
|
||||
<div tw="text-zinc-100 ml-2 mt-1 font-semibold">freemediaheckyeah</div>
|
||||
<div tw="text-zinc-100 ml-2 mt-1 font-semibold">
|
||||
freemediaheckyeah
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div tw="w-full pr-56 flex flex-col items-start justify-end">
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* Barrel generated using @taskylizard/tasker.
|
||||
*/
|
||||
|
||||
export * from "./meta";
|
||||
export * from "./opengraph";
|
||||
export * from "./rss";
|
||||
export * from "./satoriConfig";
|
||||
export * from './meta'
|
||||
export * from './opengraph'
|
||||
export * from './rss'
|
||||
export * from './satoriConfig'
|
||||
|
@@ -1,89 +1,100 @@
|
||||
import type { HeadConfig, TransformContext } from "vitepress";
|
||||
import type { HeadConfig, TransformContext } from 'vitepress'
|
||||
|
||||
export function generateMeta(context: TransformContext, hostname: string) {
|
||||
const head: HeadConfig[] = [];
|
||||
const { pageData } = context;
|
||||
const head: HeadConfig[] = []
|
||||
const { pageData } = context
|
||||
|
||||
const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, "$2")}`;
|
||||
const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2')}`
|
||||
|
||||
head.push(
|
||||
["link", { rel: "canonical", href: url }],
|
||||
["meta", { property: "og:url", content: url }],
|
||||
["meta", { name: "twitter:url", content: url }],
|
||||
["meta", { name: "twitter:card", content: "summary_large_image" }],
|
||||
["meta", { property: "og:title", content: pageData.frontmatter.title }],
|
||||
["meta", { name: "twitter:title", content: pageData.frontmatter.title }],
|
||||
);
|
||||
['link', { rel: 'canonical', href: url }],
|
||||
['meta', { property: 'og:url', content: url }],
|
||||
['meta', { name: 'twitter:url', content: url }],
|
||||
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
|
||||
['meta', { property: 'og:title', content: pageData.frontmatter.title }],
|
||||
['meta', { name: 'twitter:title', content: pageData.frontmatter.title }]
|
||||
)
|
||||
if (pageData.frontmatter.description) {
|
||||
head.push(
|
||||
[
|
||||
"meta",
|
||||
'meta',
|
||||
{
|
||||
property: "og:description",
|
||||
content: pageData.frontmatter.description,
|
||||
},
|
||||
property: 'og:description',
|
||||
content: pageData.frontmatter.description
|
||||
}
|
||||
],
|
||||
[
|
||||
"meta",
|
||||
'meta',
|
||||
{
|
||||
name: "twitter:description",
|
||||
content: pageData.frontmatter.description,
|
||||
},
|
||||
],
|
||||
);
|
||||
name: 'twitter:description',
|
||||
content: pageData.frontmatter.description
|
||||
}
|
||||
]
|
||||
)
|
||||
}
|
||||
if (pageData.frontmatter.image) {
|
||||
head.push([
|
||||
"meta",
|
||||
'meta',
|
||||
{
|
||||
property: "og:image",
|
||||
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`,
|
||||
},
|
||||
]);
|
||||
property: 'og:image',
|
||||
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, '')}`
|
||||
}
|
||||
])
|
||||
head.push([
|
||||
"meta",
|
||||
'meta',
|
||||
{
|
||||
name: "twitter:image",
|
||||
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`,
|
||||
},
|
||||
]);
|
||||
name: 'twitter:image',
|
||||
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, '')}`
|
||||
}
|
||||
])
|
||||
} else {
|
||||
const url = pageData.filePath.replace("index.md", "").replace(".md", "");
|
||||
const imageUrl = `${url}/__og_image__/og.png`.replaceAll("//", "/").replace(/^\//, "");
|
||||
const url = pageData.filePath.replace('index.md', '').replace('.md', '')
|
||||
const imageUrl = `${url}/__og_image__/og.png`
|
||||
.replaceAll('//', '/')
|
||||
.replace(/^\//, '')
|
||||
|
||||
head.push(
|
||||
["meta", { property: "og:image", content: `${hostname}/${imageUrl}` }],
|
||||
["meta", { property: "og:image:width", content: "1200" }],
|
||||
["meta", { property: "og:image:height", content: "628" }],
|
||||
["meta", { property: "og:image:type", content: "image/png" }],
|
||||
["meta", { property: "og:image:alt", content: pageData.frontmatter.title }],
|
||||
["meta", { name: "twitter:image", content: `${hostname}/${imageUrl}` }],
|
||||
["meta", { name: "twitter:image:width", content: "1200" }],
|
||||
["meta", { name: "twitter:image:height", content: "628" }],
|
||||
["meta", { name: "twitter:image:alt", content: pageData.frontmatter.title }],
|
||||
);
|
||||
['meta', { property: 'og:image', content: `${hostname}/${imageUrl}` }],
|
||||
['meta', { property: 'og:image:width', content: '1200' }],
|
||||
['meta', { property: 'og:image:height', content: '628' }],
|
||||
['meta', { property: 'og:image:type', content: 'image/png' }],
|
||||
[
|
||||
'meta',
|
||||
{ property: 'og:image:alt', content: pageData.frontmatter.title }
|
||||
],
|
||||
['meta', { name: 'twitter:image', content: `${hostname}/${imageUrl}` }],
|
||||
['meta', { name: 'twitter:image:width', content: '1200' }],
|
||||
['meta', { name: 'twitter:image:height', content: '628' }],
|
||||
[
|
||||
'meta',
|
||||
{ name: 'twitter:image:alt', content: pageData.frontmatter.title }
|
||||
]
|
||||
)
|
||||
}
|
||||
if (pageData.frontmatter.tag) {
|
||||
head.push(["meta", { property: "article:tag", content: pageData.frontmatter.tag }]);
|
||||
head.push([
|
||||
'meta',
|
||||
{ property: 'article:tag', content: pageData.frontmatter.tag }
|
||||
])
|
||||
}
|
||||
if (pageData.frontmatter.date) {
|
||||
head.push([
|
||||
"meta",
|
||||
'meta',
|
||||
{
|
||||
property: "article:published_time",
|
||||
content: pageData.frontmatter.date,
|
||||
},
|
||||
]);
|
||||
property: 'article:published_time',
|
||||
content: pageData.frontmatter.date
|
||||
}
|
||||
])
|
||||
}
|
||||
if (pageData.lastUpdated && pageData.frontmatter.lastUpdated !== false) {
|
||||
head.push([
|
||||
"meta",
|
||||
'meta',
|
||||
{
|
||||
property: "article:modified_time",
|
||||
content: new Date(pageData.lastUpdated).toISOString(),
|
||||
},
|
||||
]);
|
||||
property: 'article:modified_time',
|
||||
content: new Date(pageData.lastUpdated).toISOString()
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
return head;
|
||||
return head
|
||||
}
|
||||
|
@@ -1,71 +1,71 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createContentLoader } from "vitepress";
|
||||
import type { ContentData, SiteConfig } from "vitepress";
|
||||
import { type SatoriOptions, satoriVue } from "x-satori/vue";
|
||||
import { renderAsync } from "@resvg/resvg-js";
|
||||
import consola from "consola";
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { createContentLoader } from 'vitepress'
|
||||
import type { ContentData, SiteConfig } from 'vitepress'
|
||||
import { type SatoriOptions, satoriVue } from 'x-satori/vue'
|
||||
import { renderAsync } from '@resvg/resvg-js'
|
||||
import consola from 'consola'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const __fonts = resolve(__dirname, "../fonts");
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const __fonts = resolve(__dirname, '../fonts')
|
||||
|
||||
export async function generateImages(config: SiteConfig): Promise<void> {
|
||||
const pages = await createContentLoader("**/*.md", { excerpt: true }).load();
|
||||
const template = await readFile(resolve(__dirname, "./Template.vue"), "utf-8");
|
||||
const pages = await createContentLoader('**/*.md', { excerpt: true }).load()
|
||||
const template = await readFile(resolve(__dirname, './Template.vue'), 'utf-8')
|
||||
|
||||
const fonts: SatoriOptions["fonts"] = [
|
||||
const fonts: SatoriOptions['fonts'] = [
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Regular.otf")),
|
||||
name: 'Inter',
|
||||
data: await readFile(resolve(__fonts, 'Inter-Regular.otf')),
|
||||
weight: 400,
|
||||
style: "normal",
|
||||
style: 'normal'
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Medium.otf")),
|
||||
name: 'Inter',
|
||||
data: await readFile(resolve(__fonts, 'Inter-Medium.otf')),
|
||||
weight: 500,
|
||||
style: "normal",
|
||||
style: 'normal'
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-SemiBold.otf")),
|
||||
name: 'Inter',
|
||||
data: await readFile(resolve(__fonts, 'Inter-SemiBold.otf')),
|
||||
weight: 600,
|
||||
style: "normal",
|
||||
style: 'normal'
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Bold.otf")),
|
||||
name: 'Inter',
|
||||
data: await readFile(resolve(__fonts, 'Inter-Bold.otf')),
|
||||
weight: 700,
|
||||
style: "normal",
|
||||
},
|
||||
];
|
||||
style: 'normal'
|
||||
}
|
||||
]
|
||||
|
||||
for (const page of pages) {
|
||||
await generateImage({
|
||||
page,
|
||||
template,
|
||||
outDir: config.outDir,
|
||||
fonts,
|
||||
});
|
||||
fonts
|
||||
})
|
||||
}
|
||||
return consola.info("Generated opengraph images.");
|
||||
return consola.info('Generated opengraph images.')
|
||||
}
|
||||
|
||||
interface GenerateImagesOptions {
|
||||
page: ContentData;
|
||||
template: string;
|
||||
outDir: string;
|
||||
fonts: SatoriOptions["fonts"];
|
||||
page: ContentData
|
||||
template: string
|
||||
outDir: string
|
||||
fonts: SatoriOptions['fonts']
|
||||
}
|
||||
|
||||
async function generateImage({
|
||||
page,
|
||||
template,
|
||||
outDir,
|
||||
fonts,
|
||||
fonts
|
||||
}: GenerateImagesOptions): Promise<void> {
|
||||
const { frontmatter, url } = page;
|
||||
const { frontmatter, url } = page
|
||||
|
||||
const options: SatoriOptions = {
|
||||
width: 1200,
|
||||
@@ -73,24 +73,24 @@ async function generateImage({
|
||||
fonts,
|
||||
props: {
|
||||
title:
|
||||
frontmatter.layout === "home"
|
||||
frontmatter.layout === 'home'
|
||||
? frontmatter.hero.name ?? frontmatter.title
|
||||
: frontmatter.title,
|
||||
description:
|
||||
frontmatter.layout === "home"
|
||||
frontmatter.layout === 'home'
|
||||
? frontmatter.hero.tagline ?? frontmatter.description
|
||||
: frontmatter.description,
|
||||
},
|
||||
};
|
||||
: frontmatter.description
|
||||
}
|
||||
}
|
||||
|
||||
const svg = await satoriVue(options, template);
|
||||
const svg = await satoriVue(options, template)
|
||||
|
||||
const render = await renderAsync(svg);
|
||||
const render = await renderAsync(svg)
|
||||
|
||||
const outputFolder = resolve(outDir, url.slice(1), "__og_image__");
|
||||
const outputFile = resolve(outputFolder, "og.png");
|
||||
const outputFolder = resolve(outDir, url.slice(1), '__og_image__')
|
||||
const outputFile = resolve(outputFolder, 'og.png')
|
||||
|
||||
await mkdir(outputFolder, { recursive: true });
|
||||
await mkdir(outputFolder, { recursive: true })
|
||||
|
||||
await writeFile(outputFile, render.asPng());
|
||||
await writeFile(outputFile, render.asPng())
|
||||
}
|
||||
|
@@ -1,9 +1,13 @@
|
||||
import path from "node:path";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { Feed } from "feed";
|
||||
import { createContentLoader, type ContentData, type SiteConfig } from "vitepress";
|
||||
import consola from "consola";
|
||||
import { meta } from "../constants";
|
||||
import path from 'node:path'
|
||||
import { writeFileSync } from 'node:fs'
|
||||
import { Feed } from 'feed'
|
||||
import {
|
||||
createContentLoader,
|
||||
type ContentData,
|
||||
type SiteConfig
|
||||
} from 'vitepress'
|
||||
import consola from 'consola'
|
||||
import { meta } from '../constants'
|
||||
|
||||
export async function generateFeed(config: SiteConfig): Promise<void> {
|
||||
const feed: Feed = new Feed({
|
||||
@@ -11,32 +15,35 @@ export async function generateFeed(config: SiteConfig): Promise<void> {
|
||||
link: meta.hostname,
|
||||
title: `FMHY blog`,
|
||||
description: meta.description,
|
||||
language: "en-US",
|
||||
image: "https://github.com/fmhy.png",
|
||||
language: 'en-US',
|
||||
image: 'https://github.com/fmhy.png',
|
||||
favicon: `${meta.hostname}/favicon.ico`,
|
||||
copyright: `Copyright (c) 2023-present FMHY`,
|
||||
});
|
||||
copyright: `Copyright (c) 2023-present FMHY`
|
||||
})
|
||||
|
||||
const posts: ContentData[] = await createContentLoader("posts/*.md", {
|
||||
const posts: ContentData[] = await createContentLoader('posts/*.md', {
|
||||
excerpt: true,
|
||||
render: true,
|
||||
transform: (rawData) => {
|
||||
return rawData.sort((a, b) => {
|
||||
return Number(new Date(b.frontmatter.date)) - Number(new Date(a.frontmatter.date));
|
||||
});
|
||||
},
|
||||
}).load();
|
||||
return (
|
||||
Number(new Date(b.frontmatter.date)) -
|
||||
Number(new Date(a.frontmatter.date))
|
||||
)
|
||||
})
|
||||
}
|
||||
}).load()
|
||||
|
||||
for (const { url, frontmatter, html } of posts) {
|
||||
feed.addItem({
|
||||
title: frontmatter.title as string,
|
||||
id: `${meta.hostname}${url.replace(/\/\d+\./, "/")}`,
|
||||
link: `${meta.hostname}${url.replace(/\/\d+\./, "/")}`,
|
||||
id: `${meta.hostname}${url.replace(/\/\d+\./, '/')}`,
|
||||
link: `${meta.hostname}${url.replace(/\/\d+\./, '/')}`,
|
||||
date: frontmatter.date,
|
||||
content: html!,
|
||||
});
|
||||
content: html!
|
||||
})
|
||||
}
|
||||
|
||||
writeFileSync(path.join(config.outDir, "feed.rss"), feed.rss2());
|
||||
return consola.info("Generated rss feed.");
|
||||
writeFileSync(path.join(config.outDir, 'feed.rss'), feed.rss2())
|
||||
return consola.info('Generated rss feed.')
|
||||
}
|
||||
|
@@ -1,47 +1,47 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { SatoriOptions } from "x-satori/vue";
|
||||
import { defineSatoriConfig } from "x-satori/vue";
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { SatoriOptions } from 'x-satori/vue'
|
||||
import { defineSatoriConfig } from 'x-satori/vue'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const __fonts = resolve(__dirname, "../fonts");
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const __fonts = resolve(__dirname, '../fonts')
|
||||
|
||||
const fonts: SatoriOptions["fonts"] = [
|
||||
const fonts: SatoriOptions['fonts'] = [
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Regular.otf")),
|
||||
name: 'Inter',
|
||||
data: await readFile(resolve(__fonts, 'Inter-Regular.otf')),
|
||||
weight: 400,
|
||||
style: "normal",
|
||||
style: 'normal'
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Medium.otf")),
|
||||
name: 'Inter',
|
||||
data: await readFile(resolve(__fonts, 'Inter-Medium.otf')),
|
||||
weight: 500,
|
||||
style: "normal",
|
||||
style: 'normal'
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-SemiBold.otf")),
|
||||
name: 'Inter',
|
||||
data: await readFile(resolve(__fonts, 'Inter-SemiBold.otf')),
|
||||
weight: 600,
|
||||
style: "normal",
|
||||
style: 'normal'
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Bold.otf")),
|
||||
name: 'Inter',
|
||||
data: await readFile(resolve(__fonts, 'Inter-Bold.otf')),
|
||||
weight: 700,
|
||||
style: "normal",
|
||||
},
|
||||
];
|
||||
style: 'normal'
|
||||
}
|
||||
]
|
||||
|
||||
export default defineSatoriConfig({
|
||||
width: 1200,
|
||||
height: 628,
|
||||
fonts,
|
||||
props: {
|
||||
title: "Title",
|
||||
title: 'Title',
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.",
|
||||
dir: "/j",
|
||||
},
|
||||
});
|
||||
'Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.',
|
||||
dir: '/j'
|
||||
}
|
||||
})
|
||||
|
Reference in New Issue
Block a user