From 156af15db9e02b368fb51e86a026f116e1ae63e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A1=B9=E9=98=B3?= <39241051+insile@users.noreply.github.com> Date: Sat, 21 Feb 2026 14:17:17 +0800 Subject: [PATCH] 2026/2/21 --- manifest.json | 4 +- package-lock.json | 4 +- package.json | 2 +- src/main.ts | 446 +++++++++++++++++++++---------------- src/settings/SettingTab.ts | 4 +- src/views/LearningPage.ts | 8 +- src/views/MainView.ts | 23 +- src/views/TypePage.ts | 92 +++++--- versions.json | 2 +- 9 files changed, 354 insertions(+), 231 deletions(-) diff --git a/manifest.json b/manifest.json index b6eb8b1..40faf1a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "id": "openwords", "name": "OpenWords", - "version": "1.0.7", - "minAppVersion": "1.8.10", + "version": "1.0.8", + "minAppVersion": "1.10.0", "description": "用于英语学习中背单词与单词管理的插件", "author": "insile", "authorUrl": "https://github.com/insile", diff --git a/package-lock.json b/package-lock.json index a1f4d84..883f41c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openwords", - "version": "1.0.7", + "version": "1.0.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openwords", - "version": "1.0.7", + "version": "1.0.8", "license": "MIT", "dependencies": { "obsidian": "latest", diff --git a/package.json b/package.json index 73451dc..0ffb3ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openwords", - "version": "1.0.7", + "version": "1.0.8", "description": "用于英语学习中背单词与单词管理的插件", "main": "main.js", "type": "module", diff --git a/src/main.ts b/src/main.ts index 3b0d279..46f6b55 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import {EventRef, MarkdownView, Notice, Plugin, TFile, TFolder, Vault} from 'obsidian'; +import { EventRef, MarkdownView, Notice, Plugin, TFile, TFolder, Vault } from 'obsidian'; import { OpenWordsSettings, DEFAULT_SETTINGS } from "./settings/SettingData"; import { OpenWordsSettingTab } from './settings/SettingTab'; import { MainView, MAIN_VIEW, PageType } from './views/MainView'; @@ -125,7 +125,6 @@ export default class OpenWords extends Plugin { if (single) { this.updateStatusBar(); } return; } - const isMasteredOld = allCards.get(card.front)?.isMastered; // 更新所有单词 allCards.set(card.front, card); @@ -145,13 +144,6 @@ export default class OpenWords extends Plugin { } } - // 如果掌握状态发生变化,更新复选框 - if (isMastered !== isMasteredOld && isMasteredOld !== undefined) { - const newCheckbox = isMastered ? 'x' : ' '; - const oldCheckbox = isMasteredOld ? 'x' : ' '; - await this.syncMetadataToCheckbox(file, newCheckbox, oldCheckbox); - } - // 如果是单个文件更新状态栏 if (single) { this.updateStatusBar(); } } @@ -178,8 +170,6 @@ export default class OpenWords extends Plugin { await Promise.all(filteredFiles.map(async (file) => { await this.loadWordMetadata(file, false); })); - - // new Notice(`扫描完成!共 ${this.allCards.size} 个单词`); // 完成后更新消息 } // 更新单词属性 @@ -188,7 +178,7 @@ export default class OpenWords extends Plugin { (mode === 'new' && !this.newCards.has(card.front)) || (mode === 'old' && !this.dueCards.has(card.front)) ) { - new Notice(`${card.front} 不属于本模式范围 \n评分无效并跳过`); + new Notice(`${card.front} \n不属于本模式范围 \n评分无效并跳过`); return; } @@ -212,12 +202,12 @@ export default class OpenWords extends Plugin { // 等待元数据缓存更新(最多等待 1 秒) let attempts = 0; - while (attempts < 10) { + while (attempts < 5) { const updated = this.app.metadataCache.getFileCache(file); - if (updated?.frontmatter?.["易记因子"] === Math.round(result.efactor * 100)) { + if (updated?.frontmatter?.["到期日"] === newDate) { break; // 缓存已更新 } - await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise(resolve => setTimeout(resolve, 200)); attempts++; } } @@ -229,8 +219,8 @@ export default class OpenWords extends Plugin { return; } - // 注销监听器,避免重置过程中触发大量缓存更新事件 - this.unregisterFileWatchers(); + // // 注销监听器,避免重置过程中触发大量缓存更新事件 + // this.unregisterFileWatchers(); const notice = new Notice('重置中...', 0); // 创建一个持续显示的 Notice let count = 0; // 计数器 @@ -270,11 +260,11 @@ export default class OpenWords extends Plugin { new Notice(`重置出错: ${error}`); console.error('Reset card error:', error); } finally { - // 重新注册监听器 - this.registerFileWatchers(); - // 重新扫描所有单词文件,确保数据同步 - await this.scanAllNotes(); - this.updateStatusBar(); + // // 重新注册监听器 + // this.registerFileWatchers(); + // // 重新扫描所有单词文件,确保数据同步 + // await this.scanAllNotes(); + // this.updateStatusBar(); setTimeout(() => notice.hide(), 2000); // 2 秒后自动隐藏 Notice } } @@ -324,183 +314,163 @@ export default class OpenWords extends Plugin { } } - // 生成单词索引 + // 生成单词索引 .base 文件 async generateIndex() { - const notice = new Notice('索引中...', 0); // 创建一个持续显示的 Notice - const levels = [ - "级别/小学", "级别/中考", "级别/高考四级", "级别/考研", - "级别/六级", "级别/雅思", "级别/托福", "级别/GRE" - ]; - const wordsDir = this.settings.folderPath; // 单词文件夹路径 - const indexDir = this.settings.indexPath; // 索引文件夹路径 + const notice = new Notice('索引中...', 0); + + // 从设置读取标签并转换为配置对象(将多级标签的 '/' 替换为 '.') + const levelConfigs = this.settings.enabledTags.map(tag => { + const name = tag.replace(/\//g, '.'); + return { tag, name }; + }); - // 创建索引文件夹 (如果不存在) + const wordsDir = this.settings.folderPath; + const indexDir = this.settings.indexPath; + + // 创建索引文件夹 const existingIndexFolder = this.app.vault.getFolderByPath(indexDir); if (!existingIndexFolder) { await this.app.vault.createFolder(indexDir); } - // 遍历所有单词,记录每个单词的级别 - const wordRecords: Record = {}; - for (const level of levels) { - wordRecords[level] = {}; + // 统计每个级别的单词数 + const levelWordCounts: Record = {}; + for (const level of levelConfigs) { + levelWordCounts[level.tag] = 0; } - const folder = this.app.vault.getAbstractFileByPath(wordsDir); - if (!(folder instanceof TFolder)) return; // 确认目录存在 + const folder = this.app.vault.getAbstractFileByPath(wordsDir); + if (!(folder instanceof TFolder) || wordsDir === "/") { + notice.setMessage('指定的单词文件夹不存在!'); + setTimeout(() => notice.hide(), 2000); + return + }; - Vault.recurseChildren(folder, (file) => { - if (!(file instanceof TFile) || file.extension !== "md") return; + Vault.recurseChildren(folder, (file) => { + if (!(file instanceof TFile) || file.extension !== "md") return; - const frontMatter = this.app.metadataCache.getFileCache(file)?.frontmatter; - if (!frontMatter) return; // 如果没有 FrontMatter,跳过 - const tags: string[] = frontMatter.tags || []; - const isMastered = frontMatter["掌握"] === true; - for (const level of levels) { - if (tags.includes(level)) { - const letter = file.basename[0].toUpperCase(); // 获取单词首字母 - if (!wordRecords[level][letter]) { - wordRecords[level][letter] = []; - } - if (isMastered) - wordRecords[level][letter].push(`- [x] [[${file.basename}]]`); - else { - wordRecords[level][letter].push(`- [ ] [[${file.basename}]]`); - } + const frontMatter = this.app.metadataCache.getFileCache(file)?.frontmatter; + if (!frontMatter) return; + + const tags: string[] = frontMatter.tags || []; + for (const level of levelConfigs) { + if (tags.includes(level.tag)) { + levelWordCounts[level.tag]++; } } - }) + }); - const totalWordsList: Record = {}; // 记录每个级别的总单词数 - // 遍历级别,生成索引 - for (const level of levels) { - const levelName = level.split('/').pop(); // 获取级别名称 - const levelFolderPath = `${indexDir}/${levelName}`; - const existingLevelFolder = this.app.vault.getFolderByPath(levelFolderPath); - notice.setMessage(`索引中... ${levelName}`); // 更新 Notice 的消息 + // 总单词数 + const totalCount = this.allCards.size; - // 如果级别文件夹不存在,则创建 - if (!existingLevelFolder) { - await this.app.vault.createFolder(levelFolderPath); - } + // 生成主索引文件 + const mainIndexPath = `${indexDir}/英语单词索引.md`; + notice.setMessage('索引中... 生成主索引'); - let totalWords = 0; // 统计总单词数 - const letterStats: { letter: string; count: number }[] = []; // 记录每个字母的单词数量 - - for (let i = 65; i <= 90; i++) { - const letter = String.fromCharCode(i); - const words = wordRecords[level][letter] || []; // 如果没有单词,返回空数组 - words.sort((a, b) => { - const extractName = (line: string) => { - const match = line.match(/\[\[(.+?)\]\]/); - return match ? match[1] : line; - }; - return extractName(a).localeCompare(extractName(b)); - }); // 按字母顺序排序 - totalWords += words.length; // 累加总单词数 - letterStats.push({ letter, count: words.length }); // 添加统计信息 - - // 创建或覆盖字母索引文件 - const letterFilePath = `${levelFolderPath}/${levelName}.${letter}.md`; - const content = words.join('\n'); - await this.writeFile(letterFilePath, `##### ${levelName}.${letter} ${words.length}\n${content}`); // 写入索引文件 - } - totalWordsList[level] = totalWords; // 记录每个级别的总单词数 - - // 生成总索引文件 - const totalIndexFilePath = `${levelFolderPath}/单词索引.${levelName}.md`; - const totalIndexContent = [ - `##### 单词索引.${levelName} ${totalWords}`, - `| 首字母 | 数量 |`, - `| :-------: | --: |`, - ...letterStats.map(stat => `| [${stat.letter}](${levelName}.${stat.letter}) | ${stat.count} |`) - ].join('\n'); - - await this.writeFile(totalIndexFilePath, totalIndexContent); // 写入索引文件 - } - - // 生成全索引 - const fullIndexFolderPath = `${indexDir}/全索引`; - notice.setMessage(`索引中... 全索引`); // 更新 Notice 的消息 - - const existingFullIndexFolder = this.app.vault.getFolderByPath(fullIndexFolderPath); - if (!existingFullIndexFolder) { - await this.app.vault.createFolder(fullIndexFolderPath); - } - - // 先收集所有字母到一个新的对象 - const allWordsByLetter: Record = {}; // { A: [...], B: [...], ... } - - for (const levelRecords of Object.values(wordRecords)) { - for (const [letter, words] of Object.entries(levelRecords)) { - if (!allWordsByLetter[letter]) { - allWordsByLetter[letter] = []; - } - allWordsByLetter[letter].push(...words); - } - } - - let totalWords = 0; - const letterStats: { letter: string; count: number }[] = []; + // 构建表格 + const titleRow = '| [[英语单词索引.总计.base#总计\\|总计]] | ' + levelConfigs.map(l => `[[英语单词索引.${l.name}.base#${l.name.split('.').pop()}\\|${l.name.split('.').pop()}]]`).join(' | ') + ' |'; + const separator = '| :---: | ' + levelConfigs.map(() => ':---:').join(' | ') + ' |'; + const countRow = '| ' + totalCount + ' | ' + levelConfigs.map(l => levelWordCounts[l.tag] || 0).join(' | ') + ' |'; + // 字母行 + const letterRows: string[] = []; for (let i = 65; i <= 90; i++) { const letter = String.fromCharCode(i); - let words = allWordsByLetter[letter] || []; // 如果没有单词,返回空数组 - words = Array.from(new Set(words)); // 去重 - totalWords += words.length; // 累加总单词数 - letterStats.push({ letter, count: words.length }); - words.sort((a, b) => { - const extractName = (line: string) => { - const match = line.match(/\[\[(.+?)\]\]/); - return match ? match[1] : line; - }; - return extractName(a).localeCompare(extractName(b)); - }); - - // 创建或覆盖字母索引文件 - const letterFilePath = `${fullIndexFolderPath}/全索引.${letter}.md`; - const content = words.join('\n'); - await this.writeFile(letterFilePath, `##### 全索引.${letter} ${words.length}\n${content}`); // 写入索引文件 + const cells = [`[[英语单词索引.总计.base#总计.${letter}\\|${letter}]]`]; + for (const level of levelConfigs) { + cells.push(`[[英语单词索引.${level.name}.base#${level.name.split('.').pop()}.${letter}\\|${letter}]]`); + } + letterRows.push('| ' + cells.join(' | ') + ' |'); } - // 生成总索引文件 - const totalIndexFilePath = `${fullIndexFolderPath}/单词索引.全索引.md`; - const totalIndexContent = [ - `##### 单词索引.全索引 ${totalWords}`, - `| 首字母 | 数量 |`, - `| :-------: | --: |`, - ...letterStats.map(stat => `| [${stat.letter}](全索引.${stat.letter}) | ${stat.count} |`) + // 合并表格 + const mainContent = [ + titleRow, + separator, + countRow, + ...letterRows ].join('\n'); - await this.writeFile(totalIndexFilePath, totalIndexContent); // 写入索引文件 + await this.writeFile(mainIndexPath, '##### 英语单词索引\n\n' + mainContent); + // 生成总计 .base 配置文件(总计列) + notice.setMessage('索引中... 生成总计索引'); + const allBasePath = `${indexDir}/英语单词索引.总计.base`; + const allBaseContent = ` +filters: + and: + - file.folder.startsWith("${wordsDir}") +views: + - type: table + name: 总计 + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日 +${Array.from({ length: 26 }, (_, i) => { + const letter = String.fromCharCode(65 + i); + return ` - type: table + name: 总计.${letter} + filters: + and: + - file.basename.startsWith("${letter.toLowerCase()}") + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日`; +}).join('\n')} +`; + await this.writeFile(allBasePath, allBaseContent); - const finalIndexPath = `${indexDir}/英语单词.md`; - // 定义表头 - const header = [ - `##### 英语单词`, - ``, - `| | [n.](单词索引.名词) | [v.](单词索引.动词) | [adj.](单词索引.形容词) | [adv.](单词索引.副词) | [prep.](单词索引.介词) | [pron.](单词索引.代词) | [det.](单词索引.限定词) | [conj.](单词索引.连词) |`, - `| :--------------: | :------------: | :------------: | :---------------: | :--------------: | :---------------: | :---------------: | :---------------: | :---------------: |`, - `| [全索引](单词索引.全索引) | [小学](单词索引.小学) | [中考](单词索引.中考) | [高四](单词索引.高考四级) | [考研](单词索引.考研) | [六级](单词索引.六级) | [雅思](单词索引.雅思) | [托福](单词索引.托福) | [GRE](单词索引.GRE) |`, - `| ${this.allCards.size} | ${totalWordsList["级别/小学"]} | ${totalWordsList["级别/中考"]} | ${totalWordsList["级别/高考四级"]} | ${totalWordsList["级别/考研"]} | ${totalWordsList["级别/六级"]} | ${totalWordsList["级别/雅思"]} | ${totalWordsList["级别/托福"]} | ${totalWordsList["级别/GRE"]} |` - ]; + // 生成每个级别的 .base 配置文件 + for (const level of levelConfigs) { + notice.setMessage(`索引中... ${level.name}`); + const basePath = `${indexDir}/英语单词索引.${level.name}.base`; - // 定义字母索引行 - const alphabetRows = []; - for (let i = 65; i <= 90; i++) { - const letter = String.fromCharCode(i); - alphabetRows.push( - `| [${letter}](全索引.${letter}) | [${letter}](小学.${letter}) | [${letter}](中考.${letter}) | [${letter}](高考四级.${letter}) | [${letter}](考研.${letter}) | [${letter}](六级.${letter}) | [${letter}](雅思.${letter}) | [${letter}](托福.${letter}) | [${letter}](GRE.${letter}) |` - ); + // 构建 DataviewJS 配置 + const baseContent = ` +filters: + and: + - file.folder.startsWith("${wordsDir}") + - file.tags.contains("${level.tag}") +views: + - type: table + name: ${level.name.split('.').pop()} + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日 +${Array.from({ length: 26 }, (_, i) => { + const letter = String.fromCharCode(65 + i); + return ` - type: table + name: ${level.name.split('.').pop()}.${letter} + filters: + and: + - file.basename.startsWith("${letter.toLowerCase()}") + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日`; +}).join('\n')} +`; + + await this.writeFile(basePath, baseContent); } - // 合并表头和字母索引行 - const finalContent = [...header, ...alphabetRows].join('\n')+'\n'; - await this.writeFile(finalIndexPath, finalContent); // 写入索引文件 - - notice.setMessage("索引生成完成!"); - setTimeout(() => notice.hide(), 2000); // 2 秒后自动隐藏 Notice + notice.setMessage('索引生成完成!'); + setTimeout(() => notice.hide(), 2000); } // 覆盖写入文件 @@ -513,25 +483,123 @@ export default class OpenWords extends Plugin { } } - // 监听单词属性变化并同步到复选框状态 - async syncMetadataToCheckbox(file: TFile, newCheckbox: string, oldCheckbox: string) { - const resolvedLinks = this.app.metadataCache.resolvedLinks; - const backlinks: Record = {}; - for (const [sourcePath, links] of Object.entries(resolvedLinks)) { - if (links[file.path]) { - backlinks[sourcePath] = links[file.path]; - } - } - for (const [sourcePath, ] of Object.entries(backlinks)) { - if (sourcePath.startsWith(this.settings.indexPath)) { - const sourceFile = this.app.vault.getFileByPath(sourcePath) - if (sourceFile instanceof TFile) { - const newWord = `- [${newCheckbox}] [[${file.basename}]]`; - const oldWord = `- [${oldCheckbox}] [[${file.basename}]]`; - await this.app.vault.process(sourceFile, (data) => data.replace(oldWord, newWord)); - } - } + // 创建英语单词状态 .base 文件 + async createWordStatusBaseFile() { + const wordsDir = this.settings.folderPath; + const indexDir = this.settings.indexPath; + const enabledTagsList = this.settings.enabledTags; + + // 创建索引文件夹 + const existingIndexFolder = this.app.vault.getFolderByPath(indexDir); + if (!existingIndexFolder) { + await this.app.vault.createFolder(indexDir); } + + // 构建 enabledTags 的 or 条件 + const tagFilters = enabledTagsList.map(tag => ` - file.tags.contains("${tag}")`).join('\n'); + + // 构建 .base 文件内容 + const baseContent = `filters: + and: + - file.folder.startsWith("${wordsDir}") +views: + - type: table + name: 总计 + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日 + - type: table + name: 待学习 + filters: + and: + - or: +${tagFilters} + - and: + - 掌握 == false + - and: + - 重复次数 == 0 + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日 + - type: table + name: 待复习 + filters: + and: + - or: +${tagFilters} + - and: + - 掌握 == false + - and: + - 重复次数 != 0 + - and: + - 到期日 > today() + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日 + - type: table + name: 今日到期 + filters: + and: + - or: +${tagFilters} + - and: + - 掌握 == false + - and: + - 重复次数 != 0 + - and: + - 到期日 <= today() + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日 + - type: table + name: 已掌握 + filters: + and: + - and: + - 掌握 == true + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日 + - type: table + name: 未启用 + filters: + or: + - not: +${tagFilters} + - and: + - 掌握 == true + order: + - 掌握 + - file.name + - 易记因子 + - 重复次数 + - 间隔 + - 到期日 +`; + + // 写入或更新 .base 文件 + const basePath = `${this.settings.indexPath}/英语单词状态.base`; + await this.writeFile(basePath, baseContent); } // 更新状态栏 diff --git a/src/settings/SettingTab.ts b/src/settings/SettingTab.ts index ea5b512..dda02a7 100644 --- a/src/settings/SettingTab.ts +++ b/src/settings/SettingTab.ts @@ -169,6 +169,7 @@ export class OpenWordsSettingTab extends PluginSettingTab { await this.plugin.reinitializeFileWatchers(); await this.plugin.scanAllNotes(); new Notice(`扫描完成!共 ${this.plugin.allCards.size} 个单词`); + await this.plugin.createWordStatusBaseFile(); this.plugin.updateStatusBar(); } catch (error) { console.error('缓存过程中发生错误:', error); @@ -181,7 +182,7 @@ export class OpenWordsSettingTab extends PluginSettingTab { new Setting(containerEl) .setName('生成索引') - .setDesc('按级别生成所有单词的首字母索引文件') + .setDesc('按标签生成所有单词的首字母索引文件') .addButton(button => button .setButtonText('索引') .onClick(async () => { @@ -252,6 +253,7 @@ export class OpenWordsSettingTab extends PluginSettingTab { await this.plugin.reinitializeFileWatchers(); await this.plugin.scanAllNotes(); new Notice(`扫描完成!共 ${this.plugin.allCards.size} 个单词`); + await this.plugin.createWordStatusBaseFile(); this.plugin.updateStatusBar(); } catch (error) { console.error('恢复默认设置失败:', error); diff --git a/src/views/LearningPage.ts b/src/views/LearningPage.ts index ab6f934..e9f6107 100644 --- a/src/views/LearningPage.ts +++ b/src/views/LearningPage.ts @@ -193,8 +193,12 @@ export function registerRenderKeyEvents(this: MainView, learningContainer: HTMLD this.renderCard(cardContainer); isShowingMarkdown = false; }; - cardContainer.addEventListener('mouseenter', showMarkdown); - cardContainer.addEventListener('mouseleave', showWord); + + // 延迟1秒后注册鼠标事件 + setTimeout(() => { + cardContainer.addEventListener('mouseenter', showMarkdown); + cardContainer.addEventListener('mouseleave', showWord); + }, 1000); const handleKeyDown = async (event: KeyboardEvent) => { if (event.key === 'Tab') { diff --git a/src/views/MainView.ts b/src/views/MainView.ts index b73a75c..1cf408c 100644 --- a/src/views/MainView.ts +++ b/src/views/MainView.ts @@ -1,4 +1,4 @@ -import { ItemView, WorkspaceLeaf, Component } from 'obsidian'; +import { ItemView, WorkspaceLeaf, Component, Notice } from 'obsidian'; import { TypePage } from './TypePage'; import { SpellingPage, pickNextSCard, renderInput, checkSpelling } from './SpellingPage'; import { LearningPage, pickNextLCard, renderCard, renderSettings, rateCard, registerRatingKeyEvents, registerRenderKeyEvents } from './LearningPage'; @@ -97,10 +97,10 @@ export class MainView extends ItemView { // 清空状态栏 this.statusBarEl.empty(); - // 创建第一行:新单词、待复习、今日到期、已掌握、未启用 + // 创建第一行:待学习、待复习、今日到期、已掌握、未启用 const row1 = this.statusBarEl.createDiv({ cls: 'openwords-statusbar-row' }); - this.createStatItem(row1, '新单词', newCount); + this.createStatItem(row1, '待学习', newCount); this.createStatItem(row1, '待复习', reviewCount); this.createStatItem(row1, '今日到期', dueTodayCount); this.createStatItem(row1, '已掌握', masteredCount); @@ -115,5 +115,22 @@ export class MainView extends ItemView { const item = container.createDiv({ cls: isTotal ? 'openwords-stat-item total' : 'openwords-stat-item' }); item.createSpan({ cls: 'openwords-stat-label', text: label }); item.createSpan({ cls: 'openwords-stat-count', text: count.toString() }); + + // 添加点击事件,打开对应的视图 + if (isTotal) { + item.addEventListener('click', async () => { + // 创建单词状态 .base 文件 + await this.plugin.createWordStatusBaseFile(); + const basePath = `${this.plugin.settings.indexPath}/英语单词状态.base`; + const file = this.app.vault.getFileByPath(basePath); + if (!file) { + new Notice('单词状态文件不存在,请先生成索引'); + return; + } + // 打开 .base 文件 + const leaf = this.app.workspace.getLeaf(false); + await leaf.openFile(file); + }); + }; } } diff --git a/src/views/TypePage.ts b/src/views/TypePage.ts index 880d4e1..0365f4a 100644 --- a/src/views/TypePage.ts +++ b/src/views/TypePage.ts @@ -8,42 +8,74 @@ export function TypePage(this: MainView) { typeContainer.createDiv({ cls: 'openwords-type-title', text: 'OpenWords' }); const buttonContainer = typeContainer.createDiv({ cls: 'openwords-type-button-grid' }); + const buttons: HTMLElement[] = []; + new Setting(buttonContainer) .setName(`学习新词`) - .addButton(btn => btn - .setButtonText("开始") - .onClick(() => { - if (this.plugin.newCards.size > 0) { - this.page = 'new'; - void this.render(); - } else { - new Notice("没有新词可学了!"); - } - })); + .addButton(btn => { + const button = btn + .setButtonText("开始") + .onClick(() => { + if (this.plugin.newCards.size > 0) { + this.page = 'new'; + void this.render(); + } else { + new Notice("没有新词可学了!"); + } + }); + buttons.push(button.buttonEl); + return button; + }); new Setting(buttonContainer) .setName(`复习旧词`) - .addButton(btn => btn - .setButtonText("开始") - .onClick(() => { - if (this.plugin.dueCards.size > 0) { - this.page = 'old'; - void this.render(); - } else { - new Notice("没有需要复习的单词!"); - } - })); + .addButton(btn => { + const button = btn + .setButtonText("开始") + .onClick(() => { + if (this.plugin.dueCards.size > 0) { + this.page = 'old'; + void this.render(); + } else { + new Notice("没有需要复习的单词!"); + } + }); + buttons.push(button.buttonEl); + return button; + }); new Setting(buttonContainer) .setName('默写单词') - .addButton(btn => btn - .setButtonText('开始') - .onClick(() => { - if (this.plugin.dueCards.size > 0) { - this.page = 'spelling'; - void this.render(); - } else { - new Notice("没有需要默写的单词!"); - } - })); + .addButton(btn => { + const button = btn + .setButtonText('开始') + .onClick(() => { + if (this.plugin.dueCards.size > 0) { + this.page = 'spelling'; + void this.render(); + } else { + new Notice("没有需要默写的单词!"); + } + }); + buttons.push(button.buttonEl); + return button; + }); + + // 快捷键监听 + typeContainer.setAttribute('tabindex', '0'); + typeContainer.focus(); + + typeContainer.addEventListener('keydown', (e: KeyboardEvent) => { + if (e.key === 'q' && buttons[0]) { + e.preventDefault(); + buttons[0].click(); + } else if (e.key === 'w' && buttons[1]) { + e.preventDefault(); + buttons[1].click(); + } else if (e.key === 'e' && buttons[2]) { + e.preventDefault(); + buttons[2].click(); + } + }); + } diff --git a/versions.json b/versions.json index a6f9ff4..1f99d09 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.0.7": "1.8.10" + "1.0.8": "1.10.0" }