@@ -20,11 +20,16 @@ import { useRouter } from "next/navigation";
2020import { TreeSelect } from "antd" ;
2121import { DataNode } from "antd/es/tree" ;
2222import { buildDocsNewUrl } from "@/lib/github" ;
23- import { type DirNode , FILENAME_PATTERN } from "@/lib/submission" ;
23+ import {
24+ FILENAME_PATTERN ,
25+ normalizeFilenameBase ,
26+ type DirNode ,
27+ } from "@/lib/submission" ;
2428import {
2529 CREATE_SUBDIR_SUFFIX ,
2630 toTreeSelectData ,
2731} from "@/app/components/contribute/tree-utils" ;
32+ import { sanitizeDocumentSlug } from "@/lib/sanitizer" ;
2833
2934// 统一调用工具函数生成 GitHub 新建链接,路径规则与 Edit 按钮一致
3035function buildGithubNewUrl ( dirPath : string , filename : string , title : string ) {
@@ -59,22 +64,26 @@ export function Contribute() {
5964 const [ articleFile , setArticleFile ] = useState ( "" ) ;
6065 const [ articleFileTouched , setArticleFileTouched ] = useState ( false ) ;
6166
62- const trimmedArticleFile = useMemo ( ( ) => articleFile . trim ( ) , [ articleFile ] ) ;
67+ const normalizedArticleFile = useMemo (
68+ ( ) => normalizeFilenameBase ( articleFile ) ,
69+ [ articleFile ] ,
70+ ) ;
6371 const { isFileNameValid, fileNameError } = useMemo ( ( ) => {
64- if ( ! trimmedArticleFile ) {
72+ if ( ! normalizedArticleFile ) {
6573 return {
6674 isFileNameValid : false ,
6775 fileNameError : "请填写文件名。" ,
6876 } ;
6977 }
70- if ( ! FILENAME_PATTERN . test ( trimmedArticleFile ) ) {
78+ if ( ! FILENAME_PATTERN . test ( normalizedArticleFile ) ) {
7179 return {
7280 isFileNameValid : false ,
73- fileNameError : "文件名仅支持英文、数字、连字符或下划线。" ,
81+ fileNameError :
82+ "文件名仅支持字母、数字、连字符或下划线,并需以字母或数字开头。" ,
7483 } ;
7584 }
7685 return { isFileNameValid : true , fileNameError : "" } ;
77- } , [ trimmedArticleFile ] ) ;
86+ } , [ normalizedArticleFile ] ) ;
7887
7988 useEffect ( ( ) => {
8089 let mounted = true ;
@@ -99,22 +108,31 @@ export function Contribute() {
99108
100109 const options = useMemo ( ( ) => toTreeSelectData ( tree ) , [ tree ] ) ;
101110
111+ const sanitizedSubdir = useMemo (
112+ ( ) => sanitizeDocumentSlug ( newSub , "" ) ,
113+ [ newSub ] ,
114+ ) ;
115+
102116 const finalDirPath = useMemo ( ( ) => {
103117 if ( ! selectedKey ) return "" ;
104118 if ( selectedKey . endsWith ( CREATE_SUBDIR_SUFFIX ) ) {
105119 const l1 = selectedKey . split ( "/" ) [ 0 ] ;
106- if ( ! newSub . trim ( ) ) return "" ;
107- return `${ l1 } /${ newSub . trim ( ) . replace ( / \s + / g , "-" ) } ` ;
120+ if ( ! l1 || ! sanitizedSubdir ) return "" ;
121+ return `${ l1 } /${ sanitizedSubdir } ` ;
108122 }
109123 return selectedKey ;
110- } , [ selectedKey , newSub ] ) ;
124+ } , [ selectedKey , sanitizedSubdir ] ) ;
111125
112126 const canProceed = ! ! finalDirPath && isFileNameValid ;
113127
114128 const handleOpenGithub = ( ) => {
115129 if ( ! canProceed ) return ;
116- const filename = trimmedArticleFile . toLowerCase ( ) ;
130+ if ( ! normalizedArticleFile ) return ;
131+ const filename = normalizedArticleFile ;
117132 const title = articleTitle || filename ;
133+ if ( filename !== articleFile ) {
134+ setArticleFile ( filename ) ;
135+ }
118136 window . open (
119137 buildGithubNewUrl ( finalDirPath , filename , title ) ,
120138 "_blank" ,
@@ -263,7 +281,8 @@ export function Contribute() {
263281 onChange = { ( e ) => setNewSub ( e . target . value ) }
264282 />
265283 < p className = "text-xs text-muted-foreground" >
266- 将创建路径:{ selectedKey . split ( "/" ) [ 0 ] } / { newSub || "<未填写>" }
284+ 将创建路径:{ selectedKey . split ( "/" ) [ 0 ] } /{ " " }
285+ { sanitizedSubdir || "<未填写>" }
267286 </ p >
268287 </ div >
269288 ) }
0 commit comments