/**
* Helper functions for task content parsing and date formatting.
* @module
*/
import type { TaskMarker, TaskPriority } from "../types.js";
// =============================================================================
// Task Content Parsing
// =============================================================================
/** Regex to parse task content: MARKER [#PRIORITY] description */
const TASK_REGEX = /^(TODO|DOING|DONE|CANCELED|LATER|NOW|WAITING)\s*(?:\[#([ABC])\])?\s*(.*)$/;
/** Regex to strip any remaining task markers from the start of a description */
const STRIP_MARKERS_REGEX = /^(TODO|DOING|DONE|CANCELED|LATER|NOW|WAITING)\s*(?:\[#[ABC]\])?\s*/i;
/**
* Parses task content to extract marker, priority, and description.
*
* @param content - Block content string
* @returns Parsed components
*/
export function parseTaskContent(content: string): {
marker?: TaskMarker;
priority?: TaskPriority;
description: string;
} {
const match = content.match(TASK_REGEX);
if (!match) {
return { description: content };
}
const [, marker, priority, description] = match;
// Strip any remaining task markers from the description
// This handles cases like "DONE DOING task" where the description still contains a marker
let cleanDescription = (description ?? "").trim();
while (STRIP_MARKERS_REGEX.test(cleanDescription)) {
cleanDescription = cleanDescription.replace(STRIP_MARKERS_REGEX, "").trim();
}
const result: { marker?: TaskMarker; priority?: TaskPriority; description: string } = {
marker: marker as TaskMarker,
description: cleanDescription,
};
if (priority) {
result.priority = priority as TaskPriority;
}
return result;
}
/**
* Builds task content from components.
*
* @param marker - Task marker
* @param priority - Optional priority
* @param description - Task description
* @returns Formatted task content
*/
export function buildTaskContent(
marker: TaskMarker,
priority: TaskPriority | undefined,
description: string
): string {
const parts: string[] = [marker];
if (priority) {
parts.push(`[#${priority}]`);
}
parts.push(description);
return parts.join(" ");
}
// =============================================================================
// Date Formatting
// =============================================================================
/**
* Formats an ISO date string to Logseq's date format.
*
* @param isoDate - Date in YYYY-MM-DD format
* @returns Logseq date format like <2024-12-15 Sun>
*/
export function formatLogseqDate(isoDate: string): string {
const date = new Date(isoDate + "T00:00:00");
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const dayName = days[date.getDay()];
return `<${isoDate} ${dayName}>`;
}
/**
* Parses a Logseq date to ISO format.
*
* @param logseqDate - Date in Logseq format: number (YYYYMMDD), string like <2024-12-15 Sun>, or just the date part
* @returns ISO date string (YYYY-MM-DD) or undefined
*/
export function parseLogseqDate(logseqDate: string | number | undefined): string | undefined {
if (logseqDate === undefined || logseqDate === null) return undefined;
// Handle numeric format (YYYYMMDD) from Logseq's journalDay/deadline fields
if (typeof logseqDate === "number") {
const str = String(logseqDate);
if (str.length === 8) {
return `${str.slice(0, 4)}-${str.slice(4, 6)}-${str.slice(6, 8)}`;
}
return undefined;
}
// Handle <2024-12-15 Sun> format
const match = logseqDate.match(/<?(\d{4}-\d{2}-\d{2})/);
return match ? match[1] : undefined;
}
/**
* Gets the ordinal suffix for a day number.
*
* @param day - Day of month (1-31)
* @returns Ordinal suffix ("st", "nd", "rd", or "th")
*/
function getOrdinalSuffix(day: number): string {
if (day >= 11 && day <= 13) return "th";
switch (day % 10) {
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
}
/**
* Formats a Date as a Logseq journal page name.
*
* @param date - Date to format
* @returns Journal name like "Dec 4th, 2025"
*/
export function formatJournalName(date: Date): string {
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const month = months[date.getMonth()];
const day = date.getDate();
const year = date.getFullYear();
return `${month} ${day}${getOrdinalSuffix(day)}, ${year}`;
}
/**
* Converts an ISO date string to Logseq's journalDay format (YYYYMMDD number).
*
* @param isoDate - Date in YYYY-MM-DD format
* @returns Number in YYYYMMDD format
*/
export function isoToJournalDay(isoDate: string): number {
return parseInt(isoDate.replace(/-/g, ""), 10);
}
/**
* Gets today's date as an ISO string (YYYY-MM-DD).
*/
export function getTodayIso(): string {
const now = new Date();
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
}