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:
2026-09-09 00:14:12 +08:00
parent 711cd8b759
commit 52d328ffc3
63 changed files with 2007 additions and 1563 deletions

View File

@@ -5,7 +5,7 @@ import {
RouterProvider,
Route,
Navigate,
} from 'react-router-dom';
} from 'react-router';
import './styles/global.css';
import { AuthProvider } from './hooks/useAuth';
import { ThemeProvider } from './hooks/useTheme';

View File

@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { isRouteErrorResponse, useRouteError } from 'react-router-dom';
import { isRouteErrorResponse, useRouteError } from 'react-router';
import { Button } from '@/components/ui/button';
import { isChunkLoadError, reloadForStaleChunk } from '../utils/chunkLoad';

View File

@@ -268,13 +268,16 @@ const ArticleEditor = forwardRef<ArticleEditorHandle, Props>(function ArticleEdi
ImageGroup,
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === 'paragraph' && node.parent?.type.name === 'membersOnly') {
// prosemirror 未公开 parent 字段,这里仅读父节点类型名
const parentName = (node as unknown as { parent?: { type?: { name?: string } } })
.parent?.type?.name;
if (node.type.name === 'paragraph' && parentName === 'membersOnly') {
return MEMBERS_ONLY_PLACEHOLDER;
}
if (node.type.name === 'paragraph' && node.parent?.type.name === 'replyOnly') {
if (node.type.name === 'paragraph' && parentName === 'replyOnly') {
return REPLY_ONLY_PLACEHOLDER;
}
if (node.type.name === 'paragraph' && node.parent?.type.name === 'pointsOnly') {
if (node.type.name === 'paragraph' && parentName === 'pointsOnly') {
return '此处内容需积分解锁后可见…';
}
return placeholder;

View File

@@ -1,5 +1,5 @@
import { CalendarCheck, Check, Gift, Loader2 } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { useCheckIn } from '../hooks/useCheckIn';
import { useAuth } from '../hooks/useAuth';
import { loginPath } from '../utils/authRedirect';

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { useLocation } from 'react-router';
import { ArrowUp, MessageSquare } from 'lucide-react';
/** 滚动超过该距离后显示按钮 */

View File

@@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { Send } from 'lucide-react';
import { Switch } from '@/components/ui/switch';
import { Button } from '@/components/ui/button';

View File

@@ -1,5 +1,5 @@
import { useMemo, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { api } from '../api/client';
import { renderCommentContent } from '../utils/content';
import { handleMdCodeBlockUiClick } from '../utils/enhanceCodeBlocks';

View File

@@ -148,7 +148,9 @@ const CommentEditor = forwardRef<CommentEditorHandle, Props>(function CommentEdi
useImperativeHandle(ref, () => ({
getHTML: () => editor ? sanitizeHtml(editor.getHTML()) : value,
isEmpty: () => editor ? isEditorEmpty(editor) : !value.trim(),
focus: (options?: { preventScroll?: boolean }) => { editor?.commands.focus(options); },
focus: (options?: { preventScroll?: boolean }) => {
editor?.commands.focus(undefined, { scrollIntoView: !options?.preventScroll });
},
}), [editor, value]);
const insertSticker = useCallback((sticker: Sticker) => {

View File

@@ -1,4 +1,4 @@
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { navigateFeed } from '../utils/feedCache';
interface Props {

View File

@@ -1,5 +1,5 @@
import { useMemo, useRef } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import { useParams, useSearchParams } from 'react-router';
import { boardPath, parsePermalinkID, type PermalinkOpts } from '../utils/permalink';
import { getCachedForumLimits, useForumLimits } from '../hooks/useForumLimits';
import { Clock, MessageCircle, BadgeCheck, Loader2 } from 'lucide-react';

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { Eye, FileText, Heart, Mail, MessageCircle, UserRound } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';

View File

@@ -1,5 +1,5 @@
import { useMemo, useCallback, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router';
import { renderPostContentHtml } from '../utils/postContent';
import { handleMdCodeBlockUiClick } from '../utils/enhanceCodeBlocks';
import { loginPath, registerPath } from '../utils/authRedirect';

View File

@@ -1,5 +1,5 @@
import { memo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { MessageCircle } from 'lucide-react';
import BoardBadge from '@/components/BoardBadge';
import FeaturedIcon from '@/components/FeaturedIcon';

View File

@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import { ListTree, MessageCircle, Tags, Link2, UserPlus } from 'lucide-react';
import { useLocation, useSearchParams, useNavigate } from 'react-router-dom';
import { useLocation, useSearchParams, useNavigate } from 'react-router';
import type { AsideWidget, RecentComment, RecentUser, TagCount, User, ForumStats, FriendLink } from '../api/types';
import type { PostHeading } from '../utils/postHeadings';
import { useSiteBranding } from '../hooks/useSiteBranding';

View File

@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { Globe2 } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { api } from '../api/client';
import type { CommunityShowcaseItem } from '../api/types';
import { getSessionSnapshot, setSessionSnapshot } from '../utils/sessionPageCache';

View File

@@ -1,7 +1,7 @@
import {
Home, Star, LayoutDashboard, FolderGit2, FolderKanban, ArrowLeft, FileText, Link2, Globe2,
} from 'lucide-react';
import { useNavigate, useLocation, useSearchParams, Link } from 'react-router-dom';
import { useNavigate, useLocation, useSearchParams, Link } from 'react-router';
import type { Board } from '../api/types';
import type { PostHeading } from '../utils/postHeadings';
import { useAuth } from '../hooks/useAuth';

View File

@@ -1,6 +1,6 @@
import { useSiteBranding } from '../hooks/useSiteBranding';
import { useMediaQuery } from '../hooks/useTheme';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { useSitePages } from '../hooks/useSitePages';
import { pagePath } from '../utils/permalink';
import { useForumLimits } from '../hooks/useForumLimits';

View File

@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import type { TagCount } from '../api/types';
import { cn } from '@/lib/utils';

View File

@@ -1,5 +1,5 @@
import type { MouseEvent, ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { Link } from 'react-router';
import { cn } from '@/lib/utils';
import type { UserBadge } from '../api/types';
import { userPath } from '../utils/userPath';

View File

@@ -1,5 +1,5 @@
import { useRef, useEffect, useLayoutEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { useVirtualizer } from '@tanstack/react-virtual';
import { Inbox, SearchX } from 'lucide-react';
import { Button } from '@/components/ui/button';

View File

@@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';
import { useLocation } from 'react-router';
import { api } from '../api/client';
import { useForumLimits } from './useForumLimits';

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo } from 'react';
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
import { useLocation, useNavigate, useSearchParams } from 'react-router';
import { buildHomeUrl } from '../components/FeedSortBar';
import { navigateFeed } from '../utils/feedCache';
import { transitionTo } from '../utils/spaTransition';

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useBlocker } from 'react-router-dom';
import { useBlocker } from 'react-router';
interface Options {
/** 是否存在未保存的修改 */

View File

@@ -1,5 +1,5 @@
import { useEffect, useState, useCallback, useRef } from 'react';
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom';
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router';
import {
LayoutDashboard, FolderKanban, FileText, MessageSquare, Flag, Users, Images, Settings, ArrowLeft, Moon, Sun, Menu, X, Award, Link2, BookOpen, Globe2, Activity,
} from 'lucide-react';

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useRef, useMemo, Suspense, startTransition } from 'react';
import { Outlet, useNavigate, useSearchParams, useLocation } from 'react-router-dom';
import { Outlet, useNavigate, useSearchParams, useLocation } from 'react-router';
import { Menu, Moon, Sun, Search, Plus, PanelRight, X, Mail, SlidersHorizontal } from 'lucide-react';
import {
DropdownMenu,

View File

@@ -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 },
});

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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,

View File

@@ -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';

View File

@@ -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,

View File

@@ -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);

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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';

View File

@@ -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';

View File

@@ -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(() => { /* 入口开关稍后仍可按默认展示 */ });

View File

@@ -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';

View File

@@ -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('单页排序已更新');

View File

@@ -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);

View File

@@ -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';

View File

@@ -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: '' });

View File

@@ -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';

View File

@@ -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';

View File

@@ -1,4 +1,4 @@
import type { NavigateFunction } from 'react-router-dom';
import type { NavigateFunction } from 'react-router';
import type { PostItem } from '../api/types';
import type { FeedSort } from '../components/FeedSortBar';
import {

View File

@@ -10,8 +10,8 @@ const GATED_BLOCK_RE = /<(members-only|reply-only)(?:\s[^>]*)?>([\s\S]*?)<\/\1>/
const TURNDOWN_OPTIONS = {
headingStyle: 'atx' as const,
codeBlockStyle: 'fenced' as const,
emDelimiter: '*',
bulletListMarker: '-',
emDelimiter: '*' as const,
bulletListMarker: '-' as const,
};
/** 仅去除区块首尾空行,保留各行行首缩进 */
@@ -135,7 +135,7 @@ function addTurndownContentRules(service: TurndownService): void {
});
service.addRule('strikethrough', {
filter: ['del', 's', 'strike'],
filter: (node) => ['DEL', 'S', 'STRIKE'].includes(node.nodeName),
replacement: (content) => `~~${content}~~`,
});
@@ -234,12 +234,12 @@ function gatedBlockToMarkdown(tag: 'members-only' | 'reply-only', node: HTMLElem
}
turndown.addRule('membersOnly', {
filter: 'members-only',
filter: (node) => node.nodeName === 'MEMBERS-ONLY',
replacement: (_content, node) => gatedBlockToMarkdown('members-only', node as HTMLElement),
});
turndown.addRule('replyOnly', {
filter: 'reply-only',
filter: (node) => node.nodeName === 'REPLY-ONLY',
replacement: (_content, node) => gatedBlockToMarkdown('reply-only', node as HTMLElement),
});

View File

@@ -1,4 +1,4 @@
import type { NavigateFunction } from 'react-router-dom';
import type { NavigateFunction } from 'react-router';
import { postPath, type PermalinkOpts } from './permalink';
import { transitionTo } from './spaTransition';

View File

@@ -1,4 +1,4 @@
import type { To } from 'react-router-dom';
import type { To } from 'react-router';
import { api } from '../api/client';
import type {
CheckInStatus,

View File

@@ -1,4 +1,4 @@
import type { NavigateFunction, NavigateOptions, To } from 'react-router-dom';
import type { NavigateFunction, NavigateOptions, To } from 'react-router';
import { notify } from '@/lib/notify';
import { isChunkLoadError, reloadForStaleChunk } from './chunkLoad';
import { prefetchRoute } from './prefetchRoute';