Initial commit
This commit is contained in:
159
shared/utils/date.ts
Normal file
159
shared/utils/date.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
export const pad2 = (n: number) => String(n).padStart(2, '0')
|
||||
|
||||
export const pad3 = (n: number) => String(n).padStart(3, '0')
|
||||
|
||||
export const formatTimestamp = (d: Date) => {
|
||||
const yyyy = d.getFullYear()
|
||||
const mm = pad2(d.getMonth() + 1)
|
||||
const dd = pad2(d.getDate())
|
||||
const hh = pad2(d.getHours())
|
||||
const mi = pad2(d.getMinutes())
|
||||
const ss = pad2(d.getSeconds())
|
||||
const ms = pad3(d.getMilliseconds())
|
||||
return `${yyyy}${mm}${dd}_${hh}${mi}${ss}_${ms}`
|
||||
}
|
||||
|
||||
export const formatDate = (date: Date) => {
|
||||
const yyyy = date.getFullYear()
|
||||
const mm = pad2(date.getMonth() + 1)
|
||||
const dd = pad2(date.getDate())
|
||||
return `${yyyy}-${mm}-${dd}`
|
||||
}
|
||||
|
||||
export const formatTime = (date: Date) => {
|
||||
const hh = pad2(date.getHours())
|
||||
const mm = pad2(date.getMinutes())
|
||||
const ss = pad2(date.getSeconds())
|
||||
return `${hh}:${mm}:${ss}`
|
||||
}
|
||||
|
||||
export const formatDateTime = (date: Date) => {
|
||||
return `${formatDate(date)} ${formatTime(date)}`
|
||||
}
|
||||
|
||||
export const getTodayDate = (): string => {
|
||||
return formatDate(new Date())
|
||||
}
|
||||
|
||||
export const getTomorrowDate = (): string => {
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
return formatDate(tomorrow)
|
||||
}
|
||||
|
||||
export const formatDateDisplay = (dateStr: string): string => {
|
||||
const today = getTodayDate()
|
||||
const tomorrow = getTomorrowDate()
|
||||
if (dateStr === today) return '今天'
|
||||
if (dateStr === tomorrow) return '明天'
|
||||
const [year, month, day] = dateStr.split('-')
|
||||
const currentYear = new Date().getFullYear()
|
||||
if (parseInt(year) === currentYear) {
|
||||
return `${parseInt(month)}月${parseInt(day)}日`
|
||||
}
|
||||
return `${year}年${parseInt(month)}月${parseInt(day)}日`
|
||||
}
|
||||
|
||||
export const formatDuration = (ms: number): string => {
|
||||
const seconds = Math.floor(ms / 1000)
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const hours = Math.floor(minutes / 60)
|
||||
|
||||
if (hours > 0) {
|
||||
const remainingMinutes = minutes % 60
|
||||
return `${hours}小时${remainingMinutes > 0 ? `${remainingMinutes}分钟` : ''}`
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}分钟`
|
||||
}
|
||||
return `${seconds}秒`
|
||||
}
|
||||
|
||||
export const formatDurationShort = (ms: number): string => {
|
||||
const seconds = Math.floor(ms / 1000)
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const hours = Math.floor(minutes / 60)
|
||||
|
||||
if (hours > 0) {
|
||||
const remainingMinutes = minutes % 60
|
||||
return remainingMinutes > 0 ? `${hours}h${remainingMinutes}m` : `${hours}h`
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}m`
|
||||
}
|
||||
return `${seconds}s`
|
||||
}
|
||||
|
||||
export const getWeekStart = (date: Date): Date => {
|
||||
const d = new Date(date)
|
||||
const day = d.getDay()
|
||||
const diff = d.getDate() - day + (day === 0 ? -6 : 1)
|
||||
d.setDate(diff)
|
||||
d.setHours(0, 0, 0, 0)
|
||||
return d
|
||||
}
|
||||
|
||||
export const getWeekEnd = (date: Date): Date => {
|
||||
const weekStart = getWeekStart(date)
|
||||
const weekEnd = new Date(weekStart)
|
||||
weekEnd.setDate(weekStart.getDate() + 6)
|
||||
return weekEnd
|
||||
}
|
||||
|
||||
export const getMonthStart = (date: Date): Date => {
|
||||
return new Date(date.getFullYear(), date.getMonth(), 1)
|
||||
}
|
||||
|
||||
export const getMonthEnd = (date: Date): Date => {
|
||||
return new Date(date.getFullYear(), date.getMonth() + 1, 0)
|
||||
}
|
||||
|
||||
export const getYearStart = (date: Date): Date => {
|
||||
return new Date(date.getFullYear(), 0, 1)
|
||||
}
|
||||
|
||||
export const getYearEnd = (date: Date): Date => {
|
||||
return new Date(date.getFullYear(), 11, 31)
|
||||
}
|
||||
|
||||
export const isSameDay = (date1: Date, date2: Date): boolean => {
|
||||
return formatDate(date1) === formatDate(date2)
|
||||
}
|
||||
|
||||
export const isToday = (date: Date): boolean => {
|
||||
return isSameDay(date, new Date())
|
||||
}
|
||||
|
||||
export const addDays = (date: Date, days: number): Date => {
|
||||
const result = new Date(date)
|
||||
result.setDate(result.getDate() + days)
|
||||
return result
|
||||
}
|
||||
|
||||
export const addMonths = (date: Date, months: number): Date => {
|
||||
const result = new Date(date)
|
||||
result.setMonth(result.getMonth() + months)
|
||||
return result
|
||||
}
|
||||
|
||||
export const addYears = (date: Date, years: number): Date => {
|
||||
const result = new Date(date)
|
||||
result.setFullYear(result.getFullYear() + years)
|
||||
return result
|
||||
}
|
||||
|
||||
export const parseDate = (dateStr: string): Date | null => {
|
||||
const match = dateStr.match(/^(\d{4})-(\d{2})-(\d{2})$/)
|
||||
if (!match) return null
|
||||
const [, year, month, day] = match
|
||||
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day))
|
||||
}
|
||||
|
||||
export const getDaysInMonth = (year: number, month: number): number => {
|
||||
return new Date(year, month, 0).getDate()
|
||||
}
|
||||
|
||||
export const getDayOfWeek = (date: Date): string => {
|
||||
const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
return days[date.getDay()]
|
||||
}
|
||||
3
shared/utils/index.ts
Normal file
3
shared/utils/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './path'
|
||||
export * from './tabType'
|
||||
export * from './date'
|
||||
54
shared/utils/path.ts
Normal file
54
shared/utils/path.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
export const toPosixPath = (p: string) => p.replace(/\\/g, '/')
|
||||
|
||||
export const toWindowsPath = (p: string) => p.replace(/\//g, '\\')
|
||||
|
||||
export const normalizePath = (p: string) => {
|
||||
const parts = p.replace(/\\/g, '/').split('/')
|
||||
const stack: string[] = []
|
||||
for (const part of parts) {
|
||||
if (!part || part === '.') continue
|
||||
if (part === '..') {
|
||||
stack.pop()
|
||||
continue
|
||||
}
|
||||
stack.push(part)
|
||||
}
|
||||
return stack.join('/')
|
||||
}
|
||||
|
||||
export const joinPaths = (...paths: string[]) => {
|
||||
return paths.map(normalizePath).join('/').replace(/\/+/g, '/')
|
||||
}
|
||||
|
||||
export const getDirectoryName = (filePath: string) => {
|
||||
const normalized = normalizePath(filePath)
|
||||
const lastSlash = normalized.lastIndexOf('/')
|
||||
return lastSlash === -1 ? '' : normalized.slice(0, lastSlash)
|
||||
}
|
||||
|
||||
export const getFileName = (filePath: string) => {
|
||||
const normalized = normalizePath(filePath)
|
||||
const lastSlash = normalized.lastIndexOf('/')
|
||||
return lastSlash === -1 ? normalized : normalized.slice(lastSlash + 1)
|
||||
}
|
||||
|
||||
export const getFileExtension = (filePath: string) => {
|
||||
const fileName = getFileName(filePath)
|
||||
const lastDot = fileName.lastIndexOf('.')
|
||||
return lastDot === -1 ? '' : fileName.slice(lastDot + 1)
|
||||
}
|
||||
|
||||
export const removeExtension = (filePath: string) => {
|
||||
const ext = getFileExtension(filePath)
|
||||
if (!ext) return filePath
|
||||
return filePath.slice(0, -(ext.length + 1))
|
||||
}
|
||||
|
||||
export const isAbsolutePath = (p: string) => {
|
||||
return p.startsWith('/') || /^[A-Za-z]:/.test(p)
|
||||
}
|
||||
|
||||
export const isHiddenPath = (p: string) => {
|
||||
const normalized = normalizePath(p)
|
||||
return normalized.split('/').some(part => part.startsWith('.'))
|
||||
}
|
||||
65
shared/utils/tabType.ts
Normal file
65
shared/utils/tabType.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { TabType } from '../types/tab.js'
|
||||
|
||||
const KNOWN_MODULE_IDS = [
|
||||
'home', 'settings', 'search', 'weread',
|
||||
'recycle-bin', 'todo', 'time-tracking', 'pydemos'
|
||||
] as const
|
||||
|
||||
export function getTabTypeFromPath(filePath: string | null): TabType {
|
||||
if (!filePath) return 'other'
|
||||
|
||||
if (filePath.startsWith('remote-git://')) {
|
||||
return 'remote-git'
|
||||
}
|
||||
|
||||
if (filePath.startsWith('remote-desktop://')) {
|
||||
return 'remote-desktop'
|
||||
}
|
||||
|
||||
if (filePath.startsWith('remote-') && filePath !== 'remote-tab') {
|
||||
return 'remote-desktop'
|
||||
}
|
||||
|
||||
if (filePath === 'remote-tab' || filePath === 'remote') {
|
||||
return 'remote'
|
||||
}
|
||||
|
||||
for (const moduleId of KNOWN_MODULE_IDS) {
|
||||
if (filePath === `${moduleId}-tab` || filePath === moduleId) {
|
||||
if (moduleId === 'home' || moduleId === 'settings' || moduleId === 'search' || moduleId === 'weread') {
|
||||
return 'other'
|
||||
}
|
||||
return moduleId as TabType
|
||||
}
|
||||
}
|
||||
|
||||
if (filePath.endsWith('.md')) {
|
||||
return 'markdown'
|
||||
}
|
||||
|
||||
return 'other'
|
||||
}
|
||||
|
||||
export function getFileNameFromPath(filePath: string | null): string {
|
||||
if (!filePath) return '未知'
|
||||
|
||||
for (const moduleId of KNOWN_MODULE_IDS) {
|
||||
if (filePath === `${moduleId}-tab` || filePath === moduleId) {
|
||||
const names: Record<string, string> = {
|
||||
'home': '首页',
|
||||
'settings': '设置',
|
||||
'search': '搜索',
|
||||
'weread': '微信读书',
|
||||
'recycle-bin': '回收站',
|
||||
'todo': 'TODO',
|
||||
'time-tracking': '时间统计',
|
||||
'pydemos': 'Python Demo',
|
||||
'remote': '远程桌面',
|
||||
}
|
||||
return names[moduleId] ?? moduleId
|
||||
}
|
||||
}
|
||||
|
||||
const parts = filePath.split('/')
|
||||
return parts[parts.length - 1] || filePath
|
||||
}
|
||||
Reference in New Issue
Block a user