diff --git a/src/main.ts b/src/main.ts index 499b0d5..fbe39c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,48 +1,55 @@ -import { Plugin, WorkspaceLeaf, moment } from 'obsidian'; +import { Plugin, WorkspaceLeaf, moment } from "obsidian"; import { CSVView, VIEW_TYPE_CSV } from "./view"; -import { i18n } from './i18n'; +import { i18n } from "./i18n"; interface CSVPluginSettings { - csvSettings: string; + csvSettings: string; } const DEFAULT_SETTINGS: CSVPluginSettings = { - csvSettings: 'default' -} + csvSettings: "default", +}; export default class CSVPlugin extends Plugin { - settings: CSVPluginSettings; + settings: CSVPluginSettings; - async onload() { - await this.loadSettings(); + async onload() { + await this.loadSettings(); - // 使用 moment.locale() 来安全地获取 Obsidian 的当前语言设置 - const obsidianLang = moment.locale(); - i18n.setLocale(obsidianLang); - console.log(`CSV Plugin: Setting locale to '${obsidianLang}'`); - console.log(`CSV Plugin: Test translation - buttons.undo: '${i18n.t('buttons.undo')}'`); + // 使用 moment.locale() 来安全地获取 Obsidian 的当前语言设置 + const obsidianLang = moment.locale(); + i18n.setLocale(obsidianLang); + console.log(`CSV Plugin: Setting locale to '${obsidianLang}'`); + console.log( + `CSV Plugin: Test translation - buttons.undo: '${i18n.t( + "buttons.undo" + )}'` + ); - // 注册CSV视图类型 - this.registerView( - VIEW_TYPE_CSV, - (leaf: WorkspaceLeaf) => new CSVView(leaf) - ); + // 注册CSV视图类型 + this.registerView( + VIEW_TYPE_CSV, + (leaf: WorkspaceLeaf) => new CSVView(leaf) + ); - // 将.csv文件扩展名与视图类型绑定 - this.registerExtensions(["csv"], VIEW_TYPE_CSV); + // 将.csv文件扩展名与视图类型绑定 + this.registerExtensions(["csv"], VIEW_TYPE_CSV); + } - } + onunload() { + // 移除视图 + // this.app.workspace.detachLeavesOfType(VIEW_TYPE_CSV); // 删除此行 + } - onunload() { - // 移除视图 - // this.app.workspace.detachLeavesOfType(VIEW_TYPE_CSV); // 删除此行 - } + async loadSettings() { + this.settings = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ); + } - async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); - } - - async saveSettings() { - await this.saveData(this.settings); - } + async saveSettings() { + await this.saveData(this.settings); + } } diff --git a/src/utils/csv-utils.ts b/src/utils/csv-utils.ts index 0d4e3e5..bc64693 100644 --- a/src/utils/csv-utils.ts +++ b/src/utils/csv-utils.ts @@ -1,83 +1,86 @@ -import * as Papa from 'papaparse'; -import { Notice } from 'obsidian'; -import { i18n } from '../i18n'; +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; - escapeChar: string; + header: boolean; + dynamicTyping: boolean; + skipEmptyLines: boolean; + delimiter?: string; + quoteChar: string; + escapeChar: string; } export class CSVUtils { - // 现在这是默认设置的唯一权威来源 - static defaultConfig: CSVParseConfig = { - header: false, - dynamicTyping: false, - skipEmptyLines: false, - delimiter: ',', - quoteChar: '"', // 关键:这是修复报告bug的关键 - escapeChar: '"', - }; + // 现在这是默认设置的唯一权威来源 + static defaultConfig: CSVParseConfig = { + header: false, + dynamicTyping: false, + skipEmptyLines: false, + delimiter: ",", + quoteChar: '"', // 关键:这是修复报告bug的关键 + escapeChar: '"', + }; - /** - * 解析CSV字符串为二维数组 - */ - static parseCSV(csvString: string, config?: Partial): string[][] { - try { - const parseConfig = { ...this.defaultConfig, ...config }; - const parseResult = Papa.parse(csvString, parseConfig); - - if (parseResult.errors && parseResult.errors.length > 0) { - console.warn("CSV解析警告:", parseResult.errors); - new Notice(`CSV解析提示: ${parseResult.errors[0].message}`); - } - - return parseResult.data as string[][]; - } catch (error) { - console.error("CSV解析错误:", error); - new Notice(`${i18n.t('csv.error')}: CSV解析失败,请检查文件格式`); - return [[""]]; - } - } + /** + * 解析CSV字符串为二维数组 + */ + static parseCSV( + csvString: string, + config?: Partial + ): string[][] { + try { + const parseConfig = { ...this.defaultConfig, ...config }; + const parseResult = Papa.parse(csvString, parseConfig); - /** - * 将二维数组转换为CSV字符串 - */ - static unparseCSV(data: string[][], config?: Papa.UnparseConfig): string { - const defaultUnparseConfig = { - header: false, - newline: "\n" - }; - - return Papa.unparse(data, { ...defaultUnparseConfig, ...config }); - } + if (parseResult.errors && parseResult.errors.length > 0) { + console.warn("CSV解析警告:", parseResult.errors); + new Notice(`CSV解析提示: ${parseResult.errors[0].message}`); + } - /** - * 确保表格数据规整(所有行的列数相同) - */ - static normalizeTableData(tableData: string[][]): string[][] { - if (!tableData || tableData.length === 0) return [[""]]; - - // 找出最大列数 - let maxCols = 0; - for (const row of tableData) { - if (row) { - maxCols = Math.max(maxCols, row.length); - } - } - - // 确保每行都有相同的列数 - const normalizedData = tableData.map(row => { - const newRow = row ? [...row] : []; - while (newRow.length < maxCols) { - newRow.push(""); - } - return newRow; - }); - - return normalizedData; - } + return parseResult.data as string[][]; + } catch (error) { + console.error("CSV解析错误:", error); + new Notice(`${i18n.t("csv.error")}: CSV解析失败,请检查文件格式`); + return [[""]]; + } + } + + /** + * 将二维数组转换为CSV字符串 + */ + static unparseCSV(data: string[][], config?: Papa.UnparseConfig): string { + const defaultUnparseConfig = { + header: false, + newline: "\n", + }; + + return Papa.unparse(data, { ...defaultUnparseConfig, ...config }); + } + + /** + * 确保表格数据规整(所有行的列数相同) + */ + static normalizeTableData(tableData: string[][]): string[][] { + if (!tableData || tableData.length === 0) return [[""]]; + + // 找出最大列数 + let maxCols = 0; + for (const row of tableData) { + if (row) { + maxCols = Math.max(maxCols, row.length); + } + } + + // 确保每行都有相同的列数 + const normalizedData = tableData.map((row) => { + const newRow = row ? [...row] : []; + while (newRow.length < maxCols) { + newRow.push(""); + } + return newRow; + }); + + return normalizedData; + } } diff --git a/src/utils/file-utils.ts b/src/utils/file-utils.ts index ca634a9..f7e6e9e 100644 --- a/src/utils/file-utils.ts +++ b/src/utils/file-utils.ts @@ -1,50 +1,54 @@ -import { Notice } from 'obsidian'; +import { Notice } from "obsidian"; export class FileUtils { - /** - * Attempts to perform a file operation with retry logic - * @param operation The file operation function to execute - * @param maxRetries Maximum number of retry attempts - * @param delayMs Delay between retries in milliseconds - * @returns Promise resolving to the operation result or rejecting with an error - */ - static async withRetry( - operation: () => Promise, - maxRetries: number = 3, - delayMs: number = 500 - ): Promise { - let lastError: Error = new Error("Unknown error occurred"); + /** + * Attempts to perform a file operation with retry logic + * @param operation The file operation function to execute + * @param maxRetries Maximum number of retry attempts + * @param delayMs Delay between retries in milliseconds + * @returns Promise resolving to the operation result or rejecting with an error + */ + static async withRetry( + operation: () => Promise, + maxRetries: number = 3, + delayMs: number = 500 + ): Promise { + let lastError: Error = new Error("Unknown error occurred"); - for (let attempt = 0; attempt <= maxRetries; attempt++) { - try { - // Attempt the operation - return await operation(); - } catch (error) { - lastError = error as Error; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + // Attempt the operation + return await operation(); + } catch (error) { + lastError = error as Error; - // Check if this is a "file busy" error - const isFileBusyError = - error instanceof Error && - (error.message.includes('EBUSY') || - error.message.includes('busy') || - error.message.includes('locked')); + // Check if this is a "file busy" error + const isFileBusyError = + error instanceof Error && + (error.message.includes("EBUSY") || + error.message.includes("busy") || + error.message.includes("locked")); - // If it's not a file busy error or we've used all retries, throw the error - if (!isFileBusyError || attempt === maxRetries) { - break; - } + // If it's not a file busy error or we've used all retries, throw the error + if (!isFileBusyError || attempt === maxRetries) { + break; + } - // Show a notice on first retry - if (attempt === 0) { - new Notice(`File is busy. Retrying... (${attempt + 1}/${maxRetries})`); - } + // Show a notice on first retry + if (attempt === 0) { + new Notice( + `File is busy. Retrying... (${ + attempt + 1 + }/${maxRetries})` + ); + } - // Wait before retrying - await new Promise(resolve => setTimeout(resolve, delayMs)); - } - } + // Wait before retrying + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } + } - // If we get here, all retries failed - throw lastError; - } + // If we get here, all retries failed + throw lastError; + } } diff --git a/src/utils/history-manager.ts b/src/utils/history-manager.ts index 6a11fab..424f83e 100644 --- a/src/utils/history-manager.ts +++ b/src/utils/history-manager.ts @@ -1,127 +1,127 @@ -import { Notice } from 'obsidian'; +import { Notice } from "obsidian"; export class HistoryManager { - private history: T[] = []; - private currentIndex: number = -1; - private maxSize: number; - - constructor(initialState?: T, maxSize: number = 50) { - this.maxSize = maxSize; - if (initialState) { - this.push(initialState); - } - } - - /** - * 保存新状态到历史记录 - */ - push(state: T): void { - // 删除当前索引之后的所有历史记录 - if (this.currentIndex < this.history.length - 1) { - this.history = this.history.slice(0, this.currentIndex + 1); - } - - // 添加新状态 - this.history.push(this.cloneState(state)); - - // 如果历史记录超过最大限制,删除最早的记录 - if (this.history.length > this.maxSize) { - this.history.shift(); - } else { - this.currentIndex++; - } - } - - /** - * 撤销到上一个状态 - */ - undo(): T | null { - if (this.canUndo()) { - this.currentIndex--; - new Notice("已撤销上一步操作"); - return this.getCurrentState(); - } else { - new Notice("没有更多可撤销的操作"); - return null; - } - } - - /** - * 重做到下一个状态 - */ - redo(): T | null { - if (this.canRedo()) { - this.currentIndex++; - new Notice("已重做操作"); - return this.getCurrentState(); - } else { - new Notice("没有更多可重做的操作"); - return null; - } - } - - /** - * 获取当前状态 - */ - getCurrentState(): T | null { - if (this.currentIndex >= 0 && this.currentIndex < this.history.length) { - return this.cloneState(this.history[this.currentIndex]); - } - return null; - } - - /** - * 是否可以撤销 - */ - canUndo(): boolean { - return this.currentIndex > 0; - } - - /** - * 是否可以重做 - */ - canRedo(): boolean { - return this.currentIndex < this.history.length - 1; - } - - /** - * 重置历史记录 - */ - reset(initialState?: T): void { - this.history = []; - this.currentIndex = -1; - if (initialState) { - this.push(initialState); - } - } - - /** - * 克隆状态(泛型方法,需要在使用时重写) - */ - protected cloneState(state: T): T { - // 默认实现,应在子类中重写 - if (Array.isArray(state)) { - // 处理二维数组情况 - if (state.length > 0 && Array.isArray(state[0])) { - return state.map(row => [...row]) as unknown as T; - } - return [...state] as unknown as T; - } - - // 对象类型尝试深拷贝 - if (typeof state === 'object' && state !== null) { - return JSON.parse(JSON.stringify(state)); - } - - return state; - } + private history: T[] = []; + private currentIndex: number = -1; + private maxSize: number; + + constructor(initialState?: T, maxSize: number = 50) { + this.maxSize = maxSize; + if (initialState) { + this.push(initialState); + } + } + + /** + * 保存新状态到历史记录 + */ + push(state: T): void { + // 删除当前索引之后的所有历史记录 + if (this.currentIndex < this.history.length - 1) { + this.history = this.history.slice(0, this.currentIndex + 1); + } + + // 添加新状态 + this.history.push(this.cloneState(state)); + + // 如果历史记录超过最大限制,删除最早的记录 + if (this.history.length > this.maxSize) { + this.history.shift(); + } else { + this.currentIndex++; + } + } + + /** + * 撤销到上一个状态 + */ + undo(): T | null { + if (this.canUndo()) { + this.currentIndex--; + new Notice("已撤销上一步操作"); + return this.getCurrentState(); + } else { + new Notice("没有更多可撤销的操作"); + return null; + } + } + + /** + * 重做到下一个状态 + */ + redo(): T | null { + if (this.canRedo()) { + this.currentIndex++; + new Notice("已重做操作"); + return this.getCurrentState(); + } else { + new Notice("没有更多可重做的操作"); + return null; + } + } + + /** + * 获取当前状态 + */ + getCurrentState(): T | null { + if (this.currentIndex >= 0 && this.currentIndex < this.history.length) { + return this.cloneState(this.history[this.currentIndex]); + } + return null; + } + + /** + * 是否可以撤销 + */ + canUndo(): boolean { + return this.currentIndex > 0; + } + + /** + * 是否可以重做 + */ + canRedo(): boolean { + return this.currentIndex < this.history.length - 1; + } + + /** + * 重置历史记录 + */ + reset(initialState?: T): void { + this.history = []; + this.currentIndex = -1; + if (initialState) { + this.push(initialState); + } + } + + /** + * 克隆状态(泛型方法,需要在使用时重写) + */ + protected cloneState(state: T): T { + // 默认实现,应在子类中重写 + if (Array.isArray(state)) { + // 处理二维数组情况 + if (state.length > 0 && Array.isArray(state[0])) { + return state.map((row) => [...row]) as unknown as T; + } + return [...state] as unknown as T; + } + + // 对象类型尝试深拷贝 + if (typeof state === "object" && state !== null) { + return JSON.parse(JSON.stringify(state)); + } + + return state; + } } /** * 特化版本,专门用于处理CSV表格数据(二维字符串数组) */ export class TableHistoryManager extends HistoryManager { - protected cloneState(state: string[][]): string[][] { - return state.map(row => [...row]); - } + protected cloneState(state: string[][]): string[][] { + return state.map((row) => [...row]); + } } diff --git a/src/utils/table-utils.ts b/src/utils/table-utils.ts index d020cc9..953b021 100644 --- a/src/utils/table-utils.ts +++ b/src/utils/table-utils.ts @@ -1,88 +1,94 @@ -import { Notice } from 'obsidian'; +import { Notice } from "obsidian"; export class TableUtils { - /** - * 计算表格列宽 - */ - static calculateColumnWidths(tableData: string[][]): number[] { - if (!tableData || tableData.length === 0 || !tableData[0]) return []; - - // 初始化所有列为默认宽度 - const columnWidths = tableData[0].map(() => 100); - - // 根据内容长度进行简单调整 - tableData.forEach(row => { - row.forEach((cell, index) => { - // 根据内容长度估算合适的宽度 - const estimatedWidth = Math.max(50, Math.min(300, cell.length * 10)); - columnWidths[index] = Math.max(columnWidths[index], estimatedWidth); - }); - }); - - return columnWidths; - } - - /** - * 添加新行 - */ - static addRow(tableData: string[][]): string[][] { - const colCount = tableData.length > 0 ? tableData[0].length : 1; - const newRow = Array(colCount).fill(""); - return [...tableData, newRow]; - } - - /** - * 删除最后一行 - */ - static deleteRow(tableData: string[][]): string[][] { - if (tableData.length <= 1) { - new Notice("至少需要保留一行"); - return tableData; - } - - return tableData.slice(0, -1); - } - - /** - * 添加新列 - */ - static addColumn(tableData: string[][]): string[][] { - return tableData.map(row => [...row, ""]); - } - - /** - * 删除最后一列 - */ - static deleteColumn(tableData: string[][]): string[][] { - if (!tableData[0] || tableData[0].length <= 1) { - new Notice("至少需要保留一列"); - return tableData; - } - - return tableData.map(row => row.slice(0, -1)); - } - - /** - * 获取列标签 (A, B, C, ..., Z, AA, AB, ...) - */ - static getColumnLabel(index: number): string { - let label = ''; - let n = index; - - while (n >= 0) { - label = String.fromCharCode(65 + (n % 26)) + label; - n = Math.floor(n / 26) - 1; - } - - return label; - } - - /** - * 获取单元格地址标识(例如A1, B2) - */ - static getCellAddress(rowIndex: number, colIndex: number): string { - const colAddress = this.getColumnLabel(colIndex); - const rowAddress = rowIndex + 1; - return `${colAddress}${rowAddress}`; - } + /** + * 计算表格列宽 + */ + static calculateColumnWidths(tableData: string[][]): number[] { + if (!tableData || tableData.length === 0 || !tableData[0]) return []; + + // 初始化所有列为默认宽度 + const columnWidths = tableData[0].map(() => 100); + + // 根据内容长度进行简单调整 + tableData.forEach((row) => { + row.forEach((cell, index) => { + // 根据内容长度估算合适的宽度 + const estimatedWidth = Math.max( + 50, + Math.min(300, cell.length * 10) + ); + columnWidths[index] = Math.max( + columnWidths[index], + estimatedWidth + ); + }); + }); + + return columnWidths; + } + + /** + * 添加新行 + */ + static addRow(tableData: string[][]): string[][] { + const colCount = tableData.length > 0 ? tableData[0].length : 1; + const newRow = Array(colCount).fill(""); + return [...tableData, newRow]; + } + + /** + * 删除最后一行 + */ + static deleteRow(tableData: string[][]): string[][] { + if (tableData.length <= 1) { + new Notice("至少需要保留一行"); + return tableData; + } + + return tableData.slice(0, -1); + } + + /** + * 添加新列 + */ + static addColumn(tableData: string[][]): string[][] { + return tableData.map((row) => [...row, ""]); + } + + /** + * 删除最后一列 + */ + static deleteColumn(tableData: string[][]): string[][] { + if (!tableData[0] || tableData[0].length <= 1) { + new Notice("至少需要保留一列"); + return tableData; + } + + return tableData.map((row) => row.slice(0, -1)); + } + + /** + * 获取列标签 (A, B, C, ..., Z, AA, AB, ...) + */ + static getColumnLabel(index: number): string { + let label = ""; + let n = index; + + while (n >= 0) { + label = String.fromCharCode(65 + (n % 26)) + label; + n = Math.floor(n / 26) - 1; + } + + return label; + } + + /** + * 获取单元格地址标识(例如A1, B2) + */ + static getCellAddress(rowIndex: number, colIndex: number): string { + const colAddress = this.getColumnLabel(colIndex); + const rowAddress = rowIndex + 1; + return `${colAddress}${rowAddress}`; + } } diff --git a/src/view.ts b/src/view.ts index 8272e60..985df0e 100644 --- a/src/view.ts +++ b/src/view.ts @@ -1,552 +1,623 @@ -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'; -import { FileUtils } from './utils/file-utils'; -import { i18n } from './i18n'; // 修正导入路径 +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"; +import { FileUtils } from "./utils/file-utils"; +import { i18n } from "./i18n"; // 修正导入路径 export const VIEW_TYPE_CSV = "csv-view"; export class CSVView extends TextFileView { - tableData: string[][] = [[""]]; - tableEl: HTMLElement; - operationEl: HTMLElement; + tableData: string[][] = [[""]]; + tableEl: HTMLElement; + operationEl: HTMLElement; - // 使用新的历史记录管理器 - private historyManager: TableHistoryManager; - private maxHistorySize: number = 50; + // 使用新的历史记录管理器 + private historyManager: TableHistoryManager; + private maxHistorySize: number = 50; - // 列宽调整设置 - private columnWidths: number[] = []; - private autoResize: boolean = true; + // 列宽调整设置 + private columnWidths: number[] = []; + private autoResize: boolean = true; - // 新增:解析器设置状态 - private delimiter: string = ','; - private quoteChar: string = '"'; + // 新增:解析器设置状态 + private delimiter: string = ","; + private quoteChar: string = '"'; - // 编辑栏 - private editBarEl: HTMLElement; - private editInput: HTMLInputElement; - private activeCellEl: HTMLInputElement | null = null; - private activeRowIndex: number = -1; - private activeColIndex: number = -1; + // 编辑栏 + private editBarEl: HTMLElement; + private editInput: HTMLInputElement; + private activeCellEl: HTMLInputElement | null = null; + private activeRowIndex: number = -1; + private activeColIndex: number = -1; - constructor(leaf: any) { - super(leaf); - this.historyManager = new TableHistoryManager(undefined, this.maxHistorySize); + constructor(leaf: any) { + super(leaf); + this.historyManager = new TableHistoryManager( + undefined, + this.maxHistorySize + ); - // Setup safe save method with retry logic - this.setupSafeSave(); - } + // Setup safe save method with retry logic + this.setupSafeSave(); + } -getIcon(): IconName { - return "table"; -} - getViewData() { - return CSVUtils.unparseCSV(this.tableData); - } + getIcon(): IconName { + return "table"; + } + getViewData() { + return CSVUtils.unparseCSV(this.tableData); + } - // We need to create a wrapper for the original requestSave - private originalRequestSave: () => void; + // We need to create a wrapper for the original requestSave + private originalRequestSave: () => void; - /** - * Setup safe save method with retry logic - */ - private setupSafeSave() { - // Store the original requestSave function - this.originalRequestSave = this.requestSave; + /** + * Setup safe save method with retry logic + */ + private setupSafeSave() { + // Store the original requestSave function + this.originalRequestSave = this.requestSave; - // Replace with our version that includes retry logic - this.requestSave = async () => { - try { - // Use our retry utility to handle file busy errors - await FileUtils.withRetry(async () => { - // Call the original requestSave method - this.originalRequestSave(); - // Return a resolved promise to satisfy the async function - return Promise.resolve(); - }); - } catch (error) { - console.error("Failed to save CSV file after retries:", error); - new Notice(`Failed to save file: ${error.message}. The file might be open in another program.`); - } - }; - } + // Replace with our version that includes retry logic + this.requestSave = async () => { + try { + // Use our retry utility to handle file busy errors + await FileUtils.withRetry(async () => { + // Call the original requestSave method + this.originalRequestSave(); + // Return a resolved promise to satisfy the async function + return Promise.resolve(); + }); + } catch (error) { + console.error("Failed to save CSV file after retries:", error); + new Notice( + `Failed to save file: ${error.message}. The file might be open in another program.` + ); + } + }; + } - setViewData(data: string, clear: boolean) { - try { - // 使用新的分隔符设置解析CSV数据 - this.tableData = CSVUtils.parseCSV(data, { - delimiter: this.delimiter, - quoteChar: this.quoteChar - }); + setViewData(data: string, clear: boolean) { + try { + // 使用新的分隔符设置解析CSV数据 + this.tableData = CSVUtils.parseCSV(data, { + delimiter: this.delimiter, + quoteChar: this.quoteChar, + }); - // 确保至少有一行一列 - if (!this.tableData || this.tableData.length === 0) { - this.tableData = [[""]]; - } + // 确保至少有一行一列 + if (!this.tableData || this.tableData.length === 0) { + this.tableData = [[""]]; + } - // 使所有行的列数一致 - this.tableData = CSVUtils.normalizeTableData(this.tableData); + // 使所有行的列数一致 + this.tableData = CSVUtils.normalizeTableData(this.tableData); - // 初始化历史记录 - if (clear) { - this.historyManager.reset(this.tableData); - } + // 初始化历史记录 + if (clear) { + this.historyManager.reset(this.tableData); + } - this.refresh(); - } catch (error) { - console.error("CSV处理错误:", error); + this.refresh(); + } catch (error) { + console.error("CSV处理错误:", error); - // 出错时设置为空表格 - this.tableData = [[""]]; - if (clear) { - this.historyManager.reset(this.tableData); - } - this.refresh(); - } - } + // 出错时设置为空表格 + this.tableData = [[""]]; + if (clear) { + this.historyManager.reset(this.tableData); + } + this.refresh(); + } + } - // 新增:重新解析和刷新视图的方法 - private reparseAndRefresh() { - // 获取原始数据并用新设置重新解析 - const rawData = this.data; - this.setViewData(rawData, false); // 重新运行setViewData但不清除历史 - } + // 新增:重新解析和刷新视图的方法 + private reparseAndRefresh() { + // 获取原始数据并用新设置重新解析 + const rawData = this.data; + this.setViewData(rawData, false); // 重新运行setViewData但不清除历史 + } - refresh() { - // Safety check: ensure tableEl exists - if (!this.tableEl) { - console.warn("Table element not initialized in refresh()"); - return; - } + refresh() { + // Safety check: ensure tableEl exists + if (!this.tableEl) { + console.warn("Table element not initialized in refresh()"); + return; + } - // Safety check: ensure tableData is initialized - if (!this.tableData || !Array.isArray(this.tableData) || this.tableData.length === 0) { - console.warn("Table data not properly initialized, setting default"); - this.tableData = [[""]]; - } + // Safety check: ensure tableData is initialized + if ( + !this.tableData || + !Array.isArray(this.tableData) || + this.tableData.length === 0 + ) { + console.warn( + "Table data not properly initialized, setting default" + ); + this.tableData = [[""]]; + } - this.tableEl.empty(); + this.tableEl.empty(); - // 创建表头行用于调整列宽 - const headerRow = this.tableEl.createEl("thead").createEl("tr"); + // 创建表头行用于调整列宽 + const headerRow = this.tableEl.createEl("thead").createEl("tr"); - // 计算初始列宽(如果未设置) - if (this.columnWidths.length === 0 && this.tableData[0]) { - this.columnWidths = TableUtils.calculateColumnWidths(this.tableData); - } + // 计算初始列宽(如果未设置) + if (this.columnWidths.length === 0 && this.tableData[0]) { + this.columnWidths = TableUtils.calculateColumnWidths( + this.tableData + ); + } - // 创建表头和调整列宽的手柄 - if (this.tableData[0]) { - this.tableData[0].forEach((headerCell, index) => { - const th = headerRow.createEl("th", { - cls: "csv-th", - attr: { style: `width: ${this.columnWidths[index] || 100}px` } - }); + // 创建表头和调整列宽的手柄 + if (this.tableData[0]) { + this.tableData[0].forEach((headerCell, index) => { + const th = headerRow.createEl("th", { + cls: "csv-th", + attr: { + style: `width: ${this.columnWidths[index] || 100}px`, + }, + }); - // 添加列标题 - const headerInput = th.createEl("input", { - cls: "csv-cell-input", - attr: { value: headerCell } - }); + // 添加列标题 + const headerInput = th.createEl("input", { + cls: "csv-cell-input", + attr: { value: headerCell }, + }); - // 列标题内容变更时的处理 - headerInput.oninput = (ev) => { - if (ev.currentTarget instanceof HTMLInputElement) { - if (this.tableData[0][index] !== ev.currentTarget.value) { - this.saveSnapshot(); - } - this.tableData[0][index] = ev.currentTarget.value; - this.requestSave(); - } - }; + // 列标题内容变更时的处理 + headerInput.oninput = (ev) => { + if (ev.currentTarget instanceof HTMLInputElement) { + if ( + this.tableData[0][index] !== ev.currentTarget.value + ) { + this.saveSnapshot(); + } + this.tableData[0][index] = ev.currentTarget.value; + this.requestSave(); + } + }; - // 为表头输入框添加聚焦事件 - headerInput.onfocus = (ev) => { - this.setActiveCell(0, index, ev.currentTarget as HTMLInputElement); - }; + // 为表头输入框添加聚焦事件 + headerInput.onfocus = (ev) => { + this.setActiveCell( + 0, + index, + ev.currentTarget as HTMLInputElement + ); + }; - // 添加调整列宽的手柄 - const resizeHandle = th.createEl("div", { cls: "resize-handle" }); + // 添加调整列宽的手柄 + const resizeHandle = th.createEl("div", { + cls: "resize-handle", + }); - // 实现列宽调整 - this.setupColumnResize(resizeHandle, index); - }); - } + // 实现列宽调整 + this.setupColumnResize(resizeHandle, index); + }); + } - // 创建表格主体 - const tableBody = this.tableEl.createEl("tbody"); + // 创建表格主体 + const tableBody = this.tableEl.createEl("tbody"); - // 从第二行开始显示数据行(如果有表头) - const startRowIndex = this.tableData.length > 1 ? 1 : 0; + // 从第二行开始显示数据行(如果有表头) + const startRowIndex = this.tableData.length > 1 ? 1 : 0; - for (let i = startRowIndex; i < this.tableData.length; i++) { - const row = this.tableData[i]; - const tableRow = tableBody.createEl("tr"); + for (let i = startRowIndex; i < this.tableData.length; i++) { + const row = this.tableData[i]; + const tableRow = tableBody.createEl("tr"); - row.forEach((cell, j) => { - const td = tableRow.createEl("td",{ - attr: { style: `width: ${this.columnWidths[j] || 100}px` } + row.forEach((cell, j) => { + const td = tableRow.createEl("td", { + attr: { style: `width: ${this.columnWidths[j] || 100}px` }, + }); + + const input = td.createEl("input", { + cls: "csv-cell-input", + attr: { + value: cell, + }, + }); + + // 为输入框添加自动调整高度的功能 + this.setupAutoResize(input); + + input.oninput = (ev) => { + if (ev.currentTarget instanceof HTMLInputElement) { + // 保存历史状态(仅在第一次修改时保存) + if (this.tableData[i][j] !== ev.currentTarget.value) { + this.saveSnapshot(); + } + + this.tableData[i][j] = ev.currentTarget.value; + + // 如果是当前活动单元格,同步到编辑栏 + if ( + this.activeCellEl === ev.currentTarget && + this.editInput + ) { + this.editInput.value = ev.currentTarget.value; + } + + this.requestSave(); + + // 自动调整输入框高度 + if (this.autoResize) { + this.adjustInputHeight(ev.currentTarget); + } + } + }; + + // 为单元格输入框添加聚焦事件 + input.onfocus = (ev) => { + this.setActiveCell( + i, + j, + ev.currentTarget as HTMLInputElement + ); + }; + }); + } + } + + // 设置活动单元格 + private setActiveCell( + rowIndex: number, + colIndex: number, + cellEl: HTMLInputElement + ) { + // 移除之前单元格的高亮 + if (this.activeCellEl && this.activeCellEl.parentElement) { + this.activeCellEl.parentElement.removeClass("csv-active-cell"); + } + + // 设置新的活动单元格 + this.activeRowIndex = rowIndex; + this.activeColIndex = colIndex; + this.activeCellEl = cellEl; + + // 高亮当前单元格 + if (cellEl.parentElement) { + cellEl.parentElement.addClass("csv-active-cell"); + } + + // 更新编辑栏内容 + if (this.editInput) { + this.editInput.value = cellEl.value; + + // 更新编辑栏的地址显示 + const cellAddress = TableUtils.getCellAddress(rowIndex, colIndex); + this.editBarEl.setAttribute("data-cell-address", cellAddress); + } + } + + // 设置列宽调整功能 + private setupColumnResize(handle: HTMLElement, columnIndex: number) { + let startX: number; + let startWidth: number; + + const onMouseDown = (e: MouseEvent) => { + startX = e.clientX; + startWidth = this.columnWidths[columnIndex] || 100; + + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + + e.preventDefault(); + }; + + const onMouseMove = (e: MouseEvent) => { + const width = startWidth + (e.clientX - startX); + if (width >= 50) { + // 最小宽度限制 + this.columnWidths[columnIndex] = width; + this.refresh(); + } + }; + + const onMouseUp = () => { + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + }; + + handle.addEventListener("mousedown", onMouseDown); + } + + // 设置输入框自动调整高度 + private setupAutoResize(input: HTMLInputElement) { + // 初始调整 + this.adjustInputHeight(input); + + // 监听内容变化 + input.addEventListener("input", () => { + if (this.autoResize) { + this.adjustInputHeight(input); + } }); + } - const input = td.createEl("input", { - cls: "csv-cell-input", - attr: { - value: cell, - } - }); + // 调整输入框高度 + private adjustInputHeight(input: HTMLInputElement) { + // Style moved to styles.css: input.style.height = 'auto'; - // 为输入框添加自动调整高度的功能 - this.setupAutoResize(input); + // 获取内容行数 + const lineCount = (input.value.match(/\n/g) || []).length + 1; + const minHeight = 24; // 最小高度 + const lineHeight = 20; // 每行高度 - input.oninput = (ev) => { - if (ev.currentTarget instanceof HTMLInputElement) { - // 保存历史状态(仅在第一次修改时保存) - if (this.tableData[i][j] !== ev.currentTarget.value) { - this.saveSnapshot(); - } + // 设置高度,确保能显示所有内容 + const newHeight = Math.max(minHeight, lineCount * lineHeight); + input.style.height = `${newHeight}px`; + } - this.tableData[i][j] = ev.currentTarget.value; + // 保存当前状态到历史记录 + private saveSnapshot() { + this.historyManager.push(this.tableData); + } - // 如果是当前活动单元格,同步到编辑栏 - if (this.activeCellEl === ev.currentTarget && this.editInput) { - this.editInput.value = ev.currentTarget.value; - } + // 执行撤销操作 + undo() { + const prevState = this.historyManager.undo(); + if (prevState) { + this.tableData = prevState; + this.refresh(); + this.requestSave(); + } + } - this.requestSave(); + // 执行重做操作 + redo() { + const nextState = this.historyManager.redo(); + if (nextState) { + this.tableData = nextState; + this.refresh(); + this.requestSave(); + } + } - // 自动调整输入框高度 - if (this.autoResize) { - this.adjustInputHeight(ev.currentTarget); - } - } - }; + clear() { + this.tableData = [[""]]; + this.historyManager.reset(this.tableData); + this.refresh(); + } - // 为单元格输入框添加聚焦事件 - input.onfocus = (ev) => { - this.setActiveCell(i, j, ev.currentTarget as HTMLInputElement); - }; - }); - } - } + getViewType() { + return VIEW_TYPE_CSV; + } - // 设置活动单元格 - private setActiveCell(rowIndex: number, colIndex: number, cellEl: HTMLInputElement) { - // 移除之前单元格的高亮 - if (this.activeCellEl && this.activeCellEl.parentElement) { - this.activeCellEl.parentElement.removeClass('csv-active-cell'); - } + async onOpen() { + try { + // Clear the content element first + this.contentEl.empty(); - // 设置新的活动单元格 - this.activeRowIndex = rowIndex; - this.activeColIndex = colIndex; - this.activeCellEl = cellEl; + // 创建操作区 + this.operationEl = this.contentEl.createEl("div", { + cls: "csv-operations", + }); - // 高亮当前单元格 - if (cellEl.parentElement) { - cellEl.parentElement.addClass('csv-active-cell'); - } + // 新增:解析器设置UI + const parserSettingsEl = this.operationEl.createEl("div", { + cls: "csv-parser-settings", + }); - // 更新编辑栏内容 - if (this.editInput) { - this.editInput.value = cellEl.value; + new Setting(parserSettingsEl) + .setName("字段分隔符") + .setDesc("用于分隔字段的字符(例如:逗号、分号、制表符)") + .addText((text) => { + text.setValue(this.delimiter) + .setPlaceholder("例如:, 或 ; 或 \\t 表示制表符") + .onChange(async (value) => { + // 处理制表符的特殊情况 + this.delimiter = value === "\\t" ? "\t" : value; + this.reparseAndRefresh(); + }); + }); - // 更新编辑栏的地址显示 - const cellAddress = TableUtils.getCellAddress(rowIndex, colIndex); - this.editBarEl.setAttribute('data-cell-address', cellAddress); - } - } + new Setting(parserSettingsEl) + .setName("引号字符") + .setDesc("用于包围含有特殊字符的字段") + .addText((text) => { + text.setValue(this.quoteChar) + .setPlaceholder('默认为双引号 "') + .onChange(async (value) => { + this.quoteChar = value || '"'; + this.reparseAndRefresh(); + }); + }); - // 设置列宽调整功能 - private setupColumnResize(handle: HTMLElement, columnIndex: number) { - let startX: number; - let startWidth: number; + // 创建操作按钮容器 + const buttonContainer = this.operationEl.createEl("div", { + cls: "csv-operation-buttons", + }); - const onMouseDown = (e: MouseEvent) => { - startX = e.clientX; - startWidth = this.columnWidths[columnIndex] || 100; + // 撤销按钮 + new ButtonComponent(buttonContainer) + .setButtonText(i18n.t("buttons.undo")) + .setIcon("undo") + .onClick(() => this.undo()); - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); + // 重做按钮 + new ButtonComponent(buttonContainer) + .setButtonText(i18n.t("buttons.redo")) + .setIcon("redo") + .onClick(() => this.redo()); - e.preventDefault(); - }; + // 添加行按钮 + new ButtonComponent(buttonContainer) + .setButtonText(i18n.t("buttons.addRow")) + .onClick(() => this.addRow()); - const onMouseMove = (e: MouseEvent) => { - const width = startWidth + (e.clientX - startX); - if (width >= 50) { // 最小宽度限制 - this.columnWidths[columnIndex] = width; - this.refresh(); - } - }; + // 删除行按钮 + new ButtonComponent(buttonContainer) + .setButtonText(i18n.t("buttons.deleteRow")) + .onClick(() => this.deleteRow()); - const onMouseUp = () => { - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - }; + // 添加列按钮 + new ButtonComponent(buttonContainer) + .setButtonText(i18n.t("buttons.addColumn")) + .onClick(() => this.addColumn()); - handle.addEventListener('mousedown', onMouseDown); - } + // 删除列按钮 + new ButtonComponent(buttonContainer) + .setButtonText(i18n.t("buttons.deleteColumn")) + .onClick(() => this.deleteColumn()); - // 设置输入框自动调整高度 - private setupAutoResize(input: HTMLInputElement) { - // 初始调整 - this.adjustInputHeight(input); + // 重置列宽按钮 + new ButtonComponent(buttonContainer) + .setButtonText(i18n.t("buttons.resetColumnWidth")) + .onClick(() => { + this.columnWidths = []; + this.calculateColumnWidths(); + this.refresh(); + }); - // 监听内容变化 - input.addEventListener('input', () => { - if (this.autoResize) { - this.adjustInputHeight(input); - } - }); - } + // CSV导入导出选项 + this.operationEl.createEl("div", { cls: "csv-export-import" }); - // 调整输入框高度 - private adjustInputHeight(input: HTMLInputElement) { - // Style moved to styles.css: input.style.height = 'auto'; + // 创建编辑栏(在操作区之后) + this.editBarEl = this.contentEl.createEl("div", { + cls: "csv-edit-bar", + }); - // 获取内容行数 - const lineCount = (input.value.match(/\n/g) || []).length + 1; - const minHeight = 24; // 最小高度 - const lineHeight = 20; // 每行高度 + // 创建编辑输入框 + this.editInput = this.editBarEl.createEl("input", { + cls: "csv-edit-input", + attr: { placeholder: i18n.t("editBar.placeholder") }, + }); - // 设置高度,确保能显示所有内容 - const newHeight = Math.max(minHeight, lineCount * lineHeight); - input.style.height = `${newHeight}px`; - } + // 添加编辑栏输入处理 + this.editInput.oninput = () => { + if ( + this.activeCellEl && + this.activeRowIndex >= 0 && + this.activeColIndex >= 0 + ) { + // 更新活动单元格 + this.activeCellEl.value = this.editInput.value; - // 保存当前状态到历史记录 - private saveSnapshot() { - this.historyManager.push(this.tableData); - } + // 更新数据 + if ( + this.tableData[this.activeRowIndex][ + this.activeColIndex + ] !== this.editInput.value + ) { + this.saveSnapshot(); + } + this.tableData[this.activeRowIndex][this.activeColIndex] = + this.editInput.value; + this.requestSave(); + } + }; - // 执行撤销操作 - undo() { - const prevState = this.historyManager.undo(); - if (prevState) { - this.tableData = prevState; - this.refresh(); - this.requestSave(); - } - } + // 创建表格区域 - IMPORTANT: Create this before calling refresh() + this.tableEl = this.contentEl.createEl("table"); - // 执行重做操作 - redo() { - const nextState = this.historyManager.redo(); - if (nextState) { - this.tableData = nextState; - this.refresh(); - this.requestSave(); - } - } + // 初始化历史记录 + if (!this.historyManager) { + this.historyManager = new TableHistoryManager( + this.tableData, + this.maxHistorySize + ); + } - clear() { - this.tableData = [[""]]; - this.historyManager.reset(this.tableData); - this.refresh(); - } + // 添加键盘事件监听器 + this.registerDomEvent( + document, + "keydown", + (event: KeyboardEvent) => { + // 检测Ctrl+Z (或Mac上的Cmd+Z) + if ((event.ctrlKey || event.metaKey) && event.key === "z") { + if (event.shiftKey) { + // Ctrl+Shift+Z 或 Cmd+Shift+Z 重做 + event.preventDefault(); + this.redo(); + } else { + // Ctrl+Z 或 Cmd+Z 撤销 + event.preventDefault(); + this.undo(); + } + } + } + ); - getViewType() { - return VIEW_TYPE_CSV; - } + // Ensure tableData is initialized before refreshing + if ( + !this.tableData || + !Array.isArray(this.tableData) || + this.tableData.length === 0 + ) { + this.tableData = [[""]]; + } - async onOpen() { - try { - // Clear the content element first - this.contentEl.empty(); + // 初始化时刷新视图 + this.refresh(); + } catch (error) { + console.error("Error in onOpen:", error); + new Notice(`Failed to open CSV view: ${error.message}`); - // 创建操作区 - this.operationEl = this.contentEl.createEl("div", { cls: "csv-operations" }); + // Try to recover with minimal UI + this.contentEl.empty(); + const errorDiv = this.contentEl.createEl("div", { + cls: "csv-error", + }); + errorDiv.createEl("h3", { text: "Error opening CSV file" }); + errorDiv.createEl("p", { text: error.message }); - // 新增:解析器设置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(); - }); - }); + // Create a minimal table with default data + this.tableData = [[""]]; + this.tableEl = this.contentEl.createEl("table"); + this.refresh(); + } + } - new Setting(parserSettingsEl) - .setName("引号字符") - .setDesc("用于包围含有特殊字符的字段") - .addText(text => { - text.setValue(this.quoteChar) - .setPlaceholder('默认为双引号 "') - .onChange(async (value) => { - this.quoteChar = value || '"'; - this.reparseAndRefresh(); - }); - }); + // 需要添加calculateColumnWidths方法 + private calculateColumnWidths() { + this.columnWidths = TableUtils.calculateColumnWidths(this.tableData); + } - // 创建操作按钮容器 - const buttonContainer = this.operationEl.createEl("div", { cls: "csv-operation-buttons" }); + async onClose() { + // 移除自定义样式 + const styleEl = document.head.querySelector("#csv-edit-bar-styles"); + if (styleEl) styleEl.remove(); - // 撤销按钮 - new ButtonComponent(buttonContainer) - .setButtonText(i18n.t('buttons.undo')) - .setIcon("undo") - .onClick(() => this.undo()); + this.contentEl.empty(); + } - // 重做按钮 - new ButtonComponent(buttonContainer) - .setButtonText(i18n.t('buttons.redo')) - .setIcon("redo") - .onClick(() => this.redo()); + // 表格操作方法 + addRow() { + this.saveSnapshot(); + this.tableData = TableUtils.addRow(this.tableData); + this.refresh(); + this.requestSave(); + } - // 添加行按钮 - new ButtonComponent(buttonContainer) - .setButtonText(i18n.t('buttons.addRow')) - .onClick(() => this.addRow()); + deleteRow() { + this.saveSnapshot(); + this.tableData = TableUtils.deleteRow(this.tableData); + this.refresh(); + this.requestSave(); + } - // 删除行按钮 - new ButtonComponent(buttonContainer) - .setButtonText(i18n.t('buttons.deleteRow')) - .onClick(() => this.deleteRow()); + addColumn() { + this.saveSnapshot(); + this.tableData = TableUtils.addColumn(this.tableData); + this.refresh(); + this.requestSave(); + } - // 添加列按钮 - new ButtonComponent(buttonContainer) - .setButtonText(i18n.t('buttons.addColumn')) - .onClick(() => this.addColumn()); - - // 删除列按钮 - new ButtonComponent(buttonContainer) - .setButtonText(i18n.t('buttons.deleteColumn')) - .onClick(() => this.deleteColumn()); - - // 重置列宽按钮 - new ButtonComponent(buttonContainer) - .setButtonText(i18n.t('buttons.resetColumnWidth')) - .onClick(() => { - this.columnWidths = []; - this.calculateColumnWidths(); - this.refresh(); - }); - - // CSV导入导出选项 - this.operationEl.createEl("div", { cls: "csv-export-import" }); - - - // 创建编辑栏(在操作区之后) - this.editBarEl = this.contentEl.createEl("div", { cls: "csv-edit-bar" }); - - // 创建编辑输入框 - this.editInput = this.editBarEl.createEl("input", { - cls: "csv-edit-input", - attr: { placeholder: i18n.t('editBar.placeholder') } - }); - - // 添加编辑栏输入处理 - this.editInput.oninput = () => { - if (this.activeCellEl && this.activeRowIndex >= 0 && this.activeColIndex >= 0) { - // 更新活动单元格 - this.activeCellEl.value = this.editInput.value; - - // 更新数据 - if (this.tableData[this.activeRowIndex][this.activeColIndex] !== this.editInput.value) { - this.saveSnapshot(); - } - this.tableData[this.activeRowIndex][this.activeColIndex] = this.editInput.value; - this.requestSave(); - } - }; - - // 创建表格区域 - IMPORTANT: Create this before calling refresh() - this.tableEl = this.contentEl.createEl("table"); - - // 初始化历史记录 - if (!this.historyManager) { - this.historyManager = new TableHistoryManager(this.tableData, this.maxHistorySize); - } - - // 添加键盘事件监听器 - this.registerDomEvent(document, 'keydown', (event: KeyboardEvent) => { - // 检测Ctrl+Z (或Mac上的Cmd+Z) - if ((event.ctrlKey || event.metaKey) && event.key === 'z') { - if (event.shiftKey) { - // Ctrl+Shift+Z 或 Cmd+Shift+Z 重做 - event.preventDefault(); - this.redo(); - } else { - // Ctrl+Z 或 Cmd+Z 撤销 - event.preventDefault(); - this.undo(); - } - } - }); - - // Ensure tableData is initialized before refreshing - if (!this.tableData || !Array.isArray(this.tableData) || this.tableData.length === 0) { - this.tableData = [[""]]; - } - - // 初始化时刷新视图 - this.refresh(); - - } catch (error) { - console.error("Error in onOpen:", error); - new Notice(`Failed to open CSV view: ${error.message}`); - - // Try to recover with minimal UI - this.contentEl.empty(); - const errorDiv = this.contentEl.createEl("div", { cls: "csv-error" }); - errorDiv.createEl("h3", { text: "Error opening CSV file" }); - errorDiv.createEl("p", { text: error.message }); - - // Create a minimal table with default data - this.tableData = [[""]]; - this.tableEl = this.contentEl.createEl("table"); - this.refresh(); - } - } - - // 需要添加calculateColumnWidths方法 - private calculateColumnWidths() { - this.columnWidths = TableUtils.calculateColumnWidths(this.tableData); - } - - async onClose() { - // 移除自定义样式 - const styleEl = document.head.querySelector('#csv-edit-bar-styles'); - if (styleEl) styleEl.remove(); - - this.contentEl.empty(); - } - - // 表格操作方法 - addRow() { - this.saveSnapshot(); - this.tableData = TableUtils.addRow(this.tableData); - this.refresh(); - this.requestSave(); - } - - deleteRow() { - this.saveSnapshot(); - this.tableData = TableUtils.deleteRow(this.tableData); - this.refresh(); - this.requestSave(); - } - - addColumn() { - this.saveSnapshot(); - this.tableData = TableUtils.addColumn(this.tableData); - this.refresh(); - this.requestSave(); - } - - deleteColumn() { - this.saveSnapshot(); - this.tableData = TableUtils.deleteColumn(this.tableData); - this.refresh(); - this.requestSave(); - } + deleteColumn() { + this.saveSnapshot(); + this.tableData = TableUtils.deleteColumn(this.tableData); + this.refresh(); + this.requestSave(); + } } diff --git a/styles.css b/styles.css index b5d6b6b..8559c1d 100644 --- a/styles.css +++ b/styles.css @@ -37,48 +37,48 @@ If your plugin does not need CSS, delete this file. .csv-lite-cell-long { white-space: pre-wrap; } - /* 输入框共享样式 */ - .csv-cell-input, - .csv-edit-input { - height: auto; - padding: 4px 8px; - border: 1px solid var(--background-modifier-border); - border-radius: 4px; - box-sizing: border-box; - background: var(--background-primary); - color: var(--text-normal); - font-size: inherit; - line-height: 1.4; - } +/* 输入框共享样式 */ +.csv-cell-input, +.csv-edit-input { + height: auto; + padding: 4px 8px; + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + box-sizing: border-box; + background: var(--background-primary); + color: var(--text-normal); + font-size: inherit; + line-height: 1.4; +} - /* csv-cell-input 特有样式(宽度由 JS 控制) */ - .csv-cell-input { - width: 100%; - } +/* csv-cell-input 特有样式(宽度由 JS 控制) */ +.csv-cell-input { + width: 100%; +} - /* csv-edit-input 特有样式 */ - .csv-edit-input { - width: 75%; - margin: 0 auto; - display: block; - text-align: center; - } +/* csv-edit-input 特有样式 */ +.csv-edit-input { + width: 75%; + margin: 0 auto; + display: block; + text-align: center; +} - /* 焦点状态样式 */ - .csv-cell-input:focus, - .csv-edit-input:focus { - outline: none; - border-color: var(--interactive-accent); - box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2); - } - .csv-operation-buttons { +/* 焦点状态样式 */ +.csv-cell-input:focus, +.csv-edit-input:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2); +} +.csv-operation-buttons { display: flex; gap: 8px; flex-wrap: wrap; - } - - /* 列宽调整手柄 */ - .resize-handle { +} + +/* 列宽调整手柄 */ +.resize-handle { position: absolute; right: 0; top: 0; @@ -86,104 +86,102 @@ If your plugin does not need CSS, delete this file. width: 5px; background-color: transparent; cursor: col-resize; - } - - .resize-handle:hover, - .resize-handle:active { +} + +.resize-handle:hover, +.resize-handle:active { background-color: var(--interactive-accent); - } - - - /* 长文本单元格样式 */ - td input:focus { +} + +/* 长文本单元格样式 */ +td input:focus { white-space: pre-wrap; - } - - /* 表格容器,允许水平滚动 */ - .table-container { +} + +/* 表格容器,允许水平滚动 */ +.table-container { max-width: 100%; overflow-x: auto; - } - - /* 标签页样式 */ - .csv-tabs { +} + +/* 标签页样式 */ +.csv-tabs { display: flex; flex-direction: column; width: 100%; margin-bottom: 10px; - } - - .csv-tab-headers { +} + +.csv-tab-headers { display: flex; border-bottom: 1px solid var(--background-modifier-border); - } - - .csv-tab-header { +} + +.csv-tab-header { padding: 8px 16px; cursor: pointer; border-bottom: 2px solid transparent; margin-right: 8px; font-weight: 500; - } - - .csv-tab-header:hover { +} + +.csv-tab-header:hover { color: var(--interactive-accent); - } - - .csv-tab-header.csv-tab-active { +} + +.csv-tab-header.csv-tab-active { color: var(--interactive-accent); border-bottom-color: var(--interactive-accent); - } - - .csv-tab-panel { +} + +.csv-tab-panel { display: none; padding: 12px 0; - } - - .csv-tab-panel-active { +} + +.csv-tab-panel-active { display: block; - } - - /* CSV导入导出选项样式 */ - .csv-export-import { +} + +/* CSV导入导出选项样式 */ +.csv-export-import { margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--background-modifier-border); - } - - .csv-delimiter-container { +} + +.csv-delimiter-container { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; - } - - /* 表头行样式 */ - .csv-header-row th { +} + +/* 表头行样式 */ +.csv-header-row th { background-color: var(--interactive-accent); 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); + display: none; + 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; + border: none; + padding: 8px 0; } .csv-parser-settings .setting-item-info { - margin-right: 16px; + margin-right: 16px; } .csv-parser-settings .setting-item-control input { - max-width: 200px; + max-width: 200px; } - -