Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

on:
push:
branches: [ "dev", "master", "main" ]
pull_request:
branches: [ "dev", "master", "main" ]

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [16.x]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Prettier check (if present)
run: |
if npx --no-install prettier --version >/dev/null 2>&1; then
npx prettier --check "**/*.{ts,js,json,md,css}" || (echo "Prettier check failed" && exit 1)
else
echo "Prettier not installed - skipping check"
fi

- name: TypeScript typecheck and build
run: npm run build

- name: Run tests
run: npm test
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "csv-lite",
"name": "CSV Lite",
"version": "1.1.4",
"version": "1.1.5",
"minAppVersion": "1.8.0",
"description": "Just open and edit CSV files directly, no more. Keep it simple.",
"author": "Jay Bridge",
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-csv",
"version": "1.1.4",
"version": "1.1.5",
"description": "CSV viewer and editor for Obsidian",
"main": "main.js",
"scripts": {
Expand Down
15 changes: 14 additions & 1 deletion src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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'
}
};
15 changes: 14 additions & 1 deletion src/i18n/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export const zhCN = {
},
csv: {
error: '错误',
parsingFailed: 'CSV解析失败,请检查文件格式'
parsingFailed: 'CSV解析失败,请检查文件格式',
parseWarning: 'CSV解析提示:'
},
settings: {
fieldSeparator: '字段分隔符',
Expand Down Expand Up @@ -47,4 +48,16 @@ export const zhCN = {
moveColLeft: '向左移动一列',
moveColRight: '向右移动一列',
}
,
tableMessages: {
atLeastOneRow: '至少需要保留一行',
atLeastOneColumn: '至少需要保留一列'
}
,
notifications: {
undo: '已撤销上一步操作',
noMoreUndo: '没有更多可撤销的操作',
redo: '已重做操作',
noMoreRedo: '没有更多可重做的操作'
}
};
8 changes: 4 additions & 4 deletions src/source-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EditorState, Extension, RangeSetBuilder } from "@codemirror/state";
import { EditorView, keymap, placeholder, lineNumbers, drawSelection, Decoration, ViewPlugin, ViewUpdate, DecorationSet } from "@codemirror/view";
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands";

export const VIEW_TYPE_CSV_SOURCE = "csv-source-view";
export const VIEW_TYPE_CSV_SOURCE = "csv-lite-source-view";

// 分隔符高亮插件(逗号、分号、制表符)
const separatorHighlightPlugin = ViewPlugin.fromClass(class {
Expand Down Expand Up @@ -62,7 +62,7 @@ export class SourceView extends TextFileView {
// 1. 在 view header 的 view-actions 区域插入切换按钮(lucide/table 图标)
// 交互说明:
// - 切换按钮始终位于 header 区域,风格与 Obsidian 原生一致。
// - 点击时遍历所有 leaf,查找同一文件的目标视图(csv-view)。
// - 点击时遍历所有 leaf,查找同一文件的目标视图(csv-lite-view)。
// - 若有,则激活该 leaf(workspace.setActiveLeaf)。
// - 若无,则新建 leaf 并打开目标视图。
// - 不主动关闭原有视图,用户可自行关闭。
Expand All @@ -75,7 +75,7 @@ export class SourceView extends TextFileView {
btn.onclick = async () => {
const file = this.file;
if (!file) return;
const leaves = this.app.workspace.getLeavesOfType('csv-view');
const leaves = this.app.workspace.getLeavesOfType('csv-lite-view');
let found = false;
for (const leaf of leaves) {
if (leaf.view && (leaf.view as any).file && (leaf.view as any).file.path === file.path) {
Expand All @@ -88,7 +88,7 @@ export class SourceView extends TextFileView {
const newLeaf = this.app.workspace.getLeaf(true);
await newLeaf.openFile(file, { active: true });
await newLeaf.setViewState({
type: 'csv-view',
type: 'csv-lite-view',
active: true,
state: { file: file.path }
});
Expand Down
8 changes: 4 additions & 4 deletions src/utils/csv-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,14 @@ 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[][];
} 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 [[""]];
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/utils/history-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Notice } from "obsidian";
import { i18n } from "../i18n";

export class HistoryManager<T> {
private history: T[] = [];
Expand Down Expand Up @@ -38,10 +39,10 @@ export class HistoryManager<T> {
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;
}
}
Expand All @@ -52,10 +53,10 @@ export class HistoryManager<T> {
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;
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/utils/table-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Notice } from "obsidian";
import { i18n } from "../i18n";

export class TableUtils {
/**
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}

Expand Down
13 changes: 8 additions & 5 deletions src/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { renderTable } from "./view/table-render";
import { HighlightManager } from "./utils/highlight-manager";
import { setupHeaderContextMenu } from "./view/header-context-menu";

export const VIEW_TYPE_CSV = "csv-view";
export const VIEW_TYPE_CSV = "csv-lite-view";

export class CSVView extends TextFileView {
public file: TFile | null;
Expand Down Expand Up @@ -559,7 +559,7 @@ export class CSVView extends TextFileView {
// 1. 在 view header 的 view-actions 区域插入切换按钮(lucide/file-code 图标)
// 交互说明:
// - 切换按钮始终位于 header 区域,风格与 Obsidian 原生一致。
// - 点击时遍历所有 leaf,查找同一文件的目标视图(csv-source-view)。
// - 点击时遍历所有 leaf,查找同一文件的目标视图(csv-lite-source-view)。
// - 若有,则激活该 leaf(workspace.setActiveLeaf)。
// - 若无,则新建 leaf 并打开目标视图。
// - 不主动关闭原有视图,用户可自行关闭。
Expand All @@ -572,7 +572,7 @@ export class CSVView extends TextFileView {
btn.onclick = async () => {
const file = this.file;
if (!file) return;
const leaves = this.app.workspace.getLeavesOfType('csv-source-view');
const leaves = this.app.workspace.getLeavesOfType('csv-lite-source-view');
let found = false;
for (const leaf of leaves) {
if (leaf.view && (leaf.view as any).file && (leaf.view as any).file.path === file.path) {
Expand All @@ -585,7 +585,7 @@ export class CSVView extends TextFileView {
const newLeaf = this.app.workspace.getLeaf(true);
await newLeaf.openFile(file, { active: true, state: { mode: "source" } });
await newLeaf.setViewState({
type: "csv-source-view",
type: "csv-lite-source-view",
active: true,
state: { file: file.path }
});
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -983,7 +986,7 @@ export class CSVView extends TextFileView {
const leaf = this.app.workspace.getLeaf(true);
await leaf.openFile(file, { active: true, state: { mode: "source" } });
await leaf.setViewState({
type: "csv-source-view",
type: "csv-lite-source-view",
active: true,
state: { file: file.path }
});
Expand Down
50 changes: 50 additions & 0 deletions test/history-manager.test.ts
Original file line number Diff line number Diff line change
@@ -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'));
});
3 changes: 2 additions & 1 deletion versions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"1.0.0": "0.15.0"
"1.0.0": "0.15.0",
"1.1.5": "1.8.0"
}