mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
Add TaskNotes CLI capture command
This commit is contained in:
parent
7a1e25588f
commit
da140a4387
11 changed files with 483 additions and 91 deletions
12
AGENTS.md
12
AGENTS.md
|
|
@ -9,7 +9,7 @@ This is an Obsidian plugin. The plugin ID is `tasknotes`.
|
|||
npm run build:test
|
||||
|
||||
# After building, reload the plugin in the running Obsidian instance
|
||||
obsidian plugin:reload id=tasknotes
|
||||
obsidian plugin:reload id=tasknotes vault=test
|
||||
```
|
||||
|
||||
Always run both commands after making changes. Obsidian must be running for the CLI to work.
|
||||
|
|
@ -18,19 +18,19 @@ Always run both commands after making changes. Obsidian must be running for the
|
|||
|
||||
```bash
|
||||
# Check for JavaScript errors after reload
|
||||
obsidian dev:errors
|
||||
obsidian dev:errors vault=test
|
||||
|
||||
# View console output
|
||||
obsidian dev:console
|
||||
obsidian dev:console vault=test
|
||||
|
||||
# Run JavaScript in the Obsidian context
|
||||
obsidian dev:eval code="app.vault.getFiles().length"
|
||||
obsidian dev:eval code="app.vault.getFiles().length" vault=test
|
||||
|
||||
# Take a screenshot to verify UI changes
|
||||
obsidian dev:screenshot path=screenshot.png
|
||||
obsidian dev:screenshot path=screenshot.png vault=test
|
||||
|
||||
# Open developer tools
|
||||
obsidian dev:open
|
||||
obsidian dev:open vault=test
|
||||
```
|
||||
|
||||
## Other Build Commands
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ Example:
|
|||
|
||||
- (#1678) Added task-first timeblock creation and editing helpers, including prefilled task-title context-menu creation, prefilled task attachments, Add Task actions in the create/edit modals, auto-fill of empty titles from selected attachments, and configurable attachment search ordering
|
||||
- Thanks to @Lorite for the fix
|
||||
- Added a `tasknotes:capture` Obsidian CLI command for task creation, with shared NLP-to-task conversion across CLI, modal, and API capture flows plus explicit override flags for title, details, dates, tags, contexts, projects, recurrence, recurrence anchor, reminders, estimate, and literal-title capture
|
||||
- (#1619, #386, #621) Added drag-to-reorder for Kanban and Task List views, including grouped Task List moves, manual ordering support via the `tasknotes_manual_order` property, updated generated `.base` templates, and polished drag/drop feedback for swimlanes, filtered views, and interactive task controls
|
||||
- Thanks to @ac8318740 for the original contribution in PR #1619
|
||||
- Thanks to @iholston and @dsebastien for opening #386 and #621
|
||||
|
|
|
|||
84
src/cli/commands/captureCommand.ts
Normal file
84
src/cli/commands/captureCommand.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import type { CliData } from "obsidian";
|
||||
import type { CliCommandDefinition } from "../types";
|
||||
import { buildTaskCreationDataFromCli } from "../../utils/buildTaskCreationDataFromCli";
|
||||
|
||||
export const captureCliCommand: CliCommandDefinition = {
|
||||
command: "capture",
|
||||
description: "Create a TaskNotes task from free text or explicit flags",
|
||||
flags: {
|
||||
text: {
|
||||
value: "<text>",
|
||||
description: "Task text to parse with NLP unless --literal is set",
|
||||
},
|
||||
title: {
|
||||
value: "<title>",
|
||||
description: "Explicit task title; overrides NLP-derived title",
|
||||
},
|
||||
details: {
|
||||
value: "<details>",
|
||||
description: "Task details/body; overrides NLP-derived details",
|
||||
},
|
||||
status: {
|
||||
value: "<status>",
|
||||
description: "Explicit task status",
|
||||
},
|
||||
priority: {
|
||||
value: "<priority>",
|
||||
description: "Explicit task priority",
|
||||
},
|
||||
due: {
|
||||
value: "<date>",
|
||||
description: "Due date or datetime (YYYY-MM-DD or YYYY-MM-DDTHH:MM)",
|
||||
},
|
||||
scheduled: {
|
||||
value: "<date>",
|
||||
description: "Scheduled date or datetime (YYYY-MM-DD or YYYY-MM-DDTHH:MM)",
|
||||
},
|
||||
tags: {
|
||||
value: "<tag1,tag2>",
|
||||
description: "Comma-separated tags; overrides NLP-derived tags",
|
||||
},
|
||||
contexts: {
|
||||
value: "<ctx1,ctx2>",
|
||||
description: "Comma-separated contexts; overrides NLP-derived contexts",
|
||||
},
|
||||
projects: {
|
||||
value: "<proj1,proj2>",
|
||||
description: "Comma-separated projects; overrides NLP-derived projects",
|
||||
},
|
||||
recurrence: {
|
||||
value: "<rrule>",
|
||||
description: "Explicit recurrence rule",
|
||||
},
|
||||
"recurrence-anchor": {
|
||||
value: "<scheduled|completion>",
|
||||
description: "How recurring tasks advance: from the scheduled date or completion date",
|
||||
},
|
||||
reminders: {
|
||||
value: "<spec>",
|
||||
description: "Reminder spec(s): due:-PT1H;scheduled:-PT30M;at:2026-04-02T09:00 or a JSON array",
|
||||
},
|
||||
estimate: {
|
||||
value: "<minutes>",
|
||||
description: "Time estimate in minutes",
|
||||
},
|
||||
literal: {
|
||||
description: "Treat --text as a literal title instead of parsing it with NLP",
|
||||
},
|
||||
},
|
||||
async handler(plugin, params: CliData): Promise<string> {
|
||||
const { taskData, usedNlp } = buildTaskCreationDataFromCli(plugin, params);
|
||||
const result = await plugin.taskService.createTask(taskData);
|
||||
return JSON.stringify(
|
||||
{
|
||||
title: result.taskInfo.title,
|
||||
path: result.taskInfo.path,
|
||||
status: result.taskInfo.status,
|
||||
priority: result.taskInfo.priority,
|
||||
usedNlp,
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
},
|
||||
};
|
||||
25
src/cli/registerCliHandlers.ts
Normal file
25
src/cli/registerCliHandlers.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { requireApiVersion } from "obsidian";
|
||||
import type TaskNotesPlugin from "../main";
|
||||
import { captureCliCommand } from "./commands/captureCommand";
|
||||
import type { CliCommandDefinition } from "./types";
|
||||
|
||||
const CLI_COMMANDS: CliCommandDefinition[] = [captureCliCommand];
|
||||
|
||||
export function registerCliHandlers(plugin: TaskNotesPlugin): void {
|
||||
if (!requireApiVersion("1.12.2")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof plugin.registerCliHandler !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const definition of CLI_COMMANDS) {
|
||||
plugin.registerCliHandler(
|
||||
`${plugin.manifest.id}:${definition.command}`,
|
||||
definition.description,
|
||||
definition.flags,
|
||||
(params) => definition.handler(plugin, params)
|
||||
);
|
||||
}
|
||||
}
|
||||
9
src/cli/types.ts
Normal file
9
src/cli/types.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import type { CliData, CliFlags } from "obsidian";
|
||||
import type TaskNotesPlugin from "../main";
|
||||
|
||||
export interface CliCommandDefinition {
|
||||
command: string;
|
||||
description: string;
|
||||
flags: CliFlags | null;
|
||||
handler: (plugin: TaskNotesPlugin, params: CliData) => string | Promise<string>;
|
||||
}
|
||||
|
|
@ -87,6 +87,7 @@ import {
|
|||
registerRibbonIcons,
|
||||
registerTaskNotesIcon,
|
||||
} from "./bootstrap/pluginBootstrap";
|
||||
import { registerCliHandlers } from "./cli/registerCliHandlers";
|
||||
|
||||
interface TranslatedCommandDefinition {
|
||||
id: string;
|
||||
|
|
@ -297,6 +298,7 @@ export default class TaskNotesPlugin extends Plugin {
|
|||
|
||||
// Add commands
|
||||
this.addCommands();
|
||||
registerCliHandlers(this);
|
||||
|
||||
// Add settings tab
|
||||
this.addSettingTab(new TaskNotesSettingTab(this.app, this));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { App, SuggestModal, TFile, Notice, setIcon, debounce } from "obsidian";
|
||||
import { TaskInfo, TaskCreationData } from "../types";
|
||||
import { combineDateAndTime, getCurrentTimestamp } from "../utils/dateUtils";
|
||||
import { filterEmptyProjects, sanitizeTags } from "../utils/helpers";
|
||||
import { filterEmptyProjects } from "../utils/helpers";
|
||||
import type TaskNotesPlugin from "../main";
|
||||
import { TranslationKey } from "../i18n";
|
||||
import {
|
||||
|
|
@ -9,6 +8,7 @@ import {
|
|||
ParsedTaskData,
|
||||
} from "../services/NaturalLanguageParser";
|
||||
import { createTaskCard } from "../ui/TaskCard";
|
||||
import { buildTaskCreationDataFromParsed } from "../utils/buildTaskCreationDataFromParsed";
|
||||
|
||||
export type TaskSelectorWithCreateResult =
|
||||
| { type: "selected"; task: TaskInfo }
|
||||
|
|
@ -306,74 +306,7 @@ export class TaskSelectorWithCreateModal extends SuggestModal<TaskInfo> {
|
|||
}
|
||||
|
||||
private buildTaskDataFromParsed(parsed: ParsedTaskData): TaskCreationData {
|
||||
const now = getCurrentTimestamp();
|
||||
|
||||
const taskData: TaskCreationData = {
|
||||
title: parsed.title.trim(),
|
||||
status: parsed.status || this.plugin.settings.defaultTaskStatus,
|
||||
priority: parsed.priority || this.plugin.settings.defaultTaskPriority,
|
||||
dateCreated: now,
|
||||
dateModified: now,
|
||||
};
|
||||
|
||||
// Handle due date with time
|
||||
if (parsed.dueDate) {
|
||||
taskData.due = parsed.dueTime
|
||||
? combineDateAndTime(parsed.dueDate, parsed.dueTime)
|
||||
: parsed.dueDate;
|
||||
}
|
||||
|
||||
// Handle scheduled date with time
|
||||
if (parsed.scheduledDate) {
|
||||
taskData.scheduled = parsed.scheduledTime
|
||||
? combineDateAndTime(parsed.scheduledDate, parsed.scheduledTime)
|
||||
: parsed.scheduledDate;
|
||||
}
|
||||
|
||||
if (parsed.contexts && parsed.contexts.length > 0) {
|
||||
taskData.contexts = parsed.contexts;
|
||||
}
|
||||
|
||||
if (parsed.projects && parsed.projects.length > 0) {
|
||||
taskData.projects = parsed.projects;
|
||||
}
|
||||
|
||||
if (parsed.tags && parsed.tags.length > 0) {
|
||||
taskData.tags = parsed.tags.map((t) => sanitizeTags(t));
|
||||
}
|
||||
|
||||
if (parsed.details) {
|
||||
taskData.details = parsed.details;
|
||||
}
|
||||
|
||||
if (parsed.recurrence) {
|
||||
taskData.recurrence = parsed.recurrence;
|
||||
}
|
||||
|
||||
if (parsed.estimate && parsed.estimate > 0) {
|
||||
taskData.timeEstimate = parsed.estimate;
|
||||
}
|
||||
|
||||
// Handle NLP-parsed user fields - convert from fieldId to frontmatter key
|
||||
// Default values for user fields are applied by TaskService.createTask()
|
||||
if (parsed.userFields) {
|
||||
const userFieldDefs = this.plugin.settings.userFields || [];
|
||||
const customFrontmatter: Record<string, any> = {};
|
||||
|
||||
for (const [fieldId, value] of Object.entries(parsed.userFields)) {
|
||||
const fieldDef = userFieldDefs.find((f) => f.id === fieldId);
|
||||
if (fieldDef) {
|
||||
// Use the frontmatter key from the field definition
|
||||
customFrontmatter[fieldDef.key] = Array.isArray(value) ? value.join(", ") : value;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(customFrontmatter).length > 0) {
|
||||
taskData.customFrontmatter = customFrontmatter;
|
||||
}
|
||||
}
|
||||
|
||||
return taskData;
|
||||
return buildTaskCreationDataFromParsed(this.plugin, parsed);
|
||||
}
|
||||
|
||||
getSuggestions(query: string): TaskInfo[] {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
computeTaskTimeData,
|
||||
} from "../utils/timeTrackingUtils";
|
||||
import { collectCalendarEvents } from "../utils/calendarUtils";
|
||||
import { buildTaskCreationDataFromParsed } from "../utils/buildTaskCreationDataFromParsed";
|
||||
|
||||
/**
|
||||
* MCP (Model Context Protocol) server for TaskNotes.
|
||||
|
|
@ -326,22 +327,9 @@ export class MCPService {
|
|||
async ({ text }: any) => {
|
||||
try {
|
||||
const parsed = this.nlParser.parseInput(text);
|
||||
const taskData: TaskCreationData = {
|
||||
title: parsed.title,
|
||||
path: "",
|
||||
archived: false,
|
||||
status: parsed.status || this.plugin.settings.defaultTaskStatus,
|
||||
priority: parsed.priority || this.plugin.settings.defaultTaskPriority,
|
||||
due: parsed.dueDate,
|
||||
scheduled: parsed.scheduledDate,
|
||||
tags: parsed.tags,
|
||||
contexts: parsed.contexts,
|
||||
projects: parsed.projects,
|
||||
recurrence: parsed.recurrence,
|
||||
timeEstimate: parsed.estimate,
|
||||
details: parsed.details,
|
||||
const taskData = buildTaskCreationDataFromParsed(this.plugin, parsed, {
|
||||
creationContext: "api",
|
||||
};
|
||||
});
|
||||
const result = await this.taskService.createTask(taskData);
|
||||
|
||||
return this.jsonResult({ parsed, task: result.taskInfo });
|
||||
|
|
|
|||
34
src/types/obsidian-1.12-cli.d.ts
vendored
Normal file
34
src/types/obsidian-1.12-cli.d.ts
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Type declarations for Obsidian 1.12.2 CLI APIs
|
||||
* These augment the existing obsidian module types
|
||||
*/
|
||||
|
||||
import "obsidian";
|
||||
|
||||
declare module "obsidian" {
|
||||
export interface CliData {
|
||||
[key: string]: string | "true";
|
||||
}
|
||||
|
||||
export interface CliFlag {
|
||||
value?: string;
|
||||
description: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export type CliFlags = Record<string, CliFlag>;
|
||||
export type CliHandler = (params: CliData) => string | Promise<string>;
|
||||
|
||||
interface Plugin {
|
||||
/**
|
||||
* Register a CLI handler to handle a command from the CLI.
|
||||
* @since Obsidian 1.12.2
|
||||
*/
|
||||
registerCliHandler(
|
||||
command: string,
|
||||
description: string,
|
||||
flags: CliFlags | null,
|
||||
handler: CliHandler
|
||||
): void;
|
||||
}
|
||||
}
|
||||
234
src/utils/buildTaskCreationDataFromCli.ts
Normal file
234
src/utils/buildTaskCreationDataFromCli.ts
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
import type { CliData } from "obsidian";
|
||||
import type TaskNotesPlugin from "../main";
|
||||
import type { Reminder, TaskCreationData } from "../types";
|
||||
import { NaturalLanguageParser } from "../services/NaturalLanguageParser";
|
||||
import { getCurrentTimestamp } from "./dateUtils";
|
||||
import { sanitizeTags } from "./helpers";
|
||||
import { buildTaskCreationDataFromParsed } from "./buildTaskCreationDataFromParsed";
|
||||
|
||||
export interface CliCaptureBuildResult {
|
||||
taskData: TaskCreationData;
|
||||
usedNlp: boolean;
|
||||
}
|
||||
|
||||
function parseCsvList(value?: string): string[] | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const items = value
|
||||
.split(",")
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item.length > 0);
|
||||
|
||||
return items.length > 0 ? items : undefined;
|
||||
}
|
||||
|
||||
function parseEstimate(value?: string): number | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed) || parsed < 0) {
|
||||
throw new Error("--estimate must be a non-negative number of minutes");
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function buildReminderId(index: number): string {
|
||||
return `rem_${Date.now()}_${index}_${Math.random().toString(36).slice(2, 11)}`;
|
||||
}
|
||||
|
||||
function normalizeAbsoluteTime(value: string): string {
|
||||
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) {
|
||||
return `${value}:00`;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseReminderShorthand(value: string, index: number): Reminder {
|
||||
const trimmed = value.trim();
|
||||
|
||||
if (trimmed.startsWith("at:")) {
|
||||
const absoluteTime = normalizeAbsoluteTime(trimmed.slice(3).trim());
|
||||
if (!absoluteTime) {
|
||||
throw new Error("Absolute reminders must use at:YYYY-MM-DDTHH:MM or full ISO datetime");
|
||||
}
|
||||
|
||||
return {
|
||||
id: buildReminderId(index),
|
||||
type: "absolute",
|
||||
description: "Reminder",
|
||||
absoluteTime,
|
||||
};
|
||||
}
|
||||
|
||||
const separatorIndex = trimmed.indexOf(":");
|
||||
if (separatorIndex === -1) {
|
||||
throw new Error(
|
||||
"Reminder shorthand must use due:<offset>, scheduled:<offset>, or at:<datetime>"
|
||||
);
|
||||
}
|
||||
|
||||
const relatedTo = trimmed.slice(0, separatorIndex).trim();
|
||||
const offset = trimmed.slice(separatorIndex + 1).trim();
|
||||
if ((relatedTo !== "due" && relatedTo !== "scheduled") || !offset) {
|
||||
throw new Error(
|
||||
"Relative reminders must use due:<offset> or scheduled:<offset> with ISO 8601 durations like -PT1H"
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: buildReminderId(index),
|
||||
type: "relative",
|
||||
description: "Reminder",
|
||||
relatedTo,
|
||||
offset,
|
||||
};
|
||||
}
|
||||
|
||||
function parseReminders(value?: string): Reminder[] | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("[")) {
|
||||
const parsed = JSON.parse(trimmed);
|
||||
if (!Array.isArray(parsed)) {
|
||||
throw new Error("--reminders JSON must be an array");
|
||||
}
|
||||
|
||||
return parsed as Reminder[];
|
||||
}
|
||||
|
||||
const reminders = trimmed
|
||||
.split(";")
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry) => entry.length > 0)
|
||||
.map((entry, index) => parseReminderShorthand(entry, index));
|
||||
|
||||
return reminders.length > 0 ? reminders : undefined;
|
||||
}
|
||||
|
||||
function parseRecurrenceAnchor(
|
||||
value?: string
|
||||
): TaskCreationData["recurrence_anchor"] | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (value !== "scheduled" && value !== "completion") {
|
||||
throw new Error("--recurrence-anchor must be either 'scheduled' or 'completion'");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function buildTaskCreationDataFromCli(
|
||||
plugin: TaskNotesPlugin,
|
||||
params: CliData
|
||||
): CliCaptureBuildResult {
|
||||
const text = params.text?.trim();
|
||||
const titleOverride = params.title?.trim();
|
||||
const literal = params.literal === "true";
|
||||
|
||||
let taskData: TaskCreationData;
|
||||
let usedNlp = false;
|
||||
|
||||
if (text && !literal) {
|
||||
const parsed = NaturalLanguageParser.fromPlugin(plugin).parseInput(text);
|
||||
taskData = buildTaskCreationDataFromParsed(plugin, parsed, {
|
||||
creationContext: "api",
|
||||
});
|
||||
usedNlp = true;
|
||||
} else {
|
||||
const now = getCurrentTimestamp();
|
||||
const title = titleOverride || text;
|
||||
if (!title) {
|
||||
throw new Error("Either --text or --title is required");
|
||||
}
|
||||
|
||||
taskData = {
|
||||
title,
|
||||
status: plugin.settings.defaultTaskStatus,
|
||||
priority: plugin.settings.defaultTaskPriority,
|
||||
dateCreated: now,
|
||||
dateModified: now,
|
||||
creationContext: "api",
|
||||
};
|
||||
}
|
||||
|
||||
if (!taskData.title || taskData.title.trim() === "" || taskData.title === "Untitled Task") {
|
||||
if (!titleOverride) {
|
||||
throw new Error("Could not derive a task title from --text; pass --title to override");
|
||||
}
|
||||
}
|
||||
|
||||
if (titleOverride) {
|
||||
taskData.title = titleOverride;
|
||||
}
|
||||
|
||||
if (params.details) {
|
||||
taskData.details = params.details;
|
||||
}
|
||||
|
||||
if (params.status) {
|
||||
taskData.status = params.status;
|
||||
}
|
||||
|
||||
if (params.priority) {
|
||||
taskData.priority = params.priority;
|
||||
}
|
||||
|
||||
if (params.due) {
|
||||
taskData.due = params.due;
|
||||
}
|
||||
|
||||
if (params.scheduled) {
|
||||
taskData.scheduled = params.scheduled;
|
||||
}
|
||||
|
||||
const tags = parseCsvList(params.tags);
|
||||
if (tags) {
|
||||
taskData.tags = tags.map((tag) => sanitizeTags(tag));
|
||||
}
|
||||
|
||||
const contexts = parseCsvList(params.contexts);
|
||||
if (contexts) {
|
||||
taskData.contexts = contexts;
|
||||
}
|
||||
|
||||
const projects = parseCsvList(params.projects);
|
||||
if (projects) {
|
||||
taskData.projects = projects;
|
||||
}
|
||||
|
||||
if (params.recurrence) {
|
||||
taskData.recurrence = params.recurrence;
|
||||
}
|
||||
|
||||
const recurrenceAnchor = parseRecurrenceAnchor(params["recurrence-anchor"]);
|
||||
if (recurrenceAnchor) {
|
||||
taskData.recurrence_anchor = recurrenceAnchor;
|
||||
}
|
||||
|
||||
const estimate = parseEstimate(params.estimate);
|
||||
if (estimate !== undefined) {
|
||||
taskData.timeEstimate = estimate;
|
||||
}
|
||||
|
||||
const reminders = parseReminders(params.reminders);
|
||||
if (reminders) {
|
||||
taskData.reminders = reminders;
|
||||
}
|
||||
|
||||
return { taskData, usedNlp };
|
||||
}
|
||||
82
src/utils/buildTaskCreationDataFromParsed.ts
Normal file
82
src/utils/buildTaskCreationDataFromParsed.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import type TaskNotesPlugin from "../main";
|
||||
import type { ParsedTaskData } from "../services/NaturalLanguageParser";
|
||||
import type { TaskCreationData } from "../types";
|
||||
import { combineDateAndTime, getCurrentTimestamp } from "./dateUtils";
|
||||
import { sanitizeTags } from "./helpers";
|
||||
|
||||
interface BuildTaskCreationDataOptions {
|
||||
creationContext?: TaskCreationData["creationContext"];
|
||||
}
|
||||
|
||||
export function buildTaskCreationDataFromParsed(
|
||||
plugin: TaskNotesPlugin,
|
||||
parsed: ParsedTaskData,
|
||||
options: BuildTaskCreationDataOptions = {}
|
||||
): TaskCreationData {
|
||||
const now = getCurrentTimestamp();
|
||||
const taskData: TaskCreationData = {
|
||||
title: parsed.title.trim(),
|
||||
status: parsed.status || plugin.settings.defaultTaskStatus,
|
||||
priority: parsed.priority || plugin.settings.defaultTaskPriority,
|
||||
dateCreated: now,
|
||||
dateModified: now,
|
||||
};
|
||||
|
||||
if (options.creationContext) {
|
||||
taskData.creationContext = options.creationContext;
|
||||
}
|
||||
|
||||
if (parsed.dueDate) {
|
||||
taskData.due = parsed.dueTime
|
||||
? combineDateAndTime(parsed.dueDate, parsed.dueTime)
|
||||
: parsed.dueDate;
|
||||
}
|
||||
|
||||
if (parsed.scheduledDate) {
|
||||
taskData.scheduled = parsed.scheduledTime
|
||||
? combineDateAndTime(parsed.scheduledDate, parsed.scheduledTime)
|
||||
: parsed.scheduledDate;
|
||||
}
|
||||
|
||||
if (parsed.contexts && parsed.contexts.length > 0) {
|
||||
taskData.contexts = parsed.contexts;
|
||||
}
|
||||
|
||||
if (parsed.projects && parsed.projects.length > 0) {
|
||||
taskData.projects = parsed.projects;
|
||||
}
|
||||
|
||||
if (parsed.tags && parsed.tags.length > 0) {
|
||||
taskData.tags = parsed.tags.map((tag) => sanitizeTags(tag));
|
||||
}
|
||||
|
||||
if (parsed.details) {
|
||||
taskData.details = parsed.details;
|
||||
}
|
||||
|
||||
if (parsed.recurrence) {
|
||||
taskData.recurrence = parsed.recurrence;
|
||||
}
|
||||
|
||||
if (parsed.estimate && parsed.estimate > 0) {
|
||||
taskData.timeEstimate = parsed.estimate;
|
||||
}
|
||||
|
||||
if (parsed.userFields) {
|
||||
const userFieldDefs = plugin.settings.userFields || [];
|
||||
const customFrontmatter: Record<string, any> = {};
|
||||
|
||||
for (const [fieldId, value] of Object.entries(parsed.userFields)) {
|
||||
const fieldDef = userFieldDefs.find((field) => field.id === fieldId);
|
||||
if (fieldDef) {
|
||||
customFrontmatter[fieldDef.key] = Array.isArray(value) ? value.join(", ") : value;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(customFrontmatter).length > 0) {
|
||||
taskData.customFrontmatter = customFrontmatter;
|
||||
}
|
||||
}
|
||||
|
||||
return taskData;
|
||||
}
|
||||
Loading…
Reference in a new issue