Initial commit

This commit is contained in:
2026-03-08 01:34:54 +08:00
commit 1f104f73c8
441 changed files with 64911 additions and 0 deletions

159
shared/utils/date.ts Normal file
View 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
View File

@@ -0,0 +1,3 @@
export * from './path'
export * from './tabType'
export * from './date'

54
shared/utils/path.ts Normal file
View 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
View 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
}