mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
refactor(bases): simplify view registration and upgrade to Obsidian 1.10
- Replace complex ViewManager with streamlined registration approach - Consolidate multiple specialized view classes into unified TaskBasesView - Upgrade Obsidian API from 1.8.7 to 1.10.0 for latest features - Add kanban config override support for Bases view integration - Update file task property mappings to align with dataview standards - Remove deprecated Bases API version checking from VersionManager
This commit is contained in:
parent
791f2cd9a9
commit
5583f4678c
21 changed files with 2282 additions and 5682 deletions
|
|
@ -62,7 +62,6 @@
|
|||
"jest-environment-jsdom": "^29.5.0",
|
||||
"jszip": "^3.10.1",
|
||||
"monkey-around": "^3.0.0",
|
||||
"obsidian": "^1.8.7",
|
||||
"release-it": "^19.0.4",
|
||||
"semver": "^7.7.2",
|
||||
"ts-jest": "^29.1.0",
|
||||
|
|
@ -76,6 +75,7 @@
|
|||
"date-fns": "^4.1.0",
|
||||
"localforage": "^1.10.0",
|
||||
"rrule": "^2.8.1",
|
||||
"obsidian": "^1.10.0",
|
||||
"obsidian-daily-notes-interface": "^0.9.4",
|
||||
"sortablejs": "^1.15.6",
|
||||
"regexp-match-indices": "^1.0.2"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ importers:
|
|||
localforage:
|
||||
specifier: ^1.10.0
|
||||
version: 1.10.0
|
||||
obsidian:
|
||||
specifier: ^1.10.0
|
||||
version: 1.10.0(@codemirror/state@6.5.2)(@codemirror/view@6.36.7)
|
||||
obsidian-daily-notes-interface:
|
||||
specifier: ^0.9.4
|
||||
version: 0.9.4(@codemirror/state@6.5.2)(@codemirror/view@6.36.7)
|
||||
|
|
@ -108,9 +111,6 @@ importers:
|
|||
monkey-around:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
obsidian:
|
||||
specifier: ^1.8.7
|
||||
version: 1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.36.7)
|
||||
release-it:
|
||||
specifier: ^19.0.4
|
||||
version: 19.0.4(@types/node@16.18.126)
|
||||
|
|
@ -2489,18 +2489,18 @@ packages:
|
|||
resolution: {integrity: sha512-PILoRtZUB5wEeGnDQAPMlkVlXwDYoxkLR8Wl4STU2zLNwhcq9kKvQexiXi7sfjGlpTnL+LeAOfEVWyeVndneKg==}
|
||||
hasBin: true
|
||||
|
||||
obsidian@1.8.7:
|
||||
resolution: {integrity: sha512-h4bWwNFAGRXlMlMAzdEiIM2ppTGlrh7uGOJS6w4gClrsjc+ei/3YAtU2VdFUlCiPuTHpY4aBpFJJW75S1Tl/JA==}
|
||||
obsidian@1.10.0:
|
||||
resolution: {integrity: sha512-F7hhnmGRQD1TanDPFT//LD3iKNUVd7N8sKL7flCCHRszfTxpDJ39j3T7LHbcGpyid906i6lD5oO+cnfLBzJMKw==}
|
||||
peerDependencies:
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
'@codemirror/state': 6.5.0
|
||||
'@codemirror/view': 6.38.1
|
||||
|
||||
obsidian@https://codeload.github.com/obsidianmd/obsidian-api/tar.gz/403d1e0e70a3d3dca0cf2f032d0e3baff138973a:
|
||||
resolution: {tarball: https://codeload.github.com/obsidianmd/obsidian-api/tar.gz/403d1e0e70a3d3dca0cf2f032d0e3baff138973a}
|
||||
version: 1.8.7
|
||||
obsidian@https://codeload.github.com/obsidianmd/obsidian-api/tar.gz/3a2161f440361a0c5466d5ebc4569998933e665b:
|
||||
resolution: {tarball: https://codeload.github.com/obsidianmd/obsidian-api/tar.gz/3a2161f440361a0c5466d5ebc4569998933e665b}
|
||||
version: 1.10.0
|
||||
peerDependencies:
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
'@codemirror/state': 6.5.0
|
||||
'@codemirror/view': 6.38.1
|
||||
|
||||
ohash@2.0.11:
|
||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||
|
|
@ -5969,20 +5969,20 @@ snapshots:
|
|||
|
||||
obsidian-daily-notes-interface@0.9.4(@codemirror/state@6.5.2)(@codemirror/view@6.36.7):
|
||||
dependencies:
|
||||
obsidian: https://codeload.github.com/obsidianmd/obsidian-api/tar.gz/403d1e0e70a3d3dca0cf2f032d0e3baff138973a(@codemirror/state@6.5.2)(@codemirror/view@6.36.7)
|
||||
obsidian: https://codeload.github.com/obsidianmd/obsidian-api/tar.gz/3a2161f440361a0c5466d5ebc4569998933e665b(@codemirror/state@6.5.2)(@codemirror/view@6.36.7)
|
||||
tslib: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- '@codemirror/state'
|
||||
- '@codemirror/view'
|
||||
|
||||
obsidian@1.8.7(@codemirror/state@6.5.2)(@codemirror/view@6.36.7):
|
||||
obsidian@1.10.0(@codemirror/state@6.5.2)(@codemirror/view@6.36.7):
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.7
|
||||
'@types/codemirror': 5.60.8
|
||||
moment: 2.29.4
|
||||
|
||||
obsidian@https://codeload.github.com/obsidianmd/obsidian-api/tar.gz/403d1e0e70a3d3dca0cf2f032d0e3baff138973a(@codemirror/state@6.5.2)(@codemirror/view@6.36.7):
|
||||
obsidian@https://codeload.github.com/obsidianmd/obsidian-api/tar.gz/3a2161f440361a0c5466d5ebc4569998933e665b(@codemirror/state@6.5.2)(@codemirror/view@6.36.7):
|
||||
dependencies:
|
||||
'@codemirror/state': 6.5.2
|
||||
'@codemirror/view': 6.36.7
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export class KanbanComponent extends Component {
|
|||
label: "Priority (High to Low)",
|
||||
};
|
||||
private hideEmptyColumns: boolean = false;
|
||||
private configOverride: Partial<KanbanSpecificConfig> | null = null; // Configuration override from Bases view
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
|
|
@ -101,6 +102,34 @@ export class KanbanComponent extends Component {
|
|||
this.params = params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration override from Bases view config
|
||||
*/
|
||||
public setConfigOverride(config: Partial<KanbanSpecificConfig> | null): void {
|
||||
const isChanged = this.hasConfigOverrideChanged(config);
|
||||
this.configOverride = config;
|
||||
console.log('[Kanban] setConfigOverride received', config);
|
||||
|
||||
if (isChanged) {
|
||||
// Refresh derived state from effective config (sort/hide/column order)
|
||||
this.loadKanbanConfig();
|
||||
const eff = this.getEffectiveKanbanConfig();
|
||||
console.log('[Kanban] effective config after override', eff);
|
||||
if (this.columnContainerEl) {
|
||||
// Rebuild columns with the new configuration so changes like groupBy
|
||||
// take effect immediately without requiring a data refresh.
|
||||
this.applyFiltersAndRender();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getEffectiveKanbanConfig(): KanbanSpecificConfig | undefined {
|
||||
const pluginConfig = this.plugin.settings.viewConfiguration.find(
|
||||
(v) => v.id === this.currentViewId
|
||||
)?.specificConfig as KanbanSpecificConfig;
|
||||
return this.configOverride ? {...(pluginConfig ?? {}), ...this.configOverride} : pluginConfig;
|
||||
}
|
||||
|
||||
override onload() {
|
||||
super.onload();
|
||||
this.containerEl.empty();
|
||||
|
|
@ -143,6 +172,27 @@ export class KanbanComponent extends Component {
|
|||
console.log("KanbanComponent unloaded.");
|
||||
}
|
||||
|
||||
private hasConfigOverrideChanged(
|
||||
nextConfig: Partial<KanbanSpecificConfig> | null
|
||||
): boolean {
|
||||
if (!this.configOverride && !nextConfig) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.configOverride || !nextConfig) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
return (
|
||||
JSON.stringify(this.configOverride) !== JSON.stringify(nextConfig)
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn("Failed to compare kanban config overrides:", error);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private renderControls(containerEl: HTMLElement) {
|
||||
// Create a controls container for sort and toggle controls
|
||||
const controlsContainer = containerEl.createDiv({
|
||||
|
|
@ -482,9 +532,9 @@ export class KanbanComponent extends Component {
|
|||
this.columns.forEach((col) => this.removeChild(col));
|
||||
this.columns = [];
|
||||
|
||||
const kanbanConfig = this.plugin.settings.viewConfiguration.find(
|
||||
(v) => v.id === this.currentViewId
|
||||
)?.specificConfig as KanbanSpecificConfig;
|
||||
// Resolve effective config (Bases override wins over plugin settings)
|
||||
const kanbanConfig = this.getEffectiveKanbanConfig();
|
||||
console.log('[Kanban] renderColumns effective config', kanbanConfig);
|
||||
|
||||
const groupBy = kanbanConfig?.groupBy || "status";
|
||||
|
||||
|
|
@ -746,8 +796,10 @@ export class KanbanComponent extends Component {
|
|||
}
|
||||
|
||||
private updateColumnVisibility() {
|
||||
const effective = this.getEffectiveKanbanConfig();
|
||||
const hideEmpty = effective?.hideEmptyColumns ?? this.hideEmptyColumns;
|
||||
this.columns.forEach((column) => {
|
||||
if (this.hideEmptyColumns && column.isEmpty()) {
|
||||
if (hideEmpty && column.isEmpty()) {
|
||||
column.setVisible(false);
|
||||
} else {
|
||||
column.setVisible(true);
|
||||
|
|
@ -885,7 +937,8 @@ export class KanbanComponent extends Component {
|
|||
(v) => v.id === this.currentViewId
|
||||
)?.specificConfig as KanbanSpecificConfig;
|
||||
|
||||
const groupBy = kanbanConfig?.groupBy || "status";
|
||||
const groupByPlugin = kanbanConfig?.groupBy || "status";
|
||||
const groupBy = (this.getEffectiveKanbanConfig()?.groupBy) || groupByPlugin;
|
||||
|
||||
if (groupBy === "status") {
|
||||
// Handle status-based grouping (original logic)
|
||||
|
|
@ -903,16 +956,18 @@ export class KanbanComponent extends Component {
|
|||
);
|
||||
}
|
||||
} else {
|
||||
const effectiveCustomColumns = (this.getEffectiveKanbanConfig()?.customColumns) || kanbanConfig?.customColumns;
|
||||
|
||||
// Handle property-based grouping
|
||||
const targetValue = this.getColumnValueFromTitle(
|
||||
targetColumnTitle,
|
||||
groupBy,
|
||||
kanbanConfig?.customColumns
|
||||
effectiveCustomColumns
|
||||
);
|
||||
const sourceValue = this.getColumnValueFromTitle(
|
||||
sourceColumnTitle,
|
||||
groupBy,
|
||||
kanbanConfig?.customColumns
|
||||
effectiveCustomColumns
|
||||
);
|
||||
console.log(
|
||||
`Kanban requesting ${groupBy} update for task ${taskId} from ${sourceValue} to value: ${targetValue}`
|
||||
|
|
@ -937,9 +992,7 @@ export class KanbanComponent extends Component {
|
|||
}
|
||||
|
||||
private loadKanbanConfig() {
|
||||
const kanbanConfig = this.plugin.settings.viewConfiguration.find(
|
||||
(v) => v.id === this.currentViewId
|
||||
)?.specificConfig as KanbanSpecificConfig;
|
||||
const kanbanConfig = this.getEffectiveKanbanConfig();
|
||||
|
||||
if (kanbanConfig) {
|
||||
this.hideEmptyColumns = kanbanConfig.hideEmptyColumns || false;
|
||||
|
|
@ -951,6 +1004,11 @@ export class KanbanComponent extends Component {
|
|||
kanbanConfig.defaultSortOrder || "desc"
|
||||
),
|
||||
};
|
||||
console.log('[Kanban] loadKanbanConfig applied', {
|
||||
hideEmptyColumns: this.hideEmptyColumns,
|
||||
defaultSortField: this.sortOption.field,
|
||||
defaultSortOrder: this.sortOption.order,
|
||||
});
|
||||
}
|
||||
|
||||
// Load saved column order
|
||||
|
|
@ -1488,9 +1546,7 @@ export class KanbanComponent extends Component {
|
|||
|
||||
// Column order management methods
|
||||
private getColumnOrderKey(): string {
|
||||
const kanbanConfig = this.plugin.settings.viewConfiguration.find(
|
||||
(v) => v.id === this.currentViewId
|
||||
)?.specificConfig as KanbanSpecificConfig;
|
||||
const kanbanConfig = this.getEffectiveKanbanConfig();
|
||||
const groupBy = kanbanConfig?.groupBy || "status";
|
||||
return `kanban-column-order-${this.currentViewId}-${groupBy}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export class TaskListItemComponent extends Component {
|
|||
|
||||
this.element = createEl("div", {
|
||||
cls: "task-item",
|
||||
attr: { "data-task-id": this.task.id },
|
||||
attr: {"data-task-id": this.task.id},
|
||||
});
|
||||
|
||||
this.settings = this.plugin.settings;
|
||||
|
|
@ -237,7 +237,7 @@ export class TaskListItemComponent extends Component {
|
|||
if (sanitizedPriority) {
|
||||
classes.push(`priority-${sanitizedPriority}`);
|
||||
}
|
||||
const priorityEl = createDiv({ cls: classes });
|
||||
const priorityEl = createDiv({cls: classes});
|
||||
|
||||
// Priority icon based on level
|
||||
let icon = "•";
|
||||
|
|
@ -399,10 +399,10 @@ export class TaskListItemComponent extends Component {
|
|||
dateText = this.settings.useRelativeTimeForDate
|
||||
? getRelativeTimeString(date)
|
||||
: date.toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
if (cssClass) {
|
||||
|
|
@ -422,14 +422,14 @@ export class TaskListItemComponent extends Component {
|
|||
type === "due"
|
||||
? "dueDate"
|
||||
: type === "scheduled"
|
||||
? "scheduledDate"
|
||||
: type === "start"
|
||||
? "startDate"
|
||||
: type === "cancelled"
|
||||
? "cancelledDate"
|
||||
: type === "completed"
|
||||
? "completedDate"
|
||||
: null;
|
||||
? "scheduledDate"
|
||||
: type === "start"
|
||||
? "startDate"
|
||||
: type === "cancelled"
|
||||
? "cancelledDate"
|
||||
: type === "completed"
|
||||
? "completedDate"
|
||||
: null;
|
||||
|
||||
if (fieldType) {
|
||||
this.getInlineEditor().showMetadataEditor(
|
||||
|
|
@ -618,7 +618,7 @@ export class TaskListItemComponent extends Component {
|
|||
// Create the add metadata button
|
||||
const addBtn = addButtonContainer.createEl("button", {
|
||||
cls: "add-metadata-btn",
|
||||
attr: { "aria-label": "Add metadata" },
|
||||
attr: {"aria-label": "Add metadata"},
|
||||
});
|
||||
setIcon(addBtn, "plus");
|
||||
|
||||
|
|
@ -636,19 +636,19 @@ export class TaskListItemComponent extends Component {
|
|||
const menu = new Menu();
|
||||
|
||||
const availableFields = [
|
||||
{ key: "project", label: "Project", icon: "folder" },
|
||||
{ key: "tags", label: "Tags", icon: "tag" },
|
||||
{ key: "context", label: "Context", icon: "at-sign" },
|
||||
{ key: "dueDate", label: "Due Date", icon: "calendar" },
|
||||
{ key: "startDate", label: "Start Date", icon: "play" },
|
||||
{ key: "scheduledDate", label: "Scheduled Date", icon: "clock" },
|
||||
{ key: "cancelledDate", label: "Cancelled Date", icon: "x" },
|
||||
{ key: "completedDate", label: "Completed Date", icon: "check" },
|
||||
{ key: "priority", label: "Priority", icon: "alert-triangle" },
|
||||
{ key: "recurrence", label: "Recurrence", icon: "repeat" },
|
||||
{ key: "onCompletion", label: "On Completion", icon: "flag" },
|
||||
{ key: "dependsOn", label: "Depends On", icon: "link" },
|
||||
{ key: "id", label: "Task ID", icon: "hash" },
|
||||
{key: "project", label: "Project", icon: "folder"},
|
||||
{key: "tags", label: "Tags", icon: "tag"},
|
||||
{key: "context", label: "Context", icon: "at-sign"},
|
||||
{key: "dueDate", label: "Due Date", icon: "calendar"},
|
||||
{key: "startDate", label: "Start Date", icon: "play"},
|
||||
{key: "scheduledDate", label: "Scheduled Date", icon: "clock"},
|
||||
{key: "cancelledDate", label: "Cancelled Date", icon: "x"},
|
||||
{key: "completedDate", label: "Completed Date", icon: "check"},
|
||||
{key: "priority", label: "Priority", icon: "alert-triangle"},
|
||||
{key: "recurrence", label: "Recurrence", icon: "repeat"},
|
||||
{key: "onCompletion", label: "On Completion", icon: "flag"},
|
||||
{key: "dependsOn", label: "Depends On", icon: "link"},
|
||||
{key: "id", label: "Task ID", icon: "hash"},
|
||||
];
|
||||
|
||||
// Filter out fields that already have values
|
||||
|
|
|
|||
35
src/index.ts
35
src/index.ts
|
|
@ -7,7 +7,6 @@ import {
|
|||
Notice,
|
||||
Platform,
|
||||
Plugin,
|
||||
requireApiVersion,
|
||||
} from "obsidian";
|
||||
import { taskProgressBarExtension } from "./editor-extensions/ui-widgets/progress-bar-widget";
|
||||
import { taskTimerExtension } from "./editor-extensions/date-time/task-timer";
|
||||
|
|
@ -87,7 +86,6 @@ import { sortTasksInDocument } from "./commands/sortTaskCommands";
|
|||
import { taskGutterExtension } from "./editor-extensions/task-operations/gutter-marker";
|
||||
import { autoDateManagerExtension } from "./editor-extensions/date-time/date-manager";
|
||||
import { taskMarkCleanupExtension } from "./editor-extensions/task-operations/mark-cleanup";
|
||||
import { ViewManager } from "./pages/ViewManager";
|
||||
import { IcsManager } from "./managers/ics-manager";
|
||||
import { FluentIntegration } from "./components/features/fluent/FluentIntegration";
|
||||
import { ObsidianUriHandler } from "./utils/ObsidianUriHandler";
|
||||
|
|
@ -98,6 +96,7 @@ import { OnboardingConfigManager } from "./managers/onboarding-manager";
|
|||
import { OnCompletionManager } from "./managers/completion-manager";
|
||||
import { SettingsChangeDetector } from "./services/settings-change-detector";
|
||||
import { ONBOARDING_VIEW_TYPE, OnboardingView, } from "./components/features/onboarding/OnboardingView";
|
||||
import { registerTaskGeniusBasesViews } from "@/pages/bases/registerBasesViews";
|
||||
import { TaskTimerExporter } from "./services/timer-export-service";
|
||||
import { TaskTimerManager } from "./managers/timer-manager";
|
||||
import { McpServerManager } from "./mcp/McpServerManager";
|
||||
|
|
@ -177,21 +176,10 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// V2 Integration instance
|
||||
v2Integration?: FluentIntegration;
|
||||
|
||||
// Uninstaller for workspace drag-and-drop monkey patch
|
||||
private uninstallWorkspaceDnD?: () => void;
|
||||
|
||||
async onload() {
|
||||
console.time("[TPB] onload");
|
||||
await this.loadSettings();
|
||||
|
||||
if (
|
||||
requireApiVersion("1.9.10") &&
|
||||
this.settings.betaTest?.enableBaseView
|
||||
) {
|
||||
const viewManager = new ViewManager(this.app, this);
|
||||
this.addChild(viewManager);
|
||||
}
|
||||
|
||||
// Initialize version manager first
|
||||
this.versionManager = new VersionManager(this.app, this);
|
||||
this.addChild(this.versionManager);
|
||||
|
|
@ -203,10 +191,6 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Initialize global suggest manager
|
||||
this.globalSuggestManager = new SuggestManager(this.app, this);
|
||||
|
||||
// Initialize workspace manager
|
||||
const {WorkspaceManager} = await import(
|
||||
"@/components/features/fluent/managers/WorkspaceManager"
|
||||
);
|
||||
this.workspaceManager = new WorkspaceManager(this);
|
||||
await this.workspaceManager.migrateToV2();
|
||||
this.workspaceManager.ensureDefaultWorkspaceInvariant();
|
||||
|
|
@ -269,6 +253,14 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
})
|
||||
);
|
||||
|
||||
// Register Bases views if Bases plugin is available
|
||||
try {
|
||||
registerTaskGeniusBasesViews(this);
|
||||
} catch (error) {
|
||||
console.log("Failed to register Bases views:", error);
|
||||
// Not critical if Bases plugin is not installed
|
||||
}
|
||||
|
||||
// Add a command to open the TaskView
|
||||
this.addCommand({
|
||||
id: "open-task-genius-view",
|
||||
|
|
@ -1509,6 +1501,8 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.globalSuggestManager.cleanup();
|
||||
}
|
||||
|
||||
// Bases views are automatically unregistered by Obsidian when plugin unloads
|
||||
|
||||
// Clean up dataflow orchestrator (experimental)
|
||||
if (this.dataflowOrchestrator) {
|
||||
this.dataflowOrchestrator.cleanup().catch((error) => {
|
||||
|
|
@ -1689,8 +1683,9 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
try {
|
||||
const {workspace} = this.app;
|
||||
|
||||
const viewType = this.settings.fluentView?.enableFluent ? FLUENT_TASK_VIEW : TASK_VIEW_TYPE;
|
||||
// Check if view is already open
|
||||
const existingLeaves = workspace.getLeavesOfType(TASK_VIEW_TYPE);
|
||||
const existingLeaves = workspace.getLeavesOfType(viewType);
|
||||
|
||||
if (existingLeaves.length > 0) {
|
||||
// If view is already open, just reveal the first one
|
||||
|
|
@ -1705,8 +1700,8 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Otherwise, create a new leaf and open the view
|
||||
const leaf = workspace.getLeaf("tab");
|
||||
await leaf.setViewState({type: TASK_VIEW_TYPE});
|
||||
workspace.revealLeaf(leaf);
|
||||
await leaf.setViewState({type: viewType});
|
||||
await workspace.revealLeaf(leaf);
|
||||
} finally {
|
||||
this.isActivatingView = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,12 +44,16 @@ interface BasesEntry {
|
|||
};
|
||||
lazyEvalCache: Record<string, any>;
|
||||
properties: Record<string, any>;
|
||||
|
||||
getValue(prop: {
|
||||
type: "property" | "file" | "formula";
|
||||
name: string;
|
||||
}): any;
|
||||
|
||||
updateProperty(key: string, value: any): void;
|
||||
|
||||
getFormulaValue(formula: string): any;
|
||||
|
||||
getPropertyKeys(): string[];
|
||||
}
|
||||
|
||||
|
|
@ -58,11 +62,11 @@ export const DEFAULT_FILE_TASK_MAPPING: FileTaskPropertyMapping = {
|
|||
contentProperty: "title",
|
||||
statusProperty: "status",
|
||||
completedProperty: "completed",
|
||||
createdDateProperty: "created", // dataview standard: created
|
||||
startDateProperty: "start", // dataview standard: start
|
||||
scheduledDateProperty: "scheduled", // dataview standard: scheduled
|
||||
dueDateProperty: "due", // dataview standard: due
|
||||
completedDateProperty: "completion", // dataview standard: completion
|
||||
createdDateProperty: "createdDate", // dataview standard: created
|
||||
startDateProperty: "startDate", // dataview standard: start
|
||||
scheduledDateProperty: "scheduledDate", // dataview standard: scheduled
|
||||
dueDateProperty: "dueDate", // dataview standard: due
|
||||
completedDateProperty: "completionDate", // dataview standard: completion
|
||||
recurrenceProperty: "repeat", // dataview standard: repeat
|
||||
tagsProperty: "tags",
|
||||
projectProperty: "project",
|
||||
|
|
@ -174,7 +178,7 @@ export class FileTaskManagerImpl implements FileTaskManager {
|
|||
|
||||
// Combine dates with time components to create enhanced datetime objects
|
||||
const enhancedDates = this.combineTimestampsWithTimeComponents(
|
||||
{ startDate, dueDate, scheduledDate, completedDate },
|
||||
{startDate, dueDate, scheduledDate, completedDate},
|
||||
enhancedMetadata.timeComponents
|
||||
);
|
||||
|
||||
|
|
@ -189,21 +193,21 @@ export class FileTaskManagerImpl implements FileTaskManager {
|
|||
children: [], // File tasks don't have children by default
|
||||
|
||||
// Optional properties
|
||||
...(createdDate && { createdDate }),
|
||||
...(startDate && { startDate }),
|
||||
...(scheduledDate && { scheduledDate }),
|
||||
...(dueDate && { dueDate }),
|
||||
...(completedDate && { completedDate }),
|
||||
...(recurrence && { recurrence }),
|
||||
...(project && { project }),
|
||||
...(context && { context }),
|
||||
...(priority && { priority }),
|
||||
...(estimatedTime && { estimatedTime }),
|
||||
...(actualTime && { actualTime }),
|
||||
...(createdDate && {createdDate}),
|
||||
...(startDate && {startDate}),
|
||||
...(scheduledDate && {scheduledDate}),
|
||||
...(dueDate && {dueDate}),
|
||||
...(completedDate && {completedDate}),
|
||||
...(recurrence && {recurrence}),
|
||||
...(project && {project}),
|
||||
...(context && {context}),
|
||||
...(priority && {priority}),
|
||||
...(estimatedTime && {estimatedTime}),
|
||||
...(actualTime && {actualTime}),
|
||||
|
||||
// Enhanced time components
|
||||
...enhancedMetadata,
|
||||
...(enhancedDates && { enhancedDates }),
|
||||
...(enhancedDates && {enhancedDates}),
|
||||
},
|
||||
sourceEntry: entry,
|
||||
isFileTask: true,
|
||||
|
|
@ -320,17 +324,17 @@ export class FileTaskManagerImpl implements FileTaskManager {
|
|||
updates: Partial<FileTask>
|
||||
): Promise<void> {
|
||||
// Merge updates into the task
|
||||
const updatedTask = { ...task, ...updates };
|
||||
const updatedTask = {...task, ...updates};
|
||||
let contentHandledSeparately = false;
|
||||
|
||||
// Handle content changes - re-extract time components if content changed
|
||||
if (updates.content && updates.content !== task.content) {
|
||||
await this.handleContentUpdate(task, updates.content);
|
||||
contentHandledSeparately = true;
|
||||
|
||||
|
||||
// Re-extract time components from updated content
|
||||
const enhancedMetadata = this.extractTimeComponents(updates.content);
|
||||
|
||||
|
||||
// Update the task's metadata with new time components
|
||||
if (enhancedMetadata.timeComponents) {
|
||||
updatedTask.metadata = {
|
||||
|
|
@ -671,7 +675,8 @@ export class FileTaskManagerImpl implements FileTaskManager {
|
|||
name: propertyName,
|
||||
});
|
||||
if (value !== null && value !== undefined) return String(value);
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
// 2) Fallback: direct properties/frontmatter/note.data
|
||||
try {
|
||||
const anyEntry: any = entry as any;
|
||||
|
|
@ -693,7 +698,8 @@ export class FileTaskManagerImpl implements FileTaskManager {
|
|||
) {
|
||||
return String(anyEntry.note.data[propertyName]);
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -849,7 +855,7 @@ export class FileTaskManagerImpl implements FileTaskManager {
|
|||
|
||||
try {
|
||||
// Parse time components from content
|
||||
const { timeComponents, errors, warnings } = this.timeParsingService.parseTimeComponents(content);
|
||||
const {timeComponents, errors, warnings} = this.timeParsingService.parseTimeComponents(content);
|
||||
|
||||
// Log warnings if any
|
||||
if (warnings.length > 0) {
|
||||
|
|
@ -927,13 +933,13 @@ export class FileTaskManagerImpl implements FileTaskManager {
|
|||
enhancedDates.scheduledDateTime = combineDateTime(dates.scheduledDate, timeComponents.scheduledTime);
|
||||
}
|
||||
|
||||
// If we have a due date but the time component is scheduledTime (common with "at" keyword),
|
||||
// If we have a due date but the time component is scheduledTime (common with "at" keyword),
|
||||
// create dueDateTime using scheduledTime
|
||||
if (dates.dueDate && !timeComponents.dueTime && timeComponents.scheduledTime) {
|
||||
enhancedDates.dueDateTime = combineDateTime(dates.dueDate, timeComponents.scheduledTime);
|
||||
}
|
||||
|
||||
// If we have a scheduled date but the time component is dueTime,
|
||||
// If we have a scheduled date but the time component is dueTime,
|
||||
// create scheduledDateTime using dueTime
|
||||
if (dates.scheduledDate && !timeComponents.scheduledTime && timeComponents.dueTime) {
|
||||
enhancedDates.scheduledDateTime = combineDateTime(dates.scheduledDate, timeComponents.dueTime);
|
||||
|
|
|
|||
|
|
@ -355,35 +355,4 @@ export class VersionManager extends Component {
|
|||
canWrite,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current Obsidian version supports a specific API version
|
||||
*/
|
||||
public isObsidianVersionSupported(requiredVersion: string): boolean {
|
||||
try {
|
||||
return requireApiVersion(requiredVersion)
|
||||
} catch (error) {
|
||||
console.error("Error checking Obsidian version support:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the new Bases API (registerBasesView) is supported
|
||||
*/
|
||||
public isNewBasesApiSupported(): boolean {
|
||||
try {
|
||||
// Check if Obsidian version is 1.9.10 or higher
|
||||
const hasVersionSupport = this.isObsidianVersionSupported("1.9.10");
|
||||
|
||||
// Check if the plugin has the registerBasesView method
|
||||
const hasMethodSupport =
|
||||
typeof (this.plugin as any).registerBasesView === "function";
|
||||
|
||||
return hasVersionSupport && hasMethodSupport;
|
||||
} catch (error) {
|
||||
console.error("Error checking new Bases API support:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,573 +0,0 @@
|
|||
/**
|
||||
* View Manager
|
||||
* 负责管理和注册自定义视图
|
||||
*/
|
||||
|
||||
import { App, Component } from "obsidian";
|
||||
import { FileTaskView } from "./bases/FileTaskView";
|
||||
import { InboxBasesView } from "./bases/InboxBasesView";
|
||||
import { FlaggedBasesView } from "./bases/FlaggedBasesView";
|
||||
import { ProjectBasesView } from "./bases/ProjectBasesView";
|
||||
import { TagsBasesView } from "./bases/TagsBasesView";
|
||||
import TaskProgressBarPlugin from "../index";
|
||||
import "../styles/base-view.css";
|
||||
import { requireApiVersion } from "obsidian";
|
||||
import { BasesPlugin, BasesViewRegistration, BaseView } from "@/types/bases";
|
||||
|
||||
export class ViewManager extends Component {
|
||||
private app: App;
|
||||
private basesPlugin: BasesPlugin | null = null;
|
||||
private registeredViews: Set<string> = new Set();
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
|
||||
constructor(app: App, plugin: TaskProgressBarPlugin) {
|
||||
super();
|
||||
this.app = app;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bases 插件实例
|
||||
*/
|
||||
private getBasesPlugin(): BasesPlugin | null {
|
||||
try {
|
||||
// 使用你提供的方法获取插件
|
||||
const internalPlugins = (this.app as any).internalPlugins?.plugins;
|
||||
if (internalPlugins && internalPlugins["bases"]) {
|
||||
this.basesPlugin = internalPlugins["bases"].instance;
|
||||
console.log(
|
||||
"[ViewManager] Bases plugin found via internalPlugins"
|
||||
);
|
||||
return this.basesPlugin;
|
||||
}
|
||||
|
||||
console.warn("[ViewManager] Bases plugin not found");
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("[ViewManager] Error getting Bases plugin:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the new Bases API (registerBasesView) is supported
|
||||
*/
|
||||
private isNewBasesApiSupported(): boolean {
|
||||
try {
|
||||
// Check if plugin has the new method
|
||||
const hasPluginMethod =
|
||||
typeof (this.plugin as any).registerBasesView === "function";
|
||||
|
||||
// Check version via VersionManager if available
|
||||
const versionManager = this.plugin.versionManager;
|
||||
const hasVersionSupport = versionManager
|
||||
? versionManager.isNewBasesApiSupported()
|
||||
: false;
|
||||
|
||||
console.log(
|
||||
`[ViewManager] New Bases API support - Plugin method: ${hasPluginMethod}, Version support: ${hasVersionSupport}`
|
||||
);
|
||||
|
||||
return hasPluginMethod || hasVersionSupport;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[ViewManager] Error checking new Bases API support:",
|
||||
error
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图管理器
|
||||
*/
|
||||
async initialize(): Promise<boolean> {
|
||||
console.log("[ViewManager] Initializing...");
|
||||
|
||||
const basesPlugin = this.getBasesPlugin();
|
||||
console.log(basesPlugin);
|
||||
if (!basesPlugin) {
|
||||
console.error(
|
||||
"[ViewManager] Cannot initialize without Bases plugin"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 注册所有自定义视图
|
||||
await this.registerAllViews();
|
||||
console.log("[ViewManager] Initialization completed successfully");
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("[ViewManager] Initialization failed:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册所有自定义视图
|
||||
*/
|
||||
private async registerAllViews(): Promise<void> {
|
||||
// 注册文件任务视图
|
||||
await this.registerFileTaskView();
|
||||
|
||||
// 注册专门的视图
|
||||
await this.registerInboxView();
|
||||
await this.registerFlaggedView();
|
||||
await this.registerProjectsView();
|
||||
await this.registerTagsView();
|
||||
|
||||
// 在这里可以注册更多视图
|
||||
// await this.registerTimelineView();
|
||||
// await this.registerKanbanView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve container element from different parameter formats in Bases API
|
||||
*/
|
||||
private resolveContainerEl(arg1: any, arg2?: any): HTMLElement {
|
||||
const candidates = [arg1, arg2];
|
||||
for (const c of candidates) {
|
||||
if (!c) continue;
|
||||
if (c instanceof HTMLElement) return c;
|
||||
if (typeof c === "object") {
|
||||
if ((c as any).containerEl instanceof HTMLElement)
|
||||
return (c as any).containerEl;
|
||||
if ((c as any).viewContainerEl instanceof HTMLElement)
|
||||
return (c as any).viewContainerEl;
|
||||
if (typeof (c as any).createDiv === "function")
|
||||
return c as any as HTMLElement;
|
||||
}
|
||||
}
|
||||
console.warn(
|
||||
"[ViewManager] resolveContainerEl: Could not resolve container element from",
|
||||
arg1,
|
||||
arg2
|
||||
);
|
||||
// Fallback to detached container to avoid hard failure; the view can still render
|
||||
return document.createElement("div");
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册文件任务视图
|
||||
*/
|
||||
private async registerFileTaskView(): Promise<void> {
|
||||
const viewId = "task-genius-view";
|
||||
|
||||
if (this.registeredViews.has(viewId)) {
|
||||
console.log(`[ViewManager] View ${viewId} already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const self = this;
|
||||
const factory = function (container: any): any {
|
||||
const containerEl = self.resolveContainerEl(
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
console.log(
|
||||
`[ViewManager] Creating ${viewId} instance`,
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
return new FileTaskView(containerEl, self.app, self.plugin);
|
||||
};
|
||||
|
||||
await this.registerView(
|
||||
viewId,
|
||||
factory as any,
|
||||
"Task Genius View",
|
||||
"task-genius"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ViewManager] Failed to register view ${viewId}:`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册收件箱视图
|
||||
*/
|
||||
private async registerInboxView(): Promise<void> {
|
||||
const viewId = "inbox-bases-view";
|
||||
|
||||
if (this.registeredViews.has(viewId)) {
|
||||
console.log(`[ViewManager] View ${viewId} already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const self = this;
|
||||
const factory = function (container: any): any {
|
||||
const containerEl = self.resolveContainerEl(
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
console.log(
|
||||
`[ViewManager] Creating ${viewId} instance`,
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
return new InboxBasesView(containerEl, self.app, self.plugin);
|
||||
};
|
||||
|
||||
await this.registerView(
|
||||
viewId,
|
||||
factory as any,
|
||||
"Inbox Tasks",
|
||||
"inbox"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ViewManager] Failed to register view ${viewId}:`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册标记任务视图
|
||||
*/
|
||||
private async registerFlaggedView(): Promise<void> {
|
||||
const viewId = "flagged-bases-view";
|
||||
|
||||
if (this.registeredViews.has(viewId)) {
|
||||
console.log(`[ViewManager] View ${viewId} already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const self = this;
|
||||
const factory = function (container: any): any {
|
||||
const containerEl = self.resolveContainerEl(
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
console.log(
|
||||
`[ViewManager] Creating ${viewId} instance`,
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
return new FlaggedBasesView(containerEl, self.app, self.plugin);
|
||||
};
|
||||
|
||||
await this.registerView(
|
||||
viewId,
|
||||
factory as any,
|
||||
"Flagged Tasks",
|
||||
"flag"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ViewManager] Failed to register view ${viewId}:`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册项目视图
|
||||
*/
|
||||
private async registerProjectsView(): Promise<void> {
|
||||
const viewId = "projects-bases-view";
|
||||
|
||||
if (this.registeredViews.has(viewId)) {
|
||||
console.log(`[ViewManager] View ${viewId} already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const self = this;
|
||||
const factory = function (container: any): any {
|
||||
const containerEl = self.resolveContainerEl(
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
console.log(
|
||||
`[ViewManager] Creating ${viewId} instance`,
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
return new ProjectBasesView(containerEl, self.app, self.plugin);
|
||||
};
|
||||
|
||||
await this.registerView(
|
||||
viewId,
|
||||
factory as any,
|
||||
"Project Tasks",
|
||||
"folders"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ViewManager] Failed to register view ${viewId}:`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册标签视图
|
||||
*/
|
||||
private async registerTagsView(): Promise<void> {
|
||||
const viewId = "tags-bases-view";
|
||||
|
||||
if (this.registeredViews.has(viewId)) {
|
||||
console.log(`[ViewManager] View ${viewId} already registered`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const self = this;
|
||||
const factory = function (container: any): any {
|
||||
const containerEl = self.resolveContainerEl(
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
console.log(
|
||||
`[ViewManager] Creating ${viewId} instance`,
|
||||
container,
|
||||
(arguments as any)[1]
|
||||
);
|
||||
return new TagsBasesView(containerEl, self.app, self.plugin);
|
||||
};
|
||||
|
||||
await this.registerView(
|
||||
viewId,
|
||||
factory as any,
|
||||
"Tagged Tasks",
|
||||
"tag"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ViewManager] Failed to register view ${viewId}:`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用视图注册方法
|
||||
*/
|
||||
private async registerView(
|
||||
viewId: string,
|
||||
factory: (container: HTMLElement) => any,
|
||||
name: string,
|
||||
icon: string
|
||||
): Promise<void> {
|
||||
// Check if new API is supported
|
||||
if (this.isNewBasesApiSupported()) {
|
||||
console.log(
|
||||
`[ViewManager] Using legacy registerView API for ${viewId}`
|
||||
);
|
||||
|
||||
// Use legacy bases plugin registration method
|
||||
if (!this.basesPlugin) {
|
||||
throw new Error(
|
||||
"Bases plugin not available for legacy registration"
|
||||
);
|
||||
}
|
||||
|
||||
// Create view registration configuration
|
||||
const viewConfig: BasesViewRegistration = {
|
||||
name: name,
|
||||
icon: icon,
|
||||
factory: factory,
|
||||
};
|
||||
|
||||
// Try to register with config first, fallback to factory only
|
||||
// Register view is handled by plugin itself(Will help remove the need to register view in bases plugin)
|
||||
try {
|
||||
this.plugin.registerBasesView(viewId, viewConfig);
|
||||
} catch (configError) {
|
||||
console.warn(
|
||||
`[ViewManager] Config registration failed, trying factory-only registration:`,
|
||||
configError
|
||||
);
|
||||
this.basesPlugin.registerView(viewId, factory);
|
||||
}
|
||||
|
||||
this.registeredViews.add(viewId);
|
||||
console.log(
|
||||
`[ViewManager] Successfully registered view using legacy API: ${viewId}`
|
||||
);
|
||||
} else if (requireApiVersion("1.9.0")) {
|
||||
console.log(
|
||||
`[ViewManager] Using new registerBasesView API for ${viewId}`
|
||||
);
|
||||
|
||||
// Use new plugin-level registration method
|
||||
// Method is used between 1.9.0 and 1.9.3
|
||||
const success = (this.plugin as any).registerBasesView(
|
||||
viewId,
|
||||
factory
|
||||
);
|
||||
|
||||
if (success) {
|
||||
this.registeredViews.add(viewId);
|
||||
console.log(
|
||||
`[ViewManager] Successfully registered view using new API: ${viewId}`
|
||||
);
|
||||
} else {
|
||||
throw new Error("New API registration returned false");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销视图
|
||||
*/
|
||||
unregisterView(viewId: string): void {
|
||||
try {
|
||||
if (!this.registeredViews.has(viewId)) {
|
||||
console.log(
|
||||
`[ViewManager] View ${viewId} not registered, skipping unregistration`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For new API, the cleanup is handled automatically by the plugin
|
||||
if (this.isNewBasesApiSupported()) {
|
||||
console.log(
|
||||
`[ViewManager] View ${viewId} registered with new API, cleanup handled automatically`
|
||||
);
|
||||
} else {
|
||||
// For legacy API, manually unregister from bases plugin
|
||||
if (this.basesPlugin) {
|
||||
this.basesPlugin.deregisterView(viewId);
|
||||
console.log(
|
||||
`[ViewManager] Manually unregistered view from bases plugin: ${viewId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.registeredViews.delete(viewId);
|
||||
console.log(`[ViewManager] Unregistered view: ${viewId}`);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ViewManager] Failed to unregister view ${viewId}:`,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注销所有视图
|
||||
*/
|
||||
unregisterAllViews(): void {
|
||||
console.log("[ViewManager] Unregistering all views...");
|
||||
|
||||
// Create a copy of the set to avoid modification during iteration
|
||||
const viewsToUnregister = Array.from(this.registeredViews);
|
||||
|
||||
for (const viewId of viewsToUnregister) {
|
||||
this.unregisterView(viewId);
|
||||
}
|
||||
|
||||
console.log("[ViewManager] All views unregistered");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已注册的视图列表
|
||||
*/
|
||||
getRegisteredViews(): string[] {
|
||||
return Array.from(this.registeredViews);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查视图是否已注册
|
||||
*/
|
||||
isViewRegistered(viewId: string): boolean {
|
||||
return this.registeredViews.has(viewId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Bases 插件的可用视图类型
|
||||
*/
|
||||
getAvailableViewTypes(): string[] {
|
||||
if (!this.basesPlugin) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return this.basesPlugin.getViewTypes();
|
||||
} catch (error) {
|
||||
console.error("[ViewManager] Error getting view types:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建视图实例(用于测试)
|
||||
*/
|
||||
createViewInstance(
|
||||
viewId: string,
|
||||
container: HTMLElement
|
||||
): BaseView | null {
|
||||
if (!this.basesPlugin) {
|
||||
console.error("[ViewManager] Bases plugin not available");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const factory = this.basesPlugin.getViewFactory(viewId);
|
||||
if (factory) {
|
||||
return factory(container);
|
||||
} else {
|
||||
console.error(
|
||||
`[ViewManager] No factory found for view: ${viewId}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[ViewManager] Error creating view instance ${viewId}:`,
|
||||
error
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件状态信息
|
||||
*/
|
||||
getStatus(): {
|
||||
basesPluginAvailable: boolean;
|
||||
registeredViewsCount: number;
|
||||
registeredViews: string[];
|
||||
availableViewTypes: string[];
|
||||
usingNewApi: boolean;
|
||||
apiVersion: string;
|
||||
} {
|
||||
const usingNewApi = this.isNewBasesApiSupported();
|
||||
|
||||
return {
|
||||
basesPluginAvailable: !!this.basesPlugin,
|
||||
registeredViewsCount: this.registeredViews.size,
|
||||
registeredViews: this.getRegisteredViews(),
|
||||
availableViewTypes: this.getAvailableViewTypes(),
|
||||
usingNewApi: usingNewApi,
|
||||
apiVersion: usingNewApi ? "1.9.3+" : "legacy",
|
||||
};
|
||||
}
|
||||
|
||||
onload(): void {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component unload handler
|
||||
*/
|
||||
onunload(): void {
|
||||
console.log("[ViewManager] Unloading...");
|
||||
this.unregisterAllViews();
|
||||
super.onunload();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,353 +0,0 @@
|
|||
/**
|
||||
* Flagged Bases View
|
||||
* Specialized view for flagged and high priority tasks
|
||||
*/
|
||||
|
||||
import { App, Menu } from "obsidian";
|
||||
import { BaseTaskBasesView } from "./BaseTaskBasesView";
|
||||
import { ContentComponent } from "@/components/features/task/view/content";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { filterTasks } from "@/utils/task/task-filter-utils";
|
||||
import { t } from "@/translations/helper";
|
||||
import { Task } from "@/types/task";
|
||||
|
||||
export class FlaggedBasesView extends BaseTaskBasesView {
|
||||
type = "flagged-bases-view";
|
||||
|
||||
private contentComponent: ContentComponent;
|
||||
private isLoaded = false;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin
|
||||
) {
|
||||
super(containerEl, app, plugin, "flagged");
|
||||
this.initializeComponents();
|
||||
}
|
||||
|
||||
private initializeComponents(): void {
|
||||
// Create content component for flagged tasks
|
||||
this.contentComponent = new ContentComponent(
|
||||
this.containerEl,
|
||||
this.app,
|
||||
this.plugin,
|
||||
{
|
||||
onTaskSelected: (task) => {
|
||||
console.log("[FlaggedBasesView] Task selected:", task);
|
||||
this.handleTaskSelection(task);
|
||||
},
|
||||
onTaskCompleted: (task) => {
|
||||
console.log("[FlaggedBasesView] Task completed:", task);
|
||||
this.handleTaskCompletionLocal(task);
|
||||
},
|
||||
onTaskContextMenu: (event, task) => {
|
||||
console.log("[FlaggedBasesView] Task context menu:", task);
|
||||
this.handleTaskContextMenu(event, task);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.addChild(this.contentComponent);
|
||||
}
|
||||
|
||||
// Abstract method implementations
|
||||
protected onConfigUpdated(): void {
|
||||
console.log(
|
||||
"[FlaggedBasesView] onConfigUpdated called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
if (this.isLoaded) {
|
||||
// Convert data again in case configuration affects data processing
|
||||
this.convertEntriesToTasks();
|
||||
this.updateFlaggedTasks();
|
||||
}
|
||||
}
|
||||
|
||||
protected onDataUpdated(): void {
|
||||
// Handle data updates - convert data and update tasks
|
||||
console.log(
|
||||
"[FlaggedBasesView] onDataUpdated called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
// Force convert entries to tasks to get latest data
|
||||
this.convertEntriesToTasks();
|
||||
|
||||
// Then update the view
|
||||
this.updateFlaggedTasks();
|
||||
}
|
||||
|
||||
protected onDisplay(): void {
|
||||
this.containerEl.removeClass("is-loading");
|
||||
if (this.tasks.length === 0) {
|
||||
this.showEmptyState();
|
||||
} else {
|
||||
this.hideEmptyState();
|
||||
}
|
||||
}
|
||||
|
||||
protected onViewLoad(): void {
|
||||
this.contentComponent.load();
|
||||
this.isLoaded = true;
|
||||
this.updateFlaggedTasks();
|
||||
}
|
||||
|
||||
protected onViewUnload(): void {
|
||||
this.isLoaded = false;
|
||||
}
|
||||
|
||||
protected onViewResize(): void {
|
||||
// Content component handles its own resize
|
||||
}
|
||||
|
||||
protected getCustomActions(): Array<{
|
||||
name: string;
|
||||
callback: () => void;
|
||||
icon: string;
|
||||
}> {
|
||||
return [
|
||||
{
|
||||
name: t("Set Priority"),
|
||||
icon: "flag",
|
||||
callback: () => {
|
||||
this.openPrioritySelector();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("Clear Flags"),
|
||||
icon: "flag-off",
|
||||
callback: () => {
|
||||
this.clearAllFlags();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("Filter by Priority"),
|
||||
icon: "filter",
|
||||
callback: () => {
|
||||
this.openPriorityFilter();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected getEditMenuItems(): Array<{
|
||||
displayName: string;
|
||||
component: (container: HTMLElement) => any;
|
||||
}> {
|
||||
return [
|
||||
{
|
||||
displayName: "Priority Settings",
|
||||
component: (container: HTMLElement) => {
|
||||
return this.createPrioritySettingsComponent(container);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private updateFlaggedTasks(): void {
|
||||
console.log(
|
||||
"[FlaggedBasesView] updateFlaggedTasks called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
if (!this.isLoaded) {
|
||||
console.log(
|
||||
"[FlaggedBasesView] View not loaded yet, skipping update"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[FlaggedBasesView] Processing",
|
||||
this.tasks.length,
|
||||
"total tasks"
|
||||
);
|
||||
|
||||
try {
|
||||
// Filter tasks for flagged view (high priority and flagged tasks)
|
||||
const flaggedTasks = filterTasks(
|
||||
this.tasks,
|
||||
"flagged",
|
||||
this.plugin
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[FlaggedBasesView] Filtered ${flaggedTasks.length} flagged tasks from ${this.tasks.length} total tasks`
|
||||
);
|
||||
|
||||
// Sort by priority (highest first)
|
||||
flaggedTasks.sort((a, b) => {
|
||||
const priorityA = a.metadata.priority || 0;
|
||||
const priorityB = b.metadata.priority || 0;
|
||||
return priorityB - priorityA;
|
||||
});
|
||||
|
||||
// Update content component with filtered tasks
|
||||
this.contentComponent.setTasks(flaggedTasks, this.tasks);
|
||||
this.contentComponent.setViewMode("flagged");
|
||||
|
||||
console.log(
|
||||
`[FlaggedBasesView] Successfully updated ContentComponent with ${flaggedTasks.length} flagged tasks`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[FlaggedBasesView] Error updating flagged tasks:",
|
||||
error
|
||||
);
|
||||
this.showErrorState("Failed to update flagged tasks");
|
||||
}
|
||||
}
|
||||
|
||||
private async handleTaskCompletionLocal(task: Task): Promise<void> {
|
||||
// Use base class method for task completion
|
||||
try {
|
||||
await super.handleTaskCompletion(task);
|
||||
// Trigger refresh after completion
|
||||
setTimeout(() => {
|
||||
this.refreshTasks();
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[FlaggedBasesView] Error handling task completion:",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle task context menu
|
||||
*/
|
||||
private handleTaskContextMenu(event: MouseEvent, task: Task): void {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.addItem((item: any) => {
|
||||
item.setTitle(t("Complete"));
|
||||
item.setIcon("check-square");
|
||||
item.onClick(() => {
|
||||
this.handleTaskCompletionLocal(task);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item: any) => {
|
||||
item.setTitle(t("Edit"));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(() => {
|
||||
this.handleTaskSelection(task); // Open details view for editing
|
||||
});
|
||||
})
|
||||
.addItem((item: any) => {
|
||||
item.setTitle(t("Edit in File"));
|
||||
item.setIcon("file-edit");
|
||||
item.onClick(() => {
|
||||
this.handleTaskEdit(task);
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private openPrioritySelector(): void {
|
||||
console.log("[FlaggedBasesView] Opening priority selector");
|
||||
// This would open a priority selection modal
|
||||
}
|
||||
|
||||
private clearAllFlags(): void {
|
||||
console.log("[FlaggedBasesView] Clearing all flags");
|
||||
// This would clear flags from all visible tasks
|
||||
}
|
||||
|
||||
private openPriorityFilter(): void {
|
||||
console.log("[FlaggedBasesView] Opening priority filter");
|
||||
// This would open a priority filter modal
|
||||
}
|
||||
|
||||
private createPrioritySettingsComponent(container: HTMLElement): any {
|
||||
const settingsEl = container.createDiv({
|
||||
cls: "flagged-view-settings",
|
||||
});
|
||||
|
||||
settingsEl.createEl("h3", {
|
||||
text: "Priority Settings",
|
||||
});
|
||||
|
||||
const optionsEl = settingsEl.createDiv({
|
||||
cls: "settings-options",
|
||||
});
|
||||
|
||||
// Minimum priority threshold
|
||||
const thresholdEl = optionsEl.createDiv({
|
||||
cls: "setting-item",
|
||||
});
|
||||
|
||||
thresholdEl.createEl("label", {
|
||||
text: "Minimum priority for flagged view:",
|
||||
});
|
||||
|
||||
const thresholdInput = thresholdEl.createEl("input", {
|
||||
type: "number",
|
||||
value: "3",
|
||||
});
|
||||
thresholdInput.min = "0";
|
||||
thresholdInput.max = "10";
|
||||
|
||||
// Show completed flagged tasks
|
||||
const completedEl = optionsEl.createDiv({
|
||||
cls: "setting-item",
|
||||
});
|
||||
|
||||
completedEl.createEl("label", {
|
||||
text: "Show completed flagged tasks",
|
||||
});
|
||||
|
||||
const completedToggle = completedEl.createEl("input", {
|
||||
type: "checkbox",
|
||||
});
|
||||
|
||||
return settingsEl;
|
||||
}
|
||||
|
||||
private showEmptyState(): void {
|
||||
this.hideEmptyState();
|
||||
|
||||
const emptyEl = this.createEmptyContainer("No flagged tasks found");
|
||||
emptyEl.addClass("flagged-empty-state");
|
||||
|
||||
const helpEl = emptyEl.createDiv({
|
||||
cls: "flagged-empty-help",
|
||||
});
|
||||
|
||||
helpEl.createEl("p", {
|
||||
text: "Tasks with high priority (3+) or flagged tags will appear here.",
|
||||
});
|
||||
|
||||
const helpText = helpEl.createEl("div", {
|
||||
cls: "flagged-help-text",
|
||||
});
|
||||
|
||||
helpText.createEl("p", {
|
||||
text: "To flag a task:",
|
||||
});
|
||||
|
||||
const helpList = helpText.createEl("ul");
|
||||
helpList.createEl("li", {
|
||||
text: "Set priority to 3 or higher",
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: "Add #flagged tag to the task",
|
||||
});
|
||||
}
|
||||
|
||||
private hideEmptyState(): void {
|
||||
const emptyEl = this.containerEl.querySelector(".flagged-empty-state");
|
||||
if (emptyEl) {
|
||||
emptyEl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private showErrorState(message: string): void {
|
||||
this.containerEl.empty();
|
||||
this.createErrorContainer(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
/**
|
||||
* Inbox Bases View
|
||||
* Specialized view for inbox tasks (tasks without projects)
|
||||
*/
|
||||
|
||||
import { App, Menu } from "obsidian";
|
||||
import { BaseTaskBasesView } from "./BaseTaskBasesView";
|
||||
import { ContentComponent } from "@/components/features/task/view/content";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { filterTasks } from "@/utils/task/task-filter-utils";
|
||||
import { t } from "@/translations/helper";
|
||||
import { Task } from "@/types/task";
|
||||
|
||||
export class InboxBasesView extends BaseTaskBasesView {
|
||||
type = "inbox-bases-view";
|
||||
|
||||
private contentComponent: ContentComponent;
|
||||
private isLoaded = false;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin
|
||||
) {
|
||||
super(containerEl, app, plugin, "inbox");
|
||||
this.initializeComponents();
|
||||
}
|
||||
|
||||
private initializeComponents(): void {
|
||||
// Create content component for inbox tasks
|
||||
this.contentComponent = new ContentComponent(
|
||||
this.containerEl,
|
||||
this.app,
|
||||
this.plugin,
|
||||
{
|
||||
onTaskSelected: (task) => {
|
||||
// Handle task selection using base class method
|
||||
console.log("[InboxBasesView] Task selected:", task);
|
||||
this.handleTaskSelection(task);
|
||||
},
|
||||
onTaskCompleted: (task) => {
|
||||
// Handle task completion using base class method
|
||||
console.log("[InboxBasesView] Task completed:", task);
|
||||
this.handleTaskCompletionLocal(task);
|
||||
},
|
||||
onTaskContextMenu: (event, task) => {
|
||||
// Handle context menu
|
||||
console.log("[InboxBasesView] Task context menu:", task);
|
||||
this.handleTaskContextMenu(event, task);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.addChild(this.contentComponent);
|
||||
}
|
||||
|
||||
// Abstract method implementations
|
||||
protected onConfigUpdated(): void {
|
||||
// Handle configuration updates
|
||||
console.log(
|
||||
"[InboxBasesView] onConfigUpdated called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
if (this.isLoaded) {
|
||||
// Convert data again in case configuration affects data processing
|
||||
this.convertEntriesToTasks();
|
||||
this.updateInboxTasks();
|
||||
}
|
||||
}
|
||||
|
||||
protected onDataUpdated(): void {
|
||||
// Handle data updates - convert data and update tasks
|
||||
console.log(
|
||||
"[InboxBasesView] onDataUpdated called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
// Force convert entries to tasks to get latest data
|
||||
this.convertEntriesToTasks();
|
||||
|
||||
// Then update the view
|
||||
this.updateInboxTasks();
|
||||
}
|
||||
|
||||
protected onDisplay(): void {
|
||||
// Display the view
|
||||
this.containerEl.removeClass("is-loading");
|
||||
if (this.tasks.length === 0) {
|
||||
this.showEmptyState();
|
||||
} else {
|
||||
this.hideEmptyState();
|
||||
}
|
||||
}
|
||||
|
||||
protected onViewLoad(): void {
|
||||
// Load the view
|
||||
this.contentComponent.load();
|
||||
this.isLoaded = true;
|
||||
this.updateInboxTasks();
|
||||
}
|
||||
|
||||
protected onViewUnload(): void {
|
||||
// Unload the view
|
||||
this.isLoaded = false;
|
||||
// Component cleanup is handled by parent
|
||||
}
|
||||
|
||||
protected onViewResize(): void {
|
||||
// Handle view resize
|
||||
// Content component handles its own resize
|
||||
}
|
||||
|
||||
protected getCustomActions(): Array<{
|
||||
name: string;
|
||||
callback: () => void;
|
||||
icon: string;
|
||||
}> {
|
||||
return [
|
||||
{
|
||||
name: t("Quick Capture"),
|
||||
icon: "plus",
|
||||
callback: () => {
|
||||
// Open quick capture modal
|
||||
this.openQuickCapture();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("Filter"),
|
||||
icon: "filter",
|
||||
callback: () => {
|
||||
// Open filter options
|
||||
this.openFilterOptions();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected getEditMenuItems(): Array<{
|
||||
displayName: string;
|
||||
component: (container: HTMLElement) => any;
|
||||
}> {
|
||||
return [
|
||||
{
|
||||
displayName: "View Settings",
|
||||
component: (container: HTMLElement) => {
|
||||
return this.createViewSettingsComponent(container);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private updateInboxTasks(): void {
|
||||
console.log(
|
||||
"[InboxBasesView] updateInboxTasks called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
if (!this.isLoaded) {
|
||||
console.log(
|
||||
"[InboxBasesView] View not loaded yet, skipping update"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[InboxBasesView] Raw data:", this.data);
|
||||
console.log("[InboxBasesView] Converted tasks:", this.tasks);
|
||||
|
||||
try {
|
||||
// Filter tasks for inbox view (tasks without projects)
|
||||
const inboxTasks = filterTasks(this.tasks, "inbox", this.plugin);
|
||||
|
||||
console.log(
|
||||
`[InboxBasesView] Filtered ${inboxTasks.length} inbox tasks from ${this.tasks.length} total tasks`
|
||||
);
|
||||
|
||||
// Update content component with filtered tasks
|
||||
this.contentComponent.setTasks(inboxTasks, this.tasks);
|
||||
this.contentComponent.setViewMode("inbox");
|
||||
|
||||
console.log(
|
||||
`[InboxBasesView] Successfully updated ContentComponent with ${inboxTasks.length} inbox tasks`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[InboxBasesView] Error updating inbox tasks:",
|
||||
error
|
||||
);
|
||||
this.showErrorState("Failed to update inbox tasks");
|
||||
}
|
||||
}
|
||||
|
||||
private async handleTaskCompletionLocal(task: Task): Promise<void> {
|
||||
// Use base class method for task completion
|
||||
try {
|
||||
await super.handleTaskCompletion(task);
|
||||
// Trigger refresh after completion
|
||||
setTimeout(() => {
|
||||
this.refreshTasks();
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[InboxBasesView] Error handling task completion:",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle task context menu
|
||||
*/
|
||||
private handleTaskContextMenu(event: MouseEvent, task: Task): void {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.addItem((item: any) => {
|
||||
item.setTitle(t("Complete"));
|
||||
item.setIcon("check-square");
|
||||
item.onClick(() => {
|
||||
this.handleTaskCompletionLocal(task);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item: any) => {
|
||||
item.setTitle(t("Edit"));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(() => {
|
||||
this.handleTaskSelection(task); // Open details view for editing
|
||||
});
|
||||
})
|
||||
.addItem((item: any) => {
|
||||
item.setTitle(t("Edit in File"));
|
||||
item.setIcon("file-edit");
|
||||
item.onClick(() => {
|
||||
this.handleTaskEdit(task);
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private openQuickCapture(): void {
|
||||
// Open quick capture modal
|
||||
try {
|
||||
const {
|
||||
QuickCaptureModal,
|
||||
} = require("../components/QuickCaptureModal");
|
||||
const modal = new QuickCaptureModal(
|
||||
this.app,
|
||||
this.plugin,
|
||||
{},
|
||||
true
|
||||
);
|
||||
modal.open();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[InboxBasesView] Error opening quick capture:",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private openFilterOptions(): void {
|
||||
// Open filter options
|
||||
console.log("[InboxBasesView] Opening filter options");
|
||||
// This could open a filter modal or popover
|
||||
}
|
||||
|
||||
private createViewSettingsComponent(container: HTMLElement): any {
|
||||
// Create view settings component
|
||||
const settingsEl = container.createDiv({
|
||||
cls: "inbox-view-settings",
|
||||
});
|
||||
|
||||
settingsEl.createEl("h3", {
|
||||
text: "Inbox View Settings",
|
||||
});
|
||||
|
||||
// Add settings options here
|
||||
const optionsEl = settingsEl.createDiv({
|
||||
cls: "settings-options",
|
||||
});
|
||||
|
||||
optionsEl.createEl("label", {
|
||||
text: "Show completed tasks",
|
||||
});
|
||||
|
||||
const toggleEl = optionsEl.createEl("input", {
|
||||
type: "checkbox",
|
||||
});
|
||||
|
||||
// Add more settings as needed
|
||||
|
||||
return settingsEl;
|
||||
}
|
||||
|
||||
private showEmptyState(): void {
|
||||
// Remove any existing empty state
|
||||
this.hideEmptyState();
|
||||
|
||||
// Create empty state
|
||||
const emptyEl = this.createEmptyContainer("No inbox tasks found");
|
||||
emptyEl.addClass("inbox-empty-state");
|
||||
|
||||
// Add helpful message
|
||||
const helpEl = emptyEl.createDiv({
|
||||
cls: "inbox-empty-help",
|
||||
});
|
||||
|
||||
helpEl.createEl("p", {
|
||||
text: "Tasks without projects will appear here.",
|
||||
});
|
||||
|
||||
const captureBtn = helpEl.createEl("button", {
|
||||
cls: "inbox-capture-btn",
|
||||
text: "Create Task",
|
||||
});
|
||||
|
||||
captureBtn.addEventListener("click", () => {
|
||||
this.openQuickCapture();
|
||||
});
|
||||
}
|
||||
|
||||
private hideEmptyState(): void {
|
||||
const emptyEl = this.containerEl.querySelector(".inbox-empty-state");
|
||||
if (emptyEl) {
|
||||
emptyEl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private showErrorState(message: string): void {
|
||||
this.containerEl.empty();
|
||||
this.createErrorContainer(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,373 +0,0 @@
|
|||
/**
|
||||
* Projects Bases View
|
||||
* Specialized view for project-based task management
|
||||
*/
|
||||
|
||||
import { App, Menu } from "obsidian";
|
||||
import { BaseTaskBasesView } from "./BaseTaskBasesView";
|
||||
import { ProjectsComponent } from "@/components/features/task/view/projects";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { filterTasks } from "@/utils/task/task-filter-utils";
|
||||
import { t } from "@/translations/helper";
|
||||
import { Task } from "@/types/task";
|
||||
|
||||
export class ProjectBasesView extends BaseTaskBasesView {
|
||||
type = "projects-bases-view";
|
||||
|
||||
private projectsComponent: ProjectsComponent;
|
||||
private isLoaded = false;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin
|
||||
) {
|
||||
super(containerEl, app, plugin, "projects");
|
||||
this.initializeComponents();
|
||||
}
|
||||
|
||||
private initializeComponents(): void {
|
||||
// Create projects component for project tasks
|
||||
this.projectsComponent = new ProjectsComponent(
|
||||
this.containerEl,
|
||||
this.app,
|
||||
this.plugin,
|
||||
{
|
||||
onTaskSelected: (task) => {
|
||||
console.log("[ProjectBasesView] Task selected:", task);
|
||||
this.handleTaskSelection(task);
|
||||
},
|
||||
onTaskCompleted: (task) => {
|
||||
console.log("[ProjectBasesView] Task completed:", task);
|
||||
this.handleTaskCompletionLocal(task);
|
||||
},
|
||||
onTaskContextMenu: (event, task) => {
|
||||
console.log("[ProjectBasesView] Task context menu:", task);
|
||||
this.handleTaskContextMenu(event, task);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.addChild(this.projectsComponent);
|
||||
}
|
||||
|
||||
// Abstract method implementations
|
||||
protected onConfigUpdated(): void {
|
||||
console.log(
|
||||
"[ProjectBasesView] onConfigUpdated called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
if (this.isLoaded) {
|
||||
// Convert data again in case configuration affects data processing
|
||||
this.convertEntriesToTasks();
|
||||
this.updateProjectTasks();
|
||||
}
|
||||
}
|
||||
|
||||
protected onDataUpdated(): void {
|
||||
// Handle data updates - convert data and update tasks
|
||||
console.log(
|
||||
"[ProjectBasesView] onDataUpdated called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
console.log("[ProjectBasesView] this.data type:", typeof this.data);
|
||||
console.log("[ProjectBasesView] this.data:", this.data);
|
||||
|
||||
// Force convert entries to tasks to get latest data
|
||||
try {
|
||||
this.convertEntriesToTasks();
|
||||
} catch (error) {
|
||||
console.error("[ProjectBasesView] Error converting entries to tasks:", error);
|
||||
this.tasks = [];
|
||||
}
|
||||
|
||||
// Then update the view
|
||||
this.updateProjectTasks();
|
||||
}
|
||||
|
||||
protected onDisplay(): void {
|
||||
this.containerEl.removeClass("is-loading");
|
||||
if (this.tasks.length === 0) {
|
||||
this.showEmptyState();
|
||||
} else {
|
||||
this.hideEmptyState();
|
||||
}
|
||||
}
|
||||
|
||||
protected onViewLoad(): void {
|
||||
this.projectsComponent.load();
|
||||
this.isLoaded = true;
|
||||
this.updateProjectTasks();
|
||||
}
|
||||
|
||||
protected onViewUnload(): void {
|
||||
this.isLoaded = false;
|
||||
}
|
||||
|
||||
protected onViewResize(): void {
|
||||
// Projects component handles its own resize
|
||||
}
|
||||
|
||||
protected getCustomActions(): Array<{
|
||||
name: string;
|
||||
callback: () => void;
|
||||
icon: string;
|
||||
}> {
|
||||
return [
|
||||
{
|
||||
name: t("New Project"),
|
||||
icon: "folder-plus",
|
||||
callback: () => {
|
||||
this.createNewProject();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("Archive Completed"),
|
||||
icon: "archive",
|
||||
callback: () => {
|
||||
this.archiveCompletedProjects();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: t("Project Statistics"),
|
||||
icon: "bar-chart",
|
||||
callback: () => {
|
||||
this.showProjectStatistics();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected getEditMenuItems(): Array<{
|
||||
displayName: string;
|
||||
component: (container: HTMLElement) => any;
|
||||
}> {
|
||||
return [
|
||||
{
|
||||
displayName: "Project Settings",
|
||||
component: (container: HTMLElement) => {
|
||||
return this.createProjectSettingsComponent(container);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private updateProjectTasks(): void {
|
||||
console.log(
|
||||
"[ProjectBasesView] updateProjectTasks called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
if (!this.isLoaded) {
|
||||
console.log(
|
||||
"[ProjectBasesView] View not loaded yet, skipping update"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[ProjectBasesView] Processing",
|
||||
this.tasks.length,
|
||||
"total tasks"
|
||||
);
|
||||
|
||||
try {
|
||||
// Filter tasks for projects view (tasks with projects)
|
||||
const projectTasks = filterTasks(
|
||||
this.tasks,
|
||||
"projects",
|
||||
this.plugin
|
||||
);
|
||||
|
||||
console.log(
|
||||
`[ProjectBasesView] Filtered ${projectTasks.length} project tasks from ${this.tasks.length} total tasks`
|
||||
);
|
||||
|
||||
// Update projects component with filtered tasks
|
||||
this.projectsComponent.setTasks(projectTasks);
|
||||
|
||||
console.log(
|
||||
`[ProjectBasesView] Successfully updated ProjectsComponent with ${projectTasks.length} project tasks`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[ProjectBasesView] Error updating project tasks:",
|
||||
error
|
||||
);
|
||||
this.showErrorState("Failed to update project tasks");
|
||||
}
|
||||
}
|
||||
|
||||
private async handleTaskCompletionLocal(task: Task): Promise<void> {
|
||||
// Use base class method for task completion
|
||||
try {
|
||||
await super.handleTaskCompletion(task);
|
||||
// Trigger refresh after completion
|
||||
setTimeout(() => {
|
||||
this.refreshTasks();
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[ProjectBasesView] Error handling task completion:",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle task context menu
|
||||
*/
|
||||
private handleTaskContextMenu(event: MouseEvent, task: Task): void {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.addItem((item: any) => {
|
||||
item.setTitle(t("Complete"));
|
||||
item.setIcon("check-square");
|
||||
item.onClick(() => {
|
||||
this.handleTaskCompletionLocal(task);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item: any) => {
|
||||
item.setTitle(t("Edit"));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(() => {
|
||||
this.handleTaskSelection(task); // Open details view for editing
|
||||
});
|
||||
})
|
||||
.addItem((item: any) => {
|
||||
item.setTitle(t("Edit in File"));
|
||||
item.setIcon("file-edit");
|
||||
item.onClick(() => {
|
||||
this.handleTaskEdit(task);
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private createNewProject(): void {
|
||||
console.log("[ProjectBasesView] Creating new project");
|
||||
// This would open a new project creation modal
|
||||
}
|
||||
|
||||
private archiveCompletedProjects(): void {
|
||||
console.log("[ProjectBasesView] Archiving completed projects");
|
||||
// This would archive all completed projects
|
||||
}
|
||||
|
||||
private showProjectStatistics(): void {
|
||||
console.log("[ProjectBasesView] Showing project statistics");
|
||||
// This would show project completion statistics
|
||||
}
|
||||
|
||||
private createProjectSettingsComponent(container: HTMLElement): any {
|
||||
const settingsEl = container.createDiv({
|
||||
cls: "projects-view-settings",
|
||||
});
|
||||
|
||||
settingsEl.createEl("h3", {
|
||||
text: "Project Settings",
|
||||
});
|
||||
|
||||
const optionsEl = settingsEl.createDiv({
|
||||
cls: "settings-options",
|
||||
});
|
||||
|
||||
// Show project hierarchy
|
||||
const hierarchyEl = optionsEl.createDiv({
|
||||
cls: "setting-item",
|
||||
});
|
||||
|
||||
hierarchyEl.createEl("label", {
|
||||
text: "Show project hierarchy",
|
||||
});
|
||||
|
||||
const hierarchyToggle = hierarchyEl.createEl("input", {
|
||||
type: "checkbox",
|
||||
});
|
||||
hierarchyToggle.checked = true;
|
||||
|
||||
// Group by project
|
||||
const groupingEl = optionsEl.createDiv({
|
||||
cls: "setting-item",
|
||||
});
|
||||
|
||||
groupingEl.createEl("label", {
|
||||
text: "Group tasks by project",
|
||||
});
|
||||
|
||||
const groupingToggle = groupingEl.createEl("input", {
|
||||
type: "checkbox",
|
||||
});
|
||||
groupingToggle.checked = true;
|
||||
|
||||
// Show completed projects
|
||||
const completedEl = optionsEl.createDiv({
|
||||
cls: "setting-item",
|
||||
});
|
||||
|
||||
completedEl.createEl("label", {
|
||||
text: "Show completed projects",
|
||||
});
|
||||
|
||||
const completedToggle = completedEl.createEl("input", {
|
||||
type: "checkbox",
|
||||
});
|
||||
|
||||
return settingsEl;
|
||||
}
|
||||
|
||||
private showEmptyState(): void {
|
||||
this.hideEmptyState();
|
||||
|
||||
const emptyEl = this.createEmptyContainer("No project tasks found");
|
||||
emptyEl.addClass("projects-empty-state");
|
||||
|
||||
const helpEl = emptyEl.createDiv({
|
||||
cls: "projects-empty-help",
|
||||
});
|
||||
|
||||
helpEl.createEl("p", {
|
||||
text: "Tasks with project assignments will appear here.",
|
||||
});
|
||||
|
||||
const helpText = helpEl.createEl("div", {
|
||||
cls: "projects-help-text",
|
||||
});
|
||||
|
||||
helpText.createEl("p", {
|
||||
text: "To assign a task to a project:",
|
||||
});
|
||||
|
||||
const helpList = helpText.createEl("ul");
|
||||
helpList.createEl("li", {
|
||||
text: "Add #project/projectname tag to the task",
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: "Use project:: property in frontmatter",
|
||||
});
|
||||
|
||||
const createBtn = helpEl.createEl("button", {
|
||||
cls: "projects-create-btn",
|
||||
text: "Create Project",
|
||||
});
|
||||
|
||||
createBtn.addEventListener("click", () => {
|
||||
this.createNewProject();
|
||||
});
|
||||
}
|
||||
|
||||
private hideEmptyState(): void {
|
||||
const emptyEl = this.containerEl.querySelector(".projects-empty-state");
|
||||
if (emptyEl) {
|
||||
emptyEl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private showErrorState(message: string): void {
|
||||
this.containerEl.empty();
|
||||
this.createErrorContainer(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
/**
|
||||
* Tags Bases View
|
||||
* Specialized view for tag-based task organization
|
||||
*/
|
||||
|
||||
import { App, Menu } from "obsidian";
|
||||
import { BaseTaskBasesView } from "./BaseTaskBasesView";
|
||||
import { TagsComponent } from "@/components/features/task/view/tags";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { filterTasks } from "@/utils/task/task-filter-utils";
|
||||
import { t } from "@/translations/helper";
|
||||
import { Task } from "@/types/task";
|
||||
|
||||
export class TagsBasesView extends BaseTaskBasesView {
|
||||
type = "tags-bases-view";
|
||||
|
||||
private tagsComponent: TagsComponent;
|
||||
private isLoaded = false;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin
|
||||
) {
|
||||
super(containerEl, app, plugin, "tags");
|
||||
this.initializeComponents();
|
||||
}
|
||||
|
||||
private initializeComponents(): void {
|
||||
// Create tags component
|
||||
this.tagsComponent = new TagsComponent(
|
||||
this.containerEl,
|
||||
this.app,
|
||||
this.plugin,
|
||||
{
|
||||
onTaskSelected: (task) => {
|
||||
// Handle task selection using base class method
|
||||
console.log("[TagsBasesView] Task selected:", task);
|
||||
this.handleTaskSelection(task);
|
||||
},
|
||||
onTaskCompleted: (task) => {
|
||||
// Handle task completion using base class method
|
||||
console.log("[TagsBasesView] Task completed:", task);
|
||||
this.handleTaskCompletionLocal(task);
|
||||
},
|
||||
onTaskContextMenu: (event, task) => {
|
||||
// Handle context menu
|
||||
console.log("[TagsBasesView] Task context menu:", task);
|
||||
this.handleTaskContextMenu(event, task);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.addChild(this.tagsComponent);
|
||||
}
|
||||
|
||||
// Abstract method implementations
|
||||
protected onConfigUpdated(): void {
|
||||
console.log(
|
||||
"[TagsBasesView] onConfigUpdated called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
if (this.isLoaded) {
|
||||
// Convert data again in case configuration affects data processing
|
||||
this.convertEntriesToTasks();
|
||||
this.updateTagTasks();
|
||||
}
|
||||
}
|
||||
|
||||
protected onDataUpdated(): void {
|
||||
// Handle data updates - convert data and update tasks
|
||||
console.log(
|
||||
"[TagsBasesView] onDataUpdated called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
// Force convert entries to tasks to get latest data
|
||||
this.convertEntriesToTasks();
|
||||
|
||||
// Then update the view
|
||||
this.updateTagTasks();
|
||||
}
|
||||
|
||||
protected onDisplay(): void {
|
||||
this.containerEl.removeClass("is-loading");
|
||||
if (this.tasks.length === 0) {
|
||||
this.showEmptyState();
|
||||
} else {
|
||||
this.hideEmptyState();
|
||||
}
|
||||
}
|
||||
|
||||
protected onViewLoad(): void {
|
||||
this.tagsComponent.load();
|
||||
this.isLoaded = true;
|
||||
this.updateTagTasks();
|
||||
}
|
||||
|
||||
protected onViewUnload(): void {
|
||||
this.isLoaded = false;
|
||||
}
|
||||
|
||||
protected onViewResize(): void {
|
||||
// Tags component handles its own resize
|
||||
}
|
||||
|
||||
protected getCustomActions(): Array<{
|
||||
name: string;
|
||||
callback: () => void;
|
||||
icon: string;
|
||||
}> {
|
||||
return [
|
||||
{
|
||||
name: t("Manage Tags"),
|
||||
icon: "tags",
|
||||
callback: () => {
|
||||
this.openTagManager();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
protected getEditMenuItems(): Array<{
|
||||
displayName: string;
|
||||
component: (container: HTMLElement) => any;
|
||||
}> {
|
||||
return [];
|
||||
}
|
||||
|
||||
private updateTagTasks(): void {
|
||||
console.log(
|
||||
"[TagsBasesView] updateTagTasks called, isLoaded:",
|
||||
this.isLoaded
|
||||
);
|
||||
|
||||
if (!this.isLoaded) {
|
||||
console.log("[TagsBasesView] View not loaded yet, skipping update");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[TagsBasesView] Processing",
|
||||
this.tasks.length,
|
||||
"total tasks"
|
||||
);
|
||||
|
||||
try {
|
||||
// Filter tasks for tags view (tasks with tags)
|
||||
const tagTasks = filterTasks(this.tasks, "tags", this.plugin);
|
||||
|
||||
console.log(
|
||||
`[TagsBasesView] Filtered ${tagTasks.length} tagged tasks from ${this.tasks.length} total tasks`
|
||||
);
|
||||
|
||||
// Update tags component with filtered tasks
|
||||
this.tagsComponent.setTasks(tagTasks);
|
||||
|
||||
console.log(
|
||||
`[TagsBasesView] Successfully updated TagsComponent with ${tagTasks.length} tagged tasks`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("[TagsBasesView] Error updating tag tasks:", error);
|
||||
this.showErrorState("Failed to update tag tasks");
|
||||
}
|
||||
}
|
||||
|
||||
private async handleTaskCompletionLocal(task: Task): Promise<void> {
|
||||
// Use base class method for task completion
|
||||
try {
|
||||
await super.handleTaskCompletion(task);
|
||||
// Trigger refresh after completion
|
||||
setTimeout(() => {
|
||||
this.refreshTasks();
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[TagsBasesView] Error handling task completion:",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle task context menu
|
||||
*/
|
||||
private handleTaskContextMenu(event: MouseEvent, task: Task): void {
|
||||
const menu = new Menu();
|
||||
|
||||
menu.addItem((item: any) => {
|
||||
item.setTitle(t("Complete"));
|
||||
item.setIcon("check-square");
|
||||
item.onClick(() => {
|
||||
this.handleTaskCompletionLocal(task);
|
||||
});
|
||||
})
|
||||
.addSeparator()
|
||||
.addItem((item: any) => {
|
||||
item.setTitle(t("Edit"));
|
||||
item.setIcon("pencil");
|
||||
item.onClick(() => {
|
||||
this.handleTaskSelection(task); // Open details view for editing
|
||||
});
|
||||
})
|
||||
.addItem((item: any) => {
|
||||
item.setTitle(t("Edit in File"));
|
||||
item.setIcon("file-edit");
|
||||
item.onClick(() => {
|
||||
this.handleTaskEdit(task);
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private openTagManager(): void {
|
||||
console.log("[TagsBasesView] Opening tag manager");
|
||||
// This would open a tag management modal
|
||||
}
|
||||
|
||||
private showEmptyState(): void {
|
||||
this.hideEmptyState();
|
||||
|
||||
const emptyEl = this.createEmptyContainer("No tagged tasks found");
|
||||
emptyEl.addClass("tags-empty-state");
|
||||
}
|
||||
|
||||
private hideEmptyState(): void {
|
||||
const emptyEl = this.containerEl.querySelector(".tags-empty-state");
|
||||
if (emptyEl) {
|
||||
emptyEl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private showErrorState(message: string): void {
|
||||
this.containerEl.empty();
|
||||
this.createErrorContainer(message);
|
||||
}
|
||||
}
|
||||
1627
src/pages/bases/TaskBasesView.ts
Normal file
1627
src/pages/bases/TaskBasesView.ts
Normal file
File diff suppressed because it is too large
Load diff
186
src/pages/bases/examples/sample-tasks-base.md
Normal file
186
src/pages/bases/examples/sample-tasks-base.md
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
---
|
||||
bases:
|
||||
properties:
|
||||
- name: text
|
||||
- description: text
|
||||
- status: checkbox
|
||||
- priority: select
|
||||
options: ["1", "2", "3", "4", "5"]
|
||||
- project: text
|
||||
- tags: list
|
||||
- due_date: date
|
||||
- start_date: date
|
||||
- completed_date: date
|
||||
- context: text
|
||||
- assignee: text
|
||||
- estimated_hours: number
|
||||
---
|
||||
|
||||
# Sample Tasks Base for Task Genius Integration
|
||||
|
||||
This file demonstrates how to use Task Genius views with Bases plugin queries.
|
||||
|
||||
## Example Queries
|
||||
|
||||
### 1. Kanban Board View
|
||||
```base
|
||||
view: task-genius-kanban
|
||||
from: "Tasks"
|
||||
where: status != "x"
|
||||
config:
|
||||
taskContent: name
|
||||
taskStatus: status
|
||||
taskPriority: priority
|
||||
taskProject: project
|
||||
taskTags: tags
|
||||
taskDueDate: due_date
|
||||
```
|
||||
|
||||
### 2. Calendar View
|
||||
```base
|
||||
view: task-genius-calendar
|
||||
from: "Tasks"
|
||||
where: due_date != null
|
||||
config:
|
||||
taskContent: name
|
||||
taskDueDate: due_date
|
||||
taskStatus: status
|
||||
```
|
||||
|
||||
### 3. Gantt Chart View
|
||||
```base
|
||||
view: task-genius-gantt
|
||||
from: "Tasks"
|
||||
where: start_date != null AND due_date != null
|
||||
config:
|
||||
taskContent: name
|
||||
taskStartDate: start_date
|
||||
taskDueDate: due_date
|
||||
taskStatus: status
|
||||
taskProject: project
|
||||
```
|
||||
|
||||
### 4. Project View with Tree Structure
|
||||
```base
|
||||
view: task-genius-projects
|
||||
from: "Tasks"
|
||||
where: project != null
|
||||
sort: project asc, priority desc
|
||||
config:
|
||||
taskContent: name
|
||||
taskProject: project
|
||||
taskStatus: status
|
||||
taskPriority: priority
|
||||
```
|
||||
|
||||
### 5. Inbox View for Unprocessed Tasks
|
||||
```base
|
||||
view: task-genius-inbox
|
||||
from: "Tasks"
|
||||
where: status = " " AND project = null
|
||||
sort: created desc
|
||||
config:
|
||||
taskContent: name
|
||||
taskStatus: status
|
||||
taskPriority: priority
|
||||
taskTags: tags
|
||||
```
|
||||
|
||||
### 6. Review View for Daily Processing
|
||||
```base
|
||||
view: task-genius-review
|
||||
from: "Tasks"
|
||||
where: status != "x" AND (due_date <= date("today") OR priority >= 3)
|
||||
config:
|
||||
taskContent: name
|
||||
taskStatus: status
|
||||
taskPriority: priority
|
||||
taskDueDate: due_date
|
||||
taskProject: project
|
||||
```
|
||||
|
||||
### 7. Unified View with Custom Configuration
|
||||
```base
|
||||
view: task-genius-unified
|
||||
from: "Tasks"
|
||||
config:
|
||||
viewMode: "forecast"
|
||||
taskContent: name
|
||||
taskStatus: status
|
||||
taskPriority: priority
|
||||
taskProject: project
|
||||
taskTags: tags
|
||||
taskDueDate: due_date
|
||||
taskStartDate: start_date
|
||||
taskContext: context
|
||||
```
|
||||
|
||||
## Sample Task Entries
|
||||
|
||||
| name | description | status | priority | project | tags | due_date | start_date | context | assignee |
|
||||
|------|-------------|--------|----------|---------|------|----------|------------|---------|----------|
|
||||
| Write documentation | Create comprehensive docs for new feature | [ ] | 3 | Development/Docs | documentation, high-priority | 2024-02-15 | 2024-02-10 | @work | John |
|
||||
| Review PR #123 | Code review for authentication changes | [ ] | 4 | Development/Backend | review, urgent | 2024-02-12 | | @work | Sarah |
|
||||
| Plan Q2 roadmap | Define goals and milestones for Q2 | [ ] | 5 | Planning | planning, strategic | 2024-03-01 | 2024-02-20 | @office | Team Lead |
|
||||
| Fix login bug | Users can't login with special characters | [ ] | 5 | Development/Frontend | bug, critical | 2024-02-11 | | @urgent | Alex |
|
||||
| Update dependencies | Upgrade all npm packages to latest versions | [x] | 2 | Development/Maintenance | maintenance | 2024-02-10 | 2024-02-09 | @work | DevOps |
|
||||
| Design new logo | Create brand refresh proposal | [ ] | 3 | Design/Branding | design, creative | 2024-02-28 | 2024-02-15 | @creative | Designer |
|
||||
| Customer interview | Interview key customer about new features | [ ] | 4 | Research | research, customer | 2024-02-13 | | @calls | PM |
|
||||
| Write blog post | Technical blog about new architecture | [ ] | 2 | Marketing/Content | content, blog | 2024-02-25 | 2024-02-18 | @writing | Tech Writer |
|
||||
| Setup monitoring | Configure APM for production | [ ] | 4 | DevOps/Infrastructure | monitoring, setup | 2024-02-14 | 2024-02-12 | @technical | SRE |
|
||||
| Team retrospective | Monthly team retrospective meeting | [ ] | 3 | Team/Meetings | meeting, team | 2024-02-16 | | @meetings | Scrum Master |
|
||||
|
||||
## Configuration Notes
|
||||
|
||||
### Property Mappings
|
||||
- **taskContent**: Maps to the main task text (usually 'name' or 'title')
|
||||
- **taskStatus**: Maps to completion status (checkbox or status text)
|
||||
- **taskPriority**: Maps to priority level (1-5, where 5 is highest)
|
||||
- **taskProject**: Maps to project assignment (supports nested projects with '/')
|
||||
- **taskTags**: Maps to tags (can be list or comma-separated text)
|
||||
- **taskDueDate**: Maps to due date field
|
||||
- **taskStartDate**: Maps to start date field
|
||||
- **taskCompletedDate**: Maps to completion date field
|
||||
- **taskContext**: Maps to context (@context format)
|
||||
|
||||
### View Modes
|
||||
Each view type has specific features:
|
||||
- **Inbox**: Simple task list with filtering
|
||||
- **Forecast**: Timeline-based view
|
||||
- **Projects**: Hierarchical project organization
|
||||
- **Tags**: Tag-based grouping
|
||||
- **Calendar**: Monthly/weekly calendar
|
||||
- **Kanban**: Drag-and-drop board by status
|
||||
- **Gantt**: Project timeline visualization
|
||||
- **Review**: Task processing workflow
|
||||
- **Habits**: Recurring task tracking
|
||||
- **Flagged**: High-priority task focus
|
||||
|
||||
### Advanced Features
|
||||
1. **Live Filtering**: Use the filter button in any view
|
||||
2. **Quick Capture**: Add tasks quickly with the capture button
|
||||
3. **Details Panel**: Click tasks to see/edit details
|
||||
4. **Status Switching**: Right-click for status options
|
||||
5. **Drag & Drop**: Supported in Kanban and some other views
|
||||
6. **Keyboard Shortcuts**: Various shortcuts for task operations
|
||||
|
||||
## Tips for Best Results
|
||||
|
||||
1. **Consistent Property Names**: Use consistent property names across your bases
|
||||
2. **Status Values**: Use standard status marks (space for not started, 'x' for completed)
|
||||
3. **Project Hierarchy**: Use '/' to create nested projects (e.g., "Development/Frontend/Components")
|
||||
4. **Date Formats**: Use ISO format (YYYY-MM-DD) for dates
|
||||
5. **Priority Scale**: Use 1-5 scale where 5 is highest priority
|
||||
6. **Tags Format**: Use comma-separated values or list format for tags
|
||||
7. **Context Format**: Use @context format for contexts (e.g., "@work", "@home")
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If views don't appear:
|
||||
1. Ensure Bases plugin is installed and enabled
|
||||
2. Restart Obsidian after installing Task Genius
|
||||
3. Check console for any error messages
|
||||
4. Verify property mappings match your base schema
|
||||
5. Ensure query syntax is correct
|
||||
|
||||
For more information, see the Task Genius documentation.
|
||||
118
src/pages/bases/index.ts
Normal file
118
src/pages/bases/index.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* Bases Plugin Integration for Task Genius
|
||||
*
|
||||
* This module provides complete integration between Task Genius and the Bases plugin,
|
||||
* allowing all Task Genius views to be used as Bases views with full data conversion
|
||||
* and view management capabilities.
|
||||
*/
|
||||
|
||||
export { TaskBasesView, TaskBasesViewType } from './TaskBasesView';
|
||||
export {
|
||||
registerTaskGeniusBasesViews
|
||||
} from './registerBasesViews';
|
||||
|
||||
/**
|
||||
* Example usage in Bases:
|
||||
*
|
||||
* 1. Create a base file with task-related properties:
|
||||
* ```yaml
|
||||
* ---
|
||||
* bases:
|
||||
* properties:
|
||||
* - task_content: text
|
||||
* - status: checkbox
|
||||
* - priority: number
|
||||
* - project: text
|
||||
* - tags: list
|
||||
* - due_date: date
|
||||
* - context: text
|
||||
* ---
|
||||
* ```
|
||||
*
|
||||
* 2. Query your tasks:
|
||||
* ```base
|
||||
* view: task-genius-kanban
|
||||
* from: "Tasks"
|
||||
* where: status != "x"
|
||||
* sort: priority desc, due_date asc
|
||||
* ```
|
||||
*
|
||||
* 3. Available view types:
|
||||
* - task-genius-inbox: Inbox view for managing tasks
|
||||
* - task-genius-forecast: Timeline forecast view
|
||||
* - task-genius-projects: Project-based organization
|
||||
* - task-genius-tags: Tag-based browsing
|
||||
* - task-genius-calendar: Calendar layout
|
||||
* - task-genius-kanban: Kanban board
|
||||
* - task-genius-gantt: Gantt chart
|
||||
* - task-genius-review: Task review interface
|
||||
* - task-genius-habits: Habit tracking
|
||||
* - task-genius-flagged: High-priority flagged tasks
|
||||
* - task-genius-unified: Configurable unified view
|
||||
* - task-genius-custom: Fully customizable view
|
||||
*
|
||||
* 4. Property mappings:
|
||||
* Map your base properties to Task Genius fields:
|
||||
* - taskContent: The main task text
|
||||
* - taskStatus: Completion status (checkbox or text)
|
||||
* - taskPriority: Priority level (1-5)
|
||||
* - taskProject: Project assignment
|
||||
* - taskTags: Tags (list or comma-separated)
|
||||
* - taskDueDate: Due date
|
||||
* - taskStartDate: Start date
|
||||
* - taskCompletedDate: Completion date
|
||||
* - taskContext: Task context (@context)
|
||||
*
|
||||
* 5. View-specific configurations (saved to view config):
|
||||
*
|
||||
* Kanban View:
|
||||
* - kanban.groupBy: Group tasks by (status, priority, tags, project, context, dueDate, startDate)
|
||||
* - kanban.hideEmptyColumns: Hide columns with no tasks
|
||||
* - kanban.defaultSortField: Default sort field (priority, dueDate, scheduledDate, startDate, createdDate)
|
||||
* - kanban.defaultSortOrder: Sort order (asc or desc)
|
||||
*
|
||||
* Calendar View:
|
||||
* - calendar.firstDayOfWeek: First day of week (0=Sunday, 1=Monday, etc.)
|
||||
* - calendar.hideWeekends: Hide weekend columns
|
||||
*
|
||||
* Gantt View:
|
||||
* - gantt.showTaskLabels: Show task labels on bars
|
||||
* - gantt.useMarkdownRenderer: Use markdown rendering for task names
|
||||
*
|
||||
* Forecast View:
|
||||
* - forecast.firstDayOfWeek: First day of week (0=Sunday, 1=Monday, etc.)
|
||||
* - forecast.hideWeekends: Hide weekend columns
|
||||
*
|
||||
* These configurations are saved directly to the .base file's frontmatter and are
|
||||
* specific to each view instance. They override plugin-level settings.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Integration Features:
|
||||
*
|
||||
* 1. Full View Support:
|
||||
* - All Task Genius views available as Bases views
|
||||
* - Seamless switching between view modes
|
||||
* - Persistent configuration per view
|
||||
*
|
||||
* 2. Data Conversion:
|
||||
* - Automatic conversion between Bases entries and Task format
|
||||
* - Support for all task properties and metadata
|
||||
* - Bi-directional sync when using WriteAPI
|
||||
*
|
||||
* 3. Filtering & Sorting:
|
||||
* - Advanced task filtering system
|
||||
* - Multiple sort criteria support
|
||||
* - Live filter updates
|
||||
*
|
||||
* 4. Task Operations:
|
||||
* - Complete/uncomplete tasks
|
||||
* - Edit task properties
|
||||
* - Status switching
|
||||
* - Quick capture integration
|
||||
*
|
||||
* 5. Customization:
|
||||
* - Configurable property mappings
|
||||
* - View-specific settings
|
||||
* - Theme-aware styling
|
||||
*/
|
||||
157
src/pages/bases/registerBasesViews.ts
Normal file
157
src/pages/bases/registerBasesViews.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import { ViewOption } from 'obsidian';
|
||||
import { TaskBasesView } from './TaskBasesView';
|
||||
import TaskProgressBarPlugin from '../../index';
|
||||
import { ViewMode } from '@/common/setting-definition';
|
||||
|
||||
/**
|
||||
* View type definitions for Task Genius Bases views
|
||||
*/
|
||||
interface TaskGeniusBasesViewType {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
defaultViewMode: ViewMode;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* All available Task Genius view types for Bases
|
||||
*/
|
||||
const TASK_GENIUS_BASES_VIEWS: TaskGeniusBasesViewType[] = [
|
||||
{
|
||||
id: 'task-genius-inbox',
|
||||
name: 'Inbox (Task Genius)',
|
||||
icon: 'lucide-inbox',
|
||||
defaultViewMode: 'inbox',
|
||||
description: 'View and manage tasks in inbox mode'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-forecast',
|
||||
name: 'Forecast (Task Genius)',
|
||||
icon: 'lucide-calendar-days',
|
||||
defaultViewMode: 'forecast',
|
||||
description: 'View tasks in forecast timeline'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-projects',
|
||||
name: 'Projects (Task Genius)',
|
||||
icon: 'lucide-folder-tree',
|
||||
defaultViewMode: 'projects',
|
||||
description: 'Organize tasks by projects'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-tags',
|
||||
name: 'Tags (Task Genius)',
|
||||
icon: 'lucide-tags',
|
||||
defaultViewMode: 'tags',
|
||||
description: 'Browse tasks by tags'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-calendar',
|
||||
name: 'Calendar (Task Genius)',
|
||||
icon: 'lucide-calendar',
|
||||
defaultViewMode: 'calendar',
|
||||
description: 'View tasks in calendar layout'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-kanban',
|
||||
name: 'Kanban (Task Genius)',
|
||||
icon: 'lucide-columns-3',
|
||||
defaultViewMode: 'kanban',
|
||||
description: 'Manage tasks in kanban board'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-gantt',
|
||||
name: 'Gantt (Task Genius)',
|
||||
icon: 'lucide-gantt-chart-square',
|
||||
defaultViewMode: 'gantt',
|
||||
description: 'View tasks in Gantt chart'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-review',
|
||||
name: 'Review (Task Genius)',
|
||||
icon: 'lucide-list-checks',
|
||||
defaultViewMode: 'review',
|
||||
description: 'Review and process tasks'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-habits',
|
||||
name: 'Habits (Task Genius)',
|
||||
icon: 'lucide-target',
|
||||
defaultViewMode: 'habit',
|
||||
description: 'Track habits and recurring tasks'
|
||||
},
|
||||
{
|
||||
id: 'task-genius-flagged',
|
||||
name: 'Flagged (Task Genius)',
|
||||
icon: 'lucide-flag',
|
||||
defaultViewMode: 'flagged',
|
||||
description: 'View high-priority flagged tasks'
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Create view options for a specific view type
|
||||
*/
|
||||
function createViewOptions(viewType: TaskGeniusBasesViewType): () => ViewOption[] {
|
||||
return () => {
|
||||
// For specialized views, pass the viewMode to filter options
|
||||
const viewMode = viewType.defaultViewMode;
|
||||
const baseOptions = TaskBasesView.getViewOptions(viewMode);
|
||||
|
||||
const viewSettingsGroup = baseOptions.find(
|
||||
opt => opt.displayName === 'View Settings' && opt.type === 'group'
|
||||
);
|
||||
|
||||
if (viewSettingsGroup && 'items' in viewSettingsGroup) {
|
||||
// Remove the view mode selector for specialized views
|
||||
viewSettingsGroup.items = viewSettingsGroup.items?.filter(
|
||||
item => item.key !== 'viewMode'
|
||||
);
|
||||
|
||||
// Add a note about the view type
|
||||
viewSettingsGroup.items?.unshift({
|
||||
displayName: 'View Type',
|
||||
type: 'text',
|
||||
key: '__viewType',
|
||||
placeholder: `This is a ${viewType.name} view`,
|
||||
default: viewType.name,
|
||||
readonly: true,
|
||||
} as any);
|
||||
}
|
||||
|
||||
return baseOptions;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all Task Genius views with the Bases plugin
|
||||
* @param plugin - The main Task Genius plugin instance
|
||||
*/
|
||||
export function registerTaskGeniusBasesViews(plugin: TaskProgressBarPlugin) {
|
||||
// Register each view type
|
||||
TASK_GENIUS_BASES_VIEWS.forEach(viewType => {
|
||||
try {
|
||||
plugin.registerBasesView(viewType.id, {
|
||||
name: viewType.name,
|
||||
icon: viewType.icon,
|
||||
factory: (controller, containerEl) => {
|
||||
const view = new TaskBasesView(
|
||||
controller,
|
||||
containerEl,
|
||||
plugin,
|
||||
viewType.defaultViewMode
|
||||
);
|
||||
view.setForcedViewMode(viewType.defaultViewMode as ViewMode);
|
||||
|
||||
return view;
|
||||
},
|
||||
options: createViewOptions(viewType),
|
||||
});
|
||||
|
||||
console.log(`Registered Bases view: ${viewType.name} (${viewType.id})`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to register Bases view ${viewType.id}:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -205,6 +205,10 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.bases-view .task-genius-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Main container layout */
|
||||
.task-genius-container {
|
||||
display: flex;
|
||||
|
|
@ -405,12 +409,12 @@
|
|||
}
|
||||
|
||||
.mod-left-split
|
||||
.workspace-tab-header-status-container:has(.task-genius-action-btn) {
|
||||
.workspace-tab-header-status-container:has(.task-genius-action-btn) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mod-right-split
|
||||
.workspace-tab-header-status-container:has(.task-genius-action-btn) {
|
||||
.workspace-tab-header-status-container:has(.task-genius-action-btn) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
|||
42
src/types/obsidian-ex.d.ts
vendored
42
src/types/obsidian-ex.d.ts
vendored
|
|
@ -12,8 +12,7 @@ import {
|
|||
} from "obsidian";
|
||||
import { Component } from "obsidian";
|
||||
import { HabitProps } from "./habit-card";
|
||||
import { RootFilterState } from "../components/features/task/filter/ViewTaskFilter";
|
||||
import { BasesViewRegistration } from "./bases";
|
||||
import { RootFilterState } from "@/components/features/task/filter/ViewTaskFilter";
|
||||
|
||||
interface Token extends EditorRange {
|
||||
/** @todo Documentation incomplete. */
|
||||
|
|
@ -338,17 +337,21 @@ declare module "obsidian" {
|
|||
|
||||
interface MenuItem {
|
||||
setSubmenu(): Menu;
|
||||
|
||||
titleEl: HTMLElement;
|
||||
|
||||
setWarning(warning: boolean): this;
|
||||
}
|
||||
|
||||
interface Setting {
|
||||
open(): void;
|
||||
|
||||
openTabById(tabId: string): void;
|
||||
}
|
||||
|
||||
interface Commands {
|
||||
executeCommandById(commandId: string): void;
|
||||
|
||||
executeCommandById(commandId: string, ...args: any[]): void;
|
||||
}
|
||||
|
||||
|
|
@ -356,31 +359,17 @@ declare module "obsidian" {
|
|||
_loaded: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin interface extension for Bases API support
|
||||
*/
|
||||
interface Plugin {
|
||||
/**
|
||||
* Register a bases view (Obsidian 1.9.3+)
|
||||
* @param viewId - Unique identifier for the view
|
||||
* @param factory - Factory function to create the view
|
||||
* @returns true if registration was successful, false otherwise
|
||||
*/
|
||||
registerBasesView(
|
||||
viewId: string,
|
||||
config: BasesViewRegistration
|
||||
): boolean;
|
||||
}
|
||||
|
||||
interface Workspace {
|
||||
on(
|
||||
event: "task-genius:task-added",
|
||||
callback: (task: Task) => void
|
||||
): EventRef;
|
||||
|
||||
on(
|
||||
event: "task-genius:task-updated",
|
||||
callback: (task: Task) => void
|
||||
): EventRef;
|
||||
|
||||
on(
|
||||
event: "task-genius:task-deleted",
|
||||
callback: (taskId: string) => void
|
||||
|
|
@ -400,42 +389,55 @@ declare module "obsidian" {
|
|||
event: "task-genius:ics-cache-updated",
|
||||
callback: () => void
|
||||
): EventRef;
|
||||
|
||||
on(
|
||||
event: "task-genius:task-completed",
|
||||
callback: (task: Task) => void
|
||||
): EventRef;
|
||||
|
||||
on(
|
||||
event: "task-genius:habit-index-updated",
|
||||
callback: (habits: HabitProps[]) => void
|
||||
): EventRef;
|
||||
|
||||
on(
|
||||
event: "task-genius:filter-changed",
|
||||
callback: (filterState: RootFilterState, leafId?: string) => void
|
||||
): EventRef;
|
||||
|
||||
on(
|
||||
event: "task-genius:view-config-changed",
|
||||
callback: (payload: { reason: string; viewId?: string }) => void
|
||||
): EventRef;
|
||||
|
||||
trigger(event: "task-genius:task-completed", task: Task): void;
|
||||
|
||||
trigger(event: "task-genius:task-added", task: Task): void;
|
||||
|
||||
trigger(event: "task-genius:task-updated", task: Task): void;
|
||||
|
||||
trigger(event: "task-genius:task-deleted", taskId: string): void;
|
||||
|
||||
trigger(
|
||||
event: "task-genius:task-cache-updated",
|
||||
cache: TaskCache
|
||||
): void;
|
||||
|
||||
trigger(event: "task-genius:ics-config-changed"): void;
|
||||
|
||||
trigger(event: "task-genius:ics-cache-updated"): void;
|
||||
|
||||
trigger(
|
||||
event: "task-genius:habit-index-updated",
|
||||
habits: HabitProps[]
|
||||
): void;
|
||||
|
||||
trigger(
|
||||
event: "task-genius:filter-changed",
|
||||
filterState: RootFilterState,
|
||||
leafId?: string
|
||||
): void;
|
||||
|
||||
trigger(
|
||||
event: "task-genius:view-config-changed",
|
||||
payload: { reason: string; viewId?: string }
|
||||
|
|
@ -577,9 +579,13 @@ declare module "obsidian" {
|
|||
|
||||
interface WidgetEditorView {
|
||||
editable: boolean;
|
||||
|
||||
showEditor(): void;
|
||||
|
||||
editMode: MarkdownScrollableEditView;
|
||||
|
||||
unload(): void;
|
||||
|
||||
/**
|
||||
* Data after reference.
|
||||
*/
|
||||
|
|
|
|||
168
styles.css
168
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue