mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
refactor(fluent): improve view mode persistence and configuration
- Add per-view view mode persistence to remember mode for each navigation item - Make workspace side leaves optional with default disabled - Improve project filtering with normalized comparison logic - Expose dataflow orchestrator early for event subscription - Enhance type safety across fluent components - Add missing event listeners for cache updates in ProjectList
This commit is contained in:
parent
fc7600c93a
commit
2f2f59acf8
19 changed files with 517 additions and 226 deletions
|
|
@ -1731,6 +1731,23 @@ export const DEFAULT_SETTINGS: TaskProgressBarSettings = {
|
|||
caseSensitive: false,
|
||||
},
|
||||
},
|
||||
|
||||
fluentView: {
|
||||
enableFluent: true,
|
||||
workspaces: [
|
||||
{
|
||||
id: "default",
|
||||
name: "Default",
|
||||
color: "#3498db",
|
||||
},
|
||||
],
|
||||
fluentConfig: {
|
||||
enableWorkspaces: true,
|
||||
defaultWorkspace: "default",
|
||||
maxOtherViewsBeforeOverflow: 5,
|
||||
},
|
||||
useWorkspaceSideLeaves: false,
|
||||
},
|
||||
};
|
||||
|
||||
// Helper function to get view settings safely
|
||||
|
|
|
|||
|
|
@ -35,15 +35,17 @@ export class FluentIntegration {
|
|||
(leaf: WorkspaceLeaf) => new FluentTaskView(leaf, this.plugin),
|
||||
);
|
||||
|
||||
if (this.plugin.settings.fluentView?.useWorkspaceSideLeaves) {
|
||||
this.plugin.registerView(
|
||||
TG_LEFT_SIDEBAR_VIEW_TYPE,
|
||||
(leaf: WorkspaceLeaf) => new LeftSidebarView(leaf, this.plugin),
|
||||
);
|
||||
this.plugin.registerView(
|
||||
TG_RIGHT_DETAIL_VIEW_TYPE,
|
||||
(leaf: WorkspaceLeaf) => new RightDetailView(leaf, this.plugin),
|
||||
);
|
||||
}
|
||||
// Register side leaf views for new architecture
|
||||
this.plugin.registerView(
|
||||
TG_LEFT_SIDEBAR_VIEW_TYPE,
|
||||
(leaf: WorkspaceLeaf) => new LeftSidebarView(leaf, this.plugin),
|
||||
);
|
||||
this.plugin.registerView(
|
||||
TG_RIGHT_DETAIL_VIEW_TYPE,
|
||||
(leaf: WorkspaceLeaf) => new RightDetailView(leaf, this.plugin),
|
||||
);
|
||||
|
||||
// When any of the fluent views becomes active, reveal the other side leaves without focusing them
|
||||
this.plugin.registerEvent(
|
||||
|
|
@ -102,7 +104,7 @@ export class FluentIntegration {
|
|||
/**
|
||||
* Open the Fluent view
|
||||
*/
|
||||
private async openV2View() {
|
||||
private async openFluentView() {
|
||||
const { workspace } = this.plugin.app;
|
||||
|
||||
// Check if Fluent view is already open
|
||||
|
|
@ -131,7 +133,25 @@ export class FluentIntegration {
|
|||
private async ensureSideLeavesIfEnabled() {
|
||||
const useSideLeaves =
|
||||
!!this.plugin.settings.fluentView?.useWorkspaceSideLeaves;
|
||||
if (!useSideLeaves) return;
|
||||
if (!useSideLeaves) {
|
||||
const leftSidebarLeaves = this.plugin.app.workspace.getLeavesOfType(
|
||||
TG_LEFT_SIDEBAR_VIEW_TYPE,
|
||||
);
|
||||
if (leftSidebarLeaves.length > 0) {
|
||||
this.plugin.app.workspace.detachLeavesOfType(
|
||||
TG_LEFT_SIDEBAR_VIEW_TYPE,
|
||||
);
|
||||
}
|
||||
const rightSidebarLeaves =
|
||||
this.plugin.app.workspace.getLeavesOfType(
|
||||
TG_RIGHT_DETAIL_VIEW_TYPE,
|
||||
);
|
||||
if (rightSidebarLeaves.length > 0) {
|
||||
this.plugin.app.workspace.detachLeavesOfType(
|
||||
TG_RIGHT_DETAIL_VIEW_TYPE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ws = this.plugin.app.workspace as Workspace;
|
||||
// Left sidebar
|
||||
|
|
@ -161,15 +181,15 @@ export class FluentIntegration {
|
|||
}
|
||||
|
||||
// Default workspace configuration
|
||||
if (!this.plugin.settings.fluentView!.workspaces) {
|
||||
this.plugin.settings.fluentView!.workspaces = [
|
||||
if (!this.plugin.settings.fluentView.workspaces) {
|
||||
this.plugin.settings.fluentView.workspaces = [
|
||||
{ id: "default", name: t("Default"), color: "#3498db" },
|
||||
];
|
||||
}
|
||||
|
||||
// Default Fluent configuration
|
||||
if (this.plugin.settings.fluentView!.fluentConfig === undefined) {
|
||||
this.plugin.settings.fluentView!.fluentConfig = {
|
||||
if (this.plugin.settings.fluentView.fluentConfig === undefined) {
|
||||
this.plugin.settings.fluentView.fluentConfig = {
|
||||
enableWorkspaces: true,
|
||||
defaultWorkspace: "default",
|
||||
maxOtherViewsBeforeOverflow: 5,
|
||||
|
|
@ -177,9 +197,9 @@ export class FluentIntegration {
|
|||
}
|
||||
|
||||
// Backfill extra experimental flag without touching types
|
||||
const v2c = this.plugin.settings.fluentView;
|
||||
if (v2c.useWorkspaceSideLeaves === undefined)
|
||||
v2c.useWorkspaceSideLeaves = true;
|
||||
const fluentSetting = this.plugin.settings.fluentView;
|
||||
if (fluentSetting.useWorkspaceSideLeaves === undefined)
|
||||
fluentSetting.useWorkspaceSideLeaves = false;
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
|
@ -204,13 +224,13 @@ export class FluentIntegration {
|
|||
enableFluent: false,
|
||||
};
|
||||
}
|
||||
this.plugin.settings.fluentView!.enableFluent =
|
||||
!this.plugin.settings.fluentView!.enableFluent;
|
||||
this.plugin.settings.fluentView.enableFluent =
|
||||
!this.plugin.settings.fluentView.enableFluent;
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
// Open the appropriate view
|
||||
if (this.plugin.settings.fluentView?.enableFluent) {
|
||||
await this.openV2View();
|
||||
await this.openFluentView();
|
||||
} else {
|
||||
// Open V1 view
|
||||
const leaf = workspace.getLeaf("tab");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
import { App, Component, setIcon, Menu, Notice, Modal, Platform } from "obsidian";
|
||||
import {
|
||||
App,
|
||||
Component,
|
||||
setIcon,
|
||||
Menu,
|
||||
Notice,
|
||||
Modal,
|
||||
Platform,
|
||||
} from "obsidian";
|
||||
import { WorkspaceSelector } from "./WorkspaceSelector";
|
||||
import { ProjectList } from "@/components/features/fluent/components/ProjectList";
|
||||
import {
|
||||
Project,
|
||||
ProjectList,
|
||||
} from "@/components/features/fluent/components/ProjectList";
|
||||
import { FluentTaskNavigationItem } from "@/types/fluent-types";
|
||||
import { WorkspaceData } from "@/types/workspace";
|
||||
import {
|
||||
|
|
@ -12,11 +23,7 @@ import TaskProgressBarPlugin from "@/index";
|
|||
import { t } from "@/translations/helper";
|
||||
import { ViewConfigModal } from "@/components/features/task/view/modals/ViewConfigModal";
|
||||
import { TASK_SPECIFIC_VIEW_TYPE } from "@/pages/TaskSpecificView";
|
||||
import {
|
||||
ViewConfig,
|
||||
ViewFilterRule,
|
||||
ViewMode,
|
||||
} from "@/common/setting-definition";
|
||||
import { ViewConfig, ViewFilterRule } from "@/common/setting-definition";
|
||||
|
||||
export class FluentSidebar extends Component {
|
||||
private containerEl: HTMLElement;
|
||||
|
|
@ -46,22 +53,7 @@ export class FluentSidebar extends Component {
|
|||
{ id: "flagged", label: t("Flagged"), icon: "flag", type: "primary" },
|
||||
];
|
||||
|
||||
private otherItems: FluentTaskNavigationItem[] = [
|
||||
{
|
||||
id: "calendar",
|
||||
label: t("Calendar"),
|
||||
icon: "calendar",
|
||||
type: "other",
|
||||
},
|
||||
{ id: "gantt", label: t("Gantt"), icon: "git-branch", type: "other" },
|
||||
{
|
||||
id: "review",
|
||||
label: t("Review"),
|
||||
icon: "check-square",
|
||||
type: "other",
|
||||
},
|
||||
{ id: "tags", label: t("Tags"), icon: "tag", type: "other" },
|
||||
];
|
||||
private otherItems: FluentTaskNavigationItem[] = [];
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
|
|
@ -111,9 +103,9 @@ export class FluentSidebar extends Component {
|
|||
// New Task Button
|
||||
const newTaskBtn = header.createEl("button", {
|
||||
cls: "fluent-new-task-btn",
|
||||
text: t("New Task"),
|
||||
});
|
||||
setIcon(newTaskBtn.createDiv({ cls: "fluent-new-task-icon" }), "plus");
|
||||
newTaskBtn.createDiv({ cls: "fluent-new-task-text" }, t("New Task"));
|
||||
this.registerDomEvent(newTaskBtn, "click", () =>
|
||||
this.onNavigate("new-task"),
|
||||
);
|
||||
|
|
@ -265,7 +257,7 @@ export class FluentSidebar extends Component {
|
|||
});
|
||||
|
||||
if (remainingOther.length > 0) {
|
||||
const moreBtn = this.railEl!.createDiv({
|
||||
const moreBtn = this.railEl.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": t("More views") },
|
||||
});
|
||||
|
|
@ -276,7 +268,7 @@ export class FluentSidebar extends Component {
|
|||
}
|
||||
|
||||
// Projects menu button
|
||||
const projBtn = this.railEl!.createDiv({
|
||||
const projBtn = this.railEl.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": t("Projects") },
|
||||
});
|
||||
|
|
@ -286,7 +278,7 @@ export class FluentSidebar extends Component {
|
|||
);
|
||||
|
||||
// Add (New Task) button
|
||||
const addBtn = this.railEl!.createDiv({
|
||||
const addBtn = this.railEl.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: { "aria-label": t("New Task") },
|
||||
});
|
||||
|
|
@ -534,14 +526,14 @@ export class FluentSidebar extends Component {
|
|||
|
||||
private showProjectMenu(event: MouseEvent) {
|
||||
// Try to use existing project list data; if missing, build a temporary one
|
||||
let projects: any[] = [];
|
||||
const anyList: any = this.projectList as any;
|
||||
let projects: Project[] = [];
|
||||
const anyList: ProjectList = this.projectList as ProjectList;
|
||||
if (anyList && typeof anyList.getProjects === "function") {
|
||||
projects = anyList.getProjects();
|
||||
} else {
|
||||
const temp = createDiv();
|
||||
const tempList: any = new ProjectList(
|
||||
temp as any,
|
||||
const tempList: ProjectList = new ProjectList(
|
||||
temp,
|
||||
this.plugin,
|
||||
this.onProjectSelect,
|
||||
);
|
||||
|
|
@ -555,7 +547,7 @@ export class FluentSidebar extends Component {
|
|||
item.setTitle(p.name)
|
||||
.setIcon("folder")
|
||||
.onClick(() => {
|
||||
this.onProjectSelect(p.id);
|
||||
this.onProjectSelect(p.filterKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ export class TopNavigation extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
private setViewMode(mode: ViewMode) {
|
||||
public setViewMode(mode: ViewMode) {
|
||||
this.currentViewMode = mode;
|
||||
|
||||
this.containerEl.querySelectorAll(".fluent-view-tab").forEach((tab) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, Platform, setIcon, Menu, Modal, App } from "obsidian";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { Task } from "@/types/task";
|
||||
import { getEffectiveProject } from "@/utils/task/task-operations";
|
||||
import {
|
||||
ProjectPopover,
|
||||
ProjectModal,
|
||||
|
|
@ -9,10 +10,12 @@ import {
|
|||
import type { CustomProject } from "@/common/setting-definition";
|
||||
import { t } from "@/translations/helper";
|
||||
import { onWorkspaceSwitched } from "@/components/features/fluent/events/ui-event";
|
||||
import { Events, on } from "@/dataflow/events/Events";
|
||||
|
||||
interface Project {
|
||||
export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
filterKey: string;
|
||||
displayName?: string;
|
||||
color: string;
|
||||
taskCount: number;
|
||||
|
|
@ -87,6 +90,23 @@ export class ProjectList extends Component {
|
|||
this.refreshWithDelay();
|
||||
}),
|
||||
);
|
||||
|
||||
// Refresh when dataflow cache becomes ready or updates
|
||||
this.registerEvent(
|
||||
on(this.plugin.app, Events.CACHE_READY, () => {
|
||||
this.refreshWithDelay();
|
||||
}),
|
||||
);
|
||||
this.registerEvent(
|
||||
on(this.plugin.app, Events.TASK_CACHE_UPDATED, () => {
|
||||
this.refreshWithDelay();
|
||||
}),
|
||||
);
|
||||
this.registerEvent(
|
||||
on(this.plugin.app, Events.PROJECT_DATA_UPDATED, () => {
|
||||
this.refreshWithDelay();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private refreshTimeoutId: NodeJS.Timeout | null = null;
|
||||
|
|
@ -132,23 +152,28 @@ export class ProjectList extends Component {
|
|||
const projectMap = new Map<string, Project>();
|
||||
|
||||
tasks.forEach((task: Task) => {
|
||||
const projectName = task.metadata?.project;
|
||||
if (projectName) {
|
||||
if (!projectMap.has(projectName)) {
|
||||
// Convert dashes back to spaces for display
|
||||
const displayName = projectName.replace(/-/g, " ");
|
||||
projectMap.set(projectName, {
|
||||
id: projectName,
|
||||
name: projectName,
|
||||
displayName: displayName,
|
||||
color: this.generateColorForProject(projectName),
|
||||
taskCount: 0,
|
||||
});
|
||||
}
|
||||
const project = projectMap.get(projectName);
|
||||
if (project) {
|
||||
project.taskCount++;
|
||||
}
|
||||
const projectName = getEffectiveProject(task);
|
||||
if (!projectName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const projectId = projectName;
|
||||
|
||||
if (!projectMap.has(projectId)) {
|
||||
// Convert dashes back to spaces for display to match legacy behaviour
|
||||
const displayName = projectName.replace(/-/g, " ");
|
||||
projectMap.set(projectId, {
|
||||
id: projectId,
|
||||
name: projectId,
|
||||
filterKey: projectId,
|
||||
displayName: displayName,
|
||||
color: this.generateColorForProject(projectId),
|
||||
taskCount: 0,
|
||||
});
|
||||
}
|
||||
const project = projectMap.get(projectId);
|
||||
if (project) {
|
||||
project.taskCount++;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -272,6 +297,7 @@ export class ProjectList extends Component {
|
|||
: {
|
||||
id: currentPath,
|
||||
name: currentPath,
|
||||
filterKey: currentPath,
|
||||
displayName: segment,
|
||||
color: this.generateColorForProject(
|
||||
currentPath,
|
||||
|
|
@ -301,6 +327,7 @@ export class ProjectList extends Component {
|
|||
} else if (isLeaf && node.project.isVirtual) {
|
||||
// Update virtual node with actual project data
|
||||
node.project = project;
|
||||
node.project.filterKey = project.filterKey;
|
||||
}
|
||||
|
||||
parentNode = node;
|
||||
|
|
@ -513,19 +540,19 @@ export class ProjectList extends Component {
|
|||
treeNode?: ProjectTreeNode,
|
||||
) {
|
||||
const projectItem = container.createDiv({
|
||||
cls: "fluent-project-item",
|
||||
attr: {
|
||||
"data-project-id": project.id,
|
||||
"data-level": String(level),
|
||||
},
|
||||
});
|
||||
cls: "fluent-project-item",
|
||||
attr: {
|
||||
"data-project-id": project.filterKey,
|
||||
"data-level": String(level),
|
||||
},
|
||||
});
|
||||
|
||||
// Add virtual class for styling
|
||||
if (project.isVirtual) {
|
||||
projectItem.addClass("is-virtual");
|
||||
// Add virtual class for styling
|
||||
if (project.isVirtual) {
|
||||
projectItem.addClass("is-virtual");
|
||||
}
|
||||
|
||||
if (this.activeProjectId === project.id) {
|
||||
if (this.activeProjectId === project.filterKey) {
|
||||
projectItem.addClass("is-active");
|
||||
}
|
||||
|
||||
|
|
@ -610,8 +637,8 @@ export class ProjectList extends Component {
|
|||
if (project.isVirtual && treeNode) {
|
||||
this.selectVirtualNode(treeNode);
|
||||
} else {
|
||||
this.setActiveProject(project.id);
|
||||
this.onProjectSelect(project.id);
|
||||
this.setActiveProject(project.filterKey);
|
||||
this.onProjectSelect(project.filterKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -634,7 +661,7 @@ export class ProjectList extends Component {
|
|||
const projectIds: string[] = [];
|
||||
const collectProjects = (n: ProjectTreeNode) => {
|
||||
if (!n.project.isVirtual) {
|
||||
projectIds.push(n.project.id);
|
||||
projectIds.push(n.project.filterKey);
|
||||
}
|
||||
n.children.forEach((child) => collectProjects(child));
|
||||
};
|
||||
|
|
@ -769,6 +796,7 @@ export class ProjectList extends Component {
|
|||
this.projects.push({
|
||||
id: customProject.id,
|
||||
name: customProject.name,
|
||||
filterKey: customProject.name,
|
||||
displayName:
|
||||
customProject.displayName || customProject.name,
|
||||
color: customProject.color,
|
||||
|
|
@ -779,6 +807,7 @@ export class ProjectList extends Component {
|
|||
} else {
|
||||
// Update existing project with custom color
|
||||
this.projects[existingIndex].id = customProject.id;
|
||||
this.projects[existingIndex].filterKey = customProject.name;
|
||||
this.projects[existingIndex].color = customProject.color;
|
||||
this.projects[existingIndex].displayName =
|
||||
customProject.displayName || customProject.name;
|
||||
|
|
@ -1038,7 +1067,7 @@ export class ProjectList extends Component {
|
|||
await this.plugin.saveSettings();
|
||||
|
||||
// If this was the active project, clear selection
|
||||
if (this.activeProjectId === project.id) {
|
||||
if (this.activeProjectId === project.filterKey) {
|
||||
this.setActiveProject(null);
|
||||
this.onProjectSelect("");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,6 +184,9 @@ export class FluentDataManager extends Component {
|
|||
const viewId = this.getCurrentViewId();
|
||||
let result = [...tasks]; // Copy array to avoid mutation
|
||||
|
||||
const normalizeProjectId = (value?: string | null): string =>
|
||||
(value ?? "").trim().toLowerCase();
|
||||
|
||||
// Status filter
|
||||
if (filters.status && filters.status !== "all") {
|
||||
switch (filters.status) {
|
||||
|
|
@ -217,17 +220,28 @@ export class FluentDataManager extends Component {
|
|||
|
||||
// Project filter - Skip for Inbox view
|
||||
if (filters.project && viewId !== "inbox") {
|
||||
result = result.filter(
|
||||
(task) => task.metadata?.project === filters.project,
|
||||
);
|
||||
const targetProject = normalizeProjectId(filters.project);
|
||||
result = result.filter((task) => {
|
||||
const directProject = normalizeProjectId(
|
||||
task.metadata?.project,
|
||||
);
|
||||
const tgProject = normalizeProjectId(
|
||||
task.metadata?.tgProject?.name,
|
||||
);
|
||||
return (
|
||||
targetProject.length > 0 &&
|
||||
(targetProject === directProject ||
|
||||
targetProject === tgProject)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Tags filter
|
||||
if (filters.tags && filters.tags.length > 0) {
|
||||
result = result.filter((task) => {
|
||||
if (!task.metadata?.tags) return false;
|
||||
return filters.tags!.some((tag: string) =>
|
||||
task.metadata!.tags!.includes(tag),
|
||||
return filters.tags.some((tag: string) =>
|
||||
task.metadata.tags.includes(tag),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -239,7 +253,7 @@ export class FluentDataManager extends Component {
|
|||
if (!task.metadata?.dueDate) return false;
|
||||
return (
|
||||
new Date(task.metadata.dueDate) >=
|
||||
filters.dateRange!.start!
|
||||
filters.dateRange.start
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -247,8 +261,7 @@ export class FluentDataManager extends Component {
|
|||
result = result.filter((task) => {
|
||||
if (!task.metadata?.dueDate) return false;
|
||||
return (
|
||||
new Date(task.metadata.dueDate) <=
|
||||
filters.dateRange!.end!
|
||||
new Date(task.metadata.dueDate) <= filters.dateRange.end
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -297,10 +310,7 @@ export class FluentDataManager extends Component {
|
|||
}, 400);
|
||||
|
||||
// Register dataflow event listeners
|
||||
if (
|
||||
isDataflowEnabled(this.plugin) &&
|
||||
this.plugin.dataflowOrchestrator
|
||||
) {
|
||||
if (isDataflowEnabled(this.plugin)) {
|
||||
// Listen for batch operation start
|
||||
this.registerEvent(
|
||||
on(this.plugin.app, Events.BATCH_OPERATION_START, (payload) => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
SelectableCard,
|
||||
SelectableCardConfig,
|
||||
} from "@/components/features/onboarding/ui/SelectableCard";
|
||||
import { FluentViewSettings } from "@/common/setting-definition";
|
||||
|
||||
export function renderInterfaceSettingsTab(
|
||||
settingTab: TaskProgressBarSettingTab,
|
||||
|
|
@ -113,7 +114,7 @@ export function renderInterfaceSettingsTab(
|
|||
|
||||
// Use workspace side leaves for Sidebar & Details
|
||||
new Setting(fluentSettingsContainer)
|
||||
.setName(t("Use Workspace Side Leaves"))
|
||||
.setName(t("Use workspace side leaves"))
|
||||
.setDesc(
|
||||
t(
|
||||
"Use left/right workspace side leaves for Sidebar and Details. When enabled, the main fluent view won't render in-view sidebar or details.",
|
||||
|
|
@ -122,7 +123,7 @@ export function renderInterfaceSettingsTab(
|
|||
.addToggle((toggle) => {
|
||||
const current =
|
||||
settingTab.plugin.settings.fluentView
|
||||
?.useWorkspaceSideLeaves ?? true;
|
||||
?.useWorkspaceSideLeaves ?? false;
|
||||
toggle.setValue(current).onChange(async (value) => {
|
||||
if (!settingTab.plugin.settings.fluentView) {
|
||||
settingTab.plugin.settings.fluentView = {
|
||||
|
|
@ -137,7 +138,8 @@ export function renderInterfaceSettingsTab(
|
|||
}
|
||||
// Store via 'any' to avoid typing constraints for experimental backfill
|
||||
(
|
||||
settingTab.plugin.settings.fluentView as any
|
||||
settingTab.plugin.settings
|
||||
.fluentView as FluentViewSettings
|
||||
).useWorkspaceSideLeaves = value;
|
||||
await settingTab.plugin.saveSettings();
|
||||
new Notice(t("Saved. Reopen the view to apply."));
|
||||
|
|
@ -146,7 +148,7 @@ export function renderInterfaceSettingsTab(
|
|||
|
||||
// Max Other Views before overflow threshold
|
||||
new Setting(fluentSettingsContainer)
|
||||
.setName(t("Max Other Views before overflow"))
|
||||
.setName(t("Max other views before overflow"))
|
||||
.setDesc(
|
||||
t(
|
||||
"Number of 'Other Views' to show before grouping the rest into an overflow menu (ellipsis)",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Manages multi-selection state for tasks and coordinates bulk operations
|
||||
*/
|
||||
|
||||
import { App, Component, Workspace } from "obsidian";
|
||||
import { App, Component, ItemView, Workspace } from "obsidian";
|
||||
import { Task } from "@/types/task";
|
||||
import {
|
||||
SelectionState,
|
||||
|
|
@ -23,6 +23,7 @@ export class TaskSelectionManager extends Component {
|
|||
constructor(
|
||||
private app: App,
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private view?: ItemView,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
|
@ -211,7 +212,7 @@ export class TaskSelectionManager extends Component {
|
|||
reason,
|
||||
};
|
||||
|
||||
(this.app.workspace as any).trigger(
|
||||
(this.app.workspace as Workspace).trigger(
|
||||
"task-genius:selection-mode-changed",
|
||||
eventData,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1119,12 +1119,12 @@ export class DataflowOrchestrator {
|
|||
let inlineCandidates: string[] = Array.from(
|
||||
this.suppressedInline,
|
||||
).filter((p) =>
|
||||
this.fileFilterManager!.shouldIncludePath(p, "inline"),
|
||||
this.fileFilterManager?.shouldIncludePath(p, "inline"),
|
||||
);
|
||||
const fileTaskCandidates: string[] = Array.from(
|
||||
this.suppressedFileTasks,
|
||||
).filter((p) =>
|
||||
this.fileFilterManager!.shouldIncludePath(p, "file"),
|
||||
this.fileFilterManager?.shouldIncludePath(p, "file"),
|
||||
);
|
||||
|
||||
// Fallback: if we have no suppressed inline candidates (e.g., previous session), derive from cache keys
|
||||
|
|
@ -1142,7 +1142,7 @@ export class DataflowOrchestrator {
|
|||
inlineCandidates = Array.from(union).filter(
|
||||
(p) =>
|
||||
!indexed.has(p) &&
|
||||
this.fileFilterManager!.shouldIncludePath(
|
||||
this.fileFilterManager?.shouldIncludePath(
|
||||
p,
|
||||
"inline",
|
||||
),
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ export async function createDataflow(
|
|||
plugin,
|
||||
projectOptions
|
||||
);
|
||||
// Expose orchestrator immediately so early consumers can subscribe to events
|
||||
plugin.dataflowOrchestrator = orchestrator;
|
||||
|
||||
console.log("[createDataflow] Initializing dataflow orchestrator...");
|
||||
try {
|
||||
|
|
@ -36,6 +38,9 @@ export async function createDataflow(
|
|||
console.log(`[createDataflow] Dataflow orchestrator ready (took ${elapsed}ms)`);
|
||||
} catch (error) {
|
||||
console.error("[createDataflow] Failed to initialize orchestrator:", error);
|
||||
if (plugin.dataflowOrchestrator === orchestrator) {
|
||||
plugin.dataflowOrchestrator = undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
|
@ -47,4 +52,4 @@ export async function createDataflow(
|
|||
*/
|
||||
export function isDataflowEnabled(plugin: TaskProgressBarPlugin): boolean {
|
||||
return plugin.settings.enableIndexer ?? true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
src/index.ts
24
src/index.ts
|
|
@ -333,11 +333,15 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
const deferSpecificLeaves = this.app.workspace.getLeavesOfType(
|
||||
TASK_SPECIFIC_VIEW_TYPE,
|
||||
);
|
||||
[...deferWorkspaceLeaves, ...deferSpecificLeaves].forEach(
|
||||
(leaf) => {
|
||||
leaf.loadIfDeferred();
|
||||
},
|
||||
);
|
||||
const deferTaskGeniusLeaves =
|
||||
this.app.workspace.getLeavesOfType(FLUENT_TASK_VIEW);
|
||||
[
|
||||
...deferWorkspaceLeaves,
|
||||
...deferSpecificLeaves,
|
||||
...deferTaskGeniusLeaves,
|
||||
].forEach((leaf) => {
|
||||
leaf.loadIfDeferred();
|
||||
});
|
||||
// Initialize Task Genius Icon Manager
|
||||
this.taskGeniusIconManager = new TaskGeniusIconManager(this);
|
||||
this.addChild(this.taskGeniusIconManager);
|
||||
|
|
@ -511,10 +515,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
this.registerOnboardingView();
|
||||
|
||||
this.registerView(
|
||||
TASK_VIEW_TYPE,
|
||||
(leaf) => new TaskView(leaf, this),
|
||||
);
|
||||
this.registerView(TASK_VIEW_TYPE, (leaf) => new TaskView(leaf, this));
|
||||
|
||||
this.registerView(
|
||||
TASK_SPECIFIC_VIEW_TYPE,
|
||||
|
|
@ -596,10 +597,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
}
|
||||
|
||||
const isBeta = targetVersion.toLowerCase().includes("beta");
|
||||
void this.changelogManager.openChangelog(
|
||||
targetVersion,
|
||||
isBeta,
|
||||
);
|
||||
void this.changelogManager.openChangelog(targetVersion, isBeta);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ import { Events, on } from "@/dataflow/events/Events";
|
|||
import { RootFilterState } from "@/components/features/task/filter/ViewTaskFilter";
|
||||
import { Platform } from "obsidian";
|
||||
import { t } from "@/translations/helper";
|
||||
import {
|
||||
getInitialViewMode,
|
||||
saveViewMode,
|
||||
} from "@/utils/ui/view-mode-utils";
|
||||
|
||||
// Import managers
|
||||
import { FluentDataManager } from "@/components/features/fluent/managers/FluentDataManager";
|
||||
|
|
@ -79,6 +83,7 @@ export class FluentTaskView extends ItemView {
|
|||
private viewState: FluentTaskViewState = {
|
||||
currentWorkspace: "",
|
||||
viewMode: "list",
|
||||
viewModeByViewId: {},
|
||||
searchQuery: "",
|
||||
filters: {},
|
||||
filterInputValue: "",
|
||||
|
|
@ -135,6 +140,65 @@ export class FluentTaskView extends ItemView {
|
|||
return !!this.plugin.settings.fluentView?.useWorkspaceSideLeaves;
|
||||
}
|
||||
|
||||
private ensureViewModeForView(viewId: string): ViewMode {
|
||||
const availableModes =
|
||||
this.componentManager?.getAvailableModesForView(viewId) ?? [];
|
||||
const storedMode = this.viewState.viewModeByViewId?.[viewId];
|
||||
|
||||
const pickFallback = (): ViewMode => {
|
||||
if (availableModes.length === 0) {
|
||||
return storedMode ?? "list";
|
||||
}
|
||||
if (availableModes.includes("list")) {
|
||||
return "list";
|
||||
}
|
||||
return availableModes[0];
|
||||
};
|
||||
|
||||
if (storedMode) {
|
||||
if (
|
||||
availableModes.length === 0 ||
|
||||
availableModes.includes(storedMode)
|
||||
) {
|
||||
return storedMode;
|
||||
}
|
||||
}
|
||||
|
||||
if (availableModes.includes("tree") || availableModes.includes("list")) {
|
||||
const prefersTree = getInitialViewMode(
|
||||
this.app,
|
||||
this.plugin,
|
||||
viewId,
|
||||
);
|
||||
if (prefersTree && availableModes.includes("tree")) {
|
||||
return "tree";
|
||||
}
|
||||
if (availableModes.includes("list")) {
|
||||
return "list";
|
||||
}
|
||||
}
|
||||
|
||||
return pickFallback();
|
||||
}
|
||||
|
||||
private recordViewModeForView(viewId: string, mode: ViewMode): void {
|
||||
if (!this.viewState.viewModeByViewId) {
|
||||
this.viewState.viewModeByViewId = {};
|
||||
}
|
||||
|
||||
const availableModes =
|
||||
this.componentManager?.getAvailableModesForView(viewId);
|
||||
if (
|
||||
availableModes &&
|
||||
availableModes.length > 0 &&
|
||||
!availableModes.includes(mode)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewState.viewModeByViewId[viewId] = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main initialization method
|
||||
*/
|
||||
|
|
@ -225,7 +289,28 @@ export class FluentTaskView extends ItemView {
|
|||
}) => {
|
||||
this.viewState.filters = filters;
|
||||
this.viewState.selectedProject = selectedProject;
|
||||
this.viewState.viewMode = viewMode;
|
||||
const normalizedMode =
|
||||
(() => {
|
||||
const availableModes =
|
||||
this.componentManager?.getAvailableModesForView(
|
||||
this.currentViewId,
|
||||
) ?? [];
|
||||
if (
|
||||
availableModes.length > 0 &&
|
||||
!availableModes.includes(viewMode)
|
||||
) {
|
||||
return this.ensureViewModeForView(
|
||||
this.currentViewId,
|
||||
);
|
||||
}
|
||||
return viewMode;
|
||||
})();
|
||||
this.viewState.viewMode = normalizedMode;
|
||||
this.recordViewModeForView(
|
||||
this.currentViewId,
|
||||
normalizedMode,
|
||||
);
|
||||
this.topNavigation?.setViewMode(normalizedMode);
|
||||
if (clearSearch) {
|
||||
this.viewState.searchQuery = "";
|
||||
this.viewState.filterInputValue = "";
|
||||
|
|
@ -384,7 +469,15 @@ export class FluentTaskView extends ItemView {
|
|||
this.updateView();
|
||||
},
|
||||
onNavigateToView: (viewId) => {
|
||||
this.recordViewModeForView(
|
||||
this.currentViewId,
|
||||
this.viewState.viewMode,
|
||||
);
|
||||
this.currentViewId = viewId;
|
||||
const nextMode = this.ensureViewModeForView(viewId);
|
||||
this.viewState.viewMode = nextMode;
|
||||
this.recordViewModeForView(viewId, nextMode);
|
||||
this.topNavigation?.setViewMode(nextMode);
|
||||
this.updateView();
|
||||
},
|
||||
onSearchQueryChanged: (query) => {
|
||||
|
|
@ -399,7 +492,19 @@ export class FluentTaskView extends ItemView {
|
|||
this.viewState.selectedProject = projectId;
|
||||
|
||||
// Switch to projects view
|
||||
this.recordViewModeForView(
|
||||
this.currentViewId,
|
||||
this.viewState.viewMode,
|
||||
);
|
||||
this.currentViewId = "projects";
|
||||
const projectsViewMode =
|
||||
this.ensureViewModeForView(this.currentViewId);
|
||||
this.viewState.viewMode = projectsViewMode;
|
||||
this.recordViewModeForView(
|
||||
this.currentViewId,
|
||||
projectsViewMode,
|
||||
);
|
||||
this.topNavigation?.setViewMode(projectsViewMode);
|
||||
|
||||
// Reflect selection into the Filter UI state so the top Filter button shows active and can be reset via "X"
|
||||
try {
|
||||
|
|
@ -463,6 +568,10 @@ export class FluentTaskView extends ItemView {
|
|||
},
|
||||
onViewModeChanged: (mode) => {
|
||||
this.viewState.viewMode = mode;
|
||||
this.recordViewModeForView(this.currentViewId, mode);
|
||||
if (mode === "list" || mode === "tree") {
|
||||
saveViewMode(this.app, this.currentViewId, mode === "tree");
|
||||
}
|
||||
this.updateView();
|
||||
// Save to workspace
|
||||
this.workspaceStateManager.saveFilterStateToWorkspace();
|
||||
|
|
@ -495,7 +604,11 @@ export class FluentTaskView extends ItemView {
|
|||
this.addChild(this.workspaceStateManager);
|
||||
|
||||
// 4. TaskSelectionManager - Multi-selection and bulk operations
|
||||
this.selectionManager = new TaskSelectionManager(this.app, this.plugin);
|
||||
this.selectionManager = new TaskSelectionManager(
|
||||
this.app,
|
||||
this.plugin,
|
||||
this,
|
||||
);
|
||||
this.addChild(this.selectionManager);
|
||||
|
||||
console.log("[TG] Managers initialized");
|
||||
|
|
@ -725,7 +838,30 @@ export class FluentTaskView extends ItemView {
|
|||
this.viewState.filters = filters;
|
||||
this.viewState.selectedProject =
|
||||
selectedProject;
|
||||
this.viewState.viewMode = viewMode;
|
||||
const normalizedMode =
|
||||
(() => {
|
||||
const availableModes =
|
||||
this.componentManager?.getAvailableModesForView(
|
||||
this.currentViewId,
|
||||
) ?? [];
|
||||
if (
|
||||
availableModes.length > 0 &&
|
||||
!availableModes.includes(viewMode)
|
||||
) {
|
||||
return this.ensureViewModeForView(
|
||||
this.currentViewId,
|
||||
);
|
||||
}
|
||||
return viewMode;
|
||||
})();
|
||||
this.viewState.viewMode = normalizedMode;
|
||||
this.recordViewModeForView(
|
||||
this.currentViewId,
|
||||
normalizedMode,
|
||||
);
|
||||
this.topNavigation?.setViewMode(
|
||||
normalizedMode,
|
||||
);
|
||||
if (clearSearch) {
|
||||
this.viewState.searchQuery = "";
|
||||
this.viewState.filterInputValue = "";
|
||||
|
|
@ -917,6 +1053,13 @@ export class FluentTaskView extends ItemView {
|
|||
// This is critical for bulk operations to work
|
||||
this.selectionManager.updateTaskCache(this.filteredTasks);
|
||||
|
||||
const resolvedMode = this.ensureViewModeForView(this.currentViewId);
|
||||
if (resolvedMode !== this.viewState.viewMode) {
|
||||
this.viewState.viewMode = resolvedMode;
|
||||
this.recordViewModeForView(this.currentViewId, resolvedMode);
|
||||
this.topNavigation?.setViewMode(resolvedMode);
|
||||
}
|
||||
|
||||
// Switch to appropriate component
|
||||
this.componentManager.switchView(
|
||||
this.currentViewId,
|
||||
|
|
|
|||
|
|
@ -3,13 +3,9 @@ import {
|
|||
PluginSettingTab,
|
||||
setIcon,
|
||||
ButtonComponent,
|
||||
Setting,
|
||||
Platform,
|
||||
requireApiVersion,
|
||||
debounce,
|
||||
Menu,
|
||||
Modal,
|
||||
Notice,
|
||||
} from "obsidian";
|
||||
import TaskProgressBarPlugin from ".";
|
||||
|
||||
|
|
@ -226,7 +222,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
// icon: "beaker",
|
||||
// category: "advanced",
|
||||
// },
|
||||
{id: "about", name: t("About"), icon: "info", category: "info"},
|
||||
{ id: "about", name: t("About"), icon: "info", category: "info" },
|
||||
];
|
||||
|
||||
constructor(app: App, plugin: TaskProgressBarPlugin) {
|
||||
|
|
@ -242,7 +238,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
if (plugin.dataflowOrchestrator) {
|
||||
// Call async updateSettings and await to ensure incremental reindex completes
|
||||
await plugin.dataflowOrchestrator.updateSettings(
|
||||
plugin.settings
|
||||
plugin.settings,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +249,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
await plugin.triggerViewUpdate();
|
||||
},
|
||||
100,
|
||||
true
|
||||
true,
|
||||
);
|
||||
|
||||
this.debouncedApplyNotifications = debounce(
|
||||
|
|
@ -264,7 +260,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
// Minimal view updates are unnecessary here
|
||||
},
|
||||
100,
|
||||
true
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +280,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
}
|
||||
this.searchComponent = new SettingsSearchComponent(
|
||||
this,
|
||||
this.containerEl
|
||||
this.containerEl,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +293,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
|
||||
// Group tabs by category
|
||||
const categories = {
|
||||
core: {name: t("Core Settings"), tabs: [] as typeof this.tabs},
|
||||
core: { name: t("Core Settings"), tabs: [] as typeof this.tabs },
|
||||
display: {
|
||||
name: t("Display & Progress"),
|
||||
tabs: [] as typeof this.tabs,
|
||||
|
|
@ -318,8 +314,8 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
name: t("Integration"),
|
||||
tabs: [] as typeof this.tabs,
|
||||
},
|
||||
advanced: {name: t("Advanced"), tabs: [] as typeof this.tabs},
|
||||
info: {name: t("Information"), tabs: [] as typeof this.tabs},
|
||||
advanced: { name: t("Advanced"), tabs: [] as typeof this.tabs },
|
||||
info: { name: t("Information"), tabs: [] as typeof this.tabs },
|
||||
};
|
||||
|
||||
// Group tabs by category
|
||||
|
|
@ -379,9 +375,9 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
labelEl.addClass("settings-tab-label");
|
||||
labelEl.setText(
|
||||
tab.name +
|
||||
(tab.id === "about"
|
||||
? " v" + this.plugin.manifest.version
|
||||
: "")
|
||||
(tab.id === "about"
|
||||
? " v" + this.plugin.manifest.version
|
||||
: ""),
|
||||
);
|
||||
|
||||
// Add click handler
|
||||
|
|
@ -414,7 +410,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
|
||||
// Show active section, hide others
|
||||
const sections = this.containerEl.querySelectorAll(
|
||||
".settings-tab-section"
|
||||
".settings-tab-section",
|
||||
);
|
||||
sections.forEach((section) => {
|
||||
if (section.getAttribute("data-tab-id") === tabId) {
|
||||
|
|
@ -428,10 +424,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
|
||||
// Handle tab container and header visibility based on selected tab
|
||||
const tabsContainer = this.containerEl.querySelector(
|
||||
".settings-tabs-categorized-container"
|
||||
".settings-tabs-categorized-container",
|
||||
);
|
||||
const settingsHeader = this.containerEl.querySelector(
|
||||
".task-genius-settings-header"
|
||||
".task-genius-settings-header",
|
||||
);
|
||||
|
||||
if (tabId === "general") {
|
||||
|
|
@ -456,10 +452,11 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
if (tabId === "workspaces") {
|
||||
// Make sure the workspace section is visible even though the tab is hidden
|
||||
const workspaceSection = this.containerEl.querySelector(
|
||||
'[data-tab-id="workspaces"]'
|
||||
'[data-tab-id="workspaces"]',
|
||||
);
|
||||
if (workspaceSection) {
|
||||
(workspaceSection as unknown as HTMLElement).style.display = "block";
|
||||
(workspaceSection as unknown as HTMLElement).style.display =
|
||||
"block";
|
||||
workspaceSection.addClass("settings-tab-section-active");
|
||||
}
|
||||
}
|
||||
|
|
@ -476,7 +473,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
public navigateToTab(
|
||||
tabId: string,
|
||||
section?: string,
|
||||
search?: string
|
||||
search?: string,
|
||||
): void {
|
||||
// Set the current tab
|
||||
this.currentTab = tabId;
|
||||
|
|
@ -510,14 +507,14 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
headerText &&
|
||||
headerText.includes(sectionId.replace("-", " "))
|
||||
) {
|
||||
header.scrollIntoView({behavior: "smooth", block: "start"});
|
||||
header.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
});
|
||||
|
||||
// Special handling for MCP sections
|
||||
if (sectionId === "cursor" && this.currentTab === "mcp-integration") {
|
||||
const cursorSection = this.containerEl.querySelector(
|
||||
".mcp-client-section"
|
||||
".mcp-client-section",
|
||||
);
|
||||
if (cursorSection) {
|
||||
const header =
|
||||
|
|
@ -537,7 +534,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
private createTabSection(tabId: string): HTMLElement {
|
||||
// Get the sections container
|
||||
const sectionsContainer = this.containerEl.querySelector(
|
||||
".settings-tab-sections"
|
||||
".settings-tab-sections",
|
||||
);
|
||||
if (!sectionsContainer) return this.containerEl;
|
||||
|
||||
|
|
@ -570,7 +567,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
new IframeModal(
|
||||
this.app,
|
||||
url,
|
||||
`How to use — ${tabInfo?.name ?? tabId}`
|
||||
`How to use — ${tabInfo?.name ?? tabId}`,
|
||||
).open();
|
||||
} catch (e) {
|
||||
window.open(url);
|
||||
|
|
@ -607,7 +604,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
|
|
@ -698,7 +695,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
|
||||
// Notifications Tab
|
||||
const notificationsSection = this.createTabSection(
|
||||
"desktop-integration"
|
||||
"desktop-integration",
|
||||
);
|
||||
this.displayDesktopIntegrationSettings(notificationsSection);
|
||||
|
||||
|
|
@ -862,7 +859,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
() => {
|
||||
this.currentTab = "general";
|
||||
this.display();
|
||||
}
|
||||
},
|
||||
);
|
||||
icsSettingsComponent.display();
|
||||
}
|
||||
|
|
@ -873,7 +870,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
|
||||
private displayMcpSettings(containerEl: HTMLElement): void {
|
||||
renderMcpIntegrationSettingsTab(containerEl, this.plugin, () =>
|
||||
this.applySettingsUpdate()
|
||||
this.applySettingsUpdate(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -909,5 +906,4 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
private renderTaskTimerSettingsTab(containerEl: HTMLElement): void {
|
||||
renderTaskTimerSettingTab(this, containerEl);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,6 +280,13 @@
|
|||
gap: var(--size-4-2); /* Add spacing between events */
|
||||
}
|
||||
|
||||
.full-calendar-container .calendar-timeline-events-container.is-empty {
|
||||
color: var(--text-faint);
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
padding: var(--size-4-8);
|
||||
}
|
||||
|
||||
/* Style adjustments for events previously timed */
|
||||
.full-calendar-container .calendar-event-timed {
|
||||
/* Remove absolute positioning */
|
||||
|
|
@ -292,6 +299,37 @@
|
|||
/* Using gap on parent container now */
|
||||
/* margin-bottom: var(--size-4-2); */
|
||||
width: 100%; /* Ensure it takes full width */
|
||||
border-radius: var(--radius-s);
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.full-calendar-container .calendar-event-timed:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
/* Specific styles for day view timed events */
|
||||
.full-calendar-container .calendar-event-day-timed {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 2.5em;
|
||||
padding: var(--size-4-2);
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border-radius: var(--radius-s);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.full-calendar-container .calendar-event-day-timed:hover {
|
||||
background-color: var(--interactive-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-s);
|
||||
}
|
||||
|
||||
.full-calendar-container .calendar-event-day-timed.is-completed {
|
||||
background-color: var(--background-modifier-success-hover);
|
||||
opacity: 0.7;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* Keep internal styles */
|
||||
|
|
@ -300,16 +338,13 @@
|
|||
font-weight: bold;
|
||||
padding: 1px 3px;
|
||||
background-color: rgba(0, 0, 0, 0.1); /* Slight background for time */
|
||||
border-radius: var(--radius-s);
|
||||
margin-bottom: var(--size-4-1);
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.full-calendar-container .calendar-event-title {
|
||||
font-size: var(--font-ui-small);
|
||||
padding: 2px 3px;
|
||||
flex-grow: 1;
|
||||
/* Allow text wrapping within the event box */
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
|
||||
font-size: var(--font-ui-small);
|
||||
padding: 2px 3px;
|
||||
flex-grow: 1;
|
||||
|
|
|
|||
|
|
@ -432,6 +432,7 @@
|
|||
margin-bottom: 1rem;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.5;
|
||||
--icon-size: 48px;
|
||||
}
|
||||
|
||||
.tg-fluent-empty-title {
|
||||
|
|
@ -574,43 +575,43 @@
|
|||
height: 300px;
|
||||
}
|
||||
|
||||
.tg-fluent-button-primary {
|
||||
button.tg-fluent-button-primary {
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.tg-fluent-button-primary:hover {
|
||||
button.tg-fluent-button-primary:hover {
|
||||
background: var(--interactive-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--tg-shadow);
|
||||
}
|
||||
|
||||
.tg-fluent-button-secondary {
|
||||
button.tg-fluent-button-secondary {
|
||||
background: var(--background-secondary);
|
||||
color: var(--text-normal);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.tg-fluent-button-secondary:hover {
|
||||
button.tg-fluent-button-secondary:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.tg-fluent-button-ghost {
|
||||
button.tg-fluent-button-ghost {
|
||||
background: transparent;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.tg-fluent-button-ghost:hover {
|
||||
button.tg-fluent-button-ghost:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.tg-fluent-button-danger {
|
||||
button.tg-fluent-button-danger {
|
||||
background: var(--text-error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tg-fluent-button-danger:hover {
|
||||
button.tg-fluent-button-danger:hover {
|
||||
background: #dc2626;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--tg-shadow);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ export interface FluentTaskViewState {
|
|||
currentWorkspace: string;
|
||||
selectedProject?: string | null;
|
||||
viewMode: "list" | "kanban" | "tree" | "calendar";
|
||||
viewModeByViewId?: Record<
|
||||
string,
|
||||
"list" | "kanban" | "tree" | "calendar"
|
||||
>;
|
||||
searchQuery?: string;
|
||||
filterInputValue?: string;
|
||||
filters?: any;
|
||||
|
|
|
|||
6
src/types/obsidian-ex.d.ts
vendored
6
src/types/obsidian-ex.d.ts
vendored
|
|
@ -13,6 +13,7 @@ import {
|
|||
import { Component } from "obsidian";
|
||||
import { HabitProps } from "./habit-card";
|
||||
import { RootFilterState } from "@/components/features/task/filter/ViewTaskFilter";
|
||||
import { SelectionModeChangeEventData } from "@/types/selection";
|
||||
|
||||
interface Token extends EditorRange {
|
||||
/** @todo Documentation incomplete. */
|
||||
|
|
@ -458,6 +459,11 @@ declare module "obsidian" {
|
|||
count: number;
|
||||
},
|
||||
): void;
|
||||
|
||||
trigger(
|
||||
event: "task-genius:selection-mode-changed",
|
||||
payload: SelectionModeChangeEventData,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface WorkspaceLeaf {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ function parseDateFilterString(dateString: string): moment.Moment | null {
|
|||
export function isNotCompleted(
|
||||
plugin: TaskProgressBarPlugin,
|
||||
task: Task,
|
||||
viewId: ViewMode
|
||||
viewId: ViewMode,
|
||||
): boolean {
|
||||
const viewConfig = getViewSettingOrDefault(plugin, viewId);
|
||||
const abandonedStatus = plugin.settings.taskStatuses.abandoned.split("|");
|
||||
|
|
@ -111,7 +111,7 @@ export function isNotCompleted(
|
|||
export function isBlank(
|
||||
plugin: TaskProgressBarPlugin,
|
||||
task: Task,
|
||||
viewId: ViewMode
|
||||
viewId: ViewMode,
|
||||
): boolean {
|
||||
const viewConfig = getViewSettingOrDefault(plugin, viewId);
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ export function isBlank(
|
|||
*/
|
||||
export function applyAdvancedFilter(
|
||||
task: Task,
|
||||
filterState: RootFilterState
|
||||
filterState: RootFilterState,
|
||||
): boolean {
|
||||
// 如果没有过滤器组或过滤器组为空,返回所有任务
|
||||
if (!filterState.filterGroups || filterState.filterGroups.length === 0) {
|
||||
|
|
@ -205,7 +205,7 @@ function applyFilter(task: Task, filter: Filter): boolean {
|
|||
return applyPriorityFilter(
|
||||
task.metadata.priority,
|
||||
condition,
|
||||
value
|
||||
value,
|
||||
);
|
||||
case "dueDate":
|
||||
return applyDateFilter(task.metadata.dueDate, condition, value);
|
||||
|
|
@ -215,7 +215,7 @@ function applyFilter(task: Task, filter: Filter): boolean {
|
|||
return applyDateFilter(
|
||||
task.metadata.scheduledDate,
|
||||
condition,
|
||||
value
|
||||
value,
|
||||
);
|
||||
case "tags":
|
||||
return applyTagsFilter(task.metadata.tags, condition, value);
|
||||
|
|
@ -237,7 +237,7 @@ function applyFilter(task: Task, filter: Filter): boolean {
|
|||
function applyContentFilter(
|
||||
content: string,
|
||||
condition: string,
|
||||
value?: string
|
||||
value?: string,
|
||||
): boolean {
|
||||
if (!content) content = "";
|
||||
if (!value) value = "";
|
||||
|
|
@ -270,7 +270,7 @@ function applyContentFilter(
|
|||
function applyStatusFilter(
|
||||
status: string,
|
||||
condition: string,
|
||||
value?: string
|
||||
value?: string,
|
||||
): boolean {
|
||||
if (!status) status = "";
|
||||
if (!value) value = "";
|
||||
|
|
@ -299,7 +299,7 @@ function applyStatusFilter(
|
|||
function applyPriorityFilter(
|
||||
priority: number | undefined,
|
||||
condition: string,
|
||||
value?: string
|
||||
value?: string,
|
||||
): boolean {
|
||||
// 如果没有设置优先级,将其视为0
|
||||
const taskPriority = typeof priority === "number" ? priority : 0;
|
||||
|
|
@ -339,7 +339,7 @@ function applyPriorityFilter(
|
|||
function applyDateFilter(
|
||||
date: number | undefined,
|
||||
condition: string,
|
||||
value?: string
|
||||
value?: string,
|
||||
): boolean {
|
||||
// 处理空值条件
|
||||
switch (condition) {
|
||||
|
|
@ -390,7 +390,7 @@ function applyDateFilter(
|
|||
function applyTagsFilter(
|
||||
tags: string[],
|
||||
condition: string,
|
||||
value?: string
|
||||
value?: string,
|
||||
): boolean {
|
||||
if (!tags) tags = [];
|
||||
if (!value) value = "";
|
||||
|
|
@ -417,7 +417,7 @@ function applyTagsFilter(
|
|||
function applyFilePathFilter(
|
||||
filePath: string,
|
||||
condition: string,
|
||||
value?: string
|
||||
value?: string,
|
||||
): boolean {
|
||||
if (!filePath) filePath = "";
|
||||
if (!value) value = "";
|
||||
|
|
@ -450,7 +450,7 @@ function applyFilePathFilter(
|
|||
function applyProjectFilter(
|
||||
project: string | undefined,
|
||||
condition: string,
|
||||
value?: string
|
||||
value?: string,
|
||||
): boolean {
|
||||
const proj = (project ?? "").toLowerCase();
|
||||
const val = (value ?? "").toLowerCase();
|
||||
|
|
@ -499,7 +499,7 @@ export function filterTasks(
|
|||
allTasks: Task[],
|
||||
viewId: ViewMode,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
options: FilterOptions = {}
|
||||
options: FilterOptions = {},
|
||||
): Task[] {
|
||||
let filtered = [...allTasks];
|
||||
const viewConfig = getViewSettingOrDefault(plugin, viewId);
|
||||
|
|
@ -544,7 +544,7 @@ export function filterTasks(
|
|||
) {
|
||||
console.log("应用全局筛选器:", globalFilterRules.advancedFilter);
|
||||
filtered = filtered.filter((task) =>
|
||||
applyAdvancedFilter(task, globalFilterRules.advancedFilter!)
|
||||
applyAdvancedFilter(task, globalFilterRules.advancedFilter!),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -555,10 +555,10 @@ export function filterTasks(
|
|||
) {
|
||||
console.log(
|
||||
"应用视图配置中的基础高级过滤器:",
|
||||
filterRules.advancedFilter
|
||||
filterRules.advancedFilter,
|
||||
);
|
||||
filtered = filtered.filter((task) =>
|
||||
applyAdvancedFilter(task, filterRules.advancedFilter!)
|
||||
applyAdvancedFilter(task, filterRules.advancedFilter!),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -569,13 +569,13 @@ export function filterTasks(
|
|||
) {
|
||||
console.log("应用传入的实时高级过滤器:", options.advancedFilter);
|
||||
filtered = filtered.filter((task) =>
|
||||
applyAdvancedFilter(task, options.advancedFilter!)
|
||||
applyAdvancedFilter(task, options.advancedFilter!),
|
||||
);
|
||||
|
||||
// 如果有实时高级过滤器,应用基本规则后直接返回
|
||||
// 应用 isNotCompleted 过滤器(基于视图配置的 hideCompletedAndAbandonedTasks)
|
||||
filtered = filtered.filter((task) =>
|
||||
isNotCompleted(plugin, task, viewId)
|
||||
isNotCompleted(plugin, task, viewId),
|
||||
);
|
||||
|
||||
// 应用 isBlank 过滤器(基于视图配置的 filterBlanks)
|
||||
|
|
@ -590,8 +590,8 @@ export function filterTasks(
|
|||
task.metadata.project?.toLowerCase().includes(textFilter) ||
|
||||
task.metadata.context?.toLowerCase().includes(textFilter) ||
|
||||
task.metadata.tags?.some((tag) =>
|
||||
tag.toLowerCase().includes(textFilter)
|
||||
)
|
||||
tag.toLowerCase().includes(textFilter),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -605,16 +605,16 @@ export function filterTasks(
|
|||
if (filterRules.textContains) {
|
||||
const query = filterRules.textContains.toLowerCase();
|
||||
filtered = filtered.filter((task) =>
|
||||
task.content.toLowerCase().includes(query)
|
||||
task.content.toLowerCase().includes(query),
|
||||
);
|
||||
}
|
||||
if (filterRules.tagsInclude && filterRules.tagsInclude.length > 0) {
|
||||
filtered = filtered.filter((task) =>
|
||||
filterRules.tagsInclude?.some((tag) =>
|
||||
task.metadata.tags.some(
|
||||
(taskTag) => typeof taskTag === "string" && taskTag === tag
|
||||
)
|
||||
)
|
||||
(taskTag) => typeof taskTag === "string" && taskTag === tag,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (filterRules.tagsExclude && filterRules.tagsExclude.length > 0) {
|
||||
|
|
@ -625,11 +625,11 @@ export function filterTasks(
|
|||
|
||||
// Convert task tags to lowercase for case-insensitive comparison
|
||||
const taskTagsLower = task.metadata.tags.map((tag) =>
|
||||
tag.toLowerCase()
|
||||
tag.toLowerCase(),
|
||||
);
|
||||
|
||||
// Check if any excluded tag is in the task's tags
|
||||
return !filterRules.tagsExclude!.some((excludeTag) => {
|
||||
return !filterRules.tagsExclude?.some((excludeTag) => {
|
||||
const tagLower = excludeTag.toLowerCase();
|
||||
return (
|
||||
taskTagsLower.includes(tagLower) ||
|
||||
|
|
@ -641,7 +641,7 @@ export function filterTasks(
|
|||
if (filterRules.project) {
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
task.metadata.project?.trim() === filterRules.project?.trim()
|
||||
task.metadata.project?.trim() === filterRules.project?.trim(),
|
||||
);
|
||||
}
|
||||
if (filterRules.priority !== undefined) {
|
||||
|
|
@ -662,12 +662,12 @@ export function filterTasks(
|
|||
}
|
||||
if (filterRules.statusInclude && filterRules.statusInclude.length > 0) {
|
||||
filtered = filtered.filter((task) =>
|
||||
filterRules.statusInclude!.includes(task.status)
|
||||
filterRules.statusInclude?.includes(task.status),
|
||||
);
|
||||
}
|
||||
if (filterRules.statusExclude && filterRules.statusExclude.length > 0) {
|
||||
filtered = filtered.filter(
|
||||
(task) => !filterRules.statusExclude!.includes(task.status)
|
||||
(task) => !filterRules.statusExclude?.includes(task.status),
|
||||
);
|
||||
}
|
||||
// Path filters (Added based on content.ts logic)
|
||||
|
|
@ -677,7 +677,7 @@ export function filterTasks(
|
|||
.filter((p) => p.trim() !== "")
|
||||
.map((p) => p.trim().toLowerCase());
|
||||
filtered = filtered.filter((task) =>
|
||||
query.some((q) => task.filePath.toLowerCase().includes(q))
|
||||
query.some((q) => task.filePath.toLowerCase().includes(q)),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -699,7 +699,7 @@ export function filterTasks(
|
|||
filtered = filtered.filter((task) =>
|
||||
task.metadata.dueDate
|
||||
? moment(task.metadata.dueDate).isSame(targetDueDate, "day")
|
||||
: false
|
||||
: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -710,24 +710,24 @@ export function filterTasks(
|
|||
task.metadata.startDate
|
||||
? moment(task.metadata.startDate).isSame(
|
||||
targetStartDate,
|
||||
"day"
|
||||
)
|
||||
: false
|
||||
"day",
|
||||
)
|
||||
: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (filterRules.scheduledDate) {
|
||||
const targetScheduledDate = parseDateFilterString(
|
||||
filterRules.scheduledDate
|
||||
filterRules.scheduledDate,
|
||||
);
|
||||
if (targetScheduledDate) {
|
||||
filtered = filtered.filter((task) =>
|
||||
task.metadata.scheduledDate
|
||||
? moment(task.metadata.scheduledDate).isSame(
|
||||
targetScheduledDate,
|
||||
"day"
|
||||
)
|
||||
: false
|
||||
"day",
|
||||
)
|
||||
: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -751,7 +751,7 @@ export function filterTasks(
|
|||
(task) =>
|
||||
isToday(task.metadata?.dueDate) ||
|
||||
isToday(task.metadata?.scheduledDate) ||
|
||||
isToday(task.metadata?.startDate)
|
||||
isToday(task.metadata?.startDate),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -761,13 +761,13 @@ export function filterTasks(
|
|||
const inNext7Days = (d?: string | number | Date) =>
|
||||
d
|
||||
? moment(d).isAfter(start, "day") &&
|
||||
moment(d).isSameOrBefore(end, "day")
|
||||
moment(d).isSameOrBefore(end, "day")
|
||||
: false;
|
||||
filtered = filtered.filter(
|
||||
(task) =>
|
||||
inNext7Days(task.metadata?.dueDate) ||
|
||||
inNext7Days(task.metadata?.scheduledDate) ||
|
||||
inNext7Days(task.metadata?.startDate)
|
||||
inNext7Days(task.metadata?.startDate),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -776,7 +776,7 @@ export function filterTasks(
|
|||
(task) =>
|
||||
(task.metadata.priority ?? 0) >= 3 ||
|
||||
(task.metadata.tags?.includes("flagged") ?? false) ||
|
||||
(task.metadata.tags?.includes("#flagged") ?? false)
|
||||
(task.metadata.tags?.includes("#flagged") ?? false),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -801,8 +801,8 @@ export function filterTasks(
|
|||
task.metadata.project?.toLowerCase().includes(textFilter) ||
|
||||
task.metadata.context?.toLowerCase().includes(textFilter) ||
|
||||
task.metadata.tags?.some((tag) =>
|
||||
tag.toLowerCase().includes(textFilter)
|
||||
)
|
||||
tag.toLowerCase().includes(textFilter),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
60
styles.css
60
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue