diff --git a/src/i18n/en.ts b/src/i18n/en.ts index a2b068e..978b870 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -12,6 +12,13 @@ export const enUS = { placeholder: 'Edit selected cell...' }, csv: { - error: 'Error' + error: 'Error', + parsingFailed: 'Failed to parse CSV. Please check file format.' + }, + settings: { + fieldSeparator: 'Field Separator', + fieldSeparatorDesc: 'The character used to separate fields (e.g., comma, semicolon, tab)', + quoteChar: 'Quote Character', + quoteCharDesc: 'Character used to enclose fields containing special characters' } }; diff --git a/src/i18n/zh-cn.ts b/src/i18n/zh-cn.ts index c8e5d2d..882ca04 100644 --- a/src/i18n/zh-cn.ts +++ b/src/i18n/zh-cn.ts @@ -12,6 +12,13 @@ export const zhCN = { placeholder: '编辑选中单元格...' }, csv: { - error: '错误' + error: '错误', + parsingFailed: 'CSV解析失败,请检查文件格式' + }, + settings: { + fieldSeparator: '字段分隔符', + fieldSeparatorDesc: '用于分隔字段的字符(例如:逗号、分号、制表符)', + quoteChar: '引号字符', + quoteCharDesc: '用于包围含有特殊字符的字段' } }; diff --git a/src/utils/csv-utils.ts b/src/utils/csv-utils.ts index 8a155f4..0d4e3e5 100644 --- a/src/utils/csv-utils.ts +++ b/src/utils/csv-utils.ts @@ -1,21 +1,25 @@ import * as Papa from 'papaparse'; import { Notice } from 'obsidian'; +import { i18n } from '../i18n'; export interface CSVParseConfig { header: boolean; dynamicTyping: boolean; skipEmptyLines: boolean; delimiter?: string; - quoteChar?: string; + quoteChar: string; + escapeChar: string; } export class CSVUtils { - // 默认解析配置 + // 现在这是默认设置的唯一权威来源 static defaultConfig: CSVParseConfig = { header: false, dynamicTyping: false, skipEmptyLines: false, - quoteChar: '"' + delimiter: ',', + quoteChar: '"', // 关键:这是修复报告bug的关键 + escapeChar: '"', }; /** @@ -34,8 +38,8 @@ export class CSVUtils { return parseResult.data as string[][]; } catch (error) { console.error("CSV解析错误:", error); - new Notice("CSV解析失败,请检查文件格式"); - return [[""]]; // 返回一个空表格 + new Notice(`${i18n.t('csv.error')}: CSV解析失败,请检查文件格式`); + return [[""]]; } } @@ -60,12 +64,14 @@ export class CSVUtils { // 找出最大列数 let maxCols = 0; for (const row of tableData) { - maxCols = Math.max(maxCols, row.length); + if (row) { + maxCols = Math.max(maxCols, row.length); + } } // 确保每行都有相同的列数 const normalizedData = tableData.map(row => { - const newRow = [...row]; + const newRow = row ? [...row] : []; while (newRow.length < maxCols) { newRow.push(""); } diff --git a/src/view.ts b/src/view.ts index 316948f..8272e60 100644 --- a/src/view.ts +++ b/src/view.ts @@ -1,4 +1,4 @@ -import { TextFileView, ButtonComponent, Notice, DropdownComponent, getIcon, IconName } from "obsidian"; +import { TextFileView, ButtonComponent, Notice, DropdownComponent, getIcon, IconName, Setting } from "obsidian"; import { CSVUtils, CSVParseConfig } from './utils/csv-utils'; import { TableHistoryManager } from './utils/history-manager'; import { TableUtils } from './utils/table-utils'; @@ -20,12 +20,9 @@ export class CSVView extends TextFileView { private columnWidths: number[] = []; private autoResize: boolean = true; - // Papa Parse 配置选项 - private papaConfig: CSVParseConfig = { - header: false, - dynamicTyping: false, - skipEmptyLines: false - }; + // 新增:解析器设置状态 + private delimiter: string = ','; + private quoteChar: string = '"'; // 编辑栏 private editBarEl: HTMLElement; @@ -78,8 +75,11 @@ getIcon(): IconName { setViewData(data: string, clear: boolean) { try { - // 使用工具类解析CSV数据 - this.tableData = CSVUtils.parseCSV(data, this.papaConfig); + // 使用新的分隔符设置解析CSV数据 + this.tableData = CSVUtils.parseCSV(data, { + delimiter: this.delimiter, + quoteChar: this.quoteChar + }); // 确保至少有一行一列 if (!this.tableData || this.tableData.length === 0) { @@ -107,6 +107,13 @@ getIcon(): IconName { } } + // 新增:重新解析和刷新视图的方法 + private reparseAndRefresh() { + // 获取原始数据并用新设置重新解析 + const rawData = this.data; + this.setViewData(rawData, false); // 重新运行setViewData但不清除历史 + } + refresh() { // Safety check: ensure tableEl exists if (!this.tableEl) { @@ -352,6 +359,34 @@ getIcon(): IconName { // 创建操作区 this.operationEl = this.contentEl.createEl("div", { cls: "csv-operations" }); + // 新增:解析器设置UI + const parserSettingsEl = this.operationEl.createEl("div", { cls: "csv-parser-settings" }); + + new Setting(parserSettingsEl) + .setName("字段分隔符") + .setDesc("用于分隔字段的字符(例如:逗号、分号、制表符)") + .addText(text => { + text.setValue(this.delimiter) + .setPlaceholder("例如:, 或 ; 或 \\t 表示制表符") + .onChange(async (value) => { + // 处理制表符的特殊情况 + this.delimiter = value === '\\t' ? '\t' : value; + this.reparseAndRefresh(); + }); + }); + + new Setting(parserSettingsEl) + .setName("引号字符") + .setDesc("用于包围含有特殊字符的字段") + .addText(text => { + text.setValue(this.quoteChar) + .setPlaceholder('默认为双引号 "') + .onChange(async (value) => { + this.quoteChar = value || '"'; + this.reparseAndRefresh(); + }); + }); + // 创建操作按钮容器 const buttonContainer = this.operationEl.createEl("div", { cls: "csv-operation-buttons" }); @@ -456,8 +491,6 @@ getIcon(): IconName { // 初始化时刷新视图 this.refresh(); - // 添加样式 - // this.addStyles(); } catch (error) { console.error("Error in onOpen:", error); new Notice(`Failed to open CSV view: ${error.message}`); diff --git a/styles.css b/styles.css index f713a43..b5d6b6b 100644 --- a/styles.css +++ b/styles.css @@ -164,4 +164,26 @@ If your plugin does not need CSS, delete this file. color: var(--text-on-accent); } + /* 解析器设置样式 */ +.csv-parser-settings { + margin-bottom: 16px; + padding: 12px; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + background-color: var(--background-secondary); +} + +.csv-parser-settings .setting-item { + border: none; + padding: 8px 0; +} + +.csv-parser-settings .setting-item-info { + margin-right: 16px; +} + +.csv-parser-settings .setting-item-control input { + max-width: 200px; +} +