From 4185e3d3328fb66be6fcd611aa5e49a78cd03053 Mon Sep 17 00:00:00 2001
From: jasonliu1199 <114725833+jasonliu1199@users.noreply.github.com>
Date: Sat, 13 Jun 2026 00:32:38 +0800
Subject: [PATCH 1/3] docs: clarify community maintenance scope
---
.github/ISSUE_TEMPLATE/bug_report.yml | 38 ++++++++++++++++++++++
.github/ISSUE_TEMPLATE/feature_request.yml | 24 ++++++++++++++
.github/pull_request_template.md | 11 +++++++
.gitignore | 1 -
CHANGELOG.md | 23 +++++++++++++
MAINTAINERS.md | 15 +++++++++
README.md | 24 ++++++++++----
ROADMAP.md | 32 ++++++++++++++++++
8 files changed, 161 insertions(+), 7 deletions(-)
create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml
create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml
create mode 100644 .github/pull_request_template.md
create mode 100644 CHANGELOG.md
create mode 100644 MAINTAINERS.md
create mode 100644 ROADMAP.md
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..9dab263
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,38 @@
+name: Bug report
+description: Report a reproducible ToastFish problem.
+title: "[Bug]: "
+labels: ["bug"]
+body:
+ - type: textarea
+ id: summary
+ attributes:
+ label: Summary
+ description: What happened?
+ validations:
+ required: true
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to reproduce
+ description: List the exact steps that trigger the problem.
+ placeholder: |
+ 1. Open ToastFish
+ 2. Select ...
+ 3. Click ...
+ validations:
+ required: true
+ - type: input
+ id: version
+ attributes:
+ label: ToastFish version
+ placeholder: v3.0.1-community.1
+ - type: input
+ id: windows
+ attributes:
+ label: Windows version
+ placeholder: Windows 11 23H2
+ - type: textarea
+ id: logs
+ attributes:
+ label: Error details or screenshots
+ description: Paste exception text or attach screenshots if available.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..40cd412
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,24 @@
+name: Feature request
+description: Suggest an improvement for ToastFish.
+title: "[Feature]: "
+labels: ["enhancement"]
+body:
+ - type: textarea
+ id: problem
+ attributes:
+ label: Problem
+ description: What problem would this feature solve?
+ validations:
+ required: true
+ - type: textarea
+ id: proposal
+ attributes:
+ label: Proposed solution
+ description: What behavior do you expect?
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives
+ description: Any workaround or alternative design you have considered?
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..613c2d1
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,11 @@
+## Summary
+
+-
+
+## Verification
+
+-
+
+## Related Issues
+
+-
diff --git a/.gitignore b/.gitignore
index afbe906..e379847 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,3 @@ obj/
bin/
token.txt
.vs/
-.github/
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..4d396ea
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,23 @@
+# Changelog
+
+All notable changes in this community fork are documented here.
+
+## v3.0.1-community.1 - Unreleased
+
+### Fixed
+
+- Ensure the `Log` directory exists before learning or import flows write logs.
+- Harden Excel import against unreadable files, empty rows, empty cells, and null import results.
+- Fix Japanese word progress lookup by quoting the selected book name in SQL.
+
+### Changed
+
+- Clarified community fork status in README.
+- Added maintenance roadmap.
+- Updated GitHub Actions workflow for current Windows runners.
+
+### Known Issues
+
+- This remains a Windows-only WPF/.NET Framework application.
+- Some notification behavior depends on Windows notification settings.
+- macOS/Linux support is not planned for the first maintenance cycle.
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
new file mode 100644
index 0000000..525dc16
--- /dev/null
+++ b/MAINTAINERS.md
@@ -0,0 +1,15 @@
+# Maintainers
+
+This is a community-maintained fork of `Uahh/ToastFish`.
+
+## Maintenance Scope
+
+- Keep the Windows version buildable and usable.
+- Prioritize crash fixes, import/export reliability, notification reliability, and release packaging.
+- Preserve upstream attribution and the MIT License.
+
+## Project Boundaries
+
+- Do not present this fork as the official upstream project unless upstream grants maintainer access or transfers ownership.
+- Keep changes small and reviewable.
+- Prefer issue-linked fixes over broad rewrites.
diff --git a/README.md b/README.md
index 8e367fa..11f468f 100644
--- a/README.md
+++ b/README.md
@@ -4,18 +4,22 @@
-# ToastFish
+# ToastFish Community Edition
#### 这是一个利用Windows通知栏背单词的软件
#### 可以让你在上班、上课等恶劣环境下安全隐蔽地背单词

-
-
-[](https://github.com/Uahh/ToastFish/actions/workflows/dotnet-desktop.yml)
+
+## 社区维护说明
+
+这是 [Uahh/ToastFish](https://github.com/Uahh/ToastFish) 的社区维护版本,目标是让 ToastFish 在当前 Windows 环境中继续可用,优先修复启动、通知、词库导入、日志和发布流程问题。
+
+本 fork 不是官方上游项目,除非原作者后续授权或转移维护权限。原项目采用 MIT License,本 fork 会保留原始授权与 attribution。
+
## 使用方法
### 基本流程
1. 选择词库:
@@ -44,7 +48,7 @@
### 自定义内容
可以通过自定义Excel内容来让ToastFish推送所需要的内容。
-自定义Excel模板位于安装目录/Resources/自定义模板.xslx
+自定义Excel模板位于安装目录/Resources/自定义模板.xlsx

@@ -56,9 +60,15 @@ A: 可以在系统设置 -> 轻松使用 -> 显示 -> 通知显示的时间 里
Q: 使用英语发音功能时会闪退?
A: 请在系统设置里下载英语语音包,重启软件即可解决。
+
+Q: 点击开始后没有通知?
+A: 请确认 Windows 通知未被勿扰模式、专注助手或应用通知权限拦截,并检查系统通知中心中是否已有 ToastFish 通知。
+
+Q: 导入 Excel 时失败或无反应?
+A: 请使用 `Resources/自定义模板.xlsx` 作为模板,并保留第一行的类型标记。社区版会优先修复空单元格和格式异常导致的崩溃。
Q: 有没有Win7或是Mac版本的开发计划?
-A: 这个真没有,Win7和Mac没有Windows10的通知栏。
+A: 近期维护重点是 Windows 10/11。ToastFish 依赖 Windows 通知能力,暂不计划 macOS/Linux 版本。
Q: 没有我想要背的单词怎么办?
A: 可以使用自定义功能自己构建单词列表,如果单词数量很多,可以联系作者帮忙添加。
@@ -84,6 +94,8 @@ git clone https://github.com/Uahh/ToastFish
```
项目使用VS2019, .net环境为4.7.2.
+社区版建议使用 Visual Studio 2019/2022 或 GitHub Actions 的 Windows runner 编译。
+
## 感谢
感谢@itorr为本软件提供的支持、建议和测试!
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..c80269d
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,32 @@
+# Roadmap
+
+This fork focuses on maintenance first. Large rewrites and platform ports are out of scope until the Windows version is stable again.
+
+## v3.0.1-community.1
+
+- Keep the project buildable on current GitHub Actions Windows runners.
+- Publish build artifacts for every main branch build.
+- Fix crashes caused by missing runtime folders.
+- Harden Excel import against empty rows, empty cells, and unreadable files.
+- Fix known Japanese word review crash caused by an invalid SQL query.
+- Update README troubleshooting for Windows notification and voice-pack issues.
+
+## v3.0.2-community
+
+- Triage Windows 10/11 launch failures.
+- Improve notification reliability after notifications are collapsed into Action Center.
+- Add clearer diagnostics for import and notification failures.
+- Review thread lifecycle and reduce reliance on `Thread.Abort`.
+
+## Later
+
+- Export currently imported/custom word lists.
+- Add a way to exclude mastered words.
+- Add pause/resume for interrupted learning sessions.
+- Add shortcut customization.
+
+## Non-Goals
+
+- No macOS or Linux port in the first maintenance cycle.
+- No UI rewrite in the first maintenance cycle.
+- No framework migration until reliability issues are under control.
From d5a6109b2760b40b119c03df2d028901686bdb94 Mon Sep 17 00:00:00 2001
From: jasonliu1199 <114725833+jasonliu1199@users.noreply.github.com>
Date: Sat, 13 Jun 2026 00:32:45 +0800
Subject: [PATCH 2/3] ci: modernize Windows build workflow
---
.github/workflows/dotnet-desktop.yml | 71 ++++++++--------------------
1 file changed, 20 insertions(+), 51 deletions(-)
diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml
index e3ddccb..4d29826 100644
--- a/.github/workflows/dotnet-desktop.yml
+++ b/.github/workflows/dotnet-desktop.yml
@@ -1,65 +1,34 @@
-name: .NET Core Desktop
+name: Windows Build
+
on:
push:
- branches: [ main ]
+ branches: [main]
pull_request:
- branches: [ main ]
+ branches: [main]
+ workflow_dispatch:
jobs:
-
build:
-
- strategy:
- matrix:
- configuration: [Debug, Release]
-
runs-on: windows-latest
steps:
- - name: Checkout
- uses: actions/checkout@v2
- with:
- fetch-depth: 0
-
- # Install .NET Core
- - name: Install .NET Core
- uses: actions/setup-dotnet@v1
- with:
- dotnet-version: 3.1.202
-
- # Setup MSBuild.exe
- - name: Setup MSBuild.exe
- uses: microsoft/setup-msbuild@v1.0.2
-
- # Setup NuGet
- - name: Setup NuGet
- uses: nuget/setup-nuget@v1
- with:
- nuget-api-key: ${{ secrets.NuGetAPIKey }}
- nuget-version: '5.x'
+ - name: Checkout
+ uses: actions/checkout@v4
- # NuGet restore
- - name: NuGet restore
- run: nuget restore
+ - name: Setup MSBuild
+ uses: microsoft/setup-msbuild@v2
- # Build
- - name: Build the solution
- run: msbuild /p:Configuration=$env:Configuration
- env:
- Configuration: ${{ matrix.configuration }}
+ - name: Setup NuGet
+ uses: nuget/setup-nuget@v2
- # Execute unit tests
- - name: Execute unit tests
- run: dotnet test -c $env:Configuration
- env:
- Configuration: ${{ matrix.configuration }}
-
- # Upload bin directory
- - name: Upload bin directory
- uses: actions/upload-artifact@main
- if: ${{ success() }}
- with:
- name: ToastFish_Build
- path: ./bin/Release/
+ - name: Restore packages
+ run: nuget restore ToastFish.sln
+ - name: Build
+ run: msbuild ToastFish.sln /p:Configuration=Release /p:Platform="Any CPU"
+ - name: Upload Release build
+ uses: actions/upload-artifact@v4
+ with:
+ name: ToastFish-Release
+ path: bin/Release/**
From de06c5e4ab891d1cfc6c4155a166d11e0826eb8c Mon Sep 17 00:00:00 2001
From: jasonliu1199 <114725833+jasonliu1199@users.noreply.github.com>
Date: Sat, 13 Jun 2026 00:32:52 +0800
Subject: [PATCH 3/3] fix: harden import and learning startup flows
---
Model/Log/CreateLog.cs | 128 +++++++++++++++++++++-------------
Model/SqliteControl/Select.cs | 2 +-
View/ToastFish.xaml.cs | 24 ++++---
3 files changed, 97 insertions(+), 57 deletions(-)
diff --git a/Model/Log/CreateLog.cs b/Model/Log/CreateLog.cs
index 58e9a9f..1048f58 100644
--- a/Model/Log/CreateLog.cs
+++ b/Model/Log/CreateLog.cs
@@ -72,7 +72,7 @@ public void OutputExcel(String Path, object ObjList, String Type)
else
{
// 自定义
- FirstLine = new List { "自定义", "第一行", "第二行", "第三行" };
+ FirstLine = new List { "自定义", "第一行", "第二行", "第三行", "第四行" };
}
row = sheet.CreateRow(0);
for (int i = 0; i < FirstLine.Count; i++)
@@ -96,36 +96,45 @@ public object ImportExcel(string path)
List WordList = new List();
List JpWordList = new List();
List CustWordList = new List();
- IWorkbook WorkBook;
+ IWorkbook WorkBook = null;
try
{
- FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
-
- // Try to read WorkBook as XLSX:
- try
- {
- WorkBook = new XSSFWorkbook(fs);
- }
- catch
+ using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
- WorkBook = null;
- }
+ // Try to read WorkBook as XLSX:
+ try
+ {
+ WorkBook = new XSSFWorkbook(fs);
+ }
+ catch
+ {
+ WorkBook = null;
+ }
- // If reading fails, try to read WorkBook as XLS:
- if (WorkBook == null)
- {
- WorkBook = new HSSFWorkbook(fs);
+ // If reading fails, try to read WorkBook as XLS:
+ if (WorkBook == null)
+ {
+ fs.Position = 0;
+ WorkBook = new HSSFWorkbook(fs);
+ }
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Excel read error", MessageBoxButton.OK, MessageBoxImage.Error);
- return WordList;
+ return null;
}
ISheet Sheet = WorkBook.GetSheetAt(0);
+ if (Sheet == null)
+ {
+ MessageBox.Show("导入失败,Excel文件没有可读取的工作表");
+ return null;
+ }
+
IRow FirstRow = Sheet.GetRow(0);
- if(FirstRow.GetCell(0).ToString() == "英语")
+ string importType = GetCellValue(FirstRow, 0);
+ if(importType == "英语")
{
int RowCount = Sheet.LastRowNum;
for (int i = 1; i <= RowCount; i++)
@@ -134,58 +143,69 @@ public object ImportExcel(string path)
Word TempWord = new Word();
if (Row == null)
continue;
- TempWord.headWord = Row.GetCell(1).ToString();
- TempWord.tranCN = Row.GetCell(2).ToString();
- TempWord.ukPhone = Row.GetCell(3).ToString();
- TempWord.usPhone = Row.GetCell(4).ToString();
- TempWord.pos = Row.GetCell(5).ToString();
- TempWord.phrase = Row.GetCell(6).ToString();
- TempWord.phraseCN = Row.GetCell(7).ToString();
- TempWord.sentence = Row.GetCell(8).ToString();
- TempWord.sentenceCN = Row.GetCell(9).ToString();
- TempWord.question = Row.GetCell(10).ToString();
- TempWord.explain = Row.GetCell(11).ToString();
- TempWord.choiceIndexOne = Row.GetCell(12).ToString();
- TempWord.choiceIndexTwo = Row.GetCell(13).ToString();
- TempWord.choiceIndexThree = Row.GetCell(14).ToString();
- TempWord.choiceIndexFour = Row.GetCell(15).ToString();
- TempWord.rightIndex = Row.GetCell(16).ToString();
+ TempWord.headWord = GetCellValue(Row, 1);
+ TempWord.tranCN = GetCellValue(Row, 2);
+ TempWord.ukPhone = GetCellValue(Row, 3);
+ TempWord.usPhone = GetCellValue(Row, 4);
+ TempWord.pos = GetCellValue(Row, 5);
+ TempWord.phrase = GetCellValue(Row, 6);
+ TempWord.phraseCN = GetCellValue(Row, 7);
+ TempWord.sentence = GetCellValue(Row, 8);
+ TempWord.sentenceCN = GetCellValue(Row, 9);
+ TempWord.question = GetCellValue(Row, 10);
+ TempWord.explain = GetCellValue(Row, 11);
+ TempWord.choiceIndexOne = GetCellValue(Row, 12);
+ TempWord.choiceIndexTwo = GetCellValue(Row, 13);
+ TempWord.choiceIndexThree = GetCellValue(Row, 14);
+ TempWord.choiceIndexFour = GetCellValue(Row, 15);
+ TempWord.rightIndex = GetCellValue(Row, 16);
+ if (string.IsNullOrWhiteSpace(TempWord.headWord))
+ continue;
WordList.Add(TempWord);
}
return WordList;
}
- else if (FirstRow.GetCell(0).ToString() == "日语")
+ else if (importType == "日语")
{
int RowCount = Sheet.LastRowNum;
- for (int i = 1; i < RowCount; i++)
+ for (int i = 1; i <= RowCount; i++)
{
IRow Row = Sheet.GetRow(i);
JpWord TempWord = new JpWord();
if (Row == null)
continue;
- TempWord.headWord = Row.GetCell(1).ToString();
- TempWord.tranCN = Row.GetCell(2).ToString();
- TempWord.Phone = int.Parse(Row.GetCell(3).ToString());
- TempWord.hiragana = Row.GetCell(4).ToString();
- TempWord.pos = Row.GetCell(5).ToString();
+ TempWord.headWord = GetCellValue(Row, 1);
+ TempWord.tranCN = GetCellValue(Row, 2);
+ int phone;
+ TempWord.Phone = int.TryParse(GetCellValue(Row, 3), out phone) ? phone : 0;
+ TempWord.hiragana = GetCellValue(Row, 4);
+ TempWord.pos = GetCellValue(Row, 5);
+ if (string.IsNullOrWhiteSpace(TempWord.headWord))
+ continue;
JpWordList.Add(TempWord);
}
return JpWordList;
}
- else if (FirstRow.GetCell(0).ToString() == "自定义")
+ else if (importType == "自定义")
{
int RowCount = Sheet.LastRowNum;
- int CellCount = FirstRow.LastCellNum;
- for (int i = 1; i < RowCount; i++)
+ for (int i = 1; i <= RowCount; i++)
{
IRow Row = Sheet.GetRow(i);
CustomizeWord TempWord = new CustomizeWord();
if (Row == null)
continue;
- TempWord.firstLine = Row.GetCell(1).ToString();
- TempWord.secondLine = Row.GetCell(2).ToString();
- TempWord.thirdLine = Row.GetCell(3).ToString();
- TempWord.fourthLine = Row.GetCell(4).ToString();
+ TempWord.firstLine = GetCellValue(Row, 1);
+ TempWord.secondLine = GetCellValue(Row, 2);
+ TempWord.thirdLine = GetCellValue(Row, 3);
+ TempWord.fourthLine = GetCellValue(Row, 4);
+ if (string.IsNullOrWhiteSpace(TempWord.firstLine)
+ && string.IsNullOrWhiteSpace(TempWord.secondLine)
+ && string.IsNullOrWhiteSpace(TempWord.thirdLine)
+ && string.IsNullOrWhiteSpace(TempWord.fourthLine))
+ {
+ continue;
+ }
CustWordList.Add(TempWord);
}
return CustWordList;
@@ -196,5 +216,17 @@ public object ImportExcel(string path)
return null;
}
}
+
+ private string GetCellValue(IRow row, int cellIndex)
+ {
+ if (row == null)
+ return string.Empty;
+
+ ICell cell = row.GetCell(cellIndex);
+ if (cell == null)
+ return string.Empty;
+
+ return cell.ToString().Trim();
+ }
}
}
diff --git a/Model/SqliteControl/Select.cs b/Model/SqliteControl/Select.cs
index 0d2a685..2ec2f2f 100644
--- a/Model/SqliteControl/Select.cs
+++ b/Model/SqliteControl/Select.cs
@@ -94,7 +94,7 @@ public void UpdateTableCount()
public void UpdateCount()
{
BookCount Temp = new BookCount();
- CountList = DataBase.Query($"select * from Count where bookName = {TABLE_NAME}", Temp);
+ CountList = DataBase.Query($"select * from Count where bookName = '{TABLE_NAME}'", Temp);
var CountArray = CountList.ToArray();
foreach (var OneCount in CountArray)
{
diff --git a/View/ToastFish.xaml.cs b/View/ToastFish.xaml.cs
index 1fee98e..febd4f8 100644
--- a/View/ToastFish.xaml.cs
+++ b/View/ToastFish.xaml.cs
@@ -41,6 +41,7 @@ public partial class MainWindow : Window
//HotKey _hotKey0, _hotKey1, _hotKey2, _hotKey3, _hotKey4;
public MainWindow()
{
+ EnsureLogDirectory();
Form_Load();
InitializeComponent();
DataContext = Vm;
@@ -344,10 +345,7 @@ private void NotifyIconDoubleClick(object sender, EventArgs e)
private void Begin_Click(object sender, EventArgs e)
{
- if (!System.IO.Directory.Exists("Log")) {
- System.IO.Directory.CreateDirectory("Log");
- }
- // System.IO.Directory.CreateDirectory("Log");
+ EnsureLogDirectory();
var state = thread.ThreadState;
@@ -415,6 +413,11 @@ private void ImportWords_Click(object sender, EventArgs e)
WordType Words = new WordType();
Words.Number = Select.WORD_NUMBER;
object lstObj = Log.ImportExcel(FileName);
+ if (lstObj == null)
+ {
+ System.Windows.Forms.MessageBox.Show("导入文件出错!");
+ return;
+ }
string typeObj = lstObj.ToString();
string typeWord= typeof(List).ToString();
string typeJpWord = typeof(List).ToString();
@@ -447,9 +450,7 @@ private void ImportWords_Click(object sender, EventArgs e)
return;
}
- if (!Directory.Exists("Log")){
- System.IO.Directory.CreateDirectory("Log");
- }
+ EnsureLogDirectory();
var state = thread.ThreadState;
@@ -712,6 +713,14 @@ private void ExitApp_Click(object sender, EventArgs e)
Environment.Exit(0);
}
+ private void EnsureLogDirectory()
+ {
+ if (!Directory.Exists("Log"))
+ {
+ Directory.CreateDirectory("Log");
+ }
+ }
+
private void Start_Click(object sender, EventArgs e)
{
//StartWithWindows.SetMeStart(true);
@@ -722,4 +731,3 @@ private void Start_Click(object sender, EventArgs e)
#endregion
}
}
-