mirror of
https://github.com/tailormade-eu/obsidian-task-export-plugin.git
synced 2026-07-22 06:41:42 +00:00
feat: Add configurable CSV delimiter feature (comma/semicolon)
Co-authored-by: RaoulJacobs <18635340+RaoulJacobs@users.noreply.github.com>
This commit is contained in:
parent
a7cda7916d
commit
1ba60a8ccb
6 changed files with 47 additions and 15 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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: ','
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue