mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
refactor(fluent): streamline filter panel integration and fix view mode initialization
- Remove standalone filter panel in favor of Obsidian view header integration - Fix view mode initialization to default to list view for content-based views - Add setIsTreeView method for external view mode control - Improve component initialization logging for better debugging - Remove unused patches documentation file - Fix TypeScript type issues with filter options
This commit is contained in:
parent
6b779f84a5
commit
846d3bbeb6
5 changed files with 474 additions and 189 deletions
|
|
@ -61,7 +61,7 @@ export class ContentComponent extends Component {
|
|||
private parentEl: HTMLElement,
|
||||
private app: App,
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private params: ContentComponentParams = {}
|
||||
private params: ContentComponentParams = {},
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
|
@ -147,7 +147,7 @@ export class ContentComponent extends Component {
|
|||
{
|
||||
root: this.taskListEl, // Observe within the task list container
|
||||
threshold: 0.1, // Trigger when 10% of the marker is visible
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -158,11 +158,11 @@ export class ContentComponent extends Component {
|
|||
this.isTreeView = getInitialViewMode(
|
||||
this.app,
|
||||
this.plugin,
|
||||
this.currentViewId
|
||||
this.currentViewId,
|
||||
);
|
||||
// Update the toggle button icon to match the initial state
|
||||
const viewToggleBtn = this.headerEl?.querySelector(
|
||||
".view-toggle-btn"
|
||||
".view-toggle-btn",
|
||||
) as HTMLElement;
|
||||
if (viewToggleBtn) {
|
||||
setIcon(viewToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
|
|
@ -172,7 +172,7 @@ export class ContentComponent extends Component {
|
|||
private toggleViewMode() {
|
||||
this.isTreeView = !this.isTreeView;
|
||||
const viewToggleBtn = this.headerEl.querySelector(
|
||||
".view-toggle-btn"
|
||||
".view-toggle-btn",
|
||||
) as HTMLElement;
|
||||
if (viewToggleBtn) {
|
||||
setIcon(viewToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
|
|
@ -182,10 +182,23 @@ export class ContentComponent extends Component {
|
|||
this.refreshTaskList(); // Refresh list completely on view mode change
|
||||
}
|
||||
|
||||
public setIsTreeView(isTree: boolean) {
|
||||
if (this.isTreeView !== isTree) {
|
||||
this.isTreeView = isTree;
|
||||
const viewToggleBtn = this.headerEl?.querySelector(
|
||||
".view-toggle-btn",
|
||||
) as HTMLElement;
|
||||
if (viewToggleBtn) {
|
||||
setIcon(viewToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
}
|
||||
this.refreshTaskList();
|
||||
}
|
||||
}
|
||||
|
||||
public setTasks(
|
||||
tasks: Task[],
|
||||
notFilteredTasks: Task[],
|
||||
forceRefresh: boolean = false
|
||||
forceRefresh: boolean = false,
|
||||
) {
|
||||
// Allow forced refresh for cases where we know the data has changed
|
||||
if (forceRefresh) {
|
||||
|
|
@ -203,7 +216,7 @@ export class ContentComponent extends Component {
|
|||
// If a force refresh is pending, skip non-forced updates
|
||||
if (this.pendingForceRefresh) {
|
||||
console.log(
|
||||
"ContentComponent: Skipping non-forced update, force refresh is pending"
|
||||
"ContentComponent: Skipping non-forced update, force refresh is pending",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -215,7 +228,7 @@ export class ContentComponent extends Component {
|
|||
this.notFilteredTasks === notFilteredTasks
|
||||
) {
|
||||
console.log(
|
||||
"ContentComponent: Same array references, skipping refresh"
|
||||
"ContentComponent: Same array references, skipping refresh",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -241,7 +254,7 @@ export class ContentComponent extends Component {
|
|||
}
|
||||
if (unchanged) {
|
||||
console.log(
|
||||
"ContentComponent: Tasks unchanged, skipping refresh"
|
||||
"ContentComponent: Tasks unchanged, skipping refresh",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -282,17 +295,17 @@ export class ContentComponent extends Component {
|
|||
this.allTasks,
|
||||
this.currentViewId,
|
||||
this.plugin,
|
||||
{ textQuery: this.filterInput?.value } // Pass text query from input
|
||||
{ textQuery: this.filterInput?.value }, // Pass text query from input
|
||||
);
|
||||
|
||||
const sortCriteria = this.plugin.settings.viewConfiguration.find(
|
||||
(view) => view.id === this.currentViewId
|
||||
(view) => view.id === this.currentViewId,
|
||||
)?.sortCriteria;
|
||||
if (sortCriteria && sortCriteria.length > 0) {
|
||||
this.filteredTasks = sortTasks(
|
||||
this.filteredTasks,
|
||||
sortCriteria,
|
||||
this.plugin.settings
|
||||
this.plugin.settings,
|
||||
);
|
||||
} else {
|
||||
// Default sorting: completed tasks last, then by priority, due date, content,
|
||||
|
|
@ -320,7 +333,7 @@ export class ContentComponent extends Component {
|
|||
});
|
||||
const contentCmp = collator.compare(
|
||||
a.content ?? "",
|
||||
b.content ?? ""
|
||||
b.content ?? "",
|
||||
);
|
||||
if (contentCmp !== 0) return contentCmp;
|
||||
// Lowest-priority tie-breakers to ensure stability across files
|
||||
|
|
@ -366,7 +379,7 @@ export class ContentComponent extends Component {
|
|||
// Defer rendering if container is not visible yet (e.g., view hidden during init)
|
||||
if (!this.isContainerVisible()) {
|
||||
console.warn(
|
||||
"ContentComponent: Cannot render: Container not visible. Queuing refresh..."
|
||||
"ContentComponent: Cannot render: Container not visible. Queuing refresh...",
|
||||
);
|
||||
this.pendingForceRefresh = true;
|
||||
if (!this.pendingVisibilityRetry) {
|
||||
|
|
@ -388,7 +401,7 @@ export class ContentComponent extends Component {
|
|||
this.pendingVisibilityRetry = false;
|
||||
this.visibilityRetryCount = 0;
|
||||
console.warn(
|
||||
"ContentComponent: Container still not visible after retries; will wait for next trigger."
|
||||
"ContentComponent: Container still not visible after retries; will wait for next trigger.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -400,7 +413,7 @@ export class ContentComponent extends Component {
|
|||
// If a render is already in progress, queue a refresh instead of skipping
|
||||
if (this.isRendering) {
|
||||
console.log(
|
||||
"ContentComponent: Already rendering, queueing a refresh"
|
||||
"ContentComponent: Already rendering, queueing a refresh",
|
||||
);
|
||||
this.pendingForceRefresh = true;
|
||||
return;
|
||||
|
|
@ -429,19 +442,19 @@ export class ContentComponent extends Component {
|
|||
const taskMap = new Map<string, Task>();
|
||||
// Add all non-filtered tasks to the taskMap
|
||||
this.notFilteredTasks.forEach((task) =>
|
||||
taskMap.set(task.id, task)
|
||||
taskMap.set(task.id, task),
|
||||
);
|
||||
this.rootTasks = tasksToTree(this.filteredTasks); // Calculate root tasks
|
||||
// Sort roots according to view's sort criteria (fallback to sensible defaults)
|
||||
const viewSortCriteria =
|
||||
this.plugin.settings.viewConfiguration.find(
|
||||
(view) => view.id === this.currentViewId
|
||||
(view) => view.id === this.currentViewId,
|
||||
)?.sortCriteria;
|
||||
if (viewSortCriteria && viewSortCriteria.length > 0) {
|
||||
this.rootTasks = sortTasks(
|
||||
this.rootTasks,
|
||||
viewSortCriteria,
|
||||
this.plugin.settings
|
||||
this.plugin.settings,
|
||||
);
|
||||
} else {
|
||||
// Default sorting: completed tasks last, then by priority, due date, content,
|
||||
|
|
@ -470,12 +483,12 @@ export class ContentComponent extends Component {
|
|||
});
|
||||
const contentCmp = collator.compare(
|
||||
a.content ?? "",
|
||||
b.content ?? ""
|
||||
b.content ?? "",
|
||||
);
|
||||
if (contentCmp !== 0) return contentCmp;
|
||||
// Lowest-priority tie-breakers to ensure stability across files
|
||||
const fp = (a.filePath || "").localeCompare(
|
||||
b.filePath || ""
|
||||
b.filePath || "",
|
||||
);
|
||||
if (fp !== 0) return fp;
|
||||
return (a.line ?? 0) - (b.line ?? 0);
|
||||
|
|
@ -518,7 +531,7 @@ export class ContentComponent extends Component {
|
|||
const containerRect = container.getBoundingClientRect();
|
||||
// Find first visible task item
|
||||
const items = Array.from(
|
||||
container.querySelectorAll<HTMLElement>(".task-item")
|
||||
container.querySelectorAll<HTMLElement>(".task-item"),
|
||||
);
|
||||
for (const el of items) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
|
|
@ -543,7 +556,7 @@ export class ContentComponent extends Component {
|
|||
// Try anchor-based restoration first
|
||||
if (state.anchorId) {
|
||||
const anchorEl = container.querySelector<HTMLElement>(
|
||||
`[data-task-id="${state.anchorId}"]`
|
||||
`[data-task-id="${state.anchorId}"]`,
|
||||
);
|
||||
if (anchorEl) {
|
||||
const desiredOffset = state.anchorOffset;
|
||||
|
|
@ -573,7 +586,7 @@ export class ContentComponent extends Component {
|
|||
task,
|
||||
this.currentViewId, // Pass currentViewId
|
||||
this.app,
|
||||
this.plugin
|
||||
this.plugin,
|
||||
);
|
||||
|
||||
// Attach event handlers
|
||||
|
|
@ -586,7 +599,7 @@ export class ContentComponent extends Component {
|
|||
console.log(
|
||||
"ContentComponent onTaskUpdate",
|
||||
originalTask.content,
|
||||
updatedTask.content
|
||||
updatedTask.content,
|
||||
);
|
||||
await this.params.onTaskUpdate(originalTask, updatedTask);
|
||||
}
|
||||
|
|
@ -623,7 +636,7 @@ export class ContentComponent extends Component {
|
|||
for (let i = start; i < end; i++) {
|
||||
const rootTask = this.rootTasks[i];
|
||||
const childTasks = this.notFilteredTasks.filter(
|
||||
(task) => task.metadata.parent === rootTask.id
|
||||
(task) => task.metadata.parent === rootTask.id,
|
||||
);
|
||||
|
||||
const treeComponent = new TaskTreeItemComponent(
|
||||
|
|
@ -633,7 +646,7 @@ export class ContentComponent extends Component {
|
|||
0,
|
||||
childTasks,
|
||||
taskMap,
|
||||
this.plugin
|
||||
this.plugin,
|
||||
);
|
||||
|
||||
// Attach event handlers
|
||||
|
|
@ -707,7 +720,7 @@ export class ContentComponent extends Component {
|
|||
// );
|
||||
const taskMap = new Map<string, Task>();
|
||||
this.filteredTasks.forEach((task) =>
|
||||
taskMap.set(task.id, task)
|
||||
taskMap.set(task.id, task),
|
||||
);
|
||||
this.loadRootTaskBatch(taskMap);
|
||||
} else {
|
||||
|
|
@ -769,12 +782,12 @@ export class ContentComponent extends Component {
|
|||
public updateTask(updatedTask: Task) {
|
||||
// 1) Update sources
|
||||
const taskIndexAll = this.allTasks.findIndex(
|
||||
(t) => t.id === updatedTask.id
|
||||
(t) => t.id === updatedTask.id,
|
||||
);
|
||||
if (taskIndexAll !== -1)
|
||||
this.allTasks[taskIndexAll] = { ...updatedTask };
|
||||
const taskIndexNotFiltered = this.notFilteredTasks.findIndex(
|
||||
(t) => t.id === updatedTask.id
|
||||
(t) => t.id === updatedTask.id,
|
||||
);
|
||||
if (taskIndexNotFiltered !== -1)
|
||||
this.notFilteredTasks[taskIndexNotFiltered] = { ...updatedTask };
|
||||
|
|
@ -785,17 +798,17 @@ export class ContentComponent extends Component {
|
|||
const prevLen = this.filteredTasks.length;
|
||||
this.applyFilters();
|
||||
const taskFromFiltered = this.filteredTasks.find(
|
||||
(t) => t.id === updatedTask.id
|
||||
(t) => t.id === updatedTask.id,
|
||||
);
|
||||
const taskStillVisible = !!taskFromFiltered;
|
||||
|
||||
// Helper: insert list item at correct position (list view)
|
||||
const insertListItem = (taskToInsert: Task) => {
|
||||
const compIds = new Set(
|
||||
this.taskComponents.map((c) => c.getTask().id)
|
||||
this.taskComponents.map((c) => c.getTask().id),
|
||||
);
|
||||
const sortedIndex = this.filteredTasks.findIndex(
|
||||
(t) => t.id === taskToInsert.id
|
||||
(t) => t.id === taskToInsert.id,
|
||||
);
|
||||
// Find the next rendered neighbor after sortedIndex
|
||||
let nextComp: any = null;
|
||||
|
|
@ -803,7 +816,7 @@ export class ContentComponent extends Component {
|
|||
const id = this.filteredTasks[i].id;
|
||||
if (compIds.has(id)) {
|
||||
nextComp = this.taskComponents.find(
|
||||
(c) => c.getTask().id === id
|
||||
(c) => c.getTask().id === id,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -813,7 +826,7 @@ export class ContentComponent extends Component {
|
|||
taskToInsert,
|
||||
this.currentViewId,
|
||||
this.app,
|
||||
this.plugin
|
||||
this.plugin,
|
||||
);
|
||||
// Attach events
|
||||
taskComponent.onTaskSelected = this.selectTask.bind(this);
|
||||
|
|
@ -833,7 +846,7 @@ export class ContentComponent extends Component {
|
|||
if (nextComp) {
|
||||
this.taskListEl.insertBefore(
|
||||
taskComponent.element,
|
||||
nextComp.element
|
||||
nextComp.element,
|
||||
);
|
||||
const idx = this.taskComponents.indexOf(nextComp);
|
||||
this.taskComponents.splice(idx, 0, taskComponent);
|
||||
|
|
@ -846,7 +859,7 @@ export class ContentComponent extends Component {
|
|||
// Helper: remove list item
|
||||
const removeListItem = (taskId: string) => {
|
||||
const idx = this.taskComponents.findIndex(
|
||||
(c) => c.getTask().id === taskId
|
||||
(c) => c.getTask().id === taskId,
|
||||
);
|
||||
if (idx >= 0) {
|
||||
const comp = this.taskComponents[idx];
|
||||
|
|
@ -867,7 +880,7 @@ export class ContentComponent extends Component {
|
|||
if (!this.isTreeView) {
|
||||
// List view: update in place or insert if new to view
|
||||
const comp = this.taskComponents.find(
|
||||
(c) => c.getTask().id === updatedTask.id
|
||||
(c) => c.getTask().id === updatedTask.id,
|
||||
);
|
||||
if (comp) {
|
||||
comp.updateTask(taskFromFiltered!);
|
||||
|
|
@ -877,7 +890,7 @@ export class ContentComponent extends Component {
|
|||
} else {
|
||||
// Tree view: update existing subtree or insert to parent/root
|
||||
const comp = this.treeComponents.find(
|
||||
(c) => c.getTask().id === updatedTask.id
|
||||
(c) => c.getTask().id === updatedTask.id,
|
||||
);
|
||||
if (comp) {
|
||||
comp.updateTask(taskFromFiltered!);
|
||||
|
|
@ -902,7 +915,7 @@ export class ContentComponent extends Component {
|
|||
const newChildren =
|
||||
this.notFilteredTasks.filter(
|
||||
(t) =>
|
||||
t.metadata.parent === parentId
|
||||
t.metadata.parent === parentId,
|
||||
);
|
||||
parentComp.updateChildTasks(newChildren);
|
||||
updated = true;
|
||||
|
|
@ -913,11 +926,11 @@ export class ContentComponent extends Component {
|
|||
// Root insertion
|
||||
const taskMap = new Map<string, Task>();
|
||||
this.notFilteredTasks.forEach((t) =>
|
||||
taskMap.set(t.id, t)
|
||||
taskMap.set(t.id, t),
|
||||
);
|
||||
const childTasks = this.notFilteredTasks.filter(
|
||||
(t) =>
|
||||
t.metadata.parent === taskFromFiltered!.id
|
||||
t.metadata.parent === taskFromFiltered!.id,
|
||||
);
|
||||
const newRoot = new TaskTreeItemComponent(
|
||||
taskFromFiltered!,
|
||||
|
|
@ -926,7 +939,7 @@ export class ContentComponent extends Component {
|
|||
0,
|
||||
childTasks,
|
||||
taskMap,
|
||||
this.plugin
|
||||
this.plugin,
|
||||
);
|
||||
newRoot.onTaskSelected = this.selectTask.bind(this);
|
||||
newRoot.onTaskCompleted = (t) => {
|
||||
|
|
@ -951,7 +964,7 @@ export class ContentComponent extends Component {
|
|||
if (
|
||||
rootComparator(
|
||||
taskFromFiltered!,
|
||||
this.treeComponents[i].getTask()
|
||||
this.treeComponents[i].getTask(),
|
||||
) < 0
|
||||
) {
|
||||
insertAt = i;
|
||||
|
|
@ -961,12 +974,12 @@ export class ContentComponent extends Component {
|
|||
if (insertAt < this.treeComponents.length) {
|
||||
this.taskListEl.insertBefore(
|
||||
newRoot.element,
|
||||
this.treeComponents[insertAt].element
|
||||
this.treeComponents[insertAt].element,
|
||||
);
|
||||
this.treeComponents.splice(
|
||||
insertAt,
|
||||
0,
|
||||
newRoot
|
||||
newRoot,
|
||||
);
|
||||
} else {
|
||||
this.taskListEl.appendChild(newRoot.element);
|
||||
|
|
@ -988,7 +1001,7 @@ export class ContentComponent extends Component {
|
|||
// Tree view removal
|
||||
// If root component exists, remove it
|
||||
const idx = this.treeComponents.findIndex(
|
||||
(c) => c.getTask().id === updatedTask.id
|
||||
(c) => c.getTask().id === updatedTask.id,
|
||||
);
|
||||
if (idx >= 0) {
|
||||
const comp = this.treeComponents[idx];
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -27,7 +27,6 @@ export class V2FilterPanel {
|
|||
private onFilterChange: (filters: FilterOptions) => void,
|
||||
private tasks: Task[] = [],
|
||||
) {
|
||||
this.createPanel();
|
||||
this.extractAvailableOptions();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -483,7 +483,9 @@ export function filterTasks(
|
|||
if (!isCalendarView && !isForecastView) {
|
||||
filtered = filtered.filter((task) => {
|
||||
// 识别 ICS 事件任务(优先从 metadata.source 读取,兼容 legacy source 字段)
|
||||
const metaSourceType = (task as any).metadata?.source?.type ?? (task as any).source?.type;
|
||||
const metaSourceType =
|
||||
(task as any).metadata?.source?.type ??
|
||||
(task as any).source?.type;
|
||||
const isIcsTask = metaSourceType === "ics";
|
||||
// 非 ICS 保留;ICS 在此类视图中过滤掉
|
||||
return !isIcsTask;
|
||||
|
|
@ -702,16 +704,47 @@ export function filterTasks(
|
|||
if (Object.keys(filterRules).length === 0) {
|
||||
// Only apply default logic if no rules were defined for this view
|
||||
switch (viewId) {
|
||||
case "inbox":
|
||||
case "inbox": {
|
||||
filtered = filtered.filter((task) => !hasProject(task));
|
||||
break;
|
||||
case "flagged":
|
||||
}
|
||||
case "today": {
|
||||
const today = moment().startOf("day");
|
||||
const isToday = (d?: string | number | Date) =>
|
||||
d ? moment(d).isSame(today, "day") : false;
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
isToday(task.metadata?.dueDate) ||
|
||||
isToday(task.metadata?.scheduledDate) ||
|
||||
isToday(task.metadata?.startDate)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "upcoming": {
|
||||
const start = moment().startOf("day");
|
||||
const end = moment().add(7, "days").endOf("day");
|
||||
const inNext7Days = (d?: string | number | Date) =>
|
||||
d
|
||||
? moment(d).isAfter(start, "day") &&
|
||||
moment(d).isSameOrBefore(end, "day")
|
||||
: false;
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
inNext7Days(task.metadata?.dueDate) ||
|
||||
inNext7Days(task.metadata?.scheduledDate) ||
|
||||
inNext7Days(task.metadata?.startDate)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "flagged": {
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
(task.metadata.priority ?? 0) >= 3 ||
|
||||
task.metadata.tags?.includes("flagged")
|
||||
(task.metadata.tags?.includes("flagged") ?? false) ||
|
||||
(task.metadata.tags?.includes("#flagged") ?? false)
|
||||
);
|
||||
break;
|
||||
}
|
||||
// Projects, Tags, Review logic are handled by their specific components / options
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue