Init public

This commit is contained in:
maigamo 2025-04-22 13:59:28 +08:00
parent ec7ae825f2
commit 3f9dce59a7
27 changed files with 6949 additions and 1466 deletions

15
.gitignore vendored Normal file
View file

@ -0,0 +1,15 @@
# node modules
node_modules/
# build artifacts
main.js
main.js.map
data.json
# editor settings
.vscode/
.idea/
# operating system files
.DS_Store
Thumbs.db

48
esbuild.config.mjs Normal file
View file

@ -0,0 +1,48 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = process.argv[2] === "production";
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["src/main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
}

1466
main.js

File diff suppressed because it is too large Load diff

2363
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

28
package.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "daily-task-auto-generator",
"version": "1.0.0",
"description": "Obsidian plugin to automatically generate daily tasks",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [
"obsidian",
"tasks",
"automation"
],
"author": "maigamo",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"builtin-modules": "^3.3.0",
"esbuild": "0.17.3",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
}

10
src/ambient.d.ts vendored Normal file
View file

@ -0,0 +1,10 @@
/**
* HTMLElement接口Obsidian特有的方法
*/
interface HTMLElement {
addClass(cls: string): void;
removeClass(cls: string): void;
toggleClass(cls: string): void;
empty(): void;
createEl<K extends keyof HTMLElementTagNameMap>(tag: K, attrs?: object): HTMLElementTagNameMap[K];
}

178
src/i18n/i18n.js Normal file

File diff suppressed because one or more lines are too long

326
src/i18n/i18n.ts Normal file
View file

@ -0,0 +1,326 @@
/**
* i18n.ts
*
*/
// 定义翻译键类型
export type TranslationKey =
// 设置页面
| 'settings.title'
| 'settings.rootDir'
| 'settings.rootDir.desc'
| 'settings.rootDir.saved'
| 'settings.save'
| 'settings.autoGenerate'
| 'settings.autoGenerate.desc'
| 'settings.mode.none'
| 'settings.mode.daily'
| 'settings.mode.workday'
| 'settings.language'
| 'settings.language.desc'
| 'settings.language.auto'
| 'settings.language.zh'
| 'settings.language.en'
| 'settings.animations'
| 'settings.animations.desc'
| 'settings.template'
| 'settings.template.zh'
| 'settings.template.en'
| 'settings.template.preview'
| 'settings.template.hide'
| 'settings.resetDefault'
| 'settings.addTaskButton'
| 'settings.resetToDefault'
| 'settings.notificationDuration'
| 'settings.notificationDuration.desc'
| 'settings.preview'
| 'template.dateWithIcon'
// 通知
| 'notification.taskAdded'
| 'notification.taskExists'
| 'notification.error'
// 星期
| 'weekday.mon'
| 'weekday.tue'
| 'weekday.wed'
| 'weekday.thu'
| 'weekday.fri'
| 'weekday.sat'
| 'weekday.sun'
// 插件名称和描述
| 'plugin.name'
| 'plugin.description'
// 一般按钮和消息
| 'button.addTask'
| 'button.save'
| 'button.cancel'
| 'button.done'
// 设置
| 'settings.basicSettings'
| 'settings.templateSettings'
| 'settings.rootDir'
| 'settings.rootDir.desc'
| 'settings.rootDir.saved'
| 'settings.save'
| 'settings.autoGenerate'
| 'settings.autoGenerate.desc'
| 'settings.mode.none'
| 'settings.mode.daily'
| 'settings.mode.workday'
| 'settings.language'
| 'settings.language.desc'
| 'settings.language.auto'
| 'settings.language.zh'
| 'settings.language.en'
| 'settings.animations'
| 'settings.animations.desc'
| 'settings.template'
| 'settings.template.zh'
| 'settings.template.en'
| 'settings.template.preview'
| 'settings.template.hide'
| 'settings.resetDefault'
| 'settings.addTaskButton'
| 'settings.resetToDefault'
| 'settings.notificationDuration'
| 'settings.notificationDuration.desc'
| 'settings.preview'
| 'template.dateWithIcon'
// 通知
| 'notification.taskAdded'
| 'notification.taskExists'
| 'notification.error'
// 星期
| 'weekday.mon'
| 'weekday.tue'
| 'weekday.wed'
| 'weekday.thu'
| 'weekday.fri'
| 'weekday.sat'
| 'weekday.sun'
// 插件名称和描述
| 'plugin.name'
| 'plugin.description'
// 一般按钮和消息
| 'button.addTask'
| 'button.save'
| 'button.cancel'
| 'button.done'
// 添加任务统计相关翻译
| 'settings.taskStatistics'
| 'settings.taskStatistics.desc'
| 'statistics.title'
| 'statistics.totalTasks'
| 'statistics.completedTasks'
| 'statistics.completionRate'
| 'statistics.unfinishedTasks'
| 'statistics.suggestions'
| 'statistics.moreTasks.singular'
| 'statistics.moreTasks.plural';
// 中文翻译
const translationsZH: Record<TranslationKey, string> = {
// 设置页面
'settings.title': '每日任务自动生成器设置',
'settings.rootDir': '📁 任务文件存放目录',
'settings.rootDir.desc': '指定保存任务文件的根目录,任务将按"年份/月份.md"格式存储',
'settings.rootDir.saved': '✓ 目录已保存',
'settings.save': '💾 保存',
'settings.autoGenerate': '🔄 自动生成模式',
'settings.autoGenerate.desc': '选择何时自动生成每日任务',
'settings.mode.none': '❌ 关闭',
'settings.mode.daily': '📆 每天',
'settings.mode.workday': '💼 仅工作日',
'settings.language': '🔤 界面语言',
'settings.language.desc': '选择插件界面显示的语言',
'settings.language.auto': '🔍 自动检测',
'settings.language.zh': '🇨🇳 中文',
'settings.language.en': '🇬🇧 英文',
'settings.animations': '✨ 动画效果',
'settings.animations.desc': '启用界面动画效果',
'settings.template': '📝 任务模板',
'settings.template.zh': '🇨🇳 中文模板',
'settings.template.en': '🇬🇧 英文模板',
'settings.template.preview': '👁️ 显示预览',
'settings.template.hide': '👁️‍🗨️ 隐藏预览',
'settings.resetToDefault': '🔄 恢复默认设置',
'settings.addTaskButton': ' 手动添加今日任务',
'settings.notificationDuration': '⏱️ 通知显示时间',
'settings.notificationDuration.desc': '成功/失败提示显示时间(毫秒)',
'settings.preview': '预览模板效果',
'settings.resetDefault': '恢复默认设置',
'template.dateWithIcon': '带图标的当前日期',
'settings.basicSettings': '基本设置',
'settings.templateSettings': '模板设置',
// 通知
'notification.taskAdded': '今日任务已添加',
'notification.taskExists': '今日任务已存在',
'notification.error': '错误:',
// 星期
'weekday.mon': '星期一',
'weekday.tue': '星期二',
'weekday.wed': '星期三',
'weekday.thu': '星期四',
'weekday.fri': '星期五',
'weekday.sat': '星期六',
'weekday.sun': '星期日',
// 插件名称和描述
'plugin.name': '每日任务自动生成器',
'plugin.description': '一个强大的任务自动生成器,帮助你高效地管理日常任务',
// 一般按钮和消息
'button.addTask': '添加任务',
'button.save': '保存',
'button.cancel': '取消',
'button.done': '完成',
// 添加任务统计相关翻译
'settings.taskStatistics': '📊 任务完成统计',
'settings.taskStatistics.desc': '开启后,每日生成任务前会自动统计前一天的任务完成情况',
'statistics.title': '📊 昨日任务统计',
'statistics.totalTasks': '任务总数',
'statistics.completedTasks': '已完成任务',
'statistics.completionRate': '完成率',
'statistics.unfinishedTasks': '未完成的任务',
'statistics.suggestions': '建议今日考虑完成以下任务',
'statistics.moreTasks.singular': '还有1个未完成任务',
'statistics.moreTasks.plural': '还有更多未完成任务',
};
// 英文翻译
const translationsEN: Record<TranslationKey, string> = {
// 设置页面
'settings.title': 'Daily Task Auto Generator Settings',
'settings.rootDir': '📁 Task Directory',
'settings.rootDir.desc': 'Specify the root directory for storing task files, tasks will be stored in "Year/Month.md" format',
'settings.rootDir.saved': '✓ Directory saved',
'settings.save': '💾 Save',
'settings.autoGenerate': '🔄 Auto Generate Mode',
'settings.autoGenerate.desc': 'Choose when to automatically generate daily tasks',
'settings.mode.none': '❌ Off',
'settings.mode.daily': '📆 Daily',
'settings.mode.workday': '💼 Workdays Only',
'settings.language': '🔤 Interface Language',
'settings.language.desc': 'Select the language for the plugin interface',
'settings.language.auto': '🔍 Auto Detect',
'settings.language.zh': '🇨🇳 Chinese',
'settings.language.en': '🇬🇧 English',
'settings.animations': '✨ Animation Effects',
'settings.animations.desc': 'Enable interface animation effects',
'settings.template': '📝 Task Template',
'settings.template.zh': '🇨🇳 Chinese Template',
'settings.template.en': '🇬🇧 English Template',
'settings.template.preview': '👁️ Show Preview',
'settings.template.hide': '👁️‍🗨️ Hide Preview',
'settings.resetToDefault': '🔄 Reset to Default',
'settings.addTaskButton': ' Add Today\'s Task Manually',
'settings.notificationDuration': '⏱️ Notification Duration',
'settings.notificationDuration.desc': 'Duration to show success/failure notifications (milliseconds)',
'settings.preview': 'Preview Template',
'settings.resetDefault': 'Reset to Default',
'template.dateWithIcon': 'Current date with icon',
'settings.basicSettings': 'Basic Settings',
'settings.templateSettings': 'Template Settings',
// 通知
'notification.taskAdded': 'Today\'s task has been added',
'notification.taskExists': 'Today\'s task already exists',
'notification.error': 'Error: ',
// 星期
'weekday.mon': 'Monday',
'weekday.tue': 'Tuesday',
'weekday.wed': 'Wednesday',
'weekday.thu': 'Thursday',
'weekday.fri': 'Friday',
'weekday.sat': 'Saturday',
'weekday.sun': 'Sunday',
// 插件名称和描述
'plugin.name': 'Daily Task Auto Generator',
'plugin.description': 'A powerful task auto generator to help you efficiently manage daily tasks',
// 一般按钮和消息
'button.addTask': 'Add Task',
'button.save': 'Save',
'button.cancel': 'Cancel',
'button.done': 'Done',
// 添加任务统计相关翻译
'settings.taskStatistics': '📊 Task Completion Statistics',
'settings.taskStatistics.desc': 'When enabled, automatically analyze yesterday\'s task completion before generating today\'s tasks',
'statistics.title': '📊 Yesterday\'s Task Summary',
'statistics.totalTasks': 'Total Tasks',
'statistics.completedTasks': 'Completed Tasks',
'statistics.completionRate': 'Completion Rate',
'statistics.unfinishedTasks': 'Unfinished Tasks',
'statistics.suggestions': 'Consider completing the following tasks today',
'statistics.moreTasks.singular': 'more task',
'statistics.moreTasks.plural': 'more tasks',
};
// 翻译查找表
const translations: Record<string, Record<TranslationKey, string>> = {
'zh': translationsZH,
'en': translationsEN
};
// 当前语言
let currentLanguage = 'en';
/**
*
* @param language
*/
export function setCurrentLanguage(language: string): void {
currentLanguage = language;
}
/**
*
* @param key
* @param fallbackLanguage
* @returns
*/
export function getTranslation(key: TranslationKey, fallbackLanguage?: string): string {
const language = fallbackLanguage || currentLanguage;
// 获取对应语言的翻译
const translation = translations[language]?.[key];
// 如果没有找到对应翻译,尝试使用英文,再没有则返回键名
if (!translation) {
return translations['en'][key] || key;
}
return translation;
}
/**
*
* @param dayOfWeek (0-6, 0)
* @returns
*/
export function getLocalizedWeekday(dayOfWeek: number): string {
const weekdayKeys: TranslationKey[] = [
'weekday.sun', 'weekday.mon', 'weekday.tue',
'weekday.wed', 'weekday.thu', 'weekday.fri', 'weekday.sat'
];
// 确保dayOfWeek在有效范围内
const normalizedDayOfWeek = ((dayOfWeek % 7) + 7) % 7;
return getTranslation(weekdayKeys[normalizedDayOfWeek]);
}

100
src/main.ts Normal file
View file

@ -0,0 +1,100 @@
import { Plugin, addIcon } from 'obsidian';
import { AutoGenerateMode } from './models/settings';
import { DailyTaskSettingTab, SettingsManager } from './settings/settings';
import { setCurrentLanguage } from './i18n/i18n';
import { isWorkday } from './utils/dateUtils';
import { TaskGenerator } from './taskGenerator';
// 定义插件图标
const ICON = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
</svg>`;
/**
*
*/
export default class DailyTaskPlugin extends Plugin {
// 设置管理器
settingsManager: SettingsManager;
// 任务生成器
taskGenerator: TaskGenerator;
/**
*
*/
async onload() {
console.log('Loading Daily Task Auto Generator plugin');
// 添加插件图标
addIcon('daily-task', ICON);
// 初始化设置管理器
this.settingsManager = new SettingsManager(this);
await this.settingsManager.loadSettings();
// 初始化任务生成器
this.taskGenerator = new TaskGenerator(this.app, this.settingsManager);
// 设置语言
setCurrentLanguage(this.settingsManager.getCurrentLanguage());
// 添加设置标签页
this.addSettingTab(new DailyTaskSettingTab(this.app, this));
// 添加手动生成任务命令
this.addCommand({
id: 'add-daily-task',
name: '手动添加今日任务',
callback: async () => {
await this.taskGenerator.addTaskManually();
}
});
// 延迟10秒后检查是否需要自动生成任务
// 这样可以确保Obsidian完全加载避免与启动过程冲突
console.log('计划在10秒后检查是否需要自动生成任务');
setTimeout(async () => {
console.log('开始检查是否需要自动生成任务');
await this.checkAutoGenerate();
}, 10000);
}
/**
*
*/
onunload() {
console.log('Unloading Daily Task Auto Generator plugin');
}
/**
*
*/
private async checkAutoGenerate() {
const settings = this.settingsManager.getSettings();
switch (settings.autoGenerateMode) {
case AutoGenerateMode.DAILY:
// 每天自动生成(静默模式,不打开文件,减少日志)
console.log('根据设置,每天自动生成任务(静默模式)');
await this.taskGenerator.generateDailyTask(false, true);
break;
case AutoGenerateMode.WORKDAY:
// 工作日自动生成(静默模式,不打开文件,减少日志)
if (isWorkday()) {
console.log('根据设置,工作日自动生成任务(静默模式)');
await this.taskGenerator.generateDailyTask(false, true);
} else {
console.log('今天不是工作日,跳过自动生成任务');
}
break;
case AutoGenerateMode.NONE:
default:
// 不自动生成
console.log('已禁用自动生成任务功能');
break;
}
}
}

69
src/models/settings.js Normal file
View file

@ -0,0 +1,69 @@
/**
* 自动生成模式枚举
*/
export var AutoGenerateMode;
(function (AutoGenerateMode) {
AutoGenerateMode["NONE"] = "none";
AutoGenerateMode["DAILY"] = "daily";
AutoGenerateMode["WORKDAY"] = "workday"; // 仅工作日自动生成
})(AutoGenerateMode || (AutoGenerateMode = {}));
/**
* 设置界面语言
*/
export var Language;
(function (Language) {
Language["AUTO"] = "auto";
Language["ZH"] = "zh";
Language["EN"] = "en";
})(Language || (Language = {}));
/**
* 默认中文模板
*/
export const DEFAULT_TEMPLATE_ZH = `## {{dateWithIcon}}{{weekday}}
### 🧘 今日计划
---
- [ ] 冥想 10 分钟
- [ ] 复盘前一日计划
- [ ] 阅读 20 页书
### 📝 工作任务
---
- [ ] 整理今日工作计划
- [ ] 完成重要项目进度
`;
/**
* 默认英文模板
*/
export const DEFAULT_TEMPLATE_EN = `## {{dateWithIcon}} ({{weekday}})
### 🧘 Today's Plan
---
- [ ] Meditate for 10 minutes
- [ ] Review yesterday's plan
- [ ] Read 20 pages
### 📝 Work Tasks
---
- [ ] Organize today's work schedule
- [ ] Progress on important projects
`;
/**
* 默认设置
*/
export const DEFAULT_SETTINGS = {
rootDir: 'DailyTasks',
autoGenerateMode: AutoGenerateMode.WORKDAY,
language: Language.AUTO,
templateZh: DEFAULT_TEMPLATE_ZH,
templateEn: DEFAULT_TEMPLATE_EN,
customTemplate: '',
hasCustomTemplate: false,
taskStatistics: false,
successNotificationDuration: 3000
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2V0dGluZ3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJzZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBTixJQUFZLGdCQUlYO0FBSkQsV0FBWSxnQkFBZ0I7SUFDeEIsaUNBQWEsQ0FBQTtJQUNiLG1DQUFlLENBQUE7SUFDZix1Q0FBbUIsQ0FBQSxDQUFDLFdBQVc7QUFDbkMsQ0FBQyxFQUpXLGdCQUFnQixLQUFoQixnQkFBZ0IsUUFJM0I7QUFFRDs7R0FFRztBQUNILE1BQU0sQ0FBTixJQUFZLFFBSVg7QUFKRCxXQUFZLFFBQVE7SUFDaEIseUJBQWEsQ0FBQTtJQUNiLHFCQUFTLENBQUE7SUFDVCxxQkFBUyxDQUFBO0FBQ2IsQ0FBQyxFQUpXLFFBQVEsS0FBUixRQUFRLFFBSW5CO0FBNEJEOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQUc7Ozs7Ozs7Ozs7Ozs7O0NBY2xDLENBQUM7QUFFRjs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUFHOzs7Ozs7Ozs7Ozs7OztDQWNsQyxDQUFDO0FBRUY7O0dBRUc7QUFDSCxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBc0I7SUFDL0MsT0FBTyxFQUFFLFlBQVk7SUFDckIsZ0JBQWdCLEVBQUUsZ0JBQWdCLENBQUMsT0FBTztJQUMxQyxRQUFRLEVBQUUsUUFBUSxDQUFDLElBQUk7SUFDdkIsVUFBVSxFQUFFLG1CQUFtQjtJQUMvQixVQUFVLEVBQUUsbUJBQW1CO0lBQy9CLGNBQWMsRUFBRSxFQUFFO0lBQ2xCLGlCQUFpQixFQUFFLEtBQUs7SUFDeEIsY0FBYyxFQUFFLEtBQUs7SUFDckIsMkJBQTJCLEVBQUUsSUFBSTtDQUNwQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiDoh6rliqjnlJ/miJDmqKHlvI/mnprkuL5cbiAqL1xuZXhwb3J0IGVudW0gQXV0b0dlbmVyYXRlTW9kZSB7XG4gICAgTk9ORSA9ICdub25lJywgICAgIC8vIOWFs+mXreiHquWKqOeUn+aIkFxuICAgIERBSUxZID0gJ2RhaWx5JywgICAvLyDmr4/ml6Xoh6rliqjnlJ/miJBcbiAgICBXT1JLREFZID0gJ3dvcmtkYXknIC8vIOS7heW3peS9nOaXpeiHquWKqOeUn+aIkFxufVxuXG4vKipcbiAqIOiuvue9rueVjOmdouivreiogFxuICovXG5leHBvcnQgZW51bSBMYW5ndWFnZSB7XG4gICAgQVVUTyA9ICdhdXRvJyxcbiAgICBaSCA9ICd6aCcsXG4gICAgRU4gPSAnZW4nXG59XG5cbi8qKlxuICog5o+S5Lu26K6+572u5o6l5Y+jXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRGFpbHlUYXNrU2V0dGluZ3Mge1xuICAgIC8vIOWfuuehgOmFjee9rlxuICAgIHJvb3REaXI6IHN0cmluZzsgICAgICAgICAgICAgICAvLyDku7vliqHmlofku7blrZjmlL7nmoTmoLnnm67lvZVcbiAgICBhdXRvR2VuZXJhdGVNb2RlOiBBdXRvR2VuZXJhdGVNb2RlOyAvLyDoh6rliqjnlJ/miJDmqKHlvI9cbiAgICBsYW5ndWFnZTogc3RyaW5nOyAgICAgICAgICAgIC8vIOeVjOmdouivreiogFxuXG4gICAgLy8g5qih5p2/6YWN572uXG4gICAgdGVtcGxhdGVaaDogc3RyaW5nOyAgICAgICAgICAgIC8vIOS4reaWh+S7u+WKoeaooeadv1xuICAgIHRlbXBsYXRlRW46IHN0cmluZzsgICAgICAgICAgICAvLyDoi7Hmlofku7vliqHmqKHmnb9cblxuICAgIC8vIOaWsOWinu+8mueUqOaIt+iHquWumuS5ieaooeadv1xuICAgIGN1c3RvbVRlbXBsYXRlOiBzdHJpbmc7XG5cbiAgICAvLyDmlrDlop7vvJrmoIforrDmmK/lkKbkvb/nlKjoh6rlrprkuYnmqKHmnb9cbiAgICBoYXNDdXN0b21UZW1wbGF0ZTogYm9vbGVhbjtcblxuICAgIC8vIOaWsOWinu+8muaYr+WQpuW8gOWQr+S7u+WKoee7n+iuoeWKn+iDvVxuICAgIHRhc2tTdGF0aXN0aWNzOiBib29sZWFuO1xuXG4gICAgLy8gVUnphY3nva5cbiAgICBzdWNjZXNzTm90aWZpY2F0aW9uRHVyYXRpb246IG51bWJlcjsgLy8g5oiQ5Yqf6YCa55+l5pi+56S65pe26Ze0KOavq+enkilcbn1cblxuLyoqXG4gKiDpu5jorqTkuK3mlofmqKHmnb9cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfVEVNUExBVEVfWkggPSBgIyMge3tkYXRlV2l0aEljb259fe+8iHt7d2Vla2RheX1977yJXG5cbiMjIyDwn6eYIOS7iuaXpeiuoeWIklxuLS0tXG5cbi0gWyBdIOWGpeaDsyAxMCDliIbpkp8gIFxuLSBbIF0g5aSN55uY5YmN5LiA5pel6K6h5YiSICBcbi0gWyBdIOmYheivuyAyMCDpobXkuaZcblxuIyMjIPCfk50g5bel5L2c5Lu75YqhXG4tLS1cblxuLSBbIF0g5pW055CG5LuK5pel5bel5L2c6K6h5YiSXG4tIFsgXSDlrozmiJDph43opoHpobnnm67ov5vluqZcbmA7XG5cbi8qKlxuICog6buY6K6k6Iux5paH5qih5p2/XG4gKi9cbmV4cG9ydCBjb25zdCBERUZBVUxUX1RFTVBMQVRFX0VOID0gYCMjIHt7ZGF0ZVdpdGhJY29ufX0gKHt7d2Vla2RheX19KVxuXG4jIyMg8J+nmCBUb2RheSdzIFBsYW5cbi0tLVxuXG4tIFsgXSBNZWRpdGF0ZSBmb3IgMTAgbWludXRlcyAgXG4tIFsgXSBSZXZpZXcgeWVzdGVyZGF5J3MgcGxhbiAgXG4tIFsgXSBSZWFkIDIwIHBhZ2VzXG5cbiMjIyDwn5OdIFdvcmsgVGFza3Ncbi0tLVxuXG4tIFsgXSBPcmdhbml6ZSB0b2RheSdzIHdvcmsgc2NoZWR1bGVcbi0gWyBdIFByb2dyZXNzIG9uIGltcG9ydGFudCBwcm9qZWN0c1xuYDtcblxuLyoqXG4gKiDpu5jorqTorr7nva5cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfU0VUVElOR1M6IERhaWx5VGFza1NldHRpbmdzID0ge1xuICAgIHJvb3REaXI6ICdEYWlseVRhc2tzJyxcbiAgICBhdXRvR2VuZXJhdGVNb2RlOiBBdXRvR2VuZXJhdGVNb2RlLldPUktEQVksXG4gICAgbGFuZ3VhZ2U6IExhbmd1YWdlLkFVVE8sXG4gICAgdGVtcGxhdGVaaDogREVGQVVMVF9URU1QTEFURV9aSCxcbiAgICB0ZW1wbGF0ZUVuOiBERUZBVUxUX1RFTVBMQVRFX0VOLFxuICAgIGN1c3RvbVRlbXBsYXRlOiAnJywgLy8g6buY6K6k5Li656m677yM6KGo56S65L2/55So6K+t6KiA55u45YWz55qE6buY6K6k5qih5p2/XG4gICAgaGFzQ3VzdG9tVGVtcGxhdGU6IGZhbHNlLCAvLyDpu5jorqTkuI3kvb/nlKjoh6rlrprkuYnmqKHmnb9cbiAgICB0YXNrU3RhdGlzdGljczogZmFsc2UsIC8vIOm7mOiupOWFs+mXreS7u+WKoee7n+iuoeWKn+iDvVxuICAgIHN1Y2Nlc3NOb3RpZmljYXRpb25EdXJhdGlvbjogMzAwMFxufTsgIl19

96
src/models/settings.ts Normal file
View file

@ -0,0 +1,96 @@
/**
*
*/
export enum AutoGenerateMode {
NONE = 'none', // 关闭自动生成
DAILY = 'daily', // 每日自动生成
WORKDAY = 'workday' // 仅工作日自动生成
}
/**
*
*/
export enum Language {
AUTO = 'auto',
ZH = 'zh',
EN = 'en'
}
/**
*
*/
export interface DailyTaskSettings {
// 基础配置
rootDir: string; // 任务文件存放的根目录
autoGenerateMode: AutoGenerateMode; // 自动生成模式
language: string; // 界面语言
// 模板配置
templateZh: string; // 中文任务模板
templateEn: string; // 英文任务模板
// 新增:用户自定义模板
customTemplate: string;
// 新增:标记是否使用自定义模板
hasCustomTemplate: boolean;
// 新增:是否开启任务统计功能
taskStatistics: boolean;
// UI配置
successNotificationDuration: number; // 成功通知显示时间(毫秒)
}
/**
*
*/
export const DEFAULT_TEMPLATE_ZH = `## {{dateWithIcon}}{{weekday}}
### 🧘
---
- [ ] 10
- [ ]
- [ ] 20
### 📝
---
- [ ]
- [ ]
`;
/**
*
*/
export const DEFAULT_TEMPLATE_EN = `## {{dateWithIcon}} ({{weekday}})
### 🧘 Today's Plan
---
- [ ] Meditate for 10 minutes
- [ ] Review yesterday's plan
- [ ] Read 20 pages
### 📝 Work Tasks
---
- [ ] Organize today's work schedule
- [ ] Progress on important projects
`;
/**
*
*/
export const DEFAULT_SETTINGS: DailyTaskSettings = {
rootDir: 'DailyTasks',
autoGenerateMode: AutoGenerateMode.WORKDAY,
language: Language.AUTO,
templateZh: DEFAULT_TEMPLATE_ZH,
templateEn: DEFAULT_TEMPLATE_EN,
customTemplate: '', // 默认为空,表示使用语言相关的默认模板
hasCustomTemplate: false, // 默认不使用自定义模板
taskStatistics: false, // 默认关闭任务统计功能
successNotificationDuration: 3000
};

136
src/obsidian.d.ts vendored Normal file
View file

@ -0,0 +1,136 @@
/**
* Obsidian核心API类型声明
*/
declare module "obsidian" {
export class Plugin {
app: App;
manifest: PluginManifest;
loadData(): Promise<any>;
saveData(data: any): Promise<void>;
addSettingTab(settingTab: PluginSettingTab): void;
addCommand(command: Command): void;
}
export interface App {
workspace: Workspace;
vault: Vault;
}
export interface Workspace {
getLeaf(): WorkspaceLeaf;
}
export interface WorkspaceLeaf {
openFile(file: TFile): Promise<void>;
}
export interface Vault {
getAbstractFileByPath(path: string): TAbstractFile | null;
createFolder(path: string): Promise<void>;
create(path: string, data: string): Promise<TFile>;
read(file: TFile): Promise<string>;
modify(file: TFile, data: string): Promise<void>;
}
export class TAbstractFile {
path: string;
name: string;
vault: Vault;
}
export class TFile extends TAbstractFile {
extension: string;
}
export class TFolder extends TAbstractFile {
children: TAbstractFile[];
}
export interface PluginManifest {
id: string;
name: string;
version: string;
description: string;
}
export interface Command {
id: string;
name: string;
callback: () => any;
checkCallback?: (checking: boolean) => boolean | void;
hotkeys?: Hotkey[];
}
export interface Hotkey {
modifiers: string[];
key: string;
}
export abstract class PluginSettingTab {
constructor(app: App, plugin: Plugin);
containerEl: HTMLElement;
abstract display(): void;
}
export class Setting {
constructor(containerEl: HTMLElement);
setName(name: string): this;
setDesc(desc: string): this;
setClass(cls: string): this;
addText(cb: (text: TextComponent) => any): this;
addButton(cb: (button: ButtonComponent) => any): this;
addTextArea(cb: (text: TextAreaComponent) => any): this;
addToggle(cb: (toggle: ToggleComponent) => any): this;
addDropdown(cb: (dropdown: DropdownComponent) => any): this;
controlEl: HTMLElement;
}
export class ButtonComponent {
constructor(containerEl: HTMLElement);
buttonEl: HTMLButtonElement;
setButtonText(name: string): this;
setDisabled(disabled: boolean): this;
setCta(): this;
onClick(callback: () => any): this;
}
export class TextComponent {
constructor(containerEl: HTMLElement);
inputEl: HTMLInputElement;
setValue(value: string): this;
getValue(): string;
onChange(callback: (value: string) => any): this;
}
export class TextAreaComponent {
constructor(containerEl: HTMLElement);
inputEl: HTMLTextAreaElement;
setValue(value: string): this;
getValue(): string;
setPlaceholder(placeholder: string): this;
onChange(callback: (value: string) => any): this;
}
export class ToggleComponent {
constructor(containerEl: HTMLElement);
setValue(value: boolean): this;
onChange(callback: (value: boolean) => any): this;
}
export class DropdownComponent {
constructor(containerEl: HTMLElement);
addOption(value: string, display: string): this;
setValue(value: string): this;
onChange(callback: (value: string) => any): this;
}
export function addIcon(iconId: string, svgContent: string): void;
export class Notice {
constructor(message: string, timeout?: number);
noticeEl: HTMLElement;
}
export function normalizePath(path: string): string;
}

9
src/settings/index.ts Normal file
View file

@ -0,0 +1,9 @@
/**
*
*
*/
import { DEFAULT_SETTINGS } from '../models/settings';
export { DEFAULT_SETTINGS };
export * from './settings';

692
src/settings/settings.js Normal file

File diff suppressed because one or more lines are too long

843
src/settings/settings.ts Normal file
View file

@ -0,0 +1,843 @@
import { App, ButtonComponent, DropdownComponent, Notice, Plugin, PluginSettingTab, Setting, TextAreaComponent, TextComponent, ToggleComponent } from 'obsidian';
import { AutoGenerateMode, DEFAULT_SETTINGS, DEFAULT_TEMPLATE_EN, DEFAULT_TEMPLATE_ZH, DailyTaskSettings, Language } from '../models/settings';
import { getTranslation, setCurrentLanguage } from '../i18n/i18n';
import { renderTemplate } from '../utils/templateEngine';
import { TaskGenerator } from '../taskGenerator';
// 前向声明,避免循环导入
declare class DailyTaskPlugin {
saveData(data: any): Promise<void>;
loadData(): Promise<any>;
app: App;
}
// CSS 相关代码
const SettingsSectionCSS = "daily-task-settings-section";
const ButtonCSS = "daily-task-button";
const PreviewButtonCSS = "daily-task-preview-button";
const ResetButtonCSS = "daily-task-reset-button";
const EditorCSS = "daily-task-editor";
const VerticalStackCSS = "daily-task-vertical-stack";
const TextRightCSS = "daily-task-text-right";
const TextCenterCSS = "daily-task-text-center";
const ScrollbarSlimCSS = "daily-task-slim-scrollbar";
/**
*
*/
function addCustomStyles() {
const cssText = `
.${SettingsSectionCSS} {
margin-top: 24px;
margin-bottom: 24px;
padding: 12px 0;
border-top: 1px solid var(--background-modifier-border);
}
.${ButtonCSS} {
margin-top: 6px;
margin-bottom: 6px;
}
.${PreviewButtonCSS}, .${ResetButtonCSS} {
display: inline-block;
text-align: center !important;
width: 100%;
}
.${EditorCSS} {
height: 200px;
margin-top: 12px;
margin-bottom: 12px;
}
.${VerticalStackCSS} {
display: flex;
flex-direction: column;
}
.${TextRightCSS} {
text-align: right;
}
.${TextCenterCSS} {
text-align: center !important;
}
.${ScrollbarSlimCSS} .CodeMirror-vscrollbar {
width: 20% !important;
}
/* 自定义通知样式 */
.daily-task-success-notice {
background-color: rgba(0, 255, 127, 0.2) !important;
}
.daily-task-warning-notice {
background-color: rgba(255, 165, 0, 0.2) !important;
}
.daily-task-error-notice {
background-color: rgba(255, 69, 0, 0.2) !important;
}
`;
// 添加自定义样式
const styleElement = document.createElement('style');
styleElement.textContent = cssText;
document.head.appendChild(styleElement);
}
/**
*
*/
export class DailyTaskSettingTab extends PluginSettingTab {
plugin: Plugin;
settingsManager: SettingsManager;
taskGenerator: TaskGenerator;
previewEl: HTMLElement | null = null;
addTaskButton: ButtonComponent | null = null;
// 目录输入框
rootDirInput: TextComponent | null = null;
// 标记设置是否已修改但未保存
dirtySettings: boolean = false;
// 自动保存目录的方法
autoSaveRootDir: (value: string) => Promise<void>;
constructor(app: App, plugin: Plugin) {
super(app, plugin);
this.plugin = plugin;
// 获取父插件中的设置管理器引用
this.settingsManager = (plugin as any).settingsManager;
// 创建任务生成器
this.taskGenerator = new TaskGenerator(app, this.settingsManager);
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.classList.add('daily-task-setting-tab');
const settings = this.settingsManager.getSettings();
// 添加顶部间距(增加间距以改善界面美观)
const topSpacing = containerEl.createEl('div');
topSpacing.style.marginTop = '30px';
// 根目录设置
const rootDirSetting = new Setting(containerEl)
.setName(getTranslation('settings.rootDir'))
.setDesc(getTranslation('settings.rootDir.desc'));
// 创建输入框容器,使其可以包含额外元素
const inputContainer = document.createElement('div');
inputContainer.style.display = 'flex';
inputContainer.style.width = '100%';
inputContainer.style.position = 'relative';
rootDirSetting.controlEl.appendChild(inputContainer);
this.rootDirInput = new TextComponent(inputContainer)
.setValue(settings.rootDir)
.onChange(async (value) => {
if (value.trim() !== '') {
// 设置已更改,准备自动保存
this.dirtySettings = true;
// 启动自动保存定时器
this.autoSaveRootDir(value);
}
});
// 给input元素直接设置placeholder属性
if (this.rootDirInput && this.rootDirInput.inputEl) {
this.rootDirInput.inputEl.placeholder = 'DailyTasks';
// 增加宽度
this.rootDirInput.inputEl.style.width = '100%';
// 美化输入框样式
this.rootDirInput.inputEl.style.borderRadius = '4px';
this.rootDirInput.inputEl.style.padding = '8px 35px 8px 10px';
this.rootDirInput.inputEl.style.transition = 'all 0.3s ease';
}
// 添加自动保存指示器
const saveIndicator = document.createElement('div');
saveIndicator.classList.add('save-indicator');
saveIndicator.style.position = 'absolute';
saveIndicator.style.right = '10px';
saveIndicator.style.top = '50%';
saveIndicator.style.transform = 'translateY(-50%)';
saveIndicator.style.opacity = '0';
saveIndicator.style.transition = 'opacity 0.3s ease';
inputContainer.appendChild(saveIndicator);
// 创建保存成功图标
const saveSuccessIcon = createSpan({cls: 'svg-icon lucide-check'});
saveSuccessIcon.style.color = '#4CAF50';
saveSuccessIcon.style.width = '18px';
saveSuccessIcon.style.height = '18px';
saveIndicator.appendChild(saveSuccessIcon);
// 记录自动保存定时器
let autoSaveTimer: number | null = null;
// 自动保存方法
this.autoSaveRootDir = async (value: string) => {
// 清除之前的定时器
if (autoSaveTimer !== null) {
clearTimeout(autoSaveTimer);
}
// 设置新的定时器延迟800ms保存在用户停止输入后
autoSaveTimer = setTimeout(async () => {
let pathToSave = value.trim();
if (pathToSave === '') {
pathToSave = 'DailyTasks'; // 默认存放目录
}
// 保存设置
await this.settingsManager.updateSettings({ rootDir: pathToSave });
this.dirtySettings = false;
// 显示保存成功指示器
saveIndicator.style.opacity = '1';
// 3秒后隐藏
setTimeout(() => {
saveIndicator.style.opacity = '0';
}, 2000);
}, 800) as unknown as number;
};
// 自动生成模式
const autoGenSetting = new Setting(containerEl)
.setName(getTranslation('settings.autoGenerate'))
.setDesc(getTranslation('settings.autoGenerate.desc'));
// 自定义三选滑块
const toggleContainer = document.createElement('div');
toggleContainer.classList.add('mode-toggle-container');
toggleContainer.style.width = '20%'; // 缩短滑动条长度为原来的20%
toggleContainer.style.marginLeft = 'auto'; // 靠右显示
const modes = [
{ value: AutoGenerateMode.NONE, label: getTranslation('settings.mode.none') },
{ value: AutoGenerateMode.DAILY, label: getTranslation('settings.mode.daily') },
{ value: AutoGenerateMode.WORKDAY, label: getTranslation('settings.mode.workday') }
];
// 滑块指示器
const slider = document.createElement('div');
slider.classList.add('mode-toggle-slider');
toggleContainer.appendChild(slider);
// 更新滑块位置
const updateSlider = (index: number) => {
slider.style.left = `${index * 33.33}%`;
};
// 设置默认模式为仅工作日生成WorkDay
let currentModeIndex = modes.findIndex(mode => mode.value === settings.autoGenerateMode);
if (currentModeIndex === -1) {
// 如果没有找到有效的模式,设置为工作日模式
currentModeIndex = modes.findIndex(mode => mode.value === AutoGenerateMode.WORKDAY);
// 更新设置到工作日模式
this.settingsManager.updateSettings({ autoGenerateMode: AutoGenerateMode.WORKDAY });
}
updateSlider(currentModeIndex);
// 创建选项
modes.forEach((mode, index) => {
const option = document.createElement('div');
option.classList.add('mode-toggle-option');
option.textContent = mode.label;
if (mode.value === settings.autoGenerateMode) {
option.classList.add('active');
}
option.addEventListener('click', async () => {
// 更新视觉状态
toggleContainer.querySelectorAll('.mode-toggle-option').forEach(el => {
el.classList.remove('active');
});
option.classList.add('active');
// 更新滑块位置
updateSlider(index);
// 保存设置
await this.settingsManager.updateSettings({ autoGenerateMode: mode.value });
});
toggleContainer.appendChild(option);
});
autoGenSetting.controlEl.appendChild(toggleContainer);
// 语言设置
new Setting(containerEl)
.setName(getTranslation('settings.language'))
.setDesc(getTranslation('settings.language.desc'))
.addDropdown(dropdown => {
dropdown
.addOption(Language.AUTO, getTranslation('settings.language.auto'))
.addOption(Language.ZH, getTranslation('settings.language.zh'))
.addOption(Language.EN, getTranslation('settings.language.en'))
.setValue(settings.language)
.onChange(async (value) => {
await this.settingsManager.updateSettings({ language: value as Language });
// 需要重新加载设置页面以更新翻译
this.display();
});
});
// 通知显示时间
new Setting(containerEl)
.setName(getTranslation('settings.notificationDuration'))
.setDesc(getTranslation('settings.notificationDuration.desc'))
.addText(text => {
const component = text
.setValue(settings.successNotificationDuration.toString())
.onChange(async (value) => {
const duration = parseInt(value);
if (!isNaN(duration) && duration > 0) {
await this.settingsManager.updateSettings({ successNotificationDuration: duration });
}
});
// 直接设置placeholder属性
component.inputEl.placeholder = '3000';
return component;
});
// 任务统计功能开关
new Setting(containerEl)
.setName(getTranslation('settings.taskStatistics'))
.setDesc(getTranslation('settings.taskStatistics.desc'))
.addToggle((toggle) => {
// 设置开关样式
const toggleEl = toggle
.setValue(settings.taskStatistics)
.onChange(async (value) => {
await this.settingsManager.updateSettings({ taskStatistics: value });
});
// 自定义开关样式 - 使用DOM元素访问
// @ts-ignore - 添加这行来忽略TypeScript警告
const toggleControl = toggle.toggleEl || toggle.containerEl.querySelector('.checkbox-container');
if (toggleControl) {
toggleControl.classList.add('task-statistics-toggle');
// 添加图标 - 使用DOM父元素访问
const toggleContainer = toggleControl.parentElement;
if (toggleContainer) {
const iconEl = createSpan({cls: 'svg-icon lucide-bar-chart-2'});
iconEl.style.marginRight = '8px';
iconEl.style.color = 'var(--text-accent)';
toggleContainer.prepend(iconEl);
// 添加过渡效果
toggleContainer.style.transition = 'all 0.3s ease';
toggleControl.style.transition = 'all 0.3s ease';
}
}
return toggleEl;
});
// 模板设置
containerEl.createEl('h3', { text: getTranslation('settings.template') });
// 添加模板使用逻辑说明
const templateDescription = containerEl.createEl('p', {
text: this.settingsManager.getCurrentLanguage() === 'zh' ?
'注意:默认模板会根据当前语言环境自动选择对应语言的内容。如果您自定义模板,将在所有语言环境中使用您的自定义内容。' :
'Note: Default template automatically adapts to your language environment. If you customize the template, your content will be used in all language environments.'
});
templateDescription.style.fontSize = '0.85em';
templateDescription.style.opacity = '0.8';
templateDescription.style.marginBottom = '15px';
// 添加模板变量说明
const templateVariablesEl = containerEl.createEl('p');
templateVariablesEl.innerHTML = this.settingsManager.getCurrentLanguage() === 'zh' ?
'<strong>可用变量:</strong> {{date}} - 日期, {{dateWithIcon}} - 带图标的日期, {{weekday}} - 星期几, {{yearProgress}} - 年进度, {{monthProgress}} - 月进度, {{time}} - 当前时间' :
'<strong>Available variables:</strong> {{date}} - Date, {{dateWithIcon}} - Date with icon, {{weekday}} - Day of week, {{yearProgress}} - Year progress, {{monthProgress}} - Month progress, {{time}} - Current time';
templateVariablesEl.style.fontSize = '0.85em';
templateVariablesEl.style.marginBottom = '10px';
// 单一模板设置
const templateSetting = new Setting(containerEl)
.setName(getTranslation('settings.template'))
.setClass('template-setting');
const templateContainer = document.createElement('div');
templateContainer.style.width = '100%';
// 获取当前模板内容
const currentTemplate = this.settingsManager.hasCustomTemplate() ?
this.settingsManager.getSettings().customTemplate :
this.settingsManager.getTemplateByLanguage();
const textarea = new TextAreaComponent(templateContainer)
.setValue(currentTemplate)
.setPlaceholder(this.settingsManager.getCurrentLanguage() === 'zh' ?
'在此处输入任务模板...' :
'Enter task template here...')
.onChange(async (value) => {
// 更新为自定义模板
await this.settingsManager.updateSettings({
customTemplate: value,
hasCustomTemplate: true
});
this.updatePreview(this.previewEl, value);
});
// 改进TextArea样式
textarea.inputEl.classList.add('template-editor');
textarea.inputEl.style.height = '200px';
textarea.inputEl.style.border = '1px solid var(--background-modifier-border-hover)';
textarea.inputEl.style.borderRadius = '4px';
textarea.inputEl.style.padding = '12px';
textarea.inputEl.style.lineHeight = '1.5';
textarea.inputEl.style.fontSize = '14px';
textarea.inputEl.style.fontFamily = 'var(--font-monospace)';
textarea.inputEl.style.transition = 'all 0.2s ease';
textarea.inputEl.style.boxShadow = 'inset 0 1px 3px rgba(0, 0, 0, 0.1)';
textarea.inputEl.style.backgroundColor = 'var(--background-primary)';
textarea.inputEl.style.color = 'var(--text-normal)';
textarea.inputEl.style.resize = 'vertical';
// 当获取焦点时改变边框样式
textarea.inputEl.addEventListener('focus', () => {
textarea.inputEl.style.border = '1px solid var(--interactive-accent)';
textarea.inputEl.style.boxShadow = '0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2), inset 0 1px 3px rgba(0, 0, 0, 0.1)';
textarea.inputEl.style.outline = 'none';
});
// 失去焦点时恢复原来的边框样式
textarea.inputEl.addEventListener('blur', () => {
textarea.inputEl.style.border = '1px solid var(--background-modifier-border-hover)';
textarea.inputEl.style.boxShadow = 'inset 0 1px 3px rgba(0, 0, 0, 0.1)';
});
// 预览标题使用flex布局居中
const previewHeader = document.createElement('div');
previewHeader.classList.add('template-preview-header');
previewHeader.style.display = 'flex';
previewHeader.style.justifyContent = 'space-between'; // 改为两端对齐
previewHeader.style.marginTop = '15px';
previewHeader.style.marginBottom = '10px';
previewHeader.style.width = '100%';
// 预览按钮容器 - 左侧
const previewBtnContainer = document.createElement('div');
previewBtnContainer.style.display = 'flex';
previewBtnContainer.style.alignItems = 'center';
// 重置按钮容器 - 右侧
const resetBtnContainer = document.createElement('div');
resetBtnContainer.style.display = 'flex';
resetBtnContainer.style.alignItems = 'center';
// 预览按钮 - 改进样式
const toggleButton = new ButtonComponent(previewBtnContainer)
.setButtonText(getTranslation('settings.template.preview'));
// 添加样式类
toggleButton.buttonEl.addClass(TextCenterCSS);
toggleButton.buttonEl.style.textAlign = 'center';
toggleButton.buttonEl.style.display = 'flex';
toggleButton.buttonEl.style.alignItems = 'center';
toggleButton.buttonEl.style.justifyContent = 'center';
toggleButton.buttonEl.style.width = '130px';
toggleButton.buttonEl.style.borderRadius = '4px';
toggleButton.buttonEl.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
toggleButton.buttonEl.style.transition = 'all 0.2s ease';
// 手动添加眼睛图标
const eyeIcon = createSpan({cls: 'svg-icon lucide-eye'});
eyeIcon.style.marginRight = '6px';
toggleButton.buttonEl.prepend(eyeIcon);
// 预览区域
this.previewEl = document.createElement('div');
this.previewEl.classList.add('template-preview');
this.previewEl.style.marginTop = '15px';
this.previewEl.style.padding = '15px';
this.previewEl.style.border = '1px dashed var(--background-modifier-border)';
this.previewEl.style.borderRadius = '8px';
this.previewEl.style.backgroundColor = 'var(--background-secondary)';
this.previewEl.style.display = 'none';
this.previewEl.style.maxHeight = '200px';
this.previewEl.style.overflow = 'auto';
this.previewEl.style.boxShadow = 'inset 0 0 5px rgba(0,0,0,0.1)';
this.updatePreview(this.previewEl, currentTemplate);
templateContainer.appendChild(this.previewEl);
toggleButton.onClick(() => {
this.togglePreview(this.previewEl);
// 切换图标和按钮文本
if (this.previewEl && this.previewEl.classList.contains('visible')) {
eyeIcon.className = 'svg-icon lucide-eye-off';
toggleButton.setButtonText(getTranslation('settings.template.hide'));
toggleButton.buttonEl.style.backgroundColor = 'var(--background-modifier-success)';
} else {
eyeIcon.className = 'svg-icon lucide-eye';
toggleButton.setButtonText(getTranslation('settings.template.preview'));
toggleButton.buttonEl.style.backgroundColor = '';
}
});
// 重置按钮 - 改进样式
const resetBtn = new ButtonComponent(resetBtnContainer)
.setButtonText(getTranslation('settings.resetDefault'));
// 添加样式类
resetBtn.buttonEl.addClass(TextCenterCSS);
resetBtn.buttonEl.style.textAlign = 'center';
resetBtn.buttonEl.style.display = 'flex';
resetBtn.buttonEl.style.alignItems = 'center';
resetBtn.buttonEl.style.justifyContent = 'center';
resetBtn.buttonEl.style.width = '150px';
resetBtn.buttonEl.style.borderRadius = '4px';
resetBtn.buttonEl.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
resetBtn.buttonEl.style.transition = 'all 0.2s ease';
// 添加重置图标
const resetIcon = createSpan({cls: 'svg-icon lucide-refresh-cw'});
resetIcon.style.marginRight = '6px';
resetBtn.buttonEl.prepend(resetIcon);
// 添加重置事件
resetBtn.onClick(async () => {
// 将自定义模板设置为空,回到使用默认模板
await this.settingsManager.updateSettings({
customTemplate: '',
hasCustomTemplate: false
});
// 获取当前语言的默认模板
const defaultTemplate = this.settingsManager.getTemplateByLanguage();
// 更新输入框和预览
textarea.setValue(defaultTemplate);
this.updatePreview(this.previewEl, defaultTemplate);
// 显示成功提示动画
resetBtn.buttonEl.style.backgroundColor = 'var(--background-modifier-success)';
setTimeout(() => {
resetBtn.buttonEl.style.backgroundColor = '';
}, 1000);
});
// 将按钮添加到各自的容器
previewHeader.appendChild(previewBtnContainer);
previewHeader.appendChild(resetBtnContainer);
templateContainer.appendChild(previewHeader);
templateSetting.controlEl.appendChild(templateContainer);
// 恢复默认设置 - 创建容器让按钮右对齐
const resetContainer = document.createElement('div');
resetContainer.style.display = 'flex';
resetContainer.style.justifyContent = 'flex-end'; // 确保右对齐
resetContainer.style.marginTop = '20px';
resetContainer.style.marginBottom = '10px';
containerEl.appendChild(resetContainer);
// 恢复默认设置按钮
const resetDefaultBtn = new ButtonComponent(resetContainer)
.setButtonText(getTranslation('settings.resetToDefault'));
// 添加样式类
resetDefaultBtn.buttonEl.addClass(TextCenterCSS);
resetDefaultBtn.buttonEl.addClass('danger-button');
resetDefaultBtn.buttonEl.style.textAlign = 'center';
resetDefaultBtn.buttonEl.style.display = 'flex';
resetDefaultBtn.buttonEl.style.alignItems = 'center';
resetDefaultBtn.buttonEl.style.justifyContent = 'center';
resetDefaultBtn.buttonEl.style.width = '150px';
// 添加重置图标和危险按钮样式
resetDefaultBtn.buttonEl.prepend(createSpan({cls: 'svg-icon lucide-refresh-cw'}));
// 为全局重置按钮添加事件处理
resetDefaultBtn.onClick(async () => {
await this.settingsManager.resetToDefaults();
this.display();
});
// 手动添加今日任务按钮 - 右对齐显示
const addTaskContainer = document.createElement('div');
addTaskContainer.style.display = 'flex';
addTaskContainer.style.justifyContent = 'flex-end'; // 确保右对齐
addTaskContainer.style.marginTop = '20px';
containerEl.appendChild(addTaskContainer);
this.addTaskButton = new ButtonComponent(addTaskContainer)
.setButtonText(getTranslation('settings.addTaskButton'))
.setCta();
// 添加样式类 - 确保按钮文字居中
if (this.addTaskButton && this.addTaskButton.buttonEl) {
this.addTaskButton.buttonEl.addClass(TextCenterCSS);
this.addTaskButton.buttonEl.style.textAlign = 'center';
this.addTaskButton.buttonEl.style.display = 'flex';
this.addTaskButton.buttonEl.style.alignItems = 'center';
this.addTaskButton.buttonEl.style.justifyContent = 'center';
}
// 手动添加任务按钮事件处理
this.addTaskButton.onClick(async () => {
// 检查目录设置
const rootDir = this.settingsManager.getSettings().rootDir;
// 添加loading状态
if (this.addTaskButton && this.addTaskButton.buttonEl) {
this.addTaskButton.buttonEl.classList.add('loading');
}
this.addTaskButton?.setDisabled(true);
try {
// 添加任务
await this.taskGenerator.addTaskManually();
} catch (e) {
console.error("添加任务出错:", e);
new Notice(`添加任务失败: ${e.message || e}`);
} finally {
// 移除loading状态
setTimeout(() => {
if (this.addTaskButton && this.addTaskButton.buttonEl) {
this.addTaskButton.buttonEl.classList.remove('loading');
}
this.addTaskButton?.setDisabled(false);
}, 500);
}
});
// 添加图标
this.addTaskButton.buttonEl.prepend(createSpan({cls: 'svg-icon lucide-calendar-plus'}));
}
/**
*
*/
private updatePreview(previewEl: HTMLElement | null, template: string): void {
if (!previewEl) return;
const renderedContent = renderTemplate(template);
// 使用MarkdownRenderer需要导入相关组件
previewEl.innerHTML = renderedContent.replace(/\n/g, '<br>');
}
/**
* /
*/
private togglePreview(previewEl: HTMLElement | null): void {
if (!previewEl) return;
if (previewEl.style.display === 'none') {
previewEl.style.display = 'block';
previewEl.classList.add('visible');
} else {
previewEl.style.display = 'none';
previewEl.classList.remove('visible');
}
}
}
/**
*
* 访
*/
export class SettingsManager {
private plugin: Plugin;
private settings: DailyTaskSettings;
constructor(plugin: Plugin) {
this.plugin = plugin;
this.settings = Object.assign({}, DEFAULT_SETTINGS);
}
/**
*
*/
getSettings(): DailyTaskSettings {
return this.settings;
}
/**
*
* @param settings
*/
async updateSettings(settings: Partial<DailyTaskSettings>): Promise<void> {
this.settings = {
...this.settings,
...settings
};
await this.saveSettings();
// 更新当前语言
this.updateCurrentLanguage();
}
/**
*
*/
async saveSettings(): Promise<void> {
await this.plugin.saveData(this.settings);
}
/**
*
*/
async loadSettings(): Promise<void> {
const loadedData = await this.plugin.loadData();
if (loadedData) {
// 合并默认设置和已保存的设置
this.settings = {
...DEFAULT_SETTINGS,
...loadedData
};
// 确保在升级插件后,新增的设置项也有默认值
this.ensureSettingsCompleteness();
} else {
// 如果没有加载到数据,使用默认设置但将自动生成模式改为工作日
this.settings = {
...DEFAULT_SETTINGS,
autoGenerateMode: AutoGenerateMode.WORKDAY
};
}
// 更新当前语言
this.updateCurrentLanguage();
}
/**
*
*/
private ensureSettingsCompleteness(): void {
const defaultKeys = Object.keys(DEFAULT_SETTINGS);
defaultKeys.forEach(key => {
// 如果当前设置中缺少某个默认设置项,添加默认值
if (!(key in this.settings)) {
(this.settings as any)[key] = (DEFAULT_SETTINGS as any)[key];
}
});
}
/**
*
*/
async resetToDefaults(): Promise<void> {
this.settings = Object.assign({}, DEFAULT_SETTINGS);
await this.saveSettings();
// 更新当前语言
this.updateCurrentLanguage();
}
/**
* 使
*
*/
getCurrentTemplate(): string {
const language = this.getCurrentLanguage();
// 中文环境
if (language === 'zh') {
// 如果中文模板已被修改(不等于默认模板),使用中文模板
if (this.settings.templateZh !== DEFAULT_TEMPLATE_ZH) {
return this.settings.templateZh;
}
// 如果英文模板已被修改,使用英文模板
if (this.settings.templateEn !== DEFAULT_TEMPLATE_EN) {
return this.settings.templateEn;
}
// 如果都是默认模板,使用中文默认模板
return this.settings.templateZh;
}
// 英文环境
else {
// 如果英文模板已被修改(不等于默认模板),使用英文模板
if (this.settings.templateEn !== DEFAULT_TEMPLATE_EN) {
return this.settings.templateEn;
}
// 如果中文模板已被修改,使用中文模板
if (this.settings.templateZh !== DEFAULT_TEMPLATE_ZH) {
return this.settings.templateZh;
}
// 如果都是默认模板,使用英文默认模板
return this.settings.templateEn;
}
}
/**
*
*/
getCurrentLanguage(): string {
if (this.settings.language === Language.AUTO) {
// 自动检测系统语言
const systemLanguage = window.navigator.language.toLowerCase();
return systemLanguage.startsWith('zh') ? 'zh' : 'en';
}
return this.settings.language;
}
/**
*
*/
private updateCurrentLanguage(): void {
const language = this.getCurrentLanguage();
setCurrentLanguage(language);
}
/**
*
*/
getTemplateByLanguage(): string {
const language = this.getCurrentLanguage();
// 中文环境
if (language === 'zh') {
// 如果中文模板已被修改(不等于默认模板),使用中文模板
if (this.settings.templateZh !== DEFAULT_TEMPLATE_ZH) {
return this.settings.templateZh;
}
// 如果英文模板已被修改,使用英文模板
if (this.settings.templateEn !== DEFAULT_TEMPLATE_EN) {
return this.settings.templateEn;
}
// 如果都是默认模板,使用中文默认模板
return this.settings.templateZh;
}
// 英文环境
else {
// 如果英文模板已被修改(不等于默认模板),使用英文模板
if (this.settings.templateEn !== DEFAULT_TEMPLATE_EN) {
return this.settings.templateEn;
}
// 如果中文模板已被修改,使用中文模板
if (this.settings.templateZh !== DEFAULT_TEMPLATE_ZH) {
return this.settings.templateZh;
}
// 如果都是默认模板,使用英文默认模板
return this.settings.templateEn;
}
}
/**
*
*/
hasCustomTemplate(): boolean {
return !!this.settings.customTemplate;
}
}

91
src/styles.css Normal file
View file

@ -0,0 +1,91 @@
/* 任务统计样式 */
.daily-task-statistics {
margin-top: 10px;
margin-bottom: 20px;
padding: 15px;
border-radius: 8px;
background-color: rgba(var(--interactive-accent-rgb), 0.1);
border-left: 4px solid var(--interactive-accent);
}
.daily-task-statistics h3 {
margin-top: 0;
display: flex;
align-items: center;
font-size: 1.1em;
color: var(--text-accent);
}
.daily-task-statistics h4 {
margin-top: 12px;
margin-bottom: 8px;
font-size: 0.95em;
color: var(--text-accent);
}
/* 提示建议样式 */
.daily-task-suggestions {
padding: 10px 15px;
border-radius: 6px;
background-color: rgba(var(--background-modifier-success-rgb), 0.1);
margin-top: 10px;
}
/* 任务统计开关样式 */
.task-statistics-toggle {
position: relative;
transition: all 0.3s ease;
}
.task-statistics-toggle:hover {
transform: scale(1.05);
}
.task-statistics-toggle.is-enabled {
background-color: var(--interactive-accent) !important;
}
/* 自定义滚动条样式 */
.daily-task-setting-tab ::-webkit-scrollbar {
width: 8px; /* 设置滚动条宽度 */
}
.daily-task-setting-tab ::-webkit-scrollbar-track {
background: var(--background-secondary); /* 设置轨道背景 */
border-radius: 4px;
}
.daily-task-setting-tab ::-webkit-scrollbar-thumb {
background: var(--interactive-accent); /* 设置滑块颜色 */
border-radius: 4px;
opacity: 0.7;
transition: background 0.2s ease;
}
.daily-task-setting-tab ::-webkit-scrollbar-thumb:hover {
background: var(--interactive-accent-hover); /* 滑块悬停颜色 */
opacity: 1;
}
/* 应用滚动条样式到所有可滚动元素 */
.daily-task-slim-scrollbar {
scrollbar-width: thin;
}
.daily-task-slim-scrollbar::-webkit-scrollbar {
width: 8px;
}
.daily-task-slim-scrollbar::-webkit-scrollbar-track {
background: var(--background-secondary);
border-radius: 4px;
}
.daily-task-slim-scrollbar::-webkit-scrollbar-thumb {
background: var(--interactive-accent);
border-radius: 4px;
}
.daily-task-slim-scrollbar::-webkit-scrollbar-thumb:hover {
background: var(--interactive-accent-hover);
}

262
src/taskGenerator.js Normal file

File diff suppressed because one or more lines are too long

295
src/taskGenerator.ts Normal file
View file

@ -0,0 +1,295 @@
import { App, Notice, TFile, Vault } from "obsidian";
import { getTranslation } from "./i18n/i18n";
import { SettingsManager } from "./settings/settings";
import { appendToFile, ensureFileExists, ensureFolderExists, getTaskFilePath, todayTaskExists, fileContains, getYesterdayDate, getYesterdayTaskFilePath, extractTasksForDate, analyzeTaskCompletion, yesterdayStatisticsExists, generateStatisticsContent } from "./utils/fileUtils";
import { renderTemplate } from "./utils/templateEngine";
import { getCurrentDate } from "./utils/dateUtils";
/**
*
*
*/
export class TaskGenerator {
private app: App;
private vault: Vault;
private settingsManager: SettingsManager;
constructor(app: App, settingsManager: SettingsManager) {
this.app = app;
this.vault = app.vault;
this.settingsManager = settingsManager;
}
/**
*
* @param openFile
* @param quietMode
* @returns
*/
async generateDailyTask(openFile: boolean = true, quietMode: boolean = false): Promise<boolean> {
try {
const settings = this.settingsManager.getSettings();
const rootDir = settings.rootDir.trim() || 'DailyTasks'; // 使用默认目录
// 获取任务文件路径
const filePath = getTaskFilePath(rootDir);
if (!quietMode) console.log(`生成任务文件路径: ${filePath}`);
// 解析年份和月份
const pathParts = filePath.split('/');
const year = pathParts.length > 1 ? pathParts[1] : '';
const monthName = pathParts.length > 2 ? pathParts[2].replace('.md', '') : '';
const yearFolder = `${rootDir}/${year}`;
// 确保根目录存在
if (!quietMode) console.log(`正在确保根目录存在: ${rootDir}`);
const rootCreated = await ensureFolderExists(this.vault, rootDir);
if (!rootCreated) {
console.error(`无法访问或创建根目录: ${rootDir}`);
throw new Error(`无法访问根目录: ${rootDir},可能是存在同名文件或权限问题`);
}
if (!quietMode) console.log(`根目录确认: ${rootDir}`);
// 确保年份目录存在
if (!quietMode) console.log(`正在确保年份目录存在: ${yearFolder}`);
const yearCreated = await ensureFolderExists(this.vault, yearFolder);
if (!yearCreated) {
console.error(`无法访问或创建年份目录: ${yearFolder}`);
throw new Error(`无法访问年份目录: ${yearFolder},可能是存在同名文件或权限问题`);
}
if (!quietMode) console.log(`年份目录确认: ${yearFolder}`);
// 确保月份文件存在
if (!quietMode) console.log(`正在确保月份文件存在: ${filePath} (${monthName})`);
const fileCreated = await ensureFileExists(this.vault, filePath);
if (!fileCreated) {
console.error(`无法访问或创建月份文件: ${filePath}`);
throw new Error(`无法访问月份文件: ${filePath},请检查是否存在同名目录或权限问题`);
}
if (!quietMode) console.log(`月份文件确认: ${filePath}`);
// 检查今日任务是否已存在
const date = getCurrentDate();
// 更改检查方式:不仅检查纯日期,也检查带图标的日期格式
const dateRegex = new RegExp(`## [^\\n]*${date}[^\\n]*\\n`);
const fileContent = await this.vault.read(this.vault.getAbstractFileByPath(filePath) as TFile);
const existingTaskCheck = dateRegex.test(fileContent);
if (existingTaskCheck) {
console.log(`今日(${date})任务已存在于文件中,跳过创建`);
// 如果需要打开文件
if (openFile) {
// 显示提示并打开文件
this.showWarningNotice(`📌 ${getTranslation('notification.taskExists')}`);
const file = this.vault.getAbstractFileByPath(filePath);
if (file && file instanceof TFile) {
const leaf = this.app.workspace.getLeaf();
await leaf.openFile(file);
}
}
return true; // 任务已存在,视为成功
}
// 如果开启了任务统计功能,在添加今日任务前添加昨日统计
let statisticsContent = '';
if (settings.taskStatistics) {
statisticsContent = await this.generateYesterdayStatistics(rootDir, date, quietMode);
}
// 获取任务模板 - 使用新的模板逻辑
let template = '';
if (this.settingsManager.hasCustomTemplate()) {
// 使用用户自定义模板
template = this.settingsManager.getSettings().customTemplate;
} else {
// 使用语言相关的默认模板
template = this.settingsManager.getTemplateByLanguage();
}
// 渲染模板
const renderedContent = renderTemplate(template);
// 合并统计信息和今日任务内容
const fullContent = statisticsContent
? `${renderedContent}\n\n${statisticsContent}`
: renderedContent;
// 追加到文件
if (!quietMode) console.log(`正在向文件追加内容`);
const success = await appendToFile(this.vault, filePath, fullContent);
if (success) {
console.log(`✅ 任务内容追加成功 ${date}`);
// 如果需要打开文件
if (openFile) {
// 打开创建的文件
const file = this.vault.getAbstractFileByPath(filePath);
if (file && file instanceof TFile) {
const leaf = this.app.workspace.getLeaf();
await leaf.openFile(file);
// 延迟一下再显示通知,确保文件已经打开
setTimeout(() => {
this.showSuccessNotice(`${getTranslation('notification.taskAdded')}`);
}, 300);
} else {
throw new Error(`文件创建成功但无法打开: ${filePath}`);
}
} else {
// 静默模式,只在控制台记录
console.log(`✨ 今日(${date})任务已静默添加,无需打开文件`);
}
} else {
throw new Error(`无法向文件追加内容: ${filePath}`);
}
return success;
} catch (error) {
console.error("Error generating daily task:", error);
// 显示错误通知
const errorMsg = error instanceof Error ? error.message : String(error);
this.showErrorNotice(`${getTranslation('notification.error')} ${errorMsg}`);
return false;
}
}
/**
*
* @param rootDir
* @param todayDate
* @param quietMode
* @returns
*/
async generateYesterdayStatistics(rootDir: string, todayDate: string, quietMode: boolean = false): Promise<string> {
try {
// 获取昨天的日期和文件路径
const yesterdayDate = getYesterdayDate();
const yesterdayFilePath = getYesterdayTaskFilePath(rootDir);
if (!quietMode) console.log(`正在获取昨日(${yesterdayDate})任务统计,文件路径: ${yesterdayFilePath}`);
// 检查今日的内容中是否已经包含昨日统计
const todayFilePath = getTaskFilePath(rootDir);
const statsExists = await yesterdayStatisticsExists(this.vault, todayFilePath, todayDate);
if (statsExists) {
if (!quietMode) console.log('昨日统计信息已存在,跳过生成');
return '';
}
// 提取昨日任务内容
const yesterdayContent = await extractTasksForDate(this.vault, yesterdayFilePath, yesterdayDate);
// 如果找不到昨日任务内容
if (!yesterdayContent) {
if (!quietMode) console.log(`找不到昨日(${yesterdayDate})任务内容,跳过统计`);
return '';
}
// 分析任务完成情况
const taskStats = analyzeTaskCompletion(yesterdayContent);
if (!quietMode) {
console.log(`昨日任务统计: 总数=${taskStats.totalTasks}, 已完成=${taskStats.completedTasks}`);
console.log(`未完成任务: ${taskStats.unfinishedTasksList.length}`);
}
// 如果没有任务,不生成统计信息
if (taskStats.totalTasks === 0) {
if (!quietMode) console.log('昨日没有任务,跳过统计');
return '';
}
// 确保在所有语言环境下都调用generateStatisticsContent
return generateStatisticsContent(taskStats, getTranslation);
} catch (error) {
console.error(`生成昨日统计信息时出错:`, error);
return ''; // 出错时返回空字符串,不影响今日任务生成
}
}
/**
*
* @returns
*/
async addTaskManually(): Promise<boolean> {
try {
const settings = this.settingsManager.getSettings();
const rootDir = settings.rootDir.trim() || 'DailyTasks'; // 使用默认目录
// 检查今日任务是否已存在
const exists = await todayTaskExists(this.vault, rootDir);
if (exists) {
// 任务已存在,显示提示
this.showWarningNotice(`📌 ${getTranslation('notification.taskExists')}`);
return false;
}
// 生成任务
return await this.generateDailyTask();
} catch (error) {
console.error("Error adding task manually:", error);
// 显示错误通知
const errorMsg = error instanceof Error ? error.message : String(error);
this.showErrorNotice(`${getTranslation('notification.error')} ${errorMsg}`);
return false;
}
}
/**
*
* @param message
*/
private showSuccessNotice(message: string): void {
const notice = new Notice(
message,
this.settingsManager.getSettings().successNotificationDuration
);
// 添加成功样式
if (notice.noticeEl) {
notice.noticeEl.addClass('daily-task-success-notice');
}
}
/**
*
* @param message
*/
private showWarningNotice(message: string): void {
const notice = new Notice(
message,
this.settingsManager.getSettings().successNotificationDuration
);
// 添加警告样式
if (notice.noticeEl) {
notice.noticeEl.addClass('daily-task-warning-notice');
}
}
/**
*
* @param message
*/
private showErrorNotice(message: string): void {
const notice = new Notice(
message,
this.settingsManager.getSettings().successNotificationDuration
);
// 添加错误样式
if (notice.noticeEl) {
notice.noticeEl.addClass('daily-task-error-notice');
}
}
}

156
src/utils/dateUtils.js Normal file

File diff suppressed because one or more lines are too long

177
src/utils/dateUtils.ts Normal file
View file

@ -0,0 +1,177 @@
import { getLocalizedWeekday, getTranslation } from "../i18n/i18n";
/**
*
*/
/**
*
* @returns 2025
*/
export function getCurrentYear(): string {
return new Date().getFullYear().toString();
}
/**
*
* @returns 04
*/
export function getCurrentMonth(): string {
const month = (new Date().getMonth() + 1).toString();
return month.padStart(2, '0');
}
/**
*
* @param isEnglish 使
* @returns "4月""April"
*/
export function getLocalizedMonthName(isEnglish: boolean = false): string {
const monthIndex = new Date().getMonth(); // 0-11
// 中文月份名称
const chineseMonths = [
"1月", "2月", "3月", "4月", "5月", "6月",
"7月", "8月", "9月", "10月", "11月", "12月"
];
// 英文月份名称
const englishMonths = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
return isEnglish ? englishMonths[monthIndex] : chineseMonths[monthIndex];
}
/**
*
* @returns
*/
export function getDayIcon(): string {
const day = new Date().getDate(); // 1-31
// 为每天分配一个独特的图标
const dayIcons = [
"🌑", // 1日
"🌒", // 2日
"🌓", // 3日
"🌔", // 4日
"🌕", // 5日
"🌖", // 6日
"🌗", // 7日
"🌘", // 8日
"🌟", // 9日
"⭐", // 10日
"🌈", // 11日
"🌞", // 12日
"🌤️", // 13日
"⛅", // 14日
"🌦️", // 15日
"🌧️", // 16日
"⛈️", // 17日
"🌩️", // 18日
"🌪️", // 19日
"🌫️", // 20日
"🌬️", // 21日
"🍀", // 22日
"🌱", // 23日
"🌲", // 24日
"🌳", // 25日
"🌴", // 26日
"🌵", // 27日
"🌺", // 28日
"🌻", // 29日
"🌼", // 30日
"🌸", // 31日
];
// 索引从0开始天数从1开始所以减1
return dayIcons[day - 1] || "📅"; // 如果出现意外,返回默认日历图标
}
/**
*
* @returns
*/
export function isEnglishEnvironment(): boolean {
// 通过翻译系统中的周一测试当前语言
// 获取"weekday.mon"的翻译,如果是"Monday"则为英文环境
const mondayText = getTranslation("weekday.mon");
return mondayText === "Monday";
}
/**
*
* @returns 16
*/
export function getCurrentDay(): string {
const day = new Date().getDate().toString();
return day.padStart(2, '0');
}
/**
*
* @returns 2025-04-16
*/
export function getCurrentDate(): string {
return `${getCurrentYear()}-${getCurrentMonth()}-${getCurrentDay()}`;
}
/**
*
* @returns 🌕 2025-04-16
*/
export function getCurrentDateWithIcon(): string {
const icon = getDayIcon();
return `${icon} ${getCurrentDate()}`;
}
/**
*
* @returns
*/
export function isWorkday(): boolean {
const day = new Date().getDay();
// 0是周日1-5是周一至周五6是周六
return day >= 1 && day <= 5;
}
/**
*
* @returns
*/
export function getCurrentWeekdayName(): string {
const day = new Date().getDay();
return getLocalizedWeekday(day);
}
/**
*
* @returns
*/
export function getYearProgress(): number {
const now = new Date();
const start = new Date(now.getFullYear(), 0, 1); // 当年1月1日
const end = new Date(now.getFullYear() + 1, 0, 1); // 下一年1月1日
const totalDays = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
const passedDays = (now.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
return Math.round((passedDays / totalDays) * 100);
}
/**
*
* @returns
*/
export function getMonthProgress(): number {
const now = new Date();
const start = new Date(now.getFullYear(), now.getMonth(), 1); // 当月1日
const end = new Date(now.getFullYear(), now.getMonth() + 1, 1); // 下个月1日
const totalDays = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
const passedDays = (now.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
return Math.round((passedDays / totalDays) * 100);
}

428
src/utils/fileUtils.js Normal file

File diff suppressed because one or more lines are too long

464
src/utils/fileUtils.ts Normal file
View file

@ -0,0 +1,464 @@
import { normalizePath, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
import { getCurrentDate, getCurrentMonth, getCurrentYear, getLocalizedMonthName, isEnglishEnvironment } from "./dateUtils";
import { getTranslation, TranslationKey } from '../i18n/i18n';
import { Notice } from 'obsidian';
import { DEFAULT_SETTINGS } from '../settings/index';
/**
*
*/
/**
*
* @param vault Obsidian文件系统
* @param path
* @returns
*/
export async function ensureFolderExists(vault: Vault, path: string): Promise<boolean> {
try {
// 确保路径以/结尾,便于处理
path = path.endsWith('/') ? path : path + '/';
// 逐级创建目录
const folders = path.split('/').filter(p => p.length > 0);
let currentPath = '';
for (const folder of folders) {
// 更新当前路径
if (currentPath) {
currentPath += '/' + folder;
} else {
currentPath = folder;
}
// 检查路径是否存在
console.log(`检查目录: ${currentPath}`);
const existingItem = vault.getAbstractFileByPath(currentPath);
if (!existingItem) {
// 路径不存在,创建文件夹
console.log(`目录不存在,正在创建: ${currentPath}`);
try {
await vault.createFolder(currentPath);
console.log(`目录创建成功: ${currentPath}`);
} catch (e) {
// 捕获可能的"文件夹已存在"错误,但继续执行
// 这里处理可能的竞态条件:检查不存在但创建时已存在的情况
console.log(`创建目录时出现异常,可能已被其他进程创建: ${e}`);
// 再次检查目录是否存在
const folderAfterError = vault.getAbstractFileByPath(currentPath);
if (!folderAfterError || !(folderAfterError instanceof TFolder)) {
console.error(`目录创建失败,且无法确认目录已存在: ${currentPath}`);
return false;
}
}
} else if (!(existingItem instanceof TFolder)) {
// 路径存在但不是文件夹(可能是同名文件)
console.error(`路径 ${currentPath} 已存在但不是文件夹,而是: ${existingItem.constructor.name}`);
return false;
} else {
// 文件夹已存在,继续检查下一级
console.log(`目录已存在,无需创建: ${currentPath}`);
}
}
return true;
} catch (error) {
console.error(`创建目录时出现未预期错误(${path}):`, error);
return false;
}
}
/**
*
* @param vault Obsidian文件系统
* @param path
* @param content
* @returns
*/
export async function ensureFileExists(vault: Vault, path: string, content: string = ''): Promise<boolean> {
try {
// 先检查这个路径是否有文件或目录
const existingItem = vault.getAbstractFileByPath(path);
// 如果已存在
if (existingItem) {
// 检查是否为文件而不是文件夹
if (existingItem instanceof TFile) {
console.log(`文件已存在,无需创建: ${path}`);
return true; // 文件已存在,跳过创建
} else {
// 存在但不是文件(可能是同名文件夹)
console.error(`路径 ${path} 存在但不是文件,无法创建文件,可能是同名目录`);
return false;
}
}
// 文件不存在,检查父文件夹是否存在
const lastSlashIndex = path.lastIndexOf('/');
if (lastSlashIndex > 0) {
const parentPath = path.substring(0, lastSlashIndex);
console.log(`检查文件父目录: ${parentPath}`);
const parentExists = await ensureFolderExists(vault, parentPath);
if (!parentExists) {
console.error(`无法确保父目录存在: ${parentPath}`);
return false;
}
}
// 创建文件
try {
console.log(`开始创建文件: ${path}`);
await vault.create(path, content);
console.log(`文件创建成功: ${path}`);
return true;
} catch (e) {
// 如果创建时报错,再次检查文件是否已被创建
// 这可能是由于竞态条件或其他进程同时创建了该文件
console.log(`创建文件时出现异常: ${e}`);
// 再次检查文件是否存在
const fileAfterError = vault.getAbstractFileByPath(path);
if (fileAfterError && fileAfterError instanceof TFile) {
console.log(`尽管出现异常,但文件已存在,可能被其他进程创建: ${path}`);
return true;
}
console.error(`文件创建最终失败: ${path}`);
return false;
}
} catch (error) {
console.error(`创建文件时出现未预期错误(${path}):`, error);
return false;
}
}
/**
*
* @param vault Obsidian文件系统
* @param path
* @param content
* @returns
*/
export async function appendToFile(vault: Vault, path: string, content: string): Promise<boolean> {
try {
const file = vault.getAbstractFileByPath(path);
if (file && file instanceof TFile) {
// 文件存在,追加内容
try {
const currentContent = await vault.read(file);
await vault.modify(file, currentContent + "\n\n" + content);
return true;
} catch (e) {
console.error(`读取或修改文件时出错: ${e}`);
return false;
}
} else {
// 文件不存在,创建文件并写入内容
console.log(`文件不存在,尝试创建: ${path}`);
return await ensureFileExists(vault, path, content);
}
} catch (error) {
console.error(`Error appending to file at ${path}:`, error);
return false;
}
}
/**
*
* @param vault Obsidian文件系统
* @param path
* @param content
* @returns
*/
export async function fileContains(vault: Vault, path: string, content: string): Promise<boolean> {
try {
const file = vault.getAbstractFileByPath(path);
if (file && file instanceof TFile) {
try {
const currentContent = await vault.read(file);
return currentContent.includes(content);
} catch (e) {
console.error(`读取文件内容时出错: ${e}`);
return false;
}
}
// 文件不存在,显然不包含指定内容
console.log(`文件不存在,无法检查内容: ${path}`);
return false;
} catch (error) {
console.error(`Error checking file content at ${path}:`, error);
return false;
}
}
/**
*
* @param rootDir
* @returns
*/
export function getTaskFilePath(rootDir: string): string {
const year = getCurrentYear();
// 根据环境选择不同的月份命名方式
const isEnglish = isEnglishEnvironment();
const monthName = getLocalizedMonthName(isEnglish);
// 使用本地化的月份名称生成文件路径
return normalizePath(`${rootDir}/${year}/${monthName}.md`);
}
/**
*
* @param vault Obsidian文件系统
* @param rootDir
* @returns
*/
export async function todayTaskExists(vault: Vault, rootDir: string): Promise<boolean> {
const taskFilePath = getTaskFilePath(rootDir);
const date = getCurrentDate();
// 查找文件是否存在以及是否包含今天的日期标题(包括带图标格式)
try {
console.log(`检查今日任务是否存在于: ${taskFilePath}`);
// 更改检查方式:改用正则表达式匹配任何包含当前日期的标题行
const file = vault.getAbstractFileByPath(taskFilePath);
if (file && file instanceof TFile) {
const content = await vault.read(file);
const dateRegex = new RegExp(`## [^\\n]*${date}[^\\n]*\\n`);
return dateRegex.test(content);
}
return false;
} catch (error) {
console.error(`检查今日任务时出错:`, error);
return false;
}
}
/**
*
* @returns YYYY-MM-DD
*/
export function getYesterdayDate(): string {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
const year = yesterday.getFullYear();
const month = (yesterday.getMonth() + 1).toString().padStart(2, '0');
const day = yesterday.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
*
* @param rootDir
* @returns
*/
export function getYesterdayTaskFilePath(rootDir: string): string {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const year = yesterday.getFullYear().toString();
const isEnglish = isEnglishEnvironment();
// 获取本地化的月份名称
const month = new Date().getMonth();
const yesterdayMonth = yesterday.getMonth();
// 创建一个临时Date对象用于获取前一天的月份
const tempDate = new Date();
tempDate.setMonth(yesterdayMonth);
// 判断是否为英文环境,获取对应的月份名称
const monthName = isEnglish ?
getMonthNameEN(yesterdayMonth) :
getMonthNameZH(yesterdayMonth);
return normalizePath(`${rootDir}/${year}/${monthName}.md`);
}
/**
*
* @param monthIndex 0-11
* @returns
*/
function getMonthNameZH(monthIndex: number): string {
const months = [
"1月", "2月", "3月", "4月", "5月", "6月",
"7月", "8月", "9月", "10月", "11月", "12月"
];
return months[monthIndex];
}
/**
*
* @param monthIndex 0-11
* @returns
*/
function getMonthNameEN(monthIndex: number): string {
const months = [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
];
return months[monthIndex];
}
/**
*
* @param vault Obsidian保险库
* @param filePath
* @param date YYYY-MM-DD格式
* @returns null
*/
export async function extractTasksForDate(vault: Vault, filePath: string, date: string): Promise<string | null> {
try {
// 检查文件是否存在
const file = vault.getAbstractFileByPath(filePath);
if (!file || !(file instanceof TFile)) {
console.log(`找不到文件: ${filePath}`);
return null;
}
// 读取文件内容
const content = await vault.read(file);
// 寻找日期标题,支持带图标格式
const dateHeaderRegex = new RegExp(`## [^\\n]*${date}[^\\n]*\\n(.*?)(?=\\n## |$)`, 's');
const match = content.match(dateHeaderRegex);
if (match && match[1]) {
return match[1].trim();
}
console.log(`找不到日期 ${date} 的任务内容`);
return null;
} catch (error) {
console.error(`提取任务内容时出错: ${error}`);
return null;
}
}
/**
*
* @param taskContent
* @returns {totalTasks, completedTasks, unfinishedTasksList}
*/
export function analyzeTaskCompletion(taskContent: string): {
totalTasks: number;
completedTasks: number;
unfinishedTasksList: string[];
} {
// 默认返回结果
const result = {
totalTasks: 0,
completedTasks: 0,
unfinishedTasksList: [] as string[]
};
if (!taskContent) {
return result;
}
// 匹配所有任务行(包括已完成和未完成)
const allTasksRegex = /- \[([ x])\] (.+)$/gm;
const completedTasksRegex = /- \[x\] (.+)$/gm;
const unfinishedTasksRegex = /- \[ \] (.+)$/gm;
// 统计总任务数
const allTasksMatches = [...taskContent.matchAll(allTasksRegex)];
result.totalTasks = allTasksMatches.length;
// 统计已完成任务数
const completedTasksMatches = [...taskContent.matchAll(completedTasksRegex)];
result.completedTasks = completedTasksMatches.length;
// 提取未完成任务内容
const unfinishedTasksMatches = [...taskContent.matchAll(unfinishedTasksRegex)];
result.unfinishedTasksList = unfinishedTasksMatches.map(match => match[1].trim());
return result;
}
/**
*
* @param vault Obsidian保险库
* @param filePath
* @param date
* @returns
*/
export async function yesterdayStatisticsExists(vault: Vault, filePath: string, date: string): Promise<boolean> {
try {
// 检查文件是否存在
const file = vault.getAbstractFileByPath(filePath);
if (!file || !(file instanceof TFile)) {
console.log(`找不到文件: ${filePath}`);
return false;
}
// 读取文件内容
const content = await vault.read(file);
// 寻找今日日期下的昨日统计标题,支持带图标格式
const statisticsTitleRegex = new RegExp(`## [^\\n]*${date}[^\\n]*\\n.*?${getTranslation('statistics.title')}`, 's');
return statisticsTitleRegex.test(content);
} catch (error) {
console.error(`检查昨日统计信息时出错: ${error}`);
return false;
}
}
/**
*
* @param tasks
* @param t
* @returns
*/
export function generateStatisticsContent(
tasks: { totalTasks: number; completedTasks: number; unfinishedTasksList: string[] } | { task: string; isCompleted: boolean }[],
t: (key: TranslationKey) => string
): string {
let totalTasks: number;
let completedTasks: number;
let unfinishedTasks: { task: string }[] = [];
// 检查输入类型
if (Array.isArray(tasks)) {
// 如果是任务列表数组
totalTasks = tasks.length;
completedTasks = tasks.filter(task => task.isCompleted).length;
unfinishedTasks = tasks.filter(task => !task.isCompleted).map(task => ({ task: task.task }));
} else {
// 如果是统计对象
totalTasks = tasks.totalTasks;
completedTasks = tasks.completedTasks;
unfinishedTasks = tasks.unfinishedTasksList.map(task => ({ task }));
}
const completionRate = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
const displayTasks = unfinishedTasks.slice(0, 5);
let content = `## ${t('statistics.title')}\n---\n`;
content += `- ${t('statistics.totalTasks')}: ${totalTasks}\n`;
content += `- ${t('statistics.completedTasks')}: ${completedTasks}\n`;
content += `- ${t('statistics.completionRate')}: ${completionRate}%\n\n`;
if (unfinishedTasks.length > 0) {
content += `### ${t('statistics.suggestions')}\n---\n`;
displayTasks.forEach(item => {
content += `- [ ] ${item.task}\n`;
});
if (unfinishedTasks.length > 5) {
const remaining = unfinishedTasks.length - 5;
const moreTasks = remaining === 1
? t('statistics.moreTasks.singular')
: `${remaining} ${t('statistics.moreTasks.plural')}`;
content += `- *${moreTasks}*\n`;
}
}
return content;
}

View file

@ -0,0 +1,51 @@
import { getCurrentDate, getCurrentWeekdayName, getYearProgress, getMonthProgress, getCurrentDateWithIcon } from "./dateUtils";
/**
* 模板变量渲染引擎
*/
/**
* 获取当前时间
* @returns 当前时间如10:30
*/
function getCurrentTime() {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
}
/**
* 渲染模板内容替换其中的变量
* @param template 模板内容
* @returns 渲染后的内容
*/
export function renderTemplate(template) {
// 定义变量映射
const variableMap = {
'date': getCurrentDate(),
'dateWithIcon': getCurrentDateWithIcon(),
'weekday': getCurrentWeekdayName(),
'yearProgress': getYearProgress(),
'monthProgress': getMonthProgress(),
'time': getCurrentTime()
};
// 替换模板中的变量
let renderedContent = template;
for (const [variable, value] of Object.entries(variableMap)) {
renderedContent = renderedContent.replace(new RegExp(`{{${variable}}}`, 'g'), value.toString());
}
return renderedContent;
}
/**
* 获取变量说明
* @returns 变量说明包括中英文
*/
export function getTemplateVariables() {
return {
'date': '当前日期 / Current date (YYYY-MM-DD)',
'dateWithIcon': '带图标的当前日期 / Current date with daily icon',
'weekday': '当前星期 / Current weekday',
'yearProgress': '年度进度百分比 / Year progress percentage',
'monthProgress': '月度进度百分比 / Month progress percentage',
'time': '当前时间 / Current time (HH:MM)'
};
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVtcGxhdGVFbmdpbmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0ZW1wbGF0ZUVuZ2luZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0gsY0FBYyxFQUNkLHFCQUFxQixFQUNyQixlQUFlLEVBQ2YsZ0JBQWdCLEVBQ2hCLHNCQUFzQixFQUN6QixNQUFNLGFBQWEsQ0FBQztBQUVyQjs7R0FFRztBQUVIOzs7R0FHRztBQUNILFNBQVMsY0FBYztJQUNuQixNQUFNLEdBQUcsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFDO0lBQ3ZCLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQ3pELE1BQU0sT0FBTyxHQUFHLEdBQUcsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQzdELE9BQU8sR0FBRyxLQUFLLElBQUksT0FBTyxFQUFFLENBQUM7QUFDakMsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLFFBQWdCO0lBQzNDLFNBQVM7SUFDVCxNQUFNLFdBQVcsR0FBb0M7UUFDakQsTUFBTSxFQUFFLGNBQWMsRUFBRTtRQUN4QixjQUFjLEVBQUUsc0JBQXNCLEVBQUU7UUFDeEMsU0FBUyxFQUFFLHFCQUFxQixFQUFFO1FBQ2xDLGNBQWMsRUFBRSxlQUFlLEVBQUU7UUFDakMsZUFBZSxFQUFFLGdCQUFnQixFQUFFO1FBQ25DLE1BQU0sRUFBRSxjQUFjLEVBQUU7S0FDM0IsQ0FBQztJQUVGLFdBQVc7SUFDWCxJQUFJLGVBQWUsR0FBRyxRQUFRLENBQUM7SUFDL0IsS0FBSyxNQUFNLENBQUMsUUFBUSxFQUFFLEtBQUssQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLEVBQUU7UUFDekQsZUFBZSxHQUFHLGVBQWUsQ0FBQyxPQUFPLENBQ3JDLElBQUksTUFBTSxDQUFDLEtBQUssUUFBUSxJQUFJLEVBQUUsR0FBRyxDQUFDLEVBQ2xDLEtBQUssQ0FBQyxRQUFRLEVBQUUsQ0FDbkIsQ0FBQztLQUNMO0lBRUQsT0FBTyxlQUFlLENBQUM7QUFDM0IsQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sVUFBVSxvQkFBb0I7SUFDaEMsT0FBTztRQUNILE1BQU0sRUFBRSxrQ0FBa0M7UUFDMUMsY0FBYyxFQUFFLHlDQUF5QztRQUN6RCxTQUFTLEVBQUUsd0JBQXdCO1FBQ25DLGNBQWMsRUFBRSxvQ0FBb0M7UUFDcEQsZUFBZSxFQUFFLHFDQUFxQztRQUN0RCxNQUFNLEVBQUUsNkJBQTZCO0tBQ3hDLENBQUM7QUFDTixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgXHJcbiAgICBnZXRDdXJyZW50RGF0ZSwgXHJcbiAgICBnZXRDdXJyZW50V2Vla2RheU5hbWUsIFxyXG4gICAgZ2V0WWVhclByb2dyZXNzLCBcclxuICAgIGdldE1vbnRoUHJvZ3Jlc3MsIFxyXG4gICAgZ2V0Q3VycmVudERhdGVXaXRoSWNvbiBcclxufSBmcm9tIFwiLi9kYXRlVXRpbHNcIjtcclxuXHJcbi8qKlxyXG4gKiDmqKHmnb/lj5jph4/muLLmn5PlvJXmk45cclxuICovXHJcblxyXG4vKipcclxuICog6I635Y+W5b2T5YmN5pe26Ze0XHJcbiAqIEByZXR1cm5zIOW9k+WJjeaXtumXtO+8jOWmgjEwOjMwXHJcbiAqL1xyXG5mdW5jdGlvbiBnZXRDdXJyZW50VGltZSgpOiBzdHJpbmcge1xyXG4gICAgY29uc3Qgbm93ID0gbmV3IERhdGUoKTtcclxuICAgIGNvbnN0IGhvdXJzID0gbm93LmdldEhvdXJzKCkudG9TdHJpbmcoKS5wYWRTdGFydCgyLCAnMCcpO1xyXG4gICAgY29uc3QgbWludXRlcyA9IG5vdy5nZXRNaW51dGVzKCkudG9TdHJpbmcoKS5wYWRTdGFydCgyLCAnMCcpO1xyXG4gICAgcmV0dXJuIGAke2hvdXJzfToke21pbnV0ZXN9YDtcclxufVxyXG5cclxuLyoqXHJcbiAqIOa4suafk+aooeadv+WGheWuue+8jOabv+aNouWFtuS4reeahOWPmOmHj1xyXG4gKiBAcGFyYW0gdGVtcGxhdGUg5qih5p2/5YaF5a65XHJcbiAqIEByZXR1cm5zIOa4suafk+WQjueahOWGheWuuVxyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIHJlbmRlclRlbXBsYXRlKHRlbXBsYXRlOiBzdHJpbmcpOiBzdHJpbmcge1xyXG4gICAgLy8g5a6a5LmJ5Y+Y6YeP5pig5bCEXHJcbiAgICBjb25zdCB2YXJpYWJsZU1hcDogUmVjb3JkPHN0cmluZywgc3RyaW5nIHwgbnVtYmVyPiA9IHtcclxuICAgICAgICAnZGF0ZSc6IGdldEN1cnJlbnREYXRlKCksXHJcbiAgICAgICAgJ2RhdGVXaXRoSWNvbic6IGdldEN1cnJlbnREYXRlV2l0aEljb24oKSxcclxuICAgICAgICAnd2Vla2RheSc6IGdldEN1cnJlbnRXZWVrZGF5TmFtZSgpLFxyXG4gICAgICAgICd5ZWFyUHJvZ3Jlc3MnOiBnZXRZZWFyUHJvZ3Jlc3MoKSxcclxuICAgICAgICAnbW9udGhQcm9ncmVzcyc6IGdldE1vbnRoUHJvZ3Jlc3MoKSxcclxuICAgICAgICAndGltZSc6IGdldEN1cnJlbnRUaW1lKClcclxuICAgIH07XHJcbiAgICBcclxuICAgIC8vIOabv+aNouaooeadv+S4reeahOWPmOmHj1xyXG4gICAgbGV0IHJlbmRlcmVkQ29udGVudCA9IHRlbXBsYXRlO1xyXG4gICAgZm9yIChjb25zdCBbdmFyaWFibGUsIHZhbHVlXSBvZiBPYmplY3QuZW50cmllcyh2YXJpYWJsZU1hcCkpIHtcclxuICAgICAgICByZW5kZXJlZENvbnRlbnQgPSByZW5kZXJlZENvbnRlbnQucmVwbGFjZShcclxuICAgICAgICAgICAgbmV3IFJlZ0V4cChge3ske3ZhcmlhYmxlfX19YCwgJ2cnKSwgXHJcbiAgICAgICAgICAgIHZhbHVlLnRvU3RyaW5nKClcclxuICAgICAgICApO1xyXG4gICAgfVxyXG4gICAgXHJcbiAgICByZXR1cm4gcmVuZGVyZWRDb250ZW50O1xyXG59XHJcblxyXG4vKipcclxuICog6I635Y+W5Y+Y6YeP6K+05piOXHJcbiAqIEByZXR1cm5zIOWPmOmHj+ivtOaYju+8jOWMheaLrOS4reiLseaWh1xyXG4gKi9cclxuZXhwb3J0IGZ1bmN0aW9uIGdldFRlbXBsYXRlVmFyaWFibGVzKCk6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4ge1xyXG4gICAgcmV0dXJuIHtcclxuICAgICAgICAnZGF0ZSc6ICflvZPliY3ml6XmnJ8gLyBDdXJyZW50IGRhdGUgKFlZWVktTU0tREQpJyxcclxuICAgICAgICAnZGF0ZVdpdGhJY29uJzogJ+W4puWbvuagh+eahOW9k+WJjeaXpeacnyAvIEN1cnJlbnQgZGF0ZSB3aXRoIGRhaWx5IGljb24nLFxyXG4gICAgICAgICd3ZWVrZGF5JzogJ+W9k+WJjeaYn+acnyAvIEN1cnJlbnQgd2Vla2RheScsXHJcbiAgICAgICAgJ3llYXJQcm9ncmVzcyc6ICflubTluqbov5vluqbnmb7liIbmr5QgLyBZZWFyIHByb2dyZXNzIHBlcmNlbnRhZ2UnLFxyXG4gICAgICAgICdtb250aFByb2dyZXNzJzogJ+aciOW6pui/m+W6pueZvuWIhuavlCAvIE1vbnRoIHByb2dyZXNzIHBlcmNlbnRhZ2UnLFxyXG4gICAgICAgICd0aW1lJzogJ+W9k+WJjeaXtumXtCAvIEN1cnJlbnQgdGltZSAoSEg6TU0pJ1xyXG4gICAgfTtcclxufSAiXX0=

View file

@ -0,0 +1,65 @@
import {
getCurrentDate,
getCurrentWeekdayName,
getYearProgress,
getMonthProgress,
getCurrentDateWithIcon
} from "./dateUtils";
/**
*
*/
/**
*
* @returns 如10:30
*/
function getCurrentTime(): string {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
}
/**
*
* @param template
* @returns
*/
export function renderTemplate(template: string): string {
// 定义变量映射
const variableMap: Record<string, string | number> = {
'date': getCurrentDate(),
'dateWithIcon': getCurrentDateWithIcon(),
'weekday': getCurrentWeekdayName(),
'yearProgress': getYearProgress(),
'monthProgress': getMonthProgress(),
'time': getCurrentTime()
};
// 替换模板中的变量
let renderedContent = template;
for (const [variable, value] of Object.entries(variableMap)) {
renderedContent = renderedContent.replace(
new RegExp(`{{${variable}}}`, 'g'),
value.toString()
);
}
return renderedContent;
}
/**
*
* @returns
*/
export function getTemplateVariables(): Record<string, string> {
return {
'date': '当前日期 / Current date (YYYY-MM-DD)',
'dateWithIcon': '带图标的当前日期 / Current date with daily icon',
'weekday': '当前星期 / Current weekday',
'yearProgress': '年度进度百分比 / Year progress percentage',
'monthProgress': '月度进度百分比 / Month progress percentage',
'time': '当前时间 / Current time (HH:MM)'
};
}

26
tsconfig.json Normal file
View file

@ -0,0 +1,26 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES2017",
"allowJs": true,
"noImplicitAny": false,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"skipLibCheck": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7",
"ES2017"
]
},
"include": [
"**/*.ts"
]
}

18
version-bump.mjs Normal file
View file

@ -0,0 +1,18 @@
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.argv[2];
const minAppVersion = process.argv[3];
// read minAppVersion from manifest.json if it is not provided
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion: currentMinAppVersion } = manifest;
manifest.version = targetVersion;
if (minAppVersion) {
manifest.minAppVersion = minAppVersion;
}
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
// update versions.json with target version and minAppVersion
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = minAppVersion || currentMinAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));

3
versions.json Normal file
View file

@ -0,0 +1,3 @@
{
"1.0.0": "0.15.0"
}