diff --git a/README.md b/README.md index 8335286..72aff6f 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,13 @@ View last export time and status in bottom status bar: | **Export on Save** | Trigger on file save | `true` | Toggle | | **Export on Modify** | Trigger on any modification | `false` | Toggle | | **Show Notifications** | Display export notifications | `true` | Toggle | +| **CSV Delimiter** | Choose comma or semicolon delimiter | `,` (comma) | Dropdown | +| **Compress Levels** | Remove empty hierarchy columns | `false` | Toggle | +| **Include Header** | Include CSV header row in output | `true` | Toggle | | **Debounce Delay** | Wait time after changes (seconds) | `3` | Number | +**Note for European Users**: If Excel doesn't open the CSV correctly, try changing the delimiter to semicolon in settings. Many European locales use semicolon as the standard CSV delimiter since comma is used as the decimal separator. + ### Accessing Settings Settings → Plugin Options → Task Export Tool @@ -161,7 +166,7 @@ Standard Markdown task checkboxes: ## Output Format -CSV with dynamic columns: +CSV with dynamic columns (comma-separated by default, configurable to semicolon): ```csv CustomerName,ProjectName,Header1,Header2,Header3,Task @@ -171,6 +176,8 @@ Customer A,Project 1,Section,Sub-task (nested) Customer A,Project 1,Section,Subsection,Another task ``` +The delimiter can be changed to semicolon (`;`) in the plugin settings for better compatibility with European Excel versions. + ## Technical Details ### Technology Stack diff --git a/src/csv-writer.ts b/src/csv-writer.ts index a1bca49..ce53127 100644 --- a/src/csv-writer.ts +++ b/src/csv-writer.ts @@ -5,6 +5,12 @@ import { TaskItem } from './types'; * Ported from C# CsvExporter.cs */ export class CsvWriter { + private delimiter: string; + + constructor(delimiter: ',' | ';' = ',') { + this.delimiter = delimiter; + } + /** * Generates CSV content from tasks with UTF-8 BOM encoding. */ @@ -22,17 +28,17 @@ export class CsvWriter { // Build header row based on max level depth if (includeHeader) { - csv += 'CustomerName,ProjectName'; + csv += `CustomerName${this.delimiter}ProjectName`; for (let i = 1; i <= maxLevelDepth; i++) { - csv += `,Level${i}`; + csv += `${this.delimiter}Level${i}`; } - csv += ',Task\n'; + csv += `${this.delimiter}Task\n`; } // Write data rows for (const task of tasks) { csv += this.escapeField(task.customerName); - csv += ','; + csv += this.delimiter; csv += this.escapeField(task.projectName); if (compressLevels) { @@ -41,20 +47,20 @@ export class CsvWriter { // Output non-empty levels (no padding needed - each row can have different column count) for (const level of nonEmptyLevels) { - csv += ','; + csv += this.delimiter; csv += this.escapeField(level); } } else { // Non-compressed mode: output all levels including empty slots to preserve hierarchy for (let i = 0; i < maxLevelDepth; i++) { - csv += ','; + csv += this.delimiter; if (i < task.levels.length) { csv += this.escapeField(task.levels[i]); } } } - csv += ','; + csv += this.delimiter; csv += this.escapeField(task.task); csv += '\n'; } @@ -96,7 +102,7 @@ export class CsvWriter { /** * Escapes a CSV field according to RFC 4180. - * - Wraps in quotes if contains comma, newline, or quote + * - Wraps in quotes if contains delimiter, newline, or quote * - Doubles any quotes inside the field */ private escapeField(field: string): string { @@ -105,7 +111,7 @@ export class CsvWriter { } // Check if escaping is needed - const needsQuotes = field.includes(',') || field.includes('"') || field.includes('\n') || field.includes('\r'); + const needsQuotes = field.includes(this.delimiter) || field.includes('"') || field.includes('\n') || field.includes('\r'); if (needsQuotes) { // Double any existing quotes diff --git a/src/exporter.ts b/src/exporter.ts index d076c16..4e62e1e 100644 --- a/src/exporter.ts +++ b/src/exporter.ts @@ -15,7 +15,7 @@ export class TaskExporter { constructor(app: App) { this.app = app; this.parser = new MarkdownParser(); - this.csvWriter = new CsvWriter(); + this.csvWriter = new CsvWriter(); // Will be recreated with delimiter in exportToFile } /** @@ -81,9 +81,12 @@ export class TaskExporter { tasks: TaskItem[], outputPath: string, compressLevels: boolean = false, - includeHeader: boolean = true + includeHeader: boolean = true, + delimiter: ',' | ';' = ',' ): Promise { - const csv = this.csvWriter.generateCsv(tasks, compressLevels, includeHeader); + // Create CSV writer with specified delimiter + const csvWriter = new CsvWriter(delimiter); + const csv = csvWriter.generateCsv(tasks, compressLevels, includeHeader); const normalizedPath = normalizePath(outputPath); await this.app.vault.adapter.write(normalizedPath, csv); } diff --git a/src/main.ts b/src/main.ts index 796a83b..22a80cc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -168,7 +168,8 @@ export default class TaskExportPlugin extends Plugin { tasks, this.settings.outputPath, this.settings.compressLevels, - this.settings.includeHeader + this.settings.includeHeader, + this.settings.delimiter ); // Update last export time diff --git a/src/settings.ts b/src/settings.ts index 47b3081..0ab37b5 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -111,6 +111,19 @@ export class TaskExportSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); })); + // CSV Delimiter + new Setting(containerEl) + .setName('CSV Delimiter') + .setDesc('Choose delimiter for CSV output. Comma is standard, semicolon is common in Europe.') + .addDropdown(dropdown => dropdown + .addOption(',', 'Comma (,)') + .addOption(';', 'Semicolon (;)') + .setValue(this.plugin.settings.delimiter) + .onChange(async (value) => { + this.plugin.settings.delimiter = value as ',' | ';'; + await this.plugin.saveSettings(); + })); + // Debounce Delay new Setting(containerEl) .setName('Debounce delay') diff --git a/src/types.ts b/src/types.ts index 997e25f..bb5e836 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ export interface TaskExportSettings { compressLevels: boolean; // Remove empty hierarchy columns includeHeader: boolean; // Include CSV header row debounceDelay: number; // Debounce delay in seconds (1-30) + delimiter: ',' | ';'; // CSV delimiter: comma or semicolon } /** @@ -25,7 +26,8 @@ export const DEFAULT_SETTINGS: TaskExportSettings = { showNotifications: true, compressLevels: false, includeHeader: true, - debounceDelay: 3 + debounceDelay: 3, + delimiter: ',' }; /**