chore: 工具链升级到最新版本(Vite 8 / React 19 / react-router 8 / TS 7 / Go 1.26 依赖更新)
- 前端:vite ^8.2.2、react/react-dom ^19.2.8、react-router ^8.3.1(移除 react-router-dom)、typescript ^7.0.2、zustand ^5.0.15;适配升级带来的类型变化(react-hook-form Resolver、ProseMirror node.parent、tiptap focus 参数、timer undefined 等) - 后端:升级 gin/jwt/minio-go/gorm/modernc-sqlite/x-crypto 等直接依赖至最新;gowebp v0.1.1 已是最新版本 - tsconfig 移除 baseUrl,paths 改为相对路径以兼容 TS7
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useForm, type Resolver } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { Plus, FolderKanban } from 'lucide-react';
|
||||
@@ -52,8 +52,9 @@ export default function BoardsManagePage() {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [reordering, setReordering] = useState(false);
|
||||
|
||||
// z.coerce 的 input 类型为 unknown,这里显式断言对齐 output 类型
|
||||
const form = useForm<BoardFormValues>({
|
||||
resolver: zodResolver(boardSchema),
|
||||
resolver: zodResolver(boardSchema) as Resolver<BoardFormValues>,
|
||||
defaultValues: { name: '', description: '', sort_order: 1, icon: '', color_index: -1 },
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { useNavigate, useSearchParams, useParams, useOutletContext } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams, useParams, useOutletContext } from 'react-router';
|
||||
import { Pencil } from 'lucide-react';
|
||||
import { notify } from '@/lib/notify';
|
||||
import { api } from '../api/client';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { ArrowLeft, Star } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import { useNavigate, Link } from 'react-router';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useLocation,
|
||||
useParams,
|
||||
useNavigationType,
|
||||
} from 'react-router-dom';
|
||||
} from 'react-router';
|
||||
import { notify } from '@/lib/notify';
|
||||
import { api } from '../api/client';
|
||||
import type { PostItem } from '../api/types';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router';
|
||||
import { ArrowLeft, Link2, Pencil, Plus, Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, Link, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, Link, useSearchParams } from 'react-router';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router';
|
||||
import {
|
||||
ArrowLeft,
|
||||
AtSign,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { FileQuestion, Home } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { usePageSEO } from '../hooks/usePageSEO';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useLayoutEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { useParams, useNavigate, useOutletContext, useLocation, useNavigationType } from 'react-router-dom';
|
||||
import { useParams, useNavigate, useOutletContext, useLocation, useNavigationType } from 'react-router';
|
||||
import { ArrowLeft, ThumbsUp, Star, Lock, MessageSquare, MessageSquareOff, Flag, MoreHorizontal } from 'lucide-react';
|
||||
import FeaturedIcon from '@/components/FeaturedIcon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -155,7 +155,7 @@ export default function PostDetailPage() {
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
const commentSectionRef = useRef<HTMLDivElement>(null);
|
||||
const commentBoxRef = useRef<HTMLDivElement>(null);
|
||||
const highlightTimer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const highlightTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
const postRef = useRef<PostItem | null>(null);
|
||||
const scrollTopRef = useRef(
|
||||
initialSnap && navType === 'POP' && !location.hash ? initialSnap.scrollTop : 0,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
@@ -91,7 +91,7 @@ export default function ProfilePage() {
|
||||
const [idCopied, setIdCopied] = useState(false);
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
const dragCounter = useRef(0);
|
||||
const copyTimer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const copyTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
const sigSectionRef = useRef<HTMLDivElement>(null);
|
||||
const pendingFocusSig = useRef(false);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { ArrowLeft, FolderGit2, Search } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, Link, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, Link, useSearchParams } from 'react-router';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { ArrowLeft, ExternalLink, Globe2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useOutletContext, useParams } from 'react-router-dom';
|
||||
import { useOutletContext, useParams } from 'react-router';
|
||||
import NotFoundPage from './NotFoundPage';
|
||||
import { api } from '../api/client';
|
||||
import type { SitePage } from '../api/types';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||
import { useNavigate, useParams, useLocation } from 'react-router';
|
||||
import {
|
||||
ArrowLeft,
|
||||
FileText,
|
||||
|
||||
@@ -170,9 +170,9 @@ export default function AdminBadgesPage() {
|
||||
setReordering(true);
|
||||
try {
|
||||
const merged = mergeReorderedSubset(before, subsetBefore, reorderedFiltered);
|
||||
const after = await persistSortOrderChanges(before, merged, badge =>
|
||||
api.adminUpsertBadge({ ...badge, sort_order: badge.sort_order }),
|
||||
);
|
||||
const after = await persistSortOrderChanges(before, merged, async badge => {
|
||||
await api.adminUpsertBadge({ ...badge, sort_order: badge.sort_order });
|
||||
});
|
||||
setRows(after);
|
||||
notify.success('徽章排序已更新');
|
||||
} catch (e: unknown) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { Trash2, RotateCcw } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -53,7 +53,7 @@ export default function AdminCommentsPage() {
|
||||
const [revComment, setRevComment] = useState<Comment | null>(null);
|
||||
const [highlightId, setHighlightId] = useState<number | null>(focusId > 0 ? focusId : null);
|
||||
const focusTriedRef = useRef(false);
|
||||
const highlightTimer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const highlightTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
|
||||
const loadList = (p = page, st: Tab = tab) => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link } from 'react-router';
|
||||
import { Globe2, ExternalLink, Star } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { FileText, Flag, MessageSquare, Link2, Activity } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router';
|
||||
import {
|
||||
Link2, Plus, Trash2, Check, X, ExternalLink, Eye, RotateCcw,
|
||||
} from 'lucide-react';
|
||||
@@ -173,24 +173,27 @@ export default function AdminLinksPage() {
|
||||
aside_show_friend_links: s.limits?.aside_show_friend_links ?? true,
|
||||
}),
|
||||
);
|
||||
const base: ForumLimits = {
|
||||
open_posts_in_new_tab: true,
|
||||
open_content_links_in_new_tab: true,
|
||||
aside_show_tag_cloud: false,
|
||||
aside_show_recent_comments: false,
|
||||
aside_show_friend_links: true,
|
||||
aside_show_showcase: false,
|
||||
aside_widgets: loadedAsideWidgets,
|
||||
nav_show_friend_links: true,
|
||||
footer_show_friend_links: true,
|
||||
nav_show_showcase: false,
|
||||
footer_show_showcase: false,
|
||||
feed_list_style: 'title',
|
||||
feed_sort_tabs: DEFAULT_FEED_SORT_TABS,
|
||||
permalink_enabled: false,
|
||||
permalink_ext: 'html',
|
||||
...s.limits,
|
||||
};
|
||||
// 默认值在前、server limits 覆盖在后(JSON 缺 key 不覆盖)
|
||||
const base: ForumLimits = Object.assign(
|
||||
{
|
||||
open_posts_in_new_tab: true,
|
||||
open_content_links_in_new_tab: true,
|
||||
aside_show_tag_cloud: false,
|
||||
aside_show_recent_comments: false,
|
||||
aside_show_friend_links: true,
|
||||
aside_show_showcase: false,
|
||||
aside_widgets: loadedAsideWidgets,
|
||||
nav_show_friend_links: true,
|
||||
footer_show_friend_links: true,
|
||||
nav_show_showcase: false,
|
||||
footer_show_showcase: false,
|
||||
feed_list_style: 'title',
|
||||
feed_sort_tabs: DEFAULT_FEED_SORT_TABS,
|
||||
permalink_enabled: false,
|
||||
permalink_ext: 'html',
|
||||
} as ForumLimits,
|
||||
s.limits,
|
||||
);
|
||||
setForumLimits(mergeForumLimitsWithAsideWidgets(base, loadedAsideWidgets));
|
||||
})
|
||||
.catch(() => { /* 入口开关稍后仍可按默认展示 */ });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import { Activity, AlertTriangle } from 'lucide-react';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { FileText, Plus, Pencil, Trash2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -76,8 +76,8 @@ export default function AdminPagesPage() {
|
||||
const before = [...rows];
|
||||
setReordering(true);
|
||||
try {
|
||||
const after = await persistSortOrderChanges(before, reordered, page =>
|
||||
api.adminUpdatePage(page.id, {
|
||||
const after = await persistSortOrderChanges(before, reordered, async page => {
|
||||
await api.adminUpdatePage(page.id, {
|
||||
title: page.title,
|
||||
slug: page.slug,
|
||||
content: page.content,
|
||||
@@ -85,8 +85,8 @@ export default function AdminPagesPage() {
|
||||
sort_order: page.sort_order,
|
||||
show_in_footer: page.show_in_footer,
|
||||
show_in_nav: page.show_in_nav,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
setRows(after);
|
||||
invalidateSitePagesCache();
|
||||
notify.success('单页排序已更新');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { Search, Lock, LockOpen, MessageSquareOff, Trash2, RotateCcw } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -46,7 +46,7 @@ export default function AdminPostsPage() {
|
||||
const [search, setSearch] = useState('');
|
||||
const [highlightId, setHighlightId] = useState<number | null>(focusId > 0 ? focusId : null);
|
||||
const focusTriedRef = useRef(false);
|
||||
const highlightTimer = useRef<ReturnType<typeof setTimeout>>();
|
||||
const highlightTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
|
||||
const loadActive = (p = page, kw = search, status = tab === 'pending' ? 'pending' : 'all') => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
|
||||
@@ -294,24 +294,27 @@ export default function AdminSettingsPage() {
|
||||
}),
|
||||
);
|
||||
setAsideWidgets(loadedAsideWidgets);
|
||||
const baseLimits: ForumLimits = {
|
||||
open_posts_in_new_tab: true,
|
||||
open_content_links_in_new_tab: true,
|
||||
aside_show_tag_cloud: false,
|
||||
aside_show_recent_comments: false,
|
||||
aside_show_friend_links: true,
|
||||
aside_show_showcase: false,
|
||||
aside_widgets: loadedAsideWidgets,
|
||||
nav_show_friend_links: true,
|
||||
footer_show_friend_links: true,
|
||||
nav_show_showcase: false,
|
||||
footer_show_showcase: false,
|
||||
feed_list_style: 'title',
|
||||
feed_sort_tabs: normalizeFeedSortTabs(s.limits?.feed_sort_tabs ?? DEFAULT_FEED_SORT_TABS),
|
||||
permalink_enabled: false,
|
||||
permalink_ext: 'html',
|
||||
...s.limits,
|
||||
};
|
||||
// 默认值在前、server limits 覆盖在后(JSON 缺 key 不覆盖)
|
||||
const baseLimits: ForumLimits = Object.assign(
|
||||
{
|
||||
open_posts_in_new_tab: true,
|
||||
open_content_links_in_new_tab: true,
|
||||
aside_show_tag_cloud: false,
|
||||
aside_show_recent_comments: false,
|
||||
aside_show_friend_links: true,
|
||||
aside_show_showcase: false,
|
||||
aside_widgets: loadedAsideWidgets,
|
||||
nav_show_friend_links: true,
|
||||
footer_show_friend_links: true,
|
||||
nav_show_showcase: false,
|
||||
footer_show_showcase: false,
|
||||
feed_list_style: 'title',
|
||||
feed_sort_tabs: normalizeFeedSortTabs(s.limits?.feed_sort_tabs ?? DEFAULT_FEED_SORT_TABS),
|
||||
permalink_enabled: false,
|
||||
permalink_ext: 'html',
|
||||
} as ForumLimits,
|
||||
s.limits,
|
||||
);
|
||||
setLimits(mergeForumLimitsWithAsideWidgets(baseLimits, loadedAsideWidgets));
|
||||
setBranding({ ...DEFAULT_BRANDING, ...(s.branding ?? {}) });
|
||||
setMail({ ...EMPTY_MAIL, ...s.mail, password: '' });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import { ArrowLeft, ExternalLink, Save } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router';
|
||||
import {
|
||||
Award, Ban, BadgeCheck, MoreHorizontal, Search, Shield, UserCog,
|
||||
} from 'lucide-react';
|
||||
|
||||
Reference in New Issue
Block a user