This commit is contained in:
insile 2025-05-01 13:51:58 +08:00
parent fd72256548
commit f5866b1ba6
17 changed files with 4080 additions and 4 deletions

10
.editorconfig Normal file
View file

@ -0,0 +1,10 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
tab_width = 4

3
.eslintignore Normal file
View file

@ -0,0 +1,3 @@
node_modules/
main.js

23
.eslintrc Normal file
View file

@ -0,0 +1,23 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": { "node": true },
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
}
}

35
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: Release Obsidian plugin
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: Build plugin
run: |
npm install
npm run build
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${GITHUB_REF#refs/tags/}"
gh release create "$tag" \
--title="$tag" \
--draft \
main.js manifest.json styles.css

1
.npmrc Normal file
View file

@ -0,0 +1 @@
tag-version-prefix=""

View file

@ -1,9 +1,9 @@
## OpenWords
- 简介
- **OpenWords** 是一个用于背单词和单词管理的 [Obsidian](https://obsidian.md/) 简单插件, 使用 SuperMemo 2 间隔重复算法以及 OpenText 单词库. 依赖的第三方包主要是用于重复计算的 [supermemo](https://www.npmjs.com/package/supermemo) 和用于词形还原的 [wink-pos-tagger](https://www.npmjs.com/package/wink-pos-tagger)
- ![p7](README/p7.png)
- **OpenWords** 是一个用于背单词和单词管理的 [Obsidian](https://obsidian.md/) 简单插件, 插件入口是左侧工具栏的一个按钮加上两个命令. 使用 SuperMemo 2 间隔重复算法以及 OpenText 单词库. 依赖的第三方包主要是用于重复计算的 [supermemo](https://www.npmjs.com/package/supermemo) 和用于词形还原的 [wink-pos-tagger](https://www.npmjs.com/package/wink-pos-tagger). 本插件完全依赖单词文件及其属性, 没有表格, Json 等外部存储, 启动时会读取所有单词缓存属性, 并在更改时写入相应的单词文件, 没有设计过多的搜索功能, 可以使用内置的属性搜索单词
- ![p7](p7.png)
- 单词
- **单词库**以 [OpenText](opentext.net.cn) 为参考, 是 Obsidian 仓库中的一个文件夹, 其中按首字母分类储存所有单词文件, 单词即为文件名, 元数据使用文件 YAML 属性, 其中复选框和标签用于确定插件运行的作用域, 数字和日期是 SM 2 的计算参数. 本插件完全依赖单词文件及其属性, 没有表格, Json 等外部存储, 启动时会读取所有单词缓存属性, 并在更改时写入相应的单词文件
- **单词库**以 [OpenText](opentext.net.cn) 为参考, 是 Obsidian 仓库中的一个文件夹, 其中按首字母分类储存所有单词文件, 单词即为文件名, 元数据使用文件 YAML 属性, 其中复选框和标签用于确定插件运行的作用域, 数字和日期是 SM 2 的计算参数
- 复选框: `掌握` , 确定插件作用域, 单词被掌握意味着不会被插件处理
- 标签: `级别/小学`, `级别/中考`, `级别/高考四级`, `级别/考研`, `级别/六级`, `级别/雅思`, `级别/托福`, `级别/GRE`, 确定插件作用域, 标签意味着当前主要精力范围, 设置中没有勾选意味着不会被插件处理
- 数字: `重复次数`, 连续正确回答的次数, 初始值为 0
@ -26,8 +26,11 @@
- 背单词
- 新词指重复次数为 0 的单词, 它可能是从未被学习过的, 也可能是前面学后面忘了的. 新词调度中 70% 纯随机, 30% 从易记因子和重复次数最低的 20% 中随机; 旧词指重复次数大于 0 的单词, 它是之前回答正确的. 旧词调度从已过期的单词中 70% 纯随机, 30% 从易记因子和重复次数最低的 20% 中随机. 应该尽可能的提高每个单词的重复次数和易记因子
- ![p4](README/p4.gif)
- 默写单词
- 从作用域中随机抽取单词进行拼写, 目前只是一个练习功能, 不会改变更改单词的属性, 按下 `Tab` 键显示答案
- ![](README/p8.gif)
- 添加双链
- 为当前活动页面笔记中的单词添加双链, 单词范围是作用域中易记因子小于等于 2.5 的, 所以默认属性都是添加的. 准确率在 90 %
- 为当前活动页面笔记中的单词添加双链, 单词范围是作用域中易记因子小于等于 2.5 的, 所以默认属性都是添加的. 准确率在 90 %. 需要文章尽可能规范, 减少不必要的标点符号. 目前只能通过 `ctrl z` 回撤
- ![p5](README/p5.gif)
- 重置单词属性
- 重置作用域单词的重复次数, 间隔, 易记因子, 到期日为默认值, 重置读写会很慢

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 41 KiB

BIN
README/p8.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

49
esbuild.config.mjs Normal file
View file

@ -0,0 +1,49 @@
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.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
minify: prod,
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
}

1186
main.ts Normal file

File diff suppressed because it is too large Load diff

10
manifest.json Normal file
View file

@ -0,0 +1,10 @@
{
"id": "openwords",
"name": "OpenWords",
"version": "1.0.0",
"minAppVersion": "1.8.10",
"description": "用于英语学习中背单词与单词管理的插件",
"author": "insile",
"authorUrl": "https://opentext.net.cn/",
"isDesktopOnly": false
}

2488
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

35
package.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "openwords",
"version": "1.0.0",
"description": "用于英语学习中背单词与单词管理的插件",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [
"Obsidian",
"English",
"words",
"learning"
],
"author": "insile",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"@types/wink-pos-tagger": "^2.2.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"
},
"dependencies": {
"dayjs": "^1.11.13",
"supermemo": "^2.0.23",
"wink-pos-tagger": "^2.2.2"
}
}

192
styles.css Normal file
View file

@ -0,0 +1,192 @@
/*
This CSS file will be included with your plugin, and
available in the app when your plugin is enabled.
If your plugin does not need CSS, delete this file.
*/
/* 学习模式标题 */
.type-title {
text-align: center;
margin-bottom: 20px;
font-size: 1.5em;
font-weight: bold;
}
/* 设置 */
.openwords-toggle-grid {
display: grid;
grid-template-columns: repeat(4, 1fr); /* 每行 4 列 */
gap: 10px; /* 每个 toggle 之间的间距 */
/* margin-top: 10px; */
}
.openwords-toggle-grid .setting-item:first-child {
padding-top: 10px;
border-top: 1px solid var(--background-modifier-border)
}
.type-modal{
width: 650px; /* 设置模态框宽度 */
}
/* 卡片容器样式 */
.anki-card {
width: 500px; /* 固定宽度 */
height: 300px; /* 固定高度 */
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--color-border);
border-radius: 8px;
margin: 20px auto;
background-color: var(--background-primary);
text-align: center;
cursor: pointer; /* 鼠标悬停时显示手型 */
}
.anki-card:hover {
background-color: var(--background-modifier-hover); /* 鼠标悬停时背景变浅 */
}
/* 单词内容样式 */
.anki-card-content {
font-size: 2.5em; /* 设置单词字体大小 */
font-weight: bold; /* 设置单词加粗 */
color: var(--text-normal); /* 设置单词颜色 */
}
/* Markdown 内容样式 */
.anki-markdown-content {
width: 100%;
height: 100%;
overflow-y: auto; /* 添加垂直滚动条 */
background-color: var(--background-secondary); /* 更柔和的背景颜色 */
color: var(--text-muted); /* 使用正常文本颜色 */
font-size: 1.1em; /* 稍微增大字体 */
line-height: 1.6; /* 增加行高,提升可读性 */
border-radius: 8px; /* 添加圆角 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* 设置优雅的字体 */
user-select: text; /* 允许文本选择 */
}
/* 设置容器样式 */
.anki-settings {
margin-top: 20px;
font-size: 1em; /* 减小字体大小 */
}
.anki-settings .setting-item {
padding: 5px 10px; /* 减小内边距 */
}
/* 单词列表模态框 */
.word-list-modal{
width: 400px;
}
.word-list-title {
font-size: 1.5em;
font-weight: bold;
margin-bottom: 1em;
text-align: center;
}
.word-list-filters {
display: flex;
justify-content: space-between;
margin-bottom: 1em;
}
.word-list-container {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.word-list-filters .setting-item:first-child {
padding-top: 10px;
border-top: 1px solid var(--background-modifier-border)
}
/* 单词项容器样式 */
.word-list-item {
display: flex; /* 设置为水平布局 */
justify-content: space-between; /* 左右对齐 */
border-bottom: 1px solid var(--background-modifier-border);
position: relative; /* 为预览容器提供定位上下文 */
}
/* Markdown 预览容器样式 */
.word-preview-inline {
font-size: 1em; /* 设置预览内容字体大小 */
line-height: 1.5; /* 设置行高 */
overflow-y: auto; /* 添加垂直滚动条 */
max-height: 300px; /* 设置最大高度 */
position: absolute; /* 设置绝对定位 */
top: 100%; /* 显示在单词项的下方 */
left: 0;
width: 100%;
z-index: 10; /* 确保预览内容在最上层 */
background-color: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
border-radius: 5px;
padding: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
display: none; /* 默认隐藏 */
user-select: text; /* 允许文本选择 */
}
/* 鼠标悬停时显示预览内容 */
.word-text:hover .word-preview-inline {
display: block; /* 鼠标悬停时显示预览内容 */
}
/* 单词文本样式 */
.word-text {
font-size: 1.1em;
color: var(--text-normal);
cursor: pointer;
}
/* 默写单词模态框样式 */
.spelling-modal {
width: auto;
margin: 0 auto;
padding: 20px;
border-radius: 8px;
background-color: var(--background-primary);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.spelling-title {
font-size: 1.5em;
font-weight: bold;
text-align: center;
margin-bottom: 20px;
}
.input-container {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 20px;
}
.letter-input {
width: 50px !important;
height: 50px !important;
text-align: center;
font-size: 1.5em !important;
border: 1px solid var(--background-modifier-border);
border-radius: 5px;
outline: none;
}
.feedback-container {
margin-top: 10px;
text-align: center;
font-size: 1em;
color: var(--text-accent);
}

24
tsconfig.json Normal file
View file

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

14
version-bump.mjs Normal file
View file

@ -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"));

3
versions.json Normal file
View file

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