refactor(admin): split StepHeader, SDKSidebar, ScopeWizardTab, PIIRulesTab, ReviewExportStep, DocumentUploadSection components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
// =============================================================================
|
||||
// DocumentUpload icon components — extracted from DocumentUploadSection for LOC compliance
|
||||
// =============================================================================
|
||||
|
||||
export const UploadIcon = () => (
|
||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const QRIcon = () => (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const DocumentIcon = () => (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CheckIcon = () => (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const EditIcon = () => (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CloseIcon = () => (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
)
|
||||
@@ -0,0 +1,123 @@
|
||||
'use client'
|
||||
// =============================================================================
|
||||
// QRCodeModal — extracted from DocumentUploadSection for LOC compliance
|
||||
// =============================================================================
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { QRIcon, CloseIcon } from './DocumentUploadIcons'
|
||||
|
||||
interface QRModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
sessionId: string
|
||||
onFileUploaded?: (file: File) => void
|
||||
}
|
||||
|
||||
export function QRCodeModal({ isOpen, onClose, sessionId }: QRModalProps) {
|
||||
const [uploadUrl, setUploadUrl] = useState('')
|
||||
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
|
||||
let baseUrl = typeof window !== 'undefined' ? window.location.origin : ''
|
||||
|
||||
// Hostname to IP mapping for local network
|
||||
const hostnameToIP: Record<string, string> = {
|
||||
'macmini': '192.168.178.100',
|
||||
'macmini.local': '192.168.178.100',
|
||||
}
|
||||
|
||||
Object.entries(hostnameToIP).forEach(([hostname, ip]) => {
|
||||
if (baseUrl.includes(hostname)) {
|
||||
baseUrl = baseUrl.replace(hostname, ip)
|
||||
}
|
||||
})
|
||||
|
||||
// Force HTTP for mobile access (SSL cert is for hostname, not IP)
|
||||
// This is safe because it's only used on the local network
|
||||
if (baseUrl.startsWith('https://')) {
|
||||
baseUrl = baseUrl.replace('https://', 'http://')
|
||||
}
|
||||
|
||||
const uploadPath = `/upload/sdk/${sessionId}`
|
||||
const fullUrl = `${baseUrl}${uploadPath}`
|
||||
setUploadUrl(fullUrl)
|
||||
|
||||
const qrApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(fullUrl)}`
|
||||
setQrCodeUrl(qrApiUrl)
|
||||
}, [isOpen, sessionId])
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(uploadUrl)
|
||||
} catch (err) {
|
||||
console.error('Copy failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />
|
||||
<div className="relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-purple-100 flex items-center justify-center">
|
||||
<QRIcon />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Mit Handy hochladen</h3>
|
||||
<p className="text-sm text-gray-500">QR-Code scannen</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-lg">
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="p-4 bg-white border border-gray-200 rounded-xl">
|
||||
{qrCodeUrl ? (
|
||||
<img src={qrCodeUrl} alt="QR Code" className="w-[200px] h-[200px]" />
|
||||
) : (
|
||||
<div className="w-[200px] h-[200px] flex items-center justify-center">
|
||||
<div className="w-8 h-8 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mt-4 text-center text-sm text-gray-600">
|
||||
Scannen Sie den Code mit Ihrem Handy,<br />
|
||||
um Dokumente hochzuladen.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 w-full">
|
||||
<p className="text-xs text-gray-400 mb-2">Oder Link teilen:</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={uploadUrl}
|
||||
readOnly
|
||||
className="flex-1 px-3 py-2 text-sm border border-gray-200 rounded-lg bg-gray-50"
|
||||
/>
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||
>
|
||||
Kopieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-amber-50 border border-amber-200 rounded-lg w-full">
|
||||
<p className="text-xs text-amber-800">
|
||||
<strong>Hinweis:</strong> Ihr Handy muss im gleichen Netzwerk sein.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,257 +1,30 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useCallback, useEffect } from 'react'
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import {
|
||||
type UploadedDocument,
|
||||
type DocumentUploadSectionProps,
|
||||
formatFileSize,
|
||||
detectVersionFromFilename,
|
||||
suggestNextVersion,
|
||||
} from './DocumentUploadTypes'
|
||||
import {
|
||||
UploadIcon,
|
||||
QRIcon,
|
||||
DocumentIcon,
|
||||
CheckIcon,
|
||||
EditIcon,
|
||||
CloseIcon,
|
||||
} from './DocumentUploadIcons'
|
||||
import { QRCodeModal } from './DocumentUploadQRModal'
|
||||
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
// =============================================================================
|
||||
|
||||
export interface UploadedDocument {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
size: number
|
||||
uploadedAt: Date
|
||||
extractedVersion?: string
|
||||
extractedContent?: ExtractedContent
|
||||
status: 'uploading' | 'processing' | 'ready' | 'error'
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface ExtractedContent {
|
||||
title?: string
|
||||
version?: string
|
||||
lastModified?: string
|
||||
sections?: ExtractedSection[]
|
||||
metadata?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface ExtractedSection {
|
||||
title: string
|
||||
content: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
export interface DocumentUploadSectionProps {
|
||||
/** Type of document being uploaded (tom, dsfa, vvt, loeschfristen, etc.) */
|
||||
documentType: 'tom' | 'dsfa' | 'vvt' | 'loeschfristen' | 'consent' | 'policy' | 'custom'
|
||||
/** Title displayed in the upload section */
|
||||
title?: string
|
||||
/** Description text */
|
||||
description?: string
|
||||
/** Accepted file types */
|
||||
acceptedTypes?: string
|
||||
/** Callback when document is uploaded and processed */
|
||||
onDocumentProcessed?: (doc: UploadedDocument) => void
|
||||
/** Callback to open document in workflow editor */
|
||||
onOpenInEditor?: (doc: UploadedDocument) => void
|
||||
/** Session ID for QR upload */
|
||||
sessionId?: string
|
||||
/** Custom CSS classes */
|
||||
className?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ICONS
|
||||
// =============================================================================
|
||||
|
||||
const UploadIcon = () => (
|
||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const QRIcon = () => (
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const DocumentIcon = () => (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const EditIcon = () => (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const CloseIcon = () => (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
function detectVersionFromFilename(filename: string): string | undefined {
|
||||
// Common version patterns: v1.0, V2.1, _v3, -v1.2.3, version-2
|
||||
const patterns = [
|
||||
/[vV](\d+(?:\.\d+)*)/,
|
||||
/version[_-]?(\d+(?:\.\d+)*)/i,
|
||||
/[_-]v?(\d+\.\d+(?:\.\d+)?)[_-]/,
|
||||
]
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = filename.match(pattern)
|
||||
if (match) {
|
||||
return match[1]
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function suggestNextVersion(currentVersion?: string): string {
|
||||
if (!currentVersion) return '1.0'
|
||||
|
||||
const parts = currentVersion.split('.').map(Number)
|
||||
if (parts.length >= 2) {
|
||||
parts[parts.length - 1] += 1
|
||||
} else {
|
||||
parts.push(1)
|
||||
}
|
||||
return parts.join('.')
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// QR CODE MODAL
|
||||
// =============================================================================
|
||||
|
||||
interface QRModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
sessionId: string
|
||||
onFileUploaded?: (file: File) => void
|
||||
}
|
||||
|
||||
function QRCodeModal({ isOpen, onClose, sessionId }: QRModalProps) {
|
||||
const [uploadUrl, setUploadUrl] = useState('')
|
||||
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
|
||||
let baseUrl = typeof window !== 'undefined' ? window.location.origin : ''
|
||||
|
||||
// Hostname to IP mapping for local network
|
||||
const hostnameToIP: Record<string, string> = {
|
||||
'macmini': '192.168.178.100',
|
||||
'macmini.local': '192.168.178.100',
|
||||
}
|
||||
|
||||
Object.entries(hostnameToIP).forEach(([hostname, ip]) => {
|
||||
if (baseUrl.includes(hostname)) {
|
||||
baseUrl = baseUrl.replace(hostname, ip)
|
||||
}
|
||||
})
|
||||
|
||||
// Force HTTP for mobile access (SSL cert is for hostname, not IP)
|
||||
// This is safe because it's only used on the local network
|
||||
if (baseUrl.startsWith('https://')) {
|
||||
baseUrl = baseUrl.replace('https://', 'http://')
|
||||
}
|
||||
|
||||
const uploadPath = `/upload/sdk/${sessionId}`
|
||||
const fullUrl = `${baseUrl}${uploadPath}`
|
||||
setUploadUrl(fullUrl)
|
||||
|
||||
const qrApiUrl = `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(fullUrl)}`
|
||||
setQrCodeUrl(qrApiUrl)
|
||||
}, [isOpen, sessionId])
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(uploadUrl)
|
||||
} catch (err) {
|
||||
console.error('Copy failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />
|
||||
<div className="relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-purple-100 flex items-center justify-center">
|
||||
<QRIcon />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">Mit Handy hochladen</h3>
|
||||
<p className="text-sm text-gray-500">QR-Code scannen</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-lg">
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="p-4 bg-white border border-gray-200 rounded-xl">
|
||||
{qrCodeUrl ? (
|
||||
<img src={qrCodeUrl} alt="QR Code" className="w-[200px] h-[200px]" />
|
||||
) : (
|
||||
<div className="w-[200px] h-[200px] flex items-center justify-center">
|
||||
<div className="w-8 h-8 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mt-4 text-center text-sm text-gray-600">
|
||||
Scannen Sie den Code mit Ihrem Handy,<br />
|
||||
um Dokumente hochzuladen.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 w-full">
|
||||
<p className="text-xs text-gray-400 mb-2">Oder Link teilen:</p>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={uploadUrl}
|
||||
readOnly
|
||||
className="flex-1 px-3 py-2 text-sm border border-gray-200 rounded-lg bg-gray-50"
|
||||
/>
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="px-4 py-2 text-sm bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||
>
|
||||
Kopieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-amber-50 border border-amber-200 rounded-lg w-full">
|
||||
<p className="text-xs text-amber-800">
|
||||
<strong>Hinweis:</strong> Ihr Handy muss im gleichen Netzwerk sein.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export type {
|
||||
UploadedDocument,
|
||||
ExtractedContent,
|
||||
ExtractedSection,
|
||||
DocumentUploadSectionProps,
|
||||
} from './DocumentUploadTypes'
|
||||
|
||||
// =============================================================================
|
||||
// MAIN COMPONENT
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// =============================================================================
|
||||
// DocumentUpload shared types — extracted from DocumentUploadSection for LOC compliance
|
||||
// =============================================================================
|
||||
|
||||
export interface UploadedDocument {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
size: number
|
||||
uploadedAt: Date
|
||||
extractedVersion?: string
|
||||
extractedContent?: ExtractedContent
|
||||
status: 'uploading' | 'processing' | 'ready' | 'error'
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface ExtractedContent {
|
||||
title?: string
|
||||
version?: string
|
||||
lastModified?: string
|
||||
sections?: ExtractedSection[]
|
||||
metadata?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface ExtractedSection {
|
||||
title: string
|
||||
content: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
export interface DocumentUploadSectionProps {
|
||||
/** Type of document being uploaded (tom, dsfa, vvt, loeschfristen, etc.) */
|
||||
documentType: 'tom' | 'dsfa' | 'vvt' | 'loeschfristen' | 'consent' | 'policy' | 'custom'
|
||||
/** Title displayed in the upload section */
|
||||
title?: string
|
||||
/** Description text */
|
||||
description?: string
|
||||
/** Accepted file types */
|
||||
acceptedTypes?: string
|
||||
/** Callback when document is uploaded and processed */
|
||||
onDocumentProcessed?: (doc: UploadedDocument) => void
|
||||
/** Callback to open document in workflow editor */
|
||||
onOpenInEditor?: (doc: UploadedDocument) => void
|
||||
/** Session ID for QR upload */
|
||||
sessionId?: string
|
||||
/** Custom CSS classes */
|
||||
className?: string
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Helper functions
|
||||
// =============================================================================
|
||||
|
||||
export function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
export function detectVersionFromFilename(filename: string): string | undefined {
|
||||
// Common version patterns: v1.0, V2.1, _v3, -v1.2.3, version-2
|
||||
const patterns = [
|
||||
/[vV](\d+(?:\.\d+)*)/,
|
||||
/version[_-]?(\d+(?:\.\d+)*)/i,
|
||||
/[_-]v?(\d+\.\d+(?:\.\d+)?)[_-]/,
|
||||
]
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = filename.match(pattern)
|
||||
if (match) {
|
||||
return match[1]
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function suggestNextVersion(currentVersion?: string): string {
|
||||
if (!currentVersion) return '1.0'
|
||||
|
||||
const parts = currentVersion.split('.').map(Number)
|
||||
if (parts.length >= 2) {
|
||||
parts[parts.length - 1] += 1
|
||||
} else {
|
||||
parts.push(1)
|
||||
}
|
||||
return parts.join('.')
|
||||
}
|
||||
Reference in New Issue
Block a user