From 01a5d99d0b2de585e05656bd91cc1a36bd1e4165 Mon Sep 17 00:00:00 2001 From: Raoul Jacobs Date: Fri, 21 Nov 2025 19:42:07 +0100 Subject: [PATCH] feat: Enhance CSV export by including folder hierarchy and refining task parsing --- src/csv-writer.ts | 8 +++----- src/exporter.ts | 17 +++++++++++------ src/parser.ts | 16 +++++++++++++--- src/types.ts | 4 +++- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/csv-writer.ts b/src/csv-writer.ts index ce53127..3a806e5 100644 --- a/src/csv-writer.ts +++ b/src/csv-writer.ts @@ -28,7 +28,8 @@ export class CsvWriter { // Build header row based on max level depth if (includeHeader) { - csv += `CustomerName${this.delimiter}ProjectName`; + // Emit CustomerName, then Level columns (first level may be project name or a subfolder), then Task + csv += `CustomerName`; for (let i = 1; i <= maxLevelDepth; i++) { csv += `${this.delimiter}Level${i}`; } @@ -38,9 +39,7 @@ export class CsvWriter { // Write data rows for (const task of tasks) { csv += this.escapeField(task.customerName); - csv += this.delimiter; - csv += this.escapeField(task.projectName); - + if (compressLevels) { // Compressed mode: only output non-empty levels, skipping empty slots const nonEmptyLevels = task.levels.filter(l => l !== ''); @@ -59,7 +58,6 @@ export class CsvWriter { } } } - csv += this.delimiter; csv += this.escapeField(task.task); csv += '\n'; diff --git a/src/exporter.ts b/src/exporter.ts index 38a2ed2..0e10e04 100644 --- a/src/exporter.ts +++ b/src/exporter.ts @@ -43,8 +43,8 @@ export class TaskExporter { // Process each file for (const file of customerFiles) { - // Extract customer name and project name from file path - const { customerName, projectName } = this.extractCustomerAndProject(file, normalizedPath); + // Extract customer name, project name and folder segments from file path + const { customerName, projectName, folderSegments } = this.extractCustomerAndProject(file, normalizedPath); // Skip files in hidden directories if (customerName.startsWith('.')) { @@ -57,7 +57,8 @@ export class TaskExporter { try { const content = await this.app.vault.read(file); - const tasks = this.parser.parseFile(content, customerName, projectName); + // Pass folderSegments into parser so levels can include folder hierarchy + const tasks = this.parser.parseFile(content, customerName, projectName, folderSegments); if (tasks.length > 0) { allTasks.push(...tasks); @@ -96,7 +97,7 @@ export class TaskExporter { * Customer name is the first folder under customersFolder. * Project name is the filename without extension. */ - private extractCustomerAndProject(file: TFile, customersFolder: string): { customerName: string, projectName: string } { + private extractCustomerAndProject(file: TFile, customersFolder: string): { customerName: string, projectName: string, folderSegments: string[] } { // Remove the customers folder prefix from the path let relativePath = file.path.substring(customersFolder.length + 1); @@ -108,7 +109,11 @@ export class TaskExporter { // Project name is the filename without extension const projectName = file.basename; - - return { customerName, projectName }; + + // Folder segments are the intermediate path parts between the customer folder and the file name + // parts = [ customer, sub1, sub2, ..., filename ] so folderSegments should be parts.slice(1, parts.length - 1) + const folderSegments = parts.length > 2 ? parts.slice(1, parts.length - 1) : []; + + return { customerName, projectName, folderSegments }; } } diff --git a/src/parser.ts b/src/parser.ts index c5c7e2a..d117186 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -13,7 +13,7 @@ export class MarkdownParser { /** * Parses a markdown file and extracts all outstanding tasks. */ - parseFile(content: string, customerName: string, projectName: string): TaskItem[] { + parseFile(content: string, customerName: string, projectName: string, folderSegments: string[] = []): TaskItem[] { const tasks: TaskItem[] = []; const lines = content.split('\n'); @@ -91,9 +91,19 @@ export class MarkdownParser { levels: [] }; - // Add document headers - keep all levels including empty ones for proper structure + // Build levels with folder segments first, then document headers const levels: string[] = []; - + + // Insert folder segments (subfolders under the customer folder) + if (folderSegments && folderSegments.length > 0) { + levels.push(...folderSegments); + } + + // Insert the project name as a level after folder segments (so it appears after subfolders) + if (projectName && projectName.length > 0) { + levels.push(projectName); + } + // Copy all header levels (including empty ones) to preserve hierarchy levels.push(...headers); diff --git a/src/types.ts b/src/types.ts index bb5e836..12e71b8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,8 @@ export const DEFAULT_SETTINGS: TaskExportSettings = { export interface TaskItem { customerName: string; projectName: string; - levels: string[]; // Hierarchical headers and parent tasks + // `levels` contains folder segments (subfolders under the customer), + // followed by document header levels (##, ###, ...), then parent task texts. + levels: string[]; task: string; // The actual task text }