feat: optimize code and support multiple languages
This commit is contained in:
parent
67ba797a55
commit
27f5af6986
95 changed files with 4711 additions and 395 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -22,3 +22,6 @@ pnpm-debug.log*
|
|||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
||||
# astro tmp pages
|
||||
astro_tmp_pages_*
|
||||
|
|
|
|||
1
.netlify/build/_@astrojs-ssr-adapter.mjs
Normal file
1
.netlify/build/_@astrojs-ssr-adapter.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from '@astrojs/netlify/ssr-function.js';
|
||||
280
.netlify/build/_astro-internal_middleware.mjs
Normal file
280
.netlify/build/_astro-internal_middleware.mjs
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
import { c as cookieExports } from './chunks/index_BDj_P1f5.mjs';
|
||||
import { A as AstroError, R as ResponseSentError, F as ForbiddenRewrite } from './chunks/astro/server_CzcTbIe6.mjs';
|
||||
|
||||
const DELETED_EXPIRATION = /* @__PURE__ */ new Date(0);
|
||||
const DELETED_VALUE = "deleted";
|
||||
const responseSentSymbol = Symbol.for("astro.responseSent");
|
||||
class AstroCookie {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
json() {
|
||||
if (this.value === void 0) {
|
||||
throw new Error(`Cannot convert undefined to an object.`);
|
||||
}
|
||||
return JSON.parse(this.value);
|
||||
}
|
||||
number() {
|
||||
return Number(this.value);
|
||||
}
|
||||
boolean() {
|
||||
if (this.value === "false") return false;
|
||||
if (this.value === "0") return false;
|
||||
return Boolean(this.value);
|
||||
}
|
||||
}
|
||||
class AstroCookies {
|
||||
#request;
|
||||
#requestValues;
|
||||
#outgoing;
|
||||
#consumed;
|
||||
constructor(request) {
|
||||
this.#request = request;
|
||||
this.#requestValues = null;
|
||||
this.#outgoing = null;
|
||||
this.#consumed = false;
|
||||
}
|
||||
/**
|
||||
* Astro.cookies.delete(key) is used to delete a cookie. Using this method will result
|
||||
* in a Set-Cookie header added to the response.
|
||||
* @param key The cookie to delete
|
||||
* @param options Options related to this deletion, such as the path of the cookie.
|
||||
*/
|
||||
delete(key, options) {
|
||||
const {
|
||||
// @ts-expect-error
|
||||
maxAge: _ignoredMaxAge,
|
||||
// @ts-expect-error
|
||||
expires: _ignoredExpires,
|
||||
...sanitizedOptions
|
||||
} = options || {};
|
||||
const serializeOptions = {
|
||||
expires: DELETED_EXPIRATION,
|
||||
...sanitizedOptions
|
||||
};
|
||||
this.#ensureOutgoingMap().set(key, [
|
||||
DELETED_VALUE,
|
||||
cookieExports.serialize(key, DELETED_VALUE, serializeOptions),
|
||||
false
|
||||
]);
|
||||
}
|
||||
/**
|
||||
* Astro.cookies.get(key) is used to get a cookie value. The cookie value is read from the
|
||||
* request. If you have set a cookie via Astro.cookies.set(key, value), the value will be taken
|
||||
* from that set call, overriding any values already part of the request.
|
||||
* @param key The cookie to get.
|
||||
* @returns An object containing the cookie value as well as convenience methods for converting its value.
|
||||
*/
|
||||
get(key, options = void 0) {
|
||||
if (this.#outgoing?.has(key)) {
|
||||
let [serializedValue, , isSetValue] = this.#outgoing.get(key);
|
||||
if (isSetValue) {
|
||||
return new AstroCookie(serializedValue);
|
||||
} else {
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
const values = this.#ensureParsed(options);
|
||||
if (key in values) {
|
||||
const value = values[key];
|
||||
return new AstroCookie(value);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Astro.cookies.has(key) returns a boolean indicating whether this cookie is either
|
||||
* part of the initial request or set via Astro.cookies.set(key)
|
||||
* @param key The cookie to check for.
|
||||
* @returns
|
||||
*/
|
||||
has(key, options = void 0) {
|
||||
if (this.#outgoing?.has(key)) {
|
||||
let [, , isSetValue] = this.#outgoing.get(key);
|
||||
return isSetValue;
|
||||
}
|
||||
const values = this.#ensureParsed(options);
|
||||
return !!values[key];
|
||||
}
|
||||
/**
|
||||
* Astro.cookies.set(key, value) is used to set a cookie's value. If provided
|
||||
* an object it will be stringified via JSON.stringify(value). Additionally you
|
||||
* can provide options customizing how this cookie will be set, such as setting httpOnly
|
||||
* in order to prevent the cookie from being read in client-side JavaScript.
|
||||
* @param key The name of the cookie to set.
|
||||
* @param value A value, either a string or other primitive or an object.
|
||||
* @param options Options for the cookie, such as the path and security settings.
|
||||
*/
|
||||
set(key, value, options) {
|
||||
if (this.#consumed) {
|
||||
const warning = new Error(
|
||||
"Astro.cookies.set() was called after the cookies had already been sent to the browser.\nThis may have happened if this method was called in an imported component.\nPlease make sure that Astro.cookies.set() is only called in the frontmatter of the main page."
|
||||
);
|
||||
warning.name = "Warning";
|
||||
console.warn(warning);
|
||||
}
|
||||
let serializedValue;
|
||||
if (typeof value === "string") {
|
||||
serializedValue = value;
|
||||
} else {
|
||||
let toStringValue = value.toString();
|
||||
if (toStringValue === Object.prototype.toString.call(value)) {
|
||||
serializedValue = JSON.stringify(value);
|
||||
} else {
|
||||
serializedValue = toStringValue;
|
||||
}
|
||||
}
|
||||
const serializeOptions = {};
|
||||
if (options) {
|
||||
Object.assign(serializeOptions, options);
|
||||
}
|
||||
this.#ensureOutgoingMap().set(key, [
|
||||
serializedValue,
|
||||
cookieExports.serialize(key, serializedValue, serializeOptions),
|
||||
true
|
||||
]);
|
||||
if (this.#request[responseSentSymbol]) {
|
||||
throw new AstroError({
|
||||
...ResponseSentError
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Merges a new AstroCookies instance into the current instance. Any new cookies
|
||||
* will be added to the current instance, overwriting any existing cookies with the same name.
|
||||
*/
|
||||
merge(cookies) {
|
||||
const outgoing = cookies.#outgoing;
|
||||
if (outgoing) {
|
||||
for (const [key, value] of outgoing) {
|
||||
this.#ensureOutgoingMap().set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Astro.cookies.header() returns an iterator for the cookies that have previously
|
||||
* been set by either Astro.cookies.set() or Astro.cookies.delete().
|
||||
* This method is primarily used by adapters to set the header on outgoing responses.
|
||||
* @returns
|
||||
*/
|
||||
*headers() {
|
||||
if (this.#outgoing == null) return;
|
||||
for (const [, value] of this.#outgoing) {
|
||||
yield value[1];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Behaves the same as AstroCookies.prototype.headers(),
|
||||
* but allows a warning when cookies are set after the instance is consumed.
|
||||
*/
|
||||
static consume(cookies) {
|
||||
cookies.#consumed = true;
|
||||
return cookies.headers();
|
||||
}
|
||||
#ensureParsed(options = void 0) {
|
||||
if (!this.#requestValues) {
|
||||
this.#parse(options);
|
||||
}
|
||||
if (!this.#requestValues) {
|
||||
this.#requestValues = {};
|
||||
}
|
||||
return this.#requestValues;
|
||||
}
|
||||
#ensureOutgoingMap() {
|
||||
if (!this.#outgoing) {
|
||||
this.#outgoing = /* @__PURE__ */ new Map();
|
||||
}
|
||||
return this.#outgoing;
|
||||
}
|
||||
#parse(options = void 0) {
|
||||
const raw = this.#request.headers.get("cookie");
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
this.#requestValues = cookieExports.parse(raw, options);
|
||||
}
|
||||
}
|
||||
|
||||
function getParams(route, pathname) {
|
||||
if (!route.params.length) return {};
|
||||
const paramsMatch = route.pattern.exec(pathname);
|
||||
if (!paramsMatch) return {};
|
||||
const params = {};
|
||||
route.params.forEach((key, i) => {
|
||||
if (key.startsWith("...")) {
|
||||
params[key.slice(3)] = paramsMatch[i + 1] ? paramsMatch[i + 1] : void 0;
|
||||
} else {
|
||||
params[key] = paramsMatch[i + 1];
|
||||
}
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
const apiContextRoutesSymbol = Symbol.for("context.routes");
|
||||
|
||||
function sequence(...handlers) {
|
||||
const filtered = handlers.filter((h) => !!h);
|
||||
const length = filtered.length;
|
||||
if (!length) {
|
||||
return defineMiddleware((_context, next) => {
|
||||
return next();
|
||||
});
|
||||
}
|
||||
return defineMiddleware((context, next) => {
|
||||
let carriedPayload = void 0;
|
||||
return applyHandle(0, context);
|
||||
function applyHandle(i, handleContext) {
|
||||
const handle = filtered[i];
|
||||
const result = handle(handleContext, async (payload) => {
|
||||
if (i < length - 1) {
|
||||
if (payload) {
|
||||
let newRequest;
|
||||
if (payload instanceof Request) {
|
||||
newRequest = payload;
|
||||
} else if (payload instanceof URL) {
|
||||
newRequest = new Request(payload, handleContext.request);
|
||||
} else {
|
||||
newRequest = new Request(
|
||||
new URL(payload, handleContext.url.origin),
|
||||
handleContext.request
|
||||
);
|
||||
}
|
||||
const pipeline = Reflect.get(handleContext, apiContextRoutesSymbol);
|
||||
const { routeData, pathname } = await pipeline.tryRewrite(
|
||||
payload,
|
||||
handleContext.request
|
||||
);
|
||||
if (pipeline.serverLike === true && handleContext.isPrerendered === false && routeData.prerender === true) {
|
||||
throw new AstroError({
|
||||
...ForbiddenRewrite,
|
||||
message: ForbiddenRewrite.message(pathname, pathname, routeData.component),
|
||||
hint: ForbiddenRewrite.hint(routeData.component)
|
||||
});
|
||||
}
|
||||
carriedPayload = payload;
|
||||
handleContext.request = newRequest;
|
||||
handleContext.url = new URL(newRequest.url);
|
||||
handleContext.cookies = new AstroCookies(newRequest);
|
||||
handleContext.params = getParams(routeData, pathname);
|
||||
}
|
||||
return applyHandle(i + 1, handleContext);
|
||||
} else {
|
||||
return next(payload ?? carriedPayload);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function defineMiddleware(fn) {
|
||||
return fn;
|
||||
}
|
||||
|
||||
var __defProp=Object.defineProperty;var __name=(target,value)=>__defProp(target,"name",{value,configurable:!0});function removeTrailingSlash(url){return url.at(-1)==="/"?url.slice(0,-1):url}__name(removeTrailingSlash,"removeTrailingSlash");function resolveTrailingSlash(url){let pathName=typeof url=="string"?url:url.pathname;return pathName!=="/"&&pathName.at(-1)==="/"&&(pathName=pathName.slice(0,-1)),pathName}__name(resolveTrailingSlash,"resolveTrailingSlash");function removeHtmlExtension(url){return url.endsWith(".html")?url.slice(0,-5):url}__name(removeHtmlExtension,"removeHtmlExtension");var i18nMiddleware=defineMiddleware((context,next)=>{return next();});var onRequest$1=i18nMiddleware;
|
||||
|
||||
const onRequest = sequence(
|
||||
onRequest$1,
|
||||
|
||||
|
||||
);
|
||||
|
||||
export { onRequest };
|
||||
25
.netlify/build/chunks/_@astrojs-ssr-adapter_CvSoi7hX.mjs
Normal file
25
.netlify/build/chunks/_@astrojs-ssr-adapter_CvSoi7hX.mjs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import * as ssrFunction_js from '@astrojs/netlify/ssr-function.js';
|
||||
|
||||
function _mergeNamespaces(n, m) {
|
||||
for (var i = 0; i < m.length; i++) {
|
||||
const e = m[i];
|
||||
if (typeof e !== 'string' && !Array.isArray(e)) { for (const k in e) {
|
||||
if (k !== 'default' && !(k in n)) {
|
||||
const d = Object.getOwnPropertyDescriptor(e, k);
|
||||
if (d) {
|
||||
Object.defineProperty(n, k, d.get ? d : {
|
||||
enumerable: true,
|
||||
get: () => e[k]
|
||||
});
|
||||
}
|
||||
}
|
||||
} }
|
||||
}
|
||||
return Object.freeze(Object.defineProperty(n, Symbol.toStringTag, { value: 'Module' }));
|
||||
}
|
||||
|
||||
const serverEntrypointModule = /*#__PURE__*/_mergeNamespaces({
|
||||
__proto__: null
|
||||
}, [ssrFunction_js]);
|
||||
|
||||
export { serverEntrypointModule as s };
|
||||
1
.netlify/build/chunks/_astro_assets_C8OlYKXW.mjs
Normal file
1
.netlify/build/chunks/_astro_assets_C8OlYKXW.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
2452
.netlify/build/chunks/astro/server_CzcTbIe6.mjs
Normal file
2452
.netlify/build/chunks/astro/server_CzcTbIe6.mjs
Normal file
File diff suppressed because it is too large
Load diff
1
.netlify/build/chunks/content-assets_DleWbedO.mjs
Normal file
1
.netlify/build/chunks/content-assets_DleWbedO.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/chunks/content-modules_Dz-S_Wwv.mjs
Normal file
1
.netlify/build/chunks/content-modules_Dz-S_Wwv.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
373
.netlify/build/chunks/index_BDj_P1f5.mjs
Normal file
373
.netlify/build/chunks/index_BDj_P1f5.mjs
Normal file
File diff suppressed because one or more lines are too long
1
.netlify/build/chunks/sharp_DTShMHH2.mjs
Normal file
1
.netlify/build/chunks/sharp_DTShMHH2.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
32
.netlify/build/chunks/vnode-children_C1YIWAGb.mjs
Normal file
32
.netlify/build/chunks/vnode-children_C1YIWAGb.mjs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { createElement, Fragment } from 'react';
|
||||
|
||||
var R=0,k=1,j=2;var D=new Set(["area","base","br","col","embed","hr","img","input","keygen","link","meta","param","source","track","wbr"]),x=new Set(["script","style"]),_=/([\@\.a-z0-9_\:\-]*)\s*?=?\s*?(['"]?)([\s\S]*?)\2\s+/gim,o=/(?:<(\/?)([a-zA-Z][a-zA-Z0-9\:-]*)(?:\s([^>]*?))?((?:\s*\/)?)>|(<\!\-\-)([\s\S]*?)(\-\->)|(<\!)([\s\S]*?)(>))/gm;function P(e){let t={},a;if(e)for(_.lastIndex=0,e=" "+(e||"")+" ";a=_.exec(e);)a[0]!==" "&&(t[a[1]]=a[3]);return t}function w(e){let t=typeof e=="string"?e:e.value,a,r,n,i,l,d,g,h,s,c=[];o.lastIndex=0,r=a={type:0,children:[]};let E=0;function m(){i=t.substring(E,o.lastIndex-n[0].length),i&&r.children.push({type:2,value:i,parent:r});}for(;n=o.exec(t);){if(d=n[5]||n[8],g=n[6]||n[9],h=n[7]||n[10],x.has(r.name)&&n[2]!==r.name){l=o.lastIndex-n[0].length,r.children.length>0&&(r.children[0].value+=n[0]);continue}else if(d==="<!--"){if(l=o.lastIndex-n[0].length,x.has(r.name))continue;s={type:3,value:g,parent:r,loc:[{start:l,end:l+d.length},{start:o.lastIndex-h.length,end:o.lastIndex}]},c.push(s),s.parent.children.push(s);}else if(d==="<!")l=o.lastIndex-n[0].length,s={type:4,value:g,parent:r,loc:[{start:l,end:l+d.length},{start:o.lastIndex-h.length,end:o.lastIndex}]},c.push(s),s.parent.children.push(s);else if(n[1]!=="/")if(m(),x.has(r.name)){E=o.lastIndex,m();continue}else s={type:1,name:n[2]+"",attributes:P(n[3]),parent:r,children:[],loc:[{start:o.lastIndex-n[0].length,end:o.lastIndex}]},c.push(s),s.parent.children.push(s),n[4]&&n[4].indexOf("/")>-1||D.has(s.name)?(s.loc[1]=s.loc[0],s.isSelfClosingTag=!0):r=s;else m(),n[2]+""===r.name?(s=r,r=s.parent,s.loc.push({start:o.lastIndex-n[0].length,end:o.lastIndex}),i=t.substring(s.loc[0].end,s.loc[1].start),s.children.length===0&&s.children.push({type:2,value:i,parent:r})):n[2]+""===c[c.length-1].name&&c[c.length-1].isSelfClosingTag===!0&&(s=c[c.length-1],s.loc.push({start:o.lastIndex-n[0].length,end:o.lastIndex}));E=o.lastIndex;}return i=t.slice(E),r.children.push({type:2,value:i,parent:r}),a}
|
||||
|
||||
let ids = 0;
|
||||
function convert(children) {
|
||||
let doc = w(children.toString().trim());
|
||||
let id = ids++;
|
||||
let key = 0;
|
||||
|
||||
function createReactElementFromNode(node) {
|
||||
const childVnodes =
|
||||
Array.isArray(node.children) && node.children.length
|
||||
? node.children.map((child) => createReactElementFromNode(child)).filter(Boolean)
|
||||
: undefined;
|
||||
|
||||
if (node.type === R) {
|
||||
return createElement(Fragment, {}, childVnodes);
|
||||
} else if (node.type === k) {
|
||||
const { class: className, ...props } = node.attributes;
|
||||
return createElement(node.name, { ...props, className, key: `${id}-${key++}` }, childVnodes);
|
||||
} else if (node.type === j) {
|
||||
// 0-length text gets omitted in JSX
|
||||
return node.value.trim() ? node.value : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const root = createReactElementFromNode(doc);
|
||||
return root.props.children;
|
||||
}
|
||||
|
||||
export { convert as default };
|
||||
72
.netlify/build/entry.mjs
Normal file
72
.netlify/build/entry.mjs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { renderers } from './renderers.mjs';
|
||||
import { s as serverEntrypointModule } from './chunks/_@astrojs-ssr-adapter_CvSoi7hX.mjs';
|
||||
import { manifest } from './manifest_DsyWDHzC.mjs';
|
||||
import { createExports } from '@astrojs/netlify/ssr-function.js';
|
||||
|
||||
const serverIslandMap = new Map([
|
||||
]);;
|
||||
|
||||
const _page0 = () => import('./pages/about.astro.mjs');
|
||||
const _page1 = () => import('./pages/archive.astro.mjs');
|
||||
const _page2 = () => import('./pages/atom.xml.astro.mjs');
|
||||
const _page3 = () => import('./pages/en/about.astro.mjs');
|
||||
const _page4 = () => import('./pages/en/archive.astro.mjs');
|
||||
const _page5 = () => import('./pages/en/en/posts/_---slug_.astro.mjs');
|
||||
const _page6 = () => import('./pages/en/en/tags/_tag_.astro.mjs');
|
||||
const _page7 = () => import('./pages/en/posts/_---slug_.astro.mjs');
|
||||
const _page8 = () => import('./pages/en/posts/_---slug_.astro2.mjs');
|
||||
const _page9 = () => import('./pages/en/tags/_tag_.astro.mjs');
|
||||
const _page10 = () => import('./pages/en/tags/_tag_.astro2.mjs');
|
||||
const _page11 = () => import('./pages/en/tags.astro.mjs');
|
||||
const _page12 = () => import('./pages/en/zh/posts/_---slug_.astro.mjs');
|
||||
const _page13 = () => import('./pages/en/zh/tags/_tag_.astro.mjs');
|
||||
const _page14 = () => import('./pages/en.astro.mjs');
|
||||
const _page15 = () => import('./pages/posts/_---slug_.astro.mjs');
|
||||
const _page16 = () => import('./pages/robots.txt.astro.mjs');
|
||||
const _page17 = () => import('./pages/tags/_tag_.astro.mjs');
|
||||
const _page18 = () => import('./pages/tags.astro.mjs');
|
||||
const _page19 = () => import('./pages/zh/posts/_---slug_.astro.mjs');
|
||||
const _page20 = () => import('./pages/zh/tags/_tag_.astro.mjs');
|
||||
const _page21 = () => import('./pages/index.astro.mjs');
|
||||
const pageMap = new Map([
|
||||
["src/pages/about/index.astro", _page0],
|
||||
["src/pages/archive/index.astro", _page1],
|
||||
["src/pages/atom/index.xml.ts", _page2],
|
||||
["src/astro_tmp_pages_en/about/index.astro", _page3],
|
||||
["src/astro_tmp_pages_en/archive/index.astro", _page4],
|
||||
["src/astro_tmp_pages_en/en/posts/[...slug].astro", _page5],
|
||||
["src/astro_tmp_pages_en/en/tags/[tag].astro", _page6],
|
||||
["src/pages/en/posts/[...slug].astro", _page7],
|
||||
["src/astro_tmp_pages_en/posts/[...slug].astro", _page8],
|
||||
["src/pages/en/tags/[tag].astro", _page9],
|
||||
["src/astro_tmp_pages_en/tags/[tag].astro", _page10],
|
||||
["src/astro_tmp_pages_en/tags/index.astro", _page11],
|
||||
["src/astro_tmp_pages_en/zh/posts/[...slug].astro", _page12],
|
||||
["src/astro_tmp_pages_en/zh/tags/[tag].astro", _page13],
|
||||
["src/astro_tmp_pages_en/index.astro", _page14],
|
||||
["src/pages/posts/[...slug].astro", _page15],
|
||||
["src/pages/robots/index.txt.ts", _page16],
|
||||
["src/pages/tags/[tag].astro", _page17],
|
||||
["src/pages/tags/index.astro", _page18],
|
||||
["src/pages/zh/posts/[...slug].astro", _page19],
|
||||
["src/pages/zh/tags/[tag].astro", _page20],
|
||||
["src/pages/index.astro", _page21]
|
||||
]);
|
||||
|
||||
const _manifest = Object.assign(manifest, {
|
||||
pageMap,
|
||||
serverIslandMap,
|
||||
renderers,
|
||||
middleware: () => import('./_astro-internal_middleware.mjs')
|
||||
});
|
||||
const _args = {
|
||||
"middlewareSecret": "b398f6c4-5e35-4573-8803-aeedff401909"
|
||||
};
|
||||
const _exports = createExports(_manifest, _args);
|
||||
const __astrojsSsrVirtualEntry = _exports.default;
|
||||
const _start = 'start';
|
||||
if (_start in serverEntrypointModule) {
|
||||
serverEntrypointModule[_start](_manifest, _args);
|
||||
}
|
||||
|
||||
export { __astrojsSsrVirtualEntry as default, pageMap };
|
||||
103
.netlify/build/manifest_DsyWDHzC.mjs
Normal file
103
.netlify/build/manifest_DsyWDHzC.mjs
Normal file
File diff suppressed because one or more lines are too long
1
.netlify/build/pages/about.astro.mjs
Normal file
1
.netlify/build/pages/about.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/archive.astro.mjs
Normal file
1
.netlify/build/pages/archive.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/atom.xml.astro.mjs
Normal file
1
.netlify/build/pages/atom.xml.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en.astro.mjs
Normal file
1
.netlify/build/pages/en.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/about.astro.mjs
Normal file
1
.netlify/build/pages/en/about.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/archive.astro.mjs
Normal file
1
.netlify/build/pages/en/archive.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/en/posts/_---slug_.astro.mjs
Normal file
1
.netlify/build/pages/en/en/posts/_---slug_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/en/tags/_tag_.astro.mjs
Normal file
1
.netlify/build/pages/en/en/tags/_tag_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/posts/_---slug_.astro.mjs
Normal file
1
.netlify/build/pages/en/posts/_---slug_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/posts/_---slug_.astro2.mjs
Normal file
1
.netlify/build/pages/en/posts/_---slug_.astro2.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/tags.astro.mjs
Normal file
1
.netlify/build/pages/en/tags.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/tags/_tag_.astro.mjs
Normal file
1
.netlify/build/pages/en/tags/_tag_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/tags/_tag_.astro2.mjs
Normal file
1
.netlify/build/pages/en/tags/_tag_.astro2.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/zh/posts/_---slug_.astro.mjs
Normal file
1
.netlify/build/pages/en/zh/posts/_---slug_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/en/zh/tags/_tag_.astro.mjs
Normal file
1
.netlify/build/pages/en/zh/tags/_tag_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/index.astro.mjs
Normal file
1
.netlify/build/pages/index.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/posts/_---slug_.astro.mjs
Normal file
1
.netlify/build/pages/posts/_---slug_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/robots.txt.astro.mjs
Normal file
1
.netlify/build/pages/robots.txt.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/tags.astro.mjs
Normal file
1
.netlify/build/pages/tags.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/tags/_tag_.astro.mjs
Normal file
1
.netlify/build/pages/tags/_tag_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/zh/posts/_---slug_.astro.mjs
Normal file
1
.netlify/build/pages/zh/posts/_---slug_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
1
.netlify/build/pages/zh/tags/_tag_.astro.mjs
Normal file
1
.netlify/build/pages/zh/tags/_tag_.astro.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Contents removed by Astro as it's used for prerendering only
|
||||
310
.netlify/build/renderers.mjs
Normal file
310
.netlify/build/renderers.mjs
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
import React, { createElement } from 'react';
|
||||
import ReactDOM from 'react-dom/server';
|
||||
import { p as AstroJSX, q as renderJSX, f as createVNode, s as AstroUserError } from './chunks/astro/server_CzcTbIe6.mjs';
|
||||
|
||||
const opts = {
|
||||
experimentalReactChildren: false
|
||||
};
|
||||
|
||||
const contexts = new WeakMap();
|
||||
|
||||
const ID_PREFIX = 'r';
|
||||
|
||||
function getContext(rendererContextResult) {
|
||||
if (contexts.has(rendererContextResult)) {
|
||||
return contexts.get(rendererContextResult);
|
||||
}
|
||||
const ctx = {
|
||||
currentIndex: 0,
|
||||
get id() {
|
||||
return ID_PREFIX + this.currentIndex.toString();
|
||||
},
|
||||
};
|
||||
contexts.set(rendererContextResult, ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function incrementId(rendererContextResult) {
|
||||
const ctx = getContext(rendererContextResult);
|
||||
const id = ctx.id;
|
||||
ctx.currentIndex++;
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Astro passes `children` as a string of HTML, so we need
|
||||
* a wrapper `div` to render that content as VNodes.
|
||||
*
|
||||
* As a bonus, we can signal to React that this subtree is
|
||||
* entirely static and will never change via `shouldComponentUpdate`.
|
||||
*/
|
||||
const StaticHtml = ({ value, name, hydrate = true }) => {
|
||||
if (!value) return null;
|
||||
const tagName = hydrate ? 'astro-slot' : 'astro-static-slot';
|
||||
return createElement(tagName, {
|
||||
name,
|
||||
suppressHydrationWarning: true,
|
||||
dangerouslySetInnerHTML: { __html: value },
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This tells React to opt-out of re-rendering this subtree,
|
||||
* In addition to being a performance optimization,
|
||||
* this also allows other frameworks to attach to `children`.
|
||||
*
|
||||
* See https://preactjs.com/guide/v8/external-dom-mutations
|
||||
*/
|
||||
StaticHtml.shouldComponentUpdate = () => false;
|
||||
|
||||
const slotName$1 = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
|
||||
const reactTypeof = Symbol.for('react.element');
|
||||
|
||||
async function check$1(Component, props, children) {
|
||||
// Note: there are packages that do some unholy things to create "components".
|
||||
// Checking the $$typeof property catches most of these patterns.
|
||||
if (typeof Component === 'object') {
|
||||
return Component['$$typeof'].toString().slice('Symbol('.length).startsWith('react');
|
||||
}
|
||||
if (typeof Component !== 'function') return false;
|
||||
if (Component.name === 'QwikComponent') return false;
|
||||
|
||||
// Preact forwarded-ref components can be functions, which React does not support
|
||||
if (typeof Component === 'function' && Component['$$typeof'] === Symbol.for('react.forward_ref'))
|
||||
return false;
|
||||
|
||||
if (Component.prototype != null && typeof Component.prototype.render === 'function') {
|
||||
return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component);
|
||||
}
|
||||
|
||||
let isReactComponent = false;
|
||||
function Tester(...args) {
|
||||
try {
|
||||
const vnode = Component(...args);
|
||||
if (vnode && vnode['$$typeof'] === reactTypeof) {
|
||||
isReactComponent = true;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return React.createElement('div');
|
||||
}
|
||||
|
||||
await renderToStaticMarkup$1(Tester, props, children, {});
|
||||
|
||||
return isReactComponent;
|
||||
}
|
||||
|
||||
async function getNodeWritable() {
|
||||
let nodeStreamBuiltinModuleName = 'node:stream';
|
||||
let { Writable } = await import(/* @vite-ignore */ nodeStreamBuiltinModuleName);
|
||||
return Writable;
|
||||
}
|
||||
|
||||
function needsHydration(metadata) {
|
||||
// Adjust how this is hydrated only when the version of Astro supports `astroStaticSlot`
|
||||
return metadata.astroStaticSlot ? !!metadata.hydrate : true;
|
||||
}
|
||||
|
||||
async function renderToStaticMarkup$1(Component, props, { default: children, ...slotted }, metadata) {
|
||||
let prefix;
|
||||
if (this && this.result) {
|
||||
prefix = incrementId(this.result);
|
||||
}
|
||||
const attrs = { prefix };
|
||||
|
||||
delete props['class'];
|
||||
const slots = {};
|
||||
for (const [key, value] of Object.entries(slotted)) {
|
||||
const name = slotName$1(key);
|
||||
slots[name] = React.createElement(StaticHtml, {
|
||||
hydrate: needsHydration(metadata),
|
||||
value,
|
||||
name,
|
||||
});
|
||||
}
|
||||
// Note: create newProps to avoid mutating `props` before they are serialized
|
||||
const newProps = {
|
||||
...props,
|
||||
...slots,
|
||||
};
|
||||
const newChildren = children ?? props.children;
|
||||
if (children && opts.experimentalReactChildren) {
|
||||
attrs['data-react-children'] = true;
|
||||
const convert = await import('./chunks/vnode-children_C1YIWAGb.mjs').then((mod) => mod.default);
|
||||
newProps.children = convert(children);
|
||||
} else if (newChildren != null) {
|
||||
newProps.children = React.createElement(StaticHtml, {
|
||||
hydrate: needsHydration(metadata),
|
||||
value: newChildren,
|
||||
});
|
||||
}
|
||||
const formState = this ? await getFormState(this) : undefined;
|
||||
if (formState) {
|
||||
attrs['data-action-result'] = JSON.stringify(formState[0]);
|
||||
attrs['data-action-key'] = formState[1];
|
||||
attrs['data-action-name'] = formState[2];
|
||||
}
|
||||
const vnode = React.createElement(Component, newProps);
|
||||
const renderOptions = {
|
||||
identifierPrefix: prefix,
|
||||
formState,
|
||||
};
|
||||
let html;
|
||||
if ('renderToReadableStream' in ReactDOM) {
|
||||
html = await renderToReadableStreamAsync(vnode, renderOptions);
|
||||
} else {
|
||||
html = await renderToPipeableStreamAsync(vnode, renderOptions);
|
||||
}
|
||||
return { html, attrs };
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<[actionResult: any, actionKey: string, actionName: string] | undefined>}
|
||||
*/
|
||||
async function getFormState({ result }) {
|
||||
const { request, actionResult } = result;
|
||||
|
||||
if (!actionResult) return undefined;
|
||||
if (!isFormRequest(request.headers.get('content-type'))) return undefined;
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const formData = await request.clone().formData();
|
||||
/**
|
||||
* The key generated by React to identify each `useActionState()` call.
|
||||
* @example "k511f74df5a35d32e7cf266450d85cb6c"
|
||||
*/
|
||||
const actionKey = formData.get('$ACTION_KEY')?.toString();
|
||||
/**
|
||||
* The action name returned by an action's `toString()` property.
|
||||
* This matches the endpoint path.
|
||||
* @example "/_actions/blog.like"
|
||||
*/
|
||||
const actionName = searchParams.get('_action');
|
||||
|
||||
if (!actionKey || !actionName) return undefined;
|
||||
|
||||
return [actionResult, actionKey, actionName];
|
||||
}
|
||||
|
||||
async function renderToPipeableStreamAsync(vnode, options) {
|
||||
const Writable = await getNodeWritable();
|
||||
let html = '';
|
||||
return new Promise((resolve, reject) => {
|
||||
let error = undefined;
|
||||
let stream = ReactDOM.renderToPipeableStream(vnode, {
|
||||
...options,
|
||||
onError(err) {
|
||||
error = err;
|
||||
reject(error);
|
||||
},
|
||||
onAllReady() {
|
||||
stream.pipe(
|
||||
new Writable({
|
||||
write(chunk, _encoding, callback) {
|
||||
html += chunk.toString('utf-8');
|
||||
callback();
|
||||
},
|
||||
destroy() {
|
||||
resolve(html);
|
||||
},
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a while loop instead of "for await" due to cloudflare and Vercel Edge issues
|
||||
* See https://github.com/facebook/react/issues/24169
|
||||
*/
|
||||
async function readResult(stream) {
|
||||
const reader = stream.getReader();
|
||||
let result = '';
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
if (value) {
|
||||
result += decoder.decode(value);
|
||||
} else {
|
||||
// This closes the decoder
|
||||
decoder.decode(new Uint8Array());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
result += decoder.decode(value, { stream: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function renderToReadableStreamAsync(vnode, options) {
|
||||
return await readResult(await ReactDOM.renderToReadableStream(vnode, options));
|
||||
}
|
||||
|
||||
const formContentTypes = ['application/x-www-form-urlencoded', 'multipart/form-data'];
|
||||
|
||||
function isFormRequest(contentType) {
|
||||
// Split off parameters like charset or boundary
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type#content-type_in_html_forms
|
||||
const type = contentType?.split(';')[0].toLowerCase();
|
||||
|
||||
return formContentTypes.some((t) => type === t);
|
||||
}
|
||||
|
||||
const _renderer0 = {
|
||||
name: '@astrojs/react',
|
||||
check: check$1,
|
||||
renderToStaticMarkup: renderToStaticMarkup$1,
|
||||
supportsAstroStaticSlot: true,
|
||||
};
|
||||
|
||||
const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
|
||||
async function check(Component, props, { default: children = null, ...slotted } = {}) {
|
||||
if (typeof Component !== "function") return false;
|
||||
const slots = {};
|
||||
for (const [key, value] of Object.entries(slotted)) {
|
||||
const name = slotName(key);
|
||||
slots[name] = value;
|
||||
}
|
||||
try {
|
||||
const result = await Component({ ...props, ...slots, children });
|
||||
return result[AstroJSX];
|
||||
} catch (e) {
|
||||
throwEnhancedErrorIfMdxComponent(e, Component);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
async function renderToStaticMarkup(Component, props = {}, { default: children = null, ...slotted } = {}) {
|
||||
const slots = {};
|
||||
for (const [key, value] of Object.entries(slotted)) {
|
||||
const name = slotName(key);
|
||||
slots[name] = value;
|
||||
}
|
||||
const { result } = this;
|
||||
try {
|
||||
const html = await renderJSX(result, createVNode(Component, { ...props, ...slots, children }));
|
||||
return { html };
|
||||
} catch (e) {
|
||||
throwEnhancedErrorIfMdxComponent(e, Component);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
function throwEnhancedErrorIfMdxComponent(error, Component) {
|
||||
if (Component[Symbol.for("mdx-component")]) {
|
||||
if (AstroUserError.is(error)) return;
|
||||
error.title = error.name;
|
||||
error.hint = `This issue often occurs when your MDX component encounters runtime errors.`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const renderer = {
|
||||
name: "astro:jsx",
|
||||
check,
|
||||
renderToStaticMarkup
|
||||
};
|
||||
var server_default = renderer;
|
||||
|
||||
const renderers = [Object.assign({"name":"@astrojs/react","clientEntrypoint":"@astrojs/react/client.js","serverEntrypoint":"@astrojs/react/server.js"}, { ssr: _renderer0 }),Object.assign({"name":"astro:jsx","serverEntrypoint":"file:///Users/sunguoqi/Developer/astro-air/node_modules/.pnpm/@astrojs+mdx@4.0.2_astro@5.0.5_@types+node@22.10.2_jiti@1.21.6_rollup@4.27.4_typescript@5.7.2_yaml@2.6.1_/node_modules/@astrojs/mdx/dist/server.js"}, { ssr: server_default }),];
|
||||
|
||||
export { renderers };
|
||||
|
|
@ -1,14 +1,50 @@
|
|||
// @ts-check
|
||||
// @ts-check
|
||||
import mdx from "@astrojs/mdx"
|
||||
import netlify from "@astrojs/netlify"
|
||||
import react from "@astrojs/react"
|
||||
import sitemap from "@astrojs/sitemap"
|
||||
import tailwind from "@astrojs/tailwind"
|
||||
import { filterSitemapByDefaultLocale, i18n } from "astro-i18n-aut/integration"
|
||||
import { defineConfig } from "astro/config"
|
||||
import { defaultLanguage } from "./src/config"
|
||||
|
||||
import mdx from "@astrojs/mdx";
|
||||
const defaultLocale = defaultLanguage
|
||||
|
||||
const locales = {
|
||||
en: "en-US",
|
||||
zh: "zh-CN",
|
||||
}
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [react(), tailwind(), mdx()],
|
||||
site: "https://localhost:4321/",
|
||||
trailingSlash: "never",
|
||||
build: {
|
||||
format: "file",
|
||||
},
|
||||
vite: {
|
||||
worker: {
|
||||
plugins: () => [],
|
||||
},
|
||||
},
|
||||
integrations: [
|
||||
react(),
|
||||
tailwind(),
|
||||
mdx(),
|
||||
i18n({
|
||||
locales,
|
||||
defaultLocale,
|
||||
redirectDefaultLocale: false,
|
||||
exclude: ["**/atom/**", "**/robots/**", "**/*.xml.*", "**/*.txt.*"],
|
||||
}),
|
||||
sitemap({
|
||||
i18n: {
|
||||
locales,
|
||||
defaultLocale,
|
||||
},
|
||||
filter: filterSitemapByDefaultLocale({ defaultLocale }),
|
||||
}),
|
||||
],
|
||||
adapter: process.env.NODE_ENV === "production" ? netlify() : undefined,
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@
|
|||
"@astrojs/netlify": "^6.0.0",
|
||||
"@astrojs/react": "^4.1.0",
|
||||
"@astrojs/rss": "^4.0.10",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@astrojs/tailwind": "^5.1.3",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"astro": "^5.0.5",
|
||||
"astro-i18n-aut": "^0.7.0",
|
||||
"lucide-react": "^0.461.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
|
|
|||
107
pnpm-lock.yaml
generated
107
pnpm-lock.yaml
generated
|
|
@ -23,6 +23,9 @@ importers:
|
|||
'@astrojs/rss':
|
||||
specifier: ^4.0.10
|
||||
version: 4.0.10
|
||||
'@astrojs/sitemap':
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^5.1.3
|
||||
version: 5.1.3(astro@5.0.5(@types/node@22.10.2)(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1))(tailwindcss@3.4.16)
|
||||
|
|
@ -35,6 +38,9 @@ importers:
|
|||
astro:
|
||||
specifier: ^5.0.5
|
||||
version: 5.0.5(@types/node@22.10.2)(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1)
|
||||
astro-i18n-aut:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0(astro@5.0.5(@types/node@22.10.2)(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1))(kleur@4.1.5)
|
||||
lucide-react:
|
||||
specifier: ^0.461.0
|
||||
version: 0.461.0(react@18.3.1)
|
||||
|
|
@ -161,6 +167,9 @@ packages:
|
|||
'@astrojs/rss@4.0.10':
|
||||
resolution: {integrity: sha512-2gFdHM763uUAySkdwPYrpi6dppOBJr9ddg5VbkKXctWze8d1JHgIBBY78zWIYs7KBJT58zxadsObVAVt55RDaw==}
|
||||
|
||||
'@astrojs/sitemap@3.2.1':
|
||||
resolution: {integrity: sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA==}
|
||||
|
||||
'@astrojs/tailwind@5.1.3':
|
||||
resolution: {integrity: sha512-XF7WhXRhqEHGvADqc0kDtF7Yv/g4wAWTaj91jBBTBaYnc4+MQLH94duFfFa4NlTkRG40VQd012eF3MhO3Kk+bg==}
|
||||
peerDependencies:
|
||||
|
|
@ -977,6 +986,9 @@ packages:
|
|||
'@types/nlcst@2.0.3':
|
||||
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
||||
|
||||
'@types/node@17.0.45':
|
||||
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
||||
|
||||
'@types/node@22.10.2':
|
||||
resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==}
|
||||
|
||||
|
|
@ -992,6 +1004,9 @@ packages:
|
|||
'@types/sanitize-html@2.13.0':
|
||||
resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==}
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
|
||||
|
||||
'@types/unist@2.0.11':
|
||||
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
|
||||
|
||||
|
|
@ -1201,6 +1216,12 @@ packages:
|
|||
resolution: {integrity: sha512-F6NW1RJo5pp2kPnnM97M5Ohw8zAGjv83MpxHqfAochH68n/kiXN57+hYaNUCA7XkScoVNr6yzvly3hsY34TGfQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
astro-i18n-aut@0.7.0:
|
||||
resolution: {integrity: sha512-IPcjs5NkaNJIrTYDbLx+ay6J6LbAG0QMGE8LwjGmS6JuJHmFRbkmVnqW/OYYZhrHOxOhruhCwfY0bWo5K0TGrA==}
|
||||
peerDependencies:
|
||||
astro: ^4.0.3
|
||||
kleur: ^4.1.4
|
||||
|
||||
astro@5.0.5:
|
||||
resolution: {integrity: sha512-xfptdmurDsQcj/Anc7mU+eKlcyV7ppJIlmaSwhX3ZWwK5N/0rGKVmUqnuILgR6MB0XVJiIfublNzDGoyj4Q6BQ==}
|
||||
engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'}
|
||||
|
|
@ -1446,6 +1467,14 @@ packages:
|
|||
decode-named-character-reference@1.0.2:
|
||||
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
|
||||
|
||||
dedent@1.5.3:
|
||||
resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==}
|
||||
peerDependencies:
|
||||
babel-plugin-macros: ^3.1.0
|
||||
peerDependenciesMeta:
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
|
||||
deep-is@0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
|
||||
|
|
@ -1769,6 +1798,10 @@ packages:
|
|||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
fs-extra@11.2.0:
|
||||
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
|
||||
engines: {node: '>=14.14'}
|
||||
|
||||
fs-minipass@2.1.0:
|
||||
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
@ -2194,6 +2227,9 @@ packages:
|
|||
jsonc-parser@3.3.1:
|
||||
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
|
||||
|
||||
jsonfile@6.1.0:
|
||||
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
||||
|
||||
jsx-ast-utils@3.3.5:
|
||||
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
|
@ -3047,6 +3083,9 @@ packages:
|
|||
sass-formatter@0.7.9:
|
||||
resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==}
|
||||
|
||||
sax@1.4.1:
|
||||
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
|
||||
|
||||
scheduler@0.23.2:
|
||||
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
|
||||
|
||||
|
|
@ -3102,10 +3141,19 @@ packages:
|
|||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
sitemap@8.0.0:
|
||||
resolution: {integrity: sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==}
|
||||
engines: {node: '>=14.0.0', npm: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
slash@3.0.0:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
slash@5.1.0:
|
||||
resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
slice-ansi@5.0.0:
|
||||
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
@ -3128,6 +3176,9 @@ packages:
|
|||
sprintf-js@1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
|
||||
stream-replace-string@2.0.0:
|
||||
resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==}
|
||||
|
||||
string-argv@0.3.2:
|
||||
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
||||
engines: {node: '>=0.6.19'}
|
||||
|
|
@ -3345,6 +3396,10 @@ packages:
|
|||
unist-util-visit@5.0.0:
|
||||
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
|
||||
|
||||
universalify@2.0.1:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
update-browserslist-db@1.1.1:
|
||||
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
|
||||
hasBin: true
|
||||
|
|
@ -3844,6 +3899,12 @@ snapshots:
|
|||
fast-xml-parser: 4.5.0
|
||||
kleur: 4.1.5
|
||||
|
||||
'@astrojs/sitemap@3.2.1':
|
||||
dependencies:
|
||||
sitemap: 8.0.0
|
||||
stream-replace-string: 2.0.0
|
||||
zod: 3.23.8
|
||||
|
||||
'@astrojs/tailwind@5.1.3(astro@5.0.5(@types/node@22.10.2)(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1))(tailwindcss@3.4.16)':
|
||||
dependencies:
|
||||
astro: 5.0.5(@types/node@22.10.2)(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1)
|
||||
|
|
@ -4538,6 +4599,8 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/node@17.0.45': {}
|
||||
|
||||
'@types/node@22.10.2':
|
||||
dependencies:
|
||||
undici-types: 6.20.0
|
||||
|
|
@ -4557,6 +4620,10 @@ snapshots:
|
|||
dependencies:
|
||||
htmlparser2: 8.0.2
|
||||
|
||||
'@types/sax@1.2.7':
|
||||
dependencies:
|
||||
'@types/node': 22.10.2
|
||||
|
||||
'@types/unist@2.0.11': {}
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
|
@ -4847,6 +4914,17 @@ snapshots:
|
|||
- supports-color
|
||||
- typescript
|
||||
|
||||
astro-i18n-aut@0.7.0(astro@5.0.5(@types/node@22.10.2)(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1))(kleur@4.1.5):
|
||||
dependencies:
|
||||
astro: 5.0.5(@types/node@22.10.2)(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1)
|
||||
dedent: 1.5.3
|
||||
fast-glob: 3.3.2
|
||||
fs-extra: 11.2.0
|
||||
kleur: 4.1.5
|
||||
slash: 5.1.0
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
|
||||
astro@5.0.5(@types/node@22.10.2)(jiti@1.21.6)(rollup@4.27.4)(typescript@5.7.2)(yaml@2.6.1):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.10.3
|
||||
|
|
@ -5147,6 +5225,8 @@ snapshots:
|
|||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
|
||||
dedent@1.5.3: {}
|
||||
|
||||
deep-is@0.1.4: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
|
|
@ -5611,6 +5691,12 @@ snapshots:
|
|||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
fs-extra@11.2.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
jsonfile: 6.1.0
|
||||
universalify: 2.0.1
|
||||
|
||||
fs-minipass@2.1.0:
|
||||
dependencies:
|
||||
minipass: 3.3.6
|
||||
|
|
@ -6092,6 +6178,12 @@ snapshots:
|
|||
|
||||
jsonc-parser@3.3.1: {}
|
||||
|
||||
jsonfile@6.1.0:
|
||||
dependencies:
|
||||
universalify: 2.0.1
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
||||
jsx-ast-utils@3.3.5:
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
|
|
@ -7251,6 +7343,8 @@ snapshots:
|
|||
dependencies:
|
||||
suf-log: 2.5.3
|
||||
|
||||
sax@1.4.1: {}
|
||||
|
||||
scheduler@0.23.2:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
|
@ -7337,8 +7431,17 @@ snapshots:
|
|||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
sitemap@8.0.0:
|
||||
dependencies:
|
||||
'@types/node': 17.0.45
|
||||
'@types/sax': 1.2.7
|
||||
arg: 5.0.2
|
||||
sax: 1.4.1
|
||||
|
||||
slash@3.0.0: {}
|
||||
|
||||
slash@5.1.0: {}
|
||||
|
||||
slice-ansi@5.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
|
|
@ -7357,6 +7460,8 @@ snapshots:
|
|||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
||||
stream-replace-string@2.0.0: {}
|
||||
|
||||
string-argv@0.3.2: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
|
|
@ -7642,6 +7747,8 @@ snapshots:
|
|||
unist-util-is: 6.0.0
|
||||
unist-util-visit-parents: 6.0.1
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
||||
update-browserslist-db@1.1.1(browserslist@4.24.2):
|
||||
dependencies:
|
||||
browserslist: 4.24.2
|
||||
|
|
|
|||
|
|
@ -1,14 +1,30 @@
|
|||
---
|
||||
import { config } from "~/config"
|
||||
import { formatDate, getPosts } from "~/utils"
|
||||
import { getLocale } from "astro-i18n-aut"
|
||||
import { defaultLanguage, en, zh } from "~/config"
|
||||
import { formatDate, getPostsByLocale } from "~/utils"
|
||||
|
||||
const posts = (await getPosts()).slice(0, config.latestPosts ?? 8)
|
||||
const locale = getLocale(Astro.url)
|
||||
const config = locale === "zh" ? zh : en
|
||||
|
||||
const posts = (await getPostsByLocale(locale)).slice(0, config.latestPosts ?? 8)
|
||||
const recentPosts = locale === "zh" ? "近期文章" : "Recent Posts"
|
||||
---
|
||||
|
||||
<div class="mb-4 text-xl font-medium md:my-8">近期文章</div>
|
||||
<div class="my-8 text-xl font-medium md:my-8">{recentPosts}</div>
|
||||
{
|
||||
posts.map((post: any) => (
|
||||
<a href={`/posts/${post.slug}/`} class="visited:text-purple-500/90">
|
||||
<a
|
||||
href={
|
||||
defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? `/posts/${post.id}/`
|
||||
: `/en/posts/${post.id}/`
|
||||
: locale === "en"
|
||||
? `/posts/${post.id}/`
|
||||
: `/zn/posts/${post.id}/`
|
||||
}
|
||||
class="visited:text-purple-500/90"
|
||||
>
|
||||
<div class="my-2 flex text-lg">
|
||||
<p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
|
||||
<time>{formatDate(post.data.pubDate)}</time>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
---
|
||||
import { getLocale } from "astro-i18n-aut"
|
||||
import { Rss } from "lucide-react"
|
||||
import ThemeToggle from "~/components/astro/theme-toggle.astro"
|
||||
import { config } from "~/config"
|
||||
import { LanguageToggle } from "~/components/react/language-toggle"
|
||||
import { defaultLanguage, en, zh } from "~/config"
|
||||
|
||||
const locale = getLocale(Astro.url)
|
||||
const config = locale === "zh" ? zh : en
|
||||
---
|
||||
|
||||
<header class="flex h-24 w-full items-center justify-between">
|
||||
<a href="/">
|
||||
<a
|
||||
href={defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? "/"
|
||||
: "/en"
|
||||
: locale === "en"
|
||||
? "/"
|
||||
: "/zh"}
|
||||
>
|
||||
<div class="text-2xl font-semibold">{config.siteName}</div>
|
||||
</a>
|
||||
<div class="flex items-center gap-6">
|
||||
{
|
||||
config.social.map((social) => (
|
||||
<a href={social.link} target="_blank">
|
||||
<social.icon />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
{
|
||||
config.rss && (
|
||||
<a href="/atom.xml" target="_blank">
|
||||
|
|
@ -23,6 +29,15 @@ import { config } from "~/config"
|
|||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
config.social.map((social) => (
|
||||
<a href={social.link} target="_blank" class="hidden md:block">
|
||||
<social.icon />
|
||||
</a>
|
||||
))
|
||||
}
|
||||
|
||||
<LanguageToggle currentLocale={locale} client:load />
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
13
src/components/astro/intro.astro
Normal file
13
src/components/astro/intro.astro
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
import { getLocale } from "astro-i18n-aut"
|
||||
import IntroContentEn from "~/content/en/intro.mdx"
|
||||
import IntroContentZh from "~/content/zh/intro.mdx"
|
||||
|
||||
const locale = getLocale(Astro.url)
|
||||
|
||||
const IntroContent = locale === "zh" ? IntroContentZh : IntroContentEn
|
||||
---
|
||||
|
||||
<div class="my-10">
|
||||
<IntroContent />
|
||||
</div>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
---
|
||||
import { config } from "~/config"
|
||||
|
||||
const { home, archive, tags, custom, about } = config.navigation
|
||||
---
|
||||
|
||||
<div class="mb-8 flex gap-4 text-lg">
|
||||
{
|
||||
home && (
|
||||
<a href="/" class="hover:underline hover:underline-offset-4">
|
||||
<p>首页</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
archive && (
|
||||
<a href="/archive" class="hover:underline hover:underline-offset-4">
|
||||
<p>归档</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
tags && (
|
||||
<a href="/tags" class="hover:underline hover:underline-offset-4">
|
||||
<p>标签</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
custom?.map((tab) => (
|
||||
<a
|
||||
href={tab.link}
|
||||
class="hover:underline hover:underline-offset-4"
|
||||
target="_blank"
|
||||
>
|
||||
<p>{tab.label}</p>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
{
|
||||
about && (
|
||||
<a href="/about" class="hover:underline hover:underline-offset-4">
|
||||
<p>关于</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
120
src/components/astro/navigation.astro
Normal file
120
src/components/astro/navigation.astro
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
import { getLocale } from "astro-i18n-aut"
|
||||
import { defaultLanguage, en, zh } from "~/config"
|
||||
|
||||
const locale = getLocale(Astro.url)
|
||||
|
||||
const { home, archive, tags, custom, about } =
|
||||
locale === "zh" ? zh.navigation : en.navigation
|
||||
|
||||
let navList: {
|
||||
home: string
|
||||
archive: string
|
||||
tags: string
|
||||
about: string
|
||||
}
|
||||
|
||||
switch (locale) {
|
||||
case "zh":
|
||||
navList = {
|
||||
home: "首页",
|
||||
archive: "归档",
|
||||
tags: "标签",
|
||||
about: "关于",
|
||||
}
|
||||
break
|
||||
default:
|
||||
navList = {
|
||||
home: "Home",
|
||||
archive: "Archive",
|
||||
tags: "Tags",
|
||||
about: "About",
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
<div class="mb-10 mt-4 flex gap-4 overflow-x-auto whitespace-nowrap text-lg">
|
||||
{
|
||||
home && (
|
||||
<a
|
||||
href={
|
||||
defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? "/"
|
||||
: "/en"
|
||||
: locale === "en"
|
||||
? "/"
|
||||
: "/zh"
|
||||
}
|
||||
class="hover:underline hover:underline-offset-4"
|
||||
>
|
||||
<p>{navList.home}</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
archive && (
|
||||
<a
|
||||
href={
|
||||
defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? "/archive"
|
||||
: "/en/archive"
|
||||
: locale === "en"
|
||||
? `/archive`
|
||||
: `/zh/archive`
|
||||
}
|
||||
class="hover:underline hover:underline-offset-4"
|
||||
>
|
||||
<p>{navList.archive}</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
tags && (
|
||||
<a
|
||||
href={
|
||||
defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? "/tags"
|
||||
: "/en/tags"
|
||||
: locale === "en"
|
||||
? `/tags`
|
||||
: `/zh/tags`
|
||||
}
|
||||
class="hover:underline hover:underline-offset-4"
|
||||
>
|
||||
<p>{navList.tags}</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
custom?.map((tab) => (
|
||||
<a
|
||||
href={tab.link}
|
||||
class="hover:underline hover:underline-offset-4"
|
||||
target="_blank"
|
||||
>
|
||||
<p>{tab.label}</p>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
{
|
||||
about && (
|
||||
<a
|
||||
href={
|
||||
defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? "/about"
|
||||
: "/en/about"
|
||||
: locale === "en"
|
||||
? `/about`
|
||||
: `/zh/about`
|
||||
}
|
||||
class="hover:underline hover:underline-offset-4"
|
||||
>
|
||||
<p>{navList.about}</p>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
67
src/components/astro/tag.astro
Normal file
67
src/components/astro/tag.astro
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
import { defaultLanguage } from "~/config"
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
import { formatDate } from "~/utils"
|
||||
|
||||
interface Props {
|
||||
posts: any[]
|
||||
tag: string
|
||||
locale?: string
|
||||
}
|
||||
|
||||
const { posts, tag, locale = "en" } = Astro.props
|
||||
|
||||
const filteredPosts = posts.filter((post: any) => post.data.tags?.includes(tag))
|
||||
|
||||
const getTagTitle = (tag: string, locale: string) => {
|
||||
const titles = {
|
||||
en: `Tag: ${tag}`,
|
||||
zh: `标签:${tag}`,
|
||||
}
|
||||
|
||||
return titles[locale as keyof typeof titles] || titles["en"]
|
||||
}
|
||||
---
|
||||
|
||||
<HomeLayout>
|
||||
<div class="container mx-auto px-4">
|
||||
<h1 class="mb-6 text-2xl font-bold">
|
||||
{getTagTitle(tag, locale)}
|
||||
</h1>
|
||||
{
|
||||
filteredPosts.length > 0 ? (
|
||||
<ul class="space-y-4">
|
||||
{filteredPosts.map((post: any) => (
|
||||
<li>
|
||||
<a
|
||||
href={
|
||||
defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? `/posts/${post.id}/`
|
||||
: `/en/posts/${post.id}/`
|
||||
: locale === "en"
|
||||
? `/posts/${post.id}/`
|
||||
: `/zh/posts/${post.id}/`
|
||||
}
|
||||
class="block rounded-lg p-2 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
<div class="flex items-center text-lg">
|
||||
<p class="mr-4 w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
|
||||
<time>{formatDate(post.data.pubDate)}</time>
|
||||
</p>
|
||||
<p class="line-clamp-2 flex-grow">{post.data.title}</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p class="text-gray-500">
|
||||
{locale === "en"
|
||||
? `No posts found for tag "${tag}"`
|
||||
: `没有找到标签为 "${tag}" 的文章`}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</HomeLayout>
|
||||
34
src/components/react/language-toggle.tsx
Normal file
34
src/components/react/language-toggle.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { Languages } from "lucide-react"
|
||||
import { defaultLanguage } from "~/config"
|
||||
|
||||
interface LanguageToggleProps {
|
||||
currentLocale: string
|
||||
}
|
||||
|
||||
export function LanguageToggle({ currentLocale }: LanguageToggleProps) {
|
||||
const getNewPath = (path: string): string => {
|
||||
if (path === "/" || path === "") {
|
||||
return defaultLanguage === "zh" ? "/en" : "/zh"
|
||||
}
|
||||
|
||||
if (path === "/en" || path === "/zh") {
|
||||
return "/"
|
||||
}
|
||||
|
||||
return defaultLanguage === "zh"
|
||||
? currentLocale === "zh"
|
||||
? `/en${path}`
|
||||
: path.replace("/en", "")
|
||||
: currentLocale === "en"
|
||||
? `/zh${path}`
|
||||
: path.replace("/zh", "")
|
||||
}
|
||||
|
||||
const handleLanguageToggle = () => {
|
||||
const currentPath = window.location.pathname
|
||||
const newPath = getNewPath(currentPath)
|
||||
window.location.href = newPath
|
||||
}
|
||||
|
||||
return <Languages className="cursor-pointer" onClick={handleLanguageToggle} />
|
||||
}
|
||||
|
|
@ -1,19 +1,5 @@
|
|||
export function NoiseBg() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
zIndex: -1,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundSize: "128px",
|
||||
backgroundRepeat: "repeat",
|
||||
backgroundImage:
|
||||
"url('https://framerusercontent.com/images/rR6HYXBrMmX4cRpXfXUOvpvpB0.png')",
|
||||
opacity: 0.06,
|
||||
borderRadius: 0,
|
||||
}}
|
||||
></div>
|
||||
<div className="fixed inset-0 -z-10 h-full w-full bg-[url('https://framerusercontent.com/images/rR6HYXBrMmX4cRpXfXUOvpvpB0.png')] bg-[length:128px_128px] bg-repeat opacity-[0.06]"></div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { Github, Twitter } from "lucide-react"
|
||||
|
||||
export const config = {
|
||||
export const defaultLanguage: string = "zh"
|
||||
|
||||
const common = {
|
||||
meta: {
|
||||
favicon: "",
|
||||
title: "小孙同学",
|
||||
favicon: "/favicon.svg",
|
||||
url: "https://blog.sunguoqi.com",
|
||||
slogan: "一个浪漫的理性主义者",
|
||||
description: "读书、摄影、编程、旅行",
|
||||
},
|
||||
siteName: "小孙同学",
|
||||
social: [
|
||||
{
|
||||
icon: Twitter,
|
||||
|
|
@ -29,10 +27,50 @@ export const config = {
|
|||
about: true,
|
||||
custom: [
|
||||
{
|
||||
label: "影集",
|
||||
label: "CamLife",
|
||||
link: "https://camlife.cn",
|
||||
},
|
||||
],
|
||||
},
|
||||
latestPosts: 8,
|
||||
}
|
||||
|
||||
export const zh = {
|
||||
...common,
|
||||
siteName: "小孙同学",
|
||||
meta: {
|
||||
...common.meta,
|
||||
title: "小孙同学",
|
||||
slogan: "一个浪漫的理性主义者",
|
||||
description: "读书、摄影、编程、旅行",
|
||||
},
|
||||
navigation: {
|
||||
...common.navigation,
|
||||
custom: [
|
||||
{
|
||||
label: "影集",
|
||||
link: "https://camlife.cn",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const en = {
|
||||
...common,
|
||||
siteName: "Guoqi Sun",
|
||||
meta: {
|
||||
...common.meta,
|
||||
title: "Guoqi Sun",
|
||||
slogan: "A Romantic Rationalist",
|
||||
description: "Reading, Photography, Programming, Traveling",
|
||||
},
|
||||
navigation: {
|
||||
...common.navigation,
|
||||
custom: [
|
||||
{
|
||||
label: "CamLife",
|
||||
link: "https://camlife.cn",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,23 @@
|
|||
import { glob } from "astro/loaders"
|
||||
import { defineCollection, z } from "astro:content"
|
||||
|
||||
const posts = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
pubDate: z.coerce.date(),
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
heroImage: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
}),
|
||||
const schema = z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
pubDate: z.coerce.date(),
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
heroImage: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
export const collections = { posts }
|
||||
const posts_zh = defineCollection({
|
||||
loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/zh/posts" }),
|
||||
schema,
|
||||
})
|
||||
|
||||
const posts_en = defineCollection({
|
||||
loader: glob({ pattern: "**/*.{md,mdx}", base: "src/content/en/posts" }),
|
||||
schema,
|
||||
})
|
||||
|
||||
export const collections = { zh: posts_zh, en: posts_en }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
layout: ../layouts/post.astro
|
||||
layout: src/layouts/post.astro
|
||||
title: "关于我"
|
||||
description: "前端开发者 | 摄影爱好者 | 骑行爱好者"
|
||||
---
|
||||
1
src/content/en/intro.mdx
Normal file
1
src/content/en/intro.mdx
Normal file
|
|
@ -0,0 +1 @@
|
|||
🖥️ Front-end developer (React)|📸 Photography enthusiast (Nikon)|🛸 Travel explorer (experiencer)|🚴 Cycling freestyler (Java SILURO6-TOP 6)|🍎 Technology product enthusiast (Apple & Xiaomi)
|
||||
28
src/content/en/posts/post-1.md
Normal file
28
src/content/en/posts/post-1.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
title: "My First Blog Post"
|
||||
pubDate: 2020-07-01
|
||||
description: "This is the first post of my new Astro blog."
|
||||
author: "Astro Learner"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rose.webp"
|
||||
alt: "The Astro logo on a dark background with a pink glow."
|
||||
tags: ["astro", "blogging", "learning-in-public"]
|
||||
---
|
||||
|
||||
# My First Blog Post
|
||||
|
||||
Published on: 2020-07-01
|
||||
|
||||
Welcome to my _new blog_ about learning Astro! Here, I will share my learning journey as I build a new website.
|
||||
|
||||
## What I've accomplished
|
||||
|
||||
1. **Installing Astro**: First, I created a new Astro project and set up my online accounts.
|
||||
|
||||
2. **Making Pages**: I then learned how to make pages by creating new `.astro` files and placing them in the `src/pages/` folder.
|
||||
|
||||
3. **Making Blog Posts**: This is my first blog post! I now have Astro pages and Markdown posts!
|
||||
|
||||
## What's next
|
||||
|
||||
I will finish the Astro tutorial, and then keep adding more posts. Watch this space for more to come.
|
||||
12
src/content/en/posts/post-2.md
Normal file
12
src/content/en/posts/post-2.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: My Second Blog Post
|
||||
author: Astro Learner
|
||||
description: "After learning some Astro, I couldn't stop!"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "The Astro logo on a dark background with a purple gradient arc."
|
||||
pubDate: 2020-07-08
|
||||
tags: ["astro", "blogging", "learning-in-public", "successes"]
|
||||
---
|
||||
|
||||
After a successful first week learning Astro, I decided to try some more. I wrote and imported a small component from memory!
|
||||
12
src/content/en/posts/post-3.md
Normal file
12
src/content/en/posts/post-3.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: My Third Blog Post
|
||||
author: Astro Learner
|
||||
description: "I had some challenges, but asking in the community really helped!"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rays.webp"
|
||||
alt: "The Astro logo on a dark background with rainbow rays."
|
||||
pubDate: 2021-07-15
|
||||
tags: ["astro", "learning-in-public", "setbacks", "community"]
|
||||
---
|
||||
|
||||
It wasn't always smooth sailing, but I'm enjoying building with Astro. And, the [Discord community](https://astro.build/chat) is really friendly and helpful!
|
||||
22
src/content/en/posts/post-4.md
Normal file
22
src/content/en/posts/post-4.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: "Building a Personal Blog with Astro"
|
||||
author: Astro Learner
|
||||
description: "Sharing my experience and insights on building a personal blog with Astro"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/full-logo-light.png"
|
||||
alt: "Astro logo with full text"
|
||||
pubDate: 2022-07-22
|
||||
tags: ["astro", "blogging", "web-development", "tech-sharing"]
|
||||
---
|
||||
|
||||
Over the past few weeks, I've been building my personal blog using Astro. Astro is a modern static site generator that offers excellent performance and a great development experience.
|
||||
|
||||
## Why Astro?
|
||||
|
||||
1. **Lightning-fast page loads**: Astro uses innovative partial hydration techniques
|
||||
2. **Great developer experience**: Supports components from React, Vue, Svelte, and more
|
||||
3. **Powerful Markdown support**: Makes writing content a breeze
|
||||
|
||||
## Learnings from the Development Process
|
||||
|
||||
Through this project, I've not only learned how to use Astro but also gained a deeper understanding of many modern frontend development concepts. I plan to continue improving this blog and adding more features.
|
||||
26
src/content/en/posts/post-5.md
Normal file
26
src/content/en/posts/post-5.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: "Customizing Your Astro Blog Theme"
|
||||
author: Astro Learner
|
||||
description: "Learn how to customize your Astro blog theme with Tailwind CSS"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rays.webp"
|
||||
alt: "Astro rays on a dark background"
|
||||
pubDate: 2022-07-29
|
||||
tags: ["astro", "css", "tailwind", "design"]
|
||||
---
|
||||
|
||||
Today I'll share how I customized my Astro blog theme using Tailwind CSS. The combination of Astro and Tailwind makes styling incredibly efficient.
|
||||
|
||||
## Key Customizations
|
||||
|
||||
1. **Color Scheme**: Created a custom dark/light mode theme
|
||||
2. **Typography**: Set up a consistent type scale
|
||||
3. **Layout**: Implemented responsive design patterns
|
||||
4. **Components**: Styled reusable UI components
|
||||
|
||||
## Tips for Theme Development
|
||||
|
||||
- Start with a clear design system
|
||||
- Use Tailwind's configuration file for custom values
|
||||
- Leverage CSS variables for dynamic theming
|
||||
- Test across different devices and screen sizes
|
||||
23
src/content/en/posts/post-6.md
Normal file
23
src/content/en/posts/post-6.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
title: "Adding Dynamic Features to Static Astro Sites"
|
||||
author: Astro Learner
|
||||
description: "Exploring ways to add dynamic features to Astro's static pages"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "Astro arc on dark background"
|
||||
pubDate: 2023-08-05
|
||||
tags: ["astro", "javascript", "dynamic-content", "web-development"]
|
||||
---
|
||||
|
||||
While Astro excels at static site generation, sometimes we need dynamic features. Here's how to add interactivity without sacrificing performance.
|
||||
|
||||
## Dynamic Features Added
|
||||
|
||||
1. **Search Functionality**: Implemented client-side search
|
||||
2. **Comment System**: Integrated with a third-party service
|
||||
3. **Like Button**: Added interactive reactions
|
||||
4. **View Counter**: Track page views
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The key is using Astro's partial hydration to only load JavaScript where needed. This keeps the site fast while adding necessary dynamic features.
|
||||
26
src/content/en/posts/post-7.md
Normal file
26
src/content/en/posts/post-7.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: "Adding Internationalization to Astro"
|
||||
author: Astro Learner
|
||||
description: "A guide to implementing i18n in your Astro website"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/full-logo-light.png"
|
||||
alt: "Astro logo"
|
||||
pubDate: 2024-08-19
|
||||
tags: ["astro", "i18n", "localization", "web-development"]
|
||||
---
|
||||
|
||||
Making your website accessible to a global audience is important. Here's how to implement internationalization in Astro.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Setup**: Installing necessary i18n packages
|
||||
2. **Content Structure**: Organizing content for multiple languages
|
||||
3. **Language Switching**: Adding a language toggle
|
||||
4. **URL Management**: Handling language-specific routes
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Keep translations in separate files
|
||||
- Use ISO language codes
|
||||
- Implement fallback languages
|
||||
- Consider right-to-left languages
|
||||
23
src/content/en/posts/post-8.md
Normal file
23
src/content/en/posts/post-8.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
title: "Deploying Your Astro Site"
|
||||
author: Astro Learner
|
||||
description: "A comprehensive guide to deploying your Astro site to various platforms"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "Astro deployment illustration"
|
||||
pubDate: 2024-08-26
|
||||
tags: ["astro", "deployment", "hosting", "CI CD"]
|
||||
---
|
||||
|
||||
Ready to share your Astro site with the world? Let's explore different deployment options and best practices.
|
||||
|
||||
## Deployment Platforms
|
||||
|
||||
1. **Netlify**: Perfect for static sites
|
||||
2. **Vercel**: Great for serverless functions
|
||||
3. **GitHub Pages**: Ideal for project sites
|
||||
4. **Self-hosted**: Maximum control
|
||||
|
||||
## Deployment Process
|
||||
|
||||
We'll walk through the setup process for each platform, including configuration files, environment variables, and continuous deployment workflows.
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
---
|
||||
title: "我的第一篇博客文章"
|
||||
pubDate: 2022-07-01
|
||||
description: "这是我 Astro 博客的第一篇文章。"
|
||||
author: "Astro 学习者"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rose.webp"
|
||||
alt: "The Astro logo on a dark background with a pink glow."
|
||||
tags: ["astro", "blogging", "learning in public"]
|
||||
---
|
||||
|
||||
# 我的第一篇博客文章
|
||||
|
||||
发表于:2022-07-01
|
||||
|
||||
欢迎来到我学习关于 Astro 的新博客!在这里,我将分享我建立新网站的学习历程。
|
||||
|
||||
## 我做了什么
|
||||
|
||||
1. **安装 Astro**:首先,我创建了一个新的 Astro 项目并设置好了我的在线账号。
|
||||
|
||||
2. **制作页面**:然后我学习了如何通过创建新的 `.astro` 文件并将它们保存在 `src/pages/` 文件夹里来制作页面。
|
||||
|
||||
3. **发表博客文章**:这是我的第一篇博客文章!我现在有用 Astro 编写的页面和用 Markdown 写的文章了!
|
||||
|
||||
## 下一步计划
|
||||
|
||||
我将完成 Astro 教程,然后继续编写更多内容。关注我以获取更多信息。
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
title: 我的第二篇博客文章
|
||||
author: Astro 学习者
|
||||
description: "学习了一些 Astro 后,我根本停不下来!"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "The Astro logo on a dark background with a purple gradient arc."
|
||||
pubDate: 2023-07-08
|
||||
tags: ["astro", "blogging", "learning in public", "successes"]
|
||||
---
|
||||
|
||||
在学习 Astro 大约一周后,我决定尝试些新的东西。我编写并导入了一个小组件!
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
title: 我的第三篇博客文章
|
||||
author: Astro 学习者
|
||||
description: "我遇到了一些问题,但是在社区里面提问真的很有帮助!"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rays.webp"
|
||||
alt: "The Astro logo on a dark background with rainbow rays."
|
||||
pubDate: 2021-07-15
|
||||
tags: ["astro", "learning in public", "setbacks", "community"]
|
||||
---
|
||||
|
||||
尽管这并不总是一帆风顺,但我很享受使用 Astro 进行搭建。并且,[Discord 社区](https://astro.build/chat)真的很友好而且乐于助人!
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
title: "我的第四篇博客文章"
|
||||
author: "Astro 学习者"
|
||||
description: "这篇文章会自己出现在列表中!"
|
||||
image:
|
||||
url: "https://docs.astro.build/default-og-image.png"
|
||||
alt: "The word astro against an illustration of planets and stars."
|
||||
pubDate: 2024-08-08
|
||||
tags: ["astro", "successes"]
|
||||
---
|
||||
|
||||
这篇文章应该会与其他的博客文章一起显示,因为 `Astro.glob()` 会返回一个包含所有文章的列表,以创建这个文章列表。
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
---
|
||||
title: "探索前端新技术"
|
||||
author: "Guoqi Sun"
|
||||
description: "记录一下最近学习的一些前端新技术和心得体会"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rays.webp"
|
||||
alt: "Colorful abstract background"
|
||||
pubDate: 2024-08-15
|
||||
tags: ["前端", "技术", "学习笔记"]
|
||||
---
|
||||
|
||||
最近在学习和探索一些前端新技术,感觉收获颇丰。前端技术栈的发展真是日新月异,每天都有新的工具和框架出现。
|
||||
|
||||
## 新技术探索
|
||||
|
||||
1. Astro 框架
|
||||
|
||||
- 零 JS 优先
|
||||
- 出色的性能表现
|
||||
- 灵活的组件集成
|
||||
|
||||
2. Tailwind CSS
|
||||
- 原子化 CSS
|
||||
- 快速开发
|
||||
- 暗黑模式支持
|
||||
|
||||
## 学习心得
|
||||
|
||||
持续学习是前端工程师的必修课。在技术快速迭代的今天,保持对新技术的热情和学习的动力很重要。同时也要注意技术选型要符合实际需求,不要盲目追求新技术。
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
---
|
||||
title: "我的摄影之旅"
|
||||
author: "Guoqi Sun"
|
||||
description: "分享一些摄影经验和心得体会"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "A beautiful landscape photo"
|
||||
pubDate: 2024-09-01
|
||||
tags: ["摄影", "旅行", "生活"]
|
||||
---
|
||||
|
||||
作为一个摄影爱好者,总是希望能够用镜头记录下生活中的美好瞬间。今天想和大家分享一些摄影心得。
|
||||
|
||||
## 器材选择
|
||||
|
||||
虽然说器材不是最重要的,但合适的器材确实能帮助我们更好地表达:
|
||||
|
||||
1. 相机选择
|
||||
|
||||
- 轻便为主
|
||||
- 操控要顺手
|
||||
- 画质适中即可
|
||||
|
||||
2. 镜头搭配
|
||||
- 35mm 街拍定焦
|
||||
- 85mm 人像利器
|
||||
- 16-35mm 风光广角
|
||||
|
||||
## 拍摄技巧
|
||||
|
||||
1. 构图要点
|
||||
|
||||
- 三分法则
|
||||
- 引导线条
|
||||
- 框架构图
|
||||
|
||||
2. 光线运用
|
||||
- 黄金时间
|
||||
- 顺光逆光
|
||||
- 自然光影
|
||||
|
||||
摄影最重要的不是技术,而是发现美的眼睛。希望通过不断练习,能够拍出更多打动人心的照片。
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
---
|
||||
title: "骑行的乐趣"
|
||||
author: "Guoqi Sun"
|
||||
description: "分享骑行运动的快乐和一些个人经验"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rose.webp"
|
||||
alt: "A scenic cycling route"
|
||||
pubDate: 2024-09-15
|
||||
tags: ["骑行", "运动", "生活"]
|
||||
---
|
||||
|
||||
骑行是一项既能锻炼身体又充满乐趣的运动。今天想和大家分享一下我的骑行经历和心得。
|
||||
|
||||
## 装备选择
|
||||
|
||||
合适的装备能让骑行更加安全和舒适:
|
||||
|
||||
1. 自行车
|
||||
|
||||
- 车架材质:铝合金/碳纤维
|
||||
- 变速系统:禧玛诺套件
|
||||
- 轮胎选择:防扎耐磨
|
||||
|
||||
2. 骑行装备
|
||||
- 头盔(安全第一)
|
||||
- 骑行服(速干透气)
|
||||
- 手套和眼镜
|
||||
|
||||
## 骑行技巧
|
||||
|
||||
1. 基础要点
|
||||
|
||||
- 正确的骑行姿势
|
||||
- 变速时机的把握
|
||||
- 体力分配
|
||||
|
||||
2. 安全建议
|
||||
- 路况观察
|
||||
- 夜骑装备
|
||||
- 天气考虑
|
||||
|
||||
骑行不仅是一项运动,更是一种生活方式。在路上,你可以感受风的轻抚,欣赏沿途的风景,让身心都得到放松。希望更多的人能够加入骑行的行列,体验这项运动的魅力。
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
---
|
||||
title: "科技产品使用体验"
|
||||
author: "Guoqi Sun"
|
||||
description: "分享一些常用科技产品的使用体验和选购建议"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "Modern tech devices"
|
||||
pubDate: 2024-10-01
|
||||
tags: ["科技", "数码", "生活"]
|
||||
---
|
||||
|
||||
作为一个科技产品爱好者,经常会尝试各种新的数码产品。今天想和大家分享一下我的使用体验。
|
||||
|
||||
## 苹果生态
|
||||
|
||||
1. iPhone 体验
|
||||
|
||||
- Face ID的便捷
|
||||
- 相机系统的提升
|
||||
- iOS生态的流畅
|
||||
|
||||
2. MacBook 使用感受
|
||||
- M系列芯片的性能
|
||||
- 续航能力
|
||||
- 系统集成度
|
||||
|
||||
## 小米产品
|
||||
|
||||
1. 智能家居
|
||||
|
||||
- 米家自动化
|
||||
- 设备互联
|
||||
- 语音控制
|
||||
|
||||
2. 周边配件
|
||||
- 充电设备
|
||||
- 智能手环
|
||||
- 蓝牙耳机
|
||||
|
||||
## 选购建议
|
||||
|
||||
1. 需求为先
|
||||
|
||||
- 实际使用场景
|
||||
- 预算考虑
|
||||
- 长期价值
|
||||
|
||||
2. 生态集成
|
||||
- 设备互通
|
||||
- 数据同步
|
||||
- 使用便利性
|
||||
|
||||
科技产品应该服务于生活,而不是成为负担。在选择产品时,要考虑实际需求,不要盲目追求新品。好的产品应该能够提升生活品质,带来便利和愉悦的体验。
|
||||
18
src/content/zh/about.mdx
Normal file
18
src/content/zh/about.mdx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
layout: src/layouts/post.astro
|
||||
title: "关于我"
|
||||
description: "前端开发者 | 摄影爱好者 | 骑行爱好者"
|
||||
---
|
||||
|
||||
export const title = "关于我"
|
||||
|
||||
## {title}
|
||||
|
||||
<img
|
||||
className="block dark:hidden"
|
||||
src="https://cdn.jsdelivr.net/gh/sun0225SUN/sun0225SUN/profile-snake-contrib/github-contribution-grid-snake.svg"
|
||||
/>
|
||||
<img
|
||||
className="hidden dark:block"
|
||||
src="https://cdn.jsdelivr.net/gh/sun0225SUN/sun0225SUN/profile-snake-contrib/github-contribution-grid-snake-dark.svg"
|
||||
/>
|
||||
24
src/content/zh/posts/post-1.md
Normal file
24
src/content/zh/posts/post-1.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
title: "我的第一篇博客文章"
|
||||
pubDate: 2020-07-01
|
||||
description: "这是我 Astro 博客的第一篇文章。"
|
||||
author: "Astro 学习者"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rose.webp"
|
||||
alt: "在黑色背景上带有粉色光晕的 Astro 标志。"
|
||||
tags: ["astro", "博客", "公开学习"]
|
||||
---
|
||||
|
||||
欢迎来到我学习关于 Astro 的新博客!在这里,我将分享我建立新网站的学习历程。
|
||||
|
||||
## 我做了什么
|
||||
|
||||
1. **安装 Astro**:首先,我创建了一个新的 Astro 项目并设置好了我的在线账号。
|
||||
|
||||
2. **制作页面**:然后我学习了如何通过创建新的 `.astro` 文件并将它们保存在 `src/pages/` 文件夹里来制作页面。
|
||||
|
||||
3. **发表博客文章**:这是我的第一篇博客文章!我现在有用 Astro 编写的页面和用 Markdown 写的文章了!
|
||||
|
||||
## 下一步计划
|
||||
|
||||
我将完成 Astro 教程,然后继续编写更多内容。关注我以获取更多信息。
|
||||
12
src/content/zh/posts/post-2.md
Normal file
12
src/content/zh/posts/post-2.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: 我的第二篇博客文章
|
||||
author: Astro 学习者
|
||||
description: "在学习了一些 Astro 之后,我无法停止!"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "在深色背景上带有紫色渐变弧形的 Astro 标志。"
|
||||
pubDate: 2020-07-08
|
||||
tags: ["astro", "博客", "公开学习", "成功"]
|
||||
---
|
||||
|
||||
在成功的第一周学习 Astro 之后,我决定再试试。我从记忆中编写并导入了一个小组件!
|
||||
12
src/content/zh/posts/post-3.md
Normal file
12
src/content/zh/posts/post-3.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: 我的第三篇博客文章
|
||||
author: Astro 学习者
|
||||
description: "我遇到了一些挑战,但在社区中提问真的帮助了我!"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rays.webp"
|
||||
alt: "在黑色背景上带有彩虹光线的 Astro 标志。"
|
||||
pubDate: 2021-07-15
|
||||
tags: ["astro", "公开学习", "挫折", "社区"]
|
||||
---
|
||||
|
||||
事情并不总是一帆风顺,但我很享受使用 Astro 构建的过程。而且,[Discord 社区](https://astro.build/chat)真的很友好和乐于助人!
|
||||
22
src/content/zh/posts/post-4.md
Normal file
22
src/content/zh/posts/post-4.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: "使用 Astro 构建个人博客"
|
||||
author: Astro 学习者
|
||||
description: "分享我使用 Astro 构建个人博客的经验和见解"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/full-logo-light.png"
|
||||
alt: "带有完整文本的 Astro 徽标"
|
||||
pubDate: 2022-07-22
|
||||
tags: ["astro", "博客", "网页开发", "技术分享"]
|
||||
---
|
||||
|
||||
在过去的几周里,我一直在使用 Astro 构建我的个人博客。Astro 是一个现代静态网站生成器,提供出色的性能和良好的开发体验。
|
||||
|
||||
## 为什么选择 Astro?
|
||||
|
||||
1. **闪电般快速的页面加载**:Astro 使用创新的部分水合技术
|
||||
2. **出色的开发者体验**:支持来自 React、Vue、Svelte 等的组件
|
||||
3. **强大的 Markdown 支持**:让写作内容变得轻而易举
|
||||
|
||||
## 开发过程中的学习
|
||||
|
||||
通过这个项目,我不仅学会了如何使用 Astro,还对许多现代前端开发概念有了更深入的理解。我计划继续改进这个博客并添加更多功能。
|
||||
26
src/content/zh/posts/post-5.md
Normal file
26
src/content/zh/posts/post-5.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: "自定义你的 Astro 博客主题"
|
||||
author: Astro 学习者
|
||||
description: "学习如何使用 Tailwind CSS 自定义你的 Astro 博客主题"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/rays.webp"
|
||||
alt: "黑色背景上的 Astro 光线"
|
||||
pubDate: 2022-07-29
|
||||
tags: ["astro", "css", "tailwind", "设计"]
|
||||
---
|
||||
|
||||
今天我将分享如何使用 Tailwind CSS 自定义我的 Astro 博客主题。Astro 和 Tailwind 的结合使得样式设计变得非常高效。
|
||||
|
||||
## 关键自定义
|
||||
|
||||
1. **颜色方案**: 创建了一个自定义的暗/亮模式主题
|
||||
2. **排版**: 设置了一致的字体比例
|
||||
3. **布局**: 实现了响应式设计模式
|
||||
4. **组件**: 样式化可重用的 UI 组件
|
||||
|
||||
## 主题开发提示
|
||||
|
||||
- 从清晰的设计系统开始
|
||||
- 使用 Tailwind 的配置文件进行自定义值
|
||||
- 利用 CSS 变量进行动态主题
|
||||
- 在不同设备和屏幕尺寸上进行测试
|
||||
23
src/content/zh/posts/post-6.md
Normal file
23
src/content/zh/posts/post-6.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
title: "为静态 Astro 网站添加动态功能"
|
||||
author: Astro Learner
|
||||
description: "探索为 Astro 的静态页面添加动态功能的方法"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "黑色背景上的 Astro 弧形图"
|
||||
pubDate: 2023-08-05
|
||||
tags: ["astro", "javascript", "动态内容", "网页开发"]
|
||||
---
|
||||
|
||||
虽然 Astro 在静态网站生成方面表现出色,但有时我们需要动态功能。以下是如何在不牺牲性能的情况下添加交互性。
|
||||
|
||||
## 添加的动态功能
|
||||
|
||||
1. **搜索功能**:实现了客户端搜索
|
||||
2. **评论系统**:与第三方服务集成
|
||||
3. **点赞按钮**:添加了互动反应
|
||||
4. **浏览计数器**:跟踪页面浏览量
|
||||
|
||||
## 实施细节
|
||||
|
||||
关键是使用 Astro 的部分水合,只在需要的地方加载 JavaScript。这使得网站保持快速,同时添加必要的动态功能。
|
||||
26
src/content/zh/posts/post-7.md
Normal file
26
src/content/zh/posts/post-7.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: "为Astro添加国际化"
|
||||
author: Astro Learner
|
||||
description: "在您的Astro网站中实现i18n的指南"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/full-logo-light.png"
|
||||
alt: "Astro标志"
|
||||
pubDate: 2024-08-19
|
||||
tags: ["astro", "i18n", "本地化", "网页开发"]
|
||||
---
|
||||
|
||||
让您的网站能够接触到全球受众是很重要的。以下是如何在Astro中实现国际化的方法。
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. **设置**:安装必要的i18n包
|
||||
2. **内容结构**:为多种语言组织内容
|
||||
3. **语言切换**:添加语言切换功能
|
||||
4. **URL管理**:处理特定语言的路由
|
||||
|
||||
## 最佳实践
|
||||
|
||||
- 将翻译保存在单独的文件中
|
||||
- 使用ISO语言代码
|
||||
- 实现后备语言
|
||||
- 考虑从右到左的语言
|
||||
23
src/content/zh/posts/post-8.md
Normal file
23
src/content/zh/posts/post-8.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
title: "部署您的 Astro 网站"
|
||||
author: Astro 学习者
|
||||
description: "全面指南,帮助您将 Astro 网站部署到各种平台"
|
||||
image:
|
||||
url: "https://docs.astro.build/assets/arc.webp"
|
||||
alt: "Astro 部署插图"
|
||||
pubDate: 2024-08-19
|
||||
tags: ["astro", "i18n", "本地化", "网页开发"]
|
||||
---
|
||||
|
||||
准备好与世界分享您的 Astro 网站了吗?让我们探索不同的部署选项和最佳实践。
|
||||
|
||||
## 部署平台
|
||||
|
||||
1. **Netlify**: 适合静态网站
|
||||
2. **Vercel**: 适合无服务器函数
|
||||
3. **GitHub Pages**: 理想的项目网站
|
||||
4. **自托管**: 最大的控制权
|
||||
|
||||
## 部署过程
|
||||
|
||||
我们将逐步介绍每个平台的设置过程,包括配置文件、环境变量和持续部署工作流。
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
---
|
||||
import "~/styles/globals.css"
|
||||
import { config } from "~/config"
|
||||
import { en, zh } from "~/config"
|
||||
import { getLocale } from "astro-i18n-aut"
|
||||
import { NoiseBg } from "~/components/react/noise-bg"
|
||||
|
||||
const locale = getLocale(Astro.url)
|
||||
const config = locale === "zh" ? zh.meta : en.meta
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
|
|
@ -9,10 +13,11 @@ import { NoiseBg } from "~/components/react/noise-bg"
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href={config.meta.favicon} />
|
||||
<link rel="icon" type="image/svg+xml" href={config.favicon} />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{config.meta.title} - {config.meta.slogan}</title>
|
||||
<meta name="description" content={config.meta.description} />
|
||||
<title>{config.title} - {config.slogan}</title>
|
||||
<meta name="description" content={config.description} />
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<script is:inline>
|
||||
// set theme before page load
|
||||
const theme = (() => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
import Header from "~/components/astro/header.astro"
|
||||
import Nav from "~/components/astro/nav.astro"
|
||||
import Navigation from "~/components/astro/navigation.astro"
|
||||
import BaseLayout from "./base.astro"
|
||||
---
|
||||
|
||||
<BaseLayout>
|
||||
<main class="max-auto mb-10 w-full max-w-3xl">
|
||||
<Header />
|
||||
<Nav />
|
||||
<Navigation />
|
||||
<slot />
|
||||
</main>
|
||||
</BaseLayout>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
---
|
||||
import type { CollectionEntry } from "astro:content"
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
import "~/styles/post.css"
|
||||
import { formatDate } from "~/utils"
|
||||
|
||||
type Props = CollectionEntry<"posts">["data"]
|
||||
|
||||
const { title, description, pubDate, updatedDate } = Astro.props
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
---
|
||||
import About from "~/content/about.mdx"
|
||||
import { getLocale } from "astro-i18n-aut"
|
||||
import AboutEn from "~/content/en/about.mdx"
|
||||
import AboutZh from "~/content/zh/about.mdx"
|
||||
|
||||
const locale = getLocale(Astro.url)
|
||||
const AboutContent = locale === "zh" ? AboutZh : AboutEn
|
||||
---
|
||||
|
||||
<About />
|
||||
<AboutContent />
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
---
|
||||
import { getLocale } from "astro-i18n-aut"
|
||||
import { defaultLanguage } from "~/config"
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
import { type Post } from "~/types/post"
|
||||
import { formatDate, getPosts } from "~/utils"
|
||||
import { formatDate, getPostsByLocale } from "~/utils"
|
||||
|
||||
const posts = await getPosts()
|
||||
const locale = getLocale(Astro.url)
|
||||
const posts = await getPostsByLocale(locale)
|
||||
|
||||
const postsByYear = posts.reduce(
|
||||
(acc: Record<string, Post[]>, post: any) => {
|
||||
(acc: Record<string, any[]>, post: any) => {
|
||||
const year = new Date(post.data.pubDate).getFullYear().toString()
|
||||
if (!acc[year]) {
|
||||
acc[year] = []
|
||||
|
|
@ -14,7 +16,7 @@ const postsByYear = posts.reduce(
|
|||
acc[year].push(post)
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, Post[]>,
|
||||
{} as Record<string, any[]>,
|
||||
)
|
||||
|
||||
const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a))
|
||||
|
|
@ -28,7 +30,17 @@ const years = Object.keys(postsByYear).sort((a, b) => Number(b) - Number(a))
|
|||
<h2 class="mb-4 text-2xl font-bold">{year}</h2>
|
||||
<div class="space-y-2">
|
||||
{postsByYear[year].map((post: any) => (
|
||||
<a href={`/posts/${post.slug}/`}>
|
||||
<a
|
||||
href={
|
||||
defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? `/posts/${post.id}/`
|
||||
: `/en/posts/${post.id}/`
|
||||
: locale === "en"
|
||||
? `/posts/${post.id}/`
|
||||
: `/zh/posts/${post.id}/`
|
||||
}
|
||||
>
|
||||
<div class="my-2 flex text-lg">
|
||||
<p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
|
||||
<time>{formatDate(post.data.pubDate)}</time>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import rss from "@astrojs/rss"
|
||||
import type { CollectionEntry } from "astro:content"
|
||||
import sanitizeHtml from "sanitize-html"
|
||||
import { config } from "~/config"
|
||||
import { getPosts } from "~/utils"
|
||||
import { zh as config } from "~/config"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
|
||||
export async function GET() {
|
||||
const posts = await getPosts()
|
||||
const posts = await getPostsByLocale("zh")
|
||||
|
||||
return rss({
|
||||
title: config.meta.title,
|
||||
|
|
@ -14,11 +13,11 @@ export async function GET() {
|
|||
process.env.NODE_ENV === "development"
|
||||
? "http://localhost:4321"
|
||||
: config.meta.url,
|
||||
items: posts.map((post: CollectionEntry<"posts">) => ({
|
||||
items: posts.map((post: any) => ({
|
||||
title: post.data.title,
|
||||
description: post.data.description,
|
||||
pubDate: post.data.pubDate,
|
||||
link: `/posts/${post.slug}/`,
|
||||
link: `/posts/${post.id}/`,
|
||||
content: sanitizeHtml(post.body),
|
||||
})),
|
||||
customData: "",
|
||||
|
|
|
|||
19
src/pages/en/posts/[...slug].astro
Normal file
19
src/pages/en/posts/[...slug].astro
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
import PostLayout from "~/layouts/post.astro"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getPostsByLocale("en")
|
||||
|
||||
return posts.map((post: any) => ({
|
||||
params: { slug: post.id },
|
||||
props: { post },
|
||||
}))
|
||||
}
|
||||
|
||||
const { post } = Astro.props
|
||||
---
|
||||
|
||||
<PostLayout {...post.data}>
|
||||
<div set:html={post.rendered.html} />
|
||||
</PostLayout>
|
||||
22
src/pages/en/tags/[tag].astro
Normal file
22
src/pages/en/tags/[tag].astro
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
import TagComponent from "~/components/astro/tag.astro"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getPostsByLocale("en")
|
||||
const uniqueTags = [
|
||||
...new Set(posts.flatMap((post: any) => post.data.tags || [])),
|
||||
]
|
||||
|
||||
return uniqueTags.map((tag) => ({
|
||||
params: { tag },
|
||||
props: {
|
||||
posts,
|
||||
tag,
|
||||
locale: "en",
|
||||
},
|
||||
}))
|
||||
}
|
||||
---
|
||||
|
||||
<TagComponent {...Astro.props} />
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import Blog from "~/components/astro/blog.astro"
|
||||
import Footer from "~/components/astro/footer.astro"
|
||||
import Intro from "~/content/intro.mdx"
|
||||
import Intro from "~/components/astro/intro.astro"
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
---
|
||||
import { type CollectionEntry, getCollection } from "astro:content"
|
||||
import { defaultLanguage } from "~/config"
|
||||
import PostLayout from "~/layouts/post.astro"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection("posts")
|
||||
return posts.map((post: { slug: string; [key: string]: any }) => ({
|
||||
params: { slug: post.slug },
|
||||
props: post,
|
||||
const posts = await getPostsByLocale(defaultLanguage)
|
||||
|
||||
return posts.map((post: any) => ({
|
||||
params: { slug: post.id },
|
||||
props: { post },
|
||||
}))
|
||||
}
|
||||
type Props = CollectionEntry<"posts">
|
||||
|
||||
const post = Astro.props
|
||||
const { Content } = await post.render()
|
||||
const { post } = Astro.props
|
||||
---
|
||||
|
||||
<PostLayout {...post.data}>
|
||||
<Content />
|
||||
<div set:html={post.rendered.html} />
|
||||
</PostLayout>
|
||||
|
|
|
|||
12
src/pages/robots/index.txt.ts
Normal file
12
src/pages/robots/index.txt.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import type { APIRoute } from "astro"
|
||||
|
||||
const getRobotsTxt = (sitemapURL: URL) => `
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: ${sitemapURL.href}
|
||||
`
|
||||
|
||||
export const GET: APIRoute = ({ site }) => {
|
||||
const sitemapURL = new URL("sitemap-index.xml", site)
|
||||
return new Response(getRobotsTxt(sitemapURL))
|
||||
}
|
||||
|
|
@ -1,39 +1,23 @@
|
|||
---
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
import { formatDate, getPosts } from "~/utils"
|
||||
import TagComponent from "~/components/astro/tag.astro"
|
||||
import { defaultLanguage } from "~/config"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getPosts()
|
||||
const posts = await getPostsByLocale(defaultLanguage)
|
||||
const uniqueTags = [
|
||||
...new Set(posts.flatMap((post: any) => post.data.tags || [])),
|
||||
]
|
||||
|
||||
return uniqueTags.map((tag) => ({
|
||||
params: { tag },
|
||||
props: { posts },
|
||||
props: {
|
||||
posts,
|
||||
tag,
|
||||
locale: defaultLanguage,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
const { tag } = Astro.params
|
||||
const { posts } = Astro.props
|
||||
const filteredPosts = posts.filter((post: any) =>
|
||||
post.data.tags?.includes(tag as string),
|
||||
)
|
||||
---
|
||||
|
||||
<HomeLayout>
|
||||
<ul>
|
||||
{
|
||||
filteredPosts.map((post: any) => (
|
||||
<a href={`/posts/${post.slug}/`}>
|
||||
<div class="my-2 flex text-lg">
|
||||
<p class="flex w-36 flex-shrink-0 truncate text-gray-500 dark:text-gray-400">
|
||||
<time>{formatDate(post.data.pubDate)}</time>
|
||||
</p>
|
||||
<p class="line-clamp-2">{post.data.title}</p>
|
||||
</div>
|
||||
</a>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</HomeLayout>
|
||||
<TagComponent {...Astro.props} />
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
---
|
||||
import { getLocale } from "astro-i18n-aut"
|
||||
import { defaultLanguage } from "~/config"
|
||||
import HomeLayout from "~/layouts/home.astro"
|
||||
import { getPosts } from "~/utils"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
|
||||
const posts = await getPosts()
|
||||
const locale = getLocale(Astro.url)
|
||||
const posts = await getPostsByLocale(locale)
|
||||
const tags = [...new Set(posts.map((post: any) => post.data.tags).flat())]
|
||||
---
|
||||
|
||||
|
|
@ -11,7 +14,19 @@ const tags = [...new Set(posts.map((post: any) => post.data.tags).flat())]
|
|||
{
|
||||
tags.map((tag) => (
|
||||
<span class="mx-4">
|
||||
<a href={`/tags/${tag}`}>{tag}</a>
|
||||
<a
|
||||
href={
|
||||
defaultLanguage === "zh"
|
||||
? locale === "zh"
|
||||
? `/tags/${tag}`
|
||||
: `/en/tags/${tag}`
|
||||
: locale === "en"
|
||||
? `/tags/${tag}`
|
||||
: `/zh/tags/${tag}`
|
||||
}
|
||||
>
|
||||
{tag}
|
||||
</a>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
|
|
|
|||
19
src/pages/zh/posts/[...slug].astro
Normal file
19
src/pages/zh/posts/[...slug].astro
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
import PostLayout from "~/layouts/post.astro"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getPostsByLocale("zh")
|
||||
|
||||
return posts.map((post: any) => ({
|
||||
params: { slug: post.id },
|
||||
props: { post },
|
||||
}))
|
||||
}
|
||||
|
||||
const { post } = Astro.props
|
||||
---
|
||||
|
||||
<PostLayout {...post.data}>
|
||||
<div set:html={post.rendered.html} />
|
||||
</PostLayout>
|
||||
22
src/pages/zh/tags/[tag].astro
Normal file
22
src/pages/zh/tags/[tag].astro
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
import TagComponent from "~/components/astro/tag.astro"
|
||||
import { getPostsByLocale } from "~/utils"
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getPostsByLocale("zh")
|
||||
const uniqueTags = [
|
||||
...new Set(posts.flatMap((post: any) => post.data.tags || [])),
|
||||
]
|
||||
|
||||
return uniqueTags.map((tag) => ({
|
||||
params: { tag },
|
||||
props: {
|
||||
posts,
|
||||
tag,
|
||||
locale: "zh",
|
||||
},
|
||||
}))
|
||||
}
|
||||
---
|
||||
|
||||
<TagComponent {...Astro.props} />
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import type { CollectionEntry } from "astro:content"
|
||||
import { getCollection } from "astro:content"
|
||||
|
||||
export const formatDate = (date: Date | string | undefined): string => {
|
||||
|
|
@ -14,10 +13,10 @@ export const formatDate = (date: Date | string | undefined): string => {
|
|||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
export const getPosts = async () => {
|
||||
const posts = await getCollection("posts")
|
||||
export const getPostsByLocale = async (locale: string) => {
|
||||
const posts =
|
||||
locale === "zh" ? await getCollection("zh") : await getCollection("en")
|
||||
return posts.sort(
|
||||
(a: CollectionEntry<"posts">, b: CollectionEntry<"posts">) =>
|
||||
b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
|
||||
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*", "eslint.config.cjs"],
|
||||
"exclude": ["dist"],
|
||||
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react",
|
||||
|
|
@ -9,6 +10,8 @@
|
|||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"strictNullChecks": true, // add if using `base` template
|
||||
"allowJs": true // required, and included with all Astro templates
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue