| |
| |
| |
| |
|
|
| import type { AnalyzeResponse } from '../api/GLTR_API'; |
| import { tr } from '../lang/i18n-lite'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const normalizeFolderPath = (path: string | null | undefined): string => { |
| if (!path || path.trim() === '' || path === '/') { |
| return '/'; |
| } |
| const prefixed = path.startsWith('/') ? path : `/${path}`; |
| const condensed = prefixed.replace(/\/{2,}/g, '/'); |
| return condensed.length > 1 && condensed.endsWith('/') ? condensed.slice(0, -1) : condensed; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export const composeDemoFullPath = (folderPath: string | null | undefined, fileName: string | null | undefined): string | null => { |
| if (!fileName || !fileName.trim()) { |
| return null; |
| } |
| const normalizedFolder = normalizeFolderPath(folderPath); |
| const safeFileName = fileName.trim(); |
| return normalizedFolder === '/' ? `/${safeFileName}` : `${normalizedFolder}/${safeFileName}`; |
| }; |
|
|
| |
| |
| |
| |
| |
| export const validateFileName = (fileName: string): { valid: boolean; message?: string } => { |
| if (!fileName || !fileName.trim()) { |
| return { valid: false, message: 'File name cannot be empty' }; |
| } |
|
|
| const trimmed = fileName.trim(); |
| |
| |
| if (trimmed.length > 255) { |
| return { valid: false, message: 'File name too long (max 255 characters)' }; |
| } |
|
|
| |
| const illegalChars = /[<>:"|?*\x00-\x1f]/; |
| if (illegalChars.test(trimmed)) { |
| return { valid: false, message: 'File name contains invalid characters (cannot contain < > : " | ? * or control characters)' }; |
| } |
|
|
| |
| const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\.|$)/i; |
| if (reservedNames.test(trimmed)) { |
| return { valid: false, message: 'File name cannot be a system reserved name' }; |
| } |
|
|
| |
| if (trimmed.startsWith('.') || trimmed.endsWith('.')) { |
| return { valid: false, message: 'File name cannot start or end with a dot' }; |
| } |
|
|
| |
| if (trimmed.includes('/') || trimmed.includes('\\')) { |
| return { valid: false, message: 'File name cannot contain path separators' }; |
| } |
|
|
| return { valid: true }; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export const getDefaultDemoName = ( |
| currentData: AnalyzeResponse | null, |
| textFieldValue: string, |
| existingFileName?: string | null |
| ): string => { |
| |
| if (existingFileName && existingFileName.trim() && existingFileName !== '未选择文件') { |
| const trimmed = existingFileName.trim(); |
| |
| const nameWithoutExt = trimmed.toLowerCase().endsWith('.json') |
| ? trimmed.slice(0, -5) |
| : trimmed; |
| if (nameWithoutExt) { |
| return nameWithoutExt; |
| } |
| } |
| |
| |
| const rawText = currentData ? currentData.request.text : textFieldValue || ''; |
| if (!rawText) { |
| return '新Demo'; |
| } |
| const firstLineBreak = rawText.search(/[\r\n]/); |
| const firstLine = firstLineBreak === -1 ? rawText : rawText.slice(0, firstLineBreak); |
| return (firstLine.length ? firstLine : '新Demo').slice(0, 50); |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export const buildFolderOptions = ( |
| folders: string[], |
| lastPath: string | null |
| ): { options: Array<{ value: string; text: string }>, defaultPath: string } => { |
| |
| const sorted = [...folders].sort((a, b) => { |
| const normA = normalizeFolderPath(a); |
| const normB = normalizeFolderPath(b); |
| if (normA === '/' && normB !== '/') return -1; |
| if (normA !== '/' && normB === '/') return 1; |
| return normA.localeCompare(normB, 'zh-CN', { numeric: true, sensitivity: 'base' }); |
| }); |
| |
| |
| const options = sorted.map(folder => ({ |
| value: folder, |
| text: folder === '' || folder === '/' ? tr('/ (Root)') : folder |
| })); |
| |
| |
| let defaultPath = '/'; |
| if (lastPath && sorted.includes(lastPath)) { |
| defaultPath = lastPath; |
| } else if (sorted.length > 0) { |
| defaultPath = sorted[0] || '/'; |
| } |
| |
| return { options, defaultPath }; |
| }; |
|
|
|
|