diff --git a/.vscode/settings.json b/.vscode/settings.json index b0d60c1..ca847d7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,10 @@ ], "eslint.experimental.useFlatConfig": true, "cSpell.words": [ + "ical", "Logto", - "qrcode" + "qrcode", + "Subcomponents", + "vevent" ], } diff --git a/astro.config.mts b/astro.config.mts index 38a9ef1..5b23eda 100644 --- a/astro.config.mts +++ b/astro.config.mts @@ -1,23 +1,26 @@ -import { defineConfig } from "astro/config" -import { SITE_URL } from "./src/consts" -import vue from "@astrojs/vue" -import tailwind from "@astrojs/tailwind" -import react from "@astrojs/react" -import { handleLocalCoverPlugin } from "./src/plugins/cover" -import { themePipeline } from "./src/plugins/theme" +import { defineConfig } from "astro/config"; +import { SITE_URL } from "./src/consts"; +import vue from "@astrojs/vue"; +import tailwind from "@astrojs/tailwind"; +import react from "@astrojs/react"; +import { handleLocalCoverPlugin } from "./src/plugins/cover"; +import { themePipeline } from "./src/plugins/theme"; +import remarkToc from "remark-toc"; +import rehypeSlug from "rehype-slug"; // https://astro.build/config export default defineConfig({ site: SITE_URL, markdown: { - rehypePlugins: [handleLocalCoverPlugin, ...themePipeline], + remarkPlugins: [[remarkToc, { heading: "目录", tight: true }]], + rehypePlugins: [rehypeSlug, handleLocalCoverPlugin, ...themePipeline], syntaxHighlight: "shiki", shikiConfig: { themes: { - light: 'github-light', - dark: 'github-dark', + light: "github-light", + dark: "github-dark", }, - } + }, }, integrations: [ vue(), @@ -33,13 +36,13 @@ export default defineConfig({ "/active": { target: "https://active.nbtca.space", changeOrigin: true, - rewrite: path => path.replace(/^\/active/, ""), + rewrite: (path) => path.replace(/^\/active/, ""), }, "/saturday": { target: "http://localhost:4000", - rewrite: path => path.replace(/^\/saturday/, ""), + rewrite: (path) => path.replace(/^\/saturday/, ""), }, - } - } - } -}) + }, + }, + }, +}); diff --git a/cspell.json b/cspell.json index d16f8f1..efe88c3 100644 --- a/cspell.json +++ b/cspell.json @@ -21,6 +21,10 @@ "Rehype", "rehypePlugins", "shiki", - "tseslint" + "tseslint", + "ical", + "ICAL", + "vevents", + "getAllSubcomponents" ] -} +} \ No newline at end of file diff --git a/package.json b/package.json index 3a9f32e..60b6338 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,15 @@ "astro": "^4.16.18", "dayjs": "^1.11.13", "framer-motion": "^11.18.2", - "ical.js": "^1.5.0", + "ical.js": "^2.1.0", "md5": "^2.3.0", "openapi-fetch": "^0.12.5", "qrcode": "^1.5.4", "react": "^18.3.1", "react-dom": "^18.3.1", "rehype": "^13.0.2", + "rehype-slug": "^6.0.0", + "remark-toc": "^9.0.0", "sharp": "^0.33.5", "tailwindcss": "^3.4.17", "unist-util-visit": "^5.0.0", @@ -49,6 +51,7 @@ "@cspell/eslint-plugin": "^8.19.2", "@eslint/js": "^9.25.1", "@hey-api/openapi-ts": "^0.53.12", + "@tailwindcss/typography": "^0.5.16", "@types/eslint__js": "^8.42.3", "@types/md5": "^2.3.5", "@types/qrcode": "^1.5.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0197441..e26975f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 6.1.17(@fullcalendar/core@6.1.17) '@fullcalendar/icalendar': specifier: ^6.1.17 - version: 6.1.17(@fullcalendar/core@6.1.17)(ical.js@1.5.0) + version: 6.1.17(@fullcalendar/core@6.1.17)(ical.js@2.1.0) '@fullcalendar/react': specifier: ^6.1.17 version: 6.1.17(@fullcalendar/core@6.1.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -60,8 +60,8 @@ importers: specifier: ^11.18.2 version: 11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ical.js: - specifier: ^1.5.0 - version: 1.5.0 + specifier: ^2.1.0 + version: 2.1.0 md5: specifier: ^2.3.0 version: 2.3.0 @@ -80,6 +80,12 @@ importers: rehype: specifier: ^13.0.2 version: 13.0.2 + rehype-slug: + specifier: ^6.0.0 + version: 6.0.0 + remark-toc: + specifier: ^9.0.0 + version: 9.0.0 sharp: specifier: ^0.33.5 version: 0.33.5 @@ -108,6 +114,9 @@ importers: '@hey-api/openapi-ts': specifier: ^0.53.12 version: 0.53.12(magicast@0.3.5)(typescript@5.8.3) + '@tailwindcss/typography': + specifier: ^0.5.16 + version: 0.5.16(tailwindcss@3.4.17) '@types/eslint__js': specifier: ^8.42.3 version: 8.42.3 @@ -2371,6 +2380,11 @@ packages: '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@tailwindcss/typography@0.5.16': + resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + '@tanstack/react-virtual@3.11.3': resolution: {integrity: sha512-vCU+OTylXN3hdC8RKg68tPlBPjjxtzon7Ys46MgrSLE+JhSjSTPvoQifV6DQJeJmA8Q3KT6CphJbejupx85vFw==} peerDependencies: @@ -2462,6 +2476,9 @@ packages: '@types/react@18.3.23': resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==} + '@types/ungap__structured-clone@1.2.0': + resolution: {integrity: sha512-ZoaihZNLeZSxESbk9PUAPZOlSpcKx81I1+4emtULDVmBLkYutTcMlCj2K9VNlf9EWODxdO6gkAqEaLorXwZQVA==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -2877,11 +2894,11 @@ packages: resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} engines: {node: '>=18'} - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -3752,6 +3769,9 @@ packages: hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + hast-util-heading-rank@3.0.0: + resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + hast-util-is-element@3.0.0: resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} @@ -3767,6 +3787,9 @@ packages: hast-util-to-parse5@8.0.0: resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + hast-util-to-text@4.0.2: resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} @@ -3805,8 +3828,8 @@ packages: engines: {node: '>=18'} hasBin: true - ical.js@1.5.0: - resolution: {integrity: sha512-7ZxMkogUkkaCx810yp0ZGKvq1ZpRgJeornPttpoxe6nYZ3NLesZe1wWMXDdwTkj/b5NtXT+Y16Aakph/ao98ZQ==} + ical.js@2.1.0: + resolution: {integrity: sha512-BOVfrH55xQ6kpS3muGvIXIg2l7p+eoe12/oS7R5yrO3TL/j/bLsR0PR+tYQESFbyTbvGgPHn9zQ6tI4FWyuSaQ==} ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} @@ -4139,6 +4162,12 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -4222,6 +4251,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdast-util-toc@7.1.0: + resolution: {integrity: sha512-2TVKotOQzqdY7THOdn2gGzS9d1Sdd66bvxUyw3aNpWfcPXCLYSJCCgfPy30sEtuzkDraJgqF35dzgmz6xlvH/w==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -4680,6 +4712,10 @@ packages: peerDependencies: postcss: ^8.2.14 + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} @@ -4799,6 +4835,9 @@ packages: rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + rehype-slug@6.0.0: + resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} + rehype-stringify@10.0.1: resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} @@ -4821,6 +4860,9 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remark-toc@9.0.0: + resolution: {integrity: sha512-KJ9txbo33GjDAV1baHFze7ij4G8c7SGYoY8Kzsm2gzFpbhL/bSoVpMMzGa3vrNDSWASNd/3ppAqL7cP2zD6JIA==} + repeat-string@1.6.1: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} engines: {node: '>=0.10'} @@ -6260,10 +6302,10 @@ snapshots: dependencies: '@fullcalendar/core': 6.1.17 - '@fullcalendar/icalendar@6.1.17(@fullcalendar/core@6.1.17)(ical.js@1.5.0)': + '@fullcalendar/icalendar@6.1.17(@fullcalendar/core@6.1.17)(ical.js@2.1.0)': dependencies: '@fullcalendar/core': 6.1.17 - ical.js: 1.5.0 + ical.js: 2.1.0 '@fullcalendar/react@6.1.17(@fullcalendar/core@6.1.17)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -8772,6 +8814,14 @@ snapshots: dependencies: tslib: 2.8.1 + '@tailwindcss/typography@0.5.16(tailwindcss@3.4.17)': + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.17 + '@tanstack/react-virtual@3.11.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/virtual-core': 3.11.3 @@ -8875,6 +8925,8 @@ snapshots: '@types/prop-types': 15.7.14 csstype: 3.1.3 + '@types/ungap__structured-clone@1.2.0': {} + '@types/unist@3.0.3': {} '@types/uuid@10.0.0': {} @@ -9463,12 +9515,12 @@ snapshots: widest-line: 5.0.0 wrap-ansi: 9.0.0 - brace-expansion@1.1.11: + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 @@ -10475,6 +10527,10 @@ snapshots: vfile-location: 5.0.3 web-namespaces: 2.0.1 + hast-util-heading-rank@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-is-element@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -10523,6 +10579,10 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-text@4.0.2: dependencies: '@types/hast': 3.0.4 @@ -10563,7 +10623,7 @@ snapshots: husky@9.1.7: {} - ical.js@1.5.0: {} + ical.js@2.1.0: {} ignore@5.3.2: {} @@ -10871,6 +10931,10 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.castarray@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + lodash.merge@4.6.2: {} log-symbols@6.0.0: @@ -11040,6 +11104,16 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdast-util-toc@7.1.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/ungap__structured-clone': 1.2.0 + '@ungap/structured-clone': 1.3.0 + github-slugger: 2.0.0 + mdast-util-to-string: 4.0.0 + unist-util-is: 6.0.0 + unist-util-visit: 5.0.0 + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -11246,15 +11320,15 @@ snapshots: minimatch@3.1.2: dependencies: - brace-expansion: 1.1.11 + brace-expansion: 1.1.12 minimatch@5.1.6: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist@1.2.8: {} @@ -11582,6 +11656,11 @@ snapshots: postcss: 8.5.3 postcss-selector-parser: 6.1.2 + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 @@ -11718,6 +11797,14 @@ snapshots: hast-util-raw: 9.1.0 vfile: 6.0.3 + rehype-slug@6.0.0: + dependencies: + '@types/hast': 3.0.4 + github-slugger: 2.0.0 + hast-util-heading-rank: 3.0.0 + hast-util-to-string: 3.0.1 + unist-util-visit: 5.0.0 + rehype-stringify@10.0.1: dependencies: '@types/hast': 3.0.4 @@ -11772,6 +11859,11 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + remark-toc@9.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-toc: 7.1.0 + repeat-string@1.6.1: {} require-directory@2.1.1: {} diff --git a/src/components/header/HeaderNavigation.tsx b/src/components/header/HeaderNavigation.tsx index dd09c96..4733422 100644 --- a/src/components/header/HeaderNavigation.tsx +++ b/src/components/header/HeaderNavigation.tsx @@ -13,7 +13,7 @@ export default function App() { }, { link: "/calendar", - name: "日历", + name: "日程", }, { link: "/about", diff --git a/src/layouts/MarkdownPost.astro b/src/layouts/MarkdownPost.astro index 581a364..c211a25 100644 --- a/src/layouts/MarkdownPost.astro +++ b/src/layouts/MarkdownPost.astro @@ -76,7 +76,9 @@ const dateFormatted = formatDate(pubDate) - +
+ +
@@ -88,7 +90,13 @@ const dateFormatted = formatDate(pubDate)

- 作者: {frontmatter.author} 发表日期:{dateFormatted} + 作者: + { + typeof frontmatter.author === "object" + ? {frontmatter.author.name} + : frontmatter.author + } + ,发表日期:{dateFormatted}

diff --git a/src/pages/calendar/Schedule.tsx b/src/pages/calendar/Schedule.tsx new file mode 100644 index 0000000..52d758d --- /dev/null +++ b/src/pages/calendar/Schedule.tsx @@ -0,0 +1,335 @@ +import ICAL from "ical.js" +import dayjs from "dayjs" +import { useEffect, useMemo, useRef, useState } from "react" +import { Alert, Button, Calendar, Link, Spinner, Select, SelectItem } from "@heroui/react" +import { today, getLocalTimeZone } from "@internationalized/date" + +import "dayjs/locale/zh-cn" + +dayjs.locale("zh-cn") + +type ScheduleEvent = { + start: Date + end: Date + summary: string + description: string + recurrenceId?: string +} + +const parseCal = async (): Promise => { + const res = await fetch("https://ical.nbtca.space/").then(res => res.text()) + const jcalData = ICAL.parse(res) + return new ICAL.Component(jcalData) +} + +const extractScheduleEventsInRange = ( + icalComp: ICAL.Component, + rangeStart: ICAL.Time, + rangeEnd: ICAL.Time, +): ScheduleEvent[] => { + const vevents = icalComp.getAllSubcomponents("vevent") + return vevents + .flatMap((vevent) => { + const event = new ICAL.Event(vevent) + if (!event.isRecurring()) { + if (event.startDate.compare(rangeEnd) > 0 || event.endDate.compare(rangeStart) < 0) return [] + return [{ + start: event.startDate.toJSDate(), + end: event.endDate.toJSDate(), + summary: event.summary, + description: event.description, + recurrenceId: undefined, + }] + } + return expandEventOccurrences(event, rangeStart, rangeEnd) + }) + .sort((a, b) => b.start.getTime() - a.start.getTime()) +} + +const expandEventOccurrences = ( + event: ICAL.Event, + rangeStart: ICAL.Time, + rangeEnd: ICAL.Time, +): ScheduleEvent[] => { + const occurrences: ScheduleEvent[] = [] + const iterator = event.iterator() + let next: ICAL.Time | null = null + while ((next = iterator.next())) { + if (!next) break + if (next.compare(rangeEnd) > 0) break + if (next.compare(rangeStart) < 0) continue + const details = event.getOccurrenceDetails(next) + occurrences.push({ + start: details.startDate.toJSDate(), + end: details.endDate.toJSDate(), + summary: event.summary, + description: event.description, + recurrenceId: next.toString(), + }) + } + return occurrences +} + +const extractScheduleEvents = (icalComp: ICAL.Component): ScheduleEvent[] => { + const vevents = icalComp.getAllSubcomponents("vevent") + const rangeEnd = ICAL.Time.fromDateString("2026-01-01") + + return vevents + .flatMap((vevent) => { + const event = new ICAL.Event(vevent) + if (event.iterator().complete) { + return [{ + start: event.startDate.toJSDate(), + end: event.endDate.toJSDate(), + summary: event.summary, + description: event.description, + recurrenceId: event.uid || event.startDate.toString(), + + }] + } + return expandEventOccurrences(event, rangeEnd) + }) + .sort((a, b) => b.start.getTime() - a.start.getTime()) +} + +const formatTimePair = (s: Date, e: Date): string => { + const start = dayjs(s) + const end = dayjs(e) + if (start.isSame(end, "day")) { + return `${start.format("HH:mm")} - ${end.format("HH:mm")}` + } + return `${start.format("MM.DD")} - ${end.format("MM.DD")}` +} + +export default function Schedule() { + const [scheduledEvents, setScheduledEvents] = useState([]) + + const defaultDate = today(getLocalTimeZone()) + const [focusedDate, setFocusedDate] = useState(defaultDate) + const dateRefs = useRef>({}) + const calendarRef = useRef(null) + const [loading, setLoading] = useState(true) + + const currentYear = focusedDate.year + const currentMonth = focusedDate.month + + const years = Array.from({ length: 10 }, (_, i) => currentYear - 5 + i) + const months = Array.from({ length: 12 }, (_, i) => i + 1) + + useEffect(() => { + const rangeStart = ICAL.Time.fromJSDate(dayjs(focusedDate.toDate(getLocalTimeZone())).startOf("month").toDate()) + const rangeEnd = ICAL.Time.fromJSDate(dayjs(focusedDate.toDate(getLocalTimeZone())).endOf("month").toDate()) + if (!calendarRef.current) { + setLoading(true) + parseCal().then((icalComp) => { + calendarRef.current = icalComp + const events = extractScheduleEventsInRange( + icalComp, + rangeStart, + rangeEnd, + ) + setScheduledEvents(events) + setLoading(false) + }) + } + else { + const events = extractScheduleEventsInRange( + calendarRef.current, + rangeStart, + rangeEnd, + ) + setScheduledEvents(events) + } + }, [focusedDate]) + + useEffect(() => { + setLoading(true) + parseCal().then((icalComp) => { + setScheduledEvents(extractScheduleEvents(icalComp)) + setLoading(false) + }) + }, []) + + const groupedEvents = useMemo(() => { + const grouped = new Map() + scheduledEvents.forEach((event) => { + const dateKey = dayjs(event.start).format("YYYY-MM-DD") + if (!grouped.has(dateKey)) { + grouped.set(dateKey, []) + } + grouped.get(dateKey)!.push(event) + }) + + // 可选:对每个日期内部事件排序(按开始时间升序) + for (const events of grouped.values()) { + events.sort((a, b) => a.start.getTime() - b.start.getTime()) + } + + // 可选:按照日期从早到晚排序 + return Array.from(grouped.entries()).sort(([a], [b]) => a.localeCompare(b)) + }, [scheduledEvents]) + + const isUserChangeRef = useRef(false) + + useEffect(() => { + if (!isUserChangeRef.current) return + isUserChangeRef.current = false + + const dateKey = dayjs(focusedDate.toDate(getLocalTimeZone())).format("YYYY-MM-DD") + const target = dateRefs.current[dateKey] + if (target) { + target.scrollIntoView({ behavior: "smooth", block: "start" }) + } + else { + // to nearest date if not found + const dates = Object.keys(dateRefs.current).map(d => dayjs(d)) + const closestDate = dates.reduce((prev, curr) => { + return Math.abs(curr.diff(focusedDate.toDate(getLocalTimeZone()), "day")) < Math.abs(prev.diff(focusedDate.toDate(getLocalTimeZone()), "day")) ? curr : prev + }, dates[0]) + const closestKey = closestDate.format("YYYY-MM-DD") + const closestTarget = dateRefs.current[closestKey] + if (closestTarget) { + closestTarget.scrollIntoView({ behavior: "smooth", block: "start" }) + } + } + }, [focusedDate, groupedEvents]) + + return ( +
+
+
日程
+ + {/* Mobile Date Selector */} +
+
+ + +
+
+ console.log(e)} + href="webcal://ical.nbtca.space" + target="_blank" + > + 订阅 + + )} + /> +
+
+
+ { + dayjs(focusedDate.toDate()).format("MMMM YYYY") + } +
+
+ {loading && ( +
+ +
+ )} + {groupedEvents.length === 0 && !loading && ( +
本月暂无日程
+ )} + { + groupedEvents.map(([date, events]) => ( +
dateRefs.current[date] = el} + className="mb-6" + > +
+ { dayjs(date).format("MM.DD") } + { dayjs(date).format("ddd") } +
+ {events.map((event, index) => ( +
+
+
{event.summary}
+ {event.recurrenceId && ( + // 重复 + + + + + )} +
+ {formatTimePair(event.start, event.end)} + { + event.description && ( +

{event.description}

+ ) + } +
+ ))} +
+ )) + } +
+
+
+ { + isUserChangeRef.current = true + setFocusedDate(v) + }} + /> +
+
+
+
+
+ ) +} diff --git a/src/pages/calendar/index.astro b/src/pages/calendar/index.astro index 9716dd6..78e7fe8 100644 --- a/src/pages/calendar/index.astro +++ b/src/pages/calendar/index.astro @@ -1,8 +1,8 @@ --- -import FullCalendar from "./Calendar" +import Schedule from "./Schedule" import BaseLayout from "../../layouts/BaseLayout.astro" --- - + diff --git a/src/pages/join-us.astro b/src/pages/join-us.astro index 41f13da..148c13f 100644 --- a/src/pages/join-us.astro +++ b/src/pages/join-us.astro @@ -121,7 +121,7 @@ import logoAnimated from "./_assets/nbtca.gif"; -webkit-font-smoothing: antialiased; direction: ltr; font-weight: 400; - font-size: 17px; + font-size: 16px; line-height: 1.82353; --body-container-width: 645px; text-align: center; diff --git a/src/pages/posts/minecraftMac.md b/src/pages/posts/minecraftMac.md new file mode 100644 index 0000000..4da0047 --- /dev/null +++ b/src/pages/posts/minecraftMac.md @@ -0,0 +1,88 @@ +--- +layout: "../../layouts/MarkdownPost.astro" +title: "在Apple Silicon Mac上使用Minecraft光影和模组" +pubDate: 2025-06-10 +description: "🤪" +author: + name: "小明" + url: "https://m1ng.space/" +cover: + url: "https://i.pinimg.com/736x/6f/00/d7/6f00d73e426549d0f5b1bb873bf2015f.jpg" + alt: "cover" +tags: ["指南"] +--- + +# 简介 + +> 在本指南中,我们将向您展示如何使用Iris Shaders模组在Mac上为Minecraft添加光影和模组。这种方法可以增强Mac上Minecraft的视觉效果和游戏体验,包括使用Apple Silicon芯片的Mac。我们将介绍安装过程,确保您能获得视觉效果出众且运行流畅的Minecraft体验。无论您是刚接触模组和光影,还是想要升级您的Minecraft设置,这个简明的指南都能满足您的需求。 + +您需要的所有链接都在这里: + +| content | link | +| :------------: | :------------------------------------: | +| Minecraft Java | | +| Fabric | | +| Fabric API | | +| Sodium | | +| Iris | | + +## 目录 + +## 1. 安装Fabric + +第一步是安装Fabric加载器。前往 + +并点击Download installer (Universal/.JAR)下载fabric。 + +双击.jar包安装Fabric加载器。根据您的情况安装特定版本或最新版本。 + +## 2. 安装必要的模组 + +安装Fabric加载器后,打开Minecraft启动器。您会发现有一个fabri-loader-1.XX. +X,只需点击开始游戏下载一些必要的包。 + +如果一切正确,您就会进入游戏。现在关闭游戏并前往**/Library/Application Support/minecraft**。 + +您需要创建两个新文件夹,一个叫mods(在这里放模组)和一个叫shaderpacks(在这里放光影)。 + +现在您需要下载: + +- [Fabric API](https://modrinth.com/mod/fabric-api/) + + > Fabric API是一个极简的Minecraft模组工具链,提供了一个灵活的框架来创建模组。以其快速更新和与新Minecraft版本的兼容性而闻名,它支持客户端和服务器端模组,使其成为模组社区中的热门选择。 + +- [Sodium](https://modrinth.com/mod/sodium) + + > Sodium是一个Minecraft性能优化模组,可以显著提高游戏的帧率并减少延迟。它设计用于与Fabric模组加载器一起工作,专注于优化游戏的渲染引擎。通过重写游戏图形引擎的关键部分,Sodium提供了更流畅的游戏体验,特别是在低端硬件上。它以能够在不影响游戏视觉质量的情况下提升性能而备受好评,是希望改善Minecraft体验的玩家的热门选择。 + +- [Iris](https://modrinth.com/mod/iris) + > Iris是一个用于在Fabric上使用光影的Minecraft模组,提供与Sodium的兼容性以增强性能和视觉效果。它简化了光影管理,允许高级照明和效果。 + > 将下载的文件放入`mods`文件夹。 + +```bash +➜ mods tree +. +├── fabric-api-0.102.0+1.21.jar +├── iris-fabric-1.8.8+mc1.21.1.jar +├── litematica-fabric-1.21-0.19.58.jar +├── malilib-fabric-1.21-0.21.8.jar +├── reeses-sodium-options-fabric-1.8.3+mc1.21.4.jar +├── sodium-extra-fabric-0.6.0+mc1.21.1.jar +└── sodium-fabric-0.6.13+mc1.21.1.jar + +1 directory, 7 files +``` + +下载这些模组后,打开游戏检查是否一切正常。如果有错误,可能是因为Mac击支持或版本不兼容。选择正确的版本下载。 + +## 3. 下载您的光影包 + +前往 下载光影。您需要注意这些光影是否支持Mac。 + +将下载的光影放入shaderpacks。打开游戏并点击选项–>视频设置–>光影包,现在您可以看到您的光影了。 + +然后在列表中点击您想要的光影包,点击应用,然后完成。 + +## 4. 享受游戏 + +恭喜您,尽情享受游戏吧。 diff --git a/src/styles/global.css b/src/styles/global.css index e05d037..c89fa63 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -149,12 +149,12 @@ button:disabled { html { font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica", "Arial", sans-serif; - font-size: 106.25%; + /* font-size: 106.25%; */ quotes: "“" "”"; } body { - font-size: 17px; + /* font-size: 17px; */ line-height: 1.47059; font-weight: 400; letter-spacing: -0.022em; diff --git a/tailwind.config.mjs b/tailwind.config.mjs index 2258176..459e532 100644 --- a/tailwind.config.mjs +++ b/tailwind.config.mjs @@ -1,7 +1,12 @@ import { heroui } from "@heroui/react" +import typography from "@tailwindcss/typography" + /** @type {import('tailwindcss').Config} */ export default { - content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}", "./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}"], + content: [ + "./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}", + "./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}", + ], theme: { screens: { xs: "416px", @@ -16,5 +21,5 @@ export default { }, }, darkMode: ["selector", "body.theme-dark"], - plugins: [heroui()], + plugins: [heroui(), typography()], }