diff --git a/astro.config.ts b/astro.config.ts index e4c3173..3fb9aa2 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -18,6 +18,7 @@ export default defineConfig({ service: !options.isProd() ? { entrypoint: './plugins/resize', config: {} } : undefined, }, experimental: { + actions: true, env: { schema: { // Postgres Database diff --git a/package-lock.json b/package-lock.json index 8316a77..cf8ecc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "@napi-rs/canvas": "^0.1.53", "@types/lodash": "^4.17.5", "@types/luxon": "^3.4.2", - "@types/node": "^20.14.7", + "@types/node": "^20.14.8", "@types/pg": "^8.11.6", "@types/qrcode-svg": "^1.1.4", "@types/unist": "^3.0.2", @@ -3581,9 +3581,9 @@ ] }, "node_modules/@shikijs/core": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.7.0.tgz", - "integrity": "sha512-O6j27b7dGmJbR3mjwh/aHH8Ld+GQvA0OQsNO43wKWnqbAae3AYXrhFyScHGX8hXZD6vX2ngjzDFkZY5srtIJbQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.9.0.tgz", + "integrity": "sha512-cbSoY8P/jgGByG8UOl3jnP/CWg/Qk+1q+eAKWtcrU3pNoILF8wTsLB0jT44qUBV8Ce1SvA9uqcM9Xf+u3fJFBw==", "license": "MIT" }, "node_modules/@smithy/abort-controller": { @@ -4309,9 +4309,9 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.0.tgz", - "integrity": "sha512-5OVcC5ZcmmutY208ADY/l2eB4H4DVXs+hPUo/M1spF4/YEmF9DdLkfwBvohej2dIeVJayKY7hMlD0X8j3F3/Uw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.1.tgz", + "integrity": "sha512-xT+Bbpe5sSrC7cCWSElOreDdWzqovR1V+7xrp+fmwGAA+TPYBb78iasaXjO1pa+65sY6JjW5GtGeIoJwCK9B1g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4457,9 +4457,9 @@ } }, "node_modules/@types/node": { - "version": "20.14.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.7.tgz", - "integrity": "sha512-uTr2m2IbJJucF3KUxgnGOZvYbN0QgkGyWxG6973HCpMYFy2KfcgYuIwkJQMQkt1VbBMlvWRbpshFTLxnxCZjKQ==", + "version": "20.14.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz", + "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -5740,9 +5740,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.807", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.807.tgz", - "integrity": "sha512-kSmJl2ZwhNf/bcIuCH/imtNOKlpkLDn2jqT5FJ+/0CXjhnFaOa9cOe9gHKKy71eM49izwuQjZhKk+lWQ1JxB7A==", + "version": "1.4.810", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.810.tgz", + "integrity": "sha512-Kaxhu4T7SJGpRQx99tq216gCq2nMxJo+uuT6uzz9l8TVN2stL7M06MIIXAtr9jsrLs2Glflgf2vMQRepxawOdQ==", "license": "ISC" }, "node_modules/emmet": { @@ -9766,12 +9766,12 @@ } }, "node_modules/shiki": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.7.0.tgz", - "integrity": "sha512-H5pMn4JA7ayx8H0qOz1k2qANq6mZVCMl1gKLK6kWIrv1s2Ial4EmD4s4jE8QB5Dw03d/oCQUxc24sotuyR5byA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.9.0.tgz", + "integrity": "sha512-i6//Lqgn7+7nZA0qVjoYH0085YdNk4MC+tJV4bo+HgjgRMJ0JmkLZzFAuvVioJqLkcGDK5GAMpghZEZkCnwxpQ==", "license": "MIT", "dependencies": { - "@shikijs/core": "1.7.0" + "@shikijs/core": "1.9.0" } }, "node_modules/signal-exit": { @@ -10179,9 +10179,9 @@ } }, "node_modules/typescript-auto-import-cache": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.2.tgz", - "integrity": "sha512-+laqe5SFL1vN62FPOOJSUDTZxtgsoOXjneYOXIpx5rQ4UMiN89NAtJLpqLqyebv9fgQ/IMeeTX+mQyRnwvJzvg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.3.tgz", + "integrity": "sha512-ojEC7+Ci1ij9eE6hp8Jl9VUNnsEKzztktP5gtYNRMrTmfXVwA1PITYYAkpxCvvupdSYa/Re51B6KMcv1CTZEUA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 9c7e242..7f0259e 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "@napi-rs/canvas": "^0.1.53", "@types/lodash": "^4.17.5", "@types/luxon": "^3.4.2", - "@types/node": "^20.14.7", + "@types/node": "^20.14.8", "@types/pg": "^8.11.6", "@types/qrcode-svg": "^1.1.4", "@types/unist": "^3.0.2", diff --git a/src/actions/index.ts b/src/actions/index.ts new file mode 100644 index 0000000..b382f18 --- /dev/null +++ b/src/actions/index.ts @@ -0,0 +1,109 @@ +import Comment from '@/components/comment/Comment.astro'; +import CommentItem from '@/components/comment/CommentItem.astro'; +import { commentConfig, createComment, loadComments } from '@/components/comment/artalk'; +import { partialRender } from '@/helpers/container'; +import { decreaseLikes, increaseLikes, queryLikes } from '@/helpers/db/query'; +import { pages, posts } from '@/helpers/schema'; +import { urlJoin } from '@/helpers/tools'; +import { z } from 'astro/zod'; +import { ActionError, defineAction } from 'astro:actions'; +import crypto from 'node:crypto'; + +const keys = [...posts.map((post) => post.permalink), ...pages.map((page) => page.permalink)]; +const CommentConnectError = new ActionError({ + code: 'INTERNAL_SERVER_ERROR', + message: "couldn't connect to comment server", +}); + +export const server = { + like: defineAction({ + accept: 'json', + input: z + .object({ + key: z.custom((val) => keys.includes(val)), + }) + .and( + z + .object({ + action: z.enum(['increase']), + }) + .or( + z.object({ + action: z.enum(['decrease']), + token: z.string().min(1), + }), + ), + ), + handler: async (input) => { + // Increase the like counts. + if (input.action === 'increase') { + return await increaseLikes(input.key); + } + // Decrease the like counts. + await decreaseLikes(input.key, input.token); + return { likes: await queryLikes(input.key) }; + }, + }), + avatar: defineAction({ + accept: 'json', + input: z.object({ email: z.string().email() }), + handler: async ({ email }) => { + const config = await commentConfig(); + if (config === null) { + throw CommentConnectError; + } + + const hash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex'); + return { avatar: urlJoin(config.frontend_conf.gravatar.mirror, `${hash}?d=mm&s=80`) }; + }, + }), + comment: defineAction({ + accept: 'json', + input: z.object({ + page_key: z.string(), + name: z.string(), + email: z.string().email(), + link: z.string().optional(), + content: z.string().min(1), + rid: z.number().optional(), + }), + handler: async (request) => { + const resp = await createComment(request); + if ('msg' in resp) { + throw new ActionError({ + code: 'INTERNAL_SERVER_ERROR', + message: resp.msg, + }); + } + + const config = await commentConfig(); + const content = await partialRender(CommentItem, { + props: { depth: 2, comment: resp, pending: resp.is_pending, config: config }, + }); + + return { content }; + }, + }), + comments: defineAction({ + accept: 'json', + input: z.object({ + page_key: z.string(), + offset: z.number(), + }), + handler: async ({ page_key, offset }) => { + const config = await commentConfig(); + if (config === null) { + throw CommentConnectError; + } + + const comments = await loadComments(page_key, null, Number(offset), config); + if (comments === null) { + throw CommentConnectError; + } + + const content = await partialRender(Comment, { props: { comments: comments, config: config } }); + + return { content }; + }, + }), +}; diff --git a/src/assets/scripts/yufan.me.js b/src/assets/scripts/yufan.me.js index bdae1c9..04d4853 100644 --- a/src/assets/scripts/yufan.me.js +++ b/src/assets/scripts/yufan.me.js @@ -1,6 +1,29 @@ import Aplayer from 'aplayer/dist/APlayer.min.js'; +import { actions, isInputError } from 'astro:actions'; import stickySidebar from './sticky-sidebar.js'; +// Error Popup. +const handleActionError = (error) => { + const errorMsg = isInputError(error) + ? error.issues.map((issue) => `

${issue.message}

`).join('\n') + : error.message; + const errorPopup = `
+
+
+
+
+
+
+

${errorMsg}

+
+
+
+
`; + document.querySelector('body').insertAdjacentHTML('beforeend', errorPopup); + const popup = document.querySelector('.nice-popup-error'); + popup.querySelector('.nice-popup-close').addEventListener('click', () => popup.remove()); +}; + // Menu toggle. const menuBody = document.querySelector('.site-aside'); document.addEventListener('keydown', (event) => { @@ -119,12 +142,12 @@ if (typeof comments !== 'undefined' && comments !== null) { const email = event.target.value; if (email !== '' && email.includes('@')) { // Replace the avatar after typing the email. - fetch(`/comments/avatar?email=${email}`) - .then((res) => res.text()) - .then((link) => { - avatar.src = link; - }) - .catch((e) => console.log(e)); + actions.avatar.safe({ email }).then(({ data, error }) => { + if (error) { + return handleActionError(error); + } + avatar.src = data.avatar; + }); } else { avatar.src = avatar.dataset.src; } @@ -135,19 +158,19 @@ if (typeof comments !== 'undefined' && comments !== null) { // Loading more comments from server. if (event.target === comments.querySelector('#comments-next-button')) { const { size, offset, key } = event.target.dataset; - const html = await fetch(`/comments/list?key=${key}&offset=${offset}`) - .then((res) => res.text()) - .catch((e) => { - console.log(e); - return ''; - }); - if (html === '') { + const { data, error } = await actions.comments.safe({ offset: Number(offset), page_key: key }); + if (error) { + return handleActionError(error); + } + + const { content } = data; + if (content === '') { // Remove the load more button. event.target.remove(); } else { // Append the comments into the list. event.target.dataset.offset = Number(offset) + Number(size); - comments.querySelector('.comment-list').insertAdjacentHTML('beforeend', html); + comments.querySelector('.comment-list').insertAdjacentHTML('beforeend', content); } } @@ -185,30 +208,25 @@ if (typeof comments !== 'undefined' && comments !== null) { event.stopPropagation(); const formData = new FormData(event.target); - const data = {}; + const request = {}; for (const [key, value] of formData) { - data[key] = value; + request[key] = value; } + request.rid = request.rid === undefined ? 0 : Number(request.rid); - const resp = await fetch('/comments/new', { - method: 'POST', - headers: { - 'Content-type': 'application/json; charset=UTF-8', - }, - body: JSON.stringify(data), - }) - .then((res) => res.text()) - .catch((e) => { - console.log(e); - return '
  • 评论失败
  • '; - }); + actions.comment.safe(request).then(({ data, error }) => { + if (error) { + return handleActionError(error); + } - if (data.rid !== '0') { - replyForm.insertAdjacentHTML('beforebegin', resp); - } else { - const list = comments.querySelector('.comment-list'); - list.insertAdjacentHTML('afterbegin', resp); - } + const { content } = data; + if (request.rid !== '0') { + replyForm.insertAdjacentHTML('beforebegin', content); + } else { + const list = comments.querySelector('.comment-list'); + list.insertAdjacentHTML('afterbegin', content); + } + }); cancelReply(); }); @@ -230,8 +248,8 @@ const scrollIntoView = (elem) => { window.scroll(scrollOptions); }; +// Highlighting the selected comment. const focusComment = () => { - // Highlighting the selected comment. if (location.hash.startsWith('#atk-comment-')) { for (const li of document.querySelectorAll('.comment-body')) { li.classList.remove('active'); @@ -250,46 +268,38 @@ window.addEventListener('load', focusComment); // Add like button for updating likes. const likeButton = document.querySelector('button.post-like'); -const increaseLikes = (count) => { +const increaseLikes = (count, permalink) => { count.textContent = Number.parseInt(count.textContent) + 1; - fetch('/likes', { - method: 'POST', - headers: { - 'Content-type': 'application/json; charset=UTF-8', - }, - body: JSON.stringify({ action: 'increase' }), - }) - .then((res) => res.json()) - .then(({ likes, token }) => { - count.textContent = likes; - localStorage.setItem(window.location.href, token); - }); + actions.like.safe({ action: 'increase', key: permalink }).then(({ data, error }) => { + if (error) { + return handleActionError(error); + } + const { likes, token } = data; + count.textContent = likes; + localStorage.setItem(permalink, token); + }); }; -const decreaseLikes = (count) => { - const token = localStorage.getItem(window.location.href); +const decreaseLikes = (count, permalink) => { + const token = localStorage.getItem(permalink); if (token === null || token === '') { return; } - count.textContent = Number.parseInt(count.textContent) - 1; - fetch('/likes', { - method: 'POST', - headers: { - 'Content-type': 'application/json; charset=UTF-8', - }, - body: JSON.stringify({ action: 'decrease', token: token }), - }) - .then((res) => res.json()) - .then(({ likes }) => { - count.textContent = likes; - localStorage.removeItem(window.location.href); - }); + actions.like.safe({ action: 'decrease', key: permalink, token }).then(({ data, error }) => { + if (error) { + return handleActionError(error); + } + count.textContent = data.likes; + localStorage.removeItem(permalink); + }); }; if (typeof likeButton !== 'undefined' && likeButton !== null) { + const permalink = likeButton.dataset.permalink; + // Change the like state if it has been liked. - const token = localStorage.getItem(window.location.href); + const token = localStorage.getItem(permalink); if (token !== null && token !== '') { likeButton.classList.add('current'); } @@ -307,10 +317,10 @@ if (typeof likeButton !== 'undefined' && likeButton !== null) { // Increase the likes and set liked before submitting. if (likeButton.classList.contains('current')) { likeButton.classList.remove('current'); - decreaseLikes(count); + decreaseLikes(count, permalink); } else { likeButton.classList.add('current'); - increaseLikes(count); + increaseLikes(count, permalink); } }); } diff --git a/src/assets/styles/globals.css b/src/assets/styles/globals.css index b4cde75..953a795 100644 --- a/src/assets/styles/globals.css +++ b/src/assets/styles/globals.css @@ -2244,10 +2244,6 @@ a:hover .overlay { /*-------------------------------------------------------------- error content --------------------------------------------------------------*/ -.nice-popup-error { - align-items: flex-start; -} - .nice-popup-error .nice-popup-content { display: flex; align-items: center; diff --git a/src/components/comment/Comments.astro b/src/components/comment/Comments.astro index 89acebc..f86ae37 100644 --- a/src/components/comment/Comments.astro +++ b/src/components/comment/Comments.astro @@ -1,5 +1,5 @@ --- -import { getConfig, loadComments } from '@/components/comment/artalk'; +import { commentConfig, loadComments } from '@/components/comment/artalk'; import Comment from '@/components/comment/Comment.astro'; import { urlJoin } from '@/helpers/tools'; import options from '@/options'; @@ -11,7 +11,7 @@ interface Props { } const { commentKey, title } = Astro.props; -const config = await getConfig(); +const config = await commentConfig(); const comments = config != null ? await loadComments(commentKey, title, 0, config) : null; --- diff --git a/src/components/comment/artalk.ts b/src/components/comment/artalk.ts index fc28384..8d33422 100644 --- a/src/components/comment/artalk.ts +++ b/src/components/comment/artalk.ts @@ -20,7 +20,7 @@ import sanitize from 'ultrahtml/transformers/sanitize'; // Access the artalk in internal docker host when it was deployed on zeabur. const server = options.isProd() ? `http://${ARTALK_HOST}:23366` : options.settings.comments.server; -export const getConfig = async (): Promise => { +export const commentConfig = async (): Promise => { const data = await fetch(urlJoin(server, '/api/v2/conf')) .then((response) => response.json()) .catch((e) => { @@ -79,7 +79,11 @@ export const createComment = async (req: CommentReq): Promise => { diff --git a/src/components/comment/types.ts b/src/components/comment/types.ts index 42d6d2b..e6f357a 100644 --- a/src/components/comment/types.ts +++ b/src/components/comment/types.ts @@ -1,28 +1,18 @@ +// The configuration in artalk. export interface CommentConfig { - frontend_conf: FrontendConf; -} - -export interface FrontendConf { - flatMode: boolean; - gravatar: Gravatar; - pagination: Pagination; -} - -export interface Gravatar { - mirror: string; - params: string; -} - -export interface Pagination { - pageSize: number; -} - -export interface Comments { - comments: Comment[]; - count: number; - roots_count: number; + frontend_conf: { + flatMode: boolean; + gravatar: { + mirror: string; + params: string; + }; + pagination: { + pageSize: number; + }; + }; } +// The single comment. export interface Comment { id: number; content: string; @@ -33,17 +23,19 @@ export interface Comment { rid: number; } -export interface PV { - page_key: string; - page_title: string; - site_name: string; -} - +// Grouping the comments into parent child structure. export interface CommentItem extends Comment { children?: CommentItem[]; } -// Create comment request +// The comment list. +export interface Comments { + comments: Comment[]; + count: number; + roots_count: number; +} + +// Create comment request. export interface CommentReq { page_key: string; name: string; @@ -53,11 +45,12 @@ export interface CommentReq { rid?: number; } -// Create comment response +// Create comment response. export interface CommentResp extends Comment { is_pending: boolean; } +// Error response in creating comment. export interface ErrorResp { msg: string; } diff --git a/src/components/like/LikeButton.astro b/src/components/like/LikeButton.astro index be05c19..c3ed07e 100644 --- a/src/components/like/LikeButton.astro +++ b/src/components/like/LikeButton.astro @@ -1,17 +1,21 @@ --- import { queryLikes } from '@/helpers/db/query'; -import type { Post } from '@/helpers/schema'; interface Props { - post: Post; + permalink: string; } -const { post } = Astro.props; -const likes = await queryLikes(post.slug); +const { permalink } = Astro.props; +const likes = await queryLikes(permalink); ---
    - diff --git a/src/components/like/LikeIcon.astro b/src/components/like/LikeIcon.astro index a98e20c..142d51f 100644 --- a/src/components/like/LikeIcon.astro +++ b/src/components/like/LikeIcon.astro @@ -1,13 +1,12 @@ --- import { queryLikesAndViews } from '@/helpers/db/query'; -import type { Post } from '@/helpers/schema'; interface Props { - post: Post; + permalink: string; } -const { post } = Astro.props; -const [likes, view] = await queryLikesAndViews(post.slug); +const { permalink } = Astro.props; +const [likes, view] = await queryLikesAndViews(permalink); ---
    diff --git a/src/components/like/LikeIconSmall.astro b/src/components/like/LikeIconSmall.astro index 0170e38..373535a 100644 --- a/src/components/like/LikeIconSmall.astro +++ b/src/components/like/LikeIconSmall.astro @@ -1,13 +1,12 @@ --- import { queryLikes } from '@/helpers/db/query'; -import type { Post } from '@/helpers/schema'; interface Props { - post: Post; + permalink: string; } -const { post } = Astro.props; -const likes = await queryLikes(post.slug); +const { permalink } = Astro.props; +const likes = await queryLikes(permalink); ---
    diff --git a/src/components/like/Share.astro b/src/components/like/LikeShare.astro similarity index 100% rename from src/components/like/Share.astro rename to src/components/like/LikeShare.astro diff --git a/src/components/page/friend/FriendCard.astro b/src/components/page/friend/FriendCard.astro deleted file mode 100644 index 1f11c89..0000000 --- a/src/components/page/friend/FriendCard.astro +++ /dev/null @@ -1,38 +0,0 @@ ---- -import type { Friend } from '@/helpers/schema'; - -interface Props extends Friend {} - -const { website, description, homepage, poster, favicon } = Astro.props; ---- - -
    -
    -
    -
    -
    -
    -
    -
    -
    - {website} -
    -
    -
    -
    {description ? description : ' '}
    -
    -
    - -
    -
    diff --git a/src/components/page/friend/FriendLinks.astro b/src/components/page/friend/FriendLinks.astro deleted file mode 100644 index 319afab..0000000 --- a/src/components/page/friend/FriendLinks.astro +++ /dev/null @@ -1,28 +0,0 @@ ---- -import FriendCard from '@/components/page/friend/FriendCard.astro'; -import { friends } from '@/helpers/schema'; -import _ from 'lodash'; - -const list = _.shuffle(friends); ---- - -{ - list.length > 0 ? ( -
    -

    - 左邻右舍 排名不分前后 -

    -
    - {list.map((friend) => ( - - ))} -
    -
    - ) : ( -
    -
    -
    还没有友链呢...😭
    -
    -
    - ) -} diff --git a/src/components/page/friend/Friends.astro b/src/components/page/friend/Friends.astro new file mode 100644 index 0000000..f00ee6f --- /dev/null +++ b/src/components/page/friend/Friends.astro @@ -0,0 +1,54 @@ +--- +import { friends } from '@/helpers/schema'; +import _ from 'lodash'; + +const list = _.shuffle(friends); +--- + +{ + list.length > 0 ? ( +
    +

    + 左邻右舍 排名不分前后 +

    +
    + {list.map((friend) => ( +
    +
    +
    +
    +
    +
    +
    +
    + {friend.website} +
    +
    +
    {friend.description ? friend.description : ' '}
    +
    +
    + +
    +
    + ))} +
    +
    + ) : ( +
    +
    +
    还没有友链呢...😭
    +
    +
    + ) +} diff --git a/src/components/page/post/PostCard.astro b/src/components/page/post/PostCard.astro deleted file mode 100644 index 8c433d9..0000000 --- a/src/components/page/post/PostCard.astro +++ /dev/null @@ -1,42 +0,0 @@ ---- -import Image from '@/components/image/Image.astro'; -import LikeIcon from '@/components/like/LikeIcon.astro'; -import { formatShowDate } from '@/helpers/formatter'; -import { getCategory, type Post } from '@/helpers/schema'; - -interface Props { - post: Post; -} - -const { post } = Astro.props; -const category = getCategory(post.category, undefined); ---- - -
    - -
    -
    - -
    {post.title}
    -
    -
    -
    {post.summary ?? ''}
    -
    -
    - -
    -
    diff --git a/src/components/page/post/PostCards.astro b/src/components/page/post/PostCards.astro index 06815b7..60612b5 100644 --- a/src/components/page/post/PostCards.astro +++ b/src/components/page/post/PostCards.astro @@ -1,9 +1,10 @@ --- +import Image from '@/components/image/Image.astro'; +import LikeIcon from '@/components/like/LikeIcon.astro'; import Pagination from '@/components/page/pagination/Pagination.astro'; -import { slicePosts } from '@/helpers/formatter'; -import type { Post } from '@/helpers/schema'; +import { formatShowDate, slicePosts } from '@/helpers/formatter'; +import { getCategory, type Post } from '@/helpers/schema'; import options from '@/options'; -import PostCard from './PostCard.astro'; interface Props { posts: Post[]; @@ -20,6 +21,42 @@ const { currentPosts, totalPage } = results; ---
    -
    {currentPosts.map((post) => )}
    +
    + { + currentPosts.map((post) => ( +
    + +
    +
    + +
    {post.title}
    +
    +
    +
    {post.summary ?? ''}
    +
    +
    + +
    +
    + )) + } +
    diff --git a/src/components/page/post/PostSquare.astro b/src/components/page/post/PostSquare.astro index 4355ae5..ef4b3dc 100644 --- a/src/components/page/post/PostSquare.astro +++ b/src/components/page/post/PostSquare.astro @@ -26,7 +26,7 @@ const { post, first } = Astro.props;
    {formatShowDate(post.date)}
    - +
    diff --git a/src/content/tags/index.yml b/src/content/tags/index.yml index 44cacaf..0c93d6c 100644 --- a/src/content/tags/index.yml +++ b/src/content/tags/index.yml @@ -558,9 +558,5 @@ slug: algorithm - name: 面试 slug: interview -- name: 阿里 - slug: alibaba -- name: 腾讯 - slug: tencent - name: 读书 slug: reading diff --git a/src/env.d.ts b/src/env.d.ts index 338ba9e..a9971a2 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,3 +1,4 @@ +/// /// /// /// diff --git a/src/helpers/db/query.ts b/src/helpers/db/query.ts index 82422a7..681d581 100644 --- a/src/helpers/db/query.ts +++ b/src/helpers/db/query.ts @@ -44,8 +44,6 @@ export const latestComments = async (): Promise => { }); }; -const generateKey = (slug: string): string => urlJoin(options.website, '/posts', slug, '/'); - export const increaseViews = async (pageKey: string) => { await db .update(atk_pages) @@ -55,8 +53,10 @@ export const increaseViews = async (pageKey: string) => { .where(eq(atk_pages.key, sql`${pageKey}`)); }; -export const increaseLikes = async (slug: string): Promise<{ likes: number; token: string }> => { - const pageKey = generateKey(slug); +const generatePageKey = (permalink: string): string => urlJoin(options.website, permalink, '/'); + +export const increaseLikes = async (permalink: string): Promise<{ likes: number; token: string }> => { + const pageKey = generatePageKey(permalink); const token = makeToken(250); // Save the token await db.insert(atk_likes).values({ @@ -74,11 +74,11 @@ export const increaseLikes = async (slug: string): Promise<{ likes: number; toke }) .where(eq(atk_pages.key, sql`${pageKey}`)); - return { likes: await queryLikes(slug), token: token }; + return { likes: await queryLikes(permalink), token: token }; }; -export const decreaseLikes = async (slug: string, token: string) => { - const pageKey = generateKey(slug); +export const decreaseLikes = async (permalink: string, token: string) => { + const pageKey = generatePageKey(permalink); const results = await db .select({ id: atk_likes.id }) .from(atk_likes) @@ -108,8 +108,8 @@ export const decreaseLikes = async (slug: string, token: string) => { .where(eq(atk_pages.key, sql`${pageKey}`)); }; -export const queryLikes = async (slug: string): Promise => { - const pageKey = generateKey(slug); +export const queryLikes = async (permalink: string): Promise => { + const pageKey = generatePageKey(permalink); const results = await db .select({ like: atk_pages.vote_up }) .from(atk_pages) @@ -119,8 +119,8 @@ export const queryLikes = async (slug: string): Promise => { return results.length > 0 ? results[0].like ?? 0 : 0; }; -export const queryLikesAndViews = async (slug: string): Promise<[number, number]> => { - const pageKey = generateKey(slug); +export const queryLikesAndViews = async (permalink: string): Promise<[number, number]> => { + const pageKey = generatePageKey(permalink); const results = await db .select({ like: atk_pages.vote_up, view: atk_pages.pv }) .from(atk_pages) diff --git a/src/layouts/PageLayout.astro b/src/layouts/PageLayout.astro index 2bbb54c..b92f82f 100644 --- a/src/layouts/PageLayout.astro +++ b/src/layouts/PageLayout.astro @@ -1,8 +1,9 @@ --- import Comments from '@/components/comment/Comments.astro'; import Image from '@/components/image/Image.astro'; +import LikeButton from '@/components/like/LikeButton.astro'; import PageMeta from '@/components/meta/PageMeta.astro'; -import FriendLinks from '@/components/page/friend/FriendLinks.astro'; +import Friends from '@/components/page/friend/Friends.astro'; import MusicPlayer from '@/components/player/MusicPlayer.astro'; import type { Page } from '@/helpers/schema'; import { urlJoin } from '@/helpers/tools'; @@ -29,7 +30,8 @@ const { Content } = await page.render();
    - {page.friend && } + {page.friend && } + {page.comments && }
    diff --git a/src/layouts/PostLayout.astro b/src/layouts/PostLayout.astro index 6345c84..2f0fc9b 100644 --- a/src/layouts/PostLayout.astro +++ b/src/layouts/PostLayout.astro @@ -2,7 +2,7 @@ import Comments from '@/components/comment/Comments.astro'; import Image from '@/components/image/Image.astro'; import LikeButton from '@/components/like/LikeButton.astro'; -import Share from '@/components/like/Share.astro'; +import LikeShare from '@/components/like/LikeShare.astro'; import PostMeta from '@/components/meta/PostMeta.astro'; import MusicPlayer from '@/components/player/MusicPlayer.astro'; import Sidebar from '@/components/sidebar/Sidebar.astro'; @@ -48,8 +48,8 @@ const { Content } = await post.render(); - - + + { post.comments && ( diff --git a/src/pages/comments/avatar.ts b/src/pages/comments/avatar.ts deleted file mode 100644 index 8f2aedb..0000000 --- a/src/pages/comments/avatar.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { getConfig } from '@/components/comment/artalk'; -import { urlJoin } from '@/helpers/tools'; -import type { APIRoute } from 'astro'; -import crypto from 'node:crypto'; - -export const GET: APIRoute = async ({ url }) => { - const email = url.searchParams.get('email'); - if (email == null) { - return new Response(''); - } - - const config = await getConfig(); - if (config === null) { - return new Response(''); - } - - // Decode the email into Gravatar hash. - const hash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex'); - return new Response(urlJoin(config.frontend_conf.gravatar.mirror, `${hash}?d=mm&s=80`)); -}; diff --git a/src/pages/comments/list.ts b/src/pages/comments/list.ts deleted file mode 100644 index a7b3dba..0000000 --- a/src/pages/comments/list.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getConfig, loadComments } from '@/components/comment/artalk'; -import Comment from '@/components/comment/Comment.astro'; -import { partialRender } from '@/helpers/container'; -import type { APIRoute } from 'astro'; - -export const GET: APIRoute = async ({ url }) => { - const key = url.searchParams.get('key'); - if (key == null) { - return new Response(''); - } - - const offset = url.searchParams.get('offset'); - if (offset == null) { - return new Response(''); - } - - const config = await getConfig(); - if (config === null) { - return new Response(''); - } - - const comments = await loadComments(key, null, Number(offset), config); - if (comments === null) { - return new Response(''); - } - - const html = await partialRender(Comment, { props: { comments: comments, config: config } }); - return new Response(html); -}; diff --git a/src/pages/comments/new.ts b/src/pages/comments/new.ts deleted file mode 100644 index ac1d31c..0000000 --- a/src/pages/comments/new.ts +++ /dev/null @@ -1,21 +0,0 @@ -import CommentItem from '@/components/comment/CommentItem.astro'; -import { createComment, getConfig } from '@/components/comment/artalk'; -import type { CommentReq } from '@/components/comment/types'; -import { partialRender } from '@/helpers/container'; -import type { APIRoute } from 'astro'; - -export const POST: APIRoute = async ({ request }) => { - const body = (await request.json()) as CommentReq; - const resp = await createComment(body); - - if ('msg' in resp) { - return new Response(`
  • ${resp.msg}
  • `); - } - - const config = await getConfig(); - const content = await partialRender(CommentItem, { - props: { depth: 2, comment: resp, pending: resp.is_pending, config: config }, - }); - - return new Response(content); -}; diff --git a/src/pages/posts/[slug]/likes.ts b/src/pages/posts/[slug]/likes.ts deleted file mode 100644 index a6ab649..0000000 --- a/src/pages/posts/[slug]/likes.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { decreaseLikes, increaseLikes, queryLikes } from '@/helpers/db/query'; -import type { APIRoute } from 'astro'; - -export const POST: APIRoute = async ({ params, request }) => { - const { slug } = params; - const resp = await request.json(); - - // Increase. - if (resp.action === 'increase') { - if (typeof slug === 'undefined') { - return Response.json({ likes: 0, token: '' }); - } - - const { likes, token } = await increaseLikes(slug); - return Response.json({ likes: likes, token: token }); - } - - // Decrease. - if (resp.action === 'decrease' && resp.token !== '') { - if (typeof slug === 'undefined') { - return Response.json({ likes: 0 }); - } - - await decreaseLikes(slug, resp.token); - const likes = await queryLikes(slug); - return Response.json({ likes: likes }); - } - - return Response.json({ likes: 0 }); -};