From 2eaaddf9441cf61754f9b370c2edf5073cce323d Mon Sep 17 00:00:00 2001 From: lizhifeng Date: Sat, 10 Jan 2026 23:29:41 +0800 Subject: [PATCH] init --- .gitignore | 108 ++ README.md | 139 +++ config.json | 33 + deploy.sh | 87 ++ esbuild.config.mjs | 48 + main.js | 1753 ++++++++++++++++++++++++++++++++ manifest.json | 10 + package-lock.json | 2406 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 31 + styles.css | 1120 +++++++++++++++++++++ tsconfig.json | 22 + version-bump.mjs | 14 + versions.json | 3 + 使用示例.md | 108 ++ 安装说明.md | 101 ++ 性能优化说明.md | 0 新功能说明.md | 0 测试说明.md | 84 ++ 缓存策略说明.md | 93 ++ 自定义名称说明.md | 64 ++ 20 files changed, 6224 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.json create mode 100755 deploy.sh create mode 100644 esbuild.config.mjs create mode 100644 main.js create mode 100644 manifest.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 styles.css create mode 100644 tsconfig.json create mode 100644 version-bump.mjs create mode 100644 versions.json create mode 100644 使用示例.md create mode 100644 安装说明.md create mode 100644 性能优化说明.md create mode 100644 新功能说明.md create mode 100644 测试说明.md create mode 100644 缓存策略说明.md create mode 100644 自定义名称说明.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..807285f --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Obsidian workspace +.obsidian/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e01610 --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +# Obsidian 记账管理插件 + +基于 Obsidian 日记文件的智能记账管理插件,能够自动识别和统计您在日记中的记账记录。 + +## 功能特点 + +- 🔍 **自动识别记账记录**:从日记文件中自动解析记账信息 +- 📊 **统计分析**:提供收支统计、分类统计和时间范围查询 +- 🏷️ **分类管理**:支持自定义关键词和分类映射 +- 📅 **时间筛选**:可按日期范围查看记账记录 +- 💰 **收支管理**:区分收入和支出,计算结余 +- 🎨 **美观界面**:类似日历插件的现代化界面设计 + +## 记账格式 + +在您的日记文件中,使用以下格式记录账目: + +``` +#关键词 金额 备注说明 +``` + +### 示例: + +```markdown +# 2024年1月10日 + +早上买早餐 +#cy 15.5 豆浆油条 + +中午吃饭 +#cy 45 麻辣烫 + +打车回家 +#jt 28 滴滴打车 + +收到工资 +#sr 8500 1月份工资 +``` + +## 配置说明 + +插件使用 `config.json` 文件进行配置: + +```json +{ + "categories": { + "cy": "餐饮", + "jt": "交通", + "yl": "娱乐", + "gw": "购物", + "yl": "医疗", + "jy": "教育", + "fz": "房租", + "qt": "其他", + "sr": "收入" + }, + "expenseEmoji": "#", + "journalsPath": "journals" +} +``` + +### 配置项说明: + +- **categories**: 关键词与分类的映射关系 + - `key`: 记账时使用的关键词 + - `value`: 显示的中文分类名称 + - `sr`: 特殊关键词,表示收入(其他为支出) +- **expenseEmoji**: 记账标识符(默认为 #) +- **journalsPath**: 日记文件存放路径(默认为 journals) + +## 使用方法 + +### 1. 安装插件 + +将插件文件放入 Obsidian 插件目录并启用。 + +### 2. 配置分类 + +根据个人需求修改 `config.json` 中的分类设置。 + +### 3. 记录账目 + +在日记文件中按格式记录收支: +- 使用 `#` 开头标识记账记录 +- 紧跟关键词(如 cy、jt 等) +- 然后是金额(支持小数) +- 最后是备注说明 + +### 4. 查看统计 + +- 点击侧边栏的计算器图标打开记账管理界面 +- 使用"选择日期范围"按钮筛选特定时间段 +- 查看收支统计、分类统计和详细记录 + +## 界面功能 + +### 统计概览 +- **总收入**:选定时间范围内的收入总额 +- **总支出**:选定时间范围内的支出总额 +- **结余**:收入减去支出的余额 + +### 分类统计 +- 按支出金额排序显示各分类统计 +- 显示每个分类的总金额和记录数量 + +### 记录列表 +- 按日期分组显示所有记账记录 +- 支持右键查看原始日记文件 +- 显示每日收支汇总 + +## 快捷操作 + +- **Ctrl/Cmd + P** → 搜索"记账管理"快速打开 +- **右键记录** → 查看原文跳转到对应日记 +- **刷新数据** → 重新扫描所有日记文件 + +## 注意事项 + +1. 确保日记文件存放在配置的 `journalsPath` 目录下 +2. 记账格式必须严格按照 `#关键词 金额 备注` 的格式 +3. 金额支持小数点,但不要包含货币符号 +4. 关键词必须在配置文件中定义才能被正确识别 +5. 使用 `sr` 关键词标识收入,其他关键词默认为支出 + +## 自定义扩展 + +您可以根据需要: +- 添加新的分类关键词到 `config.json` +- 修改记账标识符(默认为 #) +- 调整日记文件路径 +- 自定义界面样式(修改 `styles.css`) + +## 技术支持 + +如有问题或建议,请联系开发者或提交 Issue。 + +--- + +💡 **提示**:建议养成每日记账的好习惯,这样可以更好地了解自己的消费情况! \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..bb93a5f --- /dev/null +++ b/config.json @@ -0,0 +1,33 @@ +{ + "appName": "每日记账", + "categories": { + "cy": "餐饮", + "jt": "交通", + "yl": "娱乐", + "gw": "购物", + "yy": "医疗", + "jy": "教育", + "fz": "房租", + "qt": "其他", + "sr": "收入" + }, + "budgets": { + "monthly": { + "total": 5000, + "categories": { + "cy": 1000, + "jt": 500, + "yl": 300, + "gw": 800, + "yy": 200, + "jy": 300, + "fz": 2000, + "qt": 200 + } + }, + "enableAlerts": true, + "alertThreshold": 0.8 + }, + "expenseEmoji": "#", + "journalsPath": "journals" +} \ No newline at end of file diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..ef9e95a --- /dev/null +++ b/deploy.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# 记账插件部署脚本 +# 将插件部署到所有 Obsidian vaults + +set -e + +PLUGIN_NAME="obsidian-accounting" +PLUGIN_ID="obsidian-accounting" + +echo "🚀 开始部署 $PLUGIN_NAME 插件..." + +# 定义基础路径 +BASE_PATH="$HOME/Library/Mobile Documents/iCloud~md~obsidian/Documents/漂泊者及其影子" +NOTE_DEMO_PATH="$HOME/Library/Mobile Documents/iCloud~md~obsidian/Documents/note-demo" + +# 定义目标 vault 目录 +VAULTS=( + "$BASE_PATH/.obsidian-mobile/plugins" + "$BASE_PATH/.obsidian-pro/plugins" + "$BASE_PATH/.obsidian-ipad/plugins" + "$BASE_PATH/.obsidian-2017/plugins" + "$BASE_PATH/.obsidian-zhang/plugins" + "$NOTE_DEMO_PATH/.obsidian/plugins" +) + +# 需要复制的文件(总是覆盖) +ALWAYS_COPY_FILES=( + "dist/main.js:main.js" + "manifest.json" + "styles.css" + "config.json" +) + +SUCCESS_COUNT=0 +FAILED_COUNT=0 + +# 部署到每个 vault +for vault in "${VAULTS[@]}"; do + if [ -d "$vault" ]; then + echo "📦 部署到: $vault" + + # 创建插件目录 + mkdir -p "$vault/$PLUGIN_ID" + + # 复制所有文件 + for file in "${ALWAYS_COPY_FILES[@]}"; do + if [[ "$file" == *":"* ]]; then + # 处理 source:target 格式 + source_file="${file%:*}" + target_file="${file#*:}" + if [ -f "$source_file" ]; then + cp "$source_file" "$vault/$PLUGIN_ID/$target_file" + echo " ✓ 复制 $source_file → $target_file" + else + echo " ⚠️ 警告: $source_file 不存在" + fi + else + # 处理普通文件 + if [ -f "$file" ]; then + cp "$file" "$vault/$PLUGIN_ID/" + echo " ✓ 复制 $file" + else + echo " ⚠️ 警告: $file 不存在" + fi + fi + done + + SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) + echo " ✅ 部署成功" + else + echo "❌ 目录不存在: $vault" + FAILED_COUNT=$((FAILED_COUNT + 1)) + fi + echo "" +done + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "📊 部署总结" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "✅ 成功: $SUCCESS_COUNT 个 vault" +echo "❌ 失败: $FAILED_COUNT 个 vault" +echo "" +echo "💡 提示: 在 Obsidian 中重新加载插件以查看更改" +echo " - 打开命令面板 (Cmd/Ctrl + P)" +echo " - 搜索 'Reload app without saving'" +echo " - 或者禁用再启用插件" \ No newline at end of file diff --git a/esbuild.config.mjs b/esbuild.config.mjs new file mode 100644 index 0000000..86fdc27 --- /dev/null +++ b/esbuild.config.mjs @@ -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: ['main.js'], + 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: 'dist/main.js', +}); + +if (prod) { + await context.rebuild(); + process.exit(0); +} else { + await context.watch(); +} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..d0b41a7 --- /dev/null +++ b/main.js @@ -0,0 +1,1753 @@ +const { Plugin, ItemView, Modal, Notice, Menu, TFile } = require('obsidian'); + +// 记账记录解析器 +class AccountingParser { + constructor(config) { + this.config = config; + } + + // 解析单行记账记录 + parseRecord(line, fileDate) { + const { categories, expenseEmoji } = this.config; + + // 检查是否包含记账表情符号 + if (!line.includes(expenseEmoji)) { + return null; + } + + // 创建关键词列表,按长度排序(避免短关键词匹配长关键词的一部分) + const keywords = Object.keys(categories).sort((a, b) => b.length - a.length); + const keywordPattern = keywords.join('|'); + + // 提取记账信息 - 只匹配配置中定义的关键词 + const regex = new RegExp(`${expenseEmoji}\\s*(${keywordPattern})\\s+([\\d.]+)\\s*(.*)`, 'g'); + const match = regex.exec(line); + + if (!match) return null; + + const [, keyword, amount, description] = match; + const category = categories[keyword] || '未分类'; + const isIncome = keyword === 'sr'; + + // 检查描述中是否包含日期(支持账单补录) + let recordDate = fileDate; + const dateRegex = /(\d{4}-\d{2}-\d{2})/; + const dateMatch = description.match(dateRegex); + + if (dateMatch) { + // 验证日期格式是否有效 + const parsedDate = new Date(dateMatch[1]); + if (!isNaN(parsedDate.getTime())) { + recordDate = dateMatch[1]; + console.log(`检测到补录日期: ${recordDate} (原文件日期: ${fileDate})`); + } + } + + return { + date: recordDate, + fileDate: fileDate, // 保留原文件日期用于追溯 + keyword, + category, + amount: parseFloat(amount), + isIncome, + description: description.trim(), + rawLine: line.trim(), + isBackfill: recordDate !== fileDate // 标记是否为补录 + }; + } + + // 解析文件内容 + parseFileContent(content, filePath) { + const lines = content.split('\n'); + const records = []; + + // 从文件路径提取日期 + const dateMatch = filePath.match(/(\d{4}-\d{2}-\d{2})/); + const fileDate = dateMatch ? dateMatch[1] : new Date().toISOString().split('T')[0]; + + lines.forEach(line => { + const record = this.parseRecord(line, fileDate); + if (record) { + records.push(record); + } + }); + + return records; + } +} + +// 记账数据管理器 +class AccountingStorage { + constructor(app, config) { + this.app = app; + this.config = config; + this.parser = new AccountingParser(config); + + // 添加缓存机制 + this.cache = { + records: null, + lastUpdate: null, + fileModTimes: new Map() // 文件修改时间缓存 + }; + + // 缓存有效期(30秒)- 短时间缓存,确保新增记录能快速显示 + this.cacheTimeout = 30 * 1000; + + // 监听文件变化 + this.setupFileWatcher(); + } + + // 设置文件监听器 + setupFileWatcher() { + // 监听文件修改事件 + this.app.vault.on('modify', (file) => { + if (file.path.startsWith(this.config.journalsPath) && file.path.endsWith('.md')) { + console.log(`检测到日记文件变化: ${file.path}`); + // 清除缓存,下次访问时重新加载 + this.clearCache(); + } + }); + + // 监听文件创建事件 + this.app.vault.on('create', (file) => { + if (file.path.startsWith(this.config.journalsPath) && file.path.endsWith('.md')) { + console.log(`检测到新日记文件: ${file.path}`); + this.clearCache(); + } + }); + + // 监听文件删除事件 + this.app.vault.on('delete', (file) => { + if (file.path.startsWith(this.config.journalsPath) && file.path.endsWith('.md')) { + console.log(`检测到日记文件删除: ${file.path}`); + this.clearCache(); + } + }); + } + + // 销毁监听器 + destroy() { + this.app.vault.off('modify'); + this.app.vault.off('create'); + this.app.vault.off('delete'); + } + + // 检查缓存是否有效 - 简化版本,主要依靠文件监听 + isCacheValid() { + // 如果没有缓存数据,无效 + if (!this.cache.records || !this.cache.lastUpdate) { + return false; + } + + // 如果缓存时间超过30秒,也认为无效(兜底机制) + const now = Date.now(); + if ((now - this.cache.lastUpdate) > this.cacheTimeout) { + console.log('缓存超时,需要刷新'); + return false; + } + + return true; + } + + // 简化的文件更新检查 - 现在主要依靠事件监听 + async hasFileUpdates() { + // 由于有文件监听,这里可以简化 + // 如果缓存被清除了,说明有文件更新 + return !this.cache.records; + } + + // 更新文件修改时间缓存 + async updateFileModTimes() { + const { vault } = this.app; + const files = vault.getMarkdownFiles().filter(file => + file.path.startsWith(this.config.journalsPath) + ); + + for (const file of files) { + try { + const stat = await vault.adapter.stat(file.path); + this.cache.fileModTimes.set(file.path, stat.mtime); + } catch (error) { + // 忽略无法获取状态的文件 + } + } + } + + // 获取所有记账记录 - 智能缓存版本 + async getAllRecords(forceRefresh = false) { + // 如果强制刷新,清除缓存 + if (forceRefresh) { + this.clearCache(); + } + + // 如果缓存有效,直接返回 + if (this.isCacheValid()) { + console.log('使用缓存的记账记录'); + return this.cache.records; + } + + console.log('重新加载记账记录...'); + + let records = []; + + try { + // 优先使用搜索方式,更高效 + records = await this.getAllRecordsBySearch(); + + // 更新缓存 + this.cache.records = records; + this.cache.lastUpdate = Date.now(); + + return records; + + } catch (error) { + console.error('获取记账记录失败:', error); + new Notice('获取记账记录失败,请检查日记文件夹'); + + // 如果有缓存,返回缓存数据 + if (this.cache.records) { + new Notice('使用缓存数据'); + return this.cache.records; + } + + return []; + } + } + + // 清除缓存 + clearCache() { + this.cache.records = null; + this.cache.lastUpdate = null; + this.cache.fileModTimes.clear(); + } + + // 使用搜索 API 的方式 - 基于配置的关键词搜索 + async getAllRecordsBySearch() { + const records = []; + const { expenseEmoji, categories } = this.config; + + try { + // 获取所有配置的关键词 + const keywords = Object.keys(categories); + console.log(`开始基于关键词搜索: ${keywords.map(k => expenseEmoji + k).join(', ')}`); + + // 使用关键词搜索文件 + const searchResults = await this.searchFilesWithKeywords(keywords, expenseEmoji); + + console.log(`通过关键词搜索找到 ${searchResults.length} 个包含记账记录的文件`); + + // 只处理搜索到的文件 + for (const file of searchResults) { + try { + const content = await this.app.vault.read(file); + const fileRecords = this.parser.parseFileContent(content, file.path); + if (fileRecords.length > 0) { + console.log(`在 ${file.path} 中找到 ${fileRecords.length} 条记账记录`); + records.push(...fileRecords); + } + } catch (error) { + console.error(`读取搜索结果文件 ${file.path} 失败:`, error); + } + } + + console.log(`总共找到 ${records.length} 条记账记录`); + return records.sort((a, b) => new Date(b.date) - new Date(a.date)); + + } catch (error) { + console.error('关键词搜索功能失败:', error); + // 如果搜索失败,回退到优化的遍历方式 + console.log('回退到传统扫描方式...'); + return await this.getAllRecordsByOptimizedTraversal(); + } + } + + // 搜索包含指定关键词的文件 - 使用 Obsidian 搜索引擎 + async searchFilesWithKeywords(keywords, expenseEmoji) { + console.log('尝试使用 Obsidian 搜索引擎...'); + + try { + // 尝试使用 Obsidian 的搜索引擎 + const searchResults = await this.useObsidianSearchEngine(keywords, expenseEmoji); + if (searchResults.length > 0) { + console.log(`Obsidian 搜索引擎找到 ${searchResults.length} 个文件`); + return searchResults; + } + } catch (error) { + console.log('Obsidian 搜索引擎不可用:', error); + } + + // 回退到自定义关键词搜索 + console.log('使用自定义关键词搜索...'); + return await this.useCustomKeywordSearch(keywords, expenseEmoji); + } + + // 使用 Obsidian 搜索引擎 + async useObsidianSearchEngine(keywords, expenseEmoji) { + const matchingFiles = new Set(); + + // 尝试使用搜索引擎 + try { + // 检查是否有搜索插件 + const searchPlugin = this.app.internalPlugins?.plugins?.['global-search']; + if (searchPlugin && searchPlugin.enabled && searchPlugin.instance) { + const searchInstance = searchPlugin.instance; + + // 为每个关键词执行搜索 + for (const keyword of keywords) { + const searchTerm = `${expenseEmoji}${keyword}`; + console.log(`搜索关键词: ${searchTerm}`); + + try { + // 执行搜索 + const query = `path:${this.config.journalsPath} "${searchTerm}"`; + + // 尝试不同的搜索方法 + let results = null; + + // 方法1: 使用搜索引擎的 searchText 方法 + if (searchInstance.searchEngine && searchInstance.searchEngine.searchText) { + results = await searchInstance.searchEngine.searchText(searchTerm, { + path: this.config.journalsPath + }); + } + + // 方法2: 使用搜索引擎的 search 方法 + if (!results && searchInstance.searchEngine && searchInstance.searchEngine.search) { + results = await searchInstance.searchEngine.search(searchTerm); + } + + // 处理搜索结果 + if (results && results.length > 0) { + results.forEach(result => { + if (result.file && result.file.path.startsWith(this.config.journalsPath)) { + matchingFiles.add(result.file); + } else if (result.path && result.path.startsWith(this.config.journalsPath)) { + const file = this.app.vault.getAbstractFileByPath(result.path); + if (file) { + matchingFiles.add(file); + } + } + }); + console.log(`关键词 ${searchTerm} 找到 ${results.length} 个结果`); + } + + } catch (error) { + console.log(`搜索关键词 ${searchTerm} 失败:`, error); + } + } + + if (matchingFiles.size > 0) { + console.log(`搜索引擎总共找到 ${matchingFiles.size} 个文件`); + return Array.from(matchingFiles); + } + } + } catch (error) { + console.log('搜索引擎访问失败:', error); + } + + throw new Error('搜索引擎未找到结果'); + } + + // 自定义关键词搜索实现 - 只扫描日期格式的文件 + async useCustomKeywordSearch(keywords, expenseEmoji) { + const { vault, metadataCache } = this.app; + const matchingFiles = new Set(); + + // 获取所有 journals 文件夹下的 markdown 文件 + const allFiles = vault.getMarkdownFiles().filter(file => + file.path.startsWith(this.config.journalsPath) + ); + + // 只保留符合日期格式 yyyy-mm-dd.md 的文件 + const datePattern = /\d{4}-\d{2}-\d{2}\.md$/; + const dateFiles = allFiles.filter(file => datePattern.test(file.name)); + + console.log(`⚠️ 警告: Obsidian 搜索引擎不可用,回退到文件扫描模式`); + console.log(`总文件数: ${allFiles.length},日期格式文件: ${dateFiles.length}`); + console.log(`搜索关键词: ${keywords.map(k => expenseEmoji + k).join(', ')}`); + + // 构建正则表达式 - 只匹配配置的关键词 + const keywordPattern = keywords.join('|'); + const searchRegex = new RegExp(`${expenseEmoji}\\s*(${keywordPattern})\\s+[\\d.]+`, 'g'); + + // 尝试使用缓存的文件内容(如果可用) + let usedCache = 0; + let readFromDisk = 0; + + // 使用并行搜索,但分批处理以避免性能问题 + const batchSize = 50; // 每批处理50个文件 + let processedCount = 0; + + for (let i = 0; i < dateFiles.length; i += batchSize) { + const batch = dateFiles.slice(i, i + batchSize); + + const batchPromises = batch.map(async (file) => { + try { + let content = null; + + // 尝试从缓存获取内容 + const cachedMetadata = metadataCache.getFileCache(file); + if (cachedMetadata && cachedMetadata.sections) { + // 如果有缓存,尝试快速检查 + content = await vault.cachedRead(file); + usedCache++; + } else { + // 从磁盘读取 + content = await vault.read(file); + readFromDisk++; + } + + // 使用正则表达式检查是否包含有效的记账记录 + if (searchRegex.test(content)) { + return file; + } + return null; + } catch (error) { + console.error(`检查文件 ${file.path} 失败:`, error); + return null; + } + }); + + const batchResults = await Promise.all(batchPromises); + const validFiles = batchResults.filter(file => file !== null); + validFiles.forEach(file => matchingFiles.add(file)); + + processedCount += batch.length; + + // 每50个文件显示一次进度 + if (processedCount % 50 === 0 || processedCount === dateFiles.length) { + console.log(`已扫描 ${processedCount}/${dateFiles.length} 个日期文件,找到 ${matchingFiles.size} 个包含记账记录的文件`); + } + } + + console.log(`✅ 扫描完成: 共找到 ${matchingFiles.size} 个包含有效记账记录的文件`); + console.log(`📊 性能统计: 缓存读取 ${usedCache} 个,磁盘读取 ${readFromDisk} 个`); + console.log(`🚀 优化效果: 跳过了 ${allFiles.length - dateFiles.length} 个非日期格式文件`); + return Array.from(matchingFiles); + } + + // 优化的遍历方式:预筛选 + 并行处理 + async getAllRecordsByOptimizedTraversal() { + const { vault } = this.app; + const { expenseEmoji } = this.config; + + // 检查文件夹是否存在 + const journalsFolder = vault.getAbstractFileByPath(this.config.journalsPath); + if (!journalsFolder) { + new Notice(`未找到 ${this.config.journalsPath} 文件夹`); + return []; + } + + // 获取所有 journals 文件夹下的 markdown 文件 + const allFiles = vault.getMarkdownFiles().filter(file => + file.path.startsWith(this.config.journalsPath) + ); + + console.log(`开始扫描 ${allFiles.length} 个日记文件...`); + + // 分批处理文件,避免一次性读取太多文件 + const batchSize = 10; + const records = []; + + for (let i = 0; i < allFiles.length; i += batchSize) { + const batch = allFiles.slice(i, i + batchSize); + + const batchPromises = batch.map(async (file) => { + try { + // 先读取文件的前几行来快速检查是否包含记账标识符 + const content = await vault.read(file); + + // 快速检查:如果文件不包含记账标识符,跳过 + if (!content.includes(expenseEmoji)) { + return []; + } + + // 解析记账记录 + const fileRecords = this.parser.parseFileContent(content, file.path); + if (fileRecords.length > 0) { + console.log(`在 ${file.path} 中找到 ${fileRecords.length} 条记账记录`); + } + + return fileRecords; + } catch (error) { + console.error(`读取文件 ${file.path} 失败:`, error); + return []; + } + }); + + const batchResults = await Promise.all(batchPromises); + batchResults.forEach(fileRecords => { + records.push(...fileRecords); + }); + } + + console.log(`总共找到 ${records.length} 条记账记录`); + return records.sort((a, b) => new Date(b.date) - new Date(a.date)); + } + + // 按日期范围筛选记录 + filterRecordsByDateRange(records, startDate, endDate) { + return records.filter(record => { + const recordDate = new Date(record.date); + return recordDate >= new Date(startDate) && recordDate <= new Date(endDate); + }); + } + + // 统计数据 + calculateStatistics(records) { + const stats = { + totalIncome: 0, + totalExpense: 0, + categoryStats: {}, + dailyStats: {}, + budgetStatus: null // 新增预算状态 + }; + + records.forEach(record => { + if (record.isIncome) { + stats.totalIncome += record.amount; + } else { + stats.totalExpense += record.amount; + } + + // 分类统计 + if (!stats.categoryStats[record.category]) { + stats.categoryStats[record.category] = { + total: 0, + count: 0, + records: [] + }; + } + stats.categoryStats[record.category].total += record.amount; + stats.categoryStats[record.category].count += 1; + stats.categoryStats[record.category].records.push(record); + + // 日期统计 + if (!stats.dailyStats[record.date]) { + stats.dailyStats[record.date] = { + income: 0, + expense: 0, + records: [] + }; + } + if (record.isIncome) { + stats.dailyStats[record.date].income += record.amount; + } else { + stats.dailyStats[record.date].expense += record.amount; + } + stats.dailyStats[record.date].records.push(record); + }); + + // 计算预算状态 + stats.budgetStatus = this.calculateBudgetStatus(stats); + + return stats; + } + + // 计算预算状态 + calculateBudgetStatus(stats) { + const budgets = this.config.budgets; + if (!budgets || !budgets.enableAlerts) { + return null; + } + + const budgetStatus = { + totalBudget: budgets.monthly.total, + totalSpent: stats.totalExpense, + totalRemaining: budgets.monthly.total - stats.totalExpense, + totalProgress: budgets.monthly.total > 0 ? stats.totalExpense / budgets.monthly.total : 0, + categories: {}, + alerts: [] + }; + + // 检查总预算 + if (budgetStatus.totalProgress >= budgets.alertThreshold) { + const alertType = budgetStatus.totalProgress >= 1 ? 'exceeded' : 'warning'; + budgetStatus.alerts.push({ + type: alertType, + category: '总预算', + message: alertType === 'exceeded' + ? `总支出已超出预算 ¥${(stats.totalExpense - budgets.monthly.total).toFixed(2)}` + : `总支出已达预算的 ${(budgetStatus.totalProgress * 100).toFixed(0)}%` + }); + } + + // 检查分类预算 + Object.entries(budgets.monthly.categories).forEach(([keyword, budget]) => { + const categoryName = this.config.categories[keyword]; + if (!categoryName || budget <= 0) return; + + const spent = stats.categoryStats[categoryName]?.total || 0; + const progress = spent / budget; + const remaining = budget - spent; + + budgetStatus.categories[categoryName] = { + budget, + spent, + remaining, + progress, + keyword + }; + + // 检查是否需要告警 + if (progress >= budgets.alertThreshold) { + const alertType = progress >= 1 ? 'exceeded' : 'warning'; + budgetStatus.alerts.push({ + type: alertType, + category: categoryName, + message: alertType === 'exceeded' + ? `${categoryName}支出已超出预算 ¥${(spent - budget).toFixed(2)}` + : `${categoryName}支出已达预算的 ${(progress * 100).toFixed(0)}%` + }); + } + }); + + return budgetStatus; + } +} + +// 分类配置模态框 +class CategoryConfigModal extends Modal { + constructor(app, plugin) { + super(app); + this.plugin = plugin; + this.appName = plugin.config.appName || '记账软件'; // 应用名称 + this.categories = { ...plugin.config.categories }; // 复制当前配置 + this.budgets = plugin.config.budgets ? { ...plugin.config.budgets } : { + monthly: { total: 0, categories: {} }, + enableAlerts: true, + alertThreshold: 0.8 + }; + this.currentTab = 'basic'; // 当前标签页,默认基础设置 + } + + onOpen() { + // 使用自定义的应用名称作为标题 + const appName = this.plugin.config.appName || '每日记账'; + this.titleEl.setText(`${appName}配置`); + const { contentEl } = this; + contentEl.empty(); + contentEl.addClass('category-config-modal'); + + // 标签页导航 + this.renderTabs(contentEl); + + // 内容区域 + this.contentArea = contentEl.createDiv('config-content'); + this.renderCurrentTab(); + + // 按钮组 + const buttons = contentEl.createDiv('config-buttons'); + + const cancelBtn = buttons.createEl('button', { + text: '取消', + cls: 'config-btn config-btn-cancel' + }); + cancelBtn.onclick = () => this.close(); + + const saveBtn = buttons.createEl('button', { + text: '保存', + cls: 'config-btn config-btn-save' + }); + saveBtn.onclick = () => this.saveConfig(); + } + + renderTabs(container) { + const tabsContainer = container.createDiv('config-tabs'); + + const tabs = [ + { key: 'basic', label: '基础设置' }, + { key: 'categories', label: '分类管理' }, + { key: 'budgets', label: '预算设置' } + ]; + + tabs.forEach(tab => { + const tabBtn = tabsContainer.createEl('button', { + text: tab.label, + cls: `config-tab ${this.currentTab === tab.key ? 'active' : ''}` + }); + tabBtn.onclick = () => this.switchTab(tab.key); + }); + } + + switchTab(tabKey) { + this.currentTab = tabKey; + + // 更新标签按钮状态 + document.querySelectorAll('.config-tab').forEach(btn => { + btn.classList.remove('active'); + }); + const tabIndex = tabKey === 'basic' ? 1 : (tabKey === 'categories' ? 2 : 3); + document.querySelector(`.config-tab:nth-child(${tabIndex})`).classList.add('active'); + + this.renderCurrentTab(); + } + + renderCurrentTab() { + this.contentArea.empty(); + + if (this.currentTab === 'basic') { + this.renderBasicTab(); + } else if (this.currentTab === 'categories') { + this.renderCategoriesTab(); + } else { + this.renderBudgetsTab(); + } + } + + renderBasicTab() { + // 说明文字 + const description = this.contentArea.createDiv('config-description'); + description.innerHTML = ` +

自定义应用名称,让记账软件更具个性化

+ `; + + // 应用名称设置 + const nameSection = this.contentArea.createDiv('config-section'); + nameSection.createEl('h3', { text: '应用名称' }); + + const nameGroup = nameSection.createDiv('config-input-group'); + nameGroup.createEl('label', { text: '显示名称:' }); + const nameInput = nameGroup.createEl('input', { + type: 'text', + cls: 'config-text-input', + value: this.appName, + attr: { placeholder: '记账软件', maxlength: '20' } + }); + nameInput.oninput = () => { + this.appName = nameInput.value.trim() || '每日记账'; + }; + + // 预览效果 + const previewSection = this.contentArea.createDiv('config-section'); + previewSection.createEl('h3', { text: '预览效果' }); + + const previewBox = previewSection.createDiv('config-preview-box'); + const previewTitle = previewBox.createEl('div', { + cls: 'preview-title' + }); + + const updatePreview = () => { + previewTitle.textContent = `💰 ${this.appName}`; + }; + + updatePreview(); + nameInput.oninput = () => { + this.appName = nameInput.value.trim() || '记账软件'; + updatePreview(); + }; + } + + renderCategoriesTab() { + // 说明文字 + const description = this.contentArea.createDiv('config-description'); + description.innerHTML = ` +

配置记账关键词和对应的分类名称

+

注意: sr 关键词表示收入,其他为支出

+ `; + + // 分类列表 + this.categoryList = this.contentArea.createDiv('category-list'); + this.renderCategoryList(); + + // 添加新分类按钮 + const addButton = this.contentArea.createEl('button', { + text: '+ 添加新分类', + cls: 'add-category-btn' + }); + addButton.onclick = () => this.addNewCategory(); + } + + renderBudgetsTab() { + // 说明文字 + const description = this.contentArea.createDiv('config-description'); + description.innerHTML = ` +

设置月度预算限额,系统会在接近或超出预算时提醒

+

提示: 设置为 0 表示不限制该分类预算

+ `; + + // 预算开关 + const alertSection = this.contentArea.createDiv('budget-section'); + alertSection.createEl('h3', { text: '预算提醒设置' }); + + const alertToggle = alertSection.createDiv('budget-toggle'); + const enableCheckbox = alertToggle.createEl('input', { type: 'checkbox' }); + enableCheckbox.checked = this.budgets.enableAlerts; + enableCheckbox.onchange = () => { + this.budgets.enableAlerts = enableCheckbox.checked; + }; + alertToggle.createSpan({ text: '启用预算告警' }); + + // 告警阈值 + const thresholdSection = alertSection.createDiv('threshold-section'); + thresholdSection.createEl('label', { text: '告警阈值 (%):' }); + const thresholdInput = thresholdSection.createEl('input', { + type: 'number', + value: (this.budgets.alertThreshold * 100).toString(), + attr: { min: '50', max: '100', step: '5' } + }); + thresholdInput.onchange = () => { + this.budgets.alertThreshold = parseInt(thresholdInput.value) / 100; + }; + + // 总预算 + const totalSection = this.contentArea.createDiv('budget-section'); + totalSection.createEl('h3', { text: '月度总预算' }); + const totalInput = totalSection.createEl('input', { + type: 'number', + cls: 'budget-input total-budget', + value: this.budgets.monthly.total.toString(), + attr: { placeholder: '月度总预算', min: '0', step: '100' } + }); + totalInput.onchange = () => { + this.budgets.monthly.total = parseFloat(totalInput.value) || 0; + }; + + // 分类预算 + const categorySection = this.contentArea.createDiv('budget-section'); + categorySection.createEl('h3', { text: '分类预算' }); + + this.budgetList = categorySection.createDiv('budget-list'); + this.renderBudgetList(); + } + + renderBudgetList() { + this.budgetList.empty(); + + Object.entries(this.categories).forEach(([keyword, categoryName]) => { + if (keyword === 'sr') return; // 跳过收入分类 + + const item = this.budgetList.createDiv('budget-item'); + + const label = item.createDiv('budget-label'); + label.textContent = `${categoryName} (${keyword})`; + + const input = item.createEl('input', { + type: 'number', + cls: 'budget-input', + value: (this.budgets.monthly.categories[keyword] || 0).toString(), + attr: { placeholder: '预算金额', min: '0', step: '50' } + }); + + input.onchange = () => { + const value = parseFloat(input.value) || 0; + if (value > 0) { + this.budgets.monthly.categories[keyword] = value; + } else { + delete this.budgets.monthly.categories[keyword]; + } + }; + }); + } + + renderCategoryList() { + this.categoryList.empty(); + + Object.entries(this.categories).forEach(([keyword, category]) => { + const item = this.categoryList.createDiv('category-item'); + + const keywordInput = item.createEl('input', { + type: 'text', + cls: 'category-keyword', + value: keyword, + placeholder: '关键词' + }); + keywordInput.maxLength = 10; + + const categoryInput = item.createEl('input', { + type: 'text', + cls: 'category-name', + value: category, + placeholder: '分类名称' + }); + categoryInput.maxLength = 20; + + const deleteBtn = item.createEl('button', { + text: '删除', + cls: 'delete-category-btn' + }); + deleteBtn.onclick = () => this.deleteCategory(keyword); + + // 监听输入变化 + keywordInput.oninput = () => this.updateCategory(keyword, keywordInput.value, categoryInput.value); + categoryInput.oninput = () => this.updateCategory(keyword, keywordInput.value, categoryInput.value); + }); + } + + addNewCategory() { + const newKeyword = `new${Date.now()}`; + this.categories[newKeyword] = '新分类'; + this.renderCategoryList(); + } + + deleteCategory(keyword) { + delete this.categories[keyword]; + // 同时删除对应的预算设置 + delete this.budgets.monthly.categories[keyword]; + this.renderCategoryList(); + if (this.currentTab === 'budgets') { + this.renderBudgetList(); + } + } + + updateCategory(oldKeyword, newKeyword, categoryName) { + if (oldKeyword !== newKeyword) { + delete this.categories[oldKeyword]; + // 更新预算设置中的关键词 + if (this.budgets.monthly.categories[oldKeyword]) { + this.budgets.monthly.categories[newKeyword] = this.budgets.monthly.categories[oldKeyword]; + delete this.budgets.monthly.categories[oldKeyword]; + } + } + this.categories[newKeyword] = categoryName; + } + + async saveConfig() { + try { + // 验证应用名称 + const cleanAppName = this.appName.trim(); + if (!cleanAppName) { + new Notice('应用名称不能为空'); + return; + } + + // 验证分类配置 + const cleanCategories = {}; + for (const [keyword, category] of Object.entries(this.categories)) { + const cleanKeyword = keyword.trim(); + const cleanCategory = category.trim(); + + if (cleanKeyword && cleanCategory) { + cleanCategories[cleanKeyword] = cleanCategory; + } + } + + if (Object.keys(cleanCategories).length === 0) { + new Notice('至少需要一个分类'); + return; + } + + // 更新配置 + this.plugin.config.appName = cleanAppName; + this.plugin.config.categories = cleanCategories; + this.plugin.config.budgets = this.budgets; + + // 保存到文件 + const configFile = this.app.vault.getAbstractFileByPath('obsidian-accounting/config.json'); + if (configFile) { + const configContent = JSON.stringify(this.plugin.config, null, 4); + await this.app.vault.modify(configFile, configContent); + } + + // 清除缓存,重新加载数据 + this.plugin.storage.clearCache(); + + new Notice('配置已保存,正在刷新...'); + this.close(); + + // 关闭并重新打开视图以刷新标题 + const leaves = this.app.workspace.getLeavesOfType(ACCOUNTING_VIEW); + for (const leaf of leaves) { + // 先分离视图 + await leaf.setViewState({ type: 'empty' }); + } + + // 等待一小段时间后重新打开 + setTimeout(async () => { + await this.plugin.activateView(); + new Notice('配置已保存并刷新'); + }, 100); + } catch (error) { + console.error('保存配置失败:', error); + new Notice('保存配置失败'); + } + } +} +class DateRangeModal extends Modal { + constructor(app, options) { + super(app); + this.options = options; + } + + onOpen() { + this.titleEl.setText('选择查询时间范围'); + const { contentEl } = this; + contentEl.empty(); + contentEl.addClass('date-range-modal'); + + // 开始日期 + const startGroup = contentEl.createDiv('date-group'); + startGroup.createEl('label', { text: '开始日期:' }); + this.startInput = startGroup.createEl('input', { + type: 'date', + cls: 'date-input' + }); + + // 结束日期 + const endGroup = contentEl.createDiv('date-group'); + endGroup.createEl('label', { text: '结束日期:' }); + this.endInput = endGroup.createEl('input', { + type: 'date', + cls: 'date-input' + }); + + // 设置默认值 + const today = new Date(); + const lastMonth = new Date(today); + lastMonth.setMonth(lastMonth.getMonth() - 1); + + this.startInput.value = this.formatDate(lastMonth); + this.endInput.value = this.formatDate(today); + + // 按钮 + const buttons = contentEl.createDiv('date-buttons'); + + const cancelBtn = buttons.createEl('button', { + text: '取消', + cls: 'date-btn date-btn-cancel' + }); + cancelBtn.onclick = () => this.close(); + + const confirmBtn = buttons.createEl('button', { + text: '确定', + cls: 'date-btn date-btn-confirm' + }); + confirmBtn.onclick = () => { + const startDate = this.startInput.value; + const endDate = this.endInput.value; + + if (startDate && endDate) { + this.options.onSelect(startDate, endDate); + this.close(); + } else { + new Notice('请选择完整的日期范围'); + } + }; + } + + formatDate(date) { + return date.toISOString().split('T')[0]; + } +} + +// 记账视图 +const ACCOUNTING_VIEW = 'accounting-view'; + +class AccountingView extends ItemView { + constructor(leaf, plugin) { + super(leaf); + this.plugin = plugin; + this.currentRecords = []; + this.currentStats = null; + } + + getViewType() { + return ACCOUNTING_VIEW; + } + + getDisplayText() { + return this.plugin.config.appName || '每日记账'; + } + + getIcon() { + return 'calculator'; + } + + async onOpen() { + await this.render(); + } + + async onClose() { + // 清理资源 + } + + async render() { + const container = this.containerEl.children[1]; + container.empty(); + container.addClass('accounting-view'); + + this.renderHeader(container); + this.renderFilters(container); + this.renderStats(container); + this.renderRecordsList(container); + + // 初始加载数据 + await this.loadAllRecords(); + } + + renderHeader(container) { + const header = container.createDiv('accounting-header'); + + // 使用配置的应用名称 + const appName = this.plugin.config.appName || '每日记账'; + header.createEl('h2', { text: `💰 ${appName}`, cls: 'accounting-title' }); + + const actions = header.createDiv('accounting-actions'); + + const refreshBtn = actions.createEl('button', { + text: '刷新数据', + cls: 'accounting-btn' + }); + refreshBtn.onclick = () => this.loadAllRecords(true); // 强制刷新 + + const configBtn = actions.createEl('button', { + text: '配置分类', + cls: 'accounting-btn' + }); + configBtn.onclick = () => this.showConfigModal(); + } + + renderFilters(container) { + const filters = container.createDiv('accounting-filters'); + + // 时间筛选区域 + const timeSection = filters.createDiv('filter-section'); + timeSection.createEl('label', { text: '时间筛选:', cls: 'filter-label' }); + + // 快速时间按钮组 + const quickButtons = timeSection.createDiv('quick-time-buttons'); + + const timeRanges = [ + { label: '本周', key: 'thisWeek' }, + { label: '上周', key: 'lastWeek' }, + { label: '本月', key: 'thisMonth' }, + { label: '上月', key: 'lastMonth' }, + { label: '自定义', key: 'custom' } + ]; + + timeRanges.forEach(range => { + const btn = quickButtons.createEl('button', { + text: range.label, + cls: 'quick-time-btn' + }); + btn.setAttribute('data-range', range.key); + btn.onclick = () => this.applyTimeRange(range.key, btn); + }); + + // 当前时间范围显示 + this.timeDisplay = timeSection.createDiv('current-time-display'); + this.timeDisplay.style.display = 'none'; + + // 清除筛选按钮 + const clearBtn = timeSection.createEl('button', { + text: '重置为本月', + cls: 'clear-filter-btn' + }); + clearBtn.onclick = () => this.resetToThisMonth(); + } + + // 应用时间范围筛选 + applyTimeRange(rangeKey, buttonEl) { + const now = new Date(); + let startDate, endDate, displayText; + + switch (rangeKey) { + case 'thisWeek': + startDate = this.getWeekStart(now); + endDate = this.getWeekEnd(now); + displayText = '本周'; + break; + + case 'lastWeek': + const lastWeek = new Date(now); + lastWeek.setDate(lastWeek.getDate() - 7); + startDate = this.getWeekStart(lastWeek); + endDate = this.getWeekEnd(lastWeek); + displayText = '上周'; + break; + + case 'thisMonth': + startDate = new Date(now.getFullYear(), now.getMonth(), 1); + endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0); + displayText = '本月'; + break; + + case 'lastMonth': + startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1); + endDate = new Date(now.getFullYear(), now.getMonth(), 0); + displayText = '上月'; + break; + + case 'custom': + this.showDateRangePicker(); + return; + } + + // 格式化日期 + const startStr = this.formatDate(startDate); + const endStr = this.formatDate(endDate); + + // 应用筛选 + const filteredRecords = this.plugin.storage.filterRecordsByDateRange( + this.currentRecords, startStr, endStr + ); + this.currentStats = this.plugin.storage.calculateStatistics(filteredRecords); + + // 更新显示 + this.timeDisplay.textContent = `${displayText} (${startStr} 至 ${endStr})`; + this.timeDisplay.style.display = 'block'; + + // 更新按钮状态 + document.querySelectorAll('.quick-time-btn').forEach(btn => btn.classList.remove('active')); + buttonEl.classList.add('active'); + + this.updateStatsDisplay(); + this.updateRecordsDisplay(filteredRecords); + } + + // 重置为本月 + resetToThisMonth() { + // 清除所有按钮状态 + document.querySelectorAll('.quick-time-btn').forEach(btn => btn.classList.remove('active')); + + // 应用本月筛选 + const thisMonthBtn = document.querySelector('.quick-time-btn[data-range="thisMonth"]'); + if (thisMonthBtn) { + this.applyTimeRange('thisMonth', thisMonthBtn); + } else { + // 如果找不到按钮,直接应用本月筛选 + this.applyDefaultTimeRange(); + } + } + + // 清除时间筛选(显示全部数据) + clearTimeFilter() { + this.currentStats = this.plugin.storage.calculateStatistics(this.currentRecords); + this.timeDisplay.style.display = 'none'; + + // 清除按钮状态 + document.querySelectorAll('.quick-time-btn').forEach(btn => btn.classList.remove('active')); + + this.updateStatsDisplay(); + this.updateRecordsDisplay(); + } + + // 获取周开始日期(周一) + getWeekStart(date) { + const d = new Date(date); + const day = d.getDay(); + const diff = d.getDate() - day + (day === 0 ? -6 : 1); // 周一为一周开始 + return new Date(d.setDate(diff)); + } + + // 获取周结束日期(周日) + getWeekEnd(date) { + const d = new Date(date); + const day = d.getDay(); + const diff = d.getDate() - day + (day === 0 ? 0 : 7); // 周日为一周结束 + return new Date(d.setDate(diff)); + } + + // 格式化日期为 YYYY-MM-DD + formatDate(date) { + return date.toISOString().split('T')[0]; + } + + renderStats(container) { + this.statsContainer = container.createDiv('accounting-stats'); + this.updateStatsDisplay(); + } + + renderRecordsList(container) { + this.recordsContainer = container.createDiv('accounting-records'); + this.updateRecordsDisplay(); + } + + async loadAllRecords(forceRefresh = false) { + try { + // 显示加载状态 + if (forceRefresh) { + new Notice('正在强制刷新记账数据...'); + } + + this.currentRecords = await this.plugin.storage.getAllRecords(forceRefresh); + this.currentStats = this.plugin.storage.calculateStatistics(this.currentRecords); + + // 默认显示本月数据 + this.applyDefaultTimeRange(); + + const message = forceRefresh + ? `已强制刷新并加载 ${this.currentRecords.length} 条记账记录` + : `已加载 ${this.currentRecords.length} 条记账记录`; + new Notice(message); + } catch (error) { + console.error('加载记账记录失败:', error); + new Notice('加载记账记录失败'); + } + } + + // 应用默认时间范围(本月) + applyDefaultTimeRange() { + const now = new Date(); + const startDate = new Date(now.getFullYear(), now.getMonth(), 1); + const endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0); + + const startStr = this.formatDate(startDate); + const endStr = this.formatDate(endDate); + + // 筛选本月数据 + const filteredRecords = this.plugin.storage.filterRecordsByDateRange( + this.currentRecords, startStr, endStr + ); + this.currentStats = this.plugin.storage.calculateStatistics(filteredRecords); + + // 更新显示 + this.timeDisplay.textContent = `本月 (${startStr} 至 ${endStr})`; + this.timeDisplay.style.display = 'block'; + + // 设置本月按钮为激活状态 + setTimeout(() => { + const thisMonthBtn = document.querySelector('.quick-time-btn[data-range="thisMonth"]'); + if (thisMonthBtn) { + thisMonthBtn.classList.add('active'); + } + }, 100); + + this.updateStatsDisplay(); + this.updateRecordsDisplay(filteredRecords); + } + + showDateRangePicker() { + new DateRangeModal(this.app, { + onSelect: (startDate, endDate) => { + const filteredRecords = this.plugin.storage.filterRecordsByDateRange( + this.currentRecords, startDate, endDate + ); + this.currentStats = this.plugin.storage.calculateStatistics(filteredRecords); + + this.dateDisplay.textContent = `${startDate} 至 ${endDate}`; + this.dateDisplay.style.display = 'block'; + + this.updateStatsDisplay(); + this.updateRecordsDisplay(filteredRecords); + } + }).open(); + } + + // 获取分类颜色 + getCategoryColor(category) { + const colors = { + '餐饮': '#dc3545', // 红色 + '交通': '#007bff', // 蓝色 + '娱乐': '#6f42c1', // 紫色 + '购物': '#fd7e14', // 橙色 + '医疗': '#20c997', // 青色 + '教育': '#198754', // 绿色 + '房租': '#6c757d', // 灰色 + '其他': '#495057', // 深灰色 + '收入': '#28a745', // 成功绿色 + '投资': '#17a2b8', // 信息蓝色 + '礼物': '#e83e8c', // 粉色 + '旅游': '#ffc107', // 警告黄色 + '运动': '#fd7e14' // 橙色 + }; + return colors[category] || '#6c757d'; // 默认灰色 + } + + updateStatsDisplay() { + if (!this.statsContainer) return; + + this.statsContainer.empty(); + + if (!this.currentStats) { + this.statsContainer.createDiv({ text: '暂无数据', cls: 'no-data' }); + return; + } + + const { totalIncome, totalExpense, categoryStats, budgetStatus } = this.currentStats; + const balance = totalIncome - totalExpense; + + // 预算告警(如果有) + if (budgetStatus && budgetStatus.alerts.length > 0) { + this.renderBudgetAlerts(budgetStatus.alerts); + } + + // 总览统计 + const overview = this.statsContainer.createDiv('stats-overview'); + + const incomeCard = overview.createDiv('stat-card income'); + incomeCard.createDiv({ text: '总收入', cls: 'stat-label' }); + incomeCard.createDiv({ text: `¥${totalIncome.toFixed(2)}`, cls: 'stat-value' }); + + const expenseCard = overview.createDiv('stat-card expense'); + expenseCard.createDiv({ text: '总支出', cls: 'stat-label' }); + expenseCard.createDiv({ text: `¥${totalExpense.toFixed(2)}`, cls: 'stat-value' }); + + const balanceCard = overview.createDiv(`stat-card balance ${balance >= 0 ? 'positive' : 'negative'}`); + balanceCard.createDiv({ text: '结余', cls: 'stat-label' }); + balanceCard.createDiv({ text: `¥${balance.toFixed(2)}`, cls: 'stat-value' }); + + // 预算状态卡片 + if (budgetStatus && budgetStatus.totalBudget > 0) { + const budgetCard = overview.createDiv('stat-card budget'); + budgetCard.createDiv({ text: '预算状态', cls: 'stat-label' }); + const remaining = budgetStatus.totalRemaining; + const progressPercent = (budgetStatus.totalProgress * 100).toFixed(0); + budgetCard.createDiv({ + text: remaining >= 0 ? `剩余 ¥${remaining.toFixed(2)}` : `超支 ¥${Math.abs(remaining).toFixed(2)}`, + cls: `stat-value ${remaining >= 0 ? 'positive' : 'negative'}` + }); + budgetCard.createDiv({ text: `已用 ${progressPercent}%`, cls: 'stat-progress' }); + } + + // 分类统计 + if (Object.keys(categoryStats).length > 0) { + const categorySection = this.statsContainer.createDiv('category-stats'); + categorySection.createEl('h3', { text: '分类统计' }); + + const categoryList = categorySection.createDiv('category-list'); + + Object.entries(categoryStats) + .sort(([,a], [,b]) => b.total - a.total) + .forEach(([category, data]) => { + const item = categoryList.createDiv('category-item'); + + const info = item.createDiv('category-info'); + + // 创建彩色标签 + const categoryLabel = info.createDiv('category-label'); + const color = this.getCategoryColor(category); + categoryLabel.style.backgroundColor = color; + categoryLabel.style.color = '#ffffff'; + categoryLabel.textContent = category; + + const amountInfo = info.createDiv('category-amount-info'); + amountInfo.createDiv({ text: `¥${data.total.toFixed(2)}`, cls: 'category-amount' }); + amountInfo.createDiv({ text: `${data.count}笔`, cls: 'category-count' }); + + // 预算进度条(如果有预算设置) + if (budgetStatus && budgetStatus.categories[category]) { + const budgetInfo = budgetStatus.categories[category]; + const progressBar = item.createDiv('budget-progress'); + const progressFill = progressBar.createDiv('budget-progress-fill'); + const progressPercent = Math.min(budgetInfo.progress * 100, 100); + progressFill.style.width = `${progressPercent}%`; + + // 根据进度设置颜色 + if (budgetInfo.progress >= 1) { + progressFill.classList.add('exceeded'); + } else if (budgetInfo.progress >= 0.8) { + progressFill.classList.add('warning'); + } else { + progressFill.classList.add('normal'); + } + + const budgetText = item.createDiv('budget-text'); + budgetText.textContent = `预算: ¥${budgetInfo.budget} | 剩余: ¥${budgetInfo.remaining.toFixed(2)}`; + } + }); + } + } + + // 渲染预算告警 + renderBudgetAlerts(alerts) { + const alertsContainer = this.statsContainer.createDiv('budget-alerts'); + + alerts.forEach(alert => { + const alertItem = alertsContainer.createDiv(`budget-alert ${alert.type}`); + const icon = alert.type === 'exceeded' ? '⚠️' : '⚡'; + alertItem.innerHTML = `${icon} ${alert.message}`; + }); + } + + updateRecordsDisplay(records = this.currentRecords) { + if (!this.recordsContainer) return; + + this.recordsContainer.empty(); + + if (!records || records.length === 0) { + this.recordsContainer.createDiv({ text: '暂无记账记录', cls: 'no-records' }); + return; + } + + const recordsList = this.recordsContainer.createDiv('records-list'); + recordsList.createEl('h3', { text: `记账记录 (${records.length}条)` }); + + // 按日期分组 + const groupedRecords = this.groupRecordsByDate(records); + + Object.entries(groupedRecords) + .sort(([a], [b]) => new Date(b) - new Date(a)) + .forEach(([date, dayRecords]) => { + this.renderDayRecords(recordsList, date, dayRecords); + }); + } + + groupRecordsByDate(records) { + const grouped = {}; + records.forEach(record => { + if (!grouped[record.date]) { + grouped[record.date] = []; + } + grouped[record.date].push(record); + }); + return grouped; + } + + // 获取分类颜色 + getCategoryColor(category) { + const colors = { + '餐饮': '#dc3545', // 红色 + '交通': '#007bff', // 蓝色 + '娱乐': '#6f42c1', // 紫色 + '购物': '#fd7e14', // 橙色 + '医疗': '#20c997', // 青色 + '教育': '#198754', // 绿色 + '房租': '#6c757d', // 灰色 + '其他': '#495057', // 深灰色 + '收入': '#28a745', // 成功绿色 + '投资': '#17a2b8', // 信息蓝色 + '礼物': '#e83e8c', // 粉色 + '旅游': '#ffc107', // 警告黄色 + '运动': '#fd7e14' // 橙色 + }; + return colors[category] || '#6c757d'; // 默认灰色 + } + + renderDayRecords(container, date, records) { + const dayGroup = container.createDiv('day-group'); + + const dayHeader = dayGroup.createDiv('day-header'); + const dayTotal = records.reduce((sum, r) => sum + (r.isIncome ? r.amount : -r.amount), 0); + + dayHeader.createSpan({ text: this.formatDateDisplay(date), cls: 'day-date' }); + dayHeader.createSpan({ + text: `¥${dayTotal.toFixed(2)}`, + cls: `day-total ${dayTotal >= 0 ? 'positive' : 'negative'}` + }); + + const recordsList = dayGroup.createDiv('day-records'); + + records.forEach(record => { + const recordItem = recordsList.createDiv('record-item'); + + // 如果是补录记录,添加标记 + if (record.isBackfill) { + recordItem.classList.add('backfill'); + recordItem.title = `补录记录 (原记录于 ${record.fileDate})`; + } + + const recordInfo = recordItem.createDiv('record-info'); + + // 创建彩色分类标签 + const categoryLabel = recordInfo.createDiv('record-category-label'); + const color = this.getCategoryColor(record.category); + categoryLabel.style.backgroundColor = color; + categoryLabel.style.color = '#ffffff'; + categoryLabel.textContent = record.category; + + // 显示描述,如果是补录则高亮日期 + let description = record.description; + if (record.isBackfill) { + const dateRegex = /(\d{4}-\d{2}-\d{2})/; + description = description.replace(dateRegex, '$1'); + } + + const descDiv = recordInfo.createDiv({ cls: 'record-description' }); + descDiv.innerHTML = description; + + const recordAmount = recordItem.createDiv('record-amount'); + const amountText = record.isIncome ? `+¥${record.amount}` : `-¥${record.amount}`; + recordAmount.createDiv({ + text: amountText, + cls: `amount ${record.isIncome ? 'income' : 'expense'}` + }); + + // 右键菜单 + recordItem.oncontextmenu = (e) => { + e.preventDefault(); + this.showRecordContextMenu(e, record); + }; + }); + } + + showRecordContextMenu(event, record) { + const menu = new Menu(); + + menu.addItem(item => { + item.setTitle('查看原文') + .setIcon('file-text') + .onClick(() => { + this.openJournalFile(record.date); + }); + }); + + menu.showAtMouseEvent(event); + } + + async openJournalFile(date) { + const fileName = `${date}.md`; + const filePath = `${this.plugin.config.journalsPath}/${fileName}`; + + const file = this.app.vault.getAbstractFileByPath(filePath); + if (file instanceof TFile) { + const leaf = this.app.workspace.getLeaf(); + await leaf.openFile(file); + } else { + new Notice(`未找到日记文件: ${filePath}`); + } + } + + formatDateDisplay(dateStr) { + const date = new Date(dateStr); + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(yesterday.getDate() - 1); + + if (dateStr === today.toISOString().split('T')[0]) { + return '今天'; + } else if (dateStr === yesterday.toISOString().split('T')[0]) { + return '昨天'; + } else { + return date.toLocaleDateString('zh-CN', { + month: 'long', + day: 'numeric', + weekday: 'short' + }); + } + } + + showConfigModal() { + new CategoryConfigModal(this.app, this.plugin).open(); + } +} + +// 主插件类 +class AccountingPlugin extends Plugin { + async onload() { + console.log('加载记账管理插件'); + + // 加载配置 + await this.loadConfig(); + + // 初始化存储管理器 + this.storage = new AccountingStorage(this.app, this.config); + + // 注册视图 + this.registerView(ACCOUNTING_VIEW, (leaf) => new AccountingView(leaf, this)); + + // 添加功能区图标 + const appName = this.config.appName || '每日记账'; + this.addRibbonIcon('calculator', appName, () => { + this.activateView(); + }); + + // 添加命令 + this.addCommand({ + id: 'open-accounting', + name: `打开${appName}`, + callback: () => this.activateView() + }); + + this.addCommand({ + id: 'refresh-accounting', + name: '刷新记账数据', + callback: () => this.refreshData() + }); + } + + async onunload() { + console.log('卸载记账管理插件'); + + // 清理文件监听器 + if (this.storage) { + this.storage.destroy(); + } + + this.app.workspace.detachLeavesOfType(ACCOUNTING_VIEW); + } + + async loadConfig() { + // 加载配置文件 + const configFile = this.app.vault.getAbstractFileByPath('obsidian-accounting/config.json'); + if (configFile instanceof TFile) { + try { + const configContent = await this.app.vault.read(configFile); + this.config = JSON.parse(configContent); + + // 兼容旧版本配置:自动补充缺失的字段 + let needUpdate = false; + + // 如果没有 appName 字段,添加默认值 + if (!this.config.appName) { + this.config.appName = "每日记账"; + needUpdate = true; + console.log('自动添加 appName 配置字段'); + } + + // 如果没有 budgets 字段,添加默认值 + if (!this.config.budgets) { + this.config.budgets = { + monthly: { total: 0, categories: {} }, + enableAlerts: true, + alertThreshold: 0.8 + }; + needUpdate = true; + console.log('自动添加 budgets 配置字段'); + } + + // 如果配置有更新,保存回文件 + if (needUpdate) { + const updatedContent = JSON.stringify(this.config, null, 4); + await this.app.vault.modify(configFile, updatedContent); + console.log('配置文件已自动更新'); + } + + } catch (error) { + console.error('加载配置失败:', error); + this.config = this.getDefaultConfig(); + } + } else { + this.config = this.getDefaultConfig(); + } + } + + getDefaultConfig() { + return { + appName: "每日记账", + categories: { + "cy": "餐饮", + "jt": "交通", + "yl": "娱乐", + "gw": "购物", + "yy": "医疗", + "jy": "教育", + "fz": "房租", + "qt": "其他", + "sr": "收入" + }, + expenseEmoji: "#", + journalsPath: "journals" + }; + } + + async activateView() { + const { workspace } = this.app; + + // 检查是否已经有打开的视图 + let leaf = workspace.getLeavesOfType(ACCOUNTING_VIEW)[0]; + + if (!leaf) { + // 创建新的标签页 + leaf = workspace.getLeaf('tab'); + await leaf.setViewState({ + type: ACCOUNTING_VIEW, + active: true + }); + } + + // 激活视图 + workspace.setActiveLeaf(leaf, { focus: true }); + } + + async refreshData() { + const leaves = this.app.workspace.getLeavesOfType(ACCOUNTING_VIEW); + for (const leaf of leaves) { + if (leaf.view instanceof AccountingView) { + await leaf.view.loadAllRecords(); + } + } + } +} + +module.exports = AccountingPlugin; \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..f31742b --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "obsidian-accounting", + "name": "每日记账", + "version": "1.0.0", + "minAppVersion": "1.2.8", + "description": "基于日记文件的每日记账插件,支持自动识别和统计记账记录。", + "author": "Your Name", + "authorUrl": "https://github.com/yourusername", + "isDesktopOnly": false +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..775f763 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2406 @@ +{ + "name": "obsidian-accounting", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "obsidian-accounting", + "version": "1.0.0", + "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" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz", + "integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.6", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz", + "integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.3.tgz", + "integrity": "sha512-1Mlz934GvbgdDmt26rTLmf03cAgLg5HyOgJN+ZGCeP3Q9ynYTNMn2/LQxIl7Uy+o4K6Rfi2OuLsr12JQQR8gNg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.3.tgz", + "integrity": "sha512-XvJsYo3dO3Pi4kpalkyMvfQsjxPWHYjoX4MDiB/FUM4YMfWcXa5l4VCwFWVYI1+92yxqjuqrhNg0CZg3gSouyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.3.tgz", + "integrity": "sha512-nuV2CmLS07Gqh5/GrZLuqkU9Bm6H6vcCspM+zjp9TdQlxJtIe+qqEXQChmfc7nWdyr/yz3h45Utk1tUn8Cz5+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.3.tgz", + "integrity": "sha512-01Hxaaat6m0Xp9AXGM8mjFtqqwDjzlMP0eQq9zll9U85ttVALGCGDuEvra5Feu/NbP5AEP1MaopPwzsTcUq1cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.3.tgz", + "integrity": "sha512-Eo2gq0Q/er2muf8Z83X21UFoB7EU6/m3GNKvrhACJkjVThd0uA+8RfKpfNhuMCl1bKRfBzKOk6xaYKQZ4lZqvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.3.tgz", + "integrity": "sha512-CN62ESxaquP61n1ZjQP/jZte8CE09M6kNn3baos2SeUfdVBkWN5n6vGp2iKyb/bm/x4JQzEvJgRHLGd5F5b81w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.3.tgz", + "integrity": "sha512-feq+K8TxIznZE+zhdVurF3WNJ/Sa35dQNYbaqM/wsCbWdzXr5lyq+AaTUSER2cUR+SXPnd/EY75EPRjf4s1SLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.3.tgz", + "integrity": "sha512-CLP3EgyNuPcg2cshbwkqYy5bbAgK+VhyfMU7oIYyn+x4Y67xb5C5ylxsNUjRmr8BX+MW3YhVNm6Lq6FKtRTWHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.3.tgz", + "integrity": "sha512-JHeZXD4auLYBnrKn6JYJ0o5nWJI9PhChA/Nt0G4MvLaMrvXuWnY93R3a7PiXeJQphpL1nYsaMcoV2QtuvRnF/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.3.tgz", + "integrity": "sha512-FyXlD2ZjZqTFh0sOQxFDiWG1uQUEOLbEh9gKN/7pFxck5Vw0qjWSDqbn6C10GAa1rXJpwsntHcmLqydY9ST9ZA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.3.tgz", + "integrity": "sha512-OrDGMvDBI2g7s04J8dh8/I7eSO+/E7nMDT2Z5IruBfUO/RiigF1OF6xoH33Dn4W/OwAWSUf1s2nXamb28ZklTA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.3.tgz", + "integrity": "sha512-DcnUpXnVCJvmv0TzuLwKBC2nsQHle8EIiAJiJ+PipEVC16wHXaPEKP0EqN8WnBe0TPvMITOUlP2aiL5YMld+CQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.3.tgz", + "integrity": "sha512-BDYf/l1WVhWE+FHAW3FzZPtVlk9QsrwsxGzABmN4g8bTjmhazsId3h127pliDRRu5674k1Y2RWejbpN46N9ZhQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.3.tgz", + "integrity": "sha512-WViAxWYMRIi+prTJTyV1wnqd2mS2cPqJlN85oscVhXdb/ZTFJdrpaqm/uDsZPGKHtbg5TuRX/ymKdOSk41YZow==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.3.tgz", + "integrity": "sha512-Iw8lkNHUC4oGP1O/KhumcVy77u2s6+KUjieUqzEU3XuWJqZ+AY7uVMrrCbAiwWTkpQHkr00BuXH5RpC6Sb/7Ug==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.3.tgz", + "integrity": "sha512-0AGkWQMzeoeAtXQRNB3s4J1/T2XbigM2/Mn2yU1tQSmQRmHIZdkGbVq2A3aDdNslPyhb9/lH0S5GMTZ4xsjBqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.3.tgz", + "integrity": "sha512-4+rR/WHOxIVh53UIQIICryjdoKdHsFZFD4zLSonJ9RRw7bhKzVyXbnRPsWSfwybYqw9sB7ots/SYyufL1mBpEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.3.tgz", + "integrity": "sha512-cVpWnkx9IYg99EjGxa5Gc0XmqumtAwK3aoz7O4Dii2vko+qXbkHoujWA68cqXjhh6TsLaQelfDO4MVnyr+ODeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.3.tgz", + "integrity": "sha512-RxmhKLbTCDAY2xOfrww6ieIZkZF+KBqG7S2Ako2SljKXRFi+0863PspK74QQ7JpmWwncChY25JTJSbVBYGQk2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.3.tgz", + "integrity": "sha512-0r36VeEJ4efwmofxVJRXDjVRP2jTmv877zc+i+Pc7MNsIr38NfsjkQj23AfF7l0WbB+RQ7VUb+LDiqC/KY/M/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.3.tgz", + "integrity": "sha512-wgO6rc7uGStH22nur4aLFcq7Wh86bE9cOFmfTr/yxN3BXvDEdCSXyKkO+U5JIt53eTOgC47v9k/C1bITWL/Teg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.3.tgz", + "integrity": "sha512-FdVl64OIuiKjgXBjwZaJLKp0eaEckifbhn10dXWhysMJkWblg3OEEGKSIyhiD5RSgAya8WzP3DNkngtIg3Nt7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz", + "integrity": "sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/type-utils": "5.29.0", + "@typescript-eslint/utils": "5.29.0", + "debug": "^4.3.4", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.29.0.tgz", + "integrity": "sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/typescript-estree": "5.29.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz", + "integrity": "sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/visitor-keys": "5.29.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz", + "integrity": "sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "5.29.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.29.0.tgz", + "integrity": "sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz", + "integrity": "sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/visitor-keys": "5.29.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.29.0.tgz", + "integrity": "sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.29.0", + "@typescript-eslint/types": "5.29.0", + "@typescript-eslint/typescript-estree": "5.29.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz", + "integrity": "sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.29.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0", + "peer": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", + "integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.3", + "@esbuild/android-arm64": "0.17.3", + "@esbuild/android-x64": "0.17.3", + "@esbuild/darwin-arm64": "0.17.3", + "@esbuild/darwin-x64": "0.17.3", + "@esbuild/freebsd-arm64": "0.17.3", + "@esbuild/freebsd-x64": "0.17.3", + "@esbuild/linux-arm": "0.17.3", + "@esbuild/linux-arm64": "0.17.3", + "@esbuild/linux-ia32": "0.17.3", + "@esbuild/linux-loong64": "0.17.3", + "@esbuild/linux-mips64el": "0.17.3", + "@esbuild/linux-ppc64": "0.17.3", + "@esbuild/linux-riscv64": "0.17.3", + "@esbuild/linux-s390x": "0.17.3", + "@esbuild/linux-x64": "0.17.3", + "@esbuild/netbsd-x64": "0.17.3", + "@esbuild/openbsd-x64": "0.17.3", + "@esbuild/sunos-x64": "0.17.3", + "@esbuild/win32-arm64": "0.17.3", + "@esbuild/win32-ia32": "0.17.3", + "@esbuild/win32-x64": "0.17.3" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/obsidian": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.11.4.tgz", + "integrity": "sha512-n0KD3S+VndgaByrEtEe8NELy0ya6/s+KZ7OcxA6xOm5NN4thxKpQjo6eqEudHEvfGCeT/TYToAKJzitQ1I3XTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "6.5.0", + "@codemirror/view": "6.38.6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..142f0e3 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "obsidian-accounting", + "version": "1.0.0", + "description": "基于日记文件的记账管理插件", + "main": "main.js", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "node esbuild.config.mjs production", + "version": "node version-bump.mjs && git add manifest.json versions.json", + "deploy": "bash deploy.sh" + }, + "keywords": [ + "obsidian", + "plugin", + "accounting", + "finance", + "journal" + ], + "author": "Your Name", + "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" + } +} \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..9ba5978 --- /dev/null +++ b/styles.css @@ -0,0 +1,1120 @@ +/* Notion 风格记账管理插件样式 */ + +.accounting-view { + padding: 24px; + height: 100%; + overflow-y: auto; + background: var(--background-primary); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, "Apple Color Emoji", Arial, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol"; +} + +/* 头部样式 - Notion 风格 */ +.accounting-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 32px; + padding-bottom: 16px; + border-bottom: 1px solid var(--background-modifier-border); +} + +.accounting-title { + margin: 0; + color: var(--text-normal); + font-size: 32px; + font-weight: 700; + line-height: 1.2; +} + +.accounting-actions { + display: flex; + gap: 8px; +} + +.accounting-btn { + padding: 8px 16px; + background: var(--background-primary); + color: var(--text-normal); + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.15s ease; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.accounting-btn:hover { + background: var(--background-modifier-hover); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + transform: translateY(-1px); +} + +/* 筛选器样式 - Notion 卡片风格 */ +.accounting-filters { + margin-bottom: 24px; + padding: 20px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.filter-section { + margin-bottom: 16px; +} + +.filter-section:last-child { + margin-bottom: 0; +} + +.filter-label { + display: block; + margin-bottom: 12px; + font-weight: 600; + color: var(--text-normal); + font-size: 14px; +} + +.quick-time-buttons { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 12px; +} + +.quick-time-btn { + padding: 8px 16px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + cursor: pointer; + color: var(--text-normal); + font-size: 13px; + font-weight: 500; + transition: all 0.15s ease; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.quick-time-btn:hover { + background: var(--background-modifier-hover); + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.quick-time-btn.active { + background: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); + box-shadow: 0 2px 8px rgba(37, 99, 235, 0.3); +} + +.current-time-display { + padding: 12px 16px; + background: linear-gradient(135deg, #f0f9ff, #e0f2fe); + color: var(--text-normal); + border: 1px solid #bae6fd; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + margin-bottom: 12px; +} + +.clear-filter-btn { + padding: 8px 16px; + background: var(--background-primary); + color: var(--text-normal); + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: all 0.15s ease; +} + +.clear-filter-btn:hover { + background: var(--background-modifier-hover); + transform: translateY(-1px); +} + +/* 统计卡片样式 - Notion 风格 */ +.accounting-stats { + margin-bottom: 32px; +} + +.stats-overview { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + margin-bottom: 24px; +} + +.stat-card { + padding: 20px; + border-radius: 8px; + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + transition: all 0.15s ease; + min-width: 0; /* 防止内容溢出 */ +} + +.stat-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +.stat-card.income { + background: linear-gradient(135deg, #f0fdf4, #dcfce7); + border-color: #bbf7d0; +} + +.stat-card.expense { + background: linear-gradient(135deg, #fef3c7, #fde68a); + border-color: #fcd34d; +} + +.stat-card.balance.positive { + background: linear-gradient(135deg, #ecfdf5, #d1fae5); + border-color: #86efac; +} + +.stat-card.balance.negative { + background: linear-gradient(135deg, #fef2f2, #fecaca); + border-color: #fca5a5; +} + +.stat-card.budget { + background: linear-gradient(135deg, #eff6ff, #dbeafe); + border-color: #93c5fd; +} + +.stat-label { + font-size: 12px; + color: var(--text-muted); + margin-bottom: 8px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; + white-space: nowrap; +} + +.stat-value { + font-size: 24px; + font-weight: 700; + color: var(--text-normal); + line-height: 1.2; + word-break: break-all; /* 防止长数字换行 */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.stat-progress { + font-size: 11px; + color: var(--text-muted); + margin-top: 4px; + font-weight: 500; + white-space: nowrap; +} + +/* 预算告警样式 - Notion 风格 */ +.budget-alerts { + margin-bottom: 24px; +} + +.budget-alert { + padding: 16px 20px; + margin-bottom: 12px; + border-radius: 8px; + font-weight: 500; + font-size: 14px; + border-left: 4px solid; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.budget-alert.warning { + background: linear-gradient(135deg, #fffbeb, #fef3c7); + color: #92400e; + border-left-color: #f59e0b; +} + +.budget-alert.exceeded { + background: linear-gradient(135deg, #fef2f2, #fecaca); + color: #991b1b; + border-left-color: #ef4444; +} + +/* 分类统计样式 - Bootstrap 风格标签 */ +.category-stats h3 { + margin-bottom: 16px; + color: var(--text-normal); + font-size: 18px; + font-weight: 600; +} + +.category-list { + display: grid; + gap: 8px; +} + +.category-item { + padding: 12px 16px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + transition: all 0.15s ease; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.category-item:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transform: translateY(-1px); +} + +.category-info { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + gap: 12px; +} + +/* Bootstrap 风格的分类标签 */ +.category-label { + display: inline-block; + padding: 4px 8px; + font-size: 12px; + font-weight: 600; + border-radius: 4px; + text-transform: uppercase; + letter-spacing: 0.5px; + white-space: nowrap; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.category-amount-info { + display: flex; + align-items: center; + gap: 8px; +} + +.category-amount { + font-weight: 700; + color: var(--text-accent); + font-size: 15px; + white-space: nowrap; +} + +.category-count { + font-size: 11px; + color: var(--text-muted); + background: var(--background-secondary); + padding: 2px 6px; + border-radius: 10px; + font-weight: 500; + white-space: nowrap; +} + +/* 预算进度条样式 - Notion 风格 */ +.budget-progress { + width: 100%; + height: 8px; + background: var(--background-modifier-border); + border-radius: 4px; + overflow: hidden; + margin: 8px 0; +} + +.budget-progress-fill { + height: 100%; + transition: width 0.3s ease; + border-radius: 4px; +} + +.budget-progress-fill.normal { + background: linear-gradient(90deg, #10b981, #059669); +} + +.budget-progress-fill.warning { + background: linear-gradient(90deg, #f59e0b, #d97706); +} + +.budget-progress-fill.exceeded { + background: linear-gradient(90deg, #ef4444, #dc2626); +} + +.budget-text { + font-size: 12px; + color: var(--text-muted); + margin-top: 4px; + font-weight: 500; +} + +/* 记录列表样式 - Notion 风格 */ +.accounting-records { + margin-top: 24px; +} + +.records-list h3 { + margin-bottom: 20px; + color: var(--text-normal); + font-size: 18px; + font-weight: 600; +} + +.day-group { + margin-bottom: 24px; + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + background: var(--background-primary); +} + +.day-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + background: var(--background-secondary); + border-bottom: 1px solid var(--background-modifier-border); +} + +.day-date { + font-weight: 600; + color: var(--text-normal); + font-size: 15px; +} + +.day-total { + font-weight: 700; + font-size: 16px; +} + +.day-total.positive { + color: #059669; +} + +.day-total.negative { + color: #dc2626; +} + +.day-records { + background: var(--background-primary); +} + +.record-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + border-bottom: 1px solid var(--background-modifier-border-focus); + cursor: pointer; + transition: all 0.15s ease; + position: relative; +} + +.record-item:hover { + background: var(--background-modifier-hover); +} + +.record-item:last-child { + border-bottom: none; +} + +/* 补录标记 */ +.record-item.backfill::before { + content: "补录"; + position: absolute; + top: 8px; + right: 8px; + font-size: 10px; + color: var(--text-muted); + background: var(--background-modifier-border); + padding: 2px 6px; + border-radius: 4px; + font-weight: 500; +} + +.record-info { + flex: 1; +} + +/* 记录中的分类标签 */ +.record-category-label { + display: inline-block; + padding: 3px 6px; + font-size: 11px; + font-weight: 600; + border-radius: 3px; + text-transform: uppercase; + letter-spacing: 0.3px; + margin-bottom: 4px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.record-category { + font-weight: 600; + color: var(--text-accent); + margin-bottom: 4px; + font-size: 14px; +} + +.record-description { + font-size: 13px; + color: var(--text-muted); + line-height: 1.4; +} + +.record-amount { + text-align: right; +} + +.amount { + font-weight: 700; + font-size: 16px; +} + +.amount.income { + color: #059669; +} + +.amount.expense { + color: #dc2626; +} + +/* 空状态样式 - Notion 风格 */ +.no-data, .no-records { + text-align: center; + padding: 48px; + color: var(--text-muted); + font-size: 15px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .accounting-view { + padding: 16px; + } + + .accounting-title { + font-size: 24px; + } + + .accounting-header { + flex-direction: column; + gap: 16px; + align-items: flex-start; + } + + .stats-overview { + grid-template-columns: 1fr 1fr; + gap: 12px; + } + + .stat-card { + padding: 16px; + } + + .stat-value { + font-size: 20px; + } + + .category-info { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + + .category-amount, + .category-count { + align-self: flex-end; + } + + .record-item { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .record-amount { + align-self: flex-end; + } + + .category-config-modal { + min-width: 90vw; + max-width: 90vw; + padding: 20px; + } + + .category-item { + grid-template-columns: 80px 1fr 60px; + gap: 8px; + padding: 10px 12px; + } + + .category-keyword, + .category-name { + font-size: 12px; + padding: 5px 8px; + } +} + +@media (max-width: 480px) { + .stats-overview { + grid-template-columns: 1fr; + } + + .quick-time-buttons { + flex-direction: column; + gap: 6px; + } + + .quick-time-btn { + width: 100%; + text-align: center; + } +} + +/* 筛选器样式 */ +.accounting-filters { + margin-bottom: 20px; + padding: 15px; + background: var(--background-secondary); + border-radius: 8px; +} + +.filter-section { + margin-bottom: 15px; +} + +.filter-section:last-child { + margin-bottom: 0; +} + +.filter-label { + display: block; + margin-bottom: 10px; + font-weight: 500; + color: var(--text-normal); +} + +.quick-time-buttons { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 10px; +} + +.quick-time-btn { + padding: 6px 12px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + cursor: pointer; + color: var(--text-normal); + font-size: 12px; + transition: all 0.2s; +} + +.quick-time-btn:hover { + background: var(--background-modifier-hover); +} + +.quick-time-btn.active { + background: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +.current-time-display { + padding: 8px 12px; + background: var(--background-modifier-success); + color: var(--text-on-accent); + border-radius: 4px; + font-size: 12px; + margin-bottom: 10px; +} + +.clear-filter-btn { + padding: 6px 12px; + background: var(--background-modifier-error); + color: var(--text-on-accent); + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; +} + +.clear-filter-btn:hover { + opacity: 0.8; +} + +.filter-group { + display: flex; + align-items: center; + gap: 10px; +} + +.filter-group label { + font-weight: 500; + color: var(--text-normal); +} + +.filter-btn { + padding: 6px 12px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + cursor: pointer; + color: var(--text-normal); +} + +.filter-btn:hover { + background: var(--background-modifier-hover); +} + +.date-display { + padding: 4px 8px; + background: var(--background-modifier-success); + color: var(--text-on-accent); + border-radius: 4px; + font-size: 12px; +} + +/* 统计卡片样式 */ +.accounting-stats { + margin-bottom: 25px; +} + +.stats-overview { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; + margin-bottom: 20px; +} + .category-name { + font-size: 13px; + padding: 6px 10px; + } + + .delete-category-btn { + margin-left: 0; + } +} + +/* 分类配置模态框样式 - Notion 风格 */ +.category-config-modal { + padding: 24px; + min-width: 650px; + max-width: 700px; + background: var(--background-primary); + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); +} + +/* 标签页样式 - Notion 风格 */ +.config-tabs { + display: flex; + margin-bottom: 32px; + border-bottom: 1px solid var(--background-modifier-border); + gap: 4px; +} + +.config-tab { + padding: 12px 20px; + background: none; + border: none; + cursor: pointer; + color: var(--text-muted); + font-size: 15px; + font-weight: 500; + border-radius: 6px 6px 0 0; + transition: all 0.15s ease; + position: relative; +} + +.config-tab:hover { + color: var(--text-normal); + background: var(--background-modifier-hover); +} + +.config-tab.active { + color: var(--text-normal); + background: var(--background-primary); + font-weight: 600; +} + +.config-tab.active::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 2px; + background: var(--interactive-accent); + border-radius: 1px; +} + +.config-content { + min-height: 400px; +} + +.config-description { + margin-bottom: 24px; + padding: 16px 20px; + background: linear-gradient(135deg, #f8fafc, #f1f5f9); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + font-size: 14px; + color: var(--text-muted); + line-height: 1.5; +} + +.config-description p { + margin: 0 0 8px 0; +} + +.config-description p:last-child { + margin-bottom: 0; +} + +/* 分类列表样式 - Notion 表格风格 */ +.category-list { + max-height: 400px; + overflow-y: auto; + margin-bottom: 20px; + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + background: var(--background-primary); +} + +.category-item { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 20px; + border-bottom: 1px solid var(--background-modifier-border-focus); + transition: background 0.15s ease; +} + +.category-item:hover { + background: var(--background-modifier-hover); +} + +.category-item:last-child { + border-bottom: none; +} + +.category-keyword, +.category-name { + padding: 8px 12px; + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + background: var(--background-primary); + color: var(--text-normal); + font-size: 14px; + transition: all 0.15s ease; +} + +.category-keyword { + width: 80px; + font-family: monospace; + text-align: center; + font-weight: 600; + flex-shrink: 0; +} + +.category-name { + width: 120px; + flex-shrink: 0; +} + +.category-keyword:focus, +.category-name:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); +} + +.delete-category-btn { + padding: 6px 12px; + background: linear-gradient(135deg, #fef2f2, #fecaca); + color: #991b1b; + border: 1px solid #fca5a5; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + transition: all 0.15s ease; + white-space: nowrap; + margin-left: auto; +} + +.delete-category-btn:hover { + background: linear-gradient(135deg, #fecaca, #fca5a5); + transform: translateY(-1px); +} + +.add-category-btn { + width: 100%; + padding: 12px 20px; + background: linear-gradient(135deg, var(--interactive-accent), #3b82f6); + color: var(--text-on-accent); + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 15px; + font-weight: 600; + margin-bottom: 24px; + transition: all 0.15s ease; + box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2); +} + +.add-category-btn:hover { + background: linear-gradient(135deg, #3b82f6, #2563eb); + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(37, 99, 235, 0.3); +} + +/* 预算设置样式 - Notion 风格 */ +.budget-section { + margin-bottom: 32px; + padding: 20px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.budget-section h3 { + margin-bottom: 16px; + color: var(--text-normal); + font-size: 18px; + font-weight: 600; +} + +.budget-toggle { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 16px; + padding: 12px; + background: var(--background-secondary); + border-radius: 6px; +} + +.budget-toggle input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: var(--interactive-accent); +} + +.budget-toggle span { + font-weight: 500; + color: var(--text-normal); +} + +.threshold-section { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 16px; + padding: 12px; + background: var(--background-secondary); + border-radius: 6px; +} + +.threshold-section label { + font-weight: 500; + color: var(--text-normal); +} + +.threshold-section input { + width: 80px; + padding: 6px 10px; + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + background: var(--background-primary); + color: var(--text-normal); + font-weight: 500; +} + +.budget-input { + width: 100%; + padding: 12px 16px; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + background: var(--background-primary); + color: var(--text-normal); + font-size: 15px; + transition: all 0.15s ease; +} + +.budget-input:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); +} + +.budget-input.total-budget { + font-size: 18px; + font-weight: 600; + text-align: center; +} + +.budget-list { + display: grid; + gap: 14px; +} + +.budget-item { + display: flex; + align-items: center; + gap: 16px; + padding: 16px 20px; + background: var(--background-secondary); + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + transition: all 0.15s ease; +} + +.budget-item:hover { + background: var(--background-modifier-hover); +} + +.budget-label { + font-weight: 500; + color: var(--text-normal); + font-size: 15px; + flex: 1; +} + +.budget-item .budget-input { + width: 150px; + flex-shrink: 0; +}round: var(--background-secondary); + border-radius: 6px; + transition: all 0.15s ease; +} + +.budget-item:hover { + background: var(--background-modifier-hover); +} + +.budget-label { + font-weight: 500; + color: var(--text-normal); + font-size: 14px; +} + +/* 按钮组样式 - Notion 风格 */ +.config-buttons { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid var(--background-modifier-border); +} + +.config-btn { + padding: 10px 20px; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.15s ease; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.config-btn-cancel { + background: var(--background-primary); + color: var(--text-normal); +} + +.config-btn-cancel:hover { + background: var(--background-modifier-hover); + transform: translateY(-1px); +} + +.config-btn-save { + background: linear-gradient(135deg, var(--interactive-accent), #3b82f6); + color: var(--text-on-accent); + border-color: var(--interactive-accent); + box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2); +} + +.config-btn-save:hover { + background: linear-gradient(135deg, #3b82f6, #2563eb); + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(37, 99, 235, 0.3); +} + +/* 配置模态框 - 基础设置 */ +.config-section { + margin-bottom: 32px; + padding: 20px; + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); +} + +.config-section h3 { + margin-bottom: 16px; + color: var(--text-normal); + font-size: 18px; + font-weight: 600; +} + +.config-input-group { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 16px; +} + +.config-input-group label { + font-weight: 500; + color: var(--text-normal); + min-width: 100px; +} + +.config-text-input { + flex: 1; + padding: 10px 14px; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + background: var(--background-primary); + color: var(--text-normal); + font-size: 15px; + transition: all 0.15s ease; +} + +.config-text-input:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); +} + +.config-preview-box { + padding: 24px; + background: linear-gradient(135deg, #f8fafc, #f1f5f9); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + text-align: center; +} + +.preview-title { + font-size: 32px; + font-weight: 700; + color: var(--text-normal); + line-height: 1.2; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..923fcd0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "inlineSourceMap": true, + "inlineSources": true, + "module": "ESNext", + "target": "ES6", + "allowJs": true, + "noImplicitAny": false, + "moduleResolution": "node", + "importHelpers": true, + "isolatedModules": true, + "strictNullChecks": true, + "lib": [ + "DOM", + "ES6" + ] + }, + "include": [ + "**/*.ts" + ] +} \ No newline at end of file diff --git a/version-bump.mjs b/version-bump.mjs new file mode 100644 index 0000000..371fc31 --- /dev/null +++ b/version-bump.mjs @@ -0,0 +1,14 @@ +import { readFileSync, writeFileSync } from "fs"; + +const targetVersion = process.env.npm_package_version; + +// read minAppVersion from manifest.json and bump version to target version +let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); +const { minAppVersion } = manifest; +manifest.version = targetVersion; +writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); + +// update versions.json with target version and minAppVersion from manifest.json +let versions = JSON.parse(readFileSync("versions.json", "utf8")); +versions[targetVersion] = minAppVersion; +writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); \ No newline at end of file diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..f1e727d --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +{ + "1.0.0": "1.2.8" +} \ No newline at end of file diff --git a/使用示例.md b/使用示例.md new file mode 100644 index 0000000..e55d407 --- /dev/null +++ b/使用示例.md @@ -0,0 +1,108 @@ +# 记账插件使用示例 + +## 📝 在日记中记账 + +在你的 `journals/2024-01-10.md` 文件中: + +```markdown +# 2024年1月10日 + +## 今日记录 + +早上买早餐 +#cy 15.5 豆浆油条 + +中午和同事吃饭 +#cy 45 麻辣烫 + +打车回家 +#jt 28 滴滴打车 + +收到工资 +#sr 8500 1月份工资 + +买技术书 +#jy 89 《深入理解计算机系统》 + +## 其他事项 +今天天气不错... +``` + +## 🎯 记账格式说明 + +### 基本格式 +``` +#关键词 金额 备注说明 +``` + +**重要**:插件只会识别配置文件中定义的完整关键词,不会误识别包含这些字母的其他单词。 + +### 正确示例 ✅ +```markdown +#cy 15.5 豆浆油条 ← 正确:完整的关键词 +#jt 28 打车费用 ← 正确:完整的关键词 +#sr 8000 工资收入 ← 正确:完整的关键词 +``` + +### 不会被识别的情况 ❌ +```markdown +- TODO 写一个 getCarlistItem 函数 ← 不会识别:cy只是单词的一部分 +- 讨论 cryptocurrency 投资 ← 不会识别:cy只是单词的一部分 +- 分析 cybersecurity 问题 ← 不会识别:cy只是单词的一部分 +``` + +### 支持的关键词 +- `#cy 25.8 午餐` → 餐饮 25.8元 +- `#jt 6 地铁` → 交通 6元 +- `#yl 58 电影` → 娱乐 58元 +- `#gw 199 衣服` → 购物 199元 +- `#yy 50 感冒药` → 医疗 50元 +- `#jy 89 书籍` → 教育 89元 +- `#fz 2000 房租` → 房租 2000元 +- `#qt 20 其他` → 其他 20元 +- `#sr 8000 工资` → 收入 8000元 ⭐ + +## 📊 查看统计 + +1. 点击左侧工具栏的 📊 计算器图标 +2. 或使用命令面板 (Cmd/Ctrl + P) 搜索"记账管理" +3. 在界面中可以看到: + - 总收入、总支出、结余 + - 分类统计 + - 按日期分组的详细记录 + - 时间范围筛选 + +## 🔧 自定义配置 + +编辑 `config.json` 添加新分类: + +```json +{ + "categories": { + "cy": "餐饮", + "jt": "交通", + "yl": "娱乐", + "gw": "购物", + "yy": "医疗", + "jy": "教育", + "fz": "房租", + "qt": "其他", + "sr": "收入", + "tz": "投资", // 新增 + "lw": "礼物" // 新增 + }, + "expenseEmoji": "#", + "journalsPath": "journals" +} +``` + +## 💡 使用技巧 + +1. **养成习惯**:每天记录消费,数据更准确 +2. **备注详细**:方便后续查看和分析 +3. **定期查看**:使用时间筛选功能分析消费趋势 +4. **分类清晰**:根据个人需求调整分类关键词 + +## 🎉 开始记账吧! + +现在你可以在日记中轻松记账,插件会自动识别并统计你的收支情况! \ No newline at end of file diff --git a/安装说明.md b/安装说明.md new file mode 100644 index 0000000..4f3a990 --- /dev/null +++ b/安装说明.md @@ -0,0 +1,101 @@ +# 记账插件安装说明 + +## 快速安装 + +1. **复制插件文件** + - 将整个 `obsidian-accounting` 文件夹复制到你的 Obsidian 库的 `.obsidian/plugins/` 目录下 + +2. **启用插件** + - 打开 Obsidian 设置 → 第三方插件 + - 找到"记账管理"插件并启用 + +3. **创建日记目录** + - 在库根目录创建 `journals` 文件夹(如果还没有的话) + +## 开始使用 + +### 第一步:在日记中记账 +在 `journals/2024-01-10.md` 文件中写入: + +```markdown +# 今天的记录 + +早餐 +#cy 15.5 豆浆油条 + +打车上班 +#jt 28 滴滴打车 + +收到工资 +#sr 5000 月薪 +``` + +### 第二步:查看统计 +- 点击左侧工具栏的计算器图标 📊 +- 或使用命令面板搜索"记账管理" + +## 记账格式说明 + +``` +#关键词 金额 备注 +``` + +### 默认关键词: +- `cy` = 餐饮 +- `jt` = 交通 +- `yl` = 娱乐 +- `gw` = 购物 +- `jy` = 教育 +- `fz` = 房租 +- `qt` = 其他 +- `sr` = 收入 ⭐ + +### 示例: +```markdown +#cy 25.8 午餐麻辣烫 +#jt 6 地铁费 +#sr 8000 工资收入 +#gw 199 买衣服 +``` + +## 自定义分类 + +编辑 `obsidian-accounting/config.json` 文件: + +```json +{ + "categories": { + "cy": "餐饮", + "jt": "交通", + "yl": "娱乐", + "gw": "购物", + "yy": "医疗", + "jy": "教育", + "fz": "房租", + "qt": "其他", + "sr": "收入", + "tz": "投资", + "lw": "礼物" + }, + "expenseEmoji": "#", + "journalsPath": "journals" +} +``` + +## 常见问题 + +**Q: 为什么看不到记账记录?** +A: 检查日记文件是否在 `journals` 目录下,格式是否正确 + +**Q: 如何修改分类?** +A: 编辑 `config.json` 文件,添加或修改 categories 中的键值对 + +**Q: 支持其他货币吗?** +A: 支持,只需要在金额中输入数字即可,不要包含货币符号 + +**Q: 可以批量导入历史数据吗?** +A: 可以,按格式在历史日记文件中添加记账记录即可 + +--- + +🎉 **安装完成!开始享受智能记账体验吧!** \ No newline at end of file diff --git a/性能优化说明.md b/性能优化说明.md new file mode 100644 index 0000000..e69de29 diff --git a/新功能说明.md b/新功能说明.md new file mode 100644 index 0000000..e69de29 diff --git a/测试说明.md b/测试说明.md new file mode 100644 index 0000000..ef8e821 --- /dev/null +++ b/测试说明.md @@ -0,0 +1,84 @@ +# 记账插件测试说明 + +## 🔍 关键词识别测试 + +### 问题描述 +之前的版本会错误识别包含关键词字母的其他单词,比如: +- `getCarlistItem` 中的 `cy` 被误识别为餐饮 +- `cryptocurrency` 中的 `cy` 被误识别为餐饮 +- `psychology` 中的 `gy` 可能被误识别 + +### 解决方案 +修改了正则表达式,现在只匹配配置文件中定义的完整关键词。 + +### 测试用例 + +#### ✅ 应该被识别的记录 +```markdown +#cy 15.5 豆浆油条 +#jt 28 打车费用 +#yl 58 看电影 +#gw 199 买衣服 +#yy 50 感冒药 +#jy 89 技术书籍 +#fz 3000 房租 +#qt 20 杂费 +#sr 8000 工资 +``` + +#### ❌ 不应该被识别的内容 +```markdown +- TODO #idea 12:00 写一个 getCarlistItem 指定字段 +- 讨论 cryptocurrency 投资策略 +- 分析 cybersecurity 问题 +- 研究 psychology 理论 +- 学习 typography 设计 +- 了解 geography 知识 +- 这里有个 cy 单词但不是记账 +- 开发 getCarlistItem 函数的 cy 参数 +``` + +#### 🧪 混合测试 +```markdown +# 2024年1月11日 + +## 工作记录 +- TODO 写一个 getCarlistItem 函数 +- 修复 cryptocurrency 相关的 bug +- 研究 cybersecurity 最佳实践 + +## 记账记录 +早餐 +#cy 12 包子豆浆 + +打车上班 +#jt 15 滴滴 + +看电影 +#yl 58 《流浪地球3》 + +## 其他内容 +今天学习了 psychology 相关知识 +``` + +在上面的例子中,只有 `#cy 12 包子豆浆`、`#jt 15 滴滴`、`#yl 58 《流浪地球3》` 应该被识别为记账记录。 + +### 验证方法 +1. 在 journals 目录创建测试文件 +2. 添加上述测试内容 +3. 打开记账管理界面 +4. 检查是否只识别了正确的记账记录 +5. 确认不会误识别包含关键词字母的其他单词 + +### 技术实现 +```javascript +// 修改前(会误识别) +const regex = new RegExp(`${expenseEmoji}\\s*(\\w+)\\s+([\\d.]+)\\s*(.*)`, 'g'); + +// 修改后(精确匹配) +const keywords = Object.keys(categories).sort((a, b) => b.length - a.length); +const keywordPattern = keywords.join('|'); +const regex = new RegExp(`${expenseEmoji}\\s*(${keywordPattern})\\s+([\\d.]+)\\s*(.*)`, 'g'); +``` + +这样确保只有配置文件中定义的完整关键词才会被识别为记账记录。 \ No newline at end of file diff --git a/缓存策略说明.md b/缓存策略说明.md new file mode 100644 index 0000000..ca62626 --- /dev/null +++ b/缓存策略说明.md @@ -0,0 +1,93 @@ +# 记账插件缓存策略说明 + +## 🚀 优化目标 + +- **即时响应**:新增记账记录后立即可见 +- **性能优化**:避免重复扫描大量文件 +- **智能更新**:只在文件真正变化时刷新 + +## 📋 缓存策略 + +### 1. 文件监听机制 +```javascript +// 监听日记文件的变化 +app.vault.on('modify', (file) => { + if (file.path.startsWith('journals/') && file.path.endsWith('.md')) { + // 立即清除缓存,下次访问时重新加载 + this.clearCache(); + } +}); +``` + +### 2. 短时间缓存 +- **缓存时间**:30秒(而不是5分钟) +- **触发条件**: + - 文件修改 → 立即清除缓存 + - 文件创建 → 立即清除缓存 + - 文件删除 → 立即清除缓存 + - 超过30秒 → 自动过期 + +### 3. 智能加载 +```javascript +// 使用场景分析 +if (缓存有效 && 没有文件变化) { + return 缓存数据; // 秒级响应 +} else { + 重新扫描文件; // 确保数据最新 + 更新缓存; +} +``` + +## 🎯 用户体验 + +### ✅ 新增记账记录 +1. 在日记中写入:`#cy 15.5 豆浆油条` +2. 保存文件 → 自动触发缓存清除 +3. 打开记账界面 → 立即显示新记录 + +### ✅ 频繁查看统计 +1. 第一次打开 → 扫描所有文件(可能较慢) +2. 30秒内再次打开 → 使用缓存(秒开) +3. 修改记账后打开 → 自动刷新数据 + +### ✅ 强制刷新 +- 点击"刷新数据"按钮 → 忽略缓存,强制重新加载 + +## 🔧 技术实现 + +### 文件扫描优化 +```javascript +// 分批处理,避免阻塞 +const batchSize = 10; +for (let i = 0; i < files.length; i += batchSize) { + const batch = files.slice(i, i + batchSize); + // 并行处理这一批文件 + const results = await Promise.all(batch.map(processFile)); +} +``` + +### 快速过滤 +```javascript +// 先检查文件是否包含记账标识符 +const content = await vault.read(file); +if (!content.includes('#')) { + return []; // 跳过不包含记账记录的文件 +} +``` + +## 📊 性能对比 + +| 场景 | 优化前 | 优化后 | +|------|--------|--------| +| 首次加载 | 扫描所有文件 | 扫描所有文件 | +| 重复查看 | 每次扫描 | 使用缓存(秒开) | +| 新增记录后 | 手动刷新 | 自动检测更新 | +| 大量文件 | 线性增长 | 分批+并行处理 | + +## 💡 使用建议 + +1. **正常使用**:无需手动刷新,系统会自动检测文件变化 +2. **数据异常**:点击"刷新数据"按钮强制更新 +3. **性能考虑**:插件会自动优化,无需担心文件数量 + +这样既保证了数据的实时性,又优化了性能表现! \ No newline at end of file diff --git a/自定义名称说明.md b/自定义名称说明.md new file mode 100644 index 0000000..639050a --- /dev/null +++ b/自定义名称说明.md @@ -0,0 +1,64 @@ +# 如何自定义应用名称 + +## 📝 操作步骤 + +1. **打开记账软件** + - 点击左侧边栏的 💰 图标 + - 或使用命令面板 (Cmd/Ctrl + P) 搜索"打开记账软件" + +2. **进入配置界面** + - 点击右上角的"配置分类"按钮 + +3. **切换到基础设置** + - 在配置窗口顶部,你会看到三个标签页: + - **基础设置** ← 点击这个 + - 分类管理 + - 预算设置 + +4. **修改应用名称** + - 在"显示名称"输入框中输入你想要的名称 + - 例如:我的账本、家庭记账、个人财务等 + - 下方会实时预览效果 + +5. **保存配置** + - 点击右下角的"保存"按钮 + - 插件会自动刷新 + +6. **查看效果** + - 侧边栏标签页名称会变成你设置的名称 + - 页面标题也会使用新名称 + - 配置窗口标题也会更新 + +## 🎯 自定义名称会应用到哪些地方? + +1. ✅ 侧边栏标签页名称:`💰 [你的名称]` +2. ✅ 页面顶部标题:`💰 [你的名称]` +3. ✅ 配置窗口标题:`[你的名称]配置` +4. ✅ 功能区图标提示:`[你的名称]` +5. ✅ 命令面板命令:`打开[你的名称]` + +## 💡 示例 + +如果你设置名称为"我的账本",那么: +- 标签页显示:💰 我的账本 +- 页面标题:💰 我的账本 +- 配置标题:我的账本配置 +- 命令:打开我的账本 + +## ⚠️ 注意事项 + +- 名称最长 20 个字符 +- 如果留空,会使用默认名称"记账软件" +- 修改后需要点击"保存"按钮才会生效 +- 保存后插件会自动刷新数据 + +## 🔧 如果看不到"基础设置"标签页 + +请确保: +1. 已经重新加载插件(命令面板 → "Reload app without saving") +2. 配置文件已更新(应该包含 `appName` 字段) +3. 使用的是最新版本的插件 + +如果还是看不到,可以手动编辑配置文件: +- 文件位置:`.obsidian/plugins/obsidian-accounting/config.json` +- 在文件开头添加:`"appName": "你的名称",`