style: format with new style
This commit is contained in:
parent
bc3bbaafeb
commit
ef422dcda8
2
.github/workflows/deploy-api.yml
vendored
2
.github/workflows/deploy-api.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
|||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: "pnpm"
|
cache: 'pnpm'
|
||||||
|
|
||||||
- run: pnpm install --no-frozen-lockfile
|
- run: pnpm install --no-frozen-lockfile
|
||||||
|
|
||||||
|
2
.github/workflows/deploy-gh-pages.yml
vendored
2
.github/workflows/deploy-gh-pages.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: '3.10'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
- name: Preprocess files
|
- name: Preprocess files
|
||||||
|
4
.github/workflows/single-page.yml
vendored
4
.github/workflows/single-page.yml
vendored
@ -19,10 +19,10 @@ jobs:
|
|||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: '3.10'
|
||||||
|
|
||||||
- name: Create local changes
|
- name: Create local changes
|
||||||
run: python .github/single-page.py
|
run: python .github/single-page.py
|
||||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
- uses: stefanzweifel/git-auto-commit-action@v4
|
||||||
with:
|
with:
|
||||||
commit_message: "♻️ update single page"
|
commit_message: '♻️ update single page'
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": true,
|
|
||||||
"printWidth": 100,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"singleQuote": false,
|
|
||||||
"quoteProps": "as-needed",
|
|
||||||
"jsxSingleQuote": false,
|
|
||||||
"trailingComma": "all",
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"bracketSameLine": true,
|
|
||||||
"arrowParens": "always",
|
|
||||||
"proseWrap": "always"
|
|
||||||
}
|
|
6
.prettierrc.yaml
Normal file
6
.prettierrc.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
proseWrap: always
|
||||||
|
semi: false
|
||||||
|
singleQuote: true
|
||||||
|
printWidth: 80
|
||||||
|
trailingComma: none
|
||||||
|
htmlWhitespaceSensitivity: ignore
|
@ -1,93 +1,108 @@
|
|||||||
import { defineConfig } from "vitepress";
|
import { defineConfig } from 'vitepress'
|
||||||
import UnoCSS from "unocss/vite";
|
import UnoCSS from 'unocss/vite'
|
||||||
import consola from "consola";
|
import consola from 'consola'
|
||||||
import { commitRef, feedback, meta, search, sidebar, socialLinks } from "./constants";
|
import {
|
||||||
import { generateImages, generateMeta, generateFeed } from "./hooks";
|
commitRef,
|
||||||
import { toggleStarredPlugin } from "./markdown/toggleStarred";
|
feedback,
|
||||||
import { base64DecodePlugin } from "./markdown/base64";
|
meta,
|
||||||
import { movePlugin, emojiRender, defs } from "./markdown/emoji";
|
search,
|
||||||
|
sidebar,
|
||||||
|
socialLinks
|
||||||
|
} from './constants'
|
||||||
|
import { generateImages, generateMeta, generateFeed } from './hooks'
|
||||||
|
import { toggleStarredPlugin } from './markdown/toggleStarred'
|
||||||
|
import { base64DecodePlugin } from './markdown/base64'
|
||||||
|
import { movePlugin, emojiRender, defs } from './markdown/emoji'
|
||||||
|
|
||||||
const baseUrl = process.env.GITHUB_ACTIONS ? "/FMHYedit" : "/";
|
const baseUrl = process.env.GITHUB_ACTIONS ? '/FMHYedit' : '/'
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
title: "FMHY",
|
title: 'FMHY',
|
||||||
description: meta.description,
|
description: meta.description,
|
||||||
titleTemplate: ":title • freemediaheckyeah",
|
titleTemplate: ':title • freemediaheckyeah',
|
||||||
lang: "en-US",
|
lang: 'en-US',
|
||||||
lastUpdated: false,
|
lastUpdated: false,
|
||||||
cleanUrls: true,
|
cleanUrls: true,
|
||||||
appearance: "dark",
|
appearance: 'dark',
|
||||||
base: baseUrl,
|
base: baseUrl,
|
||||||
srcExclude: ["readme.md", "single-page"],
|
srcExclude: ['readme.md', 'single-page'],
|
||||||
ignoreDeadLinks: true,
|
ignoreDeadLinks: true,
|
||||||
sitemap: {
|
sitemap: {
|
||||||
hostname: meta.hostname,
|
hostname: meta.hostname
|
||||||
},
|
},
|
||||||
head: [
|
head: [
|
||||||
["meta", { name: "theme-color", content: "#7bc5e4" }],
|
['meta', { name: 'theme-color', content: '#7bc5e4' }],
|
||||||
["meta", { name: "og:type", content: "website" }],
|
['meta', { name: 'og:type', content: 'website' }],
|
||||||
["meta", { name: "og:locale", content: "en" }],
|
['meta', { name: 'og:locale', content: 'en' }],
|
||||||
["link", { rel: "icon", href: "/test.png" }],
|
['link', { rel: 'icon', href: '/test.png' }],
|
||||||
// PWA
|
// PWA
|
||||||
["link", { rel: "icon", href: "/test.png", type: "image/svg+xml" }],
|
['link', { rel: 'icon', href: '/test.png', type: 'image/svg+xml' }],
|
||||||
["link", { rel: "alternate icon", href: "/test.png" }],
|
['link', { rel: 'alternate icon', href: '/test.png' }],
|
||||||
["link", { rel: "mask-icon", href: "/test.png", color: "#7bc5e4" }],
|
['link', { rel: 'mask-icon', href: '/test.png', color: '#7bc5e4' }],
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
["meta", { name: "keywords", content: meta.keywords.join(" ") }],
|
["meta", { name: "keywords", content: meta.keywords.join(" ") }],
|
||||||
["link", { rel: "apple-touch-icon", href: "/test.png", sizes: "192x192" }],
|
['link', { rel: 'apple-touch-icon', href: '/test.png', sizes: '192x192' }]
|
||||||
],
|
],
|
||||||
transformHead: async (context) => generateMeta(context, meta.hostname),
|
transformHead: async (context) => generateMeta(context, meta.hostname),
|
||||||
buildEnd: async (context) => {
|
buildEnd: async (context) => {
|
||||||
generateImages(context)
|
generateImages(context)
|
||||||
.then(() => generateFeed(context))
|
.then(() => generateFeed(context))
|
||||||
.finally(() => consola.success("Success!"));
|
.finally(() => consola.success('Success!'))
|
||||||
},
|
},
|
||||||
vite: {
|
vite: {
|
||||||
optimizeDeps: { exclude: ["workbox-window"] },
|
optimizeDeps: { exclude: ['workbox-window'] },
|
||||||
plugins: [
|
plugins: [
|
||||||
UnoCSS({
|
UnoCSS({
|
||||||
configFile: "../unocss.config.ts",
|
configFile: '../unocss.config.ts'
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "custom:adjust-order",
|
name: 'custom:adjust-order',
|
||||||
configResolved(c) {
|
configResolved(c) {
|
||||||
movePlugin(c.plugins as any, "vitepress", "before", "unocss:transformers:pre");
|
movePlugin(
|
||||||
},
|
c.plugins as any,
|
||||||
},
|
'vitepress',
|
||||||
|
'before',
|
||||||
|
'unocss:transformers:pre'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
// Shut the fuck up
|
// Shut the fuck up
|
||||||
chunkSizeWarningLimit: Number.POSITIVE_INFINITY,
|
chunkSizeWarningLimit: Number.POSITIVE_INFINITY
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
markdown: {
|
markdown: {
|
||||||
emoji: { defs },
|
emoji: { defs },
|
||||||
config(md) {
|
config(md) {
|
||||||
md.use(emojiRender);
|
md.use(emojiRender)
|
||||||
md.use(toggleStarredPlugin);
|
md.use(toggleStarredPlugin)
|
||||||
md.use(base64DecodePlugin);
|
md.use(base64DecodePlugin)
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
search,
|
search,
|
||||||
footer: {
|
footer: {
|
||||||
message: `${feedback} (rev: ${commitRef})`,
|
message: `${feedback} (rev: ${commitRef})`
|
||||||
},
|
},
|
||||||
outline: "deep",
|
outline: 'deep',
|
||||||
logo: "/fmhy.ico",
|
logo: '/fmhy.ico',
|
||||||
nav: [
|
nav: [
|
||||||
{ text: "Beginners Guide", link: "/beginners-guide" },
|
{ text: 'Beginners Guide', link: '/beginners-guide' },
|
||||||
{ text: "Glossary", link: "https://rentry.org/The-Piracy-Glossary" },
|
{ text: 'Glossary', link: 'https://rentry.org/The-Piracy-Glossary' },
|
||||||
{ text: "Guides", link: "https://rentry.co/fmhy-guides" },
|
{ text: 'Guides', link: 'https://rentry.co/fmhy-guides' },
|
||||||
{ text: "Backups", link: "https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/backups" },
|
|
||||||
{
|
{
|
||||||
text: "About",
|
text: 'Backups',
|
||||||
items: [
|
link: 'https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/backups'
|
||||||
{ text: "Posts", link: "/posts" },
|
|
||||||
{ text: "Feedback", link: "/feedback" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'About',
|
||||||
|
items: [
|
||||||
|
{ text: 'Posts', link: '/posts' },
|
||||||
|
{ text: 'Feedback', link: '/feedback' }
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
sidebar,
|
sidebar,
|
||||||
socialLinks,
|
socialLinks
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
@ -1,92 +1,101 @@
|
|||||||
import type { DefaultTheme } from "vitepress";
|
import type { DefaultTheme } from 'vitepress'
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
name: "FreeMediaHeckYeah",
|
name: 'FreeMediaHeckYeah',
|
||||||
description: "The largest collection of free stuff on the internet!",
|
description: 'The largest collection of free stuff on the internet!',
|
||||||
hostname: "https://fmhy.net",
|
hostname: 'https://fmhy.net',
|
||||||
keywords: ["stream", "movies", "gaming", "reading", "anime"],
|
keywords: ['stream', 'movies', 'gaming', 'reading', 'anime']
|
||||||
};
|
}
|
||||||
|
|
||||||
export const commitRef = process.env.CF_PAGES
|
export const commitRef = process.env.CF_PAGES
|
||||||
? `<a href="https://github.com/fmhy/FMHYEdit/commit/${
|
? `<a href="https://github.com/fmhy/FMHYEdit/commit/${
|
||||||
process.env.CF_PAGES_COMMIT_SHA
|
process.env.CF_PAGES_COMMIT_SHA
|
||||||
}">${process.env.CF_PAGES_COMMIT_SHA.slice(0, 8)}</a>`
|
}">${process.env.CF_PAGES_COMMIT_SHA.slice(0, 8)}</a>`
|
||||||
: "dev";
|
: 'dev'
|
||||||
|
|
||||||
export const feedback = `<a href="/feedback" class="feedback-footer">Made with ❤️</a>`;
|
export const feedback = `<a href="/feedback" class="feedback-footer">Made with ❤️</a>`
|
||||||
|
|
||||||
export const search: DefaultTheme.Config["search"] = {
|
export const search: DefaultTheme.Config['search'] = {
|
||||||
options: {
|
options: {
|
||||||
miniSearch: {
|
miniSearch: {
|
||||||
searchOptions: {
|
searchOptions: {
|
||||||
combineWith: "AND",
|
combineWith: 'AND',
|
||||||
fuzzy: false,
|
fuzzy: false,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
boostDocument: (_, term, storedFields: Record<string, string | string[]>) => {
|
boostDocument: (
|
||||||
|
_,
|
||||||
|
term,
|
||||||
|
storedFields: Record<string, string | string[]>
|
||||||
|
) => {
|
||||||
const titles = (storedFields?.titles as string[])
|
const titles = (storedFields?.titles as string[])
|
||||||
.filter((t) => Boolean(t))
|
.filter((t) => Boolean(t))
|
||||||
.map((t) => t.toLowerCase());
|
.map((t) => t.toLowerCase())
|
||||||
// Uprate if term appears in titles. Add bonus for higher levels (i.e. lower index)
|
// Uprate if term appears in titles. Add bonus for higher levels (i.e. lower index)
|
||||||
const titleIndex =
|
const titleIndex =
|
||||||
titles.map((t, i) => (t?.includes(term) ? i : -1)).find((i) => i >= 0) ?? -1;
|
titles
|
||||||
if (titleIndex >= 0) return 10000 - titleIndex;
|
.map((t, i) => (t?.includes(term) ? i : -1))
|
||||||
|
.find((i) => i >= 0) ?? -1
|
||||||
|
if (titleIndex >= 0) return 10000 - titleIndex
|
||||||
|
|
||||||
return 1;
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
detailedView: true
|
||||||
},
|
},
|
||||||
},
|
provider: 'local'
|
||||||
detailedView: true,
|
}
|
||||||
},
|
|
||||||
provider: "local",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const socialLinks: DefaultTheme.SocialLink[] = [
|
export const socialLinks: DefaultTheme.SocialLink[] = [
|
||||||
{ icon: "github", link: "https://github.com/fmhy/FMHYEdit" },
|
{ icon: 'github', link: 'https://github.com/fmhy/FMHYEdit' },
|
||||||
{ icon: "discord", link: "https://discord.gg/Stz6y6NgNg" },
|
{ icon: 'discord', link: 'https://discord.gg/Stz6y6NgNg' },
|
||||||
{
|
{
|
||||||
icon: "reddit",
|
icon: 'reddit',
|
||||||
link: "https://reddit.com/r/FREEMEDIAHECKYEAH",
|
link: 'https://reddit.com/r/FREEMEDIAHECKYEAH'
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
|
||||||
export const sidebar: DefaultTheme.Sidebar = [
|
export const sidebar: DefaultTheme.Sidebar = [
|
||||||
{ text: "📛 Adblocking / Privacy", link: "/adblockvpnguide" },
|
{ text: '📛 Adblocking / Privacy', link: '/adblockvpnguide' },
|
||||||
{ text: "🤖 Artificial Intelligence", link: "/ai" },
|
{ text: '🤖 Artificial Intelligence', link: '/ai' },
|
||||||
{ text: "📺 Movies / TV / Anime", link: "/videopiracyguide" },
|
{ text: '📺 Movies / TV / Anime', link: '/videopiracyguide' },
|
||||||
{ text: "🎵 Music / Podcasts / Radio", link: "/audiopiracyguide" },
|
{ text: '🎵 Music / Podcasts / Radio', link: '/audiopiracyguide' },
|
||||||
{ text: "🎮 Gaming / Emulation", link: "/gamingpiracyguide" },
|
{ text: '🎮 Gaming / Emulation', link: '/gamingpiracyguide' },
|
||||||
{ text: "📗 Books / Comics / Manga", link: "/readingpiracyguide" },
|
{ text: '📗 Books / Comics / Manga', link: '/readingpiracyguide' },
|
||||||
{ text: "💾 Downloading", link: "/downloadpiracyguide" },
|
{ text: '💾 Downloading', link: '/downloadpiracyguide' },
|
||||||
{ text: "🌀 Torrenting", link: "/torrentpiracyguide" },
|
{ text: '🌀 Torrenting', link: '/torrentpiracyguide' },
|
||||||
{ text: "🧠 Educational", link: "/edupiracyguide" },
|
{ text: '🧠 Educational', link: '/edupiracyguide' },
|
||||||
{ text: "📱 Android / iOS", link: "/android-iosguide" },
|
{ text: '📱 Android / iOS', link: '/android-iosguide' },
|
||||||
{ text: "🐧 Linux / MacOS", link: "/linuxguide" },
|
{ text: '🐧 Linux / MacOS', link: '/linuxguide' },
|
||||||
{ text: "🌍 Non-English", link: "/non-english" },
|
{ text: '🌍 Non-English', link: '/non-english' },
|
||||||
{ text: "📂 Miscellaneous", link: "/miscguide" },
|
{ text: '📂 Miscellaneous', link: '/miscguide' },
|
||||||
{
|
{
|
||||||
text: "🔧 Tools",
|
text: '🔧 Tools',
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
items: [
|
items: [
|
||||||
{ text: "💻 System Tools", link: "/system-tools" },
|
{ text: '💻 System Tools', link: '/system-tools' },
|
||||||
{ text: "🗃️ File Tools", link: "/file-tools" },
|
{ text: '🗃️ File Tools', link: '/file-tools' },
|
||||||
{ text: "🔗 Internet Tools", link: "/internet-tools" },
|
{ text: '🔗 Internet Tools', link: '/internet-tools' },
|
||||||
{ text: "💬 Social Media Tools", link: "/social-media-tools" },
|
{ text: '💬 Social Media Tools', link: '/social-media-tools' },
|
||||||
{ text: "📝 Text Tools", link: "/text-tools" },
|
{ text: '📝 Text Tools', link: '/text-tools' },
|
||||||
{ text: "👾 Gaming Tools", link: "/gamingpiracyguide#gaming-tools" },
|
{ text: '👾 Gaming Tools', link: '/gamingpiracyguide#gaming-tools' },
|
||||||
{ text: "📷 Image Tools", link: "/img-tools" },
|
{ text: '📷 Image Tools', link: '/img-tools' },
|
||||||
{ text: "📼 Video Tools", link: "/video-tools" },
|
{ text: '📼 Video Tools', link: '/video-tools' },
|
||||||
{ text: "🔊 Audio Tools", link: "/audiopiracyguide#audio-tools" },
|
{ text: '🔊 Audio Tools', link: '/audiopiracyguide#audio-tools' },
|
||||||
{ text: "🍎 Educational Tools", link: "/edupiracyguide#educational-tools" },
|
{
|
||||||
{ text: "👨💻 Developer Tools", link: "/devtools" },
|
text: '🍎 Educational Tools',
|
||||||
],
|
link: '/edupiracyguide#educational-tools'
|
||||||
|
},
|
||||||
|
{ text: '👨💻 Developer Tools', link: '/devtools' }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "➕️ More",
|
text: '➕️ More',
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [
|
||||||
{ text: "🔞 NSFW", link: "/nsfwpiracy" },
|
{ text: '🔞 NSFW', link: '/nsfwpiracy' },
|
||||||
{ text: "⚠️ Unsafe Sites", link: "/unsafesites" },
|
{ text: '⚠️ Unsafe Sites', link: '/unsafesites' },
|
||||||
{ text: "📦 Storage", link: "/storage" },
|
{ text: '📦 Storage', link: '/storage' }
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{ title: string; description?: string }>();
|
defineProps<{ title: string; description?: string }>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
tw="w-full h-full bg-black flex flex-col"
|
tw="w-full h-full bg-black flex flex-col"
|
||||||
style="background-image: url(https://fmhy.pages.dev/og.png)">
|
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="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="w-full flex justify-between items-center text-5xl font-medium">
|
||||||
<div tw="flex items-center">
|
<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>
|
</div>
|
||||||
<div tw="w-full pr-56 flex flex-col items-start justify-end">
|
<div tw="w-full pr-56 flex flex-col items-start justify-end">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* Barrel generated using @taskylizard/tasker.
|
* Barrel generated using @taskylizard/tasker.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./meta";
|
export * from './meta'
|
||||||
export * from "./opengraph";
|
export * from './opengraph'
|
||||||
export * from "./rss";
|
export * from './rss'
|
||||||
export * from "./satoriConfig";
|
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) {
|
export function generateMeta(context: TransformContext, hostname: string) {
|
||||||
const head: HeadConfig[] = [];
|
const head: HeadConfig[] = []
|
||||||
const { pageData } = context;
|
const { pageData } = context
|
||||||
|
|
||||||
const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, "$2")}`;
|
const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2')}`
|
||||||
|
|
||||||
head.push(
|
head.push(
|
||||||
["link", { rel: "canonical", href: url }],
|
['link', { rel: 'canonical', href: url }],
|
||||||
["meta", { property: "og:url", content: url }],
|
['meta', { property: 'og:url', content: url }],
|
||||||
["meta", { name: "twitter:url", content: url }],
|
['meta', { name: 'twitter:url', content: url }],
|
||||||
["meta", { name: "twitter:card", content: "summary_large_image" }],
|
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
|
||||||
["meta", { property: "og:title", content: pageData.frontmatter.title }],
|
['meta', { property: 'og:title', content: pageData.frontmatter.title }],
|
||||||
["meta", { name: "twitter:title", content: pageData.frontmatter.title }],
|
['meta', { name: 'twitter:title', content: pageData.frontmatter.title }]
|
||||||
);
|
)
|
||||||
if (pageData.frontmatter.description) {
|
if (pageData.frontmatter.description) {
|
||||||
head.push(
|
head.push(
|
||||||
[
|
[
|
||||||
"meta",
|
'meta',
|
||||||
{
|
{
|
||||||
property: "og:description",
|
property: 'og:description',
|
||||||
content: pageData.frontmatter.description,
|
content: pageData.frontmatter.description
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"meta",
|
'meta',
|
||||||
{
|
{
|
||||||
name: "twitter:description",
|
name: 'twitter:description',
|
||||||
content: pageData.frontmatter.description,
|
content: pageData.frontmatter.description
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
if (pageData.frontmatter.image) {
|
if (pageData.frontmatter.image) {
|
||||||
head.push([
|
head.push([
|
||||||
"meta",
|
'meta',
|
||||||
{
|
{
|
||||||
property: "og:image",
|
property: 'og:image',
|
||||||
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`,
|
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, '')}`
|
||||||
},
|
}
|
||||||
]);
|
])
|
||||||
head.push([
|
head.push([
|
||||||
"meta",
|
'meta',
|
||||||
{
|
{
|
||||||
name: "twitter:image",
|
name: 'twitter:image',
|
||||||
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`,
|
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, '')}`
|
||||||
},
|
}
|
||||||
]);
|
])
|
||||||
} else {
|
} else {
|
||||||
const url = pageData.filePath.replace("index.md", "").replace(".md", "");
|
const url = pageData.filePath.replace('index.md', '').replace('.md', '')
|
||||||
const imageUrl = `${url}/__og_image__/og.png`.replaceAll("//", "/").replace(/^\//, "");
|
const imageUrl = `${url}/__og_image__/og.png`
|
||||||
|
.replaceAll('//', '/')
|
||||||
|
.replace(/^\//, '')
|
||||||
|
|
||||||
head.push(
|
head.push(
|
||||||
["meta", { property: "og:image", content: `${hostname}/${imageUrl}` }],
|
['meta', { property: 'og:image', content: `${hostname}/${imageUrl}` }],
|
||||||
["meta", { property: "og:image:width", content: "1200" }],
|
['meta', { property: 'og:image:width', content: '1200' }],
|
||||||
["meta", { property: "og:image:height", content: "628" }],
|
['meta', { property: 'og:image:height', content: '628' }],
|
||||||
["meta", { property: "og:image:type", content: "image/png" }],
|
['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',
|
||||||
["meta", { name: "twitter:image:width", content: "1200" }],
|
{ property: 'og:image:alt', content: pageData.frontmatter.title }
|
||||||
["meta", { name: "twitter:image:height", content: "628" }],
|
],
|
||||||
["meta", { name: "twitter: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) {
|
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) {
|
if (pageData.frontmatter.date) {
|
||||||
head.push([
|
head.push([
|
||||||
"meta",
|
'meta',
|
||||||
{
|
{
|
||||||
property: "article:published_time",
|
property: 'article:published_time',
|
||||||
content: pageData.frontmatter.date,
|
content: pageData.frontmatter.date
|
||||||
},
|
}
|
||||||
]);
|
])
|
||||||
}
|
}
|
||||||
if (pageData.lastUpdated && pageData.frontmatter.lastUpdated !== false) {
|
if (pageData.lastUpdated && pageData.frontmatter.lastUpdated !== false) {
|
||||||
head.push([
|
head.push([
|
||||||
"meta",
|
'meta',
|
||||||
{
|
{
|
||||||
property: "article:modified_time",
|
property: 'article:modified_time',
|
||||||
content: new Date(pageData.lastUpdated).toISOString(),
|
content: new Date(pageData.lastUpdated).toISOString()
|
||||||
},
|
}
|
||||||
]);
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
return head;
|
return head
|
||||||
}
|
}
|
||||||
|
@ -1,71 +1,71 @@
|
|||||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from 'node:path'
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from 'node:url'
|
||||||
import { createContentLoader } from "vitepress";
|
import { createContentLoader } from 'vitepress'
|
||||||
import type { ContentData, SiteConfig } from "vitepress";
|
import type { ContentData, SiteConfig } from 'vitepress'
|
||||||
import { type SatoriOptions, satoriVue } from "x-satori/vue";
|
import { type SatoriOptions, satoriVue } from 'x-satori/vue'
|
||||||
import { renderAsync } from "@resvg/resvg-js";
|
import { renderAsync } from '@resvg/resvg-js'
|
||||||
import consola from "consola";
|
import consola from 'consola'
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
const __fonts = resolve(__dirname, "../fonts");
|
const __fonts = resolve(__dirname, '../fonts')
|
||||||
|
|
||||||
export async function generateImages(config: SiteConfig): Promise<void> {
|
export async function generateImages(config: SiteConfig): Promise<void> {
|
||||||
const pages = await createContentLoader("**/*.md", { excerpt: true }).load();
|
const pages = await createContentLoader('**/*.md', { excerpt: true }).load()
|
||||||
const template = await readFile(resolve(__dirname, "./Template.vue"), "utf-8");
|
const template = await readFile(resolve(__dirname, './Template.vue'), 'utf-8')
|
||||||
|
|
||||||
const fonts: SatoriOptions["fonts"] = [
|
const fonts: SatoriOptions['fonts'] = [
|
||||||
{
|
{
|
||||||
name: "Inter",
|
name: 'Inter',
|
||||||
data: await readFile(resolve(__fonts, "Inter-Regular.otf")),
|
data: await readFile(resolve(__fonts, 'Inter-Regular.otf')),
|
||||||
weight: 400,
|
weight: 400,
|
||||||
style: "normal",
|
style: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Inter",
|
name: 'Inter',
|
||||||
data: await readFile(resolve(__fonts, "Inter-Medium.otf")),
|
data: await readFile(resolve(__fonts, 'Inter-Medium.otf')),
|
||||||
weight: 500,
|
weight: 500,
|
||||||
style: "normal",
|
style: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Inter",
|
name: 'Inter',
|
||||||
data: await readFile(resolve(__fonts, "Inter-SemiBold.otf")),
|
data: await readFile(resolve(__fonts, 'Inter-SemiBold.otf')),
|
||||||
weight: 600,
|
weight: 600,
|
||||||
style: "normal",
|
style: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Inter",
|
name: 'Inter',
|
||||||
data: await readFile(resolve(__fonts, "Inter-Bold.otf")),
|
data: await readFile(resolve(__fonts, 'Inter-Bold.otf')),
|
||||||
weight: 700,
|
weight: 700,
|
||||||
style: "normal",
|
style: 'normal'
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
|
||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
await generateImage({
|
await generateImage({
|
||||||
page,
|
page,
|
||||||
template,
|
template,
|
||||||
outDir: config.outDir,
|
outDir: config.outDir,
|
||||||
fonts,
|
fonts
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
return consola.info("Generated opengraph images.");
|
return consola.info('Generated opengraph images.')
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GenerateImagesOptions {
|
interface GenerateImagesOptions {
|
||||||
page: ContentData;
|
page: ContentData
|
||||||
template: string;
|
template: string
|
||||||
outDir: string;
|
outDir: string
|
||||||
fonts: SatoriOptions["fonts"];
|
fonts: SatoriOptions['fonts']
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateImage({
|
async function generateImage({
|
||||||
page,
|
page,
|
||||||
template,
|
template,
|
||||||
outDir,
|
outDir,
|
||||||
fonts,
|
fonts
|
||||||
}: GenerateImagesOptions): Promise<void> {
|
}: GenerateImagesOptions): Promise<void> {
|
||||||
const { frontmatter, url } = page;
|
const { frontmatter, url } = page
|
||||||
|
|
||||||
const options: SatoriOptions = {
|
const options: SatoriOptions = {
|
||||||
width: 1200,
|
width: 1200,
|
||||||
@ -73,24 +73,24 @@ async function generateImage({
|
|||||||
fonts,
|
fonts,
|
||||||
props: {
|
props: {
|
||||||
title:
|
title:
|
||||||
frontmatter.layout === "home"
|
frontmatter.layout === 'home'
|
||||||
? frontmatter.hero.name ?? frontmatter.title
|
? frontmatter.hero.name ?? frontmatter.title
|
||||||
: frontmatter.title,
|
: frontmatter.title,
|
||||||
description:
|
description:
|
||||||
frontmatter.layout === "home"
|
frontmatter.layout === 'home'
|
||||||
? frontmatter.hero.tagline ?? frontmatter.description
|
? 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 outputFolder = resolve(outDir, url.slice(1), '__og_image__')
|
||||||
const outputFile = resolve(outputFolder, "og.png");
|
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 path from 'node:path'
|
||||||
import { writeFileSync } from "node:fs";
|
import { writeFileSync } from 'node:fs'
|
||||||
import { Feed } from "feed";
|
import { Feed } from 'feed'
|
||||||
import { createContentLoader, type ContentData, type SiteConfig } from "vitepress";
|
import {
|
||||||
import consola from "consola";
|
createContentLoader,
|
||||||
import { meta } from "../constants";
|
type ContentData,
|
||||||
|
type SiteConfig
|
||||||
|
} from 'vitepress'
|
||||||
|
import consola from 'consola'
|
||||||
|
import { meta } from '../constants'
|
||||||
|
|
||||||
export async function generateFeed(config: SiteConfig): Promise<void> {
|
export async function generateFeed(config: SiteConfig): Promise<void> {
|
||||||
const feed: Feed = new Feed({
|
const feed: Feed = new Feed({
|
||||||
@ -11,32 +15,35 @@ export async function generateFeed(config: SiteConfig): Promise<void> {
|
|||||||
link: meta.hostname,
|
link: meta.hostname,
|
||||||
title: `FMHY blog`,
|
title: `FMHY blog`,
|
||||||
description: meta.description,
|
description: meta.description,
|
||||||
language: "en-US",
|
language: 'en-US',
|
||||||
image: "https://github.com/fmhy.png",
|
image: 'https://github.com/fmhy.png',
|
||||||
favicon: `${meta.hostname}/favicon.ico`,
|
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,
|
excerpt: true,
|
||||||
render: true,
|
render: true,
|
||||||
transform: (rawData) => {
|
transform: (rawData) => {
|
||||||
return rawData.sort((a, b) => {
|
return rawData.sort((a, b) => {
|
||||||
return Number(new Date(b.frontmatter.date)) - Number(new Date(a.frontmatter.date));
|
return (
|
||||||
});
|
Number(new Date(b.frontmatter.date)) -
|
||||||
},
|
Number(new Date(a.frontmatter.date))
|
||||||
}).load();
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).load()
|
||||||
|
|
||||||
for (const { url, frontmatter, html } of posts) {
|
for (const { url, frontmatter, html } of posts) {
|
||||||
feed.addItem({
|
feed.addItem({
|
||||||
title: frontmatter.title as string,
|
title: frontmatter.title as string,
|
||||||
id: `${meta.hostname}${url.replace(/\/\d+\./, "/")}`,
|
id: `${meta.hostname}${url.replace(/\/\d+\./, '/')}`,
|
||||||
link: `${meta.hostname}${url.replace(/\/\d+\./, "/")}`,
|
link: `${meta.hostname}${url.replace(/\/\d+\./, '/')}`,
|
||||||
date: frontmatter.date,
|
date: frontmatter.date,
|
||||||
content: html!,
|
content: html!
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileSync(path.join(config.outDir, "feed.rss"), feed.rss2());
|
writeFileSync(path.join(config.outDir, 'feed.rss'), feed.rss2())
|
||||||
return consola.info("Generated rss feed.");
|
return consola.info('Generated rss feed.')
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,47 @@
|
|||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from 'node:fs/promises'
|
||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from 'node:path'
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from 'node:url'
|
||||||
import type { SatoriOptions } from "x-satori/vue";
|
import type { SatoriOptions } from 'x-satori/vue'
|
||||||
import { defineSatoriConfig } from "x-satori/vue";
|
import { defineSatoriConfig } from 'x-satori/vue'
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
const __fonts = resolve(__dirname, "../fonts");
|
const __fonts = resolve(__dirname, '../fonts')
|
||||||
|
|
||||||
const fonts: SatoriOptions["fonts"] = [
|
const fonts: SatoriOptions['fonts'] = [
|
||||||
{
|
{
|
||||||
name: "Inter",
|
name: 'Inter',
|
||||||
data: await readFile(resolve(__fonts, "Inter-Regular.otf")),
|
data: await readFile(resolve(__fonts, 'Inter-Regular.otf')),
|
||||||
weight: 400,
|
weight: 400,
|
||||||
style: "normal",
|
style: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Inter",
|
name: 'Inter',
|
||||||
data: await readFile(resolve(__fonts, "Inter-Medium.otf")),
|
data: await readFile(resolve(__fonts, 'Inter-Medium.otf')),
|
||||||
weight: 500,
|
weight: 500,
|
||||||
style: "normal",
|
style: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Inter",
|
name: 'Inter',
|
||||||
data: await readFile(resolve(__fonts, "Inter-SemiBold.otf")),
|
data: await readFile(resolve(__fonts, 'Inter-SemiBold.otf')),
|
||||||
weight: 600,
|
weight: 600,
|
||||||
style: "normal",
|
style: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Inter",
|
name: 'Inter',
|
||||||
data: await readFile(resolve(__fonts, "Inter-Bold.otf")),
|
data: await readFile(resolve(__fonts, 'Inter-Bold.otf')),
|
||||||
weight: 700,
|
weight: 700,
|
||||||
style: "normal",
|
style: 'normal'
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
|
||||||
export default defineSatoriConfig({
|
export default defineSatoriConfig({
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 628,
|
height: 628,
|
||||||
fonts,
|
fonts,
|
||||||
props: {
|
props: {
|
||||||
title: "Title",
|
title: 'Title',
|
||||||
description:
|
description:
|
||||||
"Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.",
|
'Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.',
|
||||||
dir: "/j",
|
dir: '/j'
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
@ -1,25 +1,29 @@
|
|||||||
import { type MarkdownRenderer } from "vitepress";
|
import { type MarkdownRenderer } from 'vitepress'
|
||||||
|
|
||||||
// FIXME: tasky: possibly write less horror jank?
|
// FIXME: tasky: possibly write less horror jank?
|
||||||
export function base64DecodePlugin(md: MarkdownRenderer) {
|
export function base64DecodePlugin(md: MarkdownRenderer) {
|
||||||
const decode = (str: string): string => Buffer.from(str, "base64").toString("binary");
|
const decode = (str: string): string =>
|
||||||
|
Buffer.from(str, 'base64').toString('binary')
|
||||||
// Save the original rule for backticks
|
// Save the original rule for backticks
|
||||||
const defaultRender =
|
const defaultRender =
|
||||||
md.renderer.rules.code_inline ||
|
md.renderer.rules.code_inline ||
|
||||||
function (tokens, idx, options, env, self) {
|
function (tokens, idx, options, env, self) {
|
||||||
return self.renderToken(tokens, idx, options);
|
return self.renderToken(tokens, idx, options)
|
||||||
};
|
}
|
||||||
|
|
||||||
md.renderer.rules.code_inline = function (tokens, idx, options, env, self) {
|
md.renderer.rules.code_inline = function (tokens, idx, options, env, self) {
|
||||||
// @ts-expect-error shut the fuck up already I HATE THIS
|
// @ts-expect-error shut the fuck up already I HATE THIS
|
||||||
if (!env.frontmatter.title || (env.frontmatter.title && !env.frontmatter.title === "base64")) {
|
if (
|
||||||
return defaultRender(tokens, idx, options, env, self);
|
!env.frontmatter.title ||
|
||||||
|
(env.frontmatter.title && !env.frontmatter.title === 'base64')
|
||||||
|
) {
|
||||||
|
return defaultRender(tokens, idx, options, env, self)
|
||||||
}
|
}
|
||||||
const token = tokens[idx];
|
const token = tokens[idx]
|
||||||
const content = token.content;
|
const content = token.content
|
||||||
|
|
||||||
return `<button class='base64' onclick="(function(btn){ const codeEl = btn.querySelector('code'); navigator.clipboard.writeText('${decode(
|
return `<button class='base64' onclick="(function(btn){ const codeEl = btn.querySelector('code'); navigator.clipboard.writeText('${decode(
|
||||||
content,
|
content
|
||||||
)}').then(() => { const originalText = codeEl.textContent; codeEl.textContent = 'Copied'; setTimeout(() => codeEl.textContent = originalText, 3000); }).catch(console.error); })(this)"><code>${content}</code></button>`;
|
)}').then(() => { const originalText = codeEl.textContent; codeEl.textContent = 'Copied'; setTimeout(() => codeEl.textContent = originalText, 3000); }).catch(console.error); })(this)"><code>${content}</code></button>`
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,42 @@
|
|||||||
import { icons as twemoji } from "@iconify-json/twemoji";
|
import { icons as twemoji } from '@iconify-json/twemoji'
|
||||||
import type { MarkdownRenderer } from "vitepress";
|
import type { MarkdownRenderer } from 'vitepress'
|
||||||
|
|
||||||
export const defs = {
|
export const defs = {
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(twemoji.icons).map(([key]) => {
|
Object.entries(twemoji.icons).map(([key]) => {
|
||||||
return [key, ""];
|
return [key, '']
|
||||||
}),
|
})
|
||||||
),
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export function emojiRender(md: MarkdownRenderer) {
|
export function emojiRender(md: MarkdownRenderer) {
|
||||||
md.renderer.rules.emoji = (tokens, idx) => {
|
md.renderer.rules.emoji = (tokens, idx) => {
|
||||||
if (tokens[idx].markup.startsWith("star")) {
|
if (tokens[idx].markup.startsWith('star')) {
|
||||||
return `<span class="i-twemoji-${tokens[idx].markup} starred"></span>`;
|
return `<span class="i-twemoji-${tokens[idx].markup} starred"></span>`
|
||||||
|
}
|
||||||
|
return `<span class="i-twemoji-${tokens[idx].markup}"></span>`
|
||||||
}
|
}
|
||||||
return `<span class="i-twemoji-${tokens[idx].markup}"></span>`;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function movePlugin(
|
export function movePlugin(
|
||||||
plugins: { name: string }[],
|
plugins: { name: string }[],
|
||||||
pluginAName: string,
|
pluginAName: string,
|
||||||
order: "before" | "after",
|
order: 'before' | 'after',
|
||||||
pluginBName: string,
|
pluginBName: string
|
||||||
) {
|
) {
|
||||||
const pluginBIndex = plugins.findIndex((p) => p.name === pluginBName);
|
const pluginBIndex = plugins.findIndex((p) => p.name === pluginBName)
|
||||||
if (pluginBIndex === -1) return;
|
if (pluginBIndex === -1) return
|
||||||
|
|
||||||
const pluginAIndex = plugins.findIndex((p) => p.name === pluginAName);
|
const pluginAIndex = plugins.findIndex((p) => p.name === pluginAName)
|
||||||
if (pluginAIndex === -1) return;
|
if (pluginAIndex === -1) return
|
||||||
|
|
||||||
if (order === "before" && pluginAIndex > pluginBIndex) {
|
if (order === 'before' && pluginAIndex > pluginBIndex) {
|
||||||
const pluginA = plugins.splice(pluginAIndex, 1)[0];
|
const pluginA = plugins.splice(pluginAIndex, 1)[0]
|
||||||
plugins.splice(pluginBIndex, 0, pluginA);
|
plugins.splice(pluginBIndex, 0, pluginA)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order === "after" && pluginAIndex < pluginBIndex) {
|
if (order === 'after' && pluginAIndex < pluginBIndex) {
|
||||||
const pluginA = plugins.splice(pluginAIndex, 1)[0];
|
const pluginA = plugins.splice(pluginAIndex, 1)[0]
|
||||||
plugins.splice(pluginBIndex, 0, pluginA);
|
plugins.splice(pluginBIndex, 0, pluginA)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import type { MarkdownRenderer } from "vitepress";
|
import type { MarkdownRenderer } from 'vitepress'
|
||||||
|
|
||||||
const excluded = ["Beginners Guide"];
|
const excluded = ['Beginners Guide']
|
||||||
|
|
||||||
export function toggleStarredPlugin(md: MarkdownRenderer) {
|
export function toggleStarredPlugin(md: MarkdownRenderer) {
|
||||||
md.renderer.rules.list_item_open = (tokens, index, options, env, self) => {
|
md.renderer.rules.list_item_open = (tokens, index, options, env, self) => {
|
||||||
const contentToken = tokens[index + 2];
|
const contentToken = tokens[index + 2]
|
||||||
if (
|
if (
|
||||||
!excluded.includes(env.frontmatter.title) &&
|
!excluded.includes(env.frontmatter.title) &&
|
||||||
contentToken &&
|
contentToken &&
|
||||||
contentToken.content.startsWith(":star:")
|
contentToken.content.startsWith(':star:')
|
||||||
) {
|
) {
|
||||||
return `<li class="starred">`;
|
return `<li class="starred">`
|
||||||
} else {
|
} else {
|
||||||
return self.renderToken(tokens, index, options);
|
return self.renderToken(tokens, index, options)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { corsEventHandler } from "nitro-cors";
|
import { corsEventHandler } from 'nitro-cors'
|
||||||
|
|
||||||
export default corsEventHandler((_event) => {}, {
|
export default corsEventHandler((_event) => {}, {
|
||||||
origin: "*",
|
origin: '*',
|
||||||
methods: "*",
|
methods: '*'
|
||||||
});
|
})
|
||||||
|
@ -1,28 +1,32 @@
|
|||||||
import { fetcher } from "itty-fetcher";
|
import { fetcher } from 'itty-fetcher'
|
||||||
import { FeedbackSchema, getFeedbackOption } from "../types/Feedback";
|
import { FeedbackSchema, getFeedbackOption } from '../types/Feedback'
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const { message, page, type } = await readValidatedBody(event, FeedbackSchema.parseAsync);
|
const { message, page, type } = await readValidatedBody(
|
||||||
const env = useRuntimeConfig(event);
|
event,
|
||||||
|
FeedbackSchema.parseAsync
|
||||||
|
)
|
||||||
|
const env = useRuntimeConfig(event)
|
||||||
|
|
||||||
let description = `${message}\n\n`;
|
let description = `${message}\n\n`
|
||||||
if (page) description += `**Page:** \`${page}\``;
|
if (page) description += `**Page:** \`${page}\``
|
||||||
|
|
||||||
await fetcher()
|
await fetcher()
|
||||||
.post(env.WEBHOOK_URL, {
|
.post(env.WEBHOOK_URL, {
|
||||||
username: "Feedback",
|
username: 'Feedback',
|
||||||
avatar_url: "https://i.kym-cdn.com/entries/icons/facebook/000/043/403/cover3.jpg",
|
avatar_url:
|
||||||
|
'https://i.kym-cdn.com/entries/icons/facebook/000/043/403/cover3.jpg',
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
color: 3447003,
|
color: 3447003,
|
||||||
title: getFeedbackOption(type).label,
|
title: getFeedbackOption(type).label,
|
||||||
description,
|
description
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw new Error(error);
|
throw new Error(error)
|
||||||
});
|
})
|
||||||
|
|
||||||
return { status: "ok" };
|
return { status: 'ok' }
|
||||||
});
|
})
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export default eventHandler(() => {
|
export default eventHandler(() => {
|
||||||
return { nitro: "works" };
|
return { nitro: 'works' }
|
||||||
});
|
})
|
||||||
|
@ -1,47 +1,47 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DefaultTheme from "vitepress/theme";
|
import DefaultTheme from 'vitepress/theme'
|
||||||
import { useData } from "vitepress";
|
import { useData } from 'vitepress'
|
||||||
import { nextTick, provide } from "vue";
|
import { nextTick, provide } from 'vue'
|
||||||
import Sidebar from "./components/SidebarCard.vue";
|
import Sidebar from './components/SidebarCard.vue'
|
||||||
import Announcement from "./components/Announcement.vue";
|
import Announcement from './components/Announcement.vue'
|
||||||
|
|
||||||
const { isDark } = useData();
|
const { isDark } = useData()
|
||||||
|
|
||||||
const enableTransitions = () =>
|
const enableTransitions = () =>
|
||||||
"startViewTransition" in document &&
|
'startViewTransition' in document &&
|
||||||
window.matchMedia("(prefers-reduced-motion: no-preference)").matches;
|
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
|
||||||
|
|
||||||
provide("toggle-appearance", async ({ clientX: x, clientY: y }: MouseEvent) => {
|
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
|
||||||
if (!enableTransitions()) {
|
if (!enableTransitions()) {
|
||||||
isDark.value = !isDark.value;
|
isDark.value = !isDark.value
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const clipPath = [
|
const clipPath = [
|
||||||
`circle(0px at ${x}px ${y}px)`,
|
`circle(0px at ${x}px ${y}px)`,
|
||||||
`circle(${Math.hypot(
|
`circle(${Math.hypot(
|
||||||
Math.max(x, innerWidth - x),
|
Math.max(x, innerWidth - x),
|
||||||
Math.max(y, innerHeight - y),
|
Math.max(y, innerHeight - y)
|
||||||
)}px at ${x}px ${y}px)`,
|
)}px at ${x}px ${y}px)`
|
||||||
];
|
]
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
await document.startViewTransition(async () => {
|
await document.startViewTransition(async () => {
|
||||||
isDark.value = !isDark.value;
|
isDark.value = !isDark.value
|
||||||
await nextTick();
|
await nextTick()
|
||||||
}).ready;
|
}).ready
|
||||||
|
|
||||||
document.documentElement.animate(
|
document.documentElement.animate(
|
||||||
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
|
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
|
||||||
{
|
{
|
||||||
duration: 300,
|
duration: 300,
|
||||||
easing: "ease-in",
|
easing: 'ease-in',
|
||||||
pseudoElement: `::view-transition-${isDark.value ? "old" : "new"}(root)`,
|
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
|
||||||
},
|
}
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
|
|
||||||
const { Layout } = DefaultTheme;
|
const { Layout } = DefaultTheme
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useData } from "vitepress";
|
import { useData } from 'vitepress'
|
||||||
import Authors from "./components/Authors.vue";
|
import Authors from './components/Authors.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
authors: string[];
|
authors: string[]
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
const formatDate = (raw: string): string => {
|
const formatDate = (raw: string): string => {
|
||||||
const date = new Date(raw);
|
const date = new Date(raw)
|
||||||
return date.toLocaleDateString("en-US", {
|
return date.toLocaleDateString('en-US', {
|
||||||
month: "short",
|
month: 'short',
|
||||||
day: "numeric",
|
day: 'numeric'
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const { frontmatter } = useData();
|
const { frontmatter } = useData()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -22,6 +22,8 @@ const { frontmatter } = useData();
|
|||||||
{{ frontmatter.title }}
|
{{ frontmatter.title }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<span>{{ frontmatter.description }} • {{ formatDate(frontmatter.date) }}</span>
|
<span>
|
||||||
|
{{ frontmatter.description }} • {{ formatDate(frontmatter.date) }}
|
||||||
|
</span>
|
||||||
<Authors :authors="props.authors" />
|
<Authors :authors="props.authors" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<!-- eslint-disable vue/require-v-for-key -->
|
<!-- eslint-disable vue/require-v-for-key -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { data as posts } from "./posts.data";
|
import { data as posts } from './posts.data'
|
||||||
|
|
||||||
const formatDate = (raw: string): string => {
|
const formatDate = (raw: string): string => {
|
||||||
const date = new Date(raw);
|
const date = new Date(raw)
|
||||||
return date.toLocaleDateString("en-US", {
|
return date.toLocaleDateString('en-US', {
|
||||||
month: "short",
|
month: 'short',
|
||||||
day: "numeric",
|
day: 'numeric'
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -28,11 +28,14 @@ const formatDate = (raw: string): string => {
|
|||||||
<ul>
|
<ul>
|
||||||
<li v-for="post of posts[year]" :key="post.url">
|
<li v-for="post of posts[year]" :key="post.url">
|
||||||
<article>
|
<article>
|
||||||
<a :href="post.url" class="border-none">{{ post.title }}</a> -
|
<a :href="post.url" class="border-none">{{ post.title }}</a>
|
||||||
|
-
|
||||||
<dl class="m-0 inline">
|
<dl class="m-0 inline">
|
||||||
<dt class="sr-only">Published on</dt>
|
<dt class="sr-only">Published on</dt>
|
||||||
<dd class="m-0 inline">
|
<dd class="m-0 inline">
|
||||||
<time :datetime="post.date" class="font-bold">{{ formatDate(post.date) }}</time>
|
<time :datetime="post.date" class="font-bold">
|
||||||
|
{{ formatDate(post.date) }}
|
||||||
|
</time>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</article>
|
</article>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useData } from "vitepress";
|
import { useData } from 'vitepress'
|
||||||
|
|
||||||
const { frontmatter } = useData();
|
const { frontmatter } = useData()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -9,7 +9,8 @@ const { frontmatter } = useData();
|
|||||||
v-if="frontmatter.hero.prelink"
|
v-if="frontmatter.hero.prelink"
|
||||||
:href="frontmatter.hero.prelink.link"
|
:href="frontmatter.hero.prelink.link"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="inline-flex items-center rounded-lg bg-[var(--vp-c-default-soft)] px-4 py-1 text-sm font-semibold mb-3">
|
class="inline-flex items-center rounded-lg bg-[var(--vp-c-default-soft)] px-4 py-1 text-sm font-semibold mb-3"
|
||||||
|
>
|
||||||
{{ frontmatter.hero.prelink.title }}
|
{{ frontmatter.hero.prelink.title }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,39 +1,41 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
authors: string[];
|
authors: string[]
|
||||||
}>();
|
}>()
|
||||||
|
|
||||||
interface Author {
|
interface Author {
|
||||||
name: string;
|
name: string
|
||||||
github: string;
|
github: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{
|
{
|
||||||
name: "nbats",
|
name: 'nbats',
|
||||||
github: "https://github.com/nbats",
|
github: 'https://github.com/nbats'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Kai",
|
name: 'Kai',
|
||||||
github: "https://github.com/Kai-FMHY",
|
github: 'https://github.com/Kai-FMHY'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "taskylizard",
|
name: 'taskylizard',
|
||||||
github: "https://github.com/taskylizard",
|
github: 'https://github.com/taskylizard'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "zinklog",
|
name: 'zinklog',
|
||||||
github: "https://github.com/zinklog2",
|
github: 'https://github.com/zinklog2'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Q",
|
name: 'Q',
|
||||||
github: "https://github.com/qiracy",
|
github: 'https://github.com/qiracy'
|
||||||
},
|
}
|
||||||
] satisfies Author[];
|
] satisfies Author[]
|
||||||
|
|
||||||
const authors = computed(() => data.filter((author) => props.authors.includes(author.name)));
|
const authors = computed(() =>
|
||||||
|
data.filter((author) => props.authors.includes(author.name))
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -41,7 +43,7 @@ const authors = computed(() => data.filter((author) => props.authors.includes(au
|
|||||||
<div v-for="(c, index) of authors" class="flex gap-2 items-center">
|
<div v-for="(c, index) of authors" class="flex gap-2 items-center">
|
||||||
<img :src="`${c.github}.png`" class="w-8 h-8 rounded-full" />
|
<img :src="`${c.github}.png`" class="w-8 h-8 rounded-full" />
|
||||||
<a :href="c.github">{{ c.name }}</a>
|
<a :href="c.github">{{ c.name }}</a>
|
||||||
<span v-if="index < authors.length - 1"> • </span>
|
<span v-if="index < authors.length - 1">•</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
defineProps<{
|
||||||
icon: string;
|
icon: string
|
||||||
}>();
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -1,48 +1,52 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref } from "vue";
|
import { reactive, ref } from 'vue'
|
||||||
import { useRouter } from "vitepress";
|
import { useRouter } from 'vitepress'
|
||||||
import { type FeedbackType, getFeedbackOption, feedbackOptions } from "../../types/Feedback";
|
import {
|
||||||
|
type FeedbackType,
|
||||||
|
getFeedbackOption,
|
||||||
|
feedbackOptions
|
||||||
|
} from '../../types/Feedback'
|
||||||
|
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false)
|
||||||
const error = ref<unknown>(null);
|
const error = ref<unknown>(null)
|
||||||
const success = ref<boolean>(false);
|
const success = ref<boolean>(false)
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter()
|
||||||
|
|
||||||
const feedback = reactive<FeedbackType>({ message: "" });
|
const feedback = reactive<FeedbackType>({ message: '' })
|
||||||
|
|
||||||
async function handleSubmit(type?: FeedbackType["type"]) {
|
async function handleSubmit(type?: FeedbackType['type']) {
|
||||||
if (type) feedback.type = type;
|
if (type) feedback.type = type
|
||||||
loading.value = true;
|
loading.value = true
|
||||||
|
|
||||||
const body: FeedbackType = {
|
const body: FeedbackType = {
|
||||||
message: feedback.message,
|
message: feedback.message,
|
||||||
type: feedback.type,
|
type: feedback.type,
|
||||||
page: router.route.path,
|
page: router.route.path
|
||||||
};
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("https://feedback.tasky.workers.dev", {
|
const response = await fetch('https://feedback.tasky.workers.dev', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body)
|
||||||
});
|
})
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json()
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
error.value = data.error;
|
error.value = data.error
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
if (data.status === "ok") {
|
if (data.status === 'ok') {
|
||||||
success.value = true;
|
success.value = true
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.value = error;
|
error.value = error
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -61,7 +65,8 @@ async function handleSubmit(type?: FeedbackType["type"]) {
|
|||||||
v-for="item in feedbackOptions"
|
v-for="item in feedbackOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
class="btn"
|
class="btn"
|
||||||
@click="handleSubmit(item.value as FeedbackType['type'])">
|
@click="handleSubmit(item.value as FeedbackType['type'])"
|
||||||
|
>
|
||||||
<span>{{ item.label }}</span>
|
<span>{{ item.label }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -71,7 +76,11 @@ async function handleSubmit(type?: FeedbackType["type"]) {
|
|||||||
<p class="desc">Page: {{ router.route.path }}</p>
|
<p class="desc">Page: {{ router.route.path }}</p>
|
||||||
<div>
|
<div>
|
||||||
<span>{{ getFeedbackOption(feedback.type)?.label }}</span>
|
<span>{{ getFeedbackOption(feedback.type)?.label }}</span>
|
||||||
<button style="margin-left: 0.5rem" class="btn" @click="feedback.type = undefined">
|
<button
|
||||||
|
style="margin-left: 0.5rem"
|
||||||
|
class="btn"
|
||||||
|
@click="feedback.type = undefined"
|
||||||
|
>
|
||||||
<span class="i-carbon-close-large">close</span>
|
<span class="i-carbon-close-large">close</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -80,19 +89,27 @@ async function handleSubmit(type?: FeedbackType["type"]) {
|
|||||||
v-model="feedback.message"
|
v-model="feedback.message"
|
||||||
autofocus
|
autofocus
|
||||||
class="input"
|
class="input"
|
||||||
placeholder="What a lovely wiki!" />
|
placeholder="What a lovely wiki!"
|
||||||
|
/>
|
||||||
<p class="desc mb-2">
|
<p class="desc mb-2">
|
||||||
If you'd prefer to be contacted through another platform, feel free to mention it in the
|
If you'd prefer to be contacted through another platform, feel free to
|
||||||
message or join our
|
mention it in the message or join our
|
||||||
<a class="text-primary font-semibold text-underline" href="https://discord.gg/Stz6y6NgNg"
|
<a
|
||||||
>Discord</a
|
class="text-primary font-semibold text-underline"
|
||||||
>.
|
href="https://discord.gg/Stz6y6NgNg"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>
|
||||||
|
.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
:disabled="feedback.message.length < 5 || feedback.message.length > 1000"
|
:disabled="
|
||||||
@click="handleSubmit()">
|
feedback.message.length < 5 || feedback.message.length > 1000
|
||||||
|
"
|
||||||
|
@click="handleSubmit()"
|
||||||
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from 'vue'
|
||||||
import {
|
import {
|
||||||
TransitionRoot,
|
TransitionRoot,
|
||||||
TransitionChild,
|
TransitionChild,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogPanel,
|
DialogPanel,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogDescription,
|
DialogDescription
|
||||||
} from "@headlessui/vue";
|
} from '@headlessui/vue'
|
||||||
|
|
||||||
const isOpen = ref(true);
|
const isOpen = ref(true)
|
||||||
|
|
||||||
const feedbackOptions = [
|
const feedbackOptions = [
|
||||||
{
|
{
|
||||||
label: "💡 Suggestion",
|
label: '💡 Suggestion',
|
||||||
value: "suggestion",
|
value: 'suggestion'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "❤️ Appreciation",
|
label: '❤️ Appreciation',
|
||||||
value: "appreciate",
|
value: 'appreciate'
|
||||||
},
|
},
|
||||||
{ label: "🐞 Bug", value: "bug" },
|
{ label: '🐞 Bug', value: 'bug' },
|
||||||
{ label: "📂 Other", value: "other" },
|
{ label: '📂 Other', value: 'other' }
|
||||||
];
|
]
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
isOpen.value = false;
|
isOpen.value = false
|
||||||
}
|
}
|
||||||
function openModal() {
|
function openModal() {
|
||||||
isOpen.value = true;
|
isOpen.value = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -36,7 +36,8 @@ function openModal() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="p-[4px 8px] text-xl i-carbon:user-favorite-alt-filled"
|
class="p-[4px 8px] text-xl i-carbon:user-favorite-alt-filled"
|
||||||
@click="openModal" />
|
@click="openModal"
|
||||||
|
/>
|
||||||
|
|
||||||
<TransitionRoot appear :show="isOpen" as="template">
|
<TransitionRoot appear :show="isOpen" as="template">
|
||||||
<Dialog as="div" class="relative z-10" @close="closeModal">
|
<Dialog as="div" class="relative z-10" @close="closeModal">
|
||||||
@ -47,12 +48,15 @@ function openModal() {
|
|||||||
enter-to="opacity-100"
|
enter-to="opacity-100"
|
||||||
leave="duration-200 ease-in"
|
leave="duration-200 ease-in"
|
||||||
leave-from="opacity-100"
|
leave-from="opacity-100"
|
||||||
leave-to="opacity-0">
|
leave-to="opacity-0"
|
||||||
|
>
|
||||||
<div class="fixed inset-0 bg-black/25" />
|
<div class="fixed inset-0 bg-black/25" />
|
||||||
</TransitionChild>
|
</TransitionChild>
|
||||||
|
|
||||||
<div class="fixed inset-0 overflow-y-auto">
|
<div class="fixed inset-0 overflow-y-auto">
|
||||||
<div class="flex min-h-full items-center justify-center p-4 text-center">
|
<div
|
||||||
|
class="flex min-h-full items-center justify-center p-4 text-center"
|
||||||
|
>
|
||||||
<TransitionChild
|
<TransitionChild
|
||||||
as="template"
|
as="template"
|
||||||
enter="duration-300 ease-out"
|
enter="duration-300 ease-out"
|
||||||
@ -60,10 +64,15 @@ function openModal() {
|
|||||||
enter-to="opacity-100 scale-100"
|
enter-to="opacity-100 scale-100"
|
||||||
leave="duration-200 ease-in"
|
leave="duration-200 ease-in"
|
||||||
leave-from="opacity-100 scale-100"
|
leave-from="opacity-100 scale-100"
|
||||||
leave-to="opacity-0 scale-95">
|
leave-to="opacity-0 scale-95"
|
||||||
|
>
|
||||||
<DialogPanel
|
<DialogPanel
|
||||||
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-bg p-6 text-left align-middle shadow-xl transition-all">
|
class="w-full max-w-md transform overflow-hidden rounded-2xl bg-bg p-6 text-left align-middle shadow-xl transition-all"
|
||||||
<DialogTitle as="h3" class="text-lg font-medium leading-6 text-text">
|
>
|
||||||
|
<DialogTitle
|
||||||
|
as="h3"
|
||||||
|
class="text-lg font-medium leading-6 text-text"
|
||||||
|
>
|
||||||
Feedback
|
Feedback
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
@ -72,7 +81,8 @@ function openModal() {
|
|||||||
<button
|
<button
|
||||||
v-for="item in feedbackOptions"
|
v-for="item in feedbackOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
class="inline-flex justify-center rounded-md border border-transparent bg-bg-alt px-4 py-2 text-sm font-medium text-text hover:border-primary focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2">
|
class="inline-flex justify-center rounded-md border border-transparent bg-bg-alt px-4 py-2 text-sm font-medium text-text hover:border-primary focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
|
||||||
|
>
|
||||||
<span>{{ item.label }}</span>
|
<span>{{ item.label }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -90,7 +100,8 @@ function openModal() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
class="inline-flex justify-center rounded-md border border-transparent bg-blue-100 px-4 py-2 text-sm font-medium text-blue-900 hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
@click="closeModal">
|
@click="closeModal"
|
||||||
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
defineProps<{
|
||||||
label: string;
|
label: string
|
||||||
id: string;
|
id: string
|
||||||
}>();
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from 'vue'
|
||||||
import Feedback from "./Feedback.vue";
|
import Feedback from './Feedback.vue'
|
||||||
|
|
||||||
const showModal = ref(false);
|
const showModal = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button class="p-[4px 8px] text-xl i-carbon:user-favorite-alt-filled" @click="showModal = true" />
|
<button
|
||||||
|
class="p-[4px 8px] text-xl i-carbon:user-favorite-alt-filled"
|
||||||
|
@click="showModal = true"
|
||||||
|
/>
|
||||||
|
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<Transition name="modal">
|
<Transition name="modal">
|
||||||
@ -14,7 +17,9 @@ const showModal = ref(false);
|
|||||||
<div class="modal-container">
|
<div class="modal-container">
|
||||||
<Feedback />
|
<Feedback />
|
||||||
<div class="model-footer">
|
<div class="model-footer">
|
||||||
<button class="modal-button" @click="showModal = false">Close</button>
|
<button class="modal-button" @click="showModal = false">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Field from "./CardField.vue";
|
import Field from './CardField.vue'
|
||||||
import Modal from "./Modal.vue";
|
import Modal from './Modal.vue'
|
||||||
import InputField from "./InputField.vue";
|
import InputField from './InputField.vue'
|
||||||
import ToggleStarred from "./ToggleStarred.vue";
|
import ToggleStarred from './ToggleStarred.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -10,9 +10,9 @@ import ToggleStarred from "./ToggleStarred.vue";
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">Emoji Legend</div>
|
<div class="card-title">Emoji Legend</div>
|
||||||
</div>
|
</div>
|
||||||
<Field icon="i-twemoji-star"> Recommendations </Field>
|
<Field icon="i-twemoji-star">Recommendations</Field>
|
||||||
<Field icon="i-twemoji-globe-with-meridians"> Indexes </Field>
|
<Field icon="i-twemoji-globe-with-meridians">Indexes</Field>
|
||||||
<Field icon="i-twemoji-repeat-button"> Storage Links </Field>
|
<Field icon="i-twemoji-repeat-button">Storage Links</Field>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="card-title">Options</div>
|
<div class="card-title">Options</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from 'vue'
|
||||||
import { Switch } from "@headlessui/vue";
|
import { Switch } from '@headlessui/vue'
|
||||||
|
|
||||||
const enabled = ref(false);
|
const enabled = ref(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Switch from "./Switch.vue";
|
import Switch from './Switch.vue'
|
||||||
|
|
||||||
const toggleStarred = () => document.documentElement.classList.toggle("starred-only");
|
const toggleStarred = () =>
|
||||||
|
document.documentElement.classList.toggle('starred-only')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import nprogress, { type NProgress } from "nprogress";
|
import nprogress, { type NProgress } from 'nprogress'
|
||||||
import type { EnhanceAppContext } from "vitepress";
|
import type { EnhanceAppContext } from 'vitepress'
|
||||||
|
|
||||||
export function loadProgress(router: EnhanceAppContext["router"]): NProgress {
|
export function loadProgress(router: EnhanceAppContext['router']): NProgress {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === 'undefined') return
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
nprogress.configure({ showSpinner: false });
|
nprogress.configure({ showSpinner: false })
|
||||||
|
|
||||||
const cacheBeforeRouteChange = router.onBeforeRouteChange;
|
const cacheBeforeRouteChange = router.onBeforeRouteChange
|
||||||
const cacheAfterRouteChange = router.onAfterRouteChanged;
|
const cacheAfterRouteChange = router.onAfterRouteChanged
|
||||||
router.onBeforeRouteChange = (to) => {
|
router.onBeforeRouteChange = (to) => {
|
||||||
nprogress.start();
|
nprogress.start()
|
||||||
cacheBeforeRouteChange?.(to);
|
cacheBeforeRouteChange?.(to)
|
||||||
};
|
}
|
||||||
router.onAfterRouteChanged = (to) => {
|
router.onAfterRouteChanged = (to) => {
|
||||||
nprogress.done();
|
nprogress.done()
|
||||||
cacheAfterRouteChange?.(to);
|
cacheAfterRouteChange?.(to)
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return nprogress;
|
return nprogress
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { type Theme } from "vitepress";
|
import { type Theme } from 'vitepress'
|
||||||
import DefaultTheme from "vitepress/theme";
|
import DefaultTheme from 'vitepress/theme'
|
||||||
import Layout from "./Layout.vue";
|
import Layout from './Layout.vue'
|
||||||
import Post from "./PostLayout.vue";
|
import Post from './PostLayout.vue'
|
||||||
import { loadProgress } from "./composables/nprogress";
|
import { loadProgress } from './composables/nprogress'
|
||||||
import "./style.css";
|
import './style.css'
|
||||||
import "uno.css";
|
import 'uno.css'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
extends: DefaultTheme,
|
extends: DefaultTheme,
|
||||||
Layout,
|
Layout,
|
||||||
enhanceApp({ router, app }) {
|
enhanceApp({ router, app }) {
|
||||||
app.component("Post", Post);
|
app.component('Post', Post)
|
||||||
loadProgress(router);
|
loadProgress(router)
|
||||||
},
|
}
|
||||||
} satisfies Theme;
|
} satisfies Theme
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
import { createContentLoader, type ContentData } from "vitepress";
|
import { createContentLoader, type ContentData } from 'vitepress'
|
||||||
import { groupBy } from "../utils";
|
import { groupBy } from '../utils'
|
||||||
|
|
||||||
interface Post {
|
interface Post {
|
||||||
title: string;
|
title: string
|
||||||
url: string;
|
url: string
|
||||||
date: string;
|
date: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dictionary = ReturnType<typeof transformRawPosts>;
|
type Dictionary = ReturnType<typeof transformRawPosts>
|
||||||
|
|
||||||
declare const data: Dictionary;
|
declare const data: Dictionary
|
||||||
export { data };
|
export { data }
|
||||||
|
|
||||||
function transformRawPosts(rawPosts: ContentData[]): Record<string, Post[]> {
|
function transformRawPosts(rawPosts: ContentData[]): Record<string, Post[]> {
|
||||||
const posts: Post[] = rawPosts
|
const posts: Post[] = rawPosts
|
||||||
.map(({ url, frontmatter }) => ({
|
.map(({ url, frontmatter }) => ({
|
||||||
title: frontmatter.title,
|
title: frontmatter.title,
|
||||||
url,
|
url,
|
||||||
date: (frontmatter.date as Date).toISOString().slice(0, 10),
|
date: (frontmatter.date as Date).toISOString().slice(0, 10)
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => b.date.localeCompare(a.date));
|
.sort((a, b) => b.date.localeCompare(a.date))
|
||||||
|
|
||||||
return groupBy(posts, (post) => post.date.slice(0, 4));
|
return groupBy(posts, (post) => post.date.slice(0, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
export default createContentLoader("posts/*.md", {
|
export default createContentLoader('posts/*.md', {
|
||||||
includeSrc: true,
|
includeSrc: true,
|
||||||
transform: (raw) => transformRawPosts(raw),
|
transform: (raw) => transformRawPosts(raw)
|
||||||
});
|
})
|
||||||
|
@ -124,9 +124,17 @@
|
|||||||
*/
|
*/
|
||||||
:root {
|
:root {
|
||||||
--vp-home-hero-name-color: transparent;
|
--vp-home-hero-name-color: transparent;
|
||||||
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #c4b5fd 30%, #7bc5e4);
|
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||||
|
120deg,
|
||||||
|
#c4b5fd 30%,
|
||||||
|
#7bc5e4
|
||||||
|
);
|
||||||
|
|
||||||
--vp-home-hero-image-background-image: linear-gradient(-45deg, #c4b5fd 50%, #47caff 50%);
|
--vp-home-hero-image-background-image: linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
#c4b5fd 50%,
|
||||||
|
#47caff 50%
|
||||||
|
);
|
||||||
--vp-home-hero-image-filter: blur(44px);
|
--vp-home-hero-image-filter: blur(44px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +1,29 @@
|
|||||||
import z from "zod";
|
import z from 'zod'
|
||||||
|
|
||||||
export const FeedbackSchema = z.object({
|
export const FeedbackSchema = z.object({
|
||||||
message: z.string().min(5).max(1000),
|
message: z.string().min(5).max(1000),
|
||||||
type: z.enum(["bug", "suggestion", "appreciate", "other"]),
|
type: z.enum(['bug', 'suggestion', 'appreciate', 'other']),
|
||||||
page: z.string().optional(),
|
page: z.string().optional()
|
||||||
});
|
})
|
||||||
|
|
||||||
export const feedbackOptions = [
|
export const feedbackOptions = [
|
||||||
{ label: "🐞 Bug", value: "bug" },
|
{ label: '🐞 Bug', value: 'bug' },
|
||||||
{
|
{
|
||||||
label: "💡 Suggestion",
|
label: '💡 Suggestion',
|
||||||
value: "suggestion",
|
value: 'suggestion'
|
||||||
},
|
},
|
||||||
{ label: "📂 Other", value: "other" },
|
{ label: '📂 Other', value: 'other' },
|
||||||
{
|
{
|
||||||
label: "❤️ Appreciation",
|
label: '❤️ Appreciation',
|
||||||
value: "appreciate",
|
value: 'appreciate'
|
||||||
},
|
}
|
||||||
];
|
]
|
||||||
|
|
||||||
export function getFeedbackOption(value: string): { label: string; value: string } {
|
export function getFeedbackOption(value: string): {
|
||||||
return feedbackOptions.find((option) => option.value === value);
|
label: string
|
||||||
|
value: string
|
||||||
|
} {
|
||||||
|
return feedbackOptions.find((option) => option.value === value)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FeedbackType = z.infer<typeof FeedbackSchema>;
|
export type FeedbackType = z.infer<typeof FeedbackSchema>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
export function groupBy<T, K extends keyof any>(arr: T[], key: (i: T) => K): Record<K, T[]> {
|
export function groupBy<T, K extends keyof any>(
|
||||||
|
arr: T[],
|
||||||
|
key: (i: T) => K
|
||||||
|
): Record<K, T[]> {
|
||||||
return arr.reduce(
|
return arr.reduce(
|
||||||
(groups, item) => {
|
(groups, item) => {
|
||||||
(groups[key(item)] ||= []).push(item);
|
;(groups[key(item)] ||= []).push(item)
|
||||||
return groups;
|
return groups
|
||||||
},
|
},
|
||||||
{} as Record<K, T[]>,
|
{} as Record<K, T[]>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
6
.vitepress/vue-shim.d.ts
vendored
6
.vitepress/vue-shim.d.ts
vendored
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable ts/consistent-type-imports */
|
/* eslint-disable ts/consistent-type-imports */
|
||||||
declare module "*.vue" {
|
declare module '*.vue' {
|
||||||
const component: import("vue").Component;
|
const component: import('vue').Component
|
||||||
export default component;
|
export default component
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import tasky from "@taskylizard/eslint-config";
|
import tasky from '@taskylizard/eslint-config'
|
||||||
|
|
||||||
export default tasky({
|
export default tasky({
|
||||||
vue: true,
|
vue: true,
|
||||||
browser: true,
|
browser: true
|
||||||
});
|
})
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
//https://nitro.unjs.io/config
|
//https://nitro.unjs.io/config
|
||||||
export default defineNitroConfig({
|
export default defineNitroConfig({
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
WEBHOOK_URL: process.env.WEBHOOK_URL,
|
WEBHOOK_URL: process.env.WEBHOOK_URL
|
||||||
},
|
},
|
||||||
srcDir: ".vitepress",
|
srcDir: '.vitepress',
|
||||||
routeRules: {
|
routeRules: {
|
||||||
"/": {
|
'/': {
|
||||||
cors: false,
|
cors: false
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "./.nitro/types/tsconfig.json",
|
"extends": "./.nitro/types/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true
|
||||||
},
|
},
|
||||||
"include": ["./.vitepress/"],
|
"include": ["./.vitepress/"]
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { defineConfig, presetUno, presetAttributify, presetIcons } from "unocss";
|
import { defineConfig, presetUno, presetAttributify, presetIcons } from 'unocss'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
theme: {
|
theme: {
|
||||||
colors: {
|
colors: {
|
||||||
primary: "var(--vp-c-brand-1)",
|
primary: 'var(--vp-c-brand-1)',
|
||||||
bg: "var(--vp-c-bg)",
|
bg: 'var(--vp-c-bg)',
|
||||||
"bg-alt": "var(--vp-c-bg-alt)",
|
'bg-alt': 'var(--vp-c-bg-alt)',
|
||||||
"bg-elv": "var(--vp-c-bg-elv)",
|
'bg-elv': 'var(--vp-c-bg-elv)',
|
||||||
text: "var(--vp-c-text-1)",
|
text: 'var(--vp-c-text-1)',
|
||||||
"text-2": "var(--vp-c-text-2)",
|
'text-2': 'var(--vp-c-text-2)',
|
||||||
div: "var(--vp-c-divider)",
|
div: 'var(--vp-c-divider)'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
presets: [
|
presets: [
|
||||||
presetUno(),
|
presetUno(),
|
||||||
@ -18,9 +18,9 @@ export default defineConfig({
|
|||||||
presetIcons({
|
presetIcons({
|
||||||
scale: 1.2,
|
scale: 1.2,
|
||||||
extraProperties: {
|
extraProperties: {
|
||||||
display: "inline-block",
|
display: 'inline-block',
|
||||||
"vertical-align": "middle",
|
'vertical-align': 'middle'
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user