mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
fix(view): update sidebar list based on filtered tasks in two-column views
This commit is contained in:
parent
d2845e687b
commit
555782b89b
6 changed files with 197 additions and 117 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue