fix(view): update sidebar list based on filtered tasks in two-column views

This commit is contained in:
Quorafind 2025-12-04 11:38:21 +08:00
parent d2845e687b
commit 555782b89b
6 changed files with 197 additions and 117 deletions

View file

@ -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);

View file

@ -43,13 +43,14 @@ export class ProjectViewComponent extends TwoColumnViewBase<string> {
/**
*
* 使 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<string> {
}
});
// 构建项目树结构
// 构建项目树结构(同样使用 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<string> {
}
// 根据视图模式使用不同的筛选逻辑
// 使用 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<string> {
}
});
// 将任务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<string> {
*
*/
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<string> {
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();

View file

@ -23,7 +23,7 @@ export class TagViewComponent extends TwoColumnViewBase<string> {
constructor(
parentEl: HTMLElement,
app: App,
plugin: TaskProgressBarPlugin
plugin: TaskProgressBarPlugin,
) {
// 配置基类需要的参数
const config: TwoColumnViewConfig = {
@ -45,31 +45,32 @@ export class TagViewComponent extends TwoColumnViewBase<string> {
* @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<string> {
private renderTagHierarchy(
node: Record<string, any>,
parentEl: HTMLElement,
level: number
level: number,
) {
// 按字母排序键,但排除元数据属性
const keys = Object.keys(node)
@ -212,7 +213,7 @@ export class TagViewComponent extends TwoColumnViewBase<string> {
this.renderTagHierarchy(
childNode,
childrenContainer,
level + 1
level + 1,
);
}
});
@ -260,9 +261,9 @@ export class TagViewComponent extends TwoColumnViewBase<string> {
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<string> {
(taskTag) =>
// 跳过非字符串类型的标签
typeof taskTag === "string" &&
(taskTag === tag || taskTag.startsWith(tag + "/"))
(taskTag === tag || taskTag.startsWith(tag + "/")),
);
});
@ -346,7 +347,7 @@ export class TagViewComponent extends TwoColumnViewBase<string> {
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<string> {
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<string> {
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<string> {
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<string> {
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<string> {
*
*/
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<string> {
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<string> {
} 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<string> {
// 跳过非字符串类型的标签
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);

View file

@ -60,8 +60,9 @@ export abstract class TwoColumnViewBase<T extends string> 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<T> = {
items: [],
tasks: [],
@ -82,7 +83,7 @@ export abstract class TwoColumnViewBase<T extends string> 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<T extends string> extends Component {
.onClick(() => {
this.toggleLeftColumnVisibility();
});
}
},
);
}
@ -214,7 +215,7 @@ export abstract class TwoColumnViewBase<T extends string> extends Component {
this.taskListContainerEl,
this.plugin,
this.app,
this.config.rendererContext
this.config.rendererContext,
);
// 连接事件处理器
@ -234,12 +235,23 @@ export abstract class TwoColumnViewBase<T extends string> 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<T extends string> 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<T extends string> 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<T extends string> 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<T extends string> 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<T extends string> 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<T extends string> extends Component {
this.filteredTasks,
this.isTreeView,
this.allTasksMap,
t("No tasks in the selected items")
t("No tasks in the selected items"),
);
}
}

View file

@ -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();

View file

@ -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<void>;
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<string, any>,
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);