From 36d8c043fe4cb70eaf7d63c788624d756af2f863 Mon Sep 17 00:00:00 2001 From: JayBridge <12310903@mail.sustech.edu.cn> Date: Sun, 7 Dec 2025 04:53:37 +0800 Subject: [PATCH 1/2] fix: localize notices + restrict undo/redo to active view; add history manager i18n and tests --- src/i18n/en.ts | 15 ++++++++++- src/i18n/zh-cn.ts | 15 ++++++++++- src/utils/csv-utils.ts | 4 +-- src/utils/history-manager.ts | 9 ++++--- src/utils/table-utils.ts | 5 ++-- src/view.ts | 3 +++ test/history-manager.test.ts | 50 ++++++++++++++++++++++++++++++++++++ 7 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 test/history-manager.test.ts diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 55126f6..b28b551 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -15,7 +15,8 @@ export const enUS = { }, csv: { error: 'Error', - parsingFailed: 'Failed to parse CSV. Please check file format.' + parsingFailed: 'Failed to parse CSV. Please check file format.', + parseWarning: 'CSV parse warning:' }, settings: { fieldSeparator: 'Field Separator', @@ -45,4 +46,16 @@ export const enUS = { moveColLeft: 'Move column left', moveColRight: 'Move column right', } + , + tableMessages: { + atLeastOneRow: 'At least one row must remain', + atLeastOneColumn: 'At least one column must remain' + } + , + notifications: { + undo: 'Undid last action', + noMoreUndo: 'There is nothing more to undo', + redo: 'Redid action', + noMoreRedo: 'There is nothing more to redo' + } }; diff --git a/src/i18n/zh-cn.ts b/src/i18n/zh-cn.ts index 7375a4a..110bc8b 100644 --- a/src/i18n/zh-cn.ts +++ b/src/i18n/zh-cn.ts @@ -17,7 +17,8 @@ export const zhCN = { }, csv: { error: '错误', - parsingFailed: 'CSV解析失败,请检查文件格式' + parsingFailed: 'CSV解析失败,请检查文件格式', + parseWarning: 'CSV解析提示:' }, settings: { fieldSeparator: '字段分隔符', @@ -47,4 +48,16 @@ export const zhCN = { moveColLeft: '向左移动一列', moveColRight: '向右移动一列', } + , + tableMessages: { + atLeastOneRow: '至少需要保留一行', + atLeastOneColumn: '至少需要保留一列' + } + , + notifications: { + undo: '已撤销上一步操作', + noMoreUndo: '没有更多可撤销的操作', + redo: '已重做操作', + noMoreRedo: '没有更多可重做的操作' + } }; diff --git a/src/utils/csv-utils.ts b/src/utils/csv-utils.ts index 486e099..b6f54ae 100644 --- a/src/utils/csv-utils.ts +++ b/src/utils/csv-utils.ts @@ -114,8 +114,8 @@ export class CSVUtils { const parseResult: any = Papa.parse(csvString, parseConfig as any); if (parseResult.errors && parseResult.errors.length > 0) { - console.warn("CSV解析警告:", parseResult.errors); - new Notice(`CSV解析提示: ${parseResult.errors[0].message}`); + console.warn("CSV parse warnings:", parseResult.errors); + new Notice(`${i18n.t("csv.parseWarning")} ${parseResult.errors[0].message}`); } return parseResult.data as string[][]; diff --git a/src/utils/history-manager.ts b/src/utils/history-manager.ts index 424f83e..2502406 100644 --- a/src/utils/history-manager.ts +++ b/src/utils/history-manager.ts @@ -1,4 +1,5 @@ import { Notice } from "obsidian"; +import { i18n } from "../i18n"; export class HistoryManager { private history: T[] = []; @@ -38,10 +39,10 @@ export class HistoryManager { undo(): T | null { if (this.canUndo()) { this.currentIndex--; - new Notice("已撤销上一步操作"); + new Notice(i18n.t("notifications.undo")); return this.getCurrentState(); } else { - new Notice("没有更多可撤销的操作"); + new Notice(i18n.t("notifications.noMoreUndo")); return null; } } @@ -52,10 +53,10 @@ export class HistoryManager { redo(): T | null { if (this.canRedo()) { this.currentIndex++; - new Notice("已重做操作"); + new Notice(i18n.t("notifications.redo")); return this.getCurrentState(); } else { - new Notice("没有更多可重做的操作"); + new Notice(i18n.t("notifications.noMoreRedo")); return null; } } diff --git a/src/utils/table-utils.ts b/src/utils/table-utils.ts index 940c2fc..1df5e0d 100644 --- a/src/utils/table-utils.ts +++ b/src/utils/table-utils.ts @@ -1,4 +1,5 @@ import { Notice } from "obsidian"; +import { i18n } from "../i18n"; export class TableUtils { /** @@ -42,7 +43,7 @@ export class TableUtils { */ static deleteRow(tableData: string[][]): string[][] { if (tableData.length <= 1) { - new Notice("至少需要保留一行"); + new Notice(i18n.t("tableMessages.atLeastOneRow")); return tableData; } @@ -61,7 +62,7 @@ export class TableUtils { */ static deleteColumn(tableData: string[][]): string[][] { if (!tableData[0] || tableData[0].length <= 1) { - new Notice("至少需要保留一列"); + new Notice(i18n.t("tableMessages.atLeastOneColumn")); return tableData; } diff --git a/src/view.ts b/src/view.ts index 33f51eb..cde404c 100644 --- a/src/view.ts +++ b/src/view.ts @@ -793,6 +793,9 @@ export class CSVView extends TextFileView { document, "keydown", (event: KeyboardEvent) => { + // Only handle undo/redo when this view is the active leaf + if (this.app.workspace.activeLeaf !== this.leaf) return; + // 检测Ctrl+Z (或Mac上的Cmd+Z) if ((event.ctrlKey || event.metaKey) && event.key === "z") { if (event.shiftKey) { diff --git a/test/history-manager.test.ts b/test/history-manager.test.ts new file mode 100644 index 0000000..79821a0 --- /dev/null +++ b/test/history-manager.test.ts @@ -0,0 +1,50 @@ +import { TableHistoryManager } from "../src/utils/history-manager"; +import { Notice } from "obsidian"; +import { i18n } from "../src/i18n"; + +beforeEach(() => { + // reset Notice mock + (Notice as any).mockClear && (Notice as any).mockClear(); +}); + +test('undo on empty history shows noMoreUndo message', () => { + const hm = new TableHistoryManager([["a"]], 10); + // Initially only one state, cannot undo + const res = hm.undo(); + expect(res).toBeNull(); + expect(Notice).toHaveBeenCalledTimes(1); + expect((Notice as any).mock.calls[0][0]).toBe(i18n.t('notifications.noMoreUndo')); +}); + +test('undo when available shows undo message', () => { + const hm = new TableHistoryManager([["a"]], 10); + hm.push([["b"]]); + // Now we can undo + const res = hm.undo(); + expect(res).toEqual([["a"]]); + expect(Notice).toHaveBeenCalledTimes(1); + expect((Notice as any).mock.calls[0][0]).toBe(i18n.t('notifications.undo')); +}); + +test('redo on no-op shows noMoreRedo message', () => { + const hm = new TableHistoryManager([["a"]], 10); + // Nothing to redo + const res = hm.redo(); + expect(res).toBeNull(); + expect(Notice).toHaveBeenCalledTimes(1); + expect((Notice as any).mock.calls[0][0]).toBe(i18n.t('notifications.noMoreRedo')); +}); + +test('redo when available shows redo message', () => { + const hm = new TableHistoryManager([["a"]], 10); + hm.push([["b"]]); + // undo back to first + const u = hm.undo(); + expect(u).toEqual([["a"]]); + // redo + const r = hm.redo(); + expect(r).toEqual([["b"]]); + // Two notices were called (undo, redo) + expect(Notice).toHaveBeenCalledTimes(2); + expect((Notice as any).mock.calls[1][0]).toBe(i18n.t('notifications.redo')); +}); From 27266721e1cde09f8bae7e66bfcba73c2fff499f Mon Sep 17 00:00:00 2001 From: JayBridge <12310903@mail.sustech.edu.cn> Date: Sun, 7 Dec 2025 04:54:26 +0800 Subject: [PATCH 2/2] fix: i18n for parse warnings and table utils; add tests for history manager --- src/utils/csv-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/csv-utils.ts b/src/utils/csv-utils.ts index b6f54ae..12f0fc6 100644 --- a/src/utils/csv-utils.ts +++ b/src/utils/csv-utils.ts @@ -120,8 +120,8 @@ export class CSVUtils { return parseResult.data as string[][]; } catch (error) { - console.error("CSV解析错误:", error); - new Notice(`${i18n.t("csv.error")}: CSV解析失败,请检查文件格式`); + console.error("CSV parse error:", error); + new Notice(i18n.t("csv.parsingFailed")); return [[""]]; } }