mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(core): integrate FileSource and add URI handler support
- Add ObsidianUriHandler for handling obsidian:// URIs - Update plugin initialization to support FileSource - Integrate file filter manager in task indexer - Add URI navigation support in settings - Improve settings search with URI parameters
This commit is contained in:
parent
d58f4873ce
commit
a7e4dafb2a
4 changed files with 472 additions and 4 deletions
|
|
@ -112,7 +112,8 @@ export class TaskIndexer extends Component implements TaskIndexerInterface {
|
|||
this.vault.on("modify", (file) => {
|
||||
if (
|
||||
file instanceof TFile &&
|
||||
isSupportedFileWithFilter(file, this.fileFilterManager)
|
||||
// Inline parsing scope for indexer (inline tasks)
|
||||
isSupportedFileWithFilter(file, this.fileFilterManager, "inline")
|
||||
) {
|
||||
this.queueFileForIndexing(file);
|
||||
}
|
||||
|
|
@ -124,7 +125,8 @@ export class TaskIndexer extends Component implements TaskIndexerInterface {
|
|||
this.vault.on("delete", (file) => {
|
||||
if (
|
||||
file instanceof TFile &&
|
||||
isSupportedFileWithFilter(file, this.fileFilterManager)
|
||||
// Inline parsing scope for indexer (inline tasks)
|
||||
isSupportedFileWithFilter(file, this.fileFilterManager, "inline")
|
||||
) {
|
||||
this.removeFileFromIndex(file);
|
||||
}
|
||||
|
|
@ -136,7 +138,8 @@ export class TaskIndexer extends Component implements TaskIndexerInterface {
|
|||
this.vault.on("create", (file) => {
|
||||
if (
|
||||
file instanceof TFile &&
|
||||
isSupportedFileWithFilter(file, this.fileFilterManager)
|
||||
// Inline parsing scope for indexer (inline tasks)
|
||||
isSupportedFileWithFilter(file, this.fileFilterManager, "inline")
|
||||
) {
|
||||
this.queueFileForIndexing(file);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ import { autoDateManagerExtension } from "./editor-extensions/date-time/date-man
|
|||
import { taskMarkCleanupExtension } from "./editor-extensions/task-operations/mark-cleanup";
|
||||
import { ViewManager } from "./pages/ViewManager";
|
||||
import { IcsManager } from "./managers/ics-manager";
|
||||
import { ObsidianUriHandler } from "./utils/ObsidianUriHandler";
|
||||
import { VersionManager } from "./managers/version-manager";
|
||||
import { RebuildProgressManager } from "./managers/rebuild-progress-manager";
|
||||
import { OnboardingConfigManager } from "./managers/onboarding-manager";
|
||||
|
|
@ -245,6 +246,9 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// MCP Server manager instance (desktop only)
|
||||
mcpServerManager?: McpServerManager;
|
||||
|
||||
// URI handler instance
|
||||
uriHandler?: ObsidianUriHandler;
|
||||
|
||||
// OnCompletion manager instance
|
||||
onCompletionManager?: OnCompletionManager;
|
||||
|
||||
|
|
@ -270,6 +274,10 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Initialize global suggest manager
|
||||
this.globalSuggestManager = new SuggestManager(this.app, this);
|
||||
|
||||
// Initialize URI handler
|
||||
this.uriHandler = new ObsidianUriHandler(this);
|
||||
this.uriHandler.register();
|
||||
|
||||
// Initialize rebuild progress manager
|
||||
this.rebuildProgressManager = new RebuildProgressManager();
|
||||
// Initialize task management systems
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
|
||||
// Tabs management
|
||||
private currentTab: string = "general";
|
||||
public containerEl: HTMLElement;
|
||||
private tabs: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -202,7 +203,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
this.applyDebounceTimer = window.setTimeout(async () => {
|
||||
await plugin.saveSettings();
|
||||
|
||||
// Parsing configuration is now handled by dataflow
|
||||
// Update dataflow orchestrator with new settings
|
||||
if (plugin.dataflowOrchestrator) {
|
||||
plugin.dataflowOrchestrator.updateSettings(plugin.settings);
|
||||
}
|
||||
|
||||
// Trigger view updates to reflect setting changes
|
||||
await plugin.triggerViewUpdate();
|
||||
|
|
@ -394,6 +398,57 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
this.display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific tab via URI
|
||||
*/
|
||||
public navigateToTab(tabId: string, section?: string, search?: string): void {
|
||||
// Set the current tab
|
||||
this.currentTab = tabId;
|
||||
|
||||
// Re-display the settings
|
||||
this.display();
|
||||
|
||||
// Wait for display to complete
|
||||
setTimeout(() => {
|
||||
// If search is provided, perform search
|
||||
if (search && this.searchComponent) {
|
||||
this.searchComponent.performSearch(search);
|
||||
}
|
||||
|
||||
// If section is provided, scroll to it
|
||||
if (section) {
|
||||
this.scrollToSection(section);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to a specific section within the current tab
|
||||
*/
|
||||
private scrollToSection(sectionId: string): void {
|
||||
// Look for headers containing the section ID
|
||||
const headers = this.containerEl.querySelectorAll("h3, h4");
|
||||
headers.forEach((header: HTMLElement) => {
|
||||
const headerText = header.textContent?.toLowerCase();
|
||||
if (headerText && headerText.includes(sectionId.replace("-", " "))) {
|
||||
header.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
});
|
||||
|
||||
// Special handling for MCP sections
|
||||
if (sectionId === "cursor" && this.currentTab === "mcp-integration") {
|
||||
const cursorSection = this.containerEl.querySelector(".mcp-client-section");
|
||||
if (cursorSection) {
|
||||
const header = cursorSection.querySelector(".mcp-client-header");
|
||||
if (header && header.textContent?.includes("Cursor")) {
|
||||
// Click to expand
|
||||
(header as HTMLElement).click();
|
||||
cursorSection.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createTabSection(tabId: string): HTMLElement {
|
||||
// Get the sections container
|
||||
const sectionsContainer = this.containerEl.querySelector(
|
||||
|
|
|
|||
402
src/utils/ObsidianUriHandler.ts
Normal file
402
src/utils/ObsidianUriHandler.ts
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
/**
|
||||
* Obsidian URI Handler for Task Genius
|
||||
* Handles custom URI scheme: obsidian://task-genius/...
|
||||
*/
|
||||
|
||||
import { Modal, Notice } from "obsidian";
|
||||
import TaskProgressBarPlugin from "../index";
|
||||
import { t } from "../translations/helper";
|
||||
|
||||
export interface UriParams {
|
||||
action?: string;
|
||||
tab?: string;
|
||||
section?: string;
|
||||
search?: string;
|
||||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
export class ObsidianUriHandler {
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
|
||||
constructor(plugin: TaskProgressBarPlugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the URI handler with Obsidian
|
||||
*/
|
||||
register(): void {
|
||||
this.plugin.registerObsidianProtocolHandler(
|
||||
"task-genius",
|
||||
async (params: UriParams) => {
|
||||
await this.handleUri(params);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming URI requests
|
||||
*/
|
||||
private async handleUri(params: UriParams): Promise<void> {
|
||||
const { action, tab, section, search } = params;
|
||||
|
||||
// Default to settings action if not specified
|
||||
const uriAction = action || "settings";
|
||||
|
||||
switch (uriAction) {
|
||||
case "settings":
|
||||
await this.handleSettingsUri(params);
|
||||
break;
|
||||
case "create-task":
|
||||
await this.handleCreateTaskUri(params);
|
||||
break;
|
||||
case "open-view":
|
||||
await this.handleOpenViewUri(params);
|
||||
break;
|
||||
default:
|
||||
new Notice(t("Unknown URI action: ") + uriAction);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle settings-related URI
|
||||
* Example: obsidian://task-genius/settings?tab=mcp-integration&action=enable
|
||||
*/
|
||||
private async handleSettingsUri(params: UriParams): Promise<void> {
|
||||
const { tab, section, search } = params;
|
||||
|
||||
// Open settings
|
||||
const settings = (this.plugin.app as any).setting;
|
||||
settings.open();
|
||||
settings.openTabById(this.plugin.manifest.id);
|
||||
|
||||
// Wait for settings to be ready
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Navigate to specific tab if provided
|
||||
if (tab) {
|
||||
this.navigateToSettingsTab(tab, section, search);
|
||||
}
|
||||
|
||||
// Handle specific actions
|
||||
if (params.action) {
|
||||
await this.handleSettingsAction(params.action, tab);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a specific settings tab
|
||||
*/
|
||||
private navigateToSettingsTab(
|
||||
tabName: string,
|
||||
section?: string,
|
||||
search?: string
|
||||
): void {
|
||||
// Use the settingTab's navigation method if available
|
||||
if (this.plugin.settingTab && this.plugin.settingTab.navigateToTab) {
|
||||
this.plugin.settingTab.navigateToTab(tabName, section, search);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: Map tab names to tab indices or identifiers
|
||||
const tabMap: Record<string, string> = {
|
||||
"general": "general",
|
||||
"index": "index",
|
||||
"view-settings": "view-settings",
|
||||
"file-filter": "file-filter",
|
||||
"progress-bar": "progress-bar",
|
||||
"task-status": "task-status",
|
||||
"task-handler": "task-handler",
|
||||
"workflow": "workflow",
|
||||
"reward": "reward",
|
||||
"habit": "habit",
|
||||
"mcp-integration": "mcp-integration",
|
||||
"ics": "ics",
|
||||
"time-parsing": "time-parsing",
|
||||
"beta-test": "beta-test",
|
||||
"about": "about"
|
||||
};
|
||||
|
||||
const tabId = tabMap[tabName];
|
||||
if (!tabId) {
|
||||
new Notice(t("Unknown settings tab: ") + tabName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback implementation
|
||||
const modal = (this.plugin.app as any).setting.activeTab;
|
||||
if (!modal) return;
|
||||
|
||||
// Find and click the tab
|
||||
const tabButtons = modal.containerEl.querySelectorAll(".settings-tab");
|
||||
tabButtons.forEach((button: HTMLElement) => {
|
||||
const buttonText = button.textContent?.toLowerCase();
|
||||
if (buttonText && buttonText.includes(tabName.replace("-", " "))) {
|
||||
button.click();
|
||||
|
||||
// If there's a section, try to scroll to it
|
||||
if (section) {
|
||||
setTimeout(() => {
|
||||
this.scrollToSection(section);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// If there's a search term, try to focus the search
|
||||
if (search) {
|
||||
setTimeout(() => {
|
||||
this.performSettingsSearch(search);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to a specific section within the settings
|
||||
*/
|
||||
private scrollToSection(sectionId: string): void {
|
||||
const modal = (this.plugin.app as any).setting.activeTab;
|
||||
if (!modal) return;
|
||||
|
||||
// Look for section headers
|
||||
const headers = modal.containerEl.querySelectorAll("h3, h4");
|
||||
headers.forEach((header: HTMLElement) => {
|
||||
const headerText = header.textContent?.toLowerCase();
|
||||
if (headerText && headerText.includes(sectionId.replace("-", " "))) {
|
||||
header.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
});
|
||||
|
||||
// Special handling for specific sections
|
||||
if (sectionId === "cursor") {
|
||||
// Look for Cursor configuration section
|
||||
const cursorSection = modal.containerEl.querySelector(".mcp-client-section");
|
||||
if (cursorSection) {
|
||||
const header = cursorSection.querySelector(".mcp-client-header");
|
||||
if (header && header.textContent?.includes("Cursor")) {
|
||||
// Click to expand
|
||||
(header as HTMLElement).click();
|
||||
cursorSection.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a search in the settings
|
||||
*/
|
||||
private performSettingsSearch(searchTerm: string): void {
|
||||
const modal = (this.plugin.app as any).setting.activeTab;
|
||||
if (!modal) return;
|
||||
|
||||
// Find the search input
|
||||
const searchInput = modal.containerEl.querySelector(
|
||||
"input[type='search'], input.search-input"
|
||||
) as HTMLInputElement;
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.value = searchTerm;
|
||||
searchInput.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
searchInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle specific actions within settings
|
||||
*/
|
||||
private async handleSettingsAction(
|
||||
action: string,
|
||||
tab?: string
|
||||
): Promise<void> {
|
||||
// Wait for settings to be fully loaded
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const modal = (this.plugin.app as any).setting.activeTab as Modal;
|
||||
if (!modal) return;
|
||||
|
||||
switch (action) {
|
||||
case "enable":
|
||||
if (tab === "mcp-integration") {
|
||||
// Find and click the enable toggle
|
||||
const toggle = modal.containerEl.querySelector(
|
||||
".setting-item:has(.setting-item-name:contains('Enable MCP Server')) .checkbox-container input"
|
||||
) as HTMLInputElement;
|
||||
if (toggle && !toggle.checked) {
|
||||
toggle.click();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "test":
|
||||
if (tab === "mcp-integration") {
|
||||
// Find and click the test button
|
||||
const testButton = Array.from(
|
||||
modal.containerEl.querySelectorAll("button")
|
||||
).find(btn => btn.textContent === t("Test"));
|
||||
if (testButton) {
|
||||
(testButton as HTMLButtonElement).click();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "regenerate-token":
|
||||
if (tab === "mcp-integration") {
|
||||
// Find and click the regenerate button
|
||||
const regenerateButton = Array.from(
|
||||
modal.containerEl.querySelectorAll("button")
|
||||
).find(btn => btn.textContent === t("Regenerate"));
|
||||
if (regenerateButton) {
|
||||
(regenerateButton as HTMLButtonElement).click();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle create task URI
|
||||
* Example: obsidian://task-genius/create-task?content=My%20Task&project=Work
|
||||
*/
|
||||
private async handleCreateTaskUri(params: UriParams): Promise<void> {
|
||||
const {
|
||||
content,
|
||||
project,
|
||||
context,
|
||||
tags,
|
||||
priority,
|
||||
dueDate,
|
||||
startDate
|
||||
} = params;
|
||||
|
||||
if (!content) {
|
||||
new Notice(t("Task content is required"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse tags if provided as comma-separated
|
||||
const taskTags = tags ? tags.split(",").map(t => t.trim()) : [];
|
||||
|
||||
// Create the task using WriteAPI
|
||||
try {
|
||||
if (!this.plugin.writeAPI) {
|
||||
new Notice(t("Task system not initialized"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the daily note or create in inbox
|
||||
const dailyNotePath = this.plugin.app.workspace.getActiveFile()?.path ||
|
||||
`Daily/${new Date().toISOString().split('T')[0]}.md`;
|
||||
|
||||
await this.plugin.writeAPI.createTask({
|
||||
content: decodeURIComponent(content),
|
||||
project: project ? decodeURIComponent(project) : undefined,
|
||||
context: context ? decodeURIComponent(context) : undefined,
|
||||
tags: taskTags,
|
||||
priority: priority ? parseInt(priority) : undefined,
|
||||
dueDate: dueDate || undefined,
|
||||
startDate: startDate || undefined,
|
||||
filePath: dailyNotePath
|
||||
});
|
||||
|
||||
new Notice(t("Task created successfully"));
|
||||
} catch (error) {
|
||||
console.error("Failed to create task from URI:", error);
|
||||
new Notice(t("Failed to create task"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle open view URI
|
||||
* Example: obsidian://task-genius/open-view?type=inbox
|
||||
*/
|
||||
private async handleOpenViewUri(params: UriParams): Promise<void> {
|
||||
const { type } = params;
|
||||
|
||||
if (!type) {
|
||||
new Notice(t("View type is required"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Map view types to leaf types
|
||||
const viewMap: Record<string, string> = {
|
||||
"inbox": "task-progress-bar-view",
|
||||
"forecast": "task-progress-bar-view",
|
||||
"project": "task-progress-bar-view",
|
||||
"tag": "task-progress-bar-view",
|
||||
"review": "task-progress-bar-view",
|
||||
"calendar": "task-progress-bar-view",
|
||||
"gantt": "task-progress-bar-view",
|
||||
"kanban": "task-progress-bar-view",
|
||||
"matrix": "task-progress-bar-view",
|
||||
"table": "task-progress-bar-view"
|
||||
};
|
||||
|
||||
const leafType = viewMap[type];
|
||||
if (!leafType) {
|
||||
new Notice(t("Unknown view type: ") + type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the view
|
||||
const leaf = this.plugin.app.workspace.getLeaf(true);
|
||||
await leaf.setViewState({
|
||||
type: leafType,
|
||||
state: { viewType: type }
|
||||
});
|
||||
|
||||
this.plugin.app.workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate URI for settings
|
||||
*/
|
||||
static generateSettingsUri(
|
||||
tab?: string,
|
||||
section?: string,
|
||||
action?: string,
|
||||
search?: string
|
||||
): string {
|
||||
const params = new URLSearchParams();
|
||||
if (tab) params.set("tab", tab);
|
||||
if (section) params.set("section", section);
|
||||
if (action) params.set("action", action);
|
||||
if (search) params.set("search", search);
|
||||
|
||||
const queryString = params.toString();
|
||||
return `obsidian://task-genius/settings${queryString ? "?" + queryString : ""}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate URI for creating a task
|
||||
*/
|
||||
static generateCreateTaskUri(
|
||||
content: string,
|
||||
options?: {
|
||||
project?: string;
|
||||
context?: string;
|
||||
tags?: string[];
|
||||
priority?: number;
|
||||
dueDate?: string;
|
||||
startDate?: string;
|
||||
}
|
||||
): string {
|
||||
const params = new URLSearchParams();
|
||||
params.set("content", encodeURIComponent(content));
|
||||
|
||||
if (options?.project) params.set("project", encodeURIComponent(options.project));
|
||||
if (options?.context) params.set("context", encodeURIComponent(options.context));
|
||||
if (options?.tags) params.set("tags", options.tags.join(","));
|
||||
if (options?.priority) params.set("priority", options.priority.toString());
|
||||
if (options?.dueDate) params.set("dueDate", options.dueDate);
|
||||
if (options?.startDate) params.set("startDate", options.startDate);
|
||||
|
||||
return `obsidian://task-genius/create-task?${params.toString()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate URI for opening a view
|
||||
*/
|
||||
static generateOpenViewUri(viewType: string): string {
|
||||
return `obsidian://task-genius/open-view?type=${viewType}`;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue