diff --git a/README.md b/README.md index 0316a9d..0caa009 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OpenWords -**OpenWords** 是一个用于背单词和单词管理的 Obsidian 插件, 与 [OpenText](https://github.com/insile/OpenText) 单词库配套设计, 已上线[社区插件市场](obsidian://show-plugin?id=openwords), 安装后插件入口是左侧工具栏上的一个按钮再加上两个绑定命令, 主要功能有生成索引, 背单词, 默写单词, 添加双链等, 其中单词文件使用 OpenText 中的单词库, 而背单词的核心逻辑是 SuperMemo 2 间隔重复算法. 本插件完全依赖单词文件及其属性, 没有外部存储文件没有记录回答历史 (考虑中), 所以不能做一些记忆曲线拟合, 单词调度看起来很简单朴素, 仅依靠上次回答记录. 插件启动时会读取所有单词缓存, 更改时会即时写入相应的单词文件并更新缓存. 插件没有设计过多的搜索功能, 提倡使用 Obsidian 内置的搜索功能. 如果您认为本插件有帮助, 欢迎微信赞赏支持. 目前等待 Obsidian Bases 正式上线后进行功能更新 +**OpenWords** 是一个用于背单词和单词管理的 Obsidian 插件, 与 [OpenText](https://github.com/insile/OpenText) 单词库配套设计, 已上线[社区插件市场](obsidian://show-plugin?id=openwords), 安装后插件入口是左侧工具栏上的一个按钮再加上两个绑定命令, 主要功能有生成索引, 背单词, 默写单词, 添加双链等, 其中单词文件使用 OpenText 中的单词库, 而背单词的核心逻辑是 SuperMemo 2 间隔重复算法. 本插件完全依赖单词文件及其属性, 没有外部存储记录回答历史, 所以不能做一些记忆曲线拟合, 单词调度看起来很简单朴素, 仅依靠上次回答记录. 插件启动时会读取所有单词缓存, 更改时会即时写入相应的单词文件并更新缓存. 插件没有设计过多的搜索功能, 提倡使用 Obsidian 内置的搜索功能. 如果您认为本插件有帮助, 欢迎微信赞赏支持. 目前等待 Obsidian Bases 正式上线后进行功能更新 ![p7](README/p7.png) @@ -41,7 +41,7 @@ ### 掌握列表 -掌握列表能够即时快速修改单词的 `掌握` 属性, 首先将自己无比熟悉的单词设置为掌握, 例如 hello, that, my 等, 可以防止它们出现在后续功能中 +掌握列表能够即时快速修改单词的 `掌握` 属性, 可以首先将自己无比熟悉的单词设置为掌握, 例如 hello, that, my 等, 可以防止它们出现在后续功能中, 到后期再将新学会的单词设置为掌握 ![p2](README/p2.gif) @@ -53,19 +53,33 @@ ### 背单词 -背单词目标是尽可能的提高每个单词的重复次数和易记因子, 详细计算过程可查阅 SM-2 文档. 新词指重复次数为 0 的单词, 它可能是从未被学习过的, 也可能是前面学后面忘了的. 新词调度中 70% 纯随机, 30% 从易记因子和重复次数最低的 20% 中随机; 旧词指重复次数大于 0 的单词, 它是之前回答正确的. 旧词调度从已过期的单词中 70% 纯随机, 30% 从易记因子和重复次数最低的 20% 中随机. 回答的 0-5 分已与数字键绑定方便键盘控制 +背单词目标是尽可能的提高每个单词的重复次数和易记因子, 详细计算过程可查阅 SM-2 文档, 随机调度概率设置每次抽卡时随机调度的概率, 其余为优先调度概率. 默认 70 % 随机调度, 30% 优先调度. `0-5` 数字键评分, `Tab` 键显示答案 +- 新词指重复次数为 0 的单词, 它可能是从未被学习过的, 也可能是前面学后面忘了的. 新词间隔只能是 0 或 1, 其中 0 是完全没有学习过的, 1 是至少学过一次的. 新词优先调度遵从间隔降序和易记因子升序, 从前 1% 中随机 +- 旧词指重复次数大于 0 的单词, 它是之前回答正确的. 旧词优先调度遵从过期的易记因子和重复次数升序, 从前 1% 中随机 + +回答评分, 答错降低易记因子, 重复次数和间隔重置, 答对易记因子增长缓慢, 间隔逐渐延长 +- 评分 0, 易记因子 -0.8 +- 评分 1, 易记因子 -0.54 +- 评分 2, 易记因子 -0.32 +- 评分 3, 易记因子 -0.14 +- 评分 4, 易记因子 -0 +- 评分 5, 易记因子 +0.1 ![p4](README/p4.gif) ### 默写单词 -默写单词是从作用域中随机抽取单词进行拼写, 目前只是一个练习功能, 不会更改任何单词属性, 按下 `Tab` 键显示答案 +默写单词是从易记因子最低的 50% 旧词中随机抽取单词进行拼写, 会影响易记因子, 按下 `Tab` 键显示答案 +- 没有看答案, 直接拼写正确, 易记因子 + 0.15 +- 没有看答案, 经过几次错误后拼写正确, 易记因子 + 0.05 +- 看答案, 易记因子 - 0.02 +- 易记因子最低不低于 1.3 ![p8](README/p8.gif) ### 添加双链 -为当前活动页面笔记中的单词添加双链, 单词范围是作用域中易记因子小于等于 2.5 的, 所以默认情况所有单词都会添加. 词形还原准确率在 90 %. 需要文章尽可能规范, 减少不必要的标点符号. 目前只能通过 `ctrl z` 回撤, 请谨慎使用 +为当前活动页面笔记中的单词添加双链, 单词范围是作用域中易记因子小于等于设置的参数, 所以默认情况所有单词都会添加. 词形还原准确率在 90 %. 需要文章尽可能规范, 减少不必要的标点符号. 可以将参数设为 1.2 进行还原, 因为没有单词易记因子小于 1.3 ![p5](README/p5.gif) @@ -118,4 +132,4 @@ function supermemo(item, grade) { }; } -``` \ No newline at end of file +``` diff --git a/manifest.json b/manifest.json index 9d621fc..67f410a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "openwords", "name": "OpenWords", - "version": "1.0.5", + "version": "1.0.6", "minAppVersion": "1.8.10", "description": "用于英语学习中背单词与单词管理的插件", "author": "insile", diff --git a/package.json b/package.json index e91540c..f1ecee4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openwords", - "version": "1.0.5", + "version": "1.0.6", "description": "用于英语学习中背单词与单词管理的插件", "main": "main.js", "scripts": { diff --git a/src/main.ts b/src/main.ts index 5190398..cda2d50 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,8 @@ interface OpenWordsSettings { enableWords6: boolean; enableWords7: boolean; enableWords8: boolean; + randomRatio: number; + maxEfactorForLink: number; } // 默认设置 @@ -32,6 +34,8 @@ const DEFAULT_SETTINGS: OpenWordsSettings = { enableWords6: false, enableWords7: false, enableWords8: false, + randomRatio: 0.7, + maxEfactorForLink: 2.6, } @@ -257,13 +261,9 @@ export default class OpenWords extends Plugin { // 建立单词双链 async addDoubleBrackets() { const unmasteredWords = new Set(Array.from(this.enabledCards.values()) - .filter(card => card.efactor <= 2.5) + .filter(card => card.efactor <= this.settings.maxEfactorForLink) // 只选择易记因子小于等于设置值的单词 .map(card => card.front) ); - if (unmasteredWords.size === 0) { - new Notice("没有单词需要添加双链!"); - return; - } const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); if (activeView) { diff --git a/src/modals/LearningModal.ts b/src/modals/LearningModal.ts index 6fc8268..6ef711a 100644 --- a/src/modals/LearningModal.ts +++ b/src/modals/LearningModal.ts @@ -21,75 +21,22 @@ export class LearningModal extends Modal { } onOpen() { + // 加载组件并选卡 this.component.load() this.pickNextCard(); + if (!this.currentCard) return; + + // 创建卡片容器和设置容器 const { contentEl } = this; contentEl.empty(); - - if (!this.currentCard) return; - - // 创建卡片容器并固定样式 const cardContainer = contentEl.createDiv({ cls: 'openwords-card' }); const settingsContainer = contentEl.createDiv({ cls: 'openwords-card-settings' }); + this.renderSettings(settingsContainer, cardContainer); + this.renderCard(cardContainer); - new Setting(settingsContainer) - .setName(`评分 0: 回答错误, 完全不会`) - .addButton(btn => btn - .setButtonText('0') - .onClick(() => this.rateCard(0 as SuperMemoGrade, cardContainer))) - new Setting(settingsContainer) - .setName(`评分 1: 回答错误, 看到正确答案后感觉很熟悉`) - .addButton(btn => btn - .setButtonText('1') - .onClick(() => this.rateCard(1 as SuperMemoGrade, cardContainer))) - new Setting(settingsContainer) - .setName(`评分 2: 回答错误, 看到正确答案后感觉很容易记住`) - .addButton(btn => btn - .setButtonText('2') - .onClick(() => this.rateCard(2 as SuperMemoGrade, cardContainer))) - new Setting(settingsContainer) - .setName(`评分 3: 回答正确, 需要花费很大力气才能回忆起来`) - .addButton(btn => btn - .setButtonText('3') - .onClick(() => this.rateCard(3 as SuperMemoGrade, cardContainer))) - new Setting(settingsContainer) - .setName(`评分 4: 回答正确, 需要经过一番犹豫才做出反应`) - .addButton(btn => btn - .setButtonText('4') - .onClick(() => this.rateCard(4 as SuperMemoGrade, cardContainer))) - new Setting(settingsContainer) - .setName(`评分 5: 回答正确, 完美响应`) - .addButton(btn => btn - .setButtonText('5') - .onClick(() => this.rateCard(5 as SuperMemoGrade, cardContainer))) - this.render(cardContainer); - - // 监听数字键 0-5 的按键事件 - const handleKeydown = async (event: KeyboardEvent) => { - if (event.key >= '0' && event.key <= '5') { - if (this.isRating) return; - this.isRating = true; - this.currentRatingKey = event.key - const grade = parseInt(event.key) as SuperMemoGrade; - await this.plugin.updateCard(this.currentCard, grade); - } - }; - const handleKeyup = async (event: KeyboardEvent) => { - if ( - this.isRating && - this.currentRatingKey !== null && - event.key === this.currentRatingKey - ) { - this.isRating=false - this.currentRatingKey = null - this.pickNextCard() - this.render(cardContainer) - } - }; - - this.plugin.registerDomEvent(contentEl, 'keydown', handleKeydown); - this.plugin.registerDomEvent(contentEl, 'keyup', handleKeyup); - + // 注册数字键和回车按键事件 + this.registerRatingKeyEvents(contentEl, cardContainer); + this.registerRenderKeyEvents(cardContainer); } pickNextCard() { @@ -104,6 +51,15 @@ export class LearningModal extends Modal { new Notice("已完成所有新词!"); return; } + // 新词排序:间隔降序,间隔相同则易记因子升序 + pool.sort((a, b) => { + const intervalA = Number(a.interval); + const intervalB = Number(b.interval); + if (intervalA !== intervalB) return intervalB - intervalA; // 降序 + const efactorA = Number(a.efactor); + const efactorB = Number(b.efactor); + return efactorA - efactorB; // 升序 + }); } else { pool = Array.from(this.plugin.dueCards.values()) .filter(card => window.moment(card.dueDate).isBefore(now)) // 筛选已过期的单词 @@ -112,40 +68,110 @@ export class LearningModal extends Modal { new Notice("没有需要复习的卡片!"); return; } + // 旧词排序:易记因子升序,易记因子相同则重复次数升序 + pool.sort((a, b) => { + const efactorA = Number(a.efactor); + const efactorB = Number(b.efactor); + if (efactorA !== efactorB) return efactorA - efactorB; // 升序 + const repetitionA = Number(a.repetition); + const repetitionB = Number(b.repetition); + return repetitionA - repetitionB; // 升序 + }); } - // 单词调度 70% 纯随机, 30% 从易记因子和重复次数最低的 20% 中随机 - const randomMode = Math.random() < 0.7; - if (randomMode) { - this.currentCard = pool[Math.floor(Math.random() * pool.length)]; - } else { - const sortedPool = pool.sort((a, b) => { - const efactorDiff = (a.efactor || 1) - (b.efactor || 1); // 易记因子升序 - if (efactorDiff !== 0) return efactorDiff; - return a.repetition - b.repetition; // 重复次数升序 - }); - const halfPool = sortedPool.slice(0, Math.ceil(sortedPool.length / 5)); // 取前 20% - this.currentCard = halfPool[Math.floor(Math.random() * halfPool.length)]; + + // 单词调度 70% 纯随机, 30% 从排序后前 1% 中随机 + const randomMode = Math.random() < this.plugin.settings.randomRatio; + if (randomMode) { + this.currentCard = pool[Math.floor(Math.random() * pool.length)]; + } else { + const topN = Math.max(1, Math.ceil(pool.length / 100)); + const topPool = pool.slice(0, topN); + this.currentCard = topPool[Math.floor(Math.random() * topPool.length)]; + } + } + + renderSettings(settingsContainer: HTMLElement, cardContainer: HTMLDivElement) { + const grades: { grade: SuperMemoGrade, label: string }[] = [ + { grade: 0, label: '评分 0: 回答错误, 完全不会' }, + { grade: 1, label: '评分 1: 回答错误, 看到正确答案后感觉很熟悉' }, + { grade: 2, label: '评分 2: 回答错误, 看到正确答案后感觉很容易记住' }, + { grade: 3, label: '评分 3: 回答正确, 需要花费很大力气才能回忆起来' }, + { grade: 4, label: '评分 4: 回答正确, 需要经过一番犹豫才做出反应' }, + { grade: 5, label: '评分 5: 回答正确, 完美响应' }, + ]; + for (const { grade, label } of grades) { + new Setting(settingsContainer) + .setName(label) + .addButton(btn => btn + .setButtonText(String(grade)) + .onClick(() => this.rateCard(grade, cardContainer))); } } - render(cardContainer: HTMLDivElement) { - cardContainer.empty() - - // 标记当前是否显示 Markdown 内容 - let isShowingMarkdown = false; - // 初始显示单词 + renderCard(cardContainer: HTMLDivElement) { + cardContainer.empty(); const wordContent = cardContainer.createDiv({ cls: 'openwords-card-content' }); wordContent.textContent = this.currentCard.front; + const file = this.plugin.app.vault.getFileByPath(this.currentCard.path); + if (!file) {return;} + const fileCache = this.app.metadataCache.getFileCache(file); + const frontMatter = fileCache?.frontmatter; + if (!frontMatter) {return;} + const tags: string[] = (frontMatter.tags || []).map((tag: string) => { + const parts = tag.split('/'); + return parts.length > 1 ? parts[1] : tag; + }); + const dueDate: string = frontMatter["到期日"] + const interval: string = frontMatter["间隔"] + const efactor: string = frontMatter["易记因子"] + const repetition: string = frontMatter["重复次数"] - // 添加鼠标悬浮事件切换内容 - cardContainer.addEventListener('mouseenter', async () => { - if (isShowingMarkdown) return; // 如果已经显示 Markdown 内容,则不重复加载 + const metaDiv = cardContainer.createDiv({ cls: 'openwords-card-meta' }); + // 第一行:标签 + const tagsDiv = metaDiv.createDiv({ cls: 'openwords-card-meta-tags' }); + tagsDiv.textContent = `标签: ${tags.join(', ')}`; + + // 第二行:其余元数据 + const infoDiv = metaDiv.createDiv({ cls: 'openwords-card-meta-info' }); + infoDiv.textContent = `易记因子: ${efactor} | 重复次数: ${repetition} | 到期日: ${dueDate} | 间隔: ${interval} `; + } + + registerRatingKeyEvents(contentEl: HTMLElement, cardContainer: HTMLDivElement) { + const handleKeydown = async (event: KeyboardEvent) => { + if (event.key >= '0' && event.key <= '5') { + if (this.isRating) return; + this.isRating = true; + this.currentRatingKey = event.key; + const grade = parseInt(event.key) as SuperMemoGrade; + await this.plugin.updateCard(this.currentCard, grade); + } + }; + const handleKeyup = async (event: KeyboardEvent) => { + if ( + this.isRating && + this.currentRatingKey !== null && + event.key === this.currentRatingKey + ) { + this.isRating = false; + this.currentRatingKey = null; + this.pickNextCard(); + this.renderCard(cardContainer); + } + }; + this.plugin.registerDomEvent(contentEl, 'keydown', handleKeydown); + this.plugin.registerDomEvent(contentEl, 'keyup', handleKeyup); + } + + registerRenderKeyEvents(cardContainer: HTMLDivElement) { + // 标记当前是否显示 Markdown 内容 + let isShowingMarkdown = false; + + const showMarkdown = async () => { + if (isShowingMarkdown) return; const file = this.plugin.app.vault.getFileByPath(this.currentCard.path); if (file instanceof TFile) { const markdownContent = await this.plugin.app.vault.cachedRead(file); - - // 清空卡片内容并渲染 Markdown 内容 cardContainer.empty(); const markdownRenderContainer = cardContainer.createDiv({ cls: 'openwords-card-markdown' }); await MarkdownRenderer.render( @@ -155,24 +181,40 @@ export class LearningModal extends Modal { file.path, this.component ); - isShowingMarkdown = true; } else { new Notice('无法加载单词的 Markdown 文件!'); } - }); + }; - cardContainer.addEventListener('mouseleave', () => { - if (!isShowingMarkdown) return; // 如果已经显示单词内容,则不重复加载 - - // 切换回显示单词 - cardContainer.empty(); - const wordContent = cardContainer.createDiv({ cls: 'openwords-card-content' }); - wordContent.textContent = this.currentCard.front; + const showWord = () => { + if (!isShowingMarkdown) return; + this.renderCard(cardContainer); isShowingMarkdown = false; - }); + }; + cardContainer.addEventListener('mouseenter', showMarkdown); + cardContainer.addEventListener('mouseleave', showWord); + // 按下回车显示内容,释放回车显示单词 + const handleKeyDown = async (event: KeyboardEvent) => { + if (event.key === 'Tab') { + event.preventDefault(); + await showMarkdown(); + } + }; + const handleKeyUp = (event: KeyboardEvent) => { + if (event.key === 'Tab') { + event.preventDefault(); + showWord(); + } + }; + this.plugin.registerDomEvent(cardContainer, 'keydown', handleKeyDown); + this.plugin.registerDomEvent(cardContainer, 'keyup', handleKeyUp); + + // 让卡片容器可聚焦以接收键盘事件 + cardContainer.tabIndex = 0; + cardContainer.focus(); } async rateCard(grade: SuperMemoGrade, cardContainer: HTMLDivElement) { @@ -180,7 +222,7 @@ export class LearningModal extends Modal { this.isRating = true; await this.plugin.updateCard(this.currentCard, grade); this.pickNextCard(); - this.render(cardContainer); + this.renderCard(cardContainer); this.isRating = false; } diff --git a/src/modals/LearningTypeModal.ts b/src/modals/LearningTypeModal.ts index f5e4217..3e5f9c0 100644 --- a/src/modals/LearningTypeModal.ts +++ b/src/modals/LearningTypeModal.ts @@ -59,7 +59,7 @@ export class LearningTypeModal extends Modal { })); new Setting(buttonContainer) - .setName('默写单词 ( 启用单词 )') + .setName('默写单词 ( 重复次数不为零 )') .addButton(btn => btn .setButtonText('开始') .onClick(() => { @@ -68,7 +68,7 @@ export class LearningTypeModal extends Modal { })); new Setting(buttonContainer) - .setName(`添加双链 ( 启用单词中易记因子 <= 2.5 )`) + .setName(`添加双链 ( 启用单词中易记因子 <= ${this.plugin.settings.maxEfactorForLink} )`) .addButton(btn => btn .setButtonText("开始") .onClick(async () => { diff --git a/src/modals/SpellingModal.ts b/src/modals/SpellingModal.ts index 1fe8efa..b1d2bf6 100644 --- a/src/modals/SpellingModal.ts +++ b/src/modals/SpellingModal.ts @@ -1,4 +1,4 @@ -import { App, Modal, MarkdownRenderer, TFile, Component } from 'obsidian'; +import { App, Modal, MarkdownRenderer, TFile, Component, Notice } from 'obsidian'; import { CardInfo } from '../card'; import OpenWords from '../main'; @@ -8,6 +8,8 @@ export class SpellingModal extends Modal { plugin: OpenWords; component: Component; currentCard: CardInfo | null = null; + hasPeeked: boolean = false; // 是否看过答案 + errorCount: number = 0; // 本轮错误次数 constructor(app: App, plugin: OpenWords) { super(app); @@ -16,6 +18,7 @@ export class SpellingModal extends Modal { } async onOpen() { + // 加载组件 const { contentEl } = this; contentEl.empty(); this.component.load() @@ -34,6 +37,7 @@ export class SpellingModal extends Modal { event.preventDefault(); // 阻止页面滚动 if (this.currentCard) { title.setText(`${this.currentCard.front}`); // 显示答案 + this.hasPeeked = true; // 标记已看答案 } } }; @@ -53,14 +57,19 @@ export class SpellingModal extends Modal { inputContainer: HTMLElement, feedbackContainer: HTMLElement ) { - const cards = Array.from(this.plugin.enabledCards.values()); + const cards = Array.from(this.plugin.dueCards.values()); if (cards.length === 0) { feedbackContainer.setText('没有更多单词了!'); return; } + this.hasPeeked = false; + this.errorCount = 0; - // 随机选择一个单词 - this.currentCard = cards[Math.floor(Math.random() * cards.length)]; + // 从易记因子最低的50%中随机选择一个单词 + const sorted = cards.slice().sort((a, b) => a.efactor - b.efactor); + const half = Math.ceil(sorted.length / 2); + const pool = sorted.slice(0, half); + this.currentCard = pool[Math.floor(Math.random() * pool.length)]; // 渲染单词的词义 const file = this.plugin.app.vault.getFileByPath(this.currentCard.path); @@ -118,7 +127,7 @@ export class SpellingModal extends Modal { } } - checkSpelling( + async checkSpelling( inputFields: HTMLInputElement[], word: string, feedbackContainer: HTMLElement, @@ -129,11 +138,35 @@ export class SpellingModal extends Modal { if (userInput.length === word.length) { if (userInput.toLowerCase() === word.toLowerCase()) { feedbackContainer.setText('正确!'); + if (this.currentCard) { + let efactor = this.currentCard.efactor; + if (this.hasPeeked) { + efactor -= 0.02; + } else if (this.errorCount === 0) { + efactor += 0.15; + } else { + efactor += 0.05; + } + if (efactor < 1.3) efactor = 1.3; + this.currentCard.efactor = efactor; + + // 同步到 frontmatter + const file = this.plugin.app.vault.getFileByPath(this.currentCard.path); + if (file instanceof TFile) { + await this.plugin.app.fileManager.processFrontMatter(file, (frontMatter) => { + frontMatter["易记因子"] = Math.round(efactor * 100); + }); + } + + new Notice(`${this.currentCard.front} \n易记因子: ${efactor.toFixed(2)} \n重复次数: ${this.currentCard.repetition} \n间隔: ${this.currentCard.interval} \n到期日: ${this.currentCard.dueDate}`); + + } setTimeout(async () => { await this.pickNextCard(wordMeaningContainer, inputContainer, feedbackContainer); }, 500); } else { feedbackContainer.setText('错误,请重试!'); + this.errorCount += 1; inputFields.forEach((field) => (field.value = '')); // 清空所有输入框 inputFields[0].focus(); // 聚焦第一个输入框 } diff --git a/src/settings.ts b/src/settings.ts index 935c53d..b691bc3 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -147,6 +147,34 @@ export class OpenWordsSettingTab extends PluginSettingTab { button.setButtonText('生成'); // 恢复按钮文本 } })); + new Setting(containerEl) + .setName("随机调度概率") + .setDesc("设置每次抽卡时随机调度的概率,其余为优先调度概率") + .addDropdown(dropdown => { + for (let i = 0; i <= 10; i++) { + const value = (i / 10).toFixed(1); + dropdown.addOption(value, value); + } + dropdown.setValue(this.plugin.settings.randomRatio.toFixed(1)); + dropdown.onChange(async (value) => { + this.plugin.settings.randomRatio = Number(value); + await this.plugin.saveSettings(); + }); + }); + new Setting(containerEl) + .setName("最大双链因子") + .setDesc("易记因子小于等于该值的单词可添加双链") + .addDropdown(dropdown => { + for (let i = 0; i < 10; i++) { + const value = (1.2 + i * 0.2).toFixed(1); + dropdown.addOption(value, value); + } + dropdown.setValue(this.plugin.settings.maxEfactorForLink.toFixed(1)); + dropdown.onChange(async (value) => { + this.plugin.settings.maxEfactorForLink = Number(value); + await this.plugin.saveSettings(); + }); + }); new Setting(containerEl) .setName('重置单词属性') .setDesc('重置作用域单词的易记因子、重复次数、间隔和到期日') diff --git a/styles.css b/styles.css index 7609428..2991270 100644 --- a/styles.css +++ b/styles.css @@ -36,6 +36,7 @@ If your plugin does not need CSS, delete this file. width: 500px; /* 固定宽度 */ height: 300px; /* 固定高度 */ display: flex; + flex-direction: column; justify-content: center; align-items: center; border: 1px solid var(--color-border); @@ -50,9 +51,17 @@ If your plugin does not need CSS, delete this file. } .openwords-card-content { /* 单词内容样式 */ - font-size: 2.5em; /* 设置单词字体大小 */ + font-size: 3em; /* 设置单词字体大小 */ font-weight: bold; /* 设置单词加粗 */ color: var(--text-normal); /* 设置单词颜色 */ + margin-top: 24px; +} + +.openwords-card-meta { + font-size: 0.85em; + line-height: 1.5; + margin-top: 24px; + } .openwords-card-markdown { /* Markdown 内容样式 */ diff --git a/versions.json b/versions.json index 1652a75..927232a 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.0.5": "1.8.10" + "1.0.6": "1.8.10" }