v0.0.1 init

This commit is contained in:
maigamo 2025-04-16 16:46:40 +08:00 committed by GitHub
parent fbe2804442
commit 540ea3e28a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1818 additions and 0 deletions

1063
main.js Normal file

File diff suppressed because it is too large Load diff

332
main.ts.bak Normal file
View file

@ -0,0 +1,332 @@
import { App, Plugin, PluginSettingTab, Setting, Notice, TFile, normalizePath } from 'obsidian';
interface DailyTaskSettings {
enabled: boolean;
targetFolder: string;
templateFile: string;
schedule: {
time: string;
days: string[];
};
autoOpen: boolean;
}
const DEFAULT_SETTINGS: DailyTaskSettings = {
enabled: true,
targetFolder: '每日任务',
templateFile: '模板/每日任务模板.md',
schedule: {
time: '06:00',
days: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']
},
autoOpen: true
};
export default class DailyTaskAutoGenerator extends Plugin {
settings: DailyTaskSettings;
timer: number | null = null;
async onload() {
await this.loadSettings();
this.addSettingTab(new DailyTaskSettingTab(this.app, this));
this.addRibbonIcon('calendar-plus', '生成今日任务', () => {
this.generateTodayTask();
});
this.addCommand({
id: 'generate-daily-task',
name: '生成今日任务',
callback: () => {
this.generateTodayTask();
}
});
// 设置定时任务
this.scheduleNextTask();
}
onunload() {
if (this.timer) {
window.clearTimeout(this.timer);
}
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
this.scheduleNextTask();
}
scheduleNextTask() {
if (this.timer) {
window.clearTimeout(this.timer);
this.timer = null;
}
if (!this.settings.enabled) {
return;
}
const now = new Date();
const [hours, minutes] = this.settings.schedule.time.split(':').map(Number);
const targetTime = new Date(now);
targetTime.setHours(hours, minutes, 0, 0);
// 如果目标时间已过,则设置为明天
if (targetTime <= now) {
targetTime.setDate(targetTime.getDate() + 1);
}
// 检查目标日期是否是设置的工作日
const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
let dayToAdd = 0;
while (!this.settings.schedule.days.includes(days[targetTime.getDay()])) {
targetTime.setDate(targetTime.getDate() + 1);
dayToAdd++;
// 防止无限循环
if (dayToAdd > 7) {
break;
}
}
const timeUntilTask = targetTime.getTime() - now.getTime();
this.timer = window.setTimeout(() => {
this.generateTodayTask();
this.scheduleNextTask();
}, timeUntilTask);
}
async generateTodayTask() {
if (!this.settings.enabled) {
new Notice('每日任务自动生成功能已禁用。');
return;
}
// 准备文件夹
const folderPath = normalizePath(this.settings.targetFolder);
try {
await this.ensureFolderExists(folderPath);
} catch (error) {
new Notice(`无法创建目标文件夹: ${error}`);
return;
}
// 生成今日日期格式
const today = new Date();
const fileName = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}.md`;
const filePath = `${folderPath}/${fileName}`;
// 检查文件是否已存在
if (await this.app.vault.exists(filePath)) {
if (this.settings.autoOpen) {
this.openFile(filePath);
}
new Notice('今日任务文件已存在。');
return;
}
// 读取模板
try {
const templatePath = normalizePath(this.settings.templateFile);
if (!await this.app.vault.exists(templatePath)) {
new Notice('模板文件不存在!');
return;
}
let templateContent = await this.app.vault.read(this.app.vault.getAbstractFileByPath(templatePath) as TFile);
// 替换模板内容中的日期变量
templateContent = this.replaceTemplateVariables(templateContent, today);
// 创建新的任务文件
await this.app.vault.create(filePath, templateContent);
new Notice('成功创建今日任务文件!');
// 自动打开文件
if (this.settings.autoOpen) {
this.openFile(filePath);
}
} catch (error) {
new Notice(`创建任务文件失败: ${error}`);
}
}
replaceTemplateVariables(content: string, date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
// 星期几(中文)
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
const weekday = weekdays[date.getDay()];
// 替换模板变量
content = content.replace(/{{date}}/g, `${year}-${month}-${day}`);
content = content.replace(/{{year}}/g, year.toString());
content = content.replace(/{{month}}/g, month);
content = content.replace(/{{day}}/g, day);
content = content.replace(/{{weekday}}/g, weekday);
return content;
}
async ensureFolderExists(path: string): Promise<void> {
const folders = path.split('/');
let currentPath = '';
for (const folder of folders) {
if (!folder) continue;
currentPath = currentPath ? `${currentPath}/${folder}` : folder;
if (!(await this.app.vault.exists(currentPath))) {
await this.app.vault.createFolder(currentPath);
}
}
}
async openFile(path: string): Promise<void> {
const file = this.app.vault.getAbstractFileByPath(path);
if (file && file instanceof TFile) {
await this.app.workspace.getLeaf().openFile(file);
}
}
}
class DailyTaskSettingTab extends PluginSettingTab {
plugin: DailyTaskAutoGenerator;
constructor(app: App, plugin: DailyTaskAutoGenerator) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const {containerEl} = this;
containerEl.empty();
containerEl.addClass('daily-task-settings');
new Setting(containerEl)
.setName('启用')
.setDesc('启用每日任务自动生成')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enabled)
.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('目标文件夹')
.setDesc('指定任务文件将被创建的文件夹路径')
.addText(text => text
.setPlaceholder('例如: 任务/每日')
.setValue(this.plugin.settings.targetFolder)
.onChange(async (value) => {
this.plugin.settings.targetFolder = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('模板文件')
.setDesc('指定用于生成任务的模板文件路径')
.addText(text => text
.setPlaceholder('例如: 模板/每日任务模板.md')
.setValue(this.plugin.settings.templateFile)
.onChange(async (value) => {
this.plugin.settings.templateFile = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('生成时间')
.setDesc('指定每日任务生成的时间 (24小时制)')
.addText(text => text
.setPlaceholder('HH:MM')
.setValue(this.plugin.settings.schedule.time)
.onChange(async (value) => {
this.plugin.settings.schedule.time = value;
await this.plugin.saveSettings();
}));
const daysContainer = containerEl.createDiv();
daysContainer.addClass('setting-item');
const daysHeading = daysContainer.createDiv();
daysHeading.addClass('setting-item-heading');
daysHeading.setText('生成日期');
const daysDescription = daysContainer.createDiv();
daysDescription.addClass('setting-item-description');
daysDescription.setText('选择哪些天生成任务');
const daysGrid = daysContainer.createDiv();
daysGrid.style.display = 'grid';
daysGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(120px, 1fr))';
daysGrid.style.gap = '8px';
daysGrid.style.marginTop = '8px';
const days = [
{ id: 'monday', label: '周一' },
{ id: 'tuesday', label: '周二' },
{ id: 'wednesday', label: '周三' },
{ id: 'thursday', label: '周四' },
{ id: 'friday', label: '周五' },
{ id: 'saturday', label: '周六' },
{ id: 'sunday', label: '周日' }
];
days.forEach(day => {
const dayItem = daysGrid.createDiv();
dayItem.addClass('day-checkbox');
const checkbox = dayItem.createEl('input');
checkbox.type = 'checkbox';
checkbox.id = day.id;
checkbox.checked = this.plugin.settings.schedule.days.includes(day.id);
const label = dayItem.createEl('label');
label.htmlFor = day.id;
label.textContent = day.label;
label.style.marginLeft = '8px';
checkbox.addEventListener('change', async () => {
if (checkbox.checked) {
if (!this.plugin.settings.schedule.days.includes(day.id)) {
this.plugin.settings.schedule.days.push(day.id);
}
} else {
this.plugin.settings.schedule.days = this.plugin.settings.schedule.days.filter(d => d !== day.id);
}
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('自动打开')
.setDesc('生成任务后自动打开文件')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.autoOpen)
.onChange(async (value) => {
this.plugin.settings.autoOpen = value;
await this.plugin.saveSettings();
}));
const buttonContainer = containerEl.createDiv();
buttonContainer.style.marginTop = '24px';
buttonContainer.style.textAlign = 'center';
const generateButton = buttonContainer.createEl('button');
generateButton.addClass('daily-task-btn');
generateButton.textContent = '立即生成今日任务';
generateButton.addEventListener('click', () => {
this.plugin.generateTodayTask();
});
}
}

10
manifest.json Normal file
View file

@ -0,0 +1,10 @@
{
"id": "daily-task-auto-generator",
"name": "Daily Task Auto Generator",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "Automatically generate daily tasks in specified folders with custom templates",
"author": "maigamo",
"authorUrl": "https://github.com/maigamo",
"isDesktopOnly": false
}

413
styles.css Normal file
View file

@ -0,0 +1,413 @@
/* 基础样式 */
.daily-task-setting-tab {
padding: 10px 30px;
transition: all 0.3s ease;
max-width: 800px;
margin: 0 auto;
}
/* 设置页面标题 */
.daily-task-setting-tab h2 {
margin-bottom: 20px;
padding-bottom: 10px;
}
.daily-task-setting-tab h3 {
margin-top: 30px;
margin-bottom: 15px;
padding-bottom: 5px;
border-bottom: 1px solid var(--background-modifier-border);
}
/* 三选滑块 */
.mode-toggle-container {
display: flex;
border: 1px solid var(--background-modifier-border);
border-radius: 6px;
overflow: hidden;
margin: 10px 0;
position: relative;
min-width: 450px; /* 增加最小宽度以确保中文显示完整 */
width: 100%;
max-width: 100%;
}
.mode-toggle-option {
flex: 1;
text-align: center;
padding: 8px 12px;
cursor: pointer;
z-index: 1;
transition: all 0.3s ease;
white-space: nowrap;
min-width: 130px; /* 增加最小宽度以确保中文显示完整 */
font-size: 0.9em;
}
.mode-toggle-option:hover {
background-color: var(--background-modifier-hover);
}
.mode-toggle-option.active {
color: var(--text-on-accent);
font-weight: bold;
}
.mode-toggle-slider {
position: absolute;
height: 100%;
width: 33.33%;
background-color: var(--interactive-accent);
border-radius: 5px;
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* 设置项动画 */
.setting-item {
padding: 16px 0; /* 增加垂直间距 */
margin-bottom: 8px;
border-bottom: 1px solid var(--background-modifier-border-subtle);
}
.setting-item:last-child {
border-bottom: none;
}
/* 设置项标题和描述间距 */
.setting-item-info {
padding-right: 20px; /* 增加右侧空间 */
}
.setting-item-description {
margin-top: 6px;
}
/* 设置控件布局 */
.setting-item-control {
margin-top: 10px; /* 增加控件与描述间距 */
}
/* 输入框样式优化 */
.setting-item-control input[type="text"] {
min-width: 300px;
width: 100%;
max-width: 100%;
padding: 8px 10px;
border-radius: 4px;
}
/* 按钮基本样式 */
button {
display: flex;
align-items: center;
justify-content: center;
white-space: nowrap;
text-align: center;
}
/* 保存按钮 - Obsidian紫色 */
.save-button {
background-color: var(--interactive-accent) !important;
color: var(--text-on-accent) !important;
font-weight: 500;
}
/* 危险操作按钮 - 红色 */
.danger-button {
background-color: #e53935 !important;
color: white !important;
font-weight: 500;
}
/* 添加任务按钮 */
.add-task-button {
display: inline-flex !important;
align-items: center;
justify-content: center;
gap: 8px;
color: var(--text-on-accent);
background-color: var(--interactive-accent);
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
margin-top: 20px; /* 增加顶部间距 */
text-align: center;
}
.add-task-button:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.add-task-button:active {
transform: scale(0.95);
}
.add-task-button.loading {
position: relative;
padding-left: 35px;
}
.add-task-button.loading:before {
content: '';
position: absolute;
left: 12px;
top: 50%;
margin-top: -8px;
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 1s linear infinite;
}
.add-task-button.disabled {
opacity: 0.5;
cursor: not-allowed;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 通知动画 */
.notice {
opacity: 0;
transform: translateY(20px);
animation: fadeInUpNotice 0.3s forwards;
}
@keyframes fadeInUpNotice {
to {
opacity: 1;
transform: translateY(0);
}
}
/* 不同类型的通知样式 */
.daily-task-success-notice {
background-color: rgba(46, 160, 67, 0.9) !important;
color: white !important;
border-left: 4px solid #2ea043 !important;
display: flex !important;
align-items: center !important;
padding-left: 12px !important;
}
.daily-task-success-notice::before {
content: '✓';
display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
margin-right: 8px;
font-weight: bold;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 50%;
}
.daily-task-warning-notice {
background-color: rgba(209, 154, 10, 0.9) !important;
color: white !important;
border-left: 4px solid #d19a0a !important;
display: flex !important;
align-items: center !important;
padding-left: 12px !important;
}
.daily-task-warning-notice::before {
content: '⚠';
display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
margin-right: 8px;
font-weight: bold;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 50%;
}
.daily-task-error-notice {
background-color: rgba(203, 36, 49, 0.9) !important;
color: white !important;
border-left: 4px solid #cb2431 !important;
display: flex !important;
align-items: center !important;
padding-left: 12px !important;
}
.daily-task-error-notice::before {
content: '✗';
display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
margin-right: 8px;
font-weight: bold;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 50%;
}
/* 模板预览容器 */
.template-preview {
background-color: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: 5px;
padding: 15px;
margin-top: 10px;
max-height: 200px;
overflow-y: auto;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: none;
}
.template-preview.visible {
display: block;
}
/* 模板编辑区域 */
.template-editor {
height: 150px;
width: 100%;
font-family: var(--font-monospace);
border: 1px solid var(--background-modifier-border);
border-radius: 5px;
padding: 10px;
transition: border 0.3s ease;
resize: vertical;
min-height: 100px;
max-height: 300px;
box-sizing: border-box;
margin-bottom: 8px; /* 增加底部间距 */
}
.template-editor:focus {
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2);
}
/* 模板设置容器样式 */
.template-setting {
display: flex;
flex-direction: column;
width: 100%;
margin-bottom: 30px; /* 增加模板组之间的间距 */
}
.template-setting .setting-item-control {
width: 100%;
}
/* 模板容器宽度 */
.template-setting > div {
width: 100%;
}
/* 元素悬停效果 */
.clickable-icon, button, .setting-item-control > *:not(.disabled) {
transition: transform 0.2s ease, opacity 0.2s ease;
}
.clickable-icon:hover, button:hover, .setting-item-control > *:not(.disabled):hover {
transform: translateY(-2px);
}
.clickable-icon:active, button:active, .setting-item-control > *:not(.disabled):active {
transform: translateY(0);
}
/* 禁用动画 */
.daily-task-no-animations * {
animation: none !important;
transition: none !important;
transform: none !important;
box-shadow: none !important;
}
/* 模板预览头部样式 */
.template-preview-header {
display: flex;
justify-content: center !important;
align-items: center;
gap: 12px;
margin-top: 10px;
margin-bottom: 8px;
}
.template-preview-header button {
font-size: 12px;
padding: 6px 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
min-width: 100px;
border-radius: 4px;
text-align: center;
}
.template-preview-header .svg-icon {
width: 16px;
height: 16px;
}
/* 图标样式 */
.svg-icon {
display: inline-block;
width: 16px;
height: 16px;
margin-right: 4px;
color: currentColor;
}
/* 移动端适配 */
@media (max-width: 768px) {
.daily-task-setting-tab {
padding: 10px 15px;
}
.mode-toggle-container {
min-width: unset;
width: 100%;
}
.mode-toggle-option {
min-width: unset;
padding: 8px 4px;
font-size: 0.8em;
}
.setting-item {
padding: 12px 0;
}
.setting-item-control input[type="text"] {
min-width: unset;
width: 100%;
}
.setting-item-control {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
margin-top: 8px;
}
/* 按钮在移动端垂直排列 */
.template-preview-header {
flex-direction: column;
align-items: flex-end;
gap: 8px;
}
.template-preview-header button {
width: 100%;
}
}