From 256d2ca844a89b43053f6e4e231500cd683673f3 Mon Sep 17 00:00:00 2001 From: Ad-closeNN <1709301095@qq.com> Date: Sat, 23 May 2026 21:18:44 +0800 Subject: [PATCH] chore: format & feedback beautification --- .gitignore | 3 +- CLAUDE.md | 1 + src/components/ConfigCarrier.astro | 1 - src/components/misc/ImageWrapper.astro | 20 +- .../widget/DnsFeedbackBanner.svelte | 291 ++++++++++++++++++ src/config.ts | 13 +- src/content.config.ts | 60 ++-- src/i18n/i18nKey.ts | 2 + src/i18n/languages/zh_CN.ts | 2 + src/layouts/Layout.astro | 26 +- src/pages/info.astro | 3 +- src/pages/posts/[...slug].astro | 57 +++- src/pages/rss.xml.ts | 56 ++-- src/types/config.ts | 16 +- src/utils/content-utils.ts | 13 +- 15 files changed, 452 insertions(+), 112 deletions(-) create mode 100644 src/components/widget/DnsFeedbackBanner.svelte diff --git a/.gitignore b/.gitignore index 4ade5e6..5bf8f81 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ yarn.lock # .obsidian .cache -build.log \ No newline at end of file +build.log +.traces \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 299a617..4e2cd1b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -99,3 +99,4 @@ src/ - Biome 不处理 CSS 文件(在 `biome.json` 中排除) - Svelte 组件使用 Svelte 5 语法 - 主题色基于 CSS 变量 `--hue` 动态计算 +- 不要执行构建,除非特别要求 \ No newline at end of file diff --git a/src/components/ConfigCarrier.astro b/src/components/ConfigCarrier.astro index 71a7621..a60fb53 100644 --- a/src/components/ConfigCarrier.astro +++ b/src/components/ConfigCarrier.astro @@ -1,7 +1,6 @@ --- import { siteConfig } from "../config"; - ---
diff --git a/src/components/misc/ImageWrapper.astro b/src/components/misc/ImageWrapper.astro index c4e0fee..8fb4e65 100644 --- a/src/components/misc/ImageWrapper.astro +++ b/src/components/misc/ImageWrapper.astro @@ -15,7 +15,15 @@ interface Props { import { Image } from "astro:assets"; import { url } from "../../utils/url-utils"; -const { id, src, alt, position = "center", basePath = "/", loader = false, blur = false } = Astro.props; +const { + id, + src, + alt, + position = "center", + basePath = "/", + loader = false, + blur = false, +} = Astro.props; const className = Astro.props.class; const isLocal = !( @@ -48,8 +56,14 @@ if (isLocal) { const imageClass = "w-full h-full object-cover"; const imageStyle = `object-position: ${position}`; -const normalizedPublicSrc = src === "/public" ? "/" : src.startsWith("/public/") ? src.slice("/public".length) : src; -const originalSrc = isLocal && img ? img.src : isPublic ? url(normalizedPublicSrc) : src; +const normalizedPublicSrc = + src === "/public" + ? "/" + : src.startsWith("/public/") + ? src.slice("/public".length) + : src; +const originalSrc = + isLocal && img ? img.src : isPublic ? url(normalizedPublicSrc) : src; const originalWidth = isLocal && img ? img.width : undefined; const originalHeight = isLocal && img ? img.height : undefined; --- diff --git a/src/components/widget/DnsFeedbackBanner.svelte b/src/components/widget/DnsFeedbackBanner.svelte new file mode 100644 index 0000000..4409c31 --- /dev/null +++ b/src/components/widget/DnsFeedbackBanner.svelte @@ -0,0 +1,291 @@ + + +{#if !dismissed} +
+
+
+
+ + +{#if isOpen} + + + + + +{/if} +{/if} + + diff --git a/src/config.ts b/src/config.ts index ec537fb..f1402d5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ import type { ExpressiveCodeConfig, + FeedbackConfig, LicenseConfig, NavBarConfig, ProfileConfig, @@ -43,8 +44,8 @@ export const siteConfig: SiteConfig = { }, favicon: [ { - src: '/assets/avatar.jpg', - } + src: "/assets/avatar.jpg", + }, // Leave this array empty to use the default favicon // { // src: '/favicon/icon.png', // Path of the favicon, relative to the /public directory @@ -126,9 +127,15 @@ export const expressiveCodeConfig: ExpressiveCodeConfig = { theme: "github-dark", }; +export const feedbackConfig: FeedbackConfig = { + enable: true, + apiEndpoint: "https://blog-feedback.adclosenn.top", + debug: false, +}; + export const umamiConfig: UmamiConfig = { enable: true, baseUrl: "https://umami.adclosenn.top", shareId: "jME4HFb9JmfJM5zs", timezone: "Asia/Shanghai", -}; \ No newline at end of file +}; diff --git a/src/content.config.ts b/src/content.config.ts index 9f50203..1514319 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -1,41 +1,41 @@ import { defineCollection } from "astro:content"; -import { z } from "astro/zod"; import { glob } from "astro/loaders"; +import { z } from "astro/zod"; const postsCollection = defineCollection({ - loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }), - schema: z.object({ - title: z.string(), - published: z.date(), - updated: z.date().optional(), - draft: z.boolean().optional().default(false), - description: z.string().optional().default(""), - image: z.string().optional().default(""), - tags: z.array(z.string()).optional().default([]), - category: z.string().optional().nullable().default(""), - showcover: z.boolean().optional().default(true), - customcover: z.string().optional().default(""), - pinned: z.boolean().optional().default(false), - outdated: z.boolean().optional().default(false), - lang: z.string().optional().default(""), - prevTitle: z.string().default(""), - prevSlug: z.string().default(""), - nextTitle: z.string().default(""), - nextSlug: z.string().default(""), - }), + loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }), + schema: z.object({ + title: z.string(), + published: z.date(), + updated: z.date().optional(), + draft: z.boolean().optional().default(false), + description: z.string().optional().default(""), + image: z.string().optional().default(""), + tags: z.array(z.string()).optional().default([]), + category: z.string().optional().nullable().default(""), + showcover: z.boolean().optional().default(true), + customcover: z.string().optional().default(""), + pinned: z.boolean().optional().default(false), + outdated: z.boolean().optional().default(false), + lang: z.string().optional().default(""), + prevTitle: z.string().default(""), + prevSlug: z.string().default(""), + nextTitle: z.string().default(""), + nextSlug: z.string().default(""), + }), }); const specCollection = defineCollection({ - loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/spec" }), - schema: z.object({ - title: z.string().optional(), - published: z.date().optional(), - updated: z.date().optional(), - draft: z.boolean().optional().default(false), - }), + loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/spec" }), + schema: z.object({ + title: z.string().optional(), + published: z.date().optional(), + updated: z.date().optional(), + draft: z.boolean().optional().default(false), + }), }); export const collections = { - posts: postsCollection, - spec: specCollection, + posts: postsCollection, + spec: specCollection, }; diff --git a/src/i18n/i18nKey.ts b/src/i18n/i18nKey.ts index f796b17..d17cdb0 100644 --- a/src/i18n/i18nKey.ts +++ b/src/i18n/i18nKey.ts @@ -32,6 +32,8 @@ enum I18nKey { author = "author", publishedAt = "publishedAt", license = "license", + + dnsFeedbackError = "dnsFeedbackError", } export default I18nKey; diff --git a/src/i18n/languages/zh_CN.ts b/src/i18n/languages/zh_CN.ts index d1b8cf1..820ae3f 100644 --- a/src/i18n/languages/zh_CN.ts +++ b/src/i18n/languages/zh_CN.ts @@ -35,4 +35,6 @@ export const zh_CN: Translation = { [Key.author]: "作者", [Key.publishedAt]: "发布于", [Key.license]: "许可协议", + + [Key.dnsFeedbackError]: "提交失败", }; diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 3e0782c..1b03e7c 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,6 +1,6 @@ --- import ConfigCarrier from "@components/ConfigCarrier.astro"; -import { Icon } from "astro-icon/components"; +import DnsFeedbackBanner from "@components/widget/DnsFeedbackBanner.svelte"; import { profileConfig, siteConfig, umamiConfig } from "@/config"; import { AUTO_MODE, @@ -274,29 +274,7 @@ const bannerOffset = data-overlayscrollbars-initialize > - - + diff --git a/src/pages/info.astro b/src/pages/info.astro index 481930e..250c50d 100644 --- a/src/pages/info.astro +++ b/src/pages/info.astro @@ -1,11 +1,12 @@ --- import { getEntry, render } from "astro:content"; +import { addIssueToContext } from "astro:schema"; import Markdown from "@components/misc/Markdown.astro"; import I18nKey from "../i18n/i18nKey"; import { i18n } from "../i18n/translation"; import MainGridLayout from "../layouts/MainGridLayout.astro"; -import { addIssueToContext } from "astro:schema"; + const aboutPost = await getEntry("spec", "info"); if (!aboutPost) { diff --git a/src/pages/posts/[...slug].astro b/src/pages/posts/[...slug].astro index 844ca56..dd85ae0 100644 --- a/src/pages/posts/[...slug].astro +++ b/src/pages/posts/[...slug].astro @@ -62,16 +62,25 @@ function getPostSourcePath(sourceKey: string | undefined) { return sourceKey?.replace("../../content/posts/", "src/content/posts/"); } -function getGitHubPostSourceUrl(repo: string | undefined, sourcePath: string | undefined) { +function getGitHubPostSourceUrl( + repo: string | undefined, + sourcePath: string | undefined, +) { if (!repo || !sourcePath) return ""; const repoUrl = repo.includes("://") ? repo : `https://github.com/${repo}`; const normalizedRepo = repoUrl.replace(/\.git$/, "").replace(/\/+$/, ""); - const encodedSourcePath = sourcePath.split("/").map(encodeURIComponent).join("/"); + const encodedSourcePath = sourcePath + .split("/") + .map(encodeURIComponent) + .join("/"); return `${normalizedRepo}/blob/main/${encodedSourcePath}?plain=1`; } -function getGitHubRawSourceUrl(repo: string | undefined, sourcePath: string | undefined) { +function getGitHubRawSourceUrl( + repo: string | undefined, + sourcePath: string | undefined, +) { if (!repo || !sourcePath) return ""; const normalizedRepo = repo.replace(/\.git$/, "").replace(/\/+$/, ""); @@ -81,11 +90,18 @@ function getGitHubRawSourceUrl(repo: string | undefined, sourcePath: string | un const [owner, repository] = repoPath.split("/"); if (!owner || !repository) return ""; - const encodedSourcePath = sourcePath.split("/").map(encodeURIComponent).join("/"); + const encodedSourcePath = sourcePath + .split("/") + .map(encodeURIComponent) + .join("/"); return `https://raw.githubusercontent.com/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/refs/heads/main/${encodedSourcePath}`; } -function getGiteaRawSourceUrl(repo: string | undefined, sourcePath: string | undefined, giteaHost: string) { +function getGiteaRawSourceUrl( + repo: string | undefined, + sourcePath: string | undefined, + giteaHost: string, +) { if (!repo || !sourcePath) return ""; const normalizedRepo = repo.replace(/\.git$/, "").replace(/\/+$/, ""); @@ -95,7 +111,10 @@ function getGiteaRawSourceUrl(repo: string | undefined, sourcePath: string | und const [owner, repository] = repoPath.split("/"); if (!owner || !repository) return ""; - const encodedSourcePath = sourcePath.split("/").map(encodeURIComponent).join("/"); + const encodedSourcePath = sourcePath + .split("/") + .map(encodeURIComponent) + .join("/"); return `https://${giteaHost}/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/src/branch/main/${encodedSourcePath}?display=source`; } @@ -103,7 +122,10 @@ function getCnbsSourceUrl(baseUrl: string, sourcePath: string | undefined) { if (!sourcePath) return ""; const normalizedUrl = baseUrl.replace(/\.git$/, "").replace(/\/+$/, ""); - const encodedSourcePath = sourcePath.split("/").map(encodeURIComponent).join("/"); + const encodedSourcePath = sourcePath + .split("/") + .map(encodeURIComponent) + .join("/"); return `${normalizedUrl}/-/blob/main/${encodedSourcePath}`; } @@ -160,10 +182,23 @@ function getAiSummaryMeta(entryId: string) { const aiSummaryMeta = getAiSummaryMeta(entry.id); const postSourceKey = getPostSourceKey(entry.id); const postSourcePath = getPostSourcePath(postSourceKey); -const postSourceUrl = getGitHubPostSourceUrl(siteConfig.githubRepo, postSourcePath); -const postRawSourceUrl = getGitHubRawSourceUrl(siteConfig.githubRepo, postSourcePath); -const postGiteaRawSourceUrl = getGiteaRawSourceUrl(siteConfig.githubRepo, postSourcePath, "git.adclosenn.top"); -const postCnbsSourceUrl = getCnbsSourceUrl("https://cnb.cool/CLN-Grated/blog-fuwari", postSourcePath); +const postSourceUrl = getGitHubPostSourceUrl( + siteConfig.githubRepo, + postSourcePath, +); +const postRawSourceUrl = getGitHubRawSourceUrl( + siteConfig.githubRepo, + postSourcePath, +); +const postGiteaRawSourceUrl = getGiteaRawSourceUrl( + siteConfig.githubRepo, + postSourcePath, + "git.adclosenn.top", +); +const postCnbsSourceUrl = getCnbsSourceUrl( + "https://cnb.cool/CLN-Grated/blog-fuwari", + postSourcePath, +); const aiPrompt = `Read from ${postRawSourceUrl} so I can ask questions about it.`; const encodedAiPrompt = encodeURIComponent(aiPrompt); const copyPageAiLinks = [ diff --git a/src/pages/rss.xml.ts b/src/pages/rss.xml.ts index 66de40b..78bb6f5 100644 --- a/src/pages/rss.xml.ts +++ b/src/pages/rss.xml.ts @@ -1,22 +1,22 @@ -import rss from '@astrojs/rss'; -import sanitizeHtml from 'sanitize-html'; -import MarkdownIt from 'markdown-it'; -import { siteConfig } from '@/config'; -import { parse as htmlParser } from 'node-html-parser'; -import { getImage } from 'astro:assets'; -import type { APIContext, ImageMetadata } from 'astro'; -import type { RSSFeedItem } from '@astrojs/rss'; -import { getSortedPosts } from '@/utils/content-utils'; +import { getImage } from "astro:assets"; +import type { RSSFeedItem } from "@astrojs/rss"; +import rss from "@astrojs/rss"; +import type { APIContext, ImageMetadata } from "astro"; +import MarkdownIt from "markdown-it"; +import { parse as htmlParser } from "node-html-parser"; +import sanitizeHtml from "sanitize-html"; +import { siteConfig } from "@/config"; +import { getSortedPosts } from "@/utils/content-utils"; const markdownParser = new MarkdownIt(); function rewritePublicPath(url: string) { - if (url === '/public') { - return '/'; + if (url === "/public") { + return "/"; } - if (url.startsWith('/public/')) { - return url.slice('/public'.length); + if (url.startsWith("/public/")) { + return url.slice("/public".length); } return url; @@ -24,12 +24,12 @@ function rewritePublicPath(url: string) { // get dynamic import of images as a map collection const imagesGlob = import.meta.glob<{ default: ImageMetadata }>( - '/src/content/**/*.{jpeg,jpg,png,gif,webp}', // include posts and assets + "/src/content/**/*.{jpeg,jpg,png,gif,webp}", // include posts and assets ); export async function GET(context: APIContext) { if (!context.site) { - throw Error('site not set'); + throw Error("site not set"); } // Use the same ordering as site listing (pinned first, then by published desc) @@ -38,39 +38,41 @@ export async function GET(context: APIContext) { for (const post of posts) { // convert markdown to html string - const body = markdownParser.render(post.body ?? ''); + const body = markdownParser.render(post.body ?? ""); // convert html string to DOM-like structure const html = htmlParser.parse(body); // hold all img tags in variable images - const images = html.querySelectorAll('img'); + const images = html.querySelectorAll("img"); for (const img of images) { - const srcAttr = img.getAttribute('src'); + const srcAttr = img.getAttribute("src"); if (!srcAttr) continue; const src = rewritePublicPath(srcAttr); // Handle content-relative images and convert them to built _astro paths - if (src.startsWith('./') || src.startsWith('../')) { + if (src.startsWith("./") || src.startsWith("../")) { let importPath: string | null = null; - if (src.startsWith('./')) { + if (src.startsWith("./")) { // Path relative to the post file directory const prefixRemoved = src.slice(2); importPath = `/src/content/posts/${prefixRemoved}`; } else { // Path like /public/pic/xxx -> relative to /src/content/ - const cleaned = src.replace(/^\.\.\//, ''); + const cleaned = src.replace(/^\.\.\//, ""); importPath = `/src/content/${cleaned}`; } - const imageMod = await imagesGlob[importPath]?.()?.then((res) => res.default); + const imageMod = await imagesGlob[importPath]?.()?.then( + (res) => res.default, + ); if (imageMod) { const optimizedImg = await getImage({ src: imageMod }); - img.setAttribute('src', new URL(optimizedImg.src, context.site).href); + img.setAttribute("src", new URL(optimizedImg.src, context.site).href); } - } else if (src.startsWith('/')) { - img.setAttribute('src', new URL(src, context.site).href); + } else if (src.startsWith("/")) { + img.setAttribute("src", new URL(src, context.site).href); } } @@ -81,14 +83,14 @@ export async function GET(context: APIContext) { link: `/posts/${post.id}/`, // sanitize the new html string with corrected image paths content: sanitizeHtml(html.toString(), { - allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']), + allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]), }), }); } return rss({ title: siteConfig.title, - description: siteConfig.subtitle || 'No description', + description: siteConfig.subtitle || "No description", site: context.site, items: feed, customData: `${siteConfig.lang}25050403786655846483370505718413312`, diff --git a/src/types/config.ts b/src/types/config.ts index f8aa0ba..fa5f853 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -21,7 +21,7 @@ export type SiteConfig = { hue: number; fixed: boolean; }; - + background: { enable: boolean; src: string; @@ -31,7 +31,7 @@ export type SiteConfig = { attachment?: "fixed" | "scroll" | "local"; opacity?: number; }; - + banner: { enable: boolean; src: string; @@ -89,9 +89,7 @@ export type LicenseConfig = { url: string; }; -export type LIGHT_DARK_MODE = - | typeof LIGHT_MODE - | typeof DARK_MODE +export type LIGHT_DARK_MODE = typeof LIGHT_MODE | typeof DARK_MODE; export type BlogPostData = { body: string; @@ -124,4 +122,10 @@ export type UmamiConfig = { baseUrl: string; shareId: string; timezone: string; -}; \ No newline at end of file +}; + +export type FeedbackConfig = { + enable: boolean; + apiEndpoint: string; + debug?: boolean; +}; diff --git a/src/utils/content-utils.ts b/src/utils/content-utils.ts index bd09f79..c7e1ba2 100644 --- a/src/utils/content-utils.ts +++ b/src/utils/content-utils.ts @@ -1,4 +1,4 @@ -import { render, type CollectionEntry, getCollection } from "astro:content"; +import { type CollectionEntry, getCollection, render } from "astro:content"; import I18nKey from "@i18n/i18nKey"; import { i18n } from "@i18n/translation"; import { getCategoryUrl } from "@utils/url-utils.ts"; @@ -60,10 +60,13 @@ export async function getTotalWords(): Promise { const posts = await getRawSortedPosts(); const renderedPosts = await Promise.all(posts.map((post) => render(post))); - const totalWords = renderedPosts.reduce((sum, { remarkPluginFrontmatter }) => { - const words = Number(remarkPluginFrontmatter?.words ?? 0); - return sum + (Number.isFinite(words) ? words : 0); - }, 0); + const totalWords = renderedPosts.reduce( + (sum, { remarkPluginFrontmatter }) => { + const words = Number(remarkPluginFrontmatter?.words ?? 0); + return sum + (Number.isFinite(words) ? words : 0); + }, + 0, + ); totalWordsCache = totalWords; return totalWords;