├── .editorconfig
├── .gitattributes
├── .gitignore
├── .vscode
└── extensions.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build-for-windows.js
├── docs
├── assets
│ ├── anki-card-back.png
│ ├── anki-card-front.png
│ ├── changelog
│ │ ├── 0.0.1
│ │ │ ├── anki-card-back.png
│ │ │ ├── anki-card-front.png
│ │ │ ├── main-screenshot.png
│ │ │ └── settings-screenshot.png
│ │ └── 0.0.2
│ │ │ ├── anki-card-back.png
│ │ │ ├── anki-card-front.png
│ │ │ ├── main-screenshot.png
│ │ │ └── settings-screenshot.png
│ ├── main-screenshot.png
│ └── settings-screenshot.png
└── tauri-develop.md
├── index.html
├── package-lock.json
├── package.json
├── public
└── tauri.svg
├── src-tauri
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── capabilities
│ └── migrated.json
├── gen
│ └── schemas
│ │ ├── acl-manifests.json
│ │ ├── capabilities.json
│ │ ├── desktop-schema.json
│ │ ├── linux-schema.json
│ │ └── windows-schema.json
├── icons
│ ├── 128x128.png
│ ├── 128x128@2x.png
│ ├── 32x32.png
│ ├── Square107x107Logo.png
│ ├── Square142x142Logo.png
│ ├── Square150x150Logo.png
│ ├── Square284x284Logo.png
│ ├── Square30x30Logo.png
│ ├── Square310x310Logo.png
│ ├── Square44x44Logo.png
│ ├── Square71x71Logo.png
│ ├── Square89x89Logo.png
│ ├── StoreLogo.png
│ ├── icon.icns
│ ├── icon.ico
│ └── icon.png
├── resources
│ ├── config-template.toml
│ └── dict.db
├── src
│ ├── application
│ │ ├── config.rs
│ │ ├── dict.rs
│ │ ├── logics
│ │ │ ├── config.rs
│ │ │ ├── dict.rs
│ │ │ ├── mod.rs
│ │ │ └── utils.rs
│ │ └── mod.rs
│ └── main.rs
├── tauri.conf.json
└── tauri.linux.conf.json
├── src
├── App.vue
├── assets
│ ├── OpenFilled.svg
│ ├── arrow-back.svg
│ ├── edit.svg
│ ├── github.svg
│ ├── model-back.html
│ ├── model-css.css
│ ├── model-front.html
│ ├── model-template-release-note.md
│ ├── play-audio.svg
│ ├── reset.svg
│ ├── settings.svg
│ └── zhb-avatar.png
├── components
│ ├── AddButton.vue
│ ├── CardStatus.ts
│ ├── CollinsCard.vue
│ ├── OxfordCard.vue
│ ├── PlayAudioButton.vue
│ ├── ResetButton.vue
│ ├── ReturnButton.vue
│ ├── ScrollMemory copy.vue
│ ├── ScrollMemory.vue
│ ├── SentencePanel.vue
│ ├── SettingButton.vue
│ ├── Token.vue
│ ├── WordCard.vue
│ ├── YoudaoCard.vue
│ └── index.ts
├── fluent-controls
│ ├── FluentButton.vue
│ ├── FluentHyperlink.vue
│ ├── FluentInput.vue
│ ├── FluentRadio.vue
│ ├── FluentSelect.vue
│ ├── fluent-scrollbar.css
│ ├── fluent-styles.css
│ ├── generateUniqueId.ts
│ ├── index.ts
│ └── useHover.ts
├── logics
│ ├── anki-connect.ts
│ ├── anki.ts
│ ├── config.ts
│ ├── dict.ts
│ ├── globals.ts
│ ├── lock.ts
│ ├── stringutils.ts
│ ├── typing.ts
│ ├── utils.ts
│ └── youdao.ts
├── main.ts
├── router.ts
├── tauri-api.ts
├── views
│ ├── Main.vue
│ └── Settings.vue
└── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.db filter=lfs diff=lfs merge=lfs -text
2 | src-tauri/icons/icon.ico filter=lfs diff=lfs merge=lfs -text
3 | src-tauri/icons/Square310x310Logo.png filter=lfs diff=lfs merge=lfs -text
4 | src-tauri/icons/128x128.png filter=lfs diff=lfs merge=lfs -text
5 | src-tauri/icons/Square150x150Logo.png filter=lfs diff=lfs merge=lfs -text
6 | src-tauri/icons/Square89x89Logo.png filter=lfs diff=lfs merge=lfs -text
7 | src-tauri/icons/Square71x71Logo.png filter=lfs diff=lfs merge=lfs -text
8 | src-tauri/icons/icon.png filter=lfs diff=lfs merge=lfs -text
9 | src-tauri/icons/Square107x107Logo.png filter=lfs diff=lfs merge=lfs -text
10 | src-tauri/icons/Square142x142Logo.png filter=lfs diff=lfs merge=lfs -text
11 | src-tauri/icons/Square30x30Logo.png filter=lfs diff=lfs merge=lfs -text
12 | src-tauri/icons/Square44x44Logo.png filter=lfs diff=lfs merge=lfs -text
13 | src-tauri/icons/128x128@2x.png filter=lfs diff=lfs merge=lfs -text
14 | src-tauri/icons/32x32.png filter=lfs diff=lfs merge=lfs -text
15 | src-tauri/icons/icon.icns filter=lfs diff=lfs merge=lfs -text
16 | src-tauri/icons/Square284x284Logo.png filter=lfs diff=lfs merge=lfs -text
17 | src-tauri/icons/StoreLogo.png filter=lfs diff=lfs merge=lfs -text
18 |
19 | *.db linguist-vendored
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.local.*
2 | *.local/
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | pnpm-debug.log*
11 | lerna-debug.log*
12 |
13 | node_modules
14 | dist
15 | dist-ssr
16 | *.local
17 |
18 | # Editor directories and files
19 | .vscode/*
20 | !.vscode/extensions.json
21 | .idea
22 | .DS_Store
23 | *.suo
24 | *.ntvs*
25 | *.njsproj
26 | *.sln
27 | *.sw?
28 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "Vue.volar",
4 | "tauri-apps.tauri-vscode",
5 | "rust-lang.rust-analyzer"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 更新日志
2 | ## [0.1.0] - 2025-01-02
3 |
4 | 各位朋友,新年快乐!🎉
5 |
6 | ### 划词助手更新内容
7 |
8 | - 支持**编辑单词笔记**。单词添加成功后,点击单词条目右侧的“编辑”按钮,即可打开 Anki 的卡片编辑器,对所添加的单词笔记进行编辑。
9 | - 新增应用和模板的检查更新功能。
10 | - 修复在点击配置文件的“打开目录”时出现 cmd 窗口的问题。
11 | - 修复在配置文件路径包含空格时“打开目录”可能失败的问题。
12 |
13 | ### 单词笔记模板更新内容
14 |
15 | 本次更新将划词助手的单词笔记模板升级至 0.1.0 版本,模板的更新内容如下:
16 |
17 | - 新增**例句朗读**功能,点击例句中的喇叭图标可以朗读例句,再次点击可以停止朗读。例句朗读的语音来自有道词典,需要联网使用。
18 | - 为条目的**展开和收起**添加了**动画效果**,点击条目的标题可以展开或收起条目的内容。
19 | - 其他卡片布局优化。
20 |
21 | ---
22 |
23 | 如果你使用过本应用的 0.0.1 版本或 [mmjang](https://github.com/mmjang) 开发的 Android 版([mmjang / ankihelper](https://github.com/mmjang/ankihelper)),那么需要手动确认是否**将已有的旧模板升级为新模板**:
24 |
25 | 1. 打开 Anki 软件(事先安装好 AnkiConnect 插件)。
26 | 1. 点击划词助手右上角的设置按钮,进入设置页面。
27 | 1. 点击设置页面的“更新模板”按钮,并在弹出的对话框中确认更新。
28 |
29 | 你也可以继续使用旧模板,这不会影响划词助手的使用。但是,建议你更新模板,以在复习单词时获得更好的体验。
30 |
31 | 如果你的 Anki 软件中没有划词助手的旧模板,或者你是第一次使用划词助手,那么无需进行上述操作,直接使用即可。
32 |
33 | ## [0.0.1] - 2024-03-17
34 |
35 | 第一个版本。
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
5 | Anki 划词助手
6 |
7 | Anki 划词助手是一个制作 Anki 卡片的工具,你可以用它标记句子中的生词,通过“单词结合上下文”的方式更好地背单词。
8 |
9 | 本项目受到了 [mmjang / ankihelper](https://github.com/mmjang/ankihelper) 的启发。由于原项目是 Android 应用,而且已经不再维护,而我自己用电脑的时间更多,于是自己用 [Tauri](https://github.com/tauri-apps/tauri) 写了一个类似的工具。
10 |
11 |
12 |
13 |
14 |
15 | # 安装
16 | ## 安装划词助手本体
17 |
18 | [Releases 页面](https://github.com/zhb2000/anki-marker/releases)提供了 Windows 平台的便携式应用(.zip)和安装程序(.msi/.exe),其余平台请自行编译。
19 |
20 | Anki 划词助手是一个基于 Tauri 的桌面应用,你的 Windows 系统需要带有 [Microsoft Edge WebView2](https://developer.microsoft.com/zh-cn/microsoft-edge/webview2/) 才能运行(Windows 10 2004 及以上版本已经自带)。
21 |
22 | ## 安装辅助工具
23 |
24 | Anki 划词助手需要通过 AnkiConnect 插件与 Anki 进行通信,你需要先安装 **Anki 应用**和 **AnkiConnect 插件**:
25 |
26 | 1. 安装 Anki 应用:[Anki - powerful, intelligent flashcards](https://apps.ankiweb.net/)。
27 | 1. 安装 AnkiConnect 插件:[AnkiConnect - AnkiWeb](https://ankiweb.net/shared/info/2055492159)。安装方法如下:
28 | 1. 打开 Anki,点击“工具-插件”。
29 | 1. 点击“获取插件”,输入 AnkiConnect 的代码 `2055492159`,点击“确定”。
30 | 1. 重启 Anki 应用。
31 |
32 | # 使用
33 |
34 | 使用 Anki 划词助手时,请确保 **Anki 应用已经打开**,AnkiConnect 的服务会在 Anki 启动时自动开启。
35 |
36 | AnkiConnect 默认会在 `localhost:8765` 上启动一个 HTTP 服务,如果你修改了 AnkiConnect 的端口号,请在设置中将“AnkiConnect 服务”这一项修改为对应的 URL:
37 |
38 |
39 |
40 |
41 |
42 | 划词界面:
43 |
44 |
45 |
46 |
47 |
48 | 添加的 Anki 卡片(正面/背面):
49 |
50 |
51 |
52 |
53 |
54 |
55 | # 开发
56 |
57 | 开发模式(热重载):
58 |
59 | ```shell
60 | cargo tauri dev
61 | # 或者
62 | npm run tauri dev
63 | ```
64 |
65 | 打包成 Windows 安装程序(.msi/.exe)和便携式应用(.zip):
66 |
67 | ```shell
68 | node build-for-windows.js
69 | ```
70 |
71 | 打包好的安装程序和便携式应用位于 `src-tauri/target/release/release-assets` 目录下。
72 |
73 | 在其他平台上构建:
74 |
75 | ```shell
76 | cargo tauri build
77 | # 或者
78 | npm run tauri build
79 | ```
80 |
81 | 打包好的应用位于 `src-tauri/target/release/bundle` 目录下。
82 |
--------------------------------------------------------------------------------
/build-for-windows.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /** Build portable zip and bundle installer for Windows. */
3 |
4 | import { spawn } from 'child_process';
5 | import fs from 'fs';
6 | import fsp from 'fs/promises'; // 使用 fs/promises 提供的异步方法
7 | import path from 'path';
8 | import archiver from 'archiver';
9 | import { fileURLToPath } from 'url';
10 | import { dirname } from 'path';
11 | // ES 模块中处理 __dirname
12 | const __filename = fileURLToPath(import.meta.url);
13 | const __dirname = dirname(__filename);
14 |
15 | /**
16 | * @param {string} productName
17 | * @param {string} packageName
18 | * @param {string} version
19 | */
20 | async function renameAndCopyAssets(productName, packageName, version) {
21 | const releaseDir = path.join(__dirname, 'src-tauri/target/release');
22 | const assetsDir = path.join(releaseDir, 'release-assets', version);
23 |
24 | // 创建 release-assets 目录
25 | await fsp.mkdir(assetsDir, { recursive: true });
26 |
27 | // 复制 portable zip
28 | const portableFilename = `${packageName}_${version}_windows_x64-portable.zip`;
29 | const portableOldPath = path.join(releaseDir, 'portable', portableFilename);
30 | const portableNewPath = path.join(assetsDir, portableFilename);
31 | await fsp.copyFile(portableOldPath, portableNewPath);
32 |
33 | // 复制 msi 文件
34 | const msiOldFilename = `${productName}_${version}_x64_zh-CN.msi`;
35 | const msiOldPath = path.join(releaseDir, 'bundle/msi', msiOldFilename);
36 | const msiNewFilename = `${packageName}_${version}_windows_x64.msi`;
37 | const msiNewPath = path.join(assetsDir, msiNewFilename);
38 | await fsp.copyFile(msiOldPath, msiNewPath);
39 |
40 | // 复制 nsis 文件
41 | const nsisOldFilename = `${productName}_${version}_x64-setup.exe`;
42 | const nsisOldPath = path.join(releaseDir, 'bundle/nsis', nsisOldFilename);
43 | const nsisNewFilename = `${packageName}_${version}_windows_x64-setup.exe`;
44 | const nsisNewPath = path.join(assetsDir, nsisNewFilename);
45 | await fsp.copyFile(nsisOldPath, nsisNewPath);
46 | }
47 |
48 | /**
49 | * @param {string} productName
50 | * @param {string} portableName
51 | */
52 | async function makePortable(productName, portableName) {
53 | const releaseDir = path.join(__dirname, 'src-tauri/target/release');
54 | const portableDir = path.join(releaseDir, 'portable');
55 | const packDir = path.join(portableDir, portableName);
56 |
57 | // 创建 portable 目录
58 | await fsp.mkdir(packDir, { recursive: true });
59 |
60 | // 拷贝 exe 和 resources 目录到 portable 目录
61 | await fsp.copyFile(
62 | path.join(releaseDir, `${productName}.exe`),
63 | path.join(packDir, `${productName}.exe`)
64 | );
65 | await fsp.cp(
66 | path.join(releaseDir, 'resources'),
67 | path.join(packDir, 'resources'),
68 | { recursive: true }
69 | );
70 |
71 | // 删除 resources/icon.ico
72 | await fsp.unlink(path.join(packDir, 'resources/icon.ico'));
73 |
74 | // 拷贝并重命名 config-template.toml 为 config.toml
75 | await fsp.copyFile(
76 | path.join(releaseDir, 'resources/config-template.toml'),
77 | path.join(packDir, 'config.toml')
78 | );
79 |
80 | // 压缩 portable 目录为 zip
81 | console.log(`开始压缩 ${portableName}.zip ...`);
82 | await new Promise((resolve, reject) => {
83 | const output = fs.createWriteStream(path.join(portableDir, `${portableName}.zip`));
84 | const archive = archiver('zip', { zlib: { level: 9 } });
85 |
86 | output.on('close', () => {
87 | const sizeInBytes = archive.pointer();
88 | const sizeInMegabytes = (sizeInBytes / (1024 * 1024)).toFixed(2);
89 | console.log(`${portableName}.zip 压缩完成,总大小: ${sizeInMegabytes} MB`);
90 | resolve();
91 | });
92 |
93 | archive.on('error', err => reject(err));
94 |
95 | archive.pipe(output);
96 | archive.directory(packDir, false);
97 | archive.finalize();
98 | });
99 | }
100 |
101 | /**
102 | * 执行命令行命令的封装
103 | * @param {string} command
104 | * @param {string[]} args
105 | */
106 | async function runCommand(command, args) {
107 | return new Promise((resolve, reject) => {
108 | // 使用 spawn 替代 exec 来实时显示输出
109 | // 使用 stdio: 'inherit' 保证 tauri 的彩色输出
110 | const process = spawn(command, args, { shell: true, stdio: 'inherit' });
111 | process.on('close', code => {
112 | if (code !== 0) {
113 | reject(new Error(`Command "${command} ${args.join(' ')}" exited with code ${code}`));
114 | } else {
115 | resolve();
116 | }
117 | });
118 | });
119 | }
120 |
121 | async function main() {
122 | const skipBuild = process.argv.includes('--skip-build');
123 | const packageJson = JSON.parse(await fsp.readFile(path.join(__dirname, 'package.json'), 'utf8'));
124 | /** @type {string} */
125 | const version = packageJson.version;
126 | /** @type {string} */
127 | const packageName = packageJson.name;
128 | const tauriJson = JSON.parse(await fsp.readFile(path.join(__dirname, 'src-tauri/tauri.conf.json'), 'utf8'));
129 | /** @type {string} */
130 | const productName = tauriJson.productName;
131 | const portableName = `${packageName}_${version}_windows_x64-portable`;
132 |
133 | if (!skipBuild) {
134 | console.log('开始构建 Tauri 项目...');
135 | await runCommand('npm', ['run', 'tauri', 'build']);
136 | } else {
137 | console.log('跳过 npm run tauri build');
138 | }
139 | await makePortable(productName, portableName);
140 | await renameAndCopyAssets(productName, packageName, version);
141 | console.log(`构建和打包完成!请查看 src-tauri/target/release/release-assets/${version} 目录。`);
142 | }
143 |
144 | main();
145 |
--------------------------------------------------------------------------------
/docs/assets/anki-card-back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/anki-card-back.png
--------------------------------------------------------------------------------
/docs/assets/anki-card-front.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/anki-card-front.png
--------------------------------------------------------------------------------
/docs/assets/changelog/0.0.1/anki-card-back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/changelog/0.0.1/anki-card-back.png
--------------------------------------------------------------------------------
/docs/assets/changelog/0.0.1/anki-card-front.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/changelog/0.0.1/anki-card-front.png
--------------------------------------------------------------------------------
/docs/assets/changelog/0.0.1/main-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/changelog/0.0.1/main-screenshot.png
--------------------------------------------------------------------------------
/docs/assets/changelog/0.0.1/settings-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/changelog/0.0.1/settings-screenshot.png
--------------------------------------------------------------------------------
/docs/assets/changelog/0.0.2/anki-card-back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/changelog/0.0.2/anki-card-back.png
--------------------------------------------------------------------------------
/docs/assets/changelog/0.0.2/anki-card-front.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/changelog/0.0.2/anki-card-front.png
--------------------------------------------------------------------------------
/docs/assets/changelog/0.0.2/main-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/changelog/0.0.2/main-screenshot.png
--------------------------------------------------------------------------------
/docs/assets/changelog/0.0.2/settings-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/changelog/0.0.2/settings-screenshot.png
--------------------------------------------------------------------------------
/docs/assets/main-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/main-screenshot.png
--------------------------------------------------------------------------------
/docs/assets/settings-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhb2000/anki-marker/e2617df927a1c674e3c4d49ea63df52738d971a9/docs/assets/settings-screenshot.png
--------------------------------------------------------------------------------
/docs/tauri-develop.md:
--------------------------------------------------------------------------------
1 | # Tauri + Vue 3 + TypeScript
2 |
3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `
13 |