From 555782b89bf3a1d22ccf3c57a72b00d316264a1b Mon Sep 17 00:00:00 2001 From: Quorafind Date: Thu, 4 Dec 2025 11:38:21 +0800 Subject: [PATCH] fix(view): update sidebar list based on filtered tasks in two-column views --- .../fluent/managers/FluentComponentManager.ts | 27 +++-- .../task/view/ProjectViewComponent.ts | 30 ++++-- .../features/task/view/TagViewComponent.ts | 58 ++++++---- .../features/task/view/TwoColumnViewBase.ts | 42 +++++--- src/components/features/task/view/projects.ts | 55 ++++++---- src/components/features/task/view/tags.ts | 102 +++++++++++------- 6 files changed, 197 insertions(+), 117 deletions(-) diff --git a/src/components/features/fluent/managers/FluentComponentManager.ts b/src/components/features/fluent/managers/FluentComponentManager.ts index c5602b38..6fc1567e 100644 --- a/src/components/features/fluent/managers/FluentComponentManager.ts +++ b/src/components/features/fluent/managers/FluentComponentManager.ts @@ -644,22 +644,28 @@ export class FluentComponentManager extends Component { // Set tasks on the component if (typeof targetComponent.setTasks === "function") { - // Special handling for components that need only all tasks (single parameter) - // Review and tags views need all tasks to build their indices - if (viewId === "review" || viewId === "tags") { + // Special handling for components that need filtered + all tasks + // Tags view: uses filtered tasks for tag index (left sidebar), all tasks for tree view lookup + if (viewId === "tags") { + console.log( + `[FluentComponent] Calling setTasks for ${viewId} with FILTERED tasks (${filteredTasks.length}) and ALL tasks (${tasks.length})`, + ); + targetComponent.setTasks(filteredTasks, tasks); + } else if (viewId === "review") { + // Review view still needs all tasks console.log( `[FluentComponent] Calling setTasks for ${viewId} with ALL tasks:`, tasks.length, ); targetComponent.setTasks(tasks); } else if (viewId === "projects" && !project) { - // Projects overview mode: pass ALL tasks to build project index - // and FILTERED tasks to apply filter to project task lists - // This ensures: left sidebar shows all projects, right panel shows filtered tasks + // Projects overview mode: standardized semantics + // First param: filtered tasks (for building sidebar project index) + // Second param: all tasks (for tree view parent-child lookup) console.log( - `[FluentComponent] Calling setTasks for projects with ALL tasks (${tasks.length}) and FILTERED tasks (${filteredTasks.length})`, + `[FluentComponent] Calling setTasks for projects with FILTERED tasks (${filteredTasks.length}) and ALL tasks (${tasks.length})`, ); - targetComponent.setTasks(tasks, filteredTasks); + targetComponent.setTasks(filteredTasks, tasks); } else { // Use filtered tasks let filteredTasksLocal = [...filteredTasks]; @@ -890,7 +896,10 @@ export class FluentComponentManager extends Component { if (target?.setTasks) { if (viewId === "projects" || this.isContentBasedView(viewId)) { target.setTasks(filteredTasks, tasks, true); - } else if (viewId === "review" || viewId === "tags") { + } else if (viewId === "tags") { + // Tags view: filtered tasks for tag index, all tasks for tree view lookup + target.setTasks(filteredTasks, tasks); + } else if (viewId === "review") { target.setTasks(tasks); } else { target.setTasks(filteredTasks); diff --git a/src/components/features/task/view/ProjectViewComponent.ts b/src/components/features/task/view/ProjectViewComponent.ts index 37bfd25b..e595ff42 100644 --- a/src/components/features/task/view/ProjectViewComponent.ts +++ b/src/components/features/task/view/ProjectViewComponent.ts @@ -43,13 +43,14 @@ export class ProjectViewComponent extends TwoColumnViewBase { /** * 重写基类中的索引构建方法,为项目创建索引 + * 使用 sourceTasks(筛选后的任务)构建索引,确保左侧栏只显示相关项目 */ protected buildItemsIndex(): void { // 清除现有索引 this.allProjectsMap.clear(); - // 为每个任务的项目建立索引,使用 getEffectiveProject 统一获取项目名 - this.allTasks.forEach((task) => { + // 使用 sourceTasks(筛选后的任务)为每个任务的项目建立索引 + this.sourceTasks.forEach((task) => { const projectName = getEffectiveProject(task); if (projectName) { if (!this.allProjectsMap.has(projectName)) { @@ -59,9 +60,12 @@ export class ProjectViewComponent extends TwoColumnViewBase { } }); - // 构建项目树结构 + // 构建项目树结构(同样使用 sourceTasks) const separator = this.plugin.settings.projectPathSeparator || "/"; - this.projectTree = buildProjectTreeFromTasks(this.allTasks, separator); + this.projectTree = buildProjectTreeFromTasks( + this.sourceTasks, + separator, + ); // 更新项目计数 if (this.countEl) { @@ -244,11 +248,12 @@ export class ProjectViewComponent extends TwoColumnViewBase { } // 根据视图模式使用不同的筛选逻辑 + // 使用 sourceTasks 进行筛选,保持外部过滤状态 if (this.viewMode === "tree" && this.projectTree) { // 树状模式:使用包含式筛选(选父含子) const separator = this.plugin.settings.projectPathSeparator || "/"; this.filteredTasks = filterTasksByProjectPaths( - this.allTasks, + this.sourceTasks, this.selectedItems.items, separator, ); @@ -265,8 +270,8 @@ export class ProjectViewComponent extends TwoColumnViewBase { } }); - // 将任务ID转换为实际任务对象 - this.filteredTasks = this.allTasks.filter((task) => + // 将任务ID转换为实际任务对象(从 sourceTasks 中筛选) + this.filteredTasks = this.sourceTasks.filter((task) => resultTaskIds.has(task.id), ); } @@ -358,6 +363,9 @@ export class ProjectViewComponent extends TwoColumnViewBase { * 更新任务 */ public updateTask(updatedTask: Task): void { + // 更新 allTasksMap + this.allTasksMap.set(updatedTask.id, updatedTask); + let needsFullRefresh = false; const taskIndex = this.allTasks.findIndex( (t) => t.id === updatedTask.id, @@ -376,6 +384,14 @@ export class ProjectViewComponent extends TwoColumnViewBase { needsFullRefresh = true; } + // 同时更新 sourceTasks(如果任务存在于其中) + const sourceIndex = this.sourceTasks.findIndex( + (t) => t.id === updatedTask.id, + ); + if (sourceIndex !== -1) { + this.sourceTasks[sourceIndex] = updatedTask; + } + // 如果项目更改或任务是新的,重建索引并完全刷新UI if (needsFullRefresh) { this.buildItemsIndex(); diff --git a/src/components/features/task/view/TagViewComponent.ts b/src/components/features/task/view/TagViewComponent.ts index 7a1720b2..512478a3 100644 --- a/src/components/features/task/view/TagViewComponent.ts +++ b/src/components/features/task/view/TagViewComponent.ts @@ -23,7 +23,7 @@ export class TagViewComponent extends TwoColumnViewBase { constructor( parentEl: HTMLElement, app: App, - plugin: TaskProgressBarPlugin + plugin: TaskProgressBarPlugin, ) { // 配置基类需要的参数 const config: TwoColumnViewConfig = { @@ -45,31 +45,32 @@ export class TagViewComponent extends TwoColumnViewBase { * @returns Normalized tag with # prefix */ private normalizeTag(tag: string): string { - if (typeof tag !== 'string') { + if (typeof tag !== "string") { return tag; } - + // Trim whitespace const trimmed = tag.trim(); - + // If empty or already starts with #, return as is - if (!trimmed || trimmed.startsWith('#')) { + if (!trimmed || trimmed.startsWith("#")) { return trimmed; } - + // Add # prefix return `#${trimmed}`; } /** * 重写基类中的索引构建方法,为标签创建索引 + * 使用 sourceTasks(筛选后的任务)构建索引,确保左侧栏只显示相关标签 */ protected buildItemsIndex(): void { // 清除已有索引 this.allTagsMap.clear(); - // 为每个任务的标签建立索引 - this.allTasks.forEach((task) => { + // 使用 sourceTasks(筛选后的任务)为每个任务的标签建立索引 + this.sourceTasks.forEach((task) => { if (task.metadata.tags && task.metadata.tags.length > 0) { task.metadata.tags.forEach((tag) => { // 跳过非字符串类型的标签 @@ -139,7 +140,7 @@ export class TagViewComponent extends TwoColumnViewBase { private renderTagHierarchy( node: Record, parentEl: HTMLElement, - level: number + level: number, ) { // 按字母排序键,但排除元数据属性 const keys = Object.keys(node) @@ -212,7 +213,7 @@ export class TagViewComponent extends TwoColumnViewBase { this.renderTagHierarchy( childNode, childrenContainer, - level + 1 + level + 1, ); } }); @@ -260,9 +261,9 @@ export class TagViewComponent extends TwoColumnViewBase { set.forEach((id) => resultTaskIds.add(id)); }); - // 将任务ID转换为实际任务对象 - this.filteredTasks = this.allTasks.filter((task) => - resultTaskIds.has(task.id) + // 将任务ID转换为实际任务对象(从 sourceTasks 中筛选,保持外部过滤状态) + this.filteredTasks = this.sourceTasks.filter((task) => + resultTaskIds.has(task.id), ); // 按优先级和截止日期排序 @@ -313,7 +314,7 @@ export class TagViewComponent extends TwoColumnViewBase { (taskTag) => // 跳过非字符串类型的标签 typeof taskTag === "string" && - (taskTag === tag || taskTag.startsWith(tag + "/")) + (taskTag === tag || taskTag.startsWith(tag + "/")), ); }); @@ -346,7 +347,7 @@ export class TagViewComponent extends TwoColumnViewBase { let title = t(this.config.rightColumnDefaultTitle); if (this.selectedItems.items.length > 1) { title = `${this.selectedItems.items.length} ${t( - this.config.multiSelectText + this.config.multiSelectText, )}`; } const countText = `${this.filteredTasks.length} ${t("tasks")}`; @@ -364,7 +365,7 @@ export class TagViewComponent extends TwoColumnViewBase { const toggleEl = headerEl.createDiv({ cls: "section-toggle" }); setIcon( toggleEl, - section.isExpanded ? "chevron-down" : "chevron-right" + section.isExpanded ? "chevron-down" : "chevron-right", ); const titleEl = headerEl.createDiv({ cls: "section-title" }); titleEl.setText(`#${section.tag.replace("#", "")}`); @@ -382,7 +383,7 @@ export class TagViewComponent extends TwoColumnViewBase { taskListEl, this.plugin, this.app, - this.config.rendererContext + this.config.rendererContext, ); section.renderer.onTaskSelected = this.onTaskSelected; section.renderer.onTaskCompleted = this.onTaskCompleted; @@ -393,7 +394,7 @@ export class TagViewComponent extends TwoColumnViewBase { section.tasks, this.isTreeView, this.allTasksMap, - t("No tasks found for this tag.") + t("No tasks found for this tag."), ); // 注册切换事件 @@ -401,7 +402,7 @@ export class TagViewComponent extends TwoColumnViewBase { section.isExpanded = !section.isExpanded; setIcon( toggleEl, - section.isExpanded ? "chevron-down" : "chevron-right" + section.isExpanded ? "chevron-down" : "chevron-right", ); section.isExpanded ? taskListEl.show() : taskListEl.hide(); }); @@ -446,9 +447,12 @@ export class TagViewComponent extends TwoColumnViewBase { * 更新任务 */ public updateTask(updatedTask: Task): void { + // 更新 allTasksMap + this.allTasksMap.set(updatedTask.id, updatedTask); + let needsFullRefresh = false; const taskIndex = this.allTasks.findIndex( - (t) => t.id === updatedTask.id + (t) => t.id === updatedTask.id, ); if (taskIndex !== -1) { @@ -469,6 +473,14 @@ export class TagViewComponent extends TwoColumnViewBase { needsFullRefresh = true; // 新任务,需要完全刷新 } + // 同时更新 sourceTasks(如果任务存在于其中) + const sourceIndex = this.sourceTasks.findIndex( + (t) => t.id === updatedTask.id, + ); + if (sourceIndex !== -1) { + this.sourceTasks[sourceIndex] = updatedTask; + } + // 如果标签变化或任务是新的,重建索引并完全刷新UI if (needsFullRefresh) { this.buildItemsIndex(); @@ -477,7 +489,7 @@ export class TagViewComponent extends TwoColumnViewBase { } else { // 否则,仅更新过滤列表中的任务 const filteredIndex = this.filteredTasks.findIndex( - (t) => t.id === updatedTask.id + (t) => t.id === updatedTask.id, ); if (filteredIndex !== -1) { this.filteredTasks[filteredIndex] = updatedTask; @@ -495,13 +507,13 @@ export class TagViewComponent extends TwoColumnViewBase { // 跳过非字符串类型的标签 typeof taskTag === "string" && (taskTag === section.tag || - taskTag.startsWith(section.tag + "/")) + taskTag.startsWith(section.tag + "/")), ) ) { // 检查任务是否实际存在于此分区的列表中 if ( section.tasks.some( - (t) => t.id === updatedTask.id + (t) => t.id === updatedTask.id, ) ) { section.renderer?.updateTask(updatedTask); diff --git a/src/components/features/task/view/TwoColumnViewBase.ts b/src/components/features/task/view/TwoColumnViewBase.ts index fe749c9e..08e4bdef 100644 --- a/src/components/features/task/view/TwoColumnViewBase.ts +++ b/src/components/features/task/view/TwoColumnViewBase.ts @@ -60,8 +60,9 @@ export abstract class TwoColumnViewBase extends Component { protected taskRenderer: TaskListRendererComponent | null = null; // State - protected allTasks: Task[] = []; - protected filteredTasks: Task[] = []; + protected sourceTasks: Task[] = []; // Tasks used for building left-side index (may be filtered) + protected allTasks: Task[] = []; // All tasks (for right-side tree view parent-child lookup) + protected filteredTasks: Task[] = []; // Tasks to display on right side (after left-side selection) protected selectedItems: SelectedItems = { items: [], tasks: [], @@ -82,7 +83,7 @@ export abstract class TwoColumnViewBase extends Component { protected parentEl: HTMLElement, protected app: App, protected plugin: TaskProgressBarPlugin, - protected config: TwoColumnViewConfig + protected config: TwoColumnViewConfig, ) { super(); } @@ -177,7 +178,7 @@ export abstract class TwoColumnViewBase extends Component { .onClick(() => { this.toggleLeftColumnVisibility(); }); - } + }, ); } @@ -214,7 +215,7 @@ export abstract class TwoColumnViewBase extends Component { this.taskListContainerEl, this.plugin, this.app, - this.config.rendererContext + this.config.rendererContext, ); // 连接事件处理器 @@ -234,12 +235,23 @@ export abstract class TwoColumnViewBase extends Component { }; } - public setTasks(tasks: Task[]) { - this.allTasks = tasks; + /** + * Set tasks for the two-column view + * @param tasks - Tasks for building left-side index (filtered tasks, shows only relevant items in sidebar) + * @param allTasks - All tasks for tree view parent-child relationship lookup (optional, defaults to tasks) + */ + public setTasks(tasks: Task[], allTasks?: Task[]) { + // tasks: used for building left-side index (filtered tasks) + // allTasks: all tasks (for right-side tree view parent-child lookup) + this.sourceTasks = tasks; + this.allTasks = allTasks && allTasks.length > 0 ? allTasks : tasks; + this.allTasksMap = new Map( + this.allTasks.map((task) => [task.id, task]), + ); this.buildItemsIndex(); this.renderItemsList(); - // 如果已选择项目,更新任务 + // If items are already selected, update tasks if (this.selectedItems.items.length > 0) { this.updateSelectedTasks(); } else { @@ -330,7 +342,7 @@ export abstract class TwoColumnViewBase extends Component { // Update the toggle button icon to match the initial state const viewToggleBtn = this.rightColumnEl?.querySelector( - ".view-toggle-btn" + ".view-toggle-btn", ) as HTMLElement; if (viewToggleBtn) { setIcon(viewToggleBtn, this.isTreeView ? "git-branch" : "list"); @@ -345,7 +357,7 @@ export abstract class TwoColumnViewBase extends Component { // Update toggle button icon const viewToggleBtn = this.rightColumnEl.querySelector( - ".view-toggle-btn" + ".view-toggle-btn", ) as HTMLElement; if (viewToggleBtn) { setIcon(viewToggleBtn, this.isTreeView ? "git-branch" : "list"); @@ -370,14 +382,14 @@ export abstract class TwoColumnViewBase extends Component { */ protected updateTaskListHeader(title: string, countText: string) { const taskHeaderEl = this.rightColumnEl.querySelector( - `.${this.config.classNamePrefix}-task-title` + `.${this.config.classNamePrefix}-task-title`, ); if (taskHeaderEl) { taskHeaderEl.textContent = title; } const taskCountEl = this.rightColumnEl.querySelector( - `.${this.config.classNamePrefix}-task-count` + `.${this.config.classNamePrefix}-task-count`, ); if (taskCountEl) { taskCountEl.textContent = countText; @@ -404,7 +416,7 @@ export abstract class TwoColumnViewBase extends Component { title = String(this.selectedItems.items[0]); } else if (this.selectedItems.items.length > 1) { title = `${this.selectedItems.items.length} ${t( - this.config.multiSelectText + this.config.multiSelectText, )}`; } const countText = `${this.filteredTasks.length} ${t("tasks")}`; @@ -412,7 +424,7 @@ export abstract class TwoColumnViewBase extends Component { console.log("filteredTasks", this.filteredTasks, this.isTreeView); this.allTasksMap = new Map( - this.allTasks.map((task) => [task.id, task]) + this.allTasks.map((task) => [task.id, task]), ); // Use renderer to display tasks if (this.taskRenderer) { @@ -420,7 +432,7 @@ export abstract class TwoColumnViewBase extends Component { this.filteredTasks, this.isTreeView, this.allTasksMap, - t("No tasks in the selected items") + t("No tasks in the selected items"), ); } } diff --git a/src/components/features/task/view/projects.ts b/src/components/features/task/view/projects.ts index e01eca0f..078c5408 100644 --- a/src/components/features/task/view/projects.ts +++ b/src/components/features/task/view/projects.ts @@ -54,9 +54,9 @@ export class ProjectsComponent extends Component { private escKeyHandler?: (e: KeyboardEvent) => void; // State - private allTasks: Task[] = []; - private filteredTasks: Task[] = []; - private externalFilteredTasks: Task[] | null = null; // Externally filtered tasks from FluentView + private sourceTasks: Task[] = []; // Tasks used for building left-side project index (filtered tasks) + private allTasks: Task[] = []; // All tasks (for tree view parent-child lookup) + private filteredTasks: Task[] = []; // Tasks to display on right side (after project selection) private selectedProjects: SelectedProjects = { projects: [], tasks: [], @@ -292,7 +292,7 @@ export class ProjectsComponent extends Component { this.app, this.plugin, { project: selectedProject }, - true + true, ).open(); } else { new Notice(t("Please select a single project first")); @@ -319,9 +319,15 @@ export class ProjectsComponent extends Component { this.updateTgProjectPropsButton(null); } - public setTasks(tasks: Task[], filteredTasks?: Task[]) { - this.allTasks = tasks; - this.externalFilteredTasks = filteredTasks || null; + /** + * Set tasks for the projects view + * @param tasks - Tasks for building left-side project index (filtered tasks, shows only relevant projects in sidebar) + * @param allTasks - All tasks for tree view parent-child relationship lookup (optional, defaults to tasks) + */ + public setTasks(tasks: Task[], allTasks?: Task[]) { + // Standardized semantics: tasks = visible/filtered (for sidebar), allTasks = context (for tree view) + this.sourceTasks = tasks; + this.allTasks = allTasks && allTasks.length > 0 ? allTasks : tasks; this.allTasksMap = new Map( this.allTasks.map((task) => [task.id, task]), ); @@ -346,8 +352,9 @@ export class ProjectsComponent extends Component { // Clear existing index this.allProjectsMap.clear(); - // Build a map of projects to task IDs - this.allTasks.forEach((task) => { + // Build a map of projects to task IDs using sourceTasks (filtered tasks) + // This ensures the sidebar only shows projects that have matching tasks + this.sourceTasks.forEach((task) => { const effectiveProject = getEffectiveProject(task); if (effectiveProject) { if (!this.allProjectsMap.has(effectiveProject)) { @@ -648,9 +655,10 @@ export class ProjectsComponent extends Component { } // Use the project filter utility for inclusive filtering in tree view + // Filter from sourceTasks to maintain external filter state if (this.isProjectTreeView) { this.filteredTasks = filterTasksByProjectPaths( - this.allTasks, + this.sourceTasks, this.selectedProjects.projects, this.plugin.settings.projectPathSeparator || "/", ); @@ -666,8 +674,8 @@ export class ProjectsComponent extends Component { } }); - // Convert task IDs to actual task objects - this.filteredTasks = this.allTasks.filter((task) => + // Convert task IDs to actual task objects (from sourceTasks) + this.filteredTasks = this.sourceTasks.filter((task) => resultTaskIds.has(task.id), ); } @@ -704,16 +712,8 @@ export class ProjectsComponent extends Component { }); } - // Apply external filter if provided (from FluentView filter system) - // This ensures project tasks respect global filter conditions - if (this.externalFilteredTasks) { - const externalFilteredIds = new Set( - this.externalFilteredTasks.map((t) => t.id), - ); - this.filteredTasks = this.filteredTasks.filter((task) => - externalFilteredIds.has(task.id), - ); - } + // Note: External filtering is now handled by using sourceTasks directly + // which already contains the filtered tasks from FluentView // Update the task list using the renderer this.renderTaskList(); @@ -1098,6 +1098,9 @@ export class ProjectsComponent extends Component { } public updateTask(updatedTask: Task) { + // Update allTasksMap + this.allTasksMap.set(updatedTask.id, updatedTask); + // Update in our main tasks list const taskIndex = this.allTasks.findIndex( (t) => t.id === updatedTask.id, @@ -1116,6 +1119,14 @@ export class ProjectsComponent extends Component { needsFullRefresh = true; } + // Also update sourceTasks if the task exists there + const sourceIndex = this.sourceTasks.findIndex( + (t) => t.id === updatedTask.id, + ); + if (sourceIndex !== -1) { + this.sourceTasks[sourceIndex] = updatedTask; + } + // If project changed or task is new, rebuild index and fully refresh UI if (needsFullRefresh) { this.buildProjectsIndex(); diff --git a/src/components/features/task/view/tags.ts b/src/components/features/task/view/tags.ts index 9ebdadc4..e8482e08 100644 --- a/src/components/features/task/view/tags.ts +++ b/src/components/features/task/view/tags.ts @@ -45,8 +45,9 @@ export class TagsComponent extends Component { private mainTaskRenderer: TaskListRendererComponent | null = null; // State - private allTasks: Task[] = []; - private filteredTasks: Task[] = []; + private allTasks: Task[] = []; // All tasks (for global lookup and tree view) + private sourceTasks: Task[] = []; // Tasks used for building tag index (filtered tasks) + private filteredTasks: Task[] = []; // Tasks filtered by selected tags private tagSections: TagSection[] = []; private selectedTags: SelectedTags = { tags: [], @@ -65,7 +66,7 @@ export class TagsComponent extends Component { onTaskCompleted?: (task: Task) => void; onTaskUpdate?: (task: Task, updatedTask: Task) => Promise; onTaskContextMenu?: (event: MouseEvent, task: Task) => void; - } = {} + } = {}, ) { super(); } @@ -178,7 +179,7 @@ export class TagsComponent extends Component { .onClick(() => { this.toggleLeftColumnVisibility(); }); - } + }, ); } @@ -209,10 +210,16 @@ export class TagsComponent extends Component { }); } - public setTasks(tasks: Task[]) { - this.allTasks = tasks; + /** + * Set tasks for the tags view + * @param tasks - Filtered tasks (used for building tag index - only shows tags from these tasks) + * @param allTasks - All tasks (used for global lookup in tree view) + */ + public setTasks(tasks: Task[], allTasks?: Task[]) { + this.sourceTasks = tasks; + this.allTasks = allTasks && allTasks.length > 0 ? allTasks : tasks; this.allTasksMap = new Map( - this.allTasks.map((task) => [task.id, task]) + this.allTasks.map((task) => [task.id, task]), ); this.buildTagsIndex(); this.renderTagsList(); @@ -230,8 +237,9 @@ export class TagsComponent extends Component { // Clear existing index this.allTagsMap.clear(); - // Build a map of tags to task IDs - this.allTasks.forEach((task) => { + // Build a map of tags to task IDs from sourceTasks (filtered tasks) + // This ensures the tag list only shows tags from filtered tasks + this.sourceTasks.forEach((task) => { if (task.metadata.tags && task.metadata.tags.length > 0) { task.metadata.tags.forEach((tag) => { // Skip non-string tags @@ -290,7 +298,7 @@ export class TagsComponent extends Component { private renderTagHierarchy( node: Record, parentEl: HTMLElement, - level: number + level: number, ) { // Sort keys alphabetically, but exclude metadata properties const keys = Object.keys(node) @@ -363,7 +371,7 @@ export class TagsComponent extends Component { this.renderTagHierarchy( childNode, childrenContainer, - level + 1 + level + 1, ); } }); @@ -388,7 +396,7 @@ export class TagsComponent extends Component { ) { this.cleanupRenderers(); this.renderEmptyTaskList( - t("Select a tag to see related tasks") + t("Select a tag to see related tasks"), ); return; } @@ -430,7 +438,7 @@ export class TagsComponent extends Component { if (this.selectedTags.tags.length === 0) { this.cleanupRenderers(); this.renderEmptyTaskList( - t("Select a tag to see related tasks") + t("Select a tag to see related tasks"), ); } } @@ -443,7 +451,7 @@ export class TagsComponent extends Component { this.isTreeView = getInitialViewMode(this.app, this.plugin, "tags"); // Update the toggle button icon to match the initial state const viewToggleBtn = this.taskContainerEl?.querySelector( - ".view-toggle-btn" + ".view-toggle-btn", ) as HTMLElement; if (viewToggleBtn) { setIcon(viewToggleBtn, this.isTreeView ? "git-branch" : "list"); @@ -455,7 +463,7 @@ export class TagsComponent extends Component { // Update toggle button icon const viewToggleBtn = this.taskContainerEl.querySelector( - ".view-toggle-btn" + ".view-toggle-btn", ) as HTMLElement; if (viewToggleBtn) { setIcon(viewToggleBtn, this.isTreeView ? "git-branch" : "list"); @@ -509,13 +517,14 @@ export class TagsComponent extends Component { set.forEach((id) => resultTaskIds.add(id)); }); - // Convert task IDs to actual task objects - this.filteredTasks = this.allTasks.filter((task) => - resultTaskIds.has(task.id) + // Convert task IDs to actual task objects from sourceTasks + // This ensures we only show tasks that match the current filter + this.filteredTasks = this.sourceTasks.filter((task) => + resultTaskIds.has(task.id), ); const viewConfig = this.plugin.settings.viewConfiguration.find( - (view) => view.id === "tags" + (view) => view.id === "tags", ); if ( viewConfig?.sortCriteria && @@ -524,7 +533,7 @@ export class TagsComponent extends Component { this.filteredTasks = sortTasks( this.filteredTasks, viewConfig.sortCriteria, - this.plugin.settings + this.plugin.settings, ); } else { this.filteredTasks.sort((a, b) => { @@ -573,7 +582,7 @@ export class TagsComponent extends Component { (taskTag) => // Skip non-string tags typeof taskTag === "string" && - (taskTag === tag || taskTag.startsWith(tag + "/")) + (taskTag === tag || taskTag.startsWith(tag + "/")), ); }); @@ -610,7 +619,7 @@ export class TagsComponent extends Component { if (this.selectedTags.tags.length === 1) { taskHeaderEl.textContent = `#${this.selectedTags.tags[0].replace( "#", - "" + "", )}`; } else if (this.selectedTags.tags.length > 1) { taskHeaderEl.textContent = `${ @@ -626,7 +635,7 @@ export class TagsComponent extends Component { if (taskCountEl) { // Use filteredTasks length for the total count across selections/sections taskCountEl.textContent = `${this.filteredTasks.length} ${t( - "tasks" + "tasks", )}`; } } @@ -684,7 +693,7 @@ export class TagsComponent extends Component { this.taskListContainerEl, this.plugin, this.app, - "tags" + "tags", ); this.params.onTaskSelected && (this.mainTaskRenderer.onTaskSelected = @@ -703,7 +712,7 @@ export class TagsComponent extends Component { this.isTreeView, this.allTasksMap, // Empty message handled above, so this shouldn't be shown - t("No tasks found.") + t("No tasks found."), ); } } @@ -720,7 +729,7 @@ export class TagsComponent extends Component { const toggleEl = headerEl.createDiv({ cls: "section-toggle" }); setIcon( toggleEl, - section.isExpanded ? "chevron-down" : "chevron-right" + section.isExpanded ? "chevron-down" : "chevron-right", ); const titleEl = headerEl.createDiv({ cls: "section-title" }); titleEl.setText(`#${section.tag.replace("#", "")}`); @@ -739,7 +748,7 @@ export class TagsComponent extends Component { taskListEl, // Render inside this section's container this.plugin, this.app, - "tags" + "tags", ); this.params.onTaskSelected && (section.renderer.onTaskSelected = this.params.onTaskSelected); @@ -757,7 +766,7 @@ export class TagsComponent extends Component { section.tasks, this.isTreeView, this.allTasksMap, - t("No tasks found for this tag.") + t("No tasks found for this tag."), ); // Register toggle event @@ -765,7 +774,7 @@ export class TagsComponent extends Component { section.isExpanded = !section.isExpanded; setIcon( toggleEl, - section.isExpanded ? "chevron-down" : "chevron-right" + section.isExpanded ? "chevron-down" : "chevron-right", ); section.isExpanded ? taskListEl.show() : taskListEl.hide(); }); @@ -787,13 +796,25 @@ export class TagsComponent extends Component { } public updateTask(updatedTask: Task) { + // Update global tasks map and list + const globalIndex = this.allTasks.findIndex( + (t) => t.id === updatedTask.id, + ); + if (globalIndex !== -1) { + this.allTasks[globalIndex] = updatedTask; + } else { + this.allTasks.push(updatedTask); + } + this.allTasksMap.set(updatedTask.id, updatedTask); + + // Check if we need to refresh the tag index (based on sourceTasks) let needsFullRefresh = false; - const taskIndex = this.allTasks.findIndex( - (t) => t.id === updatedTask.id + const sourceIndex = this.sourceTasks.findIndex( + (t) => t.id === updatedTask.id, ); - if (taskIndex !== -1) { - const oldTask = this.allTasks[taskIndex]; + if (sourceIndex !== -1) { + const oldTask = this.sourceTasks[sourceIndex]; // Check if tags changed, necessitating a rebuild/re-render const tagsChanged = !oldTask.metadata.tags || @@ -804,13 +825,12 @@ export class TagsComponent extends Component { if (tagsChanged) { needsFullRefresh = true; } - this.allTasks[taskIndex] = updatedTask; - } else { - this.allTasks.push(updatedTask); - needsFullRefresh = true; // New task, requires full refresh + this.sourceTasks[sourceIndex] = updatedTask; } + // Note: If the task is not in sourceTasks, it means it was filtered out + // and we don't need to update the tag index unless the filter changes - // If tags changed or task is new, rebuild index and fully refresh UI + // If tags changed, rebuild index and fully refresh UI if (needsFullRefresh) { this.buildTagsIndex(); this.renderTagsList(); // Update left sidebar @@ -818,7 +838,7 @@ export class TagsComponent extends Component { } else { // Otherwise, update the task in the filtered list const filteredIndex = this.filteredTasks.findIndex( - (t) => t.id === updatedTask.id + (t) => t.id === updatedTask.id, ); if (filteredIndex !== -1) { this.filteredTasks[filteredIndex] = updatedTask; @@ -835,13 +855,13 @@ export class TagsComponent extends Component { // Skip non-string tags typeof taskTag === "string" && (taskTag === section.tag || - taskTag.startsWith(section.tag + "/")) + taskTag.startsWith(section.tag + "/")), ) ) { // Check if the task is actually in this section's list if ( section.tasks.some( - (t) => t.id === updatedTask.id + (t) => t.id === updatedTask.id, ) ) { section.renderer?.updateTask(updatedTask);