feat: add upyun support. move images to upyun on build. (#45)
* style: better reply button. * feat: add astro badge. * chore: drop useless styles. * feat: use new options.ts for global configuration. * fix: the invalid import.meta.env.PROD in astro build. * feat: move og to new assert prefix. * chore: add better og description. * chore: make sure all the images link could be correctly replaced. * feat: add upyun uploader.
This commit is contained in:
parent
7fec4ba787
commit
0126b71722
27
.vscode/settings.json
vendored
27
.vscode/settings.json
vendored
@ -12,6 +12,10 @@
|
|||||||
},
|
},
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"alexinea",
|
"alexinea",
|
||||||
|
"alignfull",
|
||||||
|
"alignleft",
|
||||||
|
"alignright",
|
||||||
|
"alignwide",
|
||||||
"ameho",
|
"ameho",
|
||||||
"amehochan",
|
"amehochan",
|
||||||
"aplayer",
|
"aplayer",
|
||||||
@ -21,6 +25,7 @@
|
|||||||
"batang",
|
"batang",
|
||||||
"bigserial",
|
"bigserial",
|
||||||
"biomejs",
|
"biomejs",
|
||||||
|
"blogroll",
|
||||||
"blogster",
|
"blogster",
|
||||||
"blurhash",
|
"blurhash",
|
||||||
"captainofphb",
|
"captainofphb",
|
||||||
@ -45,12 +50,13 @@
|
|||||||
"fong",
|
"fong",
|
||||||
"forencrypt",
|
"forencrypt",
|
||||||
"giscus",
|
"giscus",
|
||||||
|
"gogogo",
|
||||||
"gotop",
|
"gotop",
|
||||||
"gungseo",
|
"gungseo",
|
||||||
"hefei",
|
"hefei",
|
||||||
"heiti",
|
"heiti",
|
||||||
"HONORBKK",
|
"honorbkk",
|
||||||
"HUAWEIYAL",
|
"huaweiyal",
|
||||||
"ianvs",
|
"ianvs",
|
||||||
"iconfont",
|
"iconfont",
|
||||||
"iroha",
|
"iroha",
|
||||||
@ -61,7 +67,7 @@
|
|||||||
"jing",
|
"jing",
|
||||||
"jungshik",
|
"jungshik",
|
||||||
"khalil",
|
"khalil",
|
||||||
"KHTML",
|
"khtml",
|
||||||
"koanughi",
|
"koanughi",
|
||||||
"koaunghi",
|
"koaunghi",
|
||||||
"lantinghei",
|
"lantinghei",
|
||||||
@ -73,15 +79,17 @@
|
|||||||
"luxon",
|
"luxon",
|
||||||
"mboker",
|
"mboker",
|
||||||
"minagi",
|
"minagi",
|
||||||
"Miui",
|
"miui",
|
||||||
"MMWEBID",
|
"mmwebid",
|
||||||
"MMWEBSDK",
|
"mmwebsdk",
|
||||||
"mochi",
|
"mochi",
|
||||||
"napi",
|
"napi",
|
||||||
"netease",
|
"netease",
|
||||||
"nextval",
|
"nextval",
|
||||||
"nian",
|
"nian",
|
||||||
|
"nocolor",
|
||||||
"nofollow",
|
"nofollow",
|
||||||
|
"nopd",
|
||||||
"noto",
|
"noto",
|
||||||
"oppo",
|
"oppo",
|
||||||
"opposans",
|
"opposans",
|
||||||
@ -95,7 +103,7 @@
|
|||||||
"qrcode",
|
"qrcode",
|
||||||
"quan",
|
"quan",
|
||||||
"recma",
|
"recma",
|
||||||
"Redmi",
|
"redmi",
|
||||||
"regclass",
|
"regclass",
|
||||||
"sauvignon",
|
"sauvignon",
|
||||||
"sheng",
|
"sheng",
|
||||||
@ -104,6 +112,8 @@
|
|||||||
"shmily",
|
"shmily",
|
||||||
"skrs",
|
"skrs",
|
||||||
"syhily",
|
"syhily",
|
||||||
|
"tabindex",
|
||||||
|
"tagcloud",
|
||||||
"taza",
|
"taza",
|
||||||
"teruteru",
|
"teruteru",
|
||||||
"timestamptz",
|
"timestamptz",
|
||||||
@ -111,6 +121,7 @@
|
|||||||
"tsconfigs",
|
"tsconfigs",
|
||||||
"ultrahtml",
|
"ultrahtml",
|
||||||
"unpic",
|
"unpic",
|
||||||
|
"upyun",
|
||||||
"urlset",
|
"urlset",
|
||||||
"varchar",
|
"varchar",
|
||||||
"velite",
|
"velite",
|
||||||
@ -120,7 +131,7 @@
|
|||||||
"weibo",
|
"weibo",
|
||||||
"xiao",
|
"xiao",
|
||||||
"xinsenz",
|
"xinsenz",
|
||||||
"XWEB",
|
"xweb",
|
||||||
"yefengs",
|
"yefengs",
|
||||||
"yetgul",
|
"yetgul",
|
||||||
"ying",
|
"ying",
|
||||||
|
@ -8,7 +8,7 @@ COPY . .
|
|||||||
ENV ASTRO_TELEMETRY_DISABLED=1
|
ENV ASTRO_TELEMETRY_DISABLED=1
|
||||||
RUN NODE_ENV=development npm install
|
RUN NODE_ENV=development npm install
|
||||||
RUN npm i patch-package && npm exec patch-package
|
RUN npm i patch-package && npm exec patch-package
|
||||||
RUN npm run build
|
RUN NODE_ENV=production npm run build
|
||||||
|
|
||||||
FROM base AS runtime
|
FROM base AS runtime
|
||||||
RUN npm install --omit=dev
|
RUN npm install --omit=dev
|
||||||
@ -16,4 +16,4 @@ COPY --from=build /app/dist ./dist
|
|||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV PORT=4321
|
ENV PORT=4321
|
||||||
EXPOSE 4321
|
EXPOSE 4321
|
||||||
CMD node ./dist/server/entry.mjs
|
CMD NODE_ENV=production node ./dist/server/entry.mjs
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx';
|
||||||
import node from '@astrojs/node';
|
import node from '@astrojs/node';
|
||||||
import { defineConfig, envField } from 'astro/config';
|
import { defineConfig, envField } from 'astro/config';
|
||||||
import { astroImage } from './remark-plugins/images';
|
import options from './options';
|
||||||
|
import { astroImage } from './plugins/images';
|
||||||
// Dynamic switch the site. This is hard coded.
|
import { upyun } from './plugins/upyun';
|
||||||
const port = 4321;
|
|
||||||
const site = import.meta.env.PROD ? 'https://yufan.me' : `http://localhost:${port}`;
|
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: site,
|
// This will override the import.meta.env.SITE. No need to introduce method.
|
||||||
|
site: options.isProd() ? options.website : options.local.website,
|
||||||
output: 'server',
|
output: 'server',
|
||||||
security: {
|
security: {
|
||||||
checkOrigin: true,
|
checkOrigin: true,
|
||||||
@ -17,11 +16,13 @@ export default defineConfig({
|
|||||||
experimental: {
|
experimental: {
|
||||||
env: {
|
env: {
|
||||||
schema: {
|
schema: {
|
||||||
|
// Postgres Database
|
||||||
POSTGRES_HOST: envField.string({ context: 'server', access: 'secret' }),
|
POSTGRES_HOST: envField.string({ context: 'server', access: 'secret' }),
|
||||||
POSTGRES_PORT: envField.number({ context: 'server', access: 'secret' }),
|
POSTGRES_PORT: envField.number({ context: 'server', access: 'secret' }),
|
||||||
POSTGRES_USERNAME: envField.string({ context: 'server', access: 'secret' }),
|
POSTGRES_USERNAME: envField.string({ context: 'server', access: 'secret' }),
|
||||||
POSTGRES_PASSWORD: envField.string({ context: 'server', access: 'secret' }),
|
POSTGRES_PASSWORD: envField.string({ context: 'server', access: 'secret' }),
|
||||||
POSTGRES_DATABASE: envField.string({ context: 'server', access: 'secret' }),
|
POSTGRES_DATABASE: envField.string({ context: 'server', access: 'secret' }),
|
||||||
|
// Artalk Comment
|
||||||
ARTALK_HOST: envField.string({ context: 'server', access: 'secret' }),
|
ARTALK_HOST: envField.string({ context: 'server', access: 'secret' }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -30,6 +31,9 @@ export default defineConfig({
|
|||||||
mdx({
|
mdx({
|
||||||
remarkPlugins: [astroImage],
|
remarkPlugins: [astroImage],
|
||||||
}),
|
}),
|
||||||
|
upyun({
|
||||||
|
path: ['images', 'og', 'cats'],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
adapter: node({
|
adapter: node({
|
||||||
mode: 'standalone',
|
mode: 'standalone',
|
||||||
@ -42,8 +46,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
port: options.local.port,
|
||||||
port: port,
|
|
||||||
},
|
},
|
||||||
devToolbar: {
|
devToolbar: {
|
||||||
// I don't need such toolbar.
|
// I don't need such toolbar.
|
||||||
@ -53,4 +56,8 @@ export default defineConfig({
|
|||||||
// Add this for avoiding the needless import optimize in Vite.
|
// Add this for avoiding the needless import optimize in Vite.
|
||||||
optimizeDeps: { exclude: ['@napi-rs/canvas'] },
|
optimizeDeps: { exclude: ['@napi-rs/canvas'] },
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
assets: 'cats',
|
||||||
|
assetsPrefix: options.assetsPrefix(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
177
options.ts
Normal file
177
options.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import { z } from 'astro/zod';
|
||||||
|
|
||||||
|
// The type of the options, use zod for better validation.
|
||||||
|
const Options = z
|
||||||
|
.object({
|
||||||
|
local: z
|
||||||
|
.object({
|
||||||
|
port: z.number(),
|
||||||
|
})
|
||||||
|
.transform((local) => ({ ...local, website: `http://localhost:${local.port}` })),
|
||||||
|
title: z.string().max(40),
|
||||||
|
website: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.refine((u) => !u.endsWith('/'))
|
||||||
|
.readonly(),
|
||||||
|
description: z.string().max(100),
|
||||||
|
keywords: z.array(z.string()),
|
||||||
|
author: z.object({ name: z.string(), email: z.string().email(), url: z.string().url() }),
|
||||||
|
navigation: z.array(z.object({ text: z.string(), link: z.string(), target: z.string().optional() })),
|
||||||
|
socials: z.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string(),
|
||||||
|
icon: z.string(),
|
||||||
|
type: z.enum(['link', 'qrcode']),
|
||||||
|
title: z.string().optional(),
|
||||||
|
link: z.string().url(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
settings: z.object({
|
||||||
|
initialYear: z.number().max(2024),
|
||||||
|
icpNo: z.string().optional(),
|
||||||
|
locale: z.string().optional().default('zh-CN'),
|
||||||
|
timeZone: z.string().optional().default('Asia/Shanghai'),
|
||||||
|
timeFormat: z.string().optional().default('yyyy-MM-dd HH:mm:ss'),
|
||||||
|
twitter: z.string(),
|
||||||
|
assetPrefix: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.refine((u) => !u.endsWith('/'))
|
||||||
|
.readonly(),
|
||||||
|
post: z.object({
|
||||||
|
sort: z.enum(['asc', 'desc']),
|
||||||
|
feature: z.array(z.string()).optional(),
|
||||||
|
category: z.array(z.string()).optional(),
|
||||||
|
}),
|
||||||
|
pagination: z.object({
|
||||||
|
posts: z.number().optional().default(5),
|
||||||
|
category: z.number().optional().default(7),
|
||||||
|
tags: z.number().optional().default(7),
|
||||||
|
search: z.number().optional().default(7),
|
||||||
|
}),
|
||||||
|
feed: z.object({
|
||||||
|
full: z.boolean().optional().default(true),
|
||||||
|
size: z.number().optional().default(20),
|
||||||
|
}),
|
||||||
|
sidebar: z.object({
|
||||||
|
search: z.boolean().default(false),
|
||||||
|
post: z.number().default(6),
|
||||||
|
comment: z.number().default(0),
|
||||||
|
tag: z.number().default(20),
|
||||||
|
}),
|
||||||
|
comments: z.object({
|
||||||
|
server: z.string().url().readonly(),
|
||||||
|
admins: z.array(z.number()),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.transform((opts) => {
|
||||||
|
const isProd = (): boolean => import.meta.env.MODE === 'production' || process.env.NODE_ENV === 'production';
|
||||||
|
const assetsPrefix = (): string => (isProd() ? opts.settings.assetPrefix : opts.local.website);
|
||||||
|
return {
|
||||||
|
...opts,
|
||||||
|
// Monkey patch for the issue https://github.com/withastro/astro/issues/11282
|
||||||
|
// No need to fallback to the import.meta.env.PROD I think.
|
||||||
|
isProd,
|
||||||
|
// Given that the import.meta.env.ASSETS_PREFIX has two types.
|
||||||
|
// I have to use this uniform method instead.
|
||||||
|
assetsPrefix,
|
||||||
|
defaultOpenGraph: (): string => `${assetsPrefix()}/images/open-graph.png`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
local: {
|
||||||
|
port: 4321,
|
||||||
|
},
|
||||||
|
title: '且听书吟',
|
||||||
|
website: 'https://yufan.me',
|
||||||
|
description: '诗与梦想的远方',
|
||||||
|
keywords: ['雨帆', '且听书吟', 'syhily', 'amehochan', 'yufan'],
|
||||||
|
author: {
|
||||||
|
name: '雨帆',
|
||||||
|
email: 'syhily@gmail.com',
|
||||||
|
url: 'https://yufan.me',
|
||||||
|
},
|
||||||
|
navigation: [
|
||||||
|
{
|
||||||
|
text: '首页',
|
||||||
|
link: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '关于',
|
||||||
|
link: '/about',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '留言',
|
||||||
|
link: '/guestbook',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '友链',
|
||||||
|
link: '/links',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '笔记',
|
||||||
|
link: 'https://note.yufan.me',
|
||||||
|
target: '_blank',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
socials: [
|
||||||
|
{
|
||||||
|
name: 'GitHub',
|
||||||
|
icon: 'icon-github-fill',
|
||||||
|
type: 'link',
|
||||||
|
link: 'https://github.com/syhily',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Twitter',
|
||||||
|
icon: 'icon-twitter',
|
||||||
|
type: 'link',
|
||||||
|
link: 'https://twitter.com/amehochan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Wechat',
|
||||||
|
icon: 'icon-wechat',
|
||||||
|
type: 'qrcode',
|
||||||
|
title: '扫码加我微信好友',
|
||||||
|
link: 'https://u.wechat.com/EBpmuKmrVz4YVFnoCJdnruA',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
initialYear: 2011,
|
||||||
|
icpNo: '皖ICP备2021002315号-2',
|
||||||
|
locale: 'zh-CN',
|
||||||
|
timeZone: 'Asia/Shanghai',
|
||||||
|
timeFormat: 'yyyy-MM-dd',
|
||||||
|
twitter: 'amehochan',
|
||||||
|
assetPrefix: 'https://cat.yufan.me',
|
||||||
|
post: {
|
||||||
|
sort: 'desc',
|
||||||
|
feature: ['secret-of-boys-mind', 'my-darling', 'happiness-caprice'],
|
||||||
|
category: ['article', 'think', 'gossip', 'coding'],
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
posts: 5,
|
||||||
|
category: 7,
|
||||||
|
tags: 7,
|
||||||
|
search: 7,
|
||||||
|
},
|
||||||
|
feed: {
|
||||||
|
full: true,
|
||||||
|
size: 10,
|
||||||
|
},
|
||||||
|
sidebar: {
|
||||||
|
search: true,
|
||||||
|
post: 6,
|
||||||
|
comment: 6,
|
||||||
|
tag: 20,
|
||||||
|
},
|
||||||
|
comments: {
|
||||||
|
server: 'https://comment.yufan.me',
|
||||||
|
admins: [3],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Options.parse(options);
|
227
package-lock.json
generated
227
package-lock.json
generated
@ -32,6 +32,7 @@
|
|||||||
"@types/pg": "^8.11.6",
|
"@types/pg": "^8.11.6",
|
||||||
"@types/qrcode-svg": "^1.1.4",
|
"@types/qrcode-svg": "^1.1.4",
|
||||||
"@types/unist": "^3.0.2",
|
"@types/unist": "^3.0.2",
|
||||||
|
"@types/upyun": "^3.4.3",
|
||||||
"aplayer": "^1.10.1",
|
"aplayer": "^1.10.1",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.3.2",
|
||||||
@ -42,7 +43,8 @@
|
|||||||
"rimraf": "^5.0.7",
|
"rimraf": "^5.0.7",
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0",
|
||||||
|
"upyun": "^3.4.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
@ -171,6 +173,29 @@
|
|||||||
"integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==",
|
"integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@astrojs/markdown-remark/node_modules/is-buffer": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@astrojs/markdown-remark/node_modules/nlcst-to-string": {
|
"node_modules/@astrojs/markdown-remark/node_modules/nlcst-to-string": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-3.1.1.tgz",
|
||||||
@ -2774,6 +2799,16 @@
|
|||||||
"integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==",
|
"integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/upyun": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/upyun/-/upyun-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-iTZTvDxt1h4dmZeJnWsB7YeVRwTwEr8GE22LSKM3F464+4VnaUoIYtcwqkWnxEDPV97ZN3XA0BzSgCx84QR8jA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ungap/structured-clone": {
|
"node_modules/@ungap/structured-clone": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||||
@ -3136,6 +3171,23 @@
|
|||||||
"sharp": "^0.33.3"
|
"sharp": "^0.33.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "0.26.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
|
||||||
|
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.14.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
|
||||||
@ -3414,6 +3466,16 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/charenc": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
@ -3685,6 +3747,19 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/comma-separated-tokens": {
|
"node_modules/comma-separated-tokens": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||||
@ -3730,6 +3805,16 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crypt": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@ -3772,6 +3857,16 @@
|
|||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@ -4349,6 +4444,27 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||||
|
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
||||||
@ -4366,6 +4482,21 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
@ -4812,6 +4943,13 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hmacsha1": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hmacsha1/-/hmacsha1-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-4FP6J0oI8jqb6gLLl9tSwVdosWJ/AKSGJ+HwYf6Ixe4MUcEkst4uWzpVQrNOCin0fzTRQbXV8ePheU8WiiDYBw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD"
|
||||||
|
},
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
||||||
@ -4925,27 +5063,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-buffer": {
|
"node_modules/is-buffer": {
|
||||||
"version": "2.0.5",
|
"version": "1.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||||
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
|
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
|
||||||
"funding": [
|
"dev": true,
|
||||||
{
|
"license": "MIT"
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.13.1",
|
"version": "2.13.1",
|
||||||
@ -5084,6 +5206,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-promise": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-reference": {
|
"node_modules/is-reference": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
|
||||||
@ -5393,6 +5522,18 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/md5": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"charenc": "0.0.2",
|
||||||
|
"crypt": "0.0.2",
|
||||||
|
"is-buffer": "~1.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mdast-util-definitions": {
|
"node_modules/mdast-util-definitions": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz",
|
||||||
@ -6464,6 +6605,29 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mimic-fn": {
|
"node_modules/mimic-fn": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||||
@ -8609,6 +8773,25 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/upyun": {
|
||||||
|
"version": "3.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/upyun/-/upyun-3.4.6.tgz",
|
||||||
|
"integrity": "sha512-ThAI7woGkVE2lsOq8MFYb0Oeg8avOQQbY3XmXmaq1aZVjzcglcMuI/RImBrq+KJw7nX39iNKCJKYs65xiAF53Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.26.1",
|
||||||
|
"base-64": "^1.0.0",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"hmacsha1": "^1.0.0",
|
||||||
|
"is-promise": "^4.0.0",
|
||||||
|
"md5": "^2.3.0",
|
||||||
|
"mime-types": "^2.1.15"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vfile": {
|
"node_modules/vfile": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz",
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
"@types/pg": "^8.11.6",
|
"@types/pg": "^8.11.6",
|
||||||
"@types/qrcode-svg": "^1.1.4",
|
"@types/qrcode-svg": "^1.1.4",
|
||||||
"@types/unist": "^3.0.2",
|
"@types/unist": "^3.0.2",
|
||||||
|
"@types/upyun": "^3.4.3",
|
||||||
"aplayer": "^1.10.1",
|
"aplayer": "^1.10.1",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.3.2",
|
||||||
@ -74,6 +75,7 @@
|
|||||||
"rimraf": "^5.0.7",
|
"rimraf": "^5.0.7",
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"unist-util-visit": "^5.0.0"
|
"unist-util-visit": "^5.0.0",
|
||||||
|
"upyun": "^3.4.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ const transformAstroImage = async (imageNode: ImageNode) => {
|
|||||||
imageNode.name = 'Image';
|
imageNode.name = 'Image';
|
||||||
imageNode.attributes = [
|
imageNode.attributes = [
|
||||||
{ type: 'mdxJsxAttribute', name: 'alt', value: imageNode.alt },
|
{ type: 'mdxJsxAttribute', name: 'alt', value: imageNode.alt },
|
||||||
{ type: 'mdxJsxAttribute', name: 'src', value: imageNode.url },
|
{ type: 'mdxJsxAttribute', name: 'src', value: metadata.src },
|
||||||
{ type: 'mdxJsxAttribute', name: 'width', value: imageNode.width ?? metadata.width },
|
{ type: 'mdxJsxAttribute', name: 'width', value: imageNode.width ?? metadata.width },
|
||||||
{ type: 'mdxJsxAttribute', name: 'height', value: imageNode.height ?? metadata.height },
|
{ type: 'mdxJsxAttribute', name: 'height', value: imageNode.height ?? metadata.height },
|
||||||
{ type: 'mdxJsxAttribute', name: 'blurDataURL', value: metadata.blurDataURL },
|
{ type: 'mdxJsxAttribute', name: 'blurDataURL', value: metadata.blurDataURL },
|
89
plugins/upyun.ts
Normal file
89
plugins/upyun.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import type { AstroIntegration, AstroIntegrationLogger, RouteData } from 'astro';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import up from 'upyun';
|
||||||
|
|
||||||
|
export type UpyunOption = {
|
||||||
|
path: string[];
|
||||||
|
bucket?: string;
|
||||||
|
operator?: string;
|
||||||
|
password?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultOption: UpyunOption = {
|
||||||
|
path: ['images'],
|
||||||
|
bucket: process.env.UPYUN_BUCKET,
|
||||||
|
operator: process.env.UPYUN_OPERATOR,
|
||||||
|
password: process.env.UPYUN_PASSWORD,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const upyun = (opt: UpyunOption): AstroIntegration => ({
|
||||||
|
name: 'upyun',
|
||||||
|
hooks: {
|
||||||
|
'astro:build:done': async ({ dir, logger }: { dir: URL; routes: RouteData[]; logger: AstroIntegrationLogger }) => {
|
||||||
|
const option: UpyunOption = { ...defaultOption, ...opt };
|
||||||
|
if (typeof option.bucket === 'undefined' || opt.bucket === null) {
|
||||||
|
logger.error('No "bucket" found on your configuration, skip deploying.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof option.operator === 'undefined' || opt.operator === null) {
|
||||||
|
logger.error('No "operator" found on your configuration, skip deploying.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof option.password === 'undefined' || opt.password === null) {
|
||||||
|
logger.error('No "password" found on your configuration, skip deploying.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (option.path.length === 0) {
|
||||||
|
logger.warn('No files need to be upload to upyun. Skip.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create UPYUN Client
|
||||||
|
const service = new up.Service(option.bucket, option.operator, option.password);
|
||||||
|
const client = new up.Client(service);
|
||||||
|
|
||||||
|
// Upload one by one
|
||||||
|
const staticRootPath = dir.pathname;
|
||||||
|
for (const dir of option.path) {
|
||||||
|
logger.info(`Start to upload the ${dir} to upyun`);
|
||||||
|
await uploadFile(logger, client, staticRootPath, dir);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizePath = (p: string): string => {
|
||||||
|
return p.includes(path.win32.sep) ? p.split(path.win32.sep).join(path.posix.sep) : p;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFile = async (logger: AstroIntegrationLogger, client: up.Client, root: string, current: string) => {
|
||||||
|
const fullPath = path.join(root, current);
|
||||||
|
const isDir = fs.statSync(fullPath).isDirectory();
|
||||||
|
|
||||||
|
// Visit file.
|
||||||
|
if (!isDir) {
|
||||||
|
const filePath = normalizePath(current);
|
||||||
|
const res1 = await client.headFile(filePath);
|
||||||
|
|
||||||
|
if (res1 === false) {
|
||||||
|
// This file need to be uploaded to upyun.
|
||||||
|
// Try Create directory first.
|
||||||
|
const newDir = filePath.substring(0, filePath.lastIndexOf(path.posix.sep));
|
||||||
|
const res2 = await client.headFile(newDir);
|
||||||
|
if (res2 === false) {
|
||||||
|
logger.info(`Try to create ${newDir} on upyun`);
|
||||||
|
await client.makeDir(newDir);
|
||||||
|
}
|
||||||
|
// Upload file.
|
||||||
|
logger.info(`Try to upload file ${filePath} to upyun`);
|
||||||
|
await client.putFile(filePath, fs.readFileSync(fullPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of fs.readdirSync(fullPath)) {
|
||||||
|
await uploadFile(logger, client, root, path.join(current, item));
|
||||||
|
}
|
||||||
|
};
|
File diff suppressed because one or more lines are too long
@ -1,3 +1,6 @@
|
|||||||
|
/*--------------------------------------------------------------
|
||||||
|
color variables
|
||||||
|
--------------------------------------------------------------*/
|
||||||
:root {
|
:root {
|
||||||
--color-primary: #008c95;
|
--color-primary: #008c95;
|
||||||
--color-dark: #151b2b;
|
--color-dark: #151b2b;
|
||||||
@ -76,7 +79,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* 0. CSS Reset
|
/* 0. CSS Reset
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
@ -190,7 +192,6 @@ table caption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* 1. Document Setup
|
/* 1. Document Setup
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ const { comment, config, depth, pending } = Astro.props;
|
|||||||
</div>
|
</div>
|
||||||
<div class="comment-footer text-xs text-muted">
|
<div class="comment-footer text-xs text-muted">
|
||||||
<time class="me-2">{formatLocalDate(comment.date)}</time>
|
<time class="me-2">{formatLocalDate(comment.date)}</time>
|
||||||
<button class="text-secondary comment-reply-link" data-rid={comment.id}>回复</button>
|
<button class="comment-reply-link" data-rid={comment.id}>回复</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
@ -8,8 +8,8 @@ import type {
|
|||||||
ErrorResp,
|
ErrorResp,
|
||||||
} from '@/components/comment/types';
|
} from '@/components/comment/types';
|
||||||
import { increaseViews } from '@/helpers/db/query';
|
import { increaseViews } from '@/helpers/db/query';
|
||||||
import { options } from '@/helpers/schema';
|
|
||||||
import { urlJoin } from '@/helpers/tools';
|
import { urlJoin } from '@/helpers/tools';
|
||||||
|
import options from '@/options';
|
||||||
import { ARTALK_HOST } from 'astro:env/server';
|
import { ARTALK_HOST } from 'astro:env/server';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
@ -18,7 +18,7 @@ import { ELEMENT_NODE, transform, walk } from 'ultrahtml';
|
|||||||
import sanitize from 'ultrahtml/transformers/sanitize';
|
import sanitize from 'ultrahtml/transformers/sanitize';
|
||||||
|
|
||||||
// Access the artalk in internal docker host when it was deployed on zeabur.
|
// Access the artalk in internal docker host when it was deployed on zeabur.
|
||||||
const server = import.meta.env.PROD ? `http://${ARTALK_HOST}:23366` : options.settings.comments.server;
|
const server = options.isProd() ? `http://${ARTALK_HOST}:23366` : options.settings.comments.server;
|
||||||
|
|
||||||
export const getConfig = async (): Promise<CommentConfig | null> => {
|
export const getConfig = async (): Promise<CommentConfig | null> => {
|
||||||
const data = await fetch(urlJoin(server, '/api/v2/conf'))
|
const data = await fetch(urlJoin(server, '/api/v2/conf'))
|
||||||
|
@ -1,24 +1,42 @@
|
|||||||
---
|
---
|
||||||
|
import options from '@/options';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
import { options } from '@/helpers/schema';
|
|
||||||
|
|
||||||
function currentYear(): number {
|
|
||||||
return DateTime.now().setZone(options.settings.timeZone).year;
|
|
||||||
}
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer class="border-top border-light text-xs text-center py-4 py-xl-5">
|
<footer class="footer border-top border-light text-xs text-center py-4 py-xl-5">
|
||||||
Copyright © {options.settings.initialYear}-{currentYear()}{' '}
|
<div class="line">
|
||||||
|
<span>Copyright © {options.settings.initialYear}-{DateTime.now().setZone(options.settings.timeZone).year}</span>
|
||||||
<a href={import.meta.env.SITE} title={options.title} rel="home">
|
<a href={import.meta.env.SITE} title={options.title} rel="home">
|
||||||
{options.title}
|
{options.title}
|
||||||
</a>
|
</a>
|
||||||
<br />
|
</div>
|
||||||
{
|
{
|
||||||
options.settings.icpNo && (
|
options.settings.icpNo && (
|
||||||
<a href="https://beian.miit.gov.cn" rel="nofollow" target="_blank" title={'良民证'}>
|
<div class="line">
|
||||||
|
<a href="https://beian.miit.gov.cn" rel="nofollow" target="_blank" title="ICP 备案">
|
||||||
{options.settings.icpNo}
|
{options.settings.icpNo}
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
<div class="line">
|
||||||
|
<a href="https://astro.build" target="_blank" rel="nofollow"
|
||||||
|
><svg class="astro-badge" fill="none" width="120" height="20" viewBox="0 0 120 20">
|
||||||
|
<title>Astro Badge</title>
|
||||||
|
<rect width="119" height="19" x=".5" y=".5" fill="#404b69" rx="3.5"></rect>
|
||||||
|
<rect width="119" height="19" x=".5" y=".5" fill="url(#a)" fill-opacity=".5" rx="3.5"></rect>
|
||||||
|
<path
|
||||||
|
fill="#fff"
|
||||||
|
d="M9.07 13V6.79h1.86c1.44 0 2.14.54 2.14 1.63 0 .52-.16.98-.76 1.22v.25c.74.21.96.76.96 1.4 0 1.14-.72 1.71-2.15 1.71H9.07Zm.91-.8h1.14c.85 0 1.2-.29 1.2-1 0-.7-.36-.98-1.2-.98H9.98v1.98Zm0-2.75h.97c.84 0 1.2-.27 1.2-.93 0-.67-.35-.94-1.2-.94h-.97v1.87Zm6.69 3.7c-1.4 0-2.06-.8-2.06-2.45V6.8h.9v3.83c0 1.16.35 1.64 1.16 1.64.8 0 1.15-.48 1.15-1.64V6.79h.9v3.92c0 1.64-.66 2.43-2.05 2.43ZM20.2 13v-.83h1.58V7.62H20.2v-.83h4.09v.83H22.7v4.55h1.59V13H20.2Zm5.96 0V6.79h.9v5.38h2.86V13h-3.76Zm6.78 0V7.62h-1.77v-.83h4.45v.83h-1.76V13h-.92Zm9.57 0-.59-6.21h.88l.4 5.23.23.03L44 8.5h1.1l.57 3.55.25-.03.38-5.23h.88L46.59 13h-1.43l-.48-3.37h-.27L43.93 13H42.5Zm5.57 0v-.83h1.59V7.62h-1.59v-.83h4.09v.83h-1.58v4.55h1.58V13h-4.09Zm7.16 0V7.62h-1.76v-.83h4.45v.83h-1.77V13h-.92Zm7.2 0v-2.77h-2.32V13h-.91V6.79h.9V9.4h2.33V6.79h.9V13h-.9Zm10.38.87c-.5-.46-.64-1.4-.44-2.1.36.44.86.57 1.38.65.8.12 1.58.08 2.32-.29l.25-.15c.07.2.1.4.07.61-.06.5-.31.9-.71 1.19-.16.12-.33.22-.5.33-.5.34-.64.74-.45 1.33l.02.06a1.33 1.33 0 0 1-.6-.5c-.14-.24-.22-.5-.22-.78 0-.13 0-.27-.02-.4-.04-.33-.2-.48-.49-.48a.57.57 0 0 0-.6.46l-.01.07Zm-2.84-2.22s1.47-.71 2.95-.71l1.12-3.45c.04-.16.16-.28.3-.28.13 0 .26.12.3.28l1.11 3.45a6.3 6.3 0 0 1 2.96.71L76.2 4.83c-.07-.2-.2-.33-.36-.33h-3c-.17 0-.29.13-.36.33l-2.51 6.82Zm15.55-.99c0 .6-.75.97-1.8.97-.67 0-.91-.17-.91-.52 0-.37.3-.55.97-.55.61 0 1.14.01 1.74.09v.01Zm0-.74a7.85 7.85 0 0 0-1.6-.14c-1.95 0-2.87.46-2.87 1.54 0 1.11.63 1.54 2.09 1.54 1.23 0 2.06-.31 2.37-1.07h.05l-.02.5c0 .4.07.43.4.43h1.51c-.08-.23-.13-.9-.13-1.46 0-.61.03-1.08.03-1.7 0-1.26-.76-2.07-3.15-2.07-1.02 0-2.16.18-3.03.44.08.34.2 1.04.26 1.5a6.64 6.64 0 0 1 2.64-.51c1.14 0 1.46.26 1.46.79v.2Zm4.18 1.09c-.2.03-.49.03-.78.03-.3 0-.58 0-.77-.03l-.01.2c0 1.05.69 1.66 3.1 1.66 2.27 0 3-.6 3-1.66 0-1-.48-1.5-2.64-1.61-1.68-.08-1.82-.26-1.82-.47 0-.24.21-.37 1.34-.37 1.16 0 1.48.16 1.48.5v.07a16.91 16.91 0 0 1 1.55 0v-.2c0-1.23-1.02-1.63-3-1.63-2.23 0-2.99.55-2.99 1.61 0 .96.6 1.55 2.75 1.64 1.58.05 1.75.23 1.75.47 0 .26-.25.38-1.36.38-1.28 0-1.6-.18-1.6-.54v-.05Zm7.28-4.41a6.43 6.43 0 0 1-2.3 1.27c.02.31.02.88.02 1.2h.55l-.02 1.8c0 1.1.59 1.95 2.42 1.95a8.4 8.4 0 0 0 1.92-.22 16.4 16.4 0 0 1-.17-1.52c-.38.13-.86.2-1.39.2-.73 0-1.03-.2-1.03-.79V9.1c.95 0 1.9.02 2.45.04a18 18 0 0 1 .03-1.48l-2.45.02c0-.37.02-.72.03-1.07h-.06Zm4.94 2.21.02-1.16h-1.66a102.96 102.96 0 0 1 0 5.08h1.9a41.9 41.9 0 0 1-.04-2.08c0-1.14.46-1.46 1.51-1.46.49 0 .84.06 1.14.16.01-.42.1-1.25.14-1.62-.31-.1-.66-.15-1.08-.15-.9-.01-1.56.36-1.87 1.24l-.06-.01Zm8.27 1.34c0 .91-.66 1.34-1.7 1.34-1.03 0-1.7-.4-1.7-1.34 0-.94.68-1.29 1.7-1.29 1.03 0 1.7.38 1.7 1.3Zm1.73-.04c0-1.82-1.42-2.63-3.43-2.63-2.02 0-3.39.81-3.39 2.63 0 1.8 1.28 2.78 3.38 2.78 2.12 0 3.44-.97 3.44-2.78Z"
|
||||||
|
></path>
|
||||||
|
<rect width="119" height="19" x=".5" y=".5" stroke="url(#b)" rx="3.5"></rect>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="a" x1="0" x2="88.9" y1="20" y2="-43.52" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#404b69"></stop>
|
||||||
|
<stop offset="1" stop-color="#404b69"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
<ul class="site-fixed-widget">
|
|
||||||
<li class="fixed-gotop">
|
|
||||||
<div class="btn btn-light btn-icon btn-lg btn-rounded btn-gotop">
|
|
||||||
<span>
|
|
||||||
<i class="iconfont icon-arrowup"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
@ -3,7 +3,7 @@ import Logo from '@/components/header/Logo.astro';
|
|||||||
import LogoLarge from '@/components/header/LogoLarge.astro';
|
import LogoLarge from '@/components/header/LogoLarge.astro';
|
||||||
import QRDialog from '@/components/image/QRDialog.astro';
|
import QRDialog from '@/components/image/QRDialog.astro';
|
||||||
import SearchDialog from '@/components/search/SearchDialog.astro';
|
import SearchDialog from '@/components/search/SearchDialog.astro';
|
||||||
import { options } from '@/helpers/schema';
|
import options from '@/options';
|
||||||
---
|
---
|
||||||
|
|
||||||
<header class="site-aside">
|
<header class="site-aside">
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { options } from '@/helpers/schema';
|
import options from '@/options';
|
||||||
---
|
---
|
||||||
|
|
||||||
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 300 300">
|
||||||
<title>{options.title}</title>
|
<title>{options.title}</title>
|
||||||
<g id="logo-dark" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="logo-dark" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<g transform="translate(9, 16)">
|
<g transform="translate(9, 16)">
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { options } from '@/helpers/schema';
|
import options from '@/options';
|
||||||
---
|
---
|
||||||
|
|
||||||
<svg viewBox="0 0 1237 300" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 1237 300">
|
||||||
<title>{options.title}</title>
|
<title>{options.title}</title>
|
||||||
<g id="logo-large" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="logo-large" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<g id="logo-white" transform="translate(20, 15)">
|
<g id="logo-white" transform="translate(20, 15)">
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
import QRDialog from '@/components/image/QRDialog.astro';
|
import QRDialog from '@/components/image/QRDialog.astro';
|
||||||
import { options, type Post } from '@/helpers/schema';
|
import type { Post } from '@/helpers/schema';
|
||||||
import { urlJoin } from '@/helpers/tools';
|
import { urlJoin } from '@/helpers/tools';
|
||||||
|
import options from '@/options';
|
||||||
import * as querystring from 'node:querystring';
|
import * as querystring from 'node:querystring';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { openGraphHeight, openGraphWidth } from '@/helpers/images';
|
import { openGraphHeight, openGraphWidth } from '@/helpers/images';
|
||||||
import { options } from '@/helpers/schema';
|
|
||||||
import { getPageMeta } from '@/helpers/seo';
|
import { getPageMeta } from '@/helpers/seo';
|
||||||
import { urlJoin } from '@/helpers/tools';
|
import options from '@/options';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -16,12 +15,7 @@ const { og, twitter } = getPageMeta({
|
|||||||
title: title || options.title,
|
title: title || options.title,
|
||||||
description: description || options.description,
|
description: description || options.description,
|
||||||
baseUrl: import.meta.env.SITE,
|
baseUrl: import.meta.env.SITE,
|
||||||
ogImageAbsoluteUrl:
|
ogImageUrl: ogImageUrl,
|
||||||
ogImageUrl === undefined
|
|
||||||
? urlJoin(import.meta.env.SITE, '/images/open-graph.png')
|
|
||||||
: ogImageUrl.startsWith('/')
|
|
||||||
? urlJoin(import.meta.env.SITE, ogImageUrl)
|
|
||||||
: ogImageUrl,
|
|
||||||
ogImageAltText: title || options.title,
|
ogImageAltText: title || options.title,
|
||||||
ogImageWidth: openGraphWidth,
|
ogImageWidth: openGraphWidth,
|
||||||
ogImageHeight: openGraphHeight,
|
ogImageHeight: openGraphHeight,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { openGraphHeight, openGraphWidth } from '@/helpers/images';
|
import { openGraphHeight, openGraphWidth } from '@/helpers/images';
|
||||||
import { options } from '@/helpers/schema';
|
|
||||||
import { getBlogPostMeta } from '@/helpers/seo';
|
import { getBlogPostMeta } from '@/helpers/seo';
|
||||||
import { urlJoin } from '@/helpers/tools';
|
import options from '@/options';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -10,7 +9,7 @@ export interface Props {
|
|||||||
publishDate: string;
|
publishDate: string;
|
||||||
requestPath: string;
|
requestPath: string;
|
||||||
ogImageUrl?: string;
|
ogImageUrl?: string;
|
||||||
ogImageAltText?: string;
|
ogImageAltText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { requestPath, title, description, publishDate, ogImageUrl, ogImageAltText } = Astro.props;
|
const { requestPath, title, description, publishDate, ogImageUrl, ogImageAltText } = Astro.props;
|
||||||
@ -21,12 +20,7 @@ const { og, twitter } = getBlogPostMeta({
|
|||||||
pageUrl: requestPath.startsWith('http') ? requestPath : import.meta.env.SITE + requestPath,
|
pageUrl: requestPath.startsWith('http') ? requestPath : import.meta.env.SITE + requestPath,
|
||||||
authorName: options.author.name,
|
authorName: options.author.name,
|
||||||
publishDate,
|
publishDate,
|
||||||
ogImageAbsoluteUrl:
|
ogImageUrl: ogImageUrl,
|
||||||
typeof ogImageUrl === 'undefined'
|
|
||||||
? urlJoin(import.meta.env.SITE, '/images/open-graph.png')
|
|
||||||
: ogImageUrl.startsWith('/')
|
|
||||||
? urlJoin(import.meta.env.SITE, ogImageUrl)
|
|
||||||
: ogImageUrl,
|
|
||||||
ogImageAltText,
|
ogImageAltText,
|
||||||
ogImageWidth: openGraphWidth,
|
ogImageWidth: openGraphWidth,
|
||||||
ogImageHeight: openGraphHeight,
|
ogImageHeight: openGraphHeight,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import PinnedCategory from '@/components/page/category/PinnedCategory.astro';
|
import PinnedCategory from '@/components/page/category/PinnedCategory.astro';
|
||||||
import { getCategory, options } from '@/helpers/schema';
|
import { getCategory } from '@/helpers/schema';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
const pinnedSlug = options.settings.post.category ?? [];
|
const pinnedSlug = options.settings.post.category ?? [];
|
||||||
const pinnedCategories = pinnedSlug
|
const pinnedCategories = pinnedSlug
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import FeaturePost from '@/components/page/post/FeaturePost.astro';
|
import FeaturePost from '@/components/page/post/FeaturePost.astro';
|
||||||
import { options, type Post } from '@/helpers/schema';
|
import type { Post } from '@/helpers/schema';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
posts: Post[];
|
posts: Post[];
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
import Pagination from '@/components/page/pagination/Pagination.astro';
|
import Pagination from '@/components/page/pagination/Pagination.astro';
|
||||||
import PostCards from '@/components/page/post/PostCards.astro';
|
import PostCards from '@/components/page/post/PostCards.astro';
|
||||||
import { slicePosts } from '@/helpers/formatter';
|
import { slicePosts } from '@/helpers/formatter';
|
||||||
import { options, type Post } from '@/helpers/schema';
|
import type { Post } from '@/helpers/schema';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
posts: Post[];
|
posts: Post[];
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { options } from '@/helpers/schema';
|
import options from '@/options';
|
||||||
---
|
---
|
||||||
|
|
||||||
<div id="search" class="widget widget_search" hidden={!options.settings.sidebar.search}>
|
<div id="search" class="widget widget_search" hidden={!options.settings.sidebar.search}>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { options, type Post } from '@/helpers/schema';
|
import type { Post } from '@/helpers/schema';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
posts: Post[];
|
posts: Post[];
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { options, type Tag } from '@/helpers/schema';
|
import type { Tag } from '@/helpers/schema';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tags: Tag[];
|
tags: Tag[];
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { latestComments } from '@/helpers/db/query';
|
import { latestComments } from '@/helpers/db/query';
|
||||||
import { options } from '@/helpers/schema';
|
import options from '@/options';
|
||||||
|
|
||||||
const comments = await latestComments();
|
const comments = await latestComments();
|
||||||
---
|
---
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { imageMetadata } from '@/helpers/images';
|
import { imageMetadata } from '@/helpers/images';
|
||||||
|
import { urlJoin } from '@/helpers/tools';
|
||||||
|
import options from '@/options';
|
||||||
import { defineCollection, z } from 'astro:content';
|
import { defineCollection, z } from 'astro:content';
|
||||||
|
|
||||||
export const defaultCover = '/images/default-cover.jpg';
|
export const defaultCover = '/images/default-cover.jpg';
|
||||||
@ -40,7 +42,9 @@ const friendsCollection = defineCollection({
|
|||||||
website: z.string().max(40),
|
website: z.string().max(40),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
homepage: z.string().url(),
|
homepage: z.string().url(),
|
||||||
poster: z.string(),
|
poster: z
|
||||||
|
.string()
|
||||||
|
.transform((poster) => (poster.startsWith('/') ? urlJoin(options.assetsPrefix(), poster) : poster)),
|
||||||
favicon: z.string().optional(),
|
favicon: z.string().optional(),
|
||||||
})
|
})
|
||||||
.transform((data) => {
|
.transform((data) => {
|
||||||
@ -52,61 +56,6 @@ const friendsCollection = defineCollection({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Options Collection
|
|
||||||
const optionsCollection = defineCollection({
|
|
||||||
type: 'data',
|
|
||||||
schema: z.object({
|
|
||||||
title: z.string().max(40),
|
|
||||||
website: z.string().url(),
|
|
||||||
description: z.string().max(100),
|
|
||||||
keywords: z.array(z.string()),
|
|
||||||
author: z.object({ name: z.string(), email: z.string().email(), url: z.string().url() }),
|
|
||||||
navigation: z.array(z.object({ text: z.string(), link: z.string(), target: z.string().optional() })),
|
|
||||||
socials: z.array(
|
|
||||||
z.object({
|
|
||||||
name: z.string(),
|
|
||||||
icon: z.string(),
|
|
||||||
type: z.enum(['link', 'qrcode']),
|
|
||||||
title: z.string().optional(),
|
|
||||||
link: z.string().url(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
settings: z.object({
|
|
||||||
initialYear: z.number().max(2024),
|
|
||||||
icpNo: z.string().optional(),
|
|
||||||
locale: z.string().optional().default('zh-CN'),
|
|
||||||
timeZone: z.string().optional().default('Asia/Shanghai'),
|
|
||||||
timeFormat: z.string().optional().default('yyyy-MM-dd HH:mm:ss'),
|
|
||||||
twitter: z.string(),
|
|
||||||
post: z.object({
|
|
||||||
sort: z.enum(['asc', 'desc']),
|
|
||||||
feature: z.array(z.string()).optional(),
|
|
||||||
category: z.array(z.string()).optional(),
|
|
||||||
}),
|
|
||||||
pagination: z.object({
|
|
||||||
posts: z.number().optional().default(5),
|
|
||||||
category: z.number().optional().default(7),
|
|
||||||
tags: z.number().optional().default(7),
|
|
||||||
search: z.number().optional().default(7),
|
|
||||||
}),
|
|
||||||
feed: z.object({
|
|
||||||
full: z.boolean().optional().default(true),
|
|
||||||
size: z.number().optional().default(20),
|
|
||||||
}),
|
|
||||||
sidebar: z.object({
|
|
||||||
search: z.boolean().default(false),
|
|
||||||
post: z.number().default(6),
|
|
||||||
comment: z.number().default(0),
|
|
||||||
tag: z.number().default(20),
|
|
||||||
}),
|
|
||||||
comments: z.object({
|
|
||||||
server: z.string().url().readonly(),
|
|
||||||
admins: z.array(z.number()),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Posts Collection
|
// Posts Collection
|
||||||
const postsCollection = defineCollection({
|
const postsCollection = defineCollection({
|
||||||
type: 'content',
|
type: 'content',
|
||||||
@ -151,7 +100,6 @@ const tagsCollection = defineCollection({
|
|||||||
export const collections = {
|
export const collections = {
|
||||||
categories: categoriesCollection,
|
categories: categoriesCollection,
|
||||||
friends: friendsCollection,
|
friends: friendsCollection,
|
||||||
options: optionsCollection,
|
|
||||||
pages: pagesCollection,
|
pages: pagesCollection,
|
||||||
posts: postsCollection,
|
posts: postsCollection,
|
||||||
tags: tagsCollection,
|
tags: tagsCollection,
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
title: 且听书吟
|
|
||||||
website: https://yufan.me
|
|
||||||
description: 诗与梦想的远方
|
|
||||||
keywords:
|
|
||||||
- 雨帆
|
|
||||||
- 且听书吟
|
|
||||||
- syhily
|
|
||||||
- amehochan
|
|
||||||
- yufan
|
|
||||||
author:
|
|
||||||
name: 雨帆
|
|
||||||
email: syhily@gmail.com
|
|
||||||
url: https://yufan.me
|
|
||||||
navigation:
|
|
||||||
- text: 首页
|
|
||||||
link: /
|
|
||||||
- text: 关于
|
|
||||||
link: /about
|
|
||||||
- text: 留言
|
|
||||||
link: /guestbook
|
|
||||||
- text: 友链
|
|
||||||
link: /links
|
|
||||||
- text: 笔记
|
|
||||||
link: https://note.yufan.me
|
|
||||||
target: _blank
|
|
||||||
socials:
|
|
||||||
- name: GitHub
|
|
||||||
icon: icon-github-fill
|
|
||||||
type: link
|
|
||||||
link: https://github.com/syhily
|
|
||||||
- name: Twitter
|
|
||||||
icon: icon-twitter
|
|
||||||
type: link
|
|
||||||
link: https://twitter.com/amehochan
|
|
||||||
- name: Wechat
|
|
||||||
icon: icon-wechat
|
|
||||||
type: qrcode
|
|
||||||
title: 扫码加我微信好友
|
|
||||||
link: https://u.wechat.com/EBpmuKmrVz4YVFnoCJdnruA
|
|
||||||
settings:
|
|
||||||
initialYear: 2011
|
|
||||||
icpNo: 皖ICP备2021002315号-2
|
|
||||||
locale: zh-CN
|
|
||||||
timeZone: Asia/Shanghai
|
|
||||||
timeFormat: yyyy-MM-dd
|
|
||||||
twitter: amehochan
|
|
||||||
post:
|
|
||||||
# asc or desc
|
|
||||||
sort: desc
|
|
||||||
# This is the PINGED posts on top of your homepage.
|
|
||||||
# You can add the posts id here.
|
|
||||||
# We only show the first three feature posts.
|
|
||||||
feature:
|
|
||||||
- secret-of-boys-mind
|
|
||||||
- my-darling
|
|
||||||
- happiness-caprice
|
|
||||||
# The pinned category slugs.
|
|
||||||
category:
|
|
||||||
- article
|
|
||||||
- think
|
|
||||||
- gossip
|
|
||||||
- coding
|
|
||||||
pagination:
|
|
||||||
# Number of posts per home page.
|
|
||||||
posts: 5
|
|
||||||
# Number of posts per category page.
|
|
||||||
category: 7
|
|
||||||
# Number of posts per tag page.
|
|
||||||
tags: 7
|
|
||||||
# Number of posts in search results.
|
|
||||||
search: 7
|
|
||||||
feed:
|
|
||||||
# Whether to show the post's content.
|
|
||||||
full: true
|
|
||||||
# The counts of the latest post to show.
|
|
||||||
size: 10
|
|
||||||
sidebar:
|
|
||||||
# Display search bar.
|
|
||||||
search: true
|
|
||||||
# Display the number of random posts. Set 0 or a negation number to disable it.
|
|
||||||
post: 6
|
|
||||||
# Display recent blog comments. Set 0 or a negative number to disable it.
|
|
||||||
comment: 6
|
|
||||||
# Display the tag cloud. Set 0 or a negative number to disable it.
|
|
||||||
tag: 20
|
|
||||||
# Artalk configuration
|
|
||||||
comments:
|
|
||||||
# The artalk server endpoint
|
|
||||||
server: https://comment.yufan.me
|
|
||||||
# The administrator user id.
|
|
||||||
admins:
|
|
||||||
- 3
|
|
@ -1,7 +1,7 @@
|
|||||||
import { db } from '@/helpers/db/pool';
|
import { db } from '@/helpers/db/pool';
|
||||||
import { atk_comments, atk_likes, atk_pages, atk_users } from '@/helpers/db/schema';
|
import { atk_comments, atk_likes, atk_pages, atk_users } from '@/helpers/db/schema';
|
||||||
import { options } from '@/helpers/schema';
|
|
||||||
import { makeToken, urlJoin } from '@/helpers/tools';
|
import { makeToken, urlJoin } from '@/helpers/tools';
|
||||||
|
import options from '@/options';
|
||||||
import { and, desc, eq, isNull, notInArray, sql } from 'drizzle-orm';
|
import { and, desc, eq, isNull, notInArray, sql } from 'drizzle-orm';
|
||||||
|
|
||||||
export interface Comment {
|
export interface Comment {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { options, type Post } from '@/helpers/schema';
|
import type { Post } from '@/helpers/schema';
|
||||||
|
import options from '@/options';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
export const slicePosts = (
|
export const slicePosts = (
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
|
import options from '../../options';
|
||||||
|
import { urlJoin } from './tools';
|
||||||
|
|
||||||
export interface Image {
|
export interface Image {
|
||||||
/**
|
/**
|
||||||
@ -55,7 +57,7 @@ export const imageMetadata = async (publicPath: string): Promise<Image> => {
|
|||||||
const { default: sharp } = await import('sharp');
|
const { default: sharp } = await import('sharp');
|
||||||
|
|
||||||
if (!publicPath.startsWith('/')) {
|
if (!publicPath.startsWith('/')) {
|
||||||
throw new Error('We only support image path in public direct. It should start with "/".');
|
throw new Error('We only support image path in "public/images" directory. The path should start with "/images/".');
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = join(process.cwd(), 'public');
|
const root = join(process.cwd(), 'public');
|
||||||
@ -71,5 +73,5 @@ export const imageMetadata = async (publicPath: string): Promise<Image> => {
|
|||||||
const blurImage = await img.resize(blurWidth, blurHeight).webp({ quality: 1 }).toBuffer();
|
const blurImage = await img.resize(blurWidth, blurHeight).webp({ quality: 1 }).toBuffer();
|
||||||
const blurDataURL = `data:image/webp;base64,${blurImage.toString('base64')}`;
|
const blurDataURL = `data:image/webp;base64,${blurImage.toString('base64')}`;
|
||||||
|
|
||||||
return { src: publicPath, height, width, blurDataURL, blurWidth, blurHeight };
|
return { src: urlJoin(options.assetsPrefix(), publicPath), height, width, blurDataURL, blurWidth, blurHeight };
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
* But I have get the approvement to use them here by asking the author https://twitter.com/yuaanlin.
|
* But I have get the approvement to use them here by asking the author https://twitter.com/yuaanlin.
|
||||||
*/
|
*/
|
||||||
import { openGraphHeight, openGraphWidth } from '@/helpers/images';
|
import { openGraphHeight, openGraphWidth } from '@/helpers/images';
|
||||||
import { options } from '@/helpers/schema';
|
import options from '@/options';
|
||||||
import { Canvas, GlobalFonts, Image, type SKRSContext2D } from '@napi-rs/canvas';
|
import { Canvas, GlobalFonts, Image, type SKRSContext2D } from '@napi-rs/canvas';
|
||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
@ -117,6 +117,11 @@ const drawImageProp = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchCover = async (cover: string): Promise<Buffer> => {
|
const fetchCover = async (cover: string): Promise<Buffer> => {
|
||||||
|
if (cover.startsWith(options.assetsPrefix())) {
|
||||||
|
const coverPath = join(process.cwd(), 'public', cover.substring(options.assetsPrefix().length));
|
||||||
|
return await readFile(coverPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (cover.startsWith('http')) {
|
if (cover.startsWith('http')) {
|
||||||
return Buffer.from(await (await fetch(cover)).arrayBuffer());
|
return Buffer.from(await (await fetch(cover)).arrayBuffer());
|
||||||
}
|
}
|
||||||
@ -132,7 +137,7 @@ export interface OpenGraphProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defaultOpenGraph = async (): Promise<Buffer> => {
|
export const defaultOpenGraph = async (): Promise<Buffer> => {
|
||||||
return await fetchCover('/images/default-cover.jpg');
|
return await fetchCover('/images/open-graph.png');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register the font if it doesn't exist
|
// Register the font if it doesn't exist
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { defaultCover } from '@/content/config.ts';
|
import { defaultCover } from '@/content/config.ts';
|
||||||
|
import options from '@/options';
|
||||||
import { getCollection, getEntryBySlug, type Render } from 'astro:content';
|
import { getCollection, getEntryBySlug, type Render } from 'astro:content';
|
||||||
|
|
||||||
// Import the collections from the astro content.
|
// Import the collections from the astro content.
|
||||||
const categoriesCollection = await getCollection('categories');
|
const categoriesCollection = await getCollection('categories');
|
||||||
const friendsCollection = await getCollection('friends');
|
const friendsCollection = await getCollection('friends');
|
||||||
const optionsCollection = await getCollection('options');
|
|
||||||
const pagesCollection = await getCollection('pages');
|
const pagesCollection = await getCollection('pages');
|
||||||
const postsCollection = await getCollection('posts');
|
const postsCollection = await getCollection('posts');
|
||||||
const tagsCollection = await getCollection('tags');
|
const tagsCollection = await getCollection('tags');
|
||||||
@ -12,7 +12,6 @@ const tagsCollection = await getCollection('tags');
|
|||||||
// Redefine the types from the astro content.
|
// Redefine the types from the astro content.
|
||||||
export type Category = (typeof categoriesCollection)[number]['data'] & { counts: number; permalink: string };
|
export type Category = (typeof categoriesCollection)[number]['data'] & { counts: number; permalink: string };
|
||||||
export type Friend = (typeof friendsCollection)[number]['data'][number];
|
export type Friend = (typeof friendsCollection)[number]['data'][number];
|
||||||
export type Options = (typeof optionsCollection)[number]['data'];
|
|
||||||
export type Page = (typeof pagesCollection)[number]['data'] & {
|
export type Page = (typeof pagesCollection)[number]['data'] & {
|
||||||
slug: string;
|
slug: string;
|
||||||
permalink: string;
|
permalink: string;
|
||||||
@ -28,10 +27,9 @@ export type Tag = (typeof tagsCollection)[number]['data'][number] & { counts: nu
|
|||||||
|
|
||||||
// Translate the Astro content into the original content for dealing with different configuration types.
|
// Translate the Astro content into the original content for dealing with different configuration types.
|
||||||
export const friends: Friend[] = friendsCollection[0].data;
|
export const friends: Friend[] = friendsCollection[0].data;
|
||||||
export const options: Options = optionsCollection[0].data;
|
|
||||||
// Override the website for local debugging
|
// Override the website for local debugging
|
||||||
export const pages: Page[] = pagesCollection
|
export const pages: Page[] = pagesCollection
|
||||||
.filter((page) => page.data.published || !import.meta.env.PROD)
|
.filter((page) => page.data.published || !options.isProd())
|
||||||
.map((page) => ({
|
.map((page) => ({
|
||||||
slug: page.slug,
|
slug: page.slug,
|
||||||
permalink: `/${page.slug}`,
|
permalink: `/${page.slug}`,
|
||||||
@ -42,7 +40,7 @@ export const pages: Page[] = pagesCollection
|
|||||||
...page.data,
|
...page.data,
|
||||||
}));
|
}));
|
||||||
export const posts: Post[] = postsCollection
|
export const posts: Post[] = postsCollection
|
||||||
.filter((post) => post.data.published || !import.meta.env.PROD)
|
.filter((post) => post.data.published || !options.isProd())
|
||||||
.map((post) => ({
|
.map((post) => ({
|
||||||
slug: post.slug,
|
slug: post.slug,
|
||||||
permalink: `/posts/${post.slug}`,
|
permalink: `/posts/${post.slug}`,
|
||||||
@ -90,7 +88,7 @@ if (missingTags.length > 0) {
|
|||||||
const missingCovers = posts
|
const missingCovers = posts
|
||||||
.filter((post) => post.cover.src === defaultCover)
|
.filter((post) => post.cover.src === defaultCover)
|
||||||
.map((post) => ({ title: post.title, slug: post.slug }));
|
.map((post) => ({ title: post.title, slug: post.slug }));
|
||||||
if (!import.meta.env.PROD && missingCovers.length > 0) {
|
if (!options.isProd() && missingCovers.length > 0) {
|
||||||
// We only warn here for this is a known improvement.
|
// We only warn here for this is a known improvement.
|
||||||
console.warn(`The following ${missingCovers.length} posts don't have a cover.`);
|
console.warn(`The following ${missingCovers.length} posts don't have a cover.`);
|
||||||
console.warn(missingCovers);
|
console.warn(missingCovers);
|
||||||
@ -124,13 +122,6 @@ if (invalidPinnedCategories.length > 0) {
|
|||||||
throw new Error(`The bellowing pinned categories are invalid:\n$${invalidPinnedCategories.join('\n')}`);
|
throw new Error(`The bellowing pinned categories are invalid:\n$${invalidPinnedCategories.join('\n')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the options with the Astro configuration.
|
|
||||||
if (import.meta.env.PROD && import.meta.env.SITE !== options.website) {
|
|
||||||
throw new Error(
|
|
||||||
`Invalid configuration in options.website: ${options.website} with astro site: ${import.meta.env.SITE}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getPost = (slug: string): Post | undefined => {
|
export const getPost = (slug: string): Post | undefined => {
|
||||||
return posts.find((post) => post.slug === slug);
|
return posts.find((post) => post.slug === slug);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,45 @@
|
|||||||
// This file is copied from https://github.com/flexdinesh/blogster/blob/main/packages/shared/src/seo.ts
|
// This file is copied from https://github.com/flexdinesh/blogster/blob/main/packages/shared/src/seo.ts
|
||||||
// I just modified it for my personal needs.
|
// I just modified it for my personal needs.
|
||||||
|
import { urlJoin } from '@/helpers/tools';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
type PageOgMeta = {
|
export interface PageMeta {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
baseUrl?: string;
|
||||||
|
ogImageUrl?: string;
|
||||||
|
ogImageAltText: string;
|
||||||
|
ogImageWidth?: number;
|
||||||
|
ogImageHeight?: number;
|
||||||
|
siteOwnerTwitterHandle?: string;
|
||||||
|
contentAuthorTwitterHandle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostMeta {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
pageUrl?: string;
|
||||||
|
authorName?: string;
|
||||||
|
publishDate: string;
|
||||||
|
ogImageUrl?: string;
|
||||||
|
ogImageAltText: string;
|
||||||
|
ogImageWidth?: number;
|
||||||
|
ogImageHeight?: number;
|
||||||
|
siteOwnerTwitterHandle?: string;
|
||||||
|
contentAuthorTwitterHandle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TwitterOgMeta {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
card: 'summary_large_image';
|
||||||
|
site?: string;
|
||||||
|
creator?: string;
|
||||||
|
image?: string;
|
||||||
|
imageAlt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PageOgMeta {
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
type: 'website';
|
type: 'website';
|
||||||
@ -10,19 +48,9 @@ type PageOgMeta = {
|
|||||||
imageAlt?: string;
|
imageAlt?: string;
|
||||||
imageWidth?: string;
|
imageWidth?: string;
|
||||||
imageHeight?: string;
|
imageHeight?: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
type PageTwitterMeta = {
|
export interface PostOgMeta {
|
||||||
title: string;
|
|
||||||
description?: string;
|
|
||||||
card: 'summary_large_image';
|
|
||||||
site?: string;
|
|
||||||
creator?: string;
|
|
||||||
image?: string;
|
|
||||||
imageAlt?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PostOgMeta = {
|
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
type: 'article';
|
type: 'article';
|
||||||
@ -34,48 +62,34 @@ type PostOgMeta = {
|
|||||||
imageAlt?: string;
|
imageAlt?: string;
|
||||||
imageWidth?: string;
|
imageWidth?: string;
|
||||||
imageHeight?: string;
|
imageHeight?: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
type PostTwitterMeta = {
|
const parseOgImageUrl = (ogImageUrl?: string): string =>
|
||||||
title: string;
|
typeof ogImageUrl === 'undefined'
|
||||||
description?: string;
|
? options.defaultOpenGraph()
|
||||||
card: 'summary_large_image';
|
: ogImageUrl.startsWith('/')
|
||||||
site?: string;
|
? urlJoin(options.assetsPrefix(), ogImageUrl)
|
||||||
creator?: string;
|
: ogImageUrl;
|
||||||
image?: string;
|
|
||||||
imageAlt?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getPageMeta({
|
export const getPageMeta = ({
|
||||||
title: pageTitle,
|
title,
|
||||||
description,
|
description,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
ogImageAbsoluteUrl,
|
ogImageUrl,
|
||||||
ogImageAltText,
|
ogImageAltText,
|
||||||
ogImageWidth,
|
ogImageWidth,
|
||||||
ogImageHeight,
|
ogImageHeight,
|
||||||
siteOwnerTwitterHandle,
|
siteOwnerTwitterHandle,
|
||||||
contentAuthorTwitterHandle,
|
contentAuthorTwitterHandle,
|
||||||
}: {
|
}: PageMeta): { og: PageOgMeta; twitter: TwitterOgMeta } => {
|
||||||
title: string;
|
if (!title) {
|
||||||
description: string;
|
|
||||||
baseUrl?: string;
|
|
||||||
ogImageAbsoluteUrl?: string; // should always be absolute
|
|
||||||
ogImageAltText?: string;
|
|
||||||
ogImageWidth?: number;
|
|
||||||
ogImageHeight?: number;
|
|
||||||
siteOwnerTwitterHandle?: string;
|
|
||||||
contentAuthorTwitterHandle?: string;
|
|
||||||
}): { og: PageOgMeta; twitter: PageTwitterMeta } {
|
|
||||||
if (!pageTitle) {
|
|
||||||
throw Error('title is required for page SEO');
|
throw Error('title is required for page SEO');
|
||||||
}
|
}
|
||||||
if (ogImageAbsoluteUrl) {
|
const ogImageAbsoluteUrl = parseOgImageUrl(ogImageUrl);
|
||||||
ogImageAltText = !ogImageAltText ? `Preview image for ${pageTitle}` : ogImageAltText;
|
|
||||||
}
|
|
||||||
|
|
||||||
const og: PageOgMeta = {
|
return {
|
||||||
title: pageTitle,
|
og: {
|
||||||
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
type: 'website',
|
type: 'website',
|
||||||
url: baseUrl,
|
url: baseUrl,
|
||||||
@ -83,58 +97,40 @@ export function getPageMeta({
|
|||||||
imageAlt: ogImageAltText,
|
imageAlt: ogImageAltText,
|
||||||
imageWidth: ogImageWidth ? String(ogImageWidth) : undefined,
|
imageWidth: ogImageWidth ? String(ogImageWidth) : undefined,
|
||||||
imageHeight: ogImageHeight ? String(ogImageHeight) : undefined,
|
imageHeight: ogImageHeight ? String(ogImageHeight) : undefined,
|
||||||
};
|
},
|
||||||
|
twitter: {
|
||||||
const twitter: PageTwitterMeta = {
|
title: title,
|
||||||
title: pageTitle,
|
|
||||||
description: description,
|
description: description,
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
site: siteOwnerTwitterHandle,
|
site: siteOwnerTwitterHandle,
|
||||||
creator: contentAuthorTwitterHandle || siteOwnerTwitterHandle,
|
creator: contentAuthorTwitterHandle || siteOwnerTwitterHandle,
|
||||||
image: ogImageAbsoluteUrl,
|
image: ogImageAbsoluteUrl,
|
||||||
imageAlt: ogImageAltText,
|
imageAlt: ogImageAltText,
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
export const getBlogPostMeta = ({
|
||||||
og,
|
title,
|
||||||
twitter,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBlogPostMeta({
|
|
||||||
title: pageTitle,
|
|
||||||
description,
|
description,
|
||||||
pageUrl,
|
pageUrl,
|
||||||
authorName,
|
authorName,
|
||||||
publishDate,
|
publishDate,
|
||||||
ogImageAbsoluteUrl,
|
ogImageUrl,
|
||||||
ogImageAltText,
|
ogImageAltText,
|
||||||
ogImageWidth,
|
ogImageWidth,
|
||||||
ogImageHeight,
|
ogImageHeight,
|
||||||
siteOwnerTwitterHandle,
|
siteOwnerTwitterHandle,
|
||||||
contentAuthorTwitterHandle,
|
contentAuthorTwitterHandle,
|
||||||
}: {
|
}: PostMeta): { og: PostOgMeta; twitter: TwitterOgMeta } => {
|
||||||
title: string;
|
if (!title) {
|
||||||
description: string;
|
|
||||||
pageUrl?: string;
|
|
||||||
authorName?: string;
|
|
||||||
publishDate: string;
|
|
||||||
ogImageAbsoluteUrl?: string; // should always be absolute
|
|
||||||
ogImageAltText?: string;
|
|
||||||
ogImageWidth?: number;
|
|
||||||
ogImageHeight?: number;
|
|
||||||
siteOwnerTwitterHandle?: string;
|
|
||||||
contentAuthorTwitterHandle?: string;
|
|
||||||
}): { og: PostOgMeta; twitter: PostTwitterMeta } {
|
|
||||||
if (!pageTitle) {
|
|
||||||
throw Error('title is required for page SEO');
|
throw Error('title is required for page SEO');
|
||||||
}
|
}
|
||||||
if (ogImageAbsoluteUrl && !ogImageAltText) {
|
const ogImageAbsoluteUrl = parseOgImageUrl(ogImageUrl);
|
||||||
ogImageAltText = `Preview image for ${pageTitle}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const og: PostOgMeta = {
|
return {
|
||||||
title: pageTitle,
|
og: {
|
||||||
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
type: 'article',
|
type: 'article',
|
||||||
url: pageUrl,
|
url: pageUrl,
|
||||||
@ -144,20 +140,15 @@ export function getBlogPostMeta({
|
|||||||
imageAlt: ogImageAltText,
|
imageAlt: ogImageAltText,
|
||||||
imageWidth: ogImageWidth ? String(ogImageWidth) : undefined,
|
imageWidth: ogImageWidth ? String(ogImageWidth) : undefined,
|
||||||
imageHeight: ogImageHeight ? String(ogImageHeight) : undefined,
|
imageHeight: ogImageHeight ? String(ogImageHeight) : undefined,
|
||||||
};
|
},
|
||||||
|
twitter: {
|
||||||
const twitter: PostTwitterMeta = {
|
title: title,
|
||||||
title: pageTitle,
|
|
||||||
description: description,
|
description: description,
|
||||||
card: 'summary_large_image',
|
card: 'summary_large_image',
|
||||||
site: siteOwnerTwitterHandle,
|
site: siteOwnerTwitterHandle,
|
||||||
creator: contentAuthorTwitterHandle || siteOwnerTwitterHandle,
|
creator: contentAuthorTwitterHandle || siteOwnerTwitterHandle,
|
||||||
image: ogImageAbsoluteUrl,
|
image: ogImageAbsoluteUrl,
|
||||||
imageAlt: ogImageAltText,
|
imageAlt: ogImageAltText,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
|
||||||
og,
|
|
||||||
twitter,
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
@ -7,10 +7,9 @@ import '@/assets/styles/reset.css';
|
|||||||
import '@/assets/styles/globals.css';
|
import '@/assets/styles/globals.css';
|
||||||
|
|
||||||
import Footer from '@/components/footer/Footer.astro';
|
import Footer from '@/components/footer/Footer.astro';
|
||||||
import GoTop from '@/components/footer/GoTop.astro';
|
|
||||||
import Header from '@/components/header/Header.astro';
|
import Header from '@/components/header/Header.astro';
|
||||||
import PageMeta from '@/components/meta/PageMeta.astro';
|
import PageMeta from '@/components/meta/PageMeta.astro';
|
||||||
import { options } from '@/helpers/schema';
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -54,7 +53,15 @@ const description = Astro.props.description || options.description;
|
|||||||
<slot />
|
<slot />
|
||||||
<Footer />
|
<Footer />
|
||||||
</main>
|
</main>
|
||||||
<GoTop />
|
<ul class="site-fixed-widget">
|
||||||
|
<li class="fixed-gotop">
|
||||||
|
<div class="btn btn-light btn-icon btn-lg btn-rounded btn-gotop">
|
||||||
|
<span>
|
||||||
|
<i class="iconfont icon-arrowup"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
import '../assets/scripts/yufan.me.js';
|
import '../assets/scripts/yufan.me.js';
|
||||||
|
@ -4,9 +4,10 @@ import Image from '@/components/image/Image.astro';
|
|||||||
import PageMeta from '@/components/meta/PageMeta.astro';
|
import PageMeta from '@/components/meta/PageMeta.astro';
|
||||||
import FriendLinks from '@/components/page/friend/FriendLinks.astro';
|
import FriendLinks from '@/components/page/friend/FriendLinks.astro';
|
||||||
import MusicPlayer from '@/components/player/MusicPlayer.astro';
|
import MusicPlayer from '@/components/player/MusicPlayer.astro';
|
||||||
import { options, type Page } from '@/helpers/schema';
|
import type { Page } from '@/helpers/schema';
|
||||||
import { urlJoin } from '@/helpers/tools';
|
import { urlJoin } from '@/helpers/tools';
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
page: Page;
|
page: Page;
|
||||||
@ -17,7 +18,7 @@ const { Content } = await page.render();
|
|||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={page.title}>
|
<BaseLayout title={page.title}>
|
||||||
<PageMeta slot="og" title={page.title} ogImageUrl={urlJoin(import.meta.env.SITE, `/og/${page.slug}.png`)} />
|
<PageMeta slot="og" title={page.title} ogImageUrl={`/og/${page.slug}.png`} />
|
||||||
|
|
||||||
<div class="px-lg-2 px-xxl-5 py-3 py-md-4 py-xxl-5">
|
<div class="px-lg-2 px-xxl-5 py-3 py-md-4 py-xxl-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -7,9 +7,10 @@ import PostMeta from '@/components/meta/PostMeta.astro';
|
|||||||
import MusicPlayer from '@/components/player/MusicPlayer.astro';
|
import MusicPlayer from '@/components/player/MusicPlayer.astro';
|
||||||
import Sidebar from '@/components/sidebar/Sidebar.astro';
|
import Sidebar from '@/components/sidebar/Sidebar.astro';
|
||||||
import { formatShowDate } from '@/helpers/formatter';
|
import { formatShowDate } from '@/helpers/formatter';
|
||||||
import { options, posts, tags, type Post } from '@/helpers/schema';
|
import { posts, tags, type Post } from '@/helpers/schema';
|
||||||
import { urlJoin } from '@/helpers/tools';
|
import { urlJoin } from '@/helpers/tools';
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
post: Post;
|
post: Post;
|
||||||
@ -26,8 +27,8 @@ const { Content } = await post.render();
|
|||||||
description={post.summary}
|
description={post.summary}
|
||||||
publishDate={post.date.toISOString()}
|
publishDate={post.date.toISOString()}
|
||||||
requestPath={post.permalink}
|
requestPath={post.permalink}
|
||||||
ogImageUrl={urlJoin(import.meta.env.SITE, `/og/${post.slug}.png`)}
|
ogImageUrl={`/og/${post.slug}.png`}
|
||||||
ogImageAltText={post.title}
|
ogImageAltText={`${post.title} - ${post.summary}`}
|
||||||
/>
|
/>
|
||||||
<div class="px-lg-2 px-xxl-5 py-3 py-md-4 py-xxl-5">
|
<div class="px-lg-2 px-xxl-5 py-3 py-md-4 py-xxl-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
import Pagination from '@/components/page/pagination/Pagination.astro';
|
import Pagination from '@/components/page/pagination/Pagination.astro';
|
||||||
import PostSquare from '@/components/page/post/PostSquare.astro';
|
import PostSquare from '@/components/page/post/PostSquare.astro';
|
||||||
import { slicePosts } from '@/helpers/formatter';
|
import { slicePosts } from '@/helpers/formatter';
|
||||||
import { options, type Category, type Post } from '@/helpers/schema';
|
import type { Category, Post } from '@/helpers/schema';
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
category: Category;
|
category: Category;
|
||||||
|
@ -21,7 +21,6 @@ const { title, posts } = Astro.props;
|
|||||||
posts.length === 0 ? (
|
posts.length === 0 ? (
|
||||||
<div class="data-null">
|
<div class="data-null">
|
||||||
<div class="my-auto">
|
<div class="my-auto">
|
||||||
<i class="svg-404" />
|
|
||||||
<h1 class="font-number">404</h1>
|
<h1 class="font-number">404</h1>
|
||||||
<div>抱歉,没有你要找的内容...</div>
|
<div>抱歉,没有你要找的内容...</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
import Pagination from '@/components/page/pagination/Pagination.astro';
|
import Pagination from '@/components/page/pagination/Pagination.astro';
|
||||||
import PostSquare from '@/components/page/post/PostSquare.astro';
|
import PostSquare from '@/components/page/post/PostSquare.astro';
|
||||||
import { slicePosts } from '@/helpers/formatter';
|
import { slicePosts } from '@/helpers/formatter';
|
||||||
import { options, type Post, type Tag } from '@/helpers/schema';
|
import type { Post, Tag } from '@/helpers/schema';
|
||||||
import BaseLayout from '@/layouts/BaseLayout.astro';
|
import BaseLayout from '@/layouts/BaseLayout.astro';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
tag: Tag;
|
tag: Tag;
|
||||||
|
@ -5,7 +5,6 @@ import BaseLayout from '@/layouts/BaseLayout.astro';
|
|||||||
<BaseLayout title="未找到页面">
|
<BaseLayout title="未找到页面">
|
||||||
<div class="data-null">
|
<div class="data-null">
|
||||||
<div class="my-auto">
|
<div class="my-auto">
|
||||||
<i class="svg-404"></i>
|
|
||||||
<h1 class="font-number">404</h1>
|
<h1 class="font-number">404</h1>
|
||||||
<div>抱歉,没有你要找的内容...</div>
|
<div>抱歉,没有你要找的内容...</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { getCategory, options, posts } from '@/helpers/schema';
|
import { getCategory, posts } from '@/helpers/schema';
|
||||||
import CategoryLayout from '@/layouts/posts/CategoryLayout.astro';
|
import CategoryLayout from '@/layouts/posts/CategoryLayout.astro';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
const { slug, num } = Astro.params;
|
const { slug, num } = Astro.params;
|
||||||
const category = getCategory(undefined, slug);
|
const category = getCategory(undefined, slug);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import PostContent from '@/components/page/post/PostContent.astro';
|
import PostContent from '@/components/page/post/PostContent.astro';
|
||||||
import { partialRender } from '@/helpers/container';
|
import { partialRender } from '@/helpers/container';
|
||||||
import { options, posts, type Post } from '@/helpers/schema';
|
import { posts, type Post } from '@/helpers/schema';
|
||||||
import { urlJoin } from '@/helpers/tools';
|
import { urlJoin } from '@/helpers/tools';
|
||||||
|
import options from '@/options';
|
||||||
import rss from '@astrojs/rss';
|
import rss from '@astrojs/rss';
|
||||||
import { ELEMENT_NODE, TEXT_NODE, transform, walk, type TextNode } from 'ultrahtml';
|
import { ELEMENT_NODE, TEXT_NODE, transform, walk, type TextNode } from 'ultrahtml';
|
||||||
import sanitize from 'ultrahtml/transformers/sanitize';
|
import sanitize from 'ultrahtml/transformers/sanitize';
|
||||||
@ -13,7 +14,7 @@ const cleanupContent = async (html: string) => {
|
|||||||
if (node.type === ELEMENT_NODE) {
|
if (node.type === ELEMENT_NODE) {
|
||||||
// Make sure images are absolute, some readers are not smart enough to figure it out
|
// Make sure images are absolute, some readers are not smart enough to figure it out
|
||||||
if (node.name === 'img' && node.attributes.src?.startsWith('/')) {
|
if (node.name === 'img' && node.attributes.src?.startsWith('/')) {
|
||||||
node.attributes.src = urlJoin(import.meta.env.SITE, node.attributes.src);
|
node.attributes.src = urlJoin(options.assetsPrefix(), node.attributes.src);
|
||||||
const { src, alt } = node.attributes;
|
const { src, alt } = node.attributes;
|
||||||
node.attributes = { src, alt };
|
node.attributes = { src, alt };
|
||||||
}
|
}
|
||||||
|
@ -45,9 +45,9 @@ export const GET: APIRoute = async ({ params }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export const getStaticPaths = async () => {
|
||||||
return [
|
return [
|
||||||
...posts.map((post) => ({ params: { slug: post.slug } })),
|
...posts.map((post) => ({ params: { slug: post.slug } })),
|
||||||
...pages.map((page) => ({ params: { slug: page.slug } })),
|
...pages.map((page) => ({ params: { slug: page.slug } })),
|
||||||
];
|
];
|
||||||
}
|
};
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { options, posts } from '@/helpers/schema';
|
import { posts } from '@/helpers/schema';
|
||||||
import { searchPosts } from '@/helpers/search';
|
import { searchPosts } from '@/helpers/search';
|
||||||
import SearchLayout from '@/layouts/posts/SearchLayout.astro';
|
import SearchLayout from '@/layouts/posts/SearchLayout.astro';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
const query = Astro.url.searchParams.get('q') || '';
|
const query = Astro.url.searchParams.get('q') || '';
|
||||||
if (query === '') {
|
if (query === '') {
|
||||||
|
@ -3,7 +3,7 @@ import { urlJoin } from '@/helpers/tools';
|
|||||||
|
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
|
|
||||||
export async function GET() {
|
export const GET = async () => {
|
||||||
const result = `
|
const result = `
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
@ -26,4 +26,4 @@ export async function GET() {
|
|||||||
'Content-Type': 'application/xml',
|
'Content-Type': 'application/xml',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
import { getTag, options, posts } from '@/helpers/schema';
|
import { getTag, posts } from '@/helpers/schema';
|
||||||
import TagLayout from '@/layouts/posts/TagLayout.astro';
|
import TagLayout from '@/layouts/posts/TagLayout.astro';
|
||||||
|
import options from '@/options';
|
||||||
|
|
||||||
const { slug, num } = Astro.params;
|
const { slug, num } = Astro.params;
|
||||||
const tag = getTag(undefined, slug);
|
const tag = getTag(undefined, slug);
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"@/options": ["./options.ts"],
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
},
|
}
|
||||||
"types": ["vite-plugin-arraybuffer/types"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user