mirror of
https://github.com/m1ngsama/FUJI.git
synced 2025-12-25 02:56:38 +00:00
Merge branch 'main' into refactor-blog
This commit is contained in:
commit
ec83bcccd7
16 changed files with 1302 additions and 520 deletions
3
.github/workflows/main.yml
vendored
3
.github/workflows/main.yml
vendored
|
|
@ -1,4 +1,7 @@
|
||||||
name: main
|
name: main
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
|
||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -8,7 +8,10 @@
|
||||||
],
|
],
|
||||||
"eslint.experimental.useFlatConfig": true,
|
"eslint.experimental.useFlatConfig": true,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"ical",
|
||||||
"Logto",
|
"Logto",
|
||||||
"qrcode"
|
"qrcode",
|
||||||
|
"Subcomponents",
|
||||||
|
"vevent"
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,26 @@
|
||||||
import { defineConfig } from "astro/config"
|
import { defineConfig } from "astro/config";
|
||||||
import { SITE_URL } from "./src/consts"
|
import { SITE_URL } from "./src/consts";
|
||||||
import vue from "@astrojs/vue"
|
import vue from "@astrojs/vue";
|
||||||
import tailwind from "@astrojs/tailwind"
|
import tailwind from "@astrojs/tailwind";
|
||||||
import react from "@astrojs/react"
|
import react from "@astrojs/react";
|
||||||
import { handleLocalCoverPlugin } from "./src/plugins/cover"
|
import { handleLocalCoverPlugin } from "./src/plugins/cover";
|
||||||
import { themePipeline } from "./src/plugins/theme"
|
import { themePipeline } from "./src/plugins/theme";
|
||||||
|
import remarkToc from "remark-toc";
|
||||||
|
import rehypeSlug from "rehype-slug";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: SITE_URL,
|
site: SITE_URL,
|
||||||
markdown: {
|
markdown: {
|
||||||
rehypePlugins: [handleLocalCoverPlugin, ...themePipeline],
|
remarkPlugins: [[remarkToc, { heading: "目录", tight: true }]],
|
||||||
|
rehypePlugins: [rehypeSlug, handleLocalCoverPlugin, ...themePipeline],
|
||||||
syntaxHighlight: "shiki",
|
syntaxHighlight: "shiki",
|
||||||
shikiConfig: {
|
shikiConfig: {
|
||||||
themes: {
|
themes: {
|
||||||
light: 'github-light',
|
light: "github-light",
|
||||||
dark: 'github-dark',
|
dark: "github-dark",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
integrations: [
|
integrations: [
|
||||||
vue(),
|
vue(),
|
||||||
|
|
@ -33,13 +36,13 @@ export default defineConfig({
|
||||||
"/active": {
|
"/active": {
|
||||||
target: "https://active.nbtca.space",
|
target: "https://active.nbtca.space",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: path => path.replace(/^\/active/, ""),
|
rewrite: (path) => path.replace(/^\/active/, ""),
|
||||||
},
|
},
|
||||||
"/saturday": {
|
"/saturday": {
|
||||||
target: "http://localhost:4000",
|
target: "http://localhost:4000",
|
||||||
rewrite: path => path.replace(/^\/saturday/, ""),
|
rewrite: (path) => path.replace(/^\/saturday/, ""),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,10 @@
|
||||||
"Rehype",
|
"Rehype",
|
||||||
"rehypePlugins",
|
"rehypePlugins",
|
||||||
"shiki",
|
"shiki",
|
||||||
"tseslint"
|
"tseslint",
|
||||||
|
"ical",
|
||||||
|
"ICAL",
|
||||||
|
"vevents",
|
||||||
|
"getAllSubcomponents"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -28,16 +28,18 @@
|
||||||
"@logto/browser": "^2.2.18",
|
"@logto/browser": "^2.2.18",
|
||||||
"@react-stately/data": "^3.13.0",
|
"@react-stately/data": "^3.13.0",
|
||||||
"@stylistic/eslint-plugin": "^2.13.0",
|
"@stylistic/eslint-plugin": "^2.13.0",
|
||||||
"astro": "^4.16.18",
|
"astro": "^4.16.19",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"framer-motion": "^11.18.2",
|
"framer-motion": "^11.18.2",
|
||||||
"ical.js": "^1.5.0",
|
"ical.js": "^2.1.0",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"openapi-fetch": "^0.12.5",
|
"openapi-fetch": "^0.12.5",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"rehype": "^13.0.2",
|
"rehype": "^13.0.2",
|
||||||
|
"rehype-slug": "^6.0.0",
|
||||||
|
"remark-toc": "^9.0.0",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
|
|
@ -49,6 +51,7 @@
|
||||||
"@cspell/eslint-plugin": "^8.19.2",
|
"@cspell/eslint-plugin": "^8.19.2",
|
||||||
"@eslint/js": "^9.25.1",
|
"@eslint/js": "^9.25.1",
|
||||||
"@hey-api/openapi-ts": "^0.53.12",
|
"@hey-api/openapi-ts": "^0.53.12",
|
||||||
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@types/eslint__js": "^8.42.3",
|
"@types/eslint__js": "^8.42.3",
|
||||||
"@types/md5": "^2.3.5",
|
"@types/md5": "^2.3.5",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
|
|
|
||||||
1050
pnpm-lock.yaml
1050
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@ export default function App() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: "/calendar",
|
link: "/calendar",
|
||||||
name: "日历",
|
name: "日程",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
link: "/about",
|
link: "/about",
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,21 @@ import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"
|
||||||
const { primaryTitle } = Astro.props
|
const { primaryTitle } = Astro.props
|
||||||
const title = primaryTitle ? `${primaryTitle} - ${SITE_TITLE}` : SITE_TITLE;
|
const title = primaryTitle ? `${primaryTitle} - ${SITE_TITLE}` : SITE_TITLE;
|
||||||
---
|
---
|
||||||
|
|
||||||
<html class="js no-touch progressive-image no-reduced-motion progressive" lang="zh-CN" dir="ltr">
|
<html class="js no-touch progressive-image no-reduced-motion progressive" lang="zh-CN" dir="ltr">
|
||||||
<head>
|
<head>
|
||||||
<BaseHead title={title} description={SITE_DESCRIPTION} />
|
<BaseHead title={title} description={SITE_DESCRIPTION} />
|
||||||
|
<script type="text/javascript" is:inline>
|
||||||
|
(function (c, l, a, r, i, t, y) {
|
||||||
|
c[a] = c[a] || function () {
|
||||||
|
(c[a].q = c[a].q || []).push(arguments)
|
||||||
|
}
|
||||||
|
t = l.createElement(r)
|
||||||
|
t.async = 1
|
||||||
|
t.src = "https://www.clarity.ms/tag/" + i
|
||||||
|
y = l.getElementsByTagName(r)[0]
|
||||||
|
y.parentNode.insertBefore(t, y)
|
||||||
|
})(window, document, "clarity", "script", "tcqhxia97o")
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="flex flex-col min-h-screen">
|
<body class="flex flex-col min-h-screen">
|
||||||
<Header />
|
<Header />
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,9 @@ const dateFormatted = formatDate(pubDate)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<div class="prose prose-neutral dark:prose-invert max-w-none">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
<div class="component">
|
<div class="component">
|
||||||
<div class="component-content">
|
<div class="component-content">
|
||||||
<div class="article-copyright">
|
<div class="article-copyright">
|
||||||
|
|
@ -88,7 +90,13 @@ const dateFormatted = formatDate(pubDate)
|
||||||
</a
|
</a
|
||||||
>
|
>
|
||||||
<p class="content">
|
<p class="content">
|
||||||
作者: {frontmatter.author} 发表日期:{dateFormatted}
|
作者:
|
||||||
|
{
|
||||||
|
typeof frontmatter.author === "object"
|
||||||
|
? <a href={frontmatter.author.url} target="_blank" rel="noopener">{frontmatter.author.name}</a>
|
||||||
|
: frontmatter.author
|
||||||
|
}
|
||||||
|
,发表日期:{dateFormatted}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
409
src/pages/calendar/Schedule.tsx
Normal file
409
src/pages/calendar/Schedule.tsx
Normal file
|
|
@ -0,0 +1,409 @@
|
||||||
|
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<ICAL.Component> => {
|
||||||
|
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")
|
||||||
|
|
||||||
|
// First, collect all exception events (events with RECURRENCE-ID)
|
||||||
|
const exceptions = new Map<string, Set<string>>() // uid -> set of recurrence-id strings
|
||||||
|
const exceptionEvents: ScheduleEvent[] = []
|
||||||
|
|
||||||
|
vevents.forEach((vevent) => {
|
||||||
|
const recurrenceId = vevent.getFirstPropertyValue("recurrence-id")
|
||||||
|
if (recurrenceId) {
|
||||||
|
const uid = vevent.getFirstPropertyValue("uid")
|
||||||
|
const event = new ICAL.Event(vevent)
|
||||||
|
|
||||||
|
// Check if exception is in range
|
||||||
|
if (event.startDate.compare(rangeEnd) <= 0 && event.endDate.compare(rangeStart) >= 0) {
|
||||||
|
exceptionEvents.push({
|
||||||
|
start: event.startDate.toJSDate(),
|
||||||
|
end: event.endDate.toJSDate(),
|
||||||
|
summary: event.summary,
|
||||||
|
description: event.description,
|
||||||
|
recurrenceId: recurrenceId.toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track exception for filtering recurring events
|
||||||
|
if (!exceptions.has(uid)) {
|
||||||
|
exceptions.set(uid, new Set())
|
||||||
|
}
|
||||||
|
exceptions.get(uid)!.add(recurrenceId.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process regular and recurring events
|
||||||
|
const regularEvents = vevents
|
||||||
|
.filter(vevent => !vevent.getFirstPropertyValue("recurrence-id"))
|
||||||
|
.flatMap<ScheduleEvent>((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,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const eventExceptions = exceptions.get(event.uid) || new Set()
|
||||||
|
return expandEventOccurrences(event, rangeStart, rangeEnd, eventExceptions)
|
||||||
|
})
|
||||||
|
|
||||||
|
return [...regularEvents, ...exceptionEvents]
|
||||||
|
.sort((a, b) => b.start.getTime() - a.start.getTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandEventOccurrences = (
|
||||||
|
event: ICAL.Event,
|
||||||
|
rangeStart: ICAL.Time,
|
||||||
|
rangeEnd: ICAL.Time,
|
||||||
|
exceptions?: Set<string>,
|
||||||
|
): 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
|
||||||
|
|
||||||
|
// Skip this occurrence if it has an exception
|
||||||
|
if (exceptions && exceptions.has(next.toString())) 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")
|
||||||
|
const rangeStart = ICAL.Time.fromDateString("2020-01-01")
|
||||||
|
|
||||||
|
// First, collect all exception events (events with RECURRENCE-ID)
|
||||||
|
const exceptions = new Map<string, Set<string>>() // uid -> set of recurrence-id strings
|
||||||
|
const exceptionEvents: ScheduleEvent[] = []
|
||||||
|
|
||||||
|
vevents.forEach((vevent) => {
|
||||||
|
const recurrenceId = vevent.getFirstPropertyValue("recurrence-id")
|
||||||
|
if (recurrenceId) {
|
||||||
|
const uid = vevent.getFirstPropertyValue("uid")
|
||||||
|
const event = new ICAL.Event(vevent)
|
||||||
|
|
||||||
|
// Check if exception is in range
|
||||||
|
if (event.startDate.compare(rangeEnd) <= 0 && event.endDate.compare(rangeStart) >= 0) {
|
||||||
|
exceptionEvents.push({
|
||||||
|
start: event.startDate.toJSDate(),
|
||||||
|
end: event.endDate.toJSDate(),
|
||||||
|
summary: event.summary,
|
||||||
|
description: event.description,
|
||||||
|
recurrenceId: recurrenceId.toString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track exception for filtering recurring events
|
||||||
|
if (!exceptions.has(uid)) {
|
||||||
|
exceptions.set(uid, new Set())
|
||||||
|
}
|
||||||
|
exceptions.get(uid)!.add(recurrenceId.toString())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Process regular and recurring events
|
||||||
|
const regularEvents = vevents
|
||||||
|
.filter(vevent => !vevent.getFirstPropertyValue("recurrence-id"))
|
||||||
|
.flatMap<ScheduleEvent>((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(),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const eventExceptions = exceptions.get(event.uid) || new Set()
|
||||||
|
return expandEventOccurrences(event, rangeStart, rangeEnd, eventExceptions)
|
||||||
|
})
|
||||||
|
|
||||||
|
return [...regularEvents, ...exceptionEvents]
|
||||||
|
.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<ScheduleEvent[]>([])
|
||||||
|
|
||||||
|
const defaultDate = today(getLocalTimeZone())
|
||||||
|
const [focusedDate, setFocusedDate] = useState(defaultDate)
|
||||||
|
const dateRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
||||||
|
const calendarRef = useRef<ICAL.Component | null>(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<string, ScheduleEvent[]>()
|
||||||
|
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 (
|
||||||
|
<div className="box-border flex justify-center">
|
||||||
|
<section className="max-w-[1024px] px-[22px] mb-24 flex flex-col w-full">
|
||||||
|
<div className="my-8 text-2xl font-bold">日程</div>
|
||||||
|
|
||||||
|
{/* Mobile Date Selector */}
|
||||||
|
<div className="sm:hidden sticky top-0 bg-white z-10 py-4 -mx-[22px] px-[22px] border-b border-gray-200 mb-4">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<Select
|
||||||
|
placeholder="选择年份"
|
||||||
|
selectedKeys={[currentYear.toString()]}
|
||||||
|
defaultSelectedKeys={[currentYear.toString()]}
|
||||||
|
onSelectionChange={(keys) => {
|
||||||
|
const year = parseInt(Array.from(keys)[0] as string)
|
||||||
|
setFocusedDate(focusedDate.set({ year }))
|
||||||
|
}}
|
||||||
|
className="flex-1"
|
||||||
|
size="sm"
|
||||||
|
aria-label="选择年份"
|
||||||
|
renderValue={(items) => {
|
||||||
|
return items.map(item => `${item.key}年`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{years.map(year => (
|
||||||
|
<SelectItem key={year.toString()} textValue={year.toString()}>
|
||||||
|
{year}年
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
placeholder="选择月份"
|
||||||
|
selectedKeys={[currentMonth.toString()]}
|
||||||
|
defaultSelectedKeys={[currentMonth.toString()]}
|
||||||
|
onSelectionChange={(keys) => {
|
||||||
|
const month = parseInt(Array.from(keys)[0] as string)
|
||||||
|
setFocusedDate(focusedDate.set({ month }))
|
||||||
|
}}
|
||||||
|
className="flex-1"
|
||||||
|
size="sm"
|
||||||
|
aria-label="选择月份"
|
||||||
|
renderValue={(items) => {
|
||||||
|
return items.map(item => `${item.key}月`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{months.map(month => (
|
||||||
|
<SelectItem key={month.toString()} textValue={month.toString()}>
|
||||||
|
{month}月
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Alert
|
||||||
|
title="你可以在日历 App 中订阅我们的日程"
|
||||||
|
className="my-4 items-center"
|
||||||
|
endContent={(
|
||||||
|
<Button
|
||||||
|
color="default"
|
||||||
|
showAnchorIcon
|
||||||
|
size="sm"
|
||||||
|
as={Link}
|
||||||
|
variant="flat"
|
||||||
|
onPress={e => console.log(e)}
|
||||||
|
href="webcal://ical.nbtca.space"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
订阅
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-8 pt-4">
|
||||||
|
<div className="flex flex-col grow">
|
||||||
|
<div className="text-2xl font-bold w-full">
|
||||||
|
{
|
||||||
|
dayjs(focusedDate.toDate()).format("MMMM YYYY")
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="h-[0.5px] bg-gray-300 my-4" />
|
||||||
|
{loading && (
|
||||||
|
<div className="text-gray-500 h-[30vh] flex items-center justify-center w-full">
|
||||||
|
<Spinner label="加载中..."></Spinner>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{groupedEvents.length === 0 && !loading && (
|
||||||
|
<div className="text-gray-500">本月暂无日程</div>
|
||||||
|
)}
|
||||||
|
{
|
||||||
|
groupedEvents.map(([date, events]) => (
|
||||||
|
<div
|
||||||
|
key={date}
|
||||||
|
ref={el => dateRefs.current[date] = el}
|
||||||
|
className="mb-6"
|
||||||
|
>
|
||||||
|
<div className="text-lg mb-2">
|
||||||
|
<span className="text-lg mr-1"> { dayjs(date).format("MM.DD") } </span>
|
||||||
|
<span className="text-base"> { dayjs(date).format("ddd") } </span>
|
||||||
|
</div>
|
||||||
|
{events.map((event, index) => (
|
||||||
|
<div key={index} className="p-4 mb-2 border rounded-lg ">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<div className="text-lg sm:text-xl font-semibold">{event.summary}</div>
|
||||||
|
{event.recurrenceId && (
|
||||||
|
// <span className="text-sm text-blue-500 border border-blue-500 px-1 rounded">重复</span>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="size-4 text-gray-500">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-gray-600">{formatTimePair(event.start, event.end)}</span>
|
||||||
|
{
|
||||||
|
event.description && (
|
||||||
|
<p className="mt-2">{event.description}</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0 hidden sm:block">
|
||||||
|
<div className="sticky top-6">
|
||||||
|
<Calendar
|
||||||
|
aria-label="Date (Controlled Focused Value)"
|
||||||
|
focusedValue={focusedDate}
|
||||||
|
value={defaultDate}
|
||||||
|
onFocusChange={(v) => {
|
||||||
|
isUserChangeRef.current = true
|
||||||
|
setFocusedDate(v)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
import FullCalendar from "./Calendar"
|
import Schedule from "./Schedule"
|
||||||
import BaseLayout from "../../layouts/BaseLayout.astro"
|
import BaseLayout from "../../layouts/BaseLayout.astro"
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout primaryTitle="日历">
|
<BaseLayout primaryTitle="日历">
|
||||||
<FullCalendar client:load />
|
<Schedule client:load />
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ import logoAnimated from "./_assets/nbtca.gif";
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 17px;
|
font-size: 16px;
|
||||||
line-height: 1.82353;
|
line-height: 1.82353;
|
||||||
--body-container-width: 645px;
|
--body-container-width: 645px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
88
src/pages/posts/minecraftMac.md
Normal file
88
src/pages/posts/minecraftMac.md
Normal file
|
|
@ -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 | <https://www.minecraft.net/> |
|
||||||
|
| Fabric | <https://fabricmc.net/use/installer/> |
|
||||||
|
| Fabric API | <https://modrinth.com/mod/fabric-api/> |
|
||||||
|
| Sodium | <https://modrinth.com/mod/sodium> |
|
||||||
|
| Iris | <https://modrinth.com/mod/iris> |
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
## 1. 安装Fabric
|
||||||
|
|
||||||
|
第一步是安装Fabric加载器。前往 <https://fabricmc.net/use/installer/>
|
||||||
|
|
||||||
|
并点击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. 下载您的光影包
|
||||||
|
|
||||||
|
前往 <https://modrinth.com/shaders> 或 <https://minecraftshader.com> 下载光影。您需要注意这些光影是否支持Mac。
|
||||||
|
|
||||||
|
将下载的光影放入shaderpacks。打开游戏并点击选项–>视频设置–>光影包,现在您可以看到您的光影了。
|
||||||
|
|
||||||
|
然后在列表中点击您想要的光影包,点击应用,然后完成。
|
||||||
|
|
||||||
|
## 4. 享受游戏
|
||||||
|
|
||||||
|
恭喜您,尽情享受游戏吧。
|
||||||
|
|
@ -140,15 +140,15 @@ function TicketDetailDrawer(props: {
|
||||||
return (
|
return (
|
||||||
<Drawer isOpen={isOpen} onOpenChange={onOpenChange}>
|
<Drawer isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<DrawerHeader>
|
<DrawerHeader className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-2">
|
||||||
<h2 className="text-2xl font-bold">维修详情</h2>
|
<h2 className="text-xl sm:text-2xl font-bold">维修详情</h2>
|
||||||
{isLoading}
|
{isLoading && <span className="text-sm text-gray-500">{isLoading}</span>}
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<DrawerBody>
|
<DrawerBody className="px-4 sm:px-6">
|
||||||
<EventDetail ref={eventDetailRef} eventId={props.event?.eventId}>
|
<EventDetail ref={eventDetailRef} eventId={props.event?.eventId}>
|
||||||
{
|
{
|
||||||
event => (
|
event => (
|
||||||
<div className="mb-12 flex flex-col gap-2">
|
<div className="mb-8 sm:mb-12 flex flex-col gap-3 sm:gap-2">
|
||||||
{
|
{
|
||||||
availableActions?.map((action) => {
|
availableActions?.map((action) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -171,8 +171,8 @@ function TicketDetailDrawer(props: {
|
||||||
}
|
}
|
||||||
</EventDetail>
|
</EventDetail>
|
||||||
</DrawerBody>
|
</DrawerBody>
|
||||||
<DrawerFooter>
|
<DrawerFooter className="px-4 sm:px-6">
|
||||||
<Button variant="flat" onPress={onClose}>
|
<Button variant="flat" onPress={onClose} className="w-full sm:w-auto">
|
||||||
关闭
|
关闭
|
||||||
</Button>
|
</Button>
|
||||||
</DrawerFooter>
|
</DrawerFooter>
|
||||||
|
|
@ -336,6 +336,52 @@ export default function App() {
|
||||||
onOpen()
|
onOpen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MobileEventCard = ({ event }: { event: PublicEvent }) => (
|
||||||
|
<button className="bg-white border border-gray-200 rounded-lg p-4 shadow-sm" onClick={() => onOpenEventDetail(event)}>
|
||||||
|
|
||||||
|
<div className="mb-3 flex gap-2 items-center justify-between">
|
||||||
|
<div className="text font-medium text-gray-900 line-clamp-2">
|
||||||
|
{event.problem}
|
||||||
|
<span className="text font-medium text-gray-400 ml-1">#{event.eventId}</span>
|
||||||
|
</div>
|
||||||
|
<div className="">
|
||||||
|
<EventStatusChip status={event.status} size="sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="h-18">
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<div className="">
|
||||||
|
{ dayjs(event.gmtCreate).format("YYYY-MM-DD HH:mm") }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{event.model && (
|
||||||
|
<div>
|
||||||
|
{event.model}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{ event.size && <Chip size="sm">size:{event.size}</Chip>}
|
||||||
|
</div>
|
||||||
|
{event.member && (
|
||||||
|
<div className="flex items-center gap-2 ">
|
||||||
|
<User
|
||||||
|
avatarProps={{ radius: "full", src: event.member.avatar, size: "sm" }}
|
||||||
|
name=""
|
||||||
|
classNames={{
|
||||||
|
base: "justify-start",
|
||||||
|
name: "text-sm",
|
||||||
|
description: "text-xs",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
const renderCell = useCallback((event: PublicEvent, columnKey: string | number) => {
|
const renderCell = useCallback((event: PublicEvent, columnKey: string | number) => {
|
||||||
const cellValue = event[columnKey]
|
const cellValue = event[columnKey]
|
||||||
|
|
||||||
|
|
@ -389,45 +435,89 @@ export default function App() {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
return (
|
return (
|
||||||
<section className="box-border max-w-[1024px] mx-auto px-[22px] mb-24">
|
<section className="box-border max-w-full px-4 sm:px-6 lg:max-w-[1024px] lg:px-[22px] mx-auto mb-16 sm:mb-24">
|
||||||
<div className="mt-6 flex justify-between items-center">
|
<div className="mt-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
<div className="text-2xl font-bold">维修管理</div>
|
<div className="text-xl sm:text-2xl font-bold">维修管理</div>
|
||||||
{
|
{
|
||||||
userInfo?.roles?.find(v => v.toLowerCase() == "repair admin")
|
userInfo?.roles?.find(v => v.toLowerCase() == "repair admin")
|
||||||
? <ExportExcelModal></ExportExcelModal>
|
? <div className="w-full sm:w-auto"><ExportExcelModal></ExportExcelModal></div>
|
||||||
: <></>
|
: <></>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="my-8 flex flex-col gap-4">
|
<div className="my-8 flex flex-col gap-4">
|
||||||
<Table
|
{/* Mobile Cards Layout */}
|
||||||
aria-label="Example table with dynamic content"
|
<div className="block sm:hidden">
|
||||||
sortDescriptor={list.sortDescriptor}
|
{/* Filter Section for Mobile */}
|
||||||
onSortChange={list.sort}
|
<div className="mb-4 flex items-center gap-2">
|
||||||
bottomContent={(
|
<span className="text-sm font-medium">筛选状态:</span>
|
||||||
<div className="flex w-full justify-center">
|
<CheckboxPopover value={statusFilter} onValueChange={setStatusFilter} />
|
||||||
<Pagination
|
</div>
|
||||||
isCompact
|
|
||||||
showControls
|
{isLoading
|
||||||
showShadow
|
? (
|
||||||
color="secondary"
|
<div className="flex justify-center py-8">
|
||||||
page={page}
|
<Spinner label="Loading..." />
|
||||||
total={pages}
|
</div>
|
||||||
onChange={page => setPage(page)}
|
)
|
||||||
/>
|
: (
|
||||||
</div>
|
<div className="flex flex-col gap-4">
|
||||||
)}
|
{items.map(event => (
|
||||||
>
|
<MobileEventCard key={event.eventId} event={event} />
|
||||||
<TableHeader columns={columns}>
|
))}
|
||||||
{column => <TableColumn key={column.key} allowsSorting={column.allowSorting} children={column.content ?? <div>{column.label}</div>}></TableColumn>}
|
{items.length === 0 && (
|
||||||
</TableHeader>
|
<div className="text-center py-8 text-gray-500">
|
||||||
<TableBody isLoading={isLoading} items={items} loadingContent={<Spinner label="Loading..." />}>
|
暂无维修记录
|
||||||
{item => (
|
</div>
|
||||||
<TableRow key={item.eventId}>
|
)}
|
||||||
{columnKey => <TableCell>{renderCell(item, columnKey)}</TableCell>}
|
</div>
|
||||||
</TableRow>
|
)}
|
||||||
|
|
||||||
|
{/* Mobile Pagination */}
|
||||||
|
<div className="mt-6 flex justify-center">
|
||||||
|
<Pagination
|
||||||
|
isCompact
|
||||||
|
showControls
|
||||||
|
showShadow
|
||||||
|
color="secondary"
|
||||||
|
page={page}
|
||||||
|
total={pages}
|
||||||
|
onChange={page => setPage(page)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop Table Layout */}
|
||||||
|
<div className="hidden sm:block">
|
||||||
|
<Table
|
||||||
|
aria-label="Example table with dynamic content"
|
||||||
|
sortDescriptor={list.sortDescriptor}
|
||||||
|
onSortChange={list.sort}
|
||||||
|
bottomContent={(
|
||||||
|
<div className="flex w-full justify-center">
|
||||||
|
<Pagination
|
||||||
|
isCompact
|
||||||
|
showControls
|
||||||
|
showShadow
|
||||||
|
color="secondary"
|
||||||
|
page={page}
|
||||||
|
total={pages}
|
||||||
|
onChange={page => setPage(page)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
>
|
||||||
</Table>
|
<TableHeader columns={columns}>
|
||||||
|
{column => <TableColumn key={column.key} allowsSorting={column.allowSorting} children={column.content ?? <div>{column.label}</div>}></TableColumn>}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody isLoading={isLoading} items={items} loadingContent={<Spinner label="Loading..." />}>
|
||||||
|
{item => (
|
||||||
|
<TableRow key={item.eventId}>
|
||||||
|
{columnKey => <TableCell>{renderCell(item, columnKey)}</TableCell>}
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TicketDetailDrawer
|
<TicketDetailDrawer
|
||||||
event={activeEvent}
|
event={activeEvent}
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,6 @@ progress {
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: none;
|
background: none;
|
||||||
border: 0;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -149,12 +148,12 @@ button:disabled {
|
||||||
html {
|
html {
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica",
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica",
|
||||||
"Arial", sans-serif;
|
"Arial", sans-serif;
|
||||||
font-size: 106.25%;
|
/* font-size: 106.25%; */
|
||||||
quotes: "“" "”";
|
quotes: "“" "”";
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-size: 17px;
|
/* font-size: 17px; */
|
||||||
line-height: 1.47059;
|
line-height: 1.47059;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: -0.022em;
|
letter-spacing: -0.022em;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
import { heroui } from "@heroui/react"
|
import { heroui } from "@heroui/react"
|
||||||
|
import typography from "@tailwindcss/typography"
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
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: {
|
theme: {
|
||||||
screens: {
|
screens: {
|
||||||
xs: "416px",
|
xs: "416px",
|
||||||
|
|
@ -16,5 +21,5 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
darkMode: ["selector", "body.theme-dark"],
|
darkMode: ["selector", "body.theme-dark"],
|
||||||
plugins: [heroui()],
|
plugins: [heroui(), typography()],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue