mirror of
https://codeberg.org/HPCesia/AstralHalo.git
synced 2025-04-08 17:34:27 +08:00
109 lines
3.6 KiB
JavaScript
109 lines
3.6 KiB
JavaScript
import dayjs from 'dayjs';
|
|
import timezone from 'dayjs/plugin/timezone.js';
|
|
import utc from 'dayjs/plugin/utc.js';
|
|
import { promises as fs } from 'fs';
|
|
import path from 'path';
|
|
|
|
dayjs.extend(utc);
|
|
dayjs.extend(timezone);
|
|
|
|
/**
|
|
* 将不同格式的时区字符串转换为标准时区偏移量
|
|
* @param {string} timezone - 时区字符串,支持以下格式:
|
|
* 1. 标准时区名称 (如 "Asia/Shanghai")
|
|
* 2. 仅小时偏移 (如 "+8", "-10", "+08")
|
|
* 3. 完整偏移 (如 "+08:00", "-7:30")
|
|
* @returns {string} 标准时区偏移量 (如 "+08:00", "-07:30")
|
|
*/
|
|
export function parseTimezoneOffset(timezone) {
|
|
// 尝试匹配完整的时区偏移格式 (+08:00)
|
|
const fullOffsetMatch = timezone.match(/^([+-])(\d{1,2}):(\d{2})$/);
|
|
if (fullOffsetMatch) {
|
|
const [, sign, hours, minutes] = fullOffsetMatch;
|
|
const paddedHours = hours.padStart(2, '0');
|
|
return `${sign}${paddedHours}:${minutes}`;
|
|
}
|
|
|
|
// 尝试匹配仅小时的偏移格式 (+8, +08)
|
|
const hourOffsetMatch = timezone.match(/^([+-])(\d{1,2})$/);
|
|
if (hourOffsetMatch) {
|
|
const [, sign, hours] = hourOffsetMatch;
|
|
const paddedHours = hours.padStart(2, '0');
|
|
return `${sign}${paddedHours}:00`;
|
|
}
|
|
|
|
// 处理标准时区名称 (如 "Asia/Shanghai")
|
|
try {
|
|
// 使用 dayjs 获取指定时区的偏移量
|
|
const date = dayjs().tz(timezone);
|
|
if (!date.isValid()) {
|
|
throw new Error('Invalid timezone');
|
|
}
|
|
|
|
const offset = date.utcOffset();
|
|
const hours = Math.floor(Math.abs(offset) / 60);
|
|
const minutes = Math.abs(offset) % 60;
|
|
const sign = offset >= 0 ? '+' : '-';
|
|
return `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
|
} catch {
|
|
throw new Error(`Invalid timezone format: ${timezone}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 将标题转换为合法的文件名
|
|
* @param {string} title - 原始标题
|
|
* @returns {string} 转换后的合法文件名,不包含特殊字符
|
|
*/
|
|
export function sanitizeTitle(title) {
|
|
return title
|
|
.replace(/[/\\:*?"<>|]/g, '-') // 替换文件系统不允许的字符
|
|
.replace(/[. ]+/g, '-') // 将一个或多个点号/空格替换为单个连字符
|
|
.replace(/^-+|-+$/g, ''); // 移除首尾的连字符
|
|
}
|
|
|
|
/**
|
|
* 获取文件路径
|
|
* @param {string} contentDir - 内容目录路径
|
|
* @param {string} title - 文件名
|
|
* @param {boolean} isDir - 是否创建为目录形式
|
|
* @returns {string} 完整的文件路径
|
|
*/
|
|
export function getFilePath(contentDir, title, isDir) {
|
|
return isDir
|
|
? path.join(contentDir, title, 'index.md')
|
|
: path.join(contentDir, `${title}.md`);
|
|
}
|
|
|
|
/**
|
|
* 检查文件是否存在,同时检查单文件和目录两种形式
|
|
* @param {string} contentDir - 内容目录路径
|
|
* @param {string} sanitizedTitle - 已转换的合法文件名
|
|
* @param {number} [counter] - 可选的编号,用于检查带编号的文件名
|
|
* @returns {Promise<{exists: boolean, filePath: string|null}>} 文件存在状态和路径
|
|
*/
|
|
export async function checkFileExists(contentDir, sanitizedTitle, counter) {
|
|
const title = counter ? `${sanitizedTitle}-${counter}` : sanitizedTitle;
|
|
const filePath = getFilePath(contentDir, title, false);
|
|
const dirPath = getFilePath(contentDir, title, true);
|
|
|
|
try {
|
|
const results = await Promise.all([
|
|
fs
|
|
.access(filePath)
|
|
.then(() => true)
|
|
.catch(() => false),
|
|
fs
|
|
.access(dirPath)
|
|
.then(() => true)
|
|
.catch(() => false),
|
|
]);
|
|
return {
|
|
exists: results[0] || results[1],
|
|
filePath: results[0] ? filePath : results[1] ? dirPath : null,
|
|
};
|
|
} catch {
|
|
return { exists: false, filePath: null };
|
|
}
|
|
}
|