epoch-tui 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1964 -222
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { render } from "ink";
|
|
5
5
|
|
|
6
6
|
// src/App.tsx
|
|
7
|
-
import { Box as
|
|
7
|
+
import { Box as Box21, Text as Text18 } from "ink";
|
|
8
8
|
|
|
9
9
|
// src/contexts/ThemeContext.tsx
|
|
10
10
|
import { createContext as createContext2, useContext as useContext2, useState as useState2, useEffect as useEffect2 } from "react";
|
|
@@ -1453,7 +1453,8 @@ var getDefaultSchema = () => ({
|
|
|
1453
1453
|
theme: "dark",
|
|
1454
1454
|
defaultStartTime: "now",
|
|
1455
1455
|
dateFormat: "MMMM do, yyyy",
|
|
1456
|
-
timeFormat: "12h"
|
|
1456
|
+
timeFormat: "12h",
|
|
1457
|
+
autoMoveUnfinishedTasks: true
|
|
1457
1458
|
}
|
|
1458
1459
|
});
|
|
1459
1460
|
var StorageService = class {
|
|
@@ -1527,6 +1528,10 @@ var StorageService = class {
|
|
|
1527
1528
|
updatedAt: new Date(task.updatedAt),
|
|
1528
1529
|
startTime: task.startTime ? new Date(task.startTime) : void 0,
|
|
1529
1530
|
endTime: task.endTime ? new Date(task.endTime) : void 0,
|
|
1531
|
+
recurrence: task.recurrence ? {
|
|
1532
|
+
...task.recurrence,
|
|
1533
|
+
endDate: task.recurrence.endDate ? new Date(task.recurrence.endDate) : void 0
|
|
1534
|
+
} : void 0,
|
|
1530
1535
|
children: task.children ? task.children.map((child) => this.hydrateTask(child)) : []
|
|
1531
1536
|
};
|
|
1532
1537
|
}
|
|
@@ -1553,6 +1558,10 @@ var StorageService = class {
|
|
|
1553
1558
|
updatedAt: task.updatedAt.toISOString(),
|
|
1554
1559
|
startTime: task.startTime ? task.startTime.toISOString() : void 0,
|
|
1555
1560
|
endTime: task.endTime ? task.endTime.toISOString() : void 0,
|
|
1561
|
+
recurrence: task.recurrence ? {
|
|
1562
|
+
...task.recurrence,
|
|
1563
|
+
endDate: task.recurrence.endDate ? task.recurrence.endDate.toISOString() : void 0
|
|
1564
|
+
} : void 0,
|
|
1556
1565
|
children: task.children ? task.children.map((child) => this.serializeTask(child)) : []
|
|
1557
1566
|
};
|
|
1558
1567
|
}
|
|
@@ -1769,8 +1778,185 @@ var useUndo = () => {
|
|
|
1769
1778
|
return context;
|
|
1770
1779
|
};
|
|
1771
1780
|
|
|
1781
|
+
// src/utils/date.ts
|
|
1782
|
+
import { format, startOfMonth, endOfMonth, eachDayOfInterval, startOfWeek, endOfWeek } from "date-fns";
|
|
1783
|
+
var formatDate = (date, formatStr = "MMM d, yyyy") => {
|
|
1784
|
+
return format(date, formatStr);
|
|
1785
|
+
};
|
|
1786
|
+
var getDateString = (date) => {
|
|
1787
|
+
return format(date, "yyyy-MM-dd");
|
|
1788
|
+
};
|
|
1789
|
+
var parseDateString = (dateStr) => {
|
|
1790
|
+
const [year, month, day] = dateStr.split("-").map(Number);
|
|
1791
|
+
return new Date(year, month - 1, day);
|
|
1792
|
+
};
|
|
1793
|
+
var generateMonthCalendar = (year, month) => {
|
|
1794
|
+
const monthStart = startOfMonth(new Date(year, month, 1));
|
|
1795
|
+
const monthEnd = endOfMonth(monthStart);
|
|
1796
|
+
const calendarStart = startOfWeek(monthStart, { weekStartsOn: 1 });
|
|
1797
|
+
const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 1 });
|
|
1798
|
+
const days = eachDayOfInterval({ start: calendarStart, end: calendarEnd });
|
|
1799
|
+
const weeks = [];
|
|
1800
|
+
for (let i = 0; i < days.length; i += 7) {
|
|
1801
|
+
weeks.push(days.slice(i, i + 7));
|
|
1802
|
+
}
|
|
1803
|
+
return weeks;
|
|
1804
|
+
};
|
|
1805
|
+
var isToday = (date) => {
|
|
1806
|
+
const today = /* @__PURE__ */ new Date();
|
|
1807
|
+
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
|
|
1808
|
+
};
|
|
1809
|
+
|
|
1810
|
+
// src/utils/logger.ts
|
|
1811
|
+
import fs2 from "fs";
|
|
1812
|
+
import path from "path";
|
|
1813
|
+
var Logger = class {
|
|
1814
|
+
constructor() {
|
|
1815
|
+
this.stream = null;
|
|
1816
|
+
this.logFile = path.join(process.cwd(), "debug.log");
|
|
1817
|
+
this.initializeLog();
|
|
1818
|
+
}
|
|
1819
|
+
initializeLog() {
|
|
1820
|
+
if (fs2.existsSync(this.logFile)) {
|
|
1821
|
+
fs2.unlinkSync(this.logFile);
|
|
1822
|
+
}
|
|
1823
|
+
this.stream = fs2.createWriteStream(this.logFile, { flags: "a" });
|
|
1824
|
+
this.log("Logger initialized");
|
|
1825
|
+
}
|
|
1826
|
+
log(message, data) {
|
|
1827
|
+
if (!this.stream) return;
|
|
1828
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1829
|
+
const logMessage = data ? `[${timestamp}] ${message} ${JSON.stringify(data, null, 2)}
|
|
1830
|
+
` : `[${timestamp}] ${message}
|
|
1831
|
+
`;
|
|
1832
|
+
this.stream.write(logMessage);
|
|
1833
|
+
}
|
|
1834
|
+
cleanup() {
|
|
1835
|
+
this.log("Cleaning up logger");
|
|
1836
|
+
if (this.stream) {
|
|
1837
|
+
this.stream.end();
|
|
1838
|
+
this.stream = null;
|
|
1839
|
+
}
|
|
1840
|
+
if (fs2.existsSync(this.logFile)) {
|
|
1841
|
+
fs2.unlinkSync(this.logFile);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
var logger = new Logger();
|
|
1846
|
+
process.on("exit", () => {
|
|
1847
|
+
logger.cleanup();
|
|
1848
|
+
});
|
|
1849
|
+
process.on("SIGINT", () => {
|
|
1850
|
+
logger.cleanup();
|
|
1851
|
+
process.exit(0);
|
|
1852
|
+
});
|
|
1853
|
+
process.on("SIGTERM", () => {
|
|
1854
|
+
logger.cleanup();
|
|
1855
|
+
process.exit(0);
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1858
|
+
// src/services/taskMoveService.ts
|
|
1859
|
+
var TaskMoveService = class {
|
|
1860
|
+
/**
|
|
1861
|
+
* Get all unfinished tasks from a specific date
|
|
1862
|
+
*/
|
|
1863
|
+
getUnfinishedTasks(tasks) {
|
|
1864
|
+
const unfinished = [];
|
|
1865
|
+
const traverse = (taskList) => {
|
|
1866
|
+
for (const task of taskList) {
|
|
1867
|
+
if (task.state === "todo") {
|
|
1868
|
+
unfinished.push(task);
|
|
1869
|
+
}
|
|
1870
|
+
if (task.children.length > 0) {
|
|
1871
|
+
traverse(task.children);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
};
|
|
1875
|
+
traverse(tasks);
|
|
1876
|
+
return unfinished;
|
|
1877
|
+
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Move unfinished tasks from a previous date to a new date
|
|
1880
|
+
*/
|
|
1881
|
+
moveUnfinishedTasksToDate(tasks, fromDate, toDate) {
|
|
1882
|
+
const sourceTasks = tasks[fromDate] || [];
|
|
1883
|
+
const unfinishedTasks = this.getUnfinishedTasks(sourceTasks);
|
|
1884
|
+
if (unfinishedTasks.length === 0) {
|
|
1885
|
+
logger.log(`No unfinished tasks to move from ${fromDate} to ${toDate}`);
|
|
1886
|
+
return tasks;
|
|
1887
|
+
}
|
|
1888
|
+
logger.log(`Moving ${unfinishedTasks.length} tasks from ${fromDate} to ${toDate}`, {
|
|
1889
|
+
taskIds: unfinishedTasks.map((t) => t.id),
|
|
1890
|
+
taskTitles: unfinishedTasks.map((t) => t.title)
|
|
1891
|
+
});
|
|
1892
|
+
const movedTasks = unfinishedTasks.map((task) => ({
|
|
1893
|
+
...task,
|
|
1894
|
+
date: toDate,
|
|
1895
|
+
startTime: void 0,
|
|
1896
|
+
endTime: void 0,
|
|
1897
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1898
|
+
}));
|
|
1899
|
+
const targetTasks = tasks[toDate] || [];
|
|
1900
|
+
const newTargetTasks = [...targetTasks, ...movedTasks];
|
|
1901
|
+
const newSourceTasks = sourceTasks.filter(
|
|
1902
|
+
(task) => !unfinishedTasks.some((ut) => ut.id === task.id)
|
|
1903
|
+
);
|
|
1904
|
+
return {
|
|
1905
|
+
...tasks,
|
|
1906
|
+
[fromDate]: newSourceTasks,
|
|
1907
|
+
[toDate]: newTargetTasks
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Get all dates that have unfinished tasks before a given date
|
|
1912
|
+
*/
|
|
1913
|
+
getDatesWithUnfinishedTasks(tasks, beforeDate) {
|
|
1914
|
+
const dates = [];
|
|
1915
|
+
const beforeDateStr = getDateString(beforeDate);
|
|
1916
|
+
for (const [dateStr, taskList] of Object.entries(tasks)) {
|
|
1917
|
+
if (dateStr < beforeDateStr) {
|
|
1918
|
+
const unfinished = this.getUnfinishedTasks(taskList);
|
|
1919
|
+
if (unfinished.length > 0) {
|
|
1920
|
+
dates.push(dateStr);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
return dates.sort();
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Auto-move all unfinished tasks from previous days to today
|
|
1928
|
+
*/
|
|
1929
|
+
/**
|
|
1930
|
+
* Auto-move all unfinished tasks from previous days to today
|
|
1931
|
+
*/
|
|
1932
|
+
autoMoveUnfinishedTasksToToday(tasks) {
|
|
1933
|
+
const today = /* @__PURE__ */ new Date();
|
|
1934
|
+
const todayStr = getDateString(today);
|
|
1935
|
+
const datesWithUnfinished = this.getDatesWithUnfinishedTasks(tasks, today);
|
|
1936
|
+
logger.log("Starting auto-move of unfinished tasks", {
|
|
1937
|
+
today: todayStr,
|
|
1938
|
+
datesWithUnfinished
|
|
1939
|
+
});
|
|
1940
|
+
let updatedTasks = { ...tasks };
|
|
1941
|
+
for (const dateStr of datesWithUnfinished) {
|
|
1942
|
+
updatedTasks = this.moveUnfinishedTasksToDate(
|
|
1943
|
+
updatedTasks,
|
|
1944
|
+
dateStr,
|
|
1945
|
+
todayStr
|
|
1946
|
+
);
|
|
1947
|
+
logger.log(`Moved tasks from ${dateStr} to ${todayStr}`);
|
|
1948
|
+
}
|
|
1949
|
+
logger.log("Auto-move completed", {
|
|
1950
|
+
tasksMoved: datesWithUnfinished.length,
|
|
1951
|
+
fromDates: datesWithUnfinished
|
|
1952
|
+
});
|
|
1953
|
+
return updatedTasks;
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
var taskMoveService = new TaskMoveService();
|
|
1957
|
+
|
|
1772
1958
|
// src/utils/version.ts
|
|
1773
|
-
var CURRENT_VERSION = "0.1.
|
|
1959
|
+
var CURRENT_VERSION = "0.1.9";
|
|
1774
1960
|
var PACKAGE_NAME = "epoch-tui";
|
|
1775
1961
|
async function checkForUpdate() {
|
|
1776
1962
|
try {
|
|
@@ -1826,6 +2012,11 @@ var AppProvider = ({ children }) => {
|
|
|
1826
2012
|
const [exitConfirmation, setExitConfirmation] = useState4(false);
|
|
1827
2013
|
const [showThemeDialog, setShowThemeDialog] = useState4(false);
|
|
1828
2014
|
const [showClearTimelineDialog, setShowClearTimelineDialog] = useState4(false);
|
|
2015
|
+
const [showSettingsDialog, setShowSettingsDialog] = useState4(false);
|
|
2016
|
+
const [showRecurringTaskDialog, setShowRecurringTaskDialog] = useState4(false);
|
|
2017
|
+
const [recurringTaskId, setRecurringTaskId] = useState4(null);
|
|
2018
|
+
const [showRecurringEditDialog, setShowRecurringEditDialog] = useState4(false);
|
|
2019
|
+
const [recurringEditConfig, setRecurringEditConfig] = useState4(null);
|
|
1829
2020
|
const [showUpdateDialog, setShowUpdateDialog] = useState4(false);
|
|
1830
2021
|
const [updateInfo, setUpdateInfo] = useState4(null);
|
|
1831
2022
|
const clearTimelineForDate = useCallback3((dateStr) => {
|
|
@@ -1873,7 +2064,25 @@ var AppProvider = ({ children }) => {
|
|
|
1873
2064
|
}, [data, save]);
|
|
1874
2065
|
useEffect3(() => {
|
|
1875
2066
|
if (data && !initialLoadDone.current) {
|
|
1876
|
-
|
|
2067
|
+
logger.log("Loading data from storage", {
|
|
2068
|
+
settings: data.settings,
|
|
2069
|
+
taskDates: Object.keys(data.tasks),
|
|
2070
|
+
timelineDates: Object.keys(data.timeline)
|
|
2071
|
+
});
|
|
2072
|
+
let tasksToSet = data.tasks;
|
|
2073
|
+
if (data.settings?.autoMoveUnfinishedTasks !== false) {
|
|
2074
|
+
logger.log("Auto-move enabled, moving unfinished tasks");
|
|
2075
|
+
tasksToSet = taskMoveService.autoMoveUnfinishedTasksToToday(data.tasks);
|
|
2076
|
+
if (tasksToSet !== data.tasks) {
|
|
2077
|
+
saveRef.current({
|
|
2078
|
+
...data,
|
|
2079
|
+
tasks: tasksToSet
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
} else {
|
|
2083
|
+
logger.log("Auto-move disabled, skipping");
|
|
2084
|
+
}
|
|
2085
|
+
setTasks(tasksToSet);
|
|
1877
2086
|
setTimeline(data.timeline);
|
|
1878
2087
|
initialLoadDone.current = true;
|
|
1879
2088
|
checkForUpdate().then((info) => {
|
|
@@ -1894,6 +2103,10 @@ var AppProvider = ({ children }) => {
|
|
|
1894
2103
|
tasks,
|
|
1895
2104
|
timeline
|
|
1896
2105
|
});
|
|
2106
|
+
logger.log("Auto-saving data", {
|
|
2107
|
+
taskCount: Object.keys(tasks).reduce((acc, date) => acc + tasks[date].length, 0),
|
|
2108
|
+
timelineCount: Object.keys(timeline).reduce((acc, date) => acc + (timeline[date]?.length || 0), 0)
|
|
2109
|
+
});
|
|
1897
2110
|
}
|
|
1898
2111
|
}, [tasks, timeline]);
|
|
1899
2112
|
return /* @__PURE__ */ jsx4(
|
|
@@ -1922,8 +2135,18 @@ var AppProvider = ({ children }) => {
|
|
|
1922
2135
|
setShowThemeDialog,
|
|
1923
2136
|
showClearTimelineDialog,
|
|
1924
2137
|
setShowClearTimelineDialog,
|
|
2138
|
+
showSettingsDialog,
|
|
2139
|
+
setShowSettingsDialog,
|
|
2140
|
+
showRecurringTaskDialog,
|
|
2141
|
+
setShowRecurringTaskDialog,
|
|
2142
|
+
recurringTaskId,
|
|
2143
|
+
setRecurringTaskId,
|
|
2144
|
+
showRecurringEditDialog,
|
|
2145
|
+
setShowRecurringEditDialog,
|
|
2146
|
+
recurringEditConfig,
|
|
2147
|
+
setRecurringEditConfig,
|
|
1925
2148
|
clearTimelineForDate,
|
|
1926
|
-
isModalOpen: showHelp || showThemeDialog || showOverview || showClearTimelineDialog || showUpdateDialog,
|
|
2149
|
+
isModalOpen: showHelp || showThemeDialog || showOverview || showClearTimelineDialog || showUpdateDialog || showSettingsDialog || showRecurringTaskDialog || showRecurringEditDialog,
|
|
1927
2150
|
saveNow,
|
|
1928
2151
|
pushUndoableAction,
|
|
1929
2152
|
performUndo,
|
|
@@ -1949,7 +2172,7 @@ var useApp = () => {
|
|
|
1949
2172
|
import { useInput } from "ink";
|
|
1950
2173
|
import { useEffect as useEffect4 } from "react";
|
|
1951
2174
|
var useKeyboardNav = () => {
|
|
1952
|
-
const { showHelp, setShowHelp, activePane, setActivePane, isInputMode, showOverview, setShowOverview, overviewMonth, setOverviewMonth, exitConfirmation, setExitConfirmation, showThemeDialog, setShowThemeDialog, showClearTimelineDialog, setShowClearTimelineDialog, saveNow, performUndo, canUndo } = useApp();
|
|
2175
|
+
const { showHelp, setShowHelp, activePane, setActivePane, isInputMode, showOverview, setShowOverview, overviewMonth, setOverviewMonth, exitConfirmation, setExitConfirmation, showThemeDialog, setShowThemeDialog, showClearTimelineDialog, setShowClearTimelineDialog, showSettingsDialog, setShowSettingsDialog, showRecurringTaskDialog, showRecurringEditDialog, showUpdateDialog, saveNow, performUndo, canUndo } = useApp();
|
|
1953
2176
|
useEffect4(() => {
|
|
1954
2177
|
if (exitConfirmation) {
|
|
1955
2178
|
const timer = setTimeout(() => {
|
|
@@ -1958,10 +2181,11 @@ var useKeyboardNav = () => {
|
|
|
1958
2181
|
return () => clearTimeout(timer);
|
|
1959
2182
|
}
|
|
1960
2183
|
}, [exitConfirmation, setExitConfirmation]);
|
|
1961
|
-
const isActive = !isInputMode && !showThemeDialog && !showClearTimelineDialog;
|
|
2184
|
+
const isActive = !isInputMode && !showThemeDialog && !showClearTimelineDialog && !showSettingsDialog && !showRecurringTaskDialog && !showRecurringEditDialog && !showUpdateDialog;
|
|
1962
2185
|
useInput((input, key) => {
|
|
1963
2186
|
if (key.ctrl && input === "c") {
|
|
1964
2187
|
if (exitConfirmation) {
|
|
2188
|
+
logger.log("Exiting application");
|
|
1965
2189
|
saveNow().then(() => {
|
|
1966
2190
|
const inkApp = global.__inkApp;
|
|
1967
2191
|
if (inkApp) {
|
|
@@ -1985,39 +2209,55 @@ var useKeyboardNav = () => {
|
|
|
1985
2209
|
});
|
|
1986
2210
|
return;
|
|
1987
2211
|
} else {
|
|
2212
|
+
logger.log("Exit confirmation requested");
|
|
1988
2213
|
setExitConfirmation(true);
|
|
1989
2214
|
return;
|
|
1990
2215
|
}
|
|
1991
2216
|
}
|
|
2217
|
+
});
|
|
2218
|
+
useInput((input, key) => {
|
|
1992
2219
|
if (key.ctrl && input === "u" && canUndo) {
|
|
2220
|
+
logger.log("Performing undo");
|
|
1993
2221
|
performUndo();
|
|
1994
2222
|
return;
|
|
1995
2223
|
}
|
|
1996
2224
|
if (input === ":") {
|
|
2225
|
+
logger.log("Toggling overview", { show: !showOverview });
|
|
1997
2226
|
setShowOverview(!showOverview);
|
|
1998
2227
|
return;
|
|
1999
2228
|
}
|
|
2000
2229
|
if (input === "?") {
|
|
2230
|
+
logger.log("Toggling help dialog", { show: !showHelp });
|
|
2001
2231
|
setShowHelp(!showHelp);
|
|
2002
2232
|
return;
|
|
2003
2233
|
}
|
|
2004
2234
|
if (key.ctrl && input === "t") {
|
|
2235
|
+
logger.log("Opening theme dialog");
|
|
2005
2236
|
setShowThemeDialog(true);
|
|
2006
2237
|
return;
|
|
2007
2238
|
}
|
|
2239
|
+
if (key.ctrl && input === "s") {
|
|
2240
|
+
logger.log("Opening settings dialog");
|
|
2241
|
+
setShowSettingsDialog(true);
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2008
2244
|
if (input === "C" && activePane === "timeline") {
|
|
2245
|
+
logger.log("Opening clear timeline dialog");
|
|
2009
2246
|
setShowClearTimelineDialog(true);
|
|
2010
2247
|
return;
|
|
2011
2248
|
}
|
|
2012
2249
|
if (input === "1") {
|
|
2250
|
+
logger.log("Switching to calendar pane");
|
|
2013
2251
|
setActivePane("calendar");
|
|
2014
2252
|
return;
|
|
2015
2253
|
}
|
|
2016
2254
|
if (input === "2" || key.tab && !key.shift) {
|
|
2255
|
+
logger.log("Switching to tasks pane");
|
|
2017
2256
|
setActivePane("tasks");
|
|
2018
2257
|
return;
|
|
2019
2258
|
}
|
|
2020
2259
|
if (input === "3" || key.tab && key.shift) {
|
|
2260
|
+
logger.log("Switching to timeline pane");
|
|
2021
2261
|
setActivePane("timeline");
|
|
2022
2262
|
return;
|
|
2023
2263
|
}
|
|
@@ -2270,31 +2510,6 @@ var KeyboardHints = ({ hints }) => {
|
|
|
2270
2510
|
);
|
|
2271
2511
|
};
|
|
2272
2512
|
|
|
2273
|
-
// src/utils/date.ts
|
|
2274
|
-
import { format, startOfMonth, endOfMonth, eachDayOfInterval, startOfWeek, endOfWeek } from "date-fns";
|
|
2275
|
-
var formatDate = (date, formatStr = "MMM d, yyyy") => {
|
|
2276
|
-
return format(date, formatStr);
|
|
2277
|
-
};
|
|
2278
|
-
var getDateString = (date) => {
|
|
2279
|
-
return format(date, "yyyy-MM-dd");
|
|
2280
|
-
};
|
|
2281
|
-
var generateMonthCalendar = (year, month) => {
|
|
2282
|
-
const monthStart = startOfMonth(new Date(year, month, 1));
|
|
2283
|
-
const monthEnd = endOfMonth(monthStart);
|
|
2284
|
-
const calendarStart = startOfWeek(monthStart);
|
|
2285
|
-
const calendarEnd = endOfWeek(monthEnd);
|
|
2286
|
-
const days = eachDayOfInterval({ start: calendarStart, end: calendarEnd });
|
|
2287
|
-
const weeks = [];
|
|
2288
|
-
for (let i = 0; i < days.length; i += 7) {
|
|
2289
|
-
weeks.push(days.slice(i, i + 7));
|
|
2290
|
-
}
|
|
2291
|
-
return weeks;
|
|
2292
|
-
};
|
|
2293
|
-
var isToday = (date) => {
|
|
2294
|
-
const today = /* @__PURE__ */ new Date();
|
|
2295
|
-
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
|
|
2296
|
-
};
|
|
2297
|
-
|
|
2298
2513
|
// src/services/calendarService.ts
|
|
2299
2514
|
var CalendarService = class {
|
|
2300
2515
|
generateMonthView(year, month, selectedDate, tasks) {
|
|
@@ -2490,9 +2705,9 @@ import { useState as useState7, useEffect as useEffect8, useMemo } from "react";
|
|
|
2490
2705
|
import { Box as Box8, Text as Text7, useInput as useInput4 } from "ink";
|
|
2491
2706
|
|
|
2492
2707
|
// src/components/common/ControlledTextInput.tsx
|
|
2493
|
-
import { useRef as useRef3, useEffect as useEffect7 } from "react";
|
|
2708
|
+
import { useRef as useRef3, useReducer, useEffect as useEffect7 } from "react";
|
|
2494
2709
|
import { Text as Text5, useInput as useInput3 } from "ink";
|
|
2495
|
-
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2710
|
+
import { Fragment, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2496
2711
|
var ControlledTextInput = ({
|
|
2497
2712
|
value,
|
|
2498
2713
|
onChange,
|
|
@@ -2505,22 +2720,39 @@ var ControlledTextInput = ({
|
|
|
2505
2720
|
color,
|
|
2506
2721
|
maxLength
|
|
2507
2722
|
}) => {
|
|
2508
|
-
const
|
|
2723
|
+
const valueRef = useRef3(value);
|
|
2724
|
+
const cursorRef = useRef3(value.length);
|
|
2725
|
+
const [, forceRender] = useReducer((x) => x + 1, 0);
|
|
2509
2726
|
const sentValuesRef = useRef3(/* @__PURE__ */ new Set([value]));
|
|
2510
2727
|
useEffect7(() => {
|
|
2511
|
-
if (value ===
|
|
2728
|
+
if (value === valueRef.current) {
|
|
2512
2729
|
return;
|
|
2513
2730
|
}
|
|
2514
2731
|
if (sentValuesRef.current.has(value)) {
|
|
2515
2732
|
return;
|
|
2516
2733
|
}
|
|
2517
|
-
|
|
2734
|
+
valueRef.current = value;
|
|
2735
|
+
cursorRef.current = value.length;
|
|
2518
2736
|
sentValuesRef.current.clear();
|
|
2519
2737
|
sentValuesRef.current.add(value);
|
|
2738
|
+
forceRender();
|
|
2520
2739
|
}, [value]);
|
|
2740
|
+
const findWordStart = (text, pos) => {
|
|
2741
|
+
let i = pos - 1;
|
|
2742
|
+
while (i >= 0 && text[i] === " ") i--;
|
|
2743
|
+
while (i >= 0 && text[i] !== " ") i--;
|
|
2744
|
+
return i + 1;
|
|
2745
|
+
};
|
|
2746
|
+
const findWordEnd = (text, pos) => {
|
|
2747
|
+
let i = pos;
|
|
2748
|
+
while (i < text.length && text[i] === " ") i++;
|
|
2749
|
+
while (i < text.length && text[i] !== " ") i++;
|
|
2750
|
+
return i;
|
|
2751
|
+
};
|
|
2521
2752
|
useInput3(
|
|
2522
2753
|
(input, key) => {
|
|
2523
|
-
const currentValue =
|
|
2754
|
+
const currentValue = valueRef.current;
|
|
2755
|
+
const cursor2 = cursorRef.current;
|
|
2524
2756
|
if (key.escape) {
|
|
2525
2757
|
onCancel?.();
|
|
2526
2758
|
return;
|
|
@@ -2531,32 +2763,92 @@ var ControlledTextInput = ({
|
|
|
2531
2763
|
}
|
|
2532
2764
|
if (input === "\r" || input === "\n") return;
|
|
2533
2765
|
let nextValue = currentValue;
|
|
2534
|
-
|
|
2535
|
-
|
|
2766
|
+
let nextCursor = cursor2;
|
|
2767
|
+
if (key.backspace) {
|
|
2768
|
+
if (key.ctrl) {
|
|
2769
|
+
const wordStart = findWordStart(currentValue, cursor2);
|
|
2770
|
+
nextValue = currentValue.slice(0, wordStart) + currentValue.slice(cursor2);
|
|
2771
|
+
nextCursor = wordStart;
|
|
2772
|
+
} else if (cursor2 > 0) {
|
|
2773
|
+
nextValue = currentValue.slice(0, cursor2 - 1) + currentValue.slice(cursor2);
|
|
2774
|
+
nextCursor = cursor2 - 1;
|
|
2775
|
+
}
|
|
2776
|
+
} else if (key.delete) {
|
|
2777
|
+
if (cursor2 >= currentValue.length && cursor2 > 0) {
|
|
2778
|
+
nextValue = currentValue.slice(0, cursor2 - 1) + currentValue.slice(cursor2);
|
|
2779
|
+
nextCursor = cursor2 - 1;
|
|
2780
|
+
} else if (cursor2 < currentValue.length) {
|
|
2781
|
+
nextValue = currentValue.slice(0, cursor2) + currentValue.slice(cursor2 + 1);
|
|
2782
|
+
}
|
|
2783
|
+
} else if (key.leftArrow && !key.ctrl) {
|
|
2784
|
+
nextCursor = Math.max(0, cursor2 - 1);
|
|
2785
|
+
} else if (key.rightArrow && !key.ctrl) {
|
|
2786
|
+
nextCursor = Math.min(currentValue.length, cursor2 + 1);
|
|
2787
|
+
} else if (key.leftArrow && key.ctrl) {
|
|
2788
|
+
const wordStart = findWordStart(currentValue, cursor2);
|
|
2789
|
+
nextCursor = wordStart;
|
|
2790
|
+
} else if (key.rightArrow && key.ctrl) {
|
|
2791
|
+
const wordEnd = findWordEnd(currentValue, cursor2);
|
|
2792
|
+
nextCursor = wordEnd;
|
|
2793
|
+
} else if (key.meta && input === "b") {
|
|
2794
|
+
const wordStart = findWordStart(currentValue, cursor2);
|
|
2795
|
+
nextCursor = wordStart;
|
|
2796
|
+
} else if (key.meta && input === "f") {
|
|
2797
|
+
const wordEnd = findWordEnd(currentValue, cursor2);
|
|
2798
|
+
nextCursor = wordEnd;
|
|
2799
|
+
} else if (key.ctrl && input === "a") {
|
|
2800
|
+
nextCursor = 0;
|
|
2801
|
+
} else if (key.ctrl && input === "e") {
|
|
2802
|
+
nextCursor = currentValue.length;
|
|
2803
|
+
} else if (key.ctrl && input === "u") {
|
|
2804
|
+
nextValue = currentValue.slice(cursor2);
|
|
2805
|
+
nextCursor = 0;
|
|
2806
|
+
} else if (key.ctrl && input === "k") {
|
|
2807
|
+
nextValue = currentValue.slice(0, cursor2);
|
|
2808
|
+
} else if (key.ctrl && input === "w") {
|
|
2809
|
+
const wordStart = findWordStart(currentValue, cursor2);
|
|
2810
|
+
nextValue = currentValue.slice(0, wordStart) + currentValue.slice(cursor2);
|
|
2811
|
+
nextCursor = wordStart;
|
|
2536
2812
|
} else if (!key.ctrl && !key.meta && input.length > 0) {
|
|
2537
2813
|
const filtered = input.replace(/[\x00-\x1F\x7F]/g, "");
|
|
2538
2814
|
if (filtered) {
|
|
2539
|
-
const possibleValue = currentValue + filtered;
|
|
2540
|
-
if (maxLength
|
|
2541
|
-
|
|
2815
|
+
const possibleValue = currentValue.slice(0, cursor2) + filtered + currentValue.slice(cursor2);
|
|
2816
|
+
if (!maxLength || possibleValue.length <= maxLength) {
|
|
2817
|
+
nextValue = possibleValue;
|
|
2818
|
+
nextCursor = cursor2 + filtered.length;
|
|
2542
2819
|
}
|
|
2543
|
-
nextValue = possibleValue;
|
|
2544
2820
|
}
|
|
2821
|
+
} else {
|
|
2822
|
+
return;
|
|
2545
2823
|
}
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2824
|
+
const valueChanged = nextValue !== currentValue;
|
|
2825
|
+
const cursorChanged = nextCursor !== cursor2;
|
|
2826
|
+
if (valueChanged || cursorChanged) {
|
|
2827
|
+
valueRef.current = nextValue;
|
|
2828
|
+
cursorRef.current = nextCursor;
|
|
2829
|
+
if (valueChanged) {
|
|
2830
|
+
sentValuesRef.current.add(nextValue);
|
|
2831
|
+
onChange(nextValue);
|
|
2832
|
+
}
|
|
2833
|
+
forceRender();
|
|
2550
2834
|
}
|
|
2551
2835
|
},
|
|
2552
2836
|
{ isActive: focus }
|
|
2553
2837
|
);
|
|
2554
|
-
const displayValue =
|
|
2838
|
+
const displayValue = valueRef.current;
|
|
2839
|
+
const cursor = cursorRef.current;
|
|
2555
2840
|
const showPlaceholder = !displayValue && placeholder;
|
|
2556
|
-
|
|
2557
|
-
|
|
2841
|
+
const beforeCursor = displayValue.slice(0, cursor);
|
|
2842
|
+
const atCursor = displayValue[cursor] || " ";
|
|
2843
|
+
const afterCursor = displayValue.slice(cursor + 1);
|
|
2844
|
+
return /* @__PURE__ */ jsx12(Text5, { children: showPlaceholder ? /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
2845
|
+
/* @__PURE__ */ jsx12(Text5, { dimColor: true, color: placeholderColor, children: placeholder }),
|
|
2558
2846
|
focus && /* @__PURE__ */ jsx12(Text5, { backgroundColor: cursorColor, children: " " })
|
|
2559
|
-
] })
|
|
2847
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
2848
|
+
/* @__PURE__ */ jsx12(Text5, { color, children: beforeCursor }),
|
|
2849
|
+
focus ? /* @__PURE__ */ jsx12(Text5, { color, backgroundColor: cursorColor, children: atCursor }) : /* @__PURE__ */ jsx12(Text5, { color, children: atCursor }),
|
|
2850
|
+
/* @__PURE__ */ jsx12(Text5, { color, children: afterCursor })
|
|
2851
|
+
] }) });
|
|
2560
2852
|
};
|
|
2561
2853
|
|
|
2562
2854
|
// src/components/tasks/TaskHeader.tsx
|
|
@@ -2589,8 +2881,20 @@ var findTaskById = (tasks, id) => {
|
|
|
2589
2881
|
return null;
|
|
2590
2882
|
};
|
|
2591
2883
|
var updateTaskInTree = (tasks, id, updates) => {
|
|
2592
|
-
|
|
2884
|
+
logger.log("[updateTaskInTree] Called", {
|
|
2885
|
+
id,
|
|
2886
|
+
updates,
|
|
2887
|
+
tasksCount: tasks.length,
|
|
2888
|
+
tasksIds: tasks.map((t) => ({ id: t.id, title: t.title, childrenCount: t.children.length }))
|
|
2889
|
+
});
|
|
2890
|
+
const result = tasks.map((task) => {
|
|
2593
2891
|
if (task.id === id) {
|
|
2892
|
+
logger.log("[updateTaskInTree] Found task to update", {
|
|
2893
|
+
id,
|
|
2894
|
+
taskTitle: task.title,
|
|
2895
|
+
currentState: task.state,
|
|
2896
|
+
updates
|
|
2897
|
+
});
|
|
2594
2898
|
return { ...task, ...updates, updatedAt: /* @__PURE__ */ new Date() };
|
|
2595
2899
|
}
|
|
2596
2900
|
return {
|
|
@@ -2598,6 +2902,11 @@ var updateTaskInTree = (tasks, id, updates) => {
|
|
|
2598
2902
|
children: updateTaskInTree(task.children, id, updates)
|
|
2599
2903
|
};
|
|
2600
2904
|
});
|
|
2905
|
+
logger.log("[updateTaskInTree] Completed", {
|
|
2906
|
+
id,
|
|
2907
|
+
resultCount: result.length
|
|
2908
|
+
});
|
|
2909
|
+
return result;
|
|
2601
2910
|
};
|
|
2602
2911
|
var deleteTaskFromTree = (tasks, id) => {
|
|
2603
2912
|
return tasks.filter((task) => task.id !== id).map((task) => ({
|
|
@@ -2684,6 +2993,7 @@ var TaskService = class {
|
|
|
2684
2993
|
if (!validation.valid) {
|
|
2685
2994
|
throw new Error(validation.error);
|
|
2686
2995
|
}
|
|
2996
|
+
logger.log("Creating new task", { title, date, state });
|
|
2687
2997
|
const now = /* @__PURE__ */ new Date();
|
|
2688
2998
|
return {
|
|
2689
2999
|
id: uuid(),
|
|
@@ -2696,6 +3006,7 @@ var TaskService = class {
|
|
|
2696
3006
|
};
|
|
2697
3007
|
}
|
|
2698
3008
|
updateTask(tasks, taskId, updates) {
|
|
3009
|
+
logger.log("Updating task", { taskId, updates });
|
|
2699
3010
|
const dateStr = Object.keys(tasks).find(
|
|
2700
3011
|
(date) => findTaskById(tasks[date], taskId)
|
|
2701
3012
|
);
|
|
@@ -2727,11 +3038,13 @@ var TaskService = class {
|
|
|
2727
3038
|
};
|
|
2728
3039
|
}
|
|
2729
3040
|
deleteTask(tasks, taskId) {
|
|
3041
|
+
logger.log("Deleting task", { taskId });
|
|
2730
3042
|
const dateStr = Object.keys(tasks).find(
|
|
2731
3043
|
(date) => findTaskById(tasks[date], taskId)
|
|
2732
3044
|
);
|
|
2733
3045
|
if (!dateStr) {
|
|
2734
|
-
|
|
3046
|
+
logger.log("Task not found for deletion, it might be an ephemeral recurring instance", { taskId });
|
|
3047
|
+
return tasks;
|
|
2735
3048
|
}
|
|
2736
3049
|
return {
|
|
2737
3050
|
...tasks,
|
|
@@ -2759,6 +3072,7 @@ var TaskService = class {
|
|
|
2759
3072
|
};
|
|
2760
3073
|
}
|
|
2761
3074
|
changeTaskState(tasks, taskId, newState) {
|
|
3075
|
+
logger.log("Changing task state", { taskId, newState });
|
|
2762
3076
|
return this.updateTask(tasks, taskId, {
|
|
2763
3077
|
state: newState,
|
|
2764
3078
|
endTime: ["completed", "delegated", "delayed"].includes(newState) ? /* @__PURE__ */ new Date() : void 0
|
|
@@ -2781,6 +3095,36 @@ var TaskService = class {
|
|
|
2781
3095
|
getTaskStats(tasks, date) {
|
|
2782
3096
|
return getTaskStats(this.getTasksForDate(tasks, date));
|
|
2783
3097
|
}
|
|
3098
|
+
excludeRecurringInstance(tasks, parentTaskId, dateStr) {
|
|
3099
|
+
logger.log("Excluding recurring instance", { parentTaskId, dateStr });
|
|
3100
|
+
const parentDateStr = Object.keys(tasks).find(
|
|
3101
|
+
(date) => tasks[date].some((t) => t.id === parentTaskId)
|
|
3102
|
+
);
|
|
3103
|
+
if (!parentDateStr) {
|
|
3104
|
+
throw new Error("Parent recurring task not found");
|
|
3105
|
+
}
|
|
3106
|
+
return {
|
|
3107
|
+
...tasks,
|
|
3108
|
+
[parentDateStr]: tasks[parentDateStr].map((task) => {
|
|
3109
|
+
if (task.id === parentTaskId) {
|
|
3110
|
+
const recurrence = task.recurrence;
|
|
3111
|
+
if (!recurrence) return task;
|
|
3112
|
+
const excludedDates = recurrence.excludedDates || [];
|
|
3113
|
+
if (!excludedDates.includes(dateStr)) {
|
|
3114
|
+
return {
|
|
3115
|
+
...task,
|
|
3116
|
+
recurrence: {
|
|
3117
|
+
...recurrence,
|
|
3118
|
+
excludedDates: [...excludedDates, dateStr]
|
|
3119
|
+
},
|
|
3120
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
return task;
|
|
3125
|
+
})
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
2784
3128
|
};
|
|
2785
3129
|
var taskService = new TaskService();
|
|
2786
3130
|
|
|
@@ -2855,8 +3199,208 @@ var TimelineService = class {
|
|
|
2855
3199
|
};
|
|
2856
3200
|
var timelineService = new TimelineService();
|
|
2857
3201
|
|
|
3202
|
+
// src/services/recurringTaskService.ts
|
|
3203
|
+
import { addDays as addDays2, addWeeks as addWeeks2, addMonths, addYears, isWeekend, isBefore, isAfter, isSameDay as isSameDay2 } from "date-fns";
|
|
3204
|
+
import { v4 as uuid3 } from "uuid";
|
|
3205
|
+
var RecurringTaskService = class {
|
|
3206
|
+
/**
|
|
3207
|
+
* Check if a date matches the recurrence pattern
|
|
3208
|
+
*/
|
|
3209
|
+
shouldGenerateOnDate(pattern, baseDate, targetDate) {
|
|
3210
|
+
const targetDateStr = getDateString(targetDate);
|
|
3211
|
+
if (pattern.excludedDates && pattern.excludedDates.includes(targetDateStr)) {
|
|
3212
|
+
return false;
|
|
3213
|
+
}
|
|
3214
|
+
if (isBefore(targetDate, baseDate)) {
|
|
3215
|
+
return false;
|
|
3216
|
+
}
|
|
3217
|
+
if (pattern.endDate && isAfter(targetDate, pattern.endDate)) {
|
|
3218
|
+
return false;
|
|
3219
|
+
}
|
|
3220
|
+
if (isSameDay2(baseDate, targetDate)) {
|
|
3221
|
+
return false;
|
|
3222
|
+
}
|
|
3223
|
+
switch (pattern.frequency) {
|
|
3224
|
+
case "daily":
|
|
3225
|
+
return true;
|
|
3226
|
+
case "weekdays":
|
|
3227
|
+
return !isWeekend(targetDate);
|
|
3228
|
+
case "weekly": {
|
|
3229
|
+
const daysDiff = Math.floor(
|
|
3230
|
+
(targetDate.getTime() - baseDate.getTime()) / (1e3 * 60 * 60 * 24)
|
|
3231
|
+
);
|
|
3232
|
+
return daysDiff % 7 === 0;
|
|
3233
|
+
}
|
|
3234
|
+
case "monthly": {
|
|
3235
|
+
return targetDate.getDate() === baseDate.getDate();
|
|
3236
|
+
}
|
|
3237
|
+
case "yearly": {
|
|
3238
|
+
return targetDate.getMonth() === baseDate.getMonth() && targetDate.getDate() === baseDate.getDate();
|
|
3239
|
+
}
|
|
3240
|
+
case "custom": {
|
|
3241
|
+
if (pattern.daysOfWeek && pattern.daysOfWeek.length > 0) {
|
|
3242
|
+
return pattern.daysOfWeek.includes(targetDate.getDay());
|
|
3243
|
+
}
|
|
3244
|
+
if (pattern.interval) {
|
|
3245
|
+
const daysDiff = Math.floor(
|
|
3246
|
+
(targetDate.getTime() - baseDate.getTime()) / (1e3 * 60 * 60 * 24)
|
|
3247
|
+
);
|
|
3248
|
+
return daysDiff % pattern.interval === 0;
|
|
3249
|
+
}
|
|
3250
|
+
return false;
|
|
3251
|
+
}
|
|
3252
|
+
default:
|
|
3253
|
+
return false;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
/**
|
|
3257
|
+
* Clone children tasks recursively for recurring instances
|
|
3258
|
+
*/
|
|
3259
|
+
cloneChildren(children, targetDateStr) {
|
|
3260
|
+
return children.map((child) => ({
|
|
3261
|
+
...child,
|
|
3262
|
+
id: uuid3(),
|
|
3263
|
+
date: targetDateStr,
|
|
3264
|
+
state: "todo",
|
|
3265
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
3266
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
3267
|
+
startTime: void 0,
|
|
3268
|
+
endTime: void 0,
|
|
3269
|
+
// Recursively clone nested children
|
|
3270
|
+
children: this.cloneChildren(child.children, targetDateStr)
|
|
3271
|
+
}));
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Generate recurring task instance for a specific date
|
|
3275
|
+
*/
|
|
3276
|
+
generateRecurringInstance(parentTask, targetDate) {
|
|
3277
|
+
const targetDateStr = getDateString(targetDate);
|
|
3278
|
+
const parentDateStr = parentTask.date;
|
|
3279
|
+
logger.log("Generating recurring instance", {
|
|
3280
|
+
parentTaskId: parentTask.id,
|
|
3281
|
+
parentTaskTitle: parentTask.title,
|
|
3282
|
+
parentDate: parentDateStr,
|
|
3283
|
+
targetDate: targetDateStr,
|
|
3284
|
+
recurrence: parentTask.recurrence,
|
|
3285
|
+
childrenCount: parentTask.children.length
|
|
3286
|
+
});
|
|
3287
|
+
return {
|
|
3288
|
+
...parentTask,
|
|
3289
|
+
id: uuid3(),
|
|
3290
|
+
date: targetDateStr,
|
|
3291
|
+
state: "todo",
|
|
3292
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
3293
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
3294
|
+
startTime: void 0,
|
|
3295
|
+
endTime: void 0,
|
|
3296
|
+
isRecurringInstance: true,
|
|
3297
|
+
recurringParentId: parentTask.id,
|
|
3298
|
+
// Clone children for recurring instances
|
|
3299
|
+
children: this.cloneChildren(parentTask.children, targetDateStr)
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
/**
|
|
3303
|
+
* Get the next occurrence date for a recurring task
|
|
3304
|
+
*/
|
|
3305
|
+
getNextOccurrence(pattern, baseDate) {
|
|
3306
|
+
let nextDate = new Date(baseDate);
|
|
3307
|
+
switch (pattern.frequency) {
|
|
3308
|
+
case "daily":
|
|
3309
|
+
nextDate = addDays2(baseDate, 1);
|
|
3310
|
+
break;
|
|
3311
|
+
case "weekdays": {
|
|
3312
|
+
nextDate = addDays2(baseDate, 1);
|
|
3313
|
+
while (isWeekend(nextDate)) {
|
|
3314
|
+
nextDate = addDays2(nextDate, 1);
|
|
3315
|
+
}
|
|
3316
|
+
break;
|
|
3317
|
+
}
|
|
3318
|
+
case "weekly":
|
|
3319
|
+
nextDate = addWeeks2(baseDate, 1);
|
|
3320
|
+
break;
|
|
3321
|
+
case "monthly":
|
|
3322
|
+
nextDate = addMonths(baseDate, 1);
|
|
3323
|
+
break;
|
|
3324
|
+
case "yearly":
|
|
3325
|
+
nextDate = addYears(baseDate, 1);
|
|
3326
|
+
break;
|
|
3327
|
+
case "custom": {
|
|
3328
|
+
if (pattern.daysOfWeek && pattern.daysOfWeek.length > 0) {
|
|
3329
|
+
nextDate = addDays2(baseDate, 1);
|
|
3330
|
+
while (!pattern.daysOfWeek.includes(nextDate.getDay())) {
|
|
3331
|
+
nextDate = addDays2(nextDate, 1);
|
|
3332
|
+
if (nextDate.getTime() - baseDate.getTime() > 7 * 24 * 60 * 60 * 1e3) {
|
|
3333
|
+
return null;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
} else if (pattern.interval) {
|
|
3337
|
+
nextDate = addDays2(baseDate, pattern.interval);
|
|
3338
|
+
} else {
|
|
3339
|
+
return null;
|
|
3340
|
+
}
|
|
3341
|
+
break;
|
|
3342
|
+
}
|
|
3343
|
+
default:
|
|
3344
|
+
return null;
|
|
3345
|
+
}
|
|
3346
|
+
if (pattern.endDate && isAfter(nextDate, pattern.endDate)) {
|
|
3347
|
+
return null;
|
|
3348
|
+
}
|
|
3349
|
+
return nextDate;
|
|
3350
|
+
}
|
|
3351
|
+
/**
|
|
3352
|
+
* Generate frequency description for display
|
|
3353
|
+
*/
|
|
3354
|
+
getFrequencyDescription(pattern) {
|
|
3355
|
+
switch (pattern.frequency) {
|
|
3356
|
+
case "daily":
|
|
3357
|
+
return "Daily";
|
|
3358
|
+
case "weekdays":
|
|
3359
|
+
return "Weekdays (Mon-Fri)";
|
|
3360
|
+
case "weekly":
|
|
3361
|
+
return "Weekly";
|
|
3362
|
+
case "monthly":
|
|
3363
|
+
return "Monthly";
|
|
3364
|
+
case "yearly":
|
|
3365
|
+
return "Yearly";
|
|
3366
|
+
case "custom": {
|
|
3367
|
+
if (pattern.daysOfWeek && pattern.daysOfWeek.length > 0) {
|
|
3368
|
+
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
3369
|
+
const days = pattern.daysOfWeek.map((d) => dayNames[d]).join(", ");
|
|
3370
|
+
return `Custom (${days})`;
|
|
3371
|
+
}
|
|
3372
|
+
if (pattern.interval) {
|
|
3373
|
+
return `Every ${pattern.interval} days`;
|
|
3374
|
+
}
|
|
3375
|
+
return "Custom";
|
|
3376
|
+
}
|
|
3377
|
+
default:
|
|
3378
|
+
return "Unknown";
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
/**
|
|
3382
|
+
* Check if a task should generate an instance for a specific date
|
|
3383
|
+
*/
|
|
3384
|
+
shouldTaskGenerateForDate(task, targetDate) {
|
|
3385
|
+
if (!task.recurrence) {
|
|
3386
|
+
return false;
|
|
3387
|
+
}
|
|
3388
|
+
const baseDate = parseDateString(task.date);
|
|
3389
|
+
const shouldGenerate = this.shouldGenerateOnDate(task.recurrence, baseDate, targetDate);
|
|
3390
|
+
logger.log("Checking if task should generate for date", {
|
|
3391
|
+
taskId: task.id,
|
|
3392
|
+
taskTitle: task.title,
|
|
3393
|
+
targetDate: getDateString(targetDate),
|
|
3394
|
+
recurrence: task.recurrence,
|
|
3395
|
+
shouldGenerate
|
|
3396
|
+
});
|
|
3397
|
+
return shouldGenerate;
|
|
3398
|
+
}
|
|
3399
|
+
};
|
|
3400
|
+
var recurringTaskService = new RecurringTaskService();
|
|
3401
|
+
|
|
2858
3402
|
// src/components/tasks/TasksPane.tsx
|
|
2859
|
-
import { Fragment, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3403
|
+
import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2860
3404
|
var TasksPane = () => {
|
|
2861
3405
|
const {
|
|
2862
3406
|
tasks,
|
|
@@ -2868,7 +3412,12 @@ var TasksPane = () => {
|
|
|
2868
3412
|
isInputMode,
|
|
2869
3413
|
setIsInputMode,
|
|
2870
3414
|
isModalOpen,
|
|
2871
|
-
pushUndoableAction
|
|
3415
|
+
pushUndoableAction,
|
|
3416
|
+
setShowRecurringTaskDialog,
|
|
3417
|
+
setRecurringTaskId,
|
|
3418
|
+
setShowRecurringEditDialog,
|
|
3419
|
+
setRecurringEditConfig,
|
|
3420
|
+
recurringEditConfig
|
|
2872
3421
|
} = useApp();
|
|
2873
3422
|
const { theme } = useTheme();
|
|
2874
3423
|
const [expandedIds, setExpandedIds] = useState7(/* @__PURE__ */ new Set());
|
|
@@ -2877,14 +3426,72 @@ var TasksPane = () => {
|
|
|
2877
3426
|
const [editValue, setEditValue] = useState7("");
|
|
2878
3427
|
const [parentTaskId, setParentTaskId] = useState7(null);
|
|
2879
3428
|
const [scrollOffset, setScrollOffset] = useState7(0);
|
|
3429
|
+
const [editingTaskId, setEditingTaskId] = useState7(null);
|
|
3430
|
+
const [pendingAction, setPendingAction] = useState7(null);
|
|
2880
3431
|
const { height: terminalHeight } = useTerminalSize();
|
|
2881
3432
|
const visibleRows = useMemo(() => {
|
|
2882
3433
|
return Math.max(5, terminalHeight - 11);
|
|
2883
3434
|
}, [terminalHeight]);
|
|
2884
|
-
const selectedDateObj =
|
|
3435
|
+
const selectedDateObj = useMemo(
|
|
3436
|
+
() => new Date(selectedDate.year, selectedDate.month, selectedDate.day),
|
|
3437
|
+
[selectedDate.year, selectedDate.month, selectedDate.day]
|
|
3438
|
+
);
|
|
2885
3439
|
const dateStr = getDateString(selectedDateObj);
|
|
2886
3440
|
const isSelectedDateToday = isToday(selectedDateObj);
|
|
2887
|
-
const dayTasks = useMemo(() =>
|
|
3441
|
+
const dayTasks = useMemo(() => {
|
|
3442
|
+
const existingTasks = tasks[dateStr] || [];
|
|
3443
|
+
const recurringInstances = [];
|
|
3444
|
+
const currentDateObj = new Date(selectedDate.year, selectedDate.month, selectedDate.day);
|
|
3445
|
+
logger.log("[dayTasks] Calculating dayTasks with recurring instances", {
|
|
3446
|
+
dateStr,
|
|
3447
|
+
existingTaskCount: existingTasks.length,
|
|
3448
|
+
existingTasks: existingTasks.map((t) => ({
|
|
3449
|
+
id: t.id,
|
|
3450
|
+
title: t.title,
|
|
3451
|
+
state: t.state,
|
|
3452
|
+
isRecurringInstance: t.isRecurringInstance,
|
|
3453
|
+
recurringParentId: t.recurringParentId,
|
|
3454
|
+
hasRecurrence: !!t.recurrence
|
|
3455
|
+
}))
|
|
3456
|
+
});
|
|
3457
|
+
for (const [taskDate, taskList] of Object.entries(tasks)) {
|
|
3458
|
+
for (const task of taskList) {
|
|
3459
|
+
if (task.recurrence && !task.isRecurringInstance) {
|
|
3460
|
+
if (recurringTaskService.shouldTaskGenerateForDate(task, currentDateObj)) {
|
|
3461
|
+
const instanceExists = existingTasks.some(
|
|
3462
|
+
(t) => t.recurringParentId === task.id
|
|
3463
|
+
);
|
|
3464
|
+
if (!instanceExists) {
|
|
3465
|
+
const instance = recurringTaskService.generateRecurringInstance(
|
|
3466
|
+
task,
|
|
3467
|
+
currentDateObj
|
|
3468
|
+
);
|
|
3469
|
+
logger.log("[dayTasks] Generated recurring instance", {
|
|
3470
|
+
instanceId: instance.id,
|
|
3471
|
+
instanceTitle: instance.title,
|
|
3472
|
+
parentId: task.id,
|
|
3473
|
+
parentDate: taskDate,
|
|
3474
|
+
targetDate: dateStr
|
|
3475
|
+
});
|
|
3476
|
+
recurringInstances.push(instance);
|
|
3477
|
+
} else {
|
|
3478
|
+
logger.log("[dayTasks] Instance already exists for parent", {
|
|
3479
|
+
parentId: task.id,
|
|
3480
|
+
parentTitle: task.title,
|
|
3481
|
+
targetDate: dateStr
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
logger.log("[dayTasks] Recurring instances generated", {
|
|
3489
|
+
dateStr,
|
|
3490
|
+
recurringInstanceCount: recurringInstances.length,
|
|
3491
|
+
totalTasks: existingTasks.length + recurringInstances.length
|
|
3492
|
+
});
|
|
3493
|
+
return [...existingTasks, ...recurringInstances];
|
|
3494
|
+
}, [tasks, dateStr, selectedDate.year, selectedDate.month, selectedDate.day]);
|
|
2888
3495
|
const stats = taskService.getTaskStats(tasks, dateStr);
|
|
2889
3496
|
const isFocused = activePane === "tasks" && !isModalOpen;
|
|
2890
3497
|
const flatTasks = useMemo(() => {
|
|
@@ -2921,9 +3528,20 @@ var TasksPane = () => {
|
|
|
2921
3528
|
return prev;
|
|
2922
3529
|
});
|
|
2923
3530
|
}, [dayTasks]);
|
|
3531
|
+
useEffect8(() => {
|
|
3532
|
+
logger.log("[TasksPane] Edit mode or input mode changed", {
|
|
3533
|
+
editMode,
|
|
3534
|
+
isInputMode,
|
|
3535
|
+
parentTaskId,
|
|
3536
|
+
editValue,
|
|
3537
|
+
isModalOpen,
|
|
3538
|
+
isFocused
|
|
3539
|
+
});
|
|
3540
|
+
}, [editMode, isInputMode, parentTaskId, editValue, isModalOpen, isFocused]);
|
|
2924
3541
|
useEffect8(() => {
|
|
2925
3542
|
setSelectedIndex(0);
|
|
2926
3543
|
setEditMode("none");
|
|
3544
|
+
setEditingTaskId(null);
|
|
2927
3545
|
}, [dateStr]);
|
|
2928
3546
|
useEffect8(() => {
|
|
2929
3547
|
if (selectedIndex >= flatTasks.length && flatTasks.length > 0) {
|
|
@@ -2943,6 +3561,240 @@ var TasksPane = () => {
|
|
|
2943
3561
|
useEffect8(() => {
|
|
2944
3562
|
setScrollOffset(0);
|
|
2945
3563
|
}, [dateStr]);
|
|
3564
|
+
const isRecurringTask = (task) => {
|
|
3565
|
+
return !!(task.recurrence || task.isRecurringInstance);
|
|
3566
|
+
};
|
|
3567
|
+
const findRootParent = (task) => {
|
|
3568
|
+
if (!task.parentId) {
|
|
3569
|
+
return task;
|
|
3570
|
+
}
|
|
3571
|
+
for (const taskList of Object.values(tasks)) {
|
|
3572
|
+
for (const potentialParent of taskList) {
|
|
3573
|
+
if (potentialParent.id === task.parentId) {
|
|
3574
|
+
return findRootParent(potentialParent);
|
|
3575
|
+
}
|
|
3576
|
+
const foundInChildren = findInChildren(potentialParent.children, task.parentId);
|
|
3577
|
+
if (foundInChildren) {
|
|
3578
|
+
return findRootParent(foundInChildren);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
return null;
|
|
3583
|
+
};
|
|
3584
|
+
const findInChildren = (children, id) => {
|
|
3585
|
+
for (const child of children) {
|
|
3586
|
+
if (child.id === id) {
|
|
3587
|
+
return child;
|
|
3588
|
+
}
|
|
3589
|
+
const found = findInChildren(child.children, id);
|
|
3590
|
+
if (found) {
|
|
3591
|
+
return found;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return null;
|
|
3595
|
+
};
|
|
3596
|
+
const hasRecurringAncestor = (task) => {
|
|
3597
|
+
const root = findRootParent(task);
|
|
3598
|
+
return root ? isRecurringTask(root) : false;
|
|
3599
|
+
};
|
|
3600
|
+
const executePendingAction = (action, thisOnly) => {
|
|
3601
|
+
if (!action) return;
|
|
3602
|
+
const task = flatTasks.find((ft) => ft.task.id === action.taskId)?.task;
|
|
3603
|
+
if (!task) return;
|
|
3604
|
+
logger.log("[TasksPane] executePendingAction called", {
|
|
3605
|
+
action: action.action,
|
|
3606
|
+
thisOnly,
|
|
3607
|
+
taskId: action.taskId,
|
|
3608
|
+
currentEditMode: editMode,
|
|
3609
|
+
currentIsInputMode: isInputMode
|
|
3610
|
+
});
|
|
3611
|
+
switch (action.action) {
|
|
3612
|
+
case "delete":
|
|
3613
|
+
performDelete(action.taskId, task, thisOnly);
|
|
3614
|
+
break;
|
|
3615
|
+
case "edit":
|
|
3616
|
+
performEdit(action.taskId, task, thisOnly);
|
|
3617
|
+
break;
|
|
3618
|
+
}
|
|
3619
|
+
logger.log("[TasksPane] executePendingAction completed", {
|
|
3620
|
+
newEditMode: editMode,
|
|
3621
|
+
newIsInputMode: isInputMode
|
|
3622
|
+
});
|
|
3623
|
+
};
|
|
3624
|
+
const performDelete = (taskId, task, thisOnly) => {
|
|
3625
|
+
try {
|
|
3626
|
+
pushUndoableAction("TASK_DELETE");
|
|
3627
|
+
if (thisOnly) {
|
|
3628
|
+
let updated = tasks;
|
|
3629
|
+
const isMaterialized = Object.values(tasks).some(
|
|
3630
|
+
(taskList) => findTaskById(taskList, taskId)
|
|
3631
|
+
);
|
|
3632
|
+
if (isMaterialized) {
|
|
3633
|
+
updated = taskService.deleteTask(tasks, taskId);
|
|
3634
|
+
}
|
|
3635
|
+
if (task.isRecurringInstance && task.recurringParentId) {
|
|
3636
|
+
updated = taskService.excludeRecurringInstance(updated, task.recurringParentId, dateStr);
|
|
3637
|
+
} else if (task.recurrence) {
|
|
3638
|
+
updated = taskService.excludeRecurringInstance(updated, taskId, dateStr);
|
|
3639
|
+
}
|
|
3640
|
+
setTasks(updated);
|
|
3641
|
+
const updatedTimeline = timelineService.removeEventsByTaskId(
|
|
3642
|
+
timeline,
|
|
3643
|
+
taskId
|
|
3644
|
+
);
|
|
3645
|
+
setTimeline(updatedTimeline);
|
|
3646
|
+
} else {
|
|
3647
|
+
const parentIdToDelete = task.recurringParentId || taskId;
|
|
3648
|
+
const updated = taskService.deleteTask(tasks, parentIdToDelete);
|
|
3649
|
+
setTasks(updated);
|
|
3650
|
+
const updatedTimeline = timelineService.removeEventsByTaskId(
|
|
3651
|
+
timeline,
|
|
3652
|
+
taskId
|
|
3653
|
+
);
|
|
3654
|
+
if (task.recurringParentId) {
|
|
3655
|
+
const parentTimeline = timelineService.removeEventsByTaskId(updatedTimeline, task.recurringParentId);
|
|
3656
|
+
setTimeline(parentTimeline);
|
|
3657
|
+
} else {
|
|
3658
|
+
setTimeline(updatedTimeline);
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
} catch (err) {
|
|
3662
|
+
console.error("Error deleting task:", err);
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
const performEdit = (taskId, task, thisOnly) => {
|
|
3666
|
+
if (thisOnly || task.isRecurringInstance) {
|
|
3667
|
+
setEditMode("edit");
|
|
3668
|
+
setEditValue(task.title);
|
|
3669
|
+
setIsInputMode(true);
|
|
3670
|
+
} else {
|
|
3671
|
+
setEditMode("edit");
|
|
3672
|
+
setEditValue(task.title);
|
|
3673
|
+
setIsInputMode(true);
|
|
3674
|
+
}
|
|
3675
|
+
};
|
|
3676
|
+
const performStateChange = (taskId, task, newState, thisOnly) => {
|
|
3677
|
+
logger.log("[performStateChange] Called", {
|
|
3678
|
+
taskId,
|
|
3679
|
+
taskTitle: task.title,
|
|
3680
|
+
taskDate: task.date,
|
|
3681
|
+
previousState: task.state,
|
|
3682
|
+
newState,
|
|
3683
|
+
thisOnly,
|
|
3684
|
+
isRecurringInstance: task.isRecurringInstance,
|
|
3685
|
+
recurringParentId: task.recurringParentId,
|
|
3686
|
+
hasRecurrence: !!task.recurrence,
|
|
3687
|
+
parentId: task.parentId
|
|
3688
|
+
});
|
|
3689
|
+
try {
|
|
3690
|
+
pushUndoableAction("TASK_UPDATE");
|
|
3691
|
+
const previousState = task.state;
|
|
3692
|
+
if (task.isRecurringInstance && task.recurringParentId) {
|
|
3693
|
+
const existingTasks = tasks[task.date] || [];
|
|
3694
|
+
const taskExists = existingTasks.some((t) => t.id === taskId);
|
|
3695
|
+
if (!taskExists) {
|
|
3696
|
+
logger.log("[performStateChange] Materializing recurring instance", {
|
|
3697
|
+
taskId,
|
|
3698
|
+
taskDate: task.date
|
|
3699
|
+
});
|
|
3700
|
+
const materializedTask = {
|
|
3701
|
+
...task,
|
|
3702
|
+
state: newState,
|
|
3703
|
+
endTime: ["completed", "delegated", "delayed"].includes(newState) ? /* @__PURE__ */ new Date() : void 0,
|
|
3704
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
3705
|
+
};
|
|
3706
|
+
const updated = {
|
|
3707
|
+
...tasks,
|
|
3708
|
+
[task.date]: [...existingTasks, materializedTask]
|
|
3709
|
+
};
|
|
3710
|
+
logger.log("[performStateChange] Materialized task added to tasks", {
|
|
3711
|
+
taskId,
|
|
3712
|
+
date: task.date,
|
|
3713
|
+
tasksForDate: updated[task.date].length
|
|
3714
|
+
});
|
|
3715
|
+
setTasks(updated);
|
|
3716
|
+
} else {
|
|
3717
|
+
logger.log("[performStateChange] Task already materialized, updating normally", {
|
|
3718
|
+
taskId,
|
|
3719
|
+
newState
|
|
3720
|
+
});
|
|
3721
|
+
const updated = taskService.changeTaskState(
|
|
3722
|
+
tasks,
|
|
3723
|
+
taskId,
|
|
3724
|
+
newState
|
|
3725
|
+
);
|
|
3726
|
+
logger.log("[performStateChange] Task state updated, setting tasks", {
|
|
3727
|
+
taskId,
|
|
3728
|
+
updatedTasksKeys: Object.keys(updated),
|
|
3729
|
+
tasksForDate: updated[task.date]?.length || 0
|
|
3730
|
+
});
|
|
3731
|
+
setTasks(updated);
|
|
3732
|
+
}
|
|
3733
|
+
} else {
|
|
3734
|
+
logger.log("[performStateChange] Calling taskService.changeTaskState", {
|
|
3735
|
+
taskId,
|
|
3736
|
+
newState
|
|
3737
|
+
});
|
|
3738
|
+
const updated = taskService.changeTaskState(
|
|
3739
|
+
tasks,
|
|
3740
|
+
taskId,
|
|
3741
|
+
newState
|
|
3742
|
+
);
|
|
3743
|
+
logger.log("[performStateChange] Task state updated, setting tasks", {
|
|
3744
|
+
taskId,
|
|
3745
|
+
updatedTasksKeys: Object.keys(updated),
|
|
3746
|
+
tasksForDate: updated[task.date]?.length || 0
|
|
3747
|
+
});
|
|
3748
|
+
setTasks(updated);
|
|
3749
|
+
}
|
|
3750
|
+
if (newState === "todo") {
|
|
3751
|
+
const eventTypeToRemove = {
|
|
3752
|
+
todo: "started" /* STARTED */,
|
|
3753
|
+
// shouldn't happen
|
|
3754
|
+
completed: "completed" /* COMPLETED */,
|
|
3755
|
+
delegated: "delegated" /* DELEGATED */,
|
|
3756
|
+
delayed: "delayed" /* DELAYED */
|
|
3757
|
+
};
|
|
3758
|
+
const updatedTimeline = timelineService.removeLastEventByType(
|
|
3759
|
+
timeline,
|
|
3760
|
+
taskId,
|
|
3761
|
+
eventTypeToRemove[previousState]
|
|
3762
|
+
);
|
|
3763
|
+
setTimeline(updatedTimeline);
|
|
3764
|
+
logger.log("[performStateChange] Toggled back to todo, removed timeline event");
|
|
3765
|
+
} else if (isSelectedDateToday) {
|
|
3766
|
+
const eventTypeMap = {
|
|
3767
|
+
todo: "started" /* STARTED */,
|
|
3768
|
+
// shouldn't happen
|
|
3769
|
+
completed: "completed" /* COMPLETED */,
|
|
3770
|
+
delegated: "delegated" /* DELEGATED */,
|
|
3771
|
+
delayed: "delayed" /* DELAYED */
|
|
3772
|
+
};
|
|
3773
|
+
const event = timelineService.createEvent(
|
|
3774
|
+
taskId,
|
|
3775
|
+
task.title,
|
|
3776
|
+
eventTypeMap[newState],
|
|
3777
|
+
/* @__PURE__ */ new Date(),
|
|
3778
|
+
previousState,
|
|
3779
|
+
newState
|
|
3780
|
+
);
|
|
3781
|
+
const updatedTimeline = timelineService.addEvent(timeline, event);
|
|
3782
|
+
setTimeline(updatedTimeline);
|
|
3783
|
+
logger.log("[performStateChange] Created timeline event for today", {
|
|
3784
|
+
eventType: eventTypeMap[newState]
|
|
3785
|
+
});
|
|
3786
|
+
} else {
|
|
3787
|
+
logger.log("[performStateChange] Not today, skipping timeline event", {
|
|
3788
|
+
isSelectedDateToday,
|
|
3789
|
+
selectedDate: dateStr
|
|
3790
|
+
});
|
|
3791
|
+
}
|
|
3792
|
+
logger.log("[performStateChange] Completed successfully");
|
|
3793
|
+
} catch (err) {
|
|
3794
|
+
logger.log("[performStateChange] Error", { error: err });
|
|
3795
|
+
console.error("Error changing task state:", err);
|
|
3796
|
+
}
|
|
3797
|
+
};
|
|
2946
3798
|
const handleAddTask = () => {
|
|
2947
3799
|
setEditMode("add");
|
|
2948
3800
|
setEditValue("");
|
|
@@ -2952,6 +3804,7 @@ var TasksPane = () => {
|
|
|
2952
3804
|
if (selectedTask) {
|
|
2953
3805
|
setEditMode("edit");
|
|
2954
3806
|
setEditValue(selectedTask.title);
|
|
3807
|
+
setEditingTaskId(selectedTask.id);
|
|
2955
3808
|
setIsInputMode(true);
|
|
2956
3809
|
}
|
|
2957
3810
|
};
|
|
@@ -2965,73 +3818,266 @@ var TasksPane = () => {
|
|
|
2965
3818
|
}
|
|
2966
3819
|
};
|
|
2967
3820
|
const handleDeleteTask = () => {
|
|
2968
|
-
if (selectedTaskId) {
|
|
2969
|
-
try {
|
|
2970
|
-
pushUndoableAction("TASK_DELETE");
|
|
2971
|
-
const updated = taskService.deleteTask(tasks, selectedTaskId);
|
|
2972
|
-
setTasks(updated);
|
|
2973
|
-
const updatedTimeline = timelineService.removeEventsByTaskId(
|
|
2974
|
-
timeline,
|
|
2975
|
-
selectedTaskId
|
|
2976
|
-
);
|
|
2977
|
-
setTimeline(updatedTimeline);
|
|
2978
|
-
} catch (err) {
|
|
2979
|
-
console.error("Error deleting task:", err);
|
|
2980
|
-
}
|
|
2981
|
-
}
|
|
2982
|
-
};
|
|
2983
|
-
const handleChangeState = (newState) => {
|
|
2984
3821
|
if (selectedTaskId && selectedTask) {
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3822
|
+
logger.log("handleDeleteTask called", {
|
|
3823
|
+
taskId: selectedTaskId,
|
|
3824
|
+
isRecurring: isRecurringTask(selectedTask),
|
|
3825
|
+
hasRecurrence: !!selectedTask.recurrence,
|
|
3826
|
+
isInstance: !!selectedTask.isRecurringInstance,
|
|
3827
|
+
hasRecurringAncestor: hasRecurringAncestor(selectedTask)
|
|
3828
|
+
});
|
|
3829
|
+
if (hasRecurringAncestor(selectedTask)) {
|
|
3830
|
+
const action = { action: "delete", taskId: selectedTaskId };
|
|
3831
|
+
const savedIndex = selectedIndex;
|
|
3832
|
+
const rootParent = findRootParent(selectedTask);
|
|
3833
|
+
setPendingAction(action);
|
|
3834
|
+
setRecurringEditConfig({
|
|
3835
|
+
taskId: selectedTaskId,
|
|
3836
|
+
taskTitle: selectedTask.title,
|
|
3837
|
+
actionType: "delete",
|
|
3838
|
+
onConfirm: (choice) => {
|
|
3839
|
+
logger.log("Recurring delete confirmed", { choice, action });
|
|
3840
|
+
pushUndoableAction("TASK_DELETE");
|
|
3841
|
+
if (choice === "this") {
|
|
3842
|
+
let updated = tasks;
|
|
3843
|
+
const isMaterialized = Object.values(tasks).some(
|
|
3844
|
+
(taskList) => findTaskById(taskList, selectedTaskId)
|
|
3845
|
+
);
|
|
3846
|
+
if (isMaterialized) {
|
|
3847
|
+
updated = taskService.deleteTask(tasks, selectedTaskId);
|
|
3848
|
+
}
|
|
3849
|
+
if (selectedTask.isRecurringInstance && selectedTask.recurringParentId) {
|
|
3850
|
+
updated = taskService.excludeRecurringInstance(updated, selectedTask.recurringParentId, dateStr);
|
|
3851
|
+
} else if (selectedTask.recurrence) {
|
|
3852
|
+
updated = taskService.excludeRecurringInstance(updated, selectedTaskId, dateStr);
|
|
3853
|
+
}
|
|
3854
|
+
setTasks(updated);
|
|
3855
|
+
const updatedTimeline = timelineService.removeEventsByTaskId(
|
|
3856
|
+
timeline,
|
|
3857
|
+
selectedTaskId
|
|
3858
|
+
);
|
|
3859
|
+
setTimeline(updatedTimeline);
|
|
3860
|
+
} else if (choice === "all" && rootParent) {
|
|
3861
|
+
if (selectedTask.parentId) {
|
|
3862
|
+
const deleteSubtaskFromTree = (task, targetId) => {
|
|
3863
|
+
return {
|
|
3864
|
+
...task,
|
|
3865
|
+
children: task.children.filter((child) => child.id !== targetId).map((child) => deleteSubtaskFromTree(child, targetId))
|
|
3866
|
+
};
|
|
3867
|
+
};
|
|
3868
|
+
let updated = { ...tasks };
|
|
3869
|
+
for (const [date, taskList] of Object.entries(tasks)) {
|
|
3870
|
+
const rootIndex = taskList.findIndex((t) => t.id === rootParent.id);
|
|
3871
|
+
if (rootIndex !== -1) {
|
|
3872
|
+
const updatedRoot = deleteSubtaskFromTree(taskList[rootIndex], selectedTaskId);
|
|
3873
|
+
updated[date] = [
|
|
3874
|
+
...taskList.slice(0, rootIndex),
|
|
3875
|
+
updatedRoot,
|
|
3876
|
+
...taskList.slice(rootIndex + 1)
|
|
3877
|
+
];
|
|
3878
|
+
break;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
setTasks(updated);
|
|
3882
|
+
} else {
|
|
3883
|
+
const parentIdToDelete = selectedTask.recurringParentId || selectedTaskId;
|
|
3884
|
+
const updated = taskService.deleteTask(tasks, parentIdToDelete);
|
|
3885
|
+
setTasks(updated);
|
|
3886
|
+
}
|
|
3887
|
+
const updatedTimeline = timelineService.removeEventsByTaskId(
|
|
3888
|
+
timeline,
|
|
3889
|
+
selectedTaskId
|
|
3890
|
+
);
|
|
3891
|
+
if (selectedTask.recurringParentId) {
|
|
3892
|
+
const parentTimeline = timelineService.removeEventsByTaskId(updatedTimeline, selectedTask.recurringParentId);
|
|
3893
|
+
setTimeline(parentTimeline);
|
|
3894
|
+
} else {
|
|
3895
|
+
setTimeline(updatedTimeline);
|
|
3896
|
+
}
|
|
3897
|
+
} else if (choice === "from-today" && rootParent) {
|
|
3898
|
+
const todayDateObj = /* @__PURE__ */ new Date();
|
|
3899
|
+
todayDateObj.setHours(0, 0, 0, 0);
|
|
3900
|
+
if (selectedTask.parentId) {
|
|
3901
|
+
const deleteSubtaskFromTree = (task, targetId) => {
|
|
3902
|
+
return {
|
|
3903
|
+
...task,
|
|
3904
|
+
children: task.children.filter((child) => child.id !== targetId).map((child) => deleteSubtaskFromTree(child, targetId))
|
|
3905
|
+
};
|
|
3906
|
+
};
|
|
3907
|
+
let updated = { ...tasks };
|
|
3908
|
+
for (const [date, taskList] of Object.entries(tasks)) {
|
|
3909
|
+
const rootIndex = taskList.findIndex((t) => t.id === rootParent.id);
|
|
3910
|
+
if (rootIndex !== -1) {
|
|
3911
|
+
const findSubtaskByTitlePath = (task, targetTask, path2 = []) => {
|
|
3912
|
+
if (task.title === targetTask.title && path2.length > 0) {
|
|
3913
|
+
return path2;
|
|
3914
|
+
}
|
|
3915
|
+
for (const child of task.children) {
|
|
3916
|
+
const result = findSubtaskByTitlePath(child, targetTask, [...path2, child.title]);
|
|
3917
|
+
if (result) return result;
|
|
3918
|
+
}
|
|
3919
|
+
return null;
|
|
3920
|
+
};
|
|
3921
|
+
const deleteByTitlePath = (task, titlePath2, currentDepth = 0) => {
|
|
3922
|
+
if (currentDepth >= titlePath2.length) return task;
|
|
3923
|
+
return {
|
|
3924
|
+
...task,
|
|
3925
|
+
children: task.children.filter((child) => child.title !== titlePath2[currentDepth]).map((child) => deleteByTitlePath(child, titlePath2, currentDepth + 1))
|
|
3926
|
+
};
|
|
3927
|
+
};
|
|
3928
|
+
const titlePath = findSubtaskByTitlePath(taskList[rootIndex], selectedTask);
|
|
3929
|
+
if (titlePath) {
|
|
3930
|
+
const updatedRoot = deleteByTitlePath(taskList[rootIndex], titlePath);
|
|
3931
|
+
updated[date] = [
|
|
3932
|
+
...taskList.slice(0, rootIndex),
|
|
3933
|
+
updatedRoot,
|
|
3934
|
+
...taskList.slice(rootIndex + 1)
|
|
3935
|
+
];
|
|
3936
|
+
logger.log("Deleting subtask from root parent by title path (from-today)", {
|
|
3937
|
+
rootParentId: rootParent.id,
|
|
3938
|
+
titlePath
|
|
3939
|
+
});
|
|
3940
|
+
} else {
|
|
3941
|
+
const updatedRoot = deleteSubtaskFromTree(taskList[rootIndex], selectedTaskId);
|
|
3942
|
+
updated[date] = [
|
|
3943
|
+
...taskList.slice(0, rootIndex),
|
|
3944
|
+
updatedRoot,
|
|
3945
|
+
...taskList.slice(rootIndex + 1)
|
|
3946
|
+
];
|
|
3947
|
+
logger.log("Deleting subtask from root parent by ID (from-today)", {
|
|
3948
|
+
rootParentId: rootParent.id,
|
|
3949
|
+
subtaskId: selectedTaskId
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
break;
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
for (const [date, taskList] of Object.entries(updated)) {
|
|
3956
|
+
const dateObj = new Date(date);
|
|
3957
|
+
dateObj.setHours(0, 0, 0, 0);
|
|
3958
|
+
if (dateObj >= todayDateObj) {
|
|
3959
|
+
let hasChanges = false;
|
|
3960
|
+
const updatedTaskList = taskList.map((task) => {
|
|
3961
|
+
if (task.isRecurringInstance && task.recurringParentId === rootParent.id) {
|
|
3962
|
+
logger.log("Deleting subtask from materialized instance (from-today)", {
|
|
3963
|
+
instanceId: task.id,
|
|
3964
|
+
instanceDate: date,
|
|
3965
|
+
subtaskTitle: selectedTask.title
|
|
3966
|
+
});
|
|
3967
|
+
hasChanges = true;
|
|
3968
|
+
const deleteByTitle = (t, targetTitle) => {
|
|
3969
|
+
return {
|
|
3970
|
+
...t,
|
|
3971
|
+
children: t.children.filter((child) => child.title !== targetTitle).map((child) => deleteByTitle(child, targetTitle))
|
|
3972
|
+
};
|
|
3973
|
+
};
|
|
3974
|
+
return deleteByTitle(task, selectedTask.title);
|
|
3975
|
+
}
|
|
3976
|
+
return task;
|
|
3977
|
+
});
|
|
3978
|
+
if (hasChanges) {
|
|
3979
|
+
updated[date] = updatedTaskList;
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
setTasks(updated);
|
|
3984
|
+
} else {
|
|
3985
|
+
let updated = { ...tasks };
|
|
3986
|
+
const isMaterialized = Object.values(tasks).some(
|
|
3987
|
+
(taskList) => taskList.some((t) => t.id === selectedTaskId)
|
|
3988
|
+
);
|
|
3989
|
+
if (isMaterialized) {
|
|
3990
|
+
updated = taskService.deleteTask(tasks, selectedTaskId);
|
|
3991
|
+
logger.log("Deleting materialized root task (from-today)", {
|
|
3992
|
+
taskId: selectedTaskId
|
|
3993
|
+
});
|
|
3994
|
+
} else {
|
|
3995
|
+
const parentId = selectedTask.recurringParentId;
|
|
3996
|
+
if (parentId) {
|
|
3997
|
+
updated = taskService.deleteTask(tasks, parentId);
|
|
3998
|
+
logger.log("Deleting parent recurring task (from-today)", {
|
|
3999
|
+
parentId,
|
|
4000
|
+
ephemeralTaskId: selectedTaskId
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
const recurringParentToDelete = selectedTask.recurringParentId || selectedTaskId;
|
|
4005
|
+
for (const [date, taskList] of Object.entries(updated)) {
|
|
4006
|
+
const dateObj = new Date(date);
|
|
4007
|
+
dateObj.setHours(0, 0, 0, 0);
|
|
4008
|
+
if (dateObj >= todayDateObj) {
|
|
4009
|
+
const filteredTaskList = taskList.filter((task) => {
|
|
4010
|
+
if (task.isRecurringInstance && task.recurringParentId === recurringParentToDelete) {
|
|
4011
|
+
logger.log("Deleting materialized instance (from-today)", {
|
|
4012
|
+
instanceId: task.id,
|
|
4013
|
+
instanceDate: date
|
|
4014
|
+
});
|
|
4015
|
+
return false;
|
|
4016
|
+
}
|
|
4017
|
+
return true;
|
|
4018
|
+
});
|
|
4019
|
+
if (filteredTaskList.length !== taskList.length) {
|
|
4020
|
+
updated[date] = filteredTaskList;
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
setTasks(updated);
|
|
4025
|
+
}
|
|
4026
|
+
const updatedTimeline = timelineService.removeEventsByTaskId(
|
|
4027
|
+
timeline,
|
|
4028
|
+
selectedTaskId
|
|
4029
|
+
);
|
|
4030
|
+
setTimeline(updatedTimeline);
|
|
4031
|
+
}
|
|
4032
|
+
setPendingAction(null);
|
|
4033
|
+
}
|
|
4034
|
+
});
|
|
4035
|
+
setShowRecurringEditDialog(true);
|
|
4036
|
+
} else {
|
|
4037
|
+
logger.log("Not recurring, deleting directly");
|
|
4038
|
+
try {
|
|
4039
|
+
pushUndoableAction("TASK_DELETE");
|
|
4040
|
+
const updated = taskService.deleteTask(tasks, selectedTaskId);
|
|
4041
|
+
setTasks(updated);
|
|
4042
|
+
const updatedTimeline = timelineService.removeEventsByTaskId(
|
|
3003
4043
|
timeline,
|
|
3004
|
-
selectedTaskId
|
|
3005
|
-
eventTypeToRemove[previousState]
|
|
4044
|
+
selectedTaskId
|
|
3006
4045
|
);
|
|
3007
4046
|
setTimeline(updatedTimeline);
|
|
3008
|
-
}
|
|
3009
|
-
|
|
3010
|
-
todo: "started" /* STARTED */,
|
|
3011
|
-
// shouldn't happen
|
|
3012
|
-
completed: "completed" /* COMPLETED */,
|
|
3013
|
-
delegated: "delegated" /* DELEGATED */,
|
|
3014
|
-
delayed: "delayed" /* DELAYED */
|
|
3015
|
-
};
|
|
3016
|
-
const event = timelineService.createEvent(
|
|
3017
|
-
selectedTaskId,
|
|
3018
|
-
selectedTask.title,
|
|
3019
|
-
eventTypeMap[newState],
|
|
3020
|
-
/* @__PURE__ */ new Date(),
|
|
3021
|
-
previousState,
|
|
3022
|
-
newState
|
|
3023
|
-
);
|
|
3024
|
-
const updatedTimeline = timelineService.addEvent(timeline, event);
|
|
3025
|
-
setTimeline(updatedTimeline);
|
|
4047
|
+
} catch (err) {
|
|
4048
|
+
console.error("Error deleting task:", err);
|
|
3026
4049
|
}
|
|
3027
|
-
} catch (err) {
|
|
3028
|
-
console.error("Error changing task state:", err);
|
|
3029
4050
|
}
|
|
3030
4051
|
}
|
|
3031
4052
|
};
|
|
4053
|
+
const handleChangeState = (newState) => {
|
|
4054
|
+
if (selectedTaskId && selectedTask) {
|
|
4055
|
+
logger.log("[handleChangeState] Called", {
|
|
4056
|
+
selectedTaskId,
|
|
4057
|
+
taskTitle: selectedTask.title,
|
|
4058
|
+
taskDate: selectedTask.date,
|
|
4059
|
+
currentState: selectedTask.state,
|
|
4060
|
+
newState,
|
|
4061
|
+
isRecurringInstance: selectedTask.isRecurringInstance,
|
|
4062
|
+
recurringParentId: selectedTask.recurringParentId,
|
|
4063
|
+
hasRecurrence: !!selectedTask.recurrence,
|
|
4064
|
+
parentId: selectedTask.parentId
|
|
4065
|
+
});
|
|
4066
|
+
performStateChange(selectedTaskId, selectedTask, newState, true);
|
|
4067
|
+
}
|
|
4068
|
+
};
|
|
3032
4069
|
const handleToggleComplete = () => {
|
|
3033
4070
|
if (selectedTask) {
|
|
3034
4071
|
const newState = selectedTask.state === "completed" ? "todo" : "completed";
|
|
4072
|
+
logger.log("[handleToggleComplete] Called", {
|
|
4073
|
+
selectedTaskId,
|
|
4074
|
+
taskTitle: selectedTask.title,
|
|
4075
|
+
taskDate: selectedTask.date,
|
|
4076
|
+
currentState: selectedTask.state,
|
|
4077
|
+
newState,
|
|
4078
|
+
isRecurringInstance: selectedTask.isRecurringInstance,
|
|
4079
|
+
recurringParentId: selectedTask.recurringParentId
|
|
4080
|
+
});
|
|
3035
4081
|
handleChangeState(newState);
|
|
3036
4082
|
}
|
|
3037
4083
|
};
|
|
@@ -3041,6 +4087,7 @@ var TasksPane = () => {
|
|
|
3041
4087
|
setEditMode("none");
|
|
3042
4088
|
setEditValue("");
|
|
3043
4089
|
setParentTaskId(null);
|
|
4090
|
+
setEditingTaskId(null);
|
|
3044
4091
|
setIsInputMode(false);
|
|
3045
4092
|
return;
|
|
3046
4093
|
}
|
|
@@ -3054,35 +4101,335 @@ var TasksPane = () => {
|
|
|
3054
4101
|
};
|
|
3055
4102
|
setTasks(newTasks);
|
|
3056
4103
|
setSelectedIndex(flatTasks.length);
|
|
4104
|
+
setEditMode("none");
|
|
4105
|
+
setEditValue("");
|
|
4106
|
+
setParentTaskId(null);
|
|
4107
|
+
setEditingTaskId(null);
|
|
4108
|
+
setIsInputMode(false);
|
|
3057
4109
|
} else if (editMode === "addSubtask" && parentTaskId) {
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
4110
|
+
const parentTask = flatTasks.find((ft) => ft.task.id === parentTaskId)?.task;
|
|
4111
|
+
if (parentTask && isRecurringTask(parentTask)) {
|
|
4112
|
+
const savedValue = trimmed;
|
|
4113
|
+
const savedParentId = parentTaskId;
|
|
4114
|
+
const savedIndex = selectedIndex;
|
|
4115
|
+
setRecurringEditConfig({
|
|
4116
|
+
taskId: savedParentId,
|
|
4117
|
+
taskTitle: parentTask.title,
|
|
4118
|
+
actionType: "add-subtask",
|
|
4119
|
+
onConfirm: (choice) => {
|
|
4120
|
+
logger.log("Recurring subtask save confirmed", { choice, newSubtask: savedValue });
|
|
4121
|
+
pushUndoableAction("TASK_ADD");
|
|
4122
|
+
if (choice === "this") {
|
|
4123
|
+
const existingTasks = tasks[dateStr] || [];
|
|
4124
|
+
const taskExists = existingTasks.some((t) => t.id === savedParentId);
|
|
4125
|
+
if (taskExists) {
|
|
4126
|
+
logger.log("Adding subtask to already materialized task (this only)", {
|
|
4127
|
+
parentId: savedParentId,
|
|
4128
|
+
subtaskTitle: savedValue
|
|
4129
|
+
});
|
|
4130
|
+
const updated = taskService.addSubtask(tasks, savedParentId, savedValue);
|
|
4131
|
+
setTasks(updated);
|
|
4132
|
+
} else {
|
|
4133
|
+
const ephemeralParent = dayTasks.find((t) => t.id === savedParentId);
|
|
4134
|
+
if (ephemeralParent && ephemeralParent.isRecurringInstance) {
|
|
4135
|
+
logger.log("Materializing ephemeral recurring instance for subtask addition (this only)", {
|
|
4136
|
+
parentId: savedParentId,
|
|
4137
|
+
recurringParentId: ephemeralParent.recurringParentId,
|
|
4138
|
+
subtaskTitle: savedValue
|
|
4139
|
+
});
|
|
4140
|
+
const newSubtask = taskService.createTask(savedValue, dateStr);
|
|
4141
|
+
const materializedTask = {
|
|
4142
|
+
...ephemeralParent,
|
|
4143
|
+
children: [...ephemeralParent.children, { ...newSubtask, parentId: savedParentId }]
|
|
4144
|
+
};
|
|
4145
|
+
const updated = {
|
|
4146
|
+
...tasks,
|
|
4147
|
+
[dateStr]: [...existingTasks, materializedTask]
|
|
4148
|
+
};
|
|
4149
|
+
setTasks(updated);
|
|
4150
|
+
} else if (ephemeralParent) {
|
|
4151
|
+
logger.log("Parent is original recurring task, adding subtask (this only - but will affect template)", {
|
|
4152
|
+
parentId: savedParentId,
|
|
4153
|
+
subtaskTitle: savedValue
|
|
4154
|
+
});
|
|
4155
|
+
const updated = taskService.addSubtask(tasks, savedParentId, savedValue);
|
|
4156
|
+
setTasks(updated);
|
|
4157
|
+
} else {
|
|
4158
|
+
logger.log("Parent task not found in ephemeral dayTasks", {
|
|
4159
|
+
parentId: savedParentId
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
} else if (choice === "all") {
|
|
4164
|
+
const recurringParentId = parentTask.recurringParentId || savedParentId;
|
|
4165
|
+
let updated = taskService.addSubtask(tasks, recurringParentId, savedValue);
|
|
4166
|
+
logger.log("Adding subtask to parent and materialized instances", {
|
|
4167
|
+
recurringParentId,
|
|
4168
|
+
subtaskTitle: savedValue
|
|
4169
|
+
});
|
|
4170
|
+
for (const [date, taskList] of Object.entries(updated)) {
|
|
4171
|
+
for (let i = 0; i < taskList.length; i++) {
|
|
4172
|
+
const task = taskList[i];
|
|
4173
|
+
if (task.isRecurringInstance && task.recurringParentId === recurringParentId) {
|
|
4174
|
+
logger.log("Adding subtask to materialized instance", {
|
|
4175
|
+
instanceId: task.id,
|
|
4176
|
+
instanceDate: date,
|
|
4177
|
+
subtaskTitle: savedValue
|
|
4178
|
+
});
|
|
4179
|
+
updated = taskService.addSubtask(updated, task.id, savedValue);
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
setTasks(updated);
|
|
4184
|
+
} else if (choice === "from-today") {
|
|
4185
|
+
const todayDateObj = /* @__PURE__ */ new Date();
|
|
4186
|
+
todayDateObj.setHours(0, 0, 0, 0);
|
|
4187
|
+
const recurringParentId = parentTask.recurringParentId || savedParentId;
|
|
4188
|
+
let updated = taskService.addSubtask(tasks, recurringParentId, savedValue);
|
|
4189
|
+
logger.log("Adding subtask to parent and future materialized instances (from-today)", {
|
|
4190
|
+
recurringParentId,
|
|
4191
|
+
subtaskTitle: savedValue
|
|
4192
|
+
});
|
|
4193
|
+
for (const [date, taskList] of Object.entries(updated)) {
|
|
4194
|
+
const dateObj = new Date(date);
|
|
4195
|
+
dateObj.setHours(0, 0, 0, 0);
|
|
4196
|
+
if (dateObj >= todayDateObj) {
|
|
4197
|
+
for (let i = 0; i < taskList.length; i++) {
|
|
4198
|
+
const task = taskList[i];
|
|
4199
|
+
if (task.isRecurringInstance && task.recurringParentId === recurringParentId) {
|
|
4200
|
+
logger.log("Adding subtask to materialized instance (from-today)", {
|
|
4201
|
+
instanceId: task.id,
|
|
4202
|
+
instanceDate: date,
|
|
4203
|
+
subtaskTitle: savedValue
|
|
4204
|
+
});
|
|
4205
|
+
updated = taskService.addSubtask(updated, task.id, savedValue);
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
setTasks(updated);
|
|
4211
|
+
}
|
|
4212
|
+
setTimeout(() => {
|
|
4213
|
+
const parentIndex = flatTasks.findIndex(
|
|
4214
|
+
(ft) => ft.task.id === savedParentId
|
|
4215
|
+
);
|
|
4216
|
+
if (parentIndex !== -1) {
|
|
4217
|
+
setSelectedIndex(parentIndex + 1);
|
|
4218
|
+
}
|
|
4219
|
+
setEditMode("none");
|
|
4220
|
+
setEditValue("");
|
|
4221
|
+
setParentTaskId(null);
|
|
4222
|
+
setEditingTaskId(null);
|
|
4223
|
+
setIsInputMode(false);
|
|
4224
|
+
}, 0);
|
|
4225
|
+
}
|
|
4226
|
+
});
|
|
4227
|
+
setShowRecurringEditDialog(true);
|
|
4228
|
+
} else {
|
|
4229
|
+
pushUndoableAction("TASK_ADD");
|
|
4230
|
+
const updated = taskService.addSubtask(tasks, parentTaskId, trimmed);
|
|
4231
|
+
setTasks(updated);
|
|
4232
|
+
const parentIndex = flatTasks.findIndex(
|
|
4233
|
+
(ft) => ft.task.id === parentTaskId
|
|
4234
|
+
);
|
|
4235
|
+
if (parentIndex !== -1) {
|
|
4236
|
+
setSelectedIndex(parentIndex + 1);
|
|
4237
|
+
}
|
|
4238
|
+
setEditMode("none");
|
|
4239
|
+
setEditValue("");
|
|
4240
|
+
setParentTaskId(null);
|
|
4241
|
+
setEditingTaskId(null);
|
|
4242
|
+
setIsInputMode(false);
|
|
4243
|
+
}
|
|
4244
|
+
} else if (editMode === "edit" && editingTaskId) {
|
|
4245
|
+
const taskBeingEdited = flatTasks.find((ft) => ft.task.id === editingTaskId)?.task;
|
|
4246
|
+
if (taskBeingEdited && hasRecurringAncestor(taskBeingEdited)) {
|
|
4247
|
+
const savedValue = trimmed;
|
|
4248
|
+
const savedTaskId = editingTaskId;
|
|
4249
|
+
const savedIndex = selectedIndex;
|
|
4250
|
+
const rootParent = findRootParent(taskBeingEdited);
|
|
4251
|
+
setRecurringEditConfig({
|
|
4252
|
+
taskId: savedTaskId,
|
|
4253
|
+
taskTitle: taskBeingEdited.title,
|
|
4254
|
+
actionType: "edit",
|
|
4255
|
+
onConfirm: (choice) => {
|
|
4256
|
+
logger.log("Recurring edit save confirmed", { choice, newTitle: savedValue });
|
|
4257
|
+
pushUndoableAction("TASK_UPDATE");
|
|
4258
|
+
if (choice === "this") {
|
|
4259
|
+
const updated = taskService.updateTask(tasks, savedTaskId, {
|
|
4260
|
+
title: savedValue
|
|
4261
|
+
});
|
|
4262
|
+
setTasks(updated);
|
|
4263
|
+
} else if (choice === "all" && rootParent) {
|
|
4264
|
+
let updated = { ...tasks };
|
|
4265
|
+
if (taskBeingEdited.parentId) {
|
|
4266
|
+
const updateSubtaskInTree = (task, targetId, newTitle) => {
|
|
4267
|
+
if (task.id === targetId) {
|
|
4268
|
+
return { ...task, title: newTitle, updatedAt: /* @__PURE__ */ new Date() };
|
|
4269
|
+
}
|
|
4270
|
+
return {
|
|
4271
|
+
...task,
|
|
4272
|
+
children: task.children.map((child) => updateSubtaskInTree(child, targetId, newTitle))
|
|
4273
|
+
};
|
|
4274
|
+
};
|
|
4275
|
+
logger.log("Updating subtask in root parent and materialized instances", {
|
|
4276
|
+
subtaskId: savedTaskId,
|
|
4277
|
+
newTitle: savedValue,
|
|
4278
|
+
rootParentId: rootParent.id
|
|
4279
|
+
});
|
|
4280
|
+
for (const [date, taskList] of Object.entries(tasks)) {
|
|
4281
|
+
const rootIndex = taskList.findIndex((t) => t.id === rootParent.id);
|
|
4282
|
+
if (rootIndex !== -1) {
|
|
4283
|
+
const updatedRoot = updateSubtaskInTree(taskList[rootIndex], savedTaskId, savedValue);
|
|
4284
|
+
updated[date] = [
|
|
4285
|
+
...taskList.slice(0, rootIndex),
|
|
4286
|
+
updatedRoot,
|
|
4287
|
+
...taskList.slice(rootIndex + 1)
|
|
4288
|
+
];
|
|
4289
|
+
break;
|
|
4290
|
+
}
|
|
4291
|
+
}
|
|
4292
|
+
for (const [date, taskList] of Object.entries(updated)) {
|
|
4293
|
+
let hasChanges = false;
|
|
4294
|
+
const updatedTaskList = taskList.map((task) => {
|
|
4295
|
+
if (task.isRecurringInstance && task.recurringParentId === rootParent.id) {
|
|
4296
|
+
logger.log("Updating subtask in materialized instance", {
|
|
4297
|
+
instanceId: task.id,
|
|
4298
|
+
instanceDate: date,
|
|
4299
|
+
subtaskId: savedTaskId,
|
|
4300
|
+
newTitle: savedValue
|
|
4301
|
+
});
|
|
4302
|
+
hasChanges = true;
|
|
4303
|
+
return updateSubtaskInTree(task, savedTaskId, savedValue);
|
|
4304
|
+
}
|
|
4305
|
+
return task;
|
|
4306
|
+
});
|
|
4307
|
+
if (hasChanges) {
|
|
4308
|
+
updated[date] = updatedTaskList;
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
setTasks(updated);
|
|
4312
|
+
} else {
|
|
4313
|
+
const updated2 = taskService.updateTask(tasks, savedTaskId, {
|
|
4314
|
+
title: savedValue
|
|
4315
|
+
});
|
|
4316
|
+
setTasks(updated2);
|
|
4317
|
+
}
|
|
4318
|
+
} else if (choice === "from-today" && rootParent) {
|
|
4319
|
+
const todayDateObj = /* @__PURE__ */ new Date();
|
|
4320
|
+
todayDateObj.setHours(0, 0, 0, 0);
|
|
4321
|
+
let updated = { ...tasks };
|
|
4322
|
+
if (taskBeingEdited.parentId) {
|
|
4323
|
+
const updateSubtaskInTree = (task, targetId, newTitle) => {
|
|
4324
|
+
if (task.id === targetId) {
|
|
4325
|
+
return { ...task, title: newTitle, updatedAt: /* @__PURE__ */ new Date() };
|
|
4326
|
+
}
|
|
4327
|
+
return {
|
|
4328
|
+
...task,
|
|
4329
|
+
children: task.children.map((child) => updateSubtaskInTree(child, targetId, newTitle))
|
|
4330
|
+
};
|
|
4331
|
+
};
|
|
4332
|
+
logger.log("Updating subtask in root parent and future materialized instances (from-today)", {
|
|
4333
|
+
subtaskId: savedTaskId,
|
|
4334
|
+
newTitle: savedValue,
|
|
4335
|
+
rootParentId: rootParent.id
|
|
4336
|
+
});
|
|
4337
|
+
for (const [date, taskList] of Object.entries(tasks)) {
|
|
4338
|
+
const rootIndex = taskList.findIndex((t) => t.id === rootParent.id);
|
|
4339
|
+
if (rootIndex !== -1) {
|
|
4340
|
+
const updatedRoot = updateSubtaskInTree(taskList[rootIndex], savedTaskId, savedValue);
|
|
4341
|
+
updated[date] = [
|
|
4342
|
+
...taskList.slice(0, rootIndex),
|
|
4343
|
+
updatedRoot,
|
|
4344
|
+
...taskList.slice(rootIndex + 1)
|
|
4345
|
+
];
|
|
4346
|
+
break;
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
for (const [date, taskList] of Object.entries(updated)) {
|
|
4350
|
+
const dateObj = new Date(date);
|
|
4351
|
+
dateObj.setHours(0, 0, 0, 0);
|
|
4352
|
+
if (dateObj >= todayDateObj) {
|
|
4353
|
+
let hasChanges = false;
|
|
4354
|
+
const updatedTaskList = taskList.map((task) => {
|
|
4355
|
+
if (task.isRecurringInstance && task.recurringParentId === rootParent.id) {
|
|
4356
|
+
logger.log("Updating subtask in materialized instance (from-today)", {
|
|
4357
|
+
instanceId: task.id,
|
|
4358
|
+
instanceDate: date,
|
|
4359
|
+
subtaskId: savedTaskId,
|
|
4360
|
+
newTitle: savedValue
|
|
4361
|
+
});
|
|
4362
|
+
hasChanges = true;
|
|
4363
|
+
return updateSubtaskInTree(task, savedTaskId, savedValue);
|
|
4364
|
+
}
|
|
4365
|
+
return task;
|
|
4366
|
+
});
|
|
4367
|
+
if (hasChanges) {
|
|
4368
|
+
updated[date] = updatedTaskList;
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
setTasks(updated);
|
|
4373
|
+
} else {
|
|
4374
|
+
let updated2 = taskService.updateTask(tasks, savedTaskId, {
|
|
4375
|
+
title: savedValue
|
|
4376
|
+
});
|
|
4377
|
+
for (const [date, taskList] of Object.entries(updated2)) {
|
|
4378
|
+
const dateObj = new Date(date);
|
|
4379
|
+
dateObj.setHours(0, 0, 0, 0);
|
|
4380
|
+
if (dateObj >= todayDateObj) {
|
|
4381
|
+
let hasChanges = false;
|
|
4382
|
+
const updatedTaskList = taskList.map((task) => {
|
|
4383
|
+
if (task.isRecurringInstance && task.recurringParentId === savedTaskId) {
|
|
4384
|
+
logger.log("Updating materialized instance (from-today)", {
|
|
4385
|
+
instanceId: task.id,
|
|
4386
|
+
instanceDate: date,
|
|
4387
|
+
newTitle: savedValue
|
|
4388
|
+
});
|
|
4389
|
+
hasChanges = true;
|
|
4390
|
+
return { ...task, title: savedValue, updatedAt: /* @__PURE__ */ new Date() };
|
|
4391
|
+
}
|
|
4392
|
+
return task;
|
|
4393
|
+
});
|
|
4394
|
+
if (hasChanges) {
|
|
4395
|
+
updated2[date] = updatedTaskList;
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
setTasks(updated2);
|
|
4400
|
+
}
|
|
4401
|
+
}
|
|
4402
|
+
setTimeout(() => {
|
|
4403
|
+
setEditMode("none");
|
|
4404
|
+
setEditValue("");
|
|
4405
|
+
setEditingTaskId(null);
|
|
4406
|
+
setIsInputMode(false);
|
|
4407
|
+
setSelectedIndex(savedIndex);
|
|
4408
|
+
}, 0);
|
|
4409
|
+
}
|
|
4410
|
+
});
|
|
4411
|
+
setShowRecurringEditDialog(true);
|
|
4412
|
+
} else {
|
|
4413
|
+
pushUndoableAction("TASK_UPDATE");
|
|
4414
|
+
const updated = taskService.updateTask(tasks, editingTaskId, {
|
|
4415
|
+
title: trimmed
|
|
4416
|
+
});
|
|
4417
|
+
setTasks(updated);
|
|
4418
|
+
setEditMode("none");
|
|
4419
|
+
setEditValue("");
|
|
4420
|
+
setEditingTaskId(null);
|
|
4421
|
+
setIsInputMode(false);
|
|
3066
4422
|
}
|
|
3067
|
-
} else if (editMode === "edit" && selectedTaskId) {
|
|
3068
|
-
pushUndoableAction("TASK_UPDATE");
|
|
3069
|
-
const updated = taskService.updateTask(tasks, selectedTaskId, {
|
|
3070
|
-
title: trimmed
|
|
3071
|
-
});
|
|
3072
|
-
setTasks(updated);
|
|
3073
4423
|
}
|
|
3074
4424
|
} catch (err) {
|
|
3075
4425
|
console.error("Error saving task:", err);
|
|
3076
4426
|
}
|
|
3077
|
-
setEditMode("none");
|
|
3078
|
-
setEditValue("");
|
|
3079
|
-
setParentTaskId(null);
|
|
3080
|
-
setIsInputMode(false);
|
|
3081
4427
|
};
|
|
3082
4428
|
const handleCancelEdit = () => {
|
|
3083
4429
|
setEditMode("none");
|
|
3084
4430
|
setEditValue("");
|
|
3085
4431
|
setParentTaskId(null);
|
|
4432
|
+
setEditingTaskId(null);
|
|
3086
4433
|
setIsInputMode(false);
|
|
3087
4434
|
};
|
|
3088
4435
|
const handleToggleExpand = () => {
|
|
@@ -3215,6 +4562,15 @@ var TasksPane = () => {
|
|
|
3215
4562
|
}
|
|
3216
4563
|
return;
|
|
3217
4564
|
}
|
|
4565
|
+
if (input === "r" && selectedTask) {
|
|
4566
|
+
if (selectedTask.parentId) {
|
|
4567
|
+
logger.log("Cannot mark nested task as recurring", { taskId: selectedTask.id, parentId: selectedTask.parentId });
|
|
4568
|
+
return;
|
|
4569
|
+
}
|
|
4570
|
+
setRecurringTaskId(selectedTaskId);
|
|
4571
|
+
setShowRecurringTaskDialog(true);
|
|
4572
|
+
return;
|
|
4573
|
+
}
|
|
3218
4574
|
if (key.return && selectedTask) {
|
|
3219
4575
|
handleToggleExpand();
|
|
3220
4576
|
return;
|
|
@@ -3309,7 +4665,7 @@ var TasksPane = () => {
|
|
|
3309
4665
|
}
|
|
3310
4666
|
),
|
|
3311
4667
|
/* @__PURE__ */ jsx14(Text7, { children: " " }),
|
|
3312
|
-
task.children.length > 0 && /* @__PURE__ */ jsxs9(
|
|
4668
|
+
task.children.length > 0 && /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3313
4669
|
/* @__PURE__ */ jsx14(Text7, { color: theme.colors.foreground, children: isExpanded ? "\u25BC" : "\u25B6" }),
|
|
3314
4670
|
/* @__PURE__ */ jsx14(Text7, { children: " " })
|
|
3315
4671
|
] }),
|
|
@@ -3322,6 +4678,16 @@ var TasksPane = () => {
|
|
|
3322
4678
|
children: task.title
|
|
3323
4679
|
}
|
|
3324
4680
|
),
|
|
4681
|
+
task.recurrence && /* @__PURE__ */ jsxs9(
|
|
4682
|
+
Text7,
|
|
4683
|
+
{
|
|
4684
|
+
color: isSelected ? theme.colors.focusIndicator : theme.colors.timelineEventStarted,
|
|
4685
|
+
children: [
|
|
4686
|
+
" ",
|
|
4687
|
+
"\u21BA"
|
|
4688
|
+
]
|
|
4689
|
+
}
|
|
4690
|
+
),
|
|
3325
4691
|
task.startTime && !task.endTime && /* @__PURE__ */ jsxs9(
|
|
3326
4692
|
Text7,
|
|
3327
4693
|
{
|
|
@@ -3349,6 +4715,7 @@ var TasksPane = () => {
|
|
|
3349
4715
|
{ key: "D", description: "delegate" },
|
|
3350
4716
|
{ key: "x", description: "delay" },
|
|
3351
4717
|
{ key: "s", description: "start" },
|
|
4718
|
+
{ key: "r", description: "recurring" },
|
|
3352
4719
|
{ key: "\u2190/\u2192", description: "collapse/expand" },
|
|
3353
4720
|
{ key: "Cmd+\u2190/\u2192", description: "all" }
|
|
3354
4721
|
]
|
|
@@ -3576,7 +4943,7 @@ var TimelinePane = () => {
|
|
|
3576
4943
|
};
|
|
3577
4944
|
|
|
3578
4945
|
// src/components/common/HelpDialog.tsx
|
|
3579
|
-
import { Box as Box13, Text as Text10 } from "ink";
|
|
4946
|
+
import { Box as Box13, Text as Text10, useInput as useInput6 } from "ink";
|
|
3580
4947
|
|
|
3581
4948
|
// src/components/common/Modal.tsx
|
|
3582
4949
|
import { Box as Box12, useStdout as useStdout3 } from "ink";
|
|
@@ -3630,12 +4997,23 @@ var Modal = ({ children }) => {
|
|
|
3630
4997
|
};
|
|
3631
4998
|
|
|
3632
4999
|
// src/components/common/HelpDialog.tsx
|
|
3633
|
-
import { Fragment as
|
|
5000
|
+
import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3634
5001
|
var HelpDialog = () => {
|
|
3635
5002
|
const { theme } = useTheme();
|
|
5003
|
+
const { setShowHelp, setShowSettingsDialog } = useApp();
|
|
5004
|
+
useInput6((input, key) => {
|
|
5005
|
+
if (key.escape || input === "?") {
|
|
5006
|
+
setShowHelp(false);
|
|
5007
|
+
}
|
|
5008
|
+
if (key.ctrl && input === "s") {
|
|
5009
|
+
setShowHelp(false);
|
|
5010
|
+
setShowSettingsDialog(true);
|
|
5011
|
+
}
|
|
5012
|
+
}, { isActive: true });
|
|
3636
5013
|
const shortcuts = [
|
|
3637
5014
|
{ key: "Ctrl+C (twice)", action: "Quit application" },
|
|
3638
5015
|
{ key: "Ctrl+U", action: "Undo last action" },
|
|
5016
|
+
{ key: "Ctrl+S", action: "Open settings" },
|
|
3639
5017
|
{ key: "?", action: "Toggle help dialog" },
|
|
3640
5018
|
{ key: "Shift+;", action: "Show month overview" },
|
|
3641
5019
|
{ key: "t", action: "Select theme" },
|
|
@@ -3658,11 +5036,22 @@ var HelpDialog = () => {
|
|
|
3658
5036
|
{ key: "s", action: "Start task" },
|
|
3659
5037
|
{ key: "D", action: "Mark delegated" },
|
|
3660
5038
|
{ key: "x", action: "Mark delayed/cancelled" },
|
|
5039
|
+
{ key: "r", action: "Make task recurring" },
|
|
3661
5040
|
{ key: "Enter", action: "Expand/collapse subtasks" },
|
|
3662
5041
|
{ key: "", action: "" },
|
|
3663
5042
|
{ key: "Timeline Pane", action: "" },
|
|
3664
5043
|
{ key: "j/k", action: "Scroll timeline" },
|
|
3665
|
-
{ key: "Shift+C", action: "Clear timeline" }
|
|
5044
|
+
{ key: "Shift+C", action: "Clear timeline" },
|
|
5045
|
+
{ key: "", action: "" },
|
|
5046
|
+
{ key: "Input Editing", action: "" },
|
|
5047
|
+
{ key: "\u2190/\u2192", action: "Move cursor left/right" },
|
|
5048
|
+
{ key: "Ctrl+\u2190/\u2192", action: "Move by word" },
|
|
5049
|
+
{ key: "Ctrl+A", action: "Move to start of line" },
|
|
5050
|
+
{ key: "Ctrl+E", action: "Move to end of line" },
|
|
5051
|
+
{ key: "Delete/Backspace", action: "Delete character" },
|
|
5052
|
+
{ key: "Ctrl+W", action: "Delete word before cursor" },
|
|
5053
|
+
{ key: "Ctrl+U", action: "Delete to start of line" },
|
|
5054
|
+
{ key: "Ctrl+K", action: "Delete to end of line" }
|
|
3666
5055
|
];
|
|
3667
5056
|
return /* @__PURE__ */ jsx19(Modal, { children: /* @__PURE__ */ jsxs12(
|
|
3668
5057
|
Box13,
|
|
@@ -3675,11 +5064,11 @@ var HelpDialog = () => {
|
|
|
3675
5064
|
backgroundColor: theme.colors.modalBackground || theme.colors.background,
|
|
3676
5065
|
children: [
|
|
3677
5066
|
/* @__PURE__ */ jsx19(Text10, { bold: true, color: theme.colors.calendarHeader, children: "Keyboard Shortcuts" }),
|
|
3678
|
-
/* @__PURE__ */ jsx19(Box13, { flexDirection: "column", marginTop: 1, children: shortcuts.map((item, idx) => /* @__PURE__ */ jsx19(Box13, { marginY: 0, children: item.key ? /* @__PURE__ */ jsxs12(
|
|
5067
|
+
/* @__PURE__ */ jsx19(Box13, { flexDirection: "column", marginTop: 1, children: shortcuts.map((item, idx) => /* @__PURE__ */ jsx19(Box13, { marginY: 0, children: item.key ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
|
|
3679
5068
|
/* @__PURE__ */ jsx19(Box13, { width: 20, children: /* @__PURE__ */ jsx19(Text10, { color: theme.colors.timelineEventStarted, children: item.key }) }),
|
|
3680
5069
|
/* @__PURE__ */ jsx19(Text10, { color: theme.colors.foreground, children: item.action })
|
|
3681
5070
|
] }) : /* @__PURE__ */ jsx19(Text10, { color: theme.colors.separator, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }, idx)) }),
|
|
3682
|
-
/* @__PURE__ */ jsx19(Box13, { marginY: 1, children: /* @__PURE__ */ jsx19(Text10, { color: theme.colors.keyboardHint, dimColor: true, children: "Press '?' to close" }) })
|
|
5071
|
+
/* @__PURE__ */ jsx19(Box13, { marginY: 1, children: /* @__PURE__ */ jsx19(Text10, { color: theme.colors.keyboardHint, dimColor: true, children: "Press '?' or Esc to close" }) })
|
|
3683
5072
|
]
|
|
3684
5073
|
}
|
|
3685
5074
|
) });
|
|
@@ -3687,7 +5076,7 @@ var HelpDialog = () => {
|
|
|
3687
5076
|
|
|
3688
5077
|
// src/components/common/ThemeDialog.tsx
|
|
3689
5078
|
import { useState as useState9, useMemo as useMemo3, useEffect as useEffect10 } from "react";
|
|
3690
|
-
import { Box as Box14, Text as Text11, useInput as
|
|
5079
|
+
import { Box as Box14, Text as Text11, useInput as useInput7 } from "ink";
|
|
3691
5080
|
import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3692
5081
|
var ThemeDialog = () => {
|
|
3693
5082
|
const { theme, setTheme, themeName } = useTheme();
|
|
@@ -3731,7 +5120,7 @@ var ThemeDialog = () => {
|
|
|
3731
5120
|
useEffect10(() => {
|
|
3732
5121
|
setSelectedIndex(0);
|
|
3733
5122
|
}, [searchQuery]);
|
|
3734
|
-
|
|
5123
|
+
useInput7(
|
|
3735
5124
|
(input, key) => {
|
|
3736
5125
|
if (key.escape) {
|
|
3737
5126
|
setShowThemeDialog(false);
|
|
@@ -3842,10 +5231,299 @@ var ThemeDialog = () => {
|
|
|
3842
5231
|
) });
|
|
3843
5232
|
};
|
|
3844
5233
|
|
|
5234
|
+
// src/components/common/SettingsDialog.tsx
|
|
5235
|
+
import { useState as useState10 } from "react";
|
|
5236
|
+
import { Box as Box15, Text as Text12, useInput as useInput8 } from "ink";
|
|
5237
|
+
import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
5238
|
+
var SettingsDialog = () => {
|
|
5239
|
+
const { theme } = useTheme();
|
|
5240
|
+
const { setShowSettingsDialog } = useApp();
|
|
5241
|
+
const { data, save } = useStorage();
|
|
5242
|
+
const [selectedIndex, setSelectedIndex] = useState10(0);
|
|
5243
|
+
const settings = [
|
|
5244
|
+
{
|
|
5245
|
+
label: "Auto-move unfinished tasks to next day",
|
|
5246
|
+
key: "autoMoveUnfinishedTasks",
|
|
5247
|
+
value: data?.settings?.autoMoveUnfinishedTasks ?? true
|
|
5248
|
+
}
|
|
5249
|
+
];
|
|
5250
|
+
const toggleSetting = (key) => {
|
|
5251
|
+
if (!data) return;
|
|
5252
|
+
const newValue = !data.settings[key];
|
|
5253
|
+
logger.log("Toggling setting", { key, newValue });
|
|
5254
|
+
save({
|
|
5255
|
+
...data,
|
|
5256
|
+
settings: {
|
|
5257
|
+
...data.settings,
|
|
5258
|
+
[key]: newValue
|
|
5259
|
+
}
|
|
5260
|
+
});
|
|
5261
|
+
};
|
|
5262
|
+
useInput8(
|
|
5263
|
+
(input, key) => {
|
|
5264
|
+
if (key.escape) {
|
|
5265
|
+
logger.log("Closing settings dialog");
|
|
5266
|
+
setShowSettingsDialog(false);
|
|
5267
|
+
return;
|
|
5268
|
+
}
|
|
5269
|
+
if (key.upArrow || input === "k") {
|
|
5270
|
+
setSelectedIndex((prev) => prev > 0 ? prev - 1 : settings.length - 1);
|
|
5271
|
+
return;
|
|
5272
|
+
}
|
|
5273
|
+
if (key.downArrow || input === "j") {
|
|
5274
|
+
setSelectedIndex((prev) => prev < settings.length - 1 ? prev + 1 : 0);
|
|
5275
|
+
return;
|
|
5276
|
+
}
|
|
5277
|
+
if (key.return || input === " ") {
|
|
5278
|
+
toggleSetting(settings[selectedIndex].key);
|
|
5279
|
+
return;
|
|
5280
|
+
}
|
|
5281
|
+
},
|
|
5282
|
+
{ isActive: true }
|
|
5283
|
+
);
|
|
5284
|
+
return /* @__PURE__ */ jsx21(Modal, { children: /* @__PURE__ */ jsxs14(
|
|
5285
|
+
Box15,
|
|
5286
|
+
{
|
|
5287
|
+
flexDirection: "column",
|
|
5288
|
+
borderStyle: "double",
|
|
5289
|
+
borderColor: theme.colors.helpDialogBorder,
|
|
5290
|
+
paddingX: 2,
|
|
5291
|
+
paddingY: 1,
|
|
5292
|
+
width: 70,
|
|
5293
|
+
backgroundColor: theme.colors.modalBackground || theme.colors.background,
|
|
5294
|
+
children: [
|
|
5295
|
+
/* @__PURE__ */ jsx21(Text12, { bold: true, color: theme.colors.calendarHeader, underline: true, children: "Settings" }),
|
|
5296
|
+
/* @__PURE__ */ jsx21(Box15, { flexDirection: "column", marginTop: 1, children: settings.map((setting, idx) => {
|
|
5297
|
+
const isSelected = idx === selectedIndex;
|
|
5298
|
+
const isEnabled = setting.value;
|
|
5299
|
+
return /* @__PURE__ */ jsx21(Box15, { marginY: 0, children: /* @__PURE__ */ jsxs14(
|
|
5300
|
+
Text12,
|
|
5301
|
+
{
|
|
5302
|
+
color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground,
|
|
5303
|
+
bold: isSelected,
|
|
5304
|
+
children: [
|
|
5305
|
+
isSelected ? "\u279C " : " ",
|
|
5306
|
+
setting.label,
|
|
5307
|
+
": ",
|
|
5308
|
+
/* @__PURE__ */ jsx21(
|
|
5309
|
+
Text12,
|
|
5310
|
+
{
|
|
5311
|
+
color: isEnabled ? theme.colors.taskStateCompleted : theme.colors.taskStateDelayed,
|
|
5312
|
+
children: isEnabled ? "ON" : "OFF"
|
|
5313
|
+
}
|
|
5314
|
+
)
|
|
5315
|
+
]
|
|
5316
|
+
}
|
|
5317
|
+
) }, setting.key);
|
|
5318
|
+
}) }),
|
|
5319
|
+
/* @__PURE__ */ jsx21(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text12, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 or k/j to navigate \u2022 Space/Enter to toggle \u2022 Esc to close" }) })
|
|
5320
|
+
]
|
|
5321
|
+
}
|
|
5322
|
+
) });
|
|
5323
|
+
};
|
|
5324
|
+
|
|
5325
|
+
// src/components/common/RecurringTaskDialog.tsx
|
|
5326
|
+
import { useState as useState11 } from "react";
|
|
5327
|
+
import { Box as Box16, Text as Text13, useInput as useInput9 } from "ink";
|
|
5328
|
+
import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
5329
|
+
var RecurringTaskDialog = ({
|
|
5330
|
+
onConfirm,
|
|
5331
|
+
onCancel
|
|
5332
|
+
}) => {
|
|
5333
|
+
const { theme } = useTheme();
|
|
5334
|
+
const frequencies = [
|
|
5335
|
+
{ value: "daily", label: "Daily" },
|
|
5336
|
+
{ value: "weekdays", label: "Weekdays (Mon-Fri)" },
|
|
5337
|
+
{ value: "weekly", label: "Weekly" },
|
|
5338
|
+
{ value: "monthly", label: "Monthly" },
|
|
5339
|
+
{ value: "yearly", label: "Yearly" }
|
|
5340
|
+
];
|
|
5341
|
+
const [selectedIndex, setSelectedIndex] = useState11(0);
|
|
5342
|
+
useInput9(
|
|
5343
|
+
(input, key) => {
|
|
5344
|
+
if (key.escape) {
|
|
5345
|
+
logger.log("Cancelling recurring task dialog");
|
|
5346
|
+
onCancel();
|
|
5347
|
+
return;
|
|
5348
|
+
}
|
|
5349
|
+
if (key.upArrow || input === "k") {
|
|
5350
|
+
setSelectedIndex(
|
|
5351
|
+
(prev) => prev > 0 ? prev - 1 : frequencies.length - 1
|
|
5352
|
+
);
|
|
5353
|
+
return;
|
|
5354
|
+
}
|
|
5355
|
+
if (key.downArrow || input === "j") {
|
|
5356
|
+
setSelectedIndex(
|
|
5357
|
+
(prev) => prev < frequencies.length - 1 ? prev + 1 : 0
|
|
5358
|
+
);
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
5361
|
+
if (key.return) {
|
|
5362
|
+
const selectedFrequency = frequencies[selectedIndex].value;
|
|
5363
|
+
const pattern = {
|
|
5364
|
+
frequency: selectedFrequency
|
|
5365
|
+
};
|
|
5366
|
+
logger.log("Setting task recurrence", { frequency: selectedFrequency });
|
|
5367
|
+
onConfirm(pattern);
|
|
5368
|
+
return;
|
|
5369
|
+
}
|
|
5370
|
+
},
|
|
5371
|
+
{ isActive: true }
|
|
5372
|
+
);
|
|
5373
|
+
return /* @__PURE__ */ jsx22(Modal, { children: /* @__PURE__ */ jsxs15(
|
|
5374
|
+
Box16,
|
|
5375
|
+
{
|
|
5376
|
+
flexDirection: "column",
|
|
5377
|
+
borderStyle: "double",
|
|
5378
|
+
borderColor: theme.colors.helpDialogBorder,
|
|
5379
|
+
paddingX: 2,
|
|
5380
|
+
paddingY: 1,
|
|
5381
|
+
width: 44,
|
|
5382
|
+
backgroundColor: theme.colors.modalBackground || theme.colors.background,
|
|
5383
|
+
children: [
|
|
5384
|
+
/* @__PURE__ */ jsx22(Text13, { bold: true, color: theme.colors.calendarHeader, underline: true, children: "Make Task Recurring" }),
|
|
5385
|
+
/* @__PURE__ */ jsx22(Box16, { flexDirection: "column", marginTop: 1, children: frequencies.map((freq, idx) => {
|
|
5386
|
+
const isSelected = idx === selectedIndex;
|
|
5387
|
+
return /* @__PURE__ */ jsx22(Box16, { marginY: 0, children: /* @__PURE__ */ jsxs15(
|
|
5388
|
+
Text13,
|
|
5389
|
+
{
|
|
5390
|
+
color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground,
|
|
5391
|
+
bold: isSelected,
|
|
5392
|
+
children: [
|
|
5393
|
+
isSelected ? "\u279C " : " ",
|
|
5394
|
+
freq.label
|
|
5395
|
+
]
|
|
5396
|
+
}
|
|
5397
|
+
) }, freq.value);
|
|
5398
|
+
}) }),
|
|
5399
|
+
/* @__PURE__ */ jsx22(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx22(Text13, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 or k/j to navigate \u2022 Enter to confirm \u2022 Esc to cancel" }) })
|
|
5400
|
+
]
|
|
5401
|
+
}
|
|
5402
|
+
) });
|
|
5403
|
+
};
|
|
5404
|
+
|
|
5405
|
+
// src/components/common/RecurringEditDialog.tsx
|
|
5406
|
+
import { useState as useState12 } from "react";
|
|
5407
|
+
import { Box as Box17, Text as Text14, useInput as useInput10 } from "ink";
|
|
5408
|
+
import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
5409
|
+
var RecurringEditDialog = ({
|
|
5410
|
+
taskTitle,
|
|
5411
|
+
actionType,
|
|
5412
|
+
onConfirm
|
|
5413
|
+
}) => {
|
|
5414
|
+
const { theme } = useTheme();
|
|
5415
|
+
const [selectedIndex, setSelectedIndex] = useState12(0);
|
|
5416
|
+
const options = [
|
|
5417
|
+
{ value: "this", label: getThisOptionLabel(actionType) },
|
|
5418
|
+
{ value: "all", label: "All occurrences" },
|
|
5419
|
+
{ value: "from-today", label: "All occurrences from today" }
|
|
5420
|
+
];
|
|
5421
|
+
function getThisOptionLabel(action) {
|
|
5422
|
+
switch (action) {
|
|
5423
|
+
case "edit":
|
|
5424
|
+
return "This task only";
|
|
5425
|
+
case "delete":
|
|
5426
|
+
return "This task only";
|
|
5427
|
+
case "complete":
|
|
5428
|
+
return "This task only";
|
|
5429
|
+
case "state-change":
|
|
5430
|
+
return "This task only";
|
|
5431
|
+
case "add-subtask":
|
|
5432
|
+
return "This task only";
|
|
5433
|
+
default:
|
|
5434
|
+
return "This task only";
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
function getActionDescription(action) {
|
|
5438
|
+
switch (action) {
|
|
5439
|
+
case "edit":
|
|
5440
|
+
return "editing";
|
|
5441
|
+
case "delete":
|
|
5442
|
+
return "deleting";
|
|
5443
|
+
case "complete":
|
|
5444
|
+
return "completing";
|
|
5445
|
+
case "state-change":
|
|
5446
|
+
return "changing the state of";
|
|
5447
|
+
case "add-subtask":
|
|
5448
|
+
return "adding a subtask to";
|
|
5449
|
+
default:
|
|
5450
|
+
return "modifying";
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
useInput10(
|
|
5454
|
+
(input, key) => {
|
|
5455
|
+
if (key.escape) {
|
|
5456
|
+
logger.log("Cancelling recurring edit dialog");
|
|
5457
|
+
onConfirm("cancel");
|
|
5458
|
+
return;
|
|
5459
|
+
}
|
|
5460
|
+
if (key.upArrow || input === "k") {
|
|
5461
|
+
setSelectedIndex((prev) => prev > 0 ? prev - 1 : options.length - 1);
|
|
5462
|
+
return;
|
|
5463
|
+
}
|
|
5464
|
+
if (key.downArrow || input === "j") {
|
|
5465
|
+
setSelectedIndex((prev) => prev < options.length - 1 ? prev + 1 : 0);
|
|
5466
|
+
return;
|
|
5467
|
+
}
|
|
5468
|
+
if (key.return) {
|
|
5469
|
+
const selectedAction = options[selectedIndex].value;
|
|
5470
|
+
logger.log("Recurring edit action selected", {
|
|
5471
|
+
actionType,
|
|
5472
|
+
selectedAction,
|
|
5473
|
+
taskTitle
|
|
5474
|
+
});
|
|
5475
|
+
onConfirm(selectedAction);
|
|
5476
|
+
return;
|
|
5477
|
+
}
|
|
5478
|
+
},
|
|
5479
|
+
{ isActive: true }
|
|
5480
|
+
);
|
|
5481
|
+
return /* @__PURE__ */ jsx23(Modal, { children: /* @__PURE__ */ jsxs16(
|
|
5482
|
+
Box17,
|
|
5483
|
+
{
|
|
5484
|
+
flexDirection: "column",
|
|
5485
|
+
borderStyle: "double",
|
|
5486
|
+
borderColor: theme.colors.helpDialogBorder,
|
|
5487
|
+
paddingX: 2,
|
|
5488
|
+
paddingY: 1,
|
|
5489
|
+
width: 54,
|
|
5490
|
+
backgroundColor: theme.colors.modalBackground || theme.colors.background,
|
|
5491
|
+
children: [
|
|
5492
|
+
/* @__PURE__ */ jsx23(Text14, { bold: true, color: theme.colors.calendarHeader, children: "Modify Recurring Task" }),
|
|
5493
|
+
/* @__PURE__ */ jsx23(Box17, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text14, { color: theme.colors.foreground, children: [
|
|
5494
|
+
"You are ",
|
|
5495
|
+
getActionDescription(actionType),
|
|
5496
|
+
" a recurring task:"
|
|
5497
|
+
] }) }),
|
|
5498
|
+
/* @__PURE__ */ jsx23(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text14, { color: theme.colors.focusIndicator, bold: true, children: [
|
|
5499
|
+
'"',
|
|
5500
|
+
taskTitle,
|
|
5501
|
+
'"'
|
|
5502
|
+
] }) }),
|
|
5503
|
+
/* @__PURE__ */ jsx23(Box17, { flexDirection: "column", marginTop: 1, children: options.map((option, idx) => {
|
|
5504
|
+
const isSelected = idx === selectedIndex;
|
|
5505
|
+
return /* @__PURE__ */ jsx23(Box17, { marginY: 0, children: /* @__PURE__ */ jsxs16(
|
|
5506
|
+
Text14,
|
|
5507
|
+
{
|
|
5508
|
+
color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground,
|
|
5509
|
+
bold: isSelected,
|
|
5510
|
+
children: [
|
|
5511
|
+
isSelected ? "\u279C " : " ",
|
|
5512
|
+
option.label
|
|
5513
|
+
]
|
|
5514
|
+
}
|
|
5515
|
+
) }, option.value);
|
|
5516
|
+
}) }),
|
|
5517
|
+
/* @__PURE__ */ jsx23(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx23(Text14, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 or k/j to navigate \u2022 Enter to confirm \u2022 Esc to cancel" }) })
|
|
5518
|
+
]
|
|
5519
|
+
}
|
|
5520
|
+
) });
|
|
5521
|
+
};
|
|
5522
|
+
|
|
3845
5523
|
// src/components/common/ClearTimelineDialog.tsx
|
|
3846
|
-
import { Box as
|
|
5524
|
+
import { Box as Box18, Text as Text15, useInput as useInput11 } from "ink";
|
|
3847
5525
|
import { format as format2 } from "date-fns";
|
|
3848
|
-
import { jsx as
|
|
5526
|
+
import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3849
5527
|
var ClearTimelineDialog = () => {
|
|
3850
5528
|
const { theme } = useTheme();
|
|
3851
5529
|
const {
|
|
@@ -3865,15 +5543,15 @@ var ClearTimelineDialog = () => {
|
|
|
3865
5543
|
const handleCancel = () => {
|
|
3866
5544
|
setShowClearTimelineDialog(false);
|
|
3867
5545
|
};
|
|
3868
|
-
|
|
5546
|
+
useInput11((input, key) => {
|
|
3869
5547
|
if (input.toLowerCase() === "y" || key.return) {
|
|
3870
5548
|
handleConfirm();
|
|
3871
5549
|
} else if (input.toLowerCase() === "n" || key.escape) {
|
|
3872
5550
|
handleCancel();
|
|
3873
5551
|
}
|
|
3874
5552
|
});
|
|
3875
|
-
return /* @__PURE__ */
|
|
3876
|
-
|
|
5553
|
+
return /* @__PURE__ */ jsx24(Modal, { children: /* @__PURE__ */ jsxs17(
|
|
5554
|
+
Box18,
|
|
3877
5555
|
{
|
|
3878
5556
|
flexDirection: "column",
|
|
3879
5557
|
borderStyle: "double",
|
|
@@ -3882,34 +5560,34 @@ var ClearTimelineDialog = () => {
|
|
|
3882
5560
|
paddingY: 2,
|
|
3883
5561
|
backgroundColor: theme.colors.modalBackground || theme.colors.background,
|
|
3884
5562
|
children: [
|
|
3885
|
-
/* @__PURE__ */
|
|
3886
|
-
/* @__PURE__ */
|
|
3887
|
-
/* @__PURE__ */
|
|
3888
|
-
/* @__PURE__ */
|
|
3889
|
-
eventCount > 0 ? /* @__PURE__ */
|
|
5563
|
+
/* @__PURE__ */ jsx24(Box18, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsx24(Text15, { bold: true, color: theme.colors.taskStateDelayed, children: "Clear Timeline" }) }),
|
|
5564
|
+
/* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", marginY: 1, children: [
|
|
5565
|
+
/* @__PURE__ */ jsx24(Text15, { color: theme.colors.foreground, children: "You are about to clear the timeline for" }),
|
|
5566
|
+
/* @__PURE__ */ jsx24(Box18, { justifyContent: "center", marginY: 1, children: /* @__PURE__ */ jsx24(Text15, { bold: true, color: theme.colors.calendarHeader, children: formattedDate }) }),
|
|
5567
|
+
eventCount > 0 ? /* @__PURE__ */ jsxs17(Text15, { color: theme.colors.keyboardHint, children: [
|
|
3890
5568
|
"This will remove ",
|
|
3891
5569
|
eventCount,
|
|
3892
5570
|
" event",
|
|
3893
5571
|
eventCount !== 1 ? "s" : "",
|
|
3894
5572
|
" from the timeline."
|
|
3895
|
-
] }) : /* @__PURE__ */
|
|
5573
|
+
] }) : /* @__PURE__ */ jsx24(Text15, { color: theme.colors.keyboardHint, dimColor: true, children: "The timeline is already empty." })
|
|
3896
5574
|
] }),
|
|
3897
|
-
/* @__PURE__ */
|
|
3898
|
-
/* @__PURE__ */
|
|
3899
|
-
/* @__PURE__ */
|
|
3900
|
-
/* @__PURE__ */
|
|
3901
|
-
/* @__PURE__ */
|
|
5575
|
+
/* @__PURE__ */ jsxs17(Box18, { marginTop: 2, justifyContent: "center", children: [
|
|
5576
|
+
/* @__PURE__ */ jsx24(Text15, { color: theme.colors.foreground, children: "Are you sure? " }),
|
|
5577
|
+
/* @__PURE__ */ jsx24(Text15, { color: theme.colors.taskStateCompleted, bold: true, children: "[Y]es" }),
|
|
5578
|
+
/* @__PURE__ */ jsx24(Text15, { color: theme.colors.foreground, children: " / " }),
|
|
5579
|
+
/* @__PURE__ */ jsx24(Text15, { color: theme.colors.taskStateDelayed, bold: true, children: "[N]o" })
|
|
3902
5580
|
] }),
|
|
3903
|
-
/* @__PURE__ */
|
|
5581
|
+
/* @__PURE__ */ jsx24(Box18, { marginTop: 2, justifyContent: "center", children: /* @__PURE__ */ jsx24(Text15, { color: theme.colors.keyboardHint, dimColor: true, children: "Press Y to confirm, N or Esc to cancel" }) })
|
|
3904
5582
|
]
|
|
3905
5583
|
}
|
|
3906
5584
|
) });
|
|
3907
5585
|
};
|
|
3908
5586
|
|
|
3909
5587
|
// src/components/common/UpdateDialog.tsx
|
|
3910
|
-
import { useState as
|
|
3911
|
-
import { Box as
|
|
3912
|
-
import { jsx as
|
|
5588
|
+
import { useState as useState13 } from "react";
|
|
5589
|
+
import { Box as Box19, Text as Text16, useInput as useInput12 } from "ink";
|
|
5590
|
+
import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3913
5591
|
var UpdateDialog = ({
|
|
3914
5592
|
currentVersion,
|
|
3915
5593
|
latestVersion,
|
|
@@ -3917,12 +5595,12 @@ var UpdateDialog = ({
|
|
|
3917
5595
|
onSkipVersion
|
|
3918
5596
|
}) => {
|
|
3919
5597
|
const { theme } = useTheme();
|
|
3920
|
-
const [selectedOption, setSelectedOption] =
|
|
5598
|
+
const [selectedOption, setSelectedOption] = useState13(0);
|
|
3921
5599
|
const options = [
|
|
3922
5600
|
{ label: "Dismiss", action: onDismiss },
|
|
3923
5601
|
{ label: "Skip this version", action: () => onSkipVersion(latestVersion) }
|
|
3924
5602
|
];
|
|
3925
|
-
|
|
5603
|
+
useInput12((input, key) => {
|
|
3926
5604
|
if (key.escape) {
|
|
3927
5605
|
onDismiss();
|
|
3928
5606
|
return;
|
|
@@ -3940,8 +5618,8 @@ var UpdateDialog = ({
|
|
|
3940
5618
|
return;
|
|
3941
5619
|
}
|
|
3942
5620
|
});
|
|
3943
|
-
return /* @__PURE__ */
|
|
3944
|
-
|
|
5621
|
+
return /* @__PURE__ */ jsx25(Modal, { children: /* @__PURE__ */ jsxs18(
|
|
5622
|
+
Box19,
|
|
3945
5623
|
{
|
|
3946
5624
|
flexDirection: "column",
|
|
3947
5625
|
borderStyle: "double",
|
|
@@ -3951,27 +5629,27 @@ var UpdateDialog = ({
|
|
|
3951
5629
|
width: 50,
|
|
3952
5630
|
backgroundColor: theme.colors.modalBackground || theme.colors.background,
|
|
3953
5631
|
children: [
|
|
3954
|
-
/* @__PURE__ */
|
|
3955
|
-
/* @__PURE__ */
|
|
3956
|
-
/* @__PURE__ */
|
|
3957
|
-
/* @__PURE__ */
|
|
3958
|
-
/* @__PURE__ */
|
|
5632
|
+
/* @__PURE__ */ jsx25(Text16, { bold: true, color: theme.colors.calendarHeader, children: "Update Available" }),
|
|
5633
|
+
/* @__PURE__ */ jsxs18(Box19, { marginTop: 1, flexDirection: "column", children: [
|
|
5634
|
+
/* @__PURE__ */ jsx25(Text16, { color: theme.colors.foreground, children: "A new version of Epoch is available!" }),
|
|
5635
|
+
/* @__PURE__ */ jsxs18(Box19, { marginTop: 1, children: [
|
|
5636
|
+
/* @__PURE__ */ jsxs18(Text16, { color: theme.colors.foreground, dimColor: true, children: [
|
|
3959
5637
|
"Current:",
|
|
3960
5638
|
" "
|
|
3961
5639
|
] }),
|
|
3962
|
-
/* @__PURE__ */
|
|
3963
|
-
/* @__PURE__ */
|
|
3964
|
-
/* @__PURE__ */
|
|
5640
|
+
/* @__PURE__ */ jsx25(Text16, { color: theme.colors.taskDelayed, children: currentVersion }),
|
|
5641
|
+
/* @__PURE__ */ jsx25(Text16, { color: theme.colors.foreground, dimColor: true, children: " \u2192 " }),
|
|
5642
|
+
/* @__PURE__ */ jsx25(Text16, { color: theme.colors.taskCompleted, bold: true, children: latestVersion })
|
|
3965
5643
|
] })
|
|
3966
5644
|
] }),
|
|
3967
|
-
/* @__PURE__ */
|
|
3968
|
-
/* @__PURE__ */
|
|
3969
|
-
/* @__PURE__ */
|
|
5645
|
+
/* @__PURE__ */ jsxs18(Box19, { marginTop: 1, flexDirection: "column", children: [
|
|
5646
|
+
/* @__PURE__ */ jsx25(Text16, { color: theme.colors.foreground, dimColor: true, children: "To update, run:" }),
|
|
5647
|
+
/* @__PURE__ */ jsx25(Box19, { marginTop: 0, paddingLeft: 1, children: /* @__PURE__ */ jsx25(Text16, { color: theme.colors.timelineEventStarted, children: "npm install -g epoch-tui@latest" }) })
|
|
3970
5648
|
] }),
|
|
3971
|
-
/* @__PURE__ */
|
|
5649
|
+
/* @__PURE__ */ jsx25(Box19, { marginTop: 1, flexDirection: "column", children: options.map((option, idx) => {
|
|
3972
5650
|
const isSelected = idx === selectedOption;
|
|
3973
|
-
return /* @__PURE__ */
|
|
3974
|
-
|
|
5651
|
+
return /* @__PURE__ */ jsx25(Box19, { children: /* @__PURE__ */ jsxs18(
|
|
5652
|
+
Text16,
|
|
3975
5653
|
{
|
|
3976
5654
|
color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground,
|
|
3977
5655
|
bold: isSelected,
|
|
@@ -3982,21 +5660,21 @@ var UpdateDialog = ({
|
|
|
3982
5660
|
}
|
|
3983
5661
|
) }, option.label);
|
|
3984
5662
|
}) }),
|
|
3985
|
-
/* @__PURE__ */
|
|
5663
|
+
/* @__PURE__ */ jsx25(Box19, { marginTop: 1, children: /* @__PURE__ */ jsx25(Text16, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 to navigate \u2022 Enter to select \u2022 Esc to dismiss" }) })
|
|
3986
5664
|
]
|
|
3987
5665
|
}
|
|
3988
5666
|
) });
|
|
3989
5667
|
};
|
|
3990
5668
|
|
|
3991
5669
|
// src/components/overview/OverviewScreen.tsx
|
|
3992
|
-
import
|
|
3993
|
-
import { Box as
|
|
5670
|
+
import React14, { useMemo as useMemo4 } from "react";
|
|
5671
|
+
import { Box as Box20, Text as Text17, useInput as useInput13 } from "ink";
|
|
3994
5672
|
import { startOfMonth as startOfMonth2, endOfMonth as endOfMonth2, eachDayOfInterval as eachDayOfInterval2 } from "date-fns";
|
|
3995
|
-
import { jsx as
|
|
5673
|
+
import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
3996
5674
|
var OverviewScreen = () => {
|
|
3997
5675
|
const { theme } = useTheme();
|
|
3998
5676
|
const { tasks, overviewMonth, setOverviewMonth, setShowOverview } = useApp();
|
|
3999
|
-
const [scrollOffset, setScrollOffset] =
|
|
5677
|
+
const [scrollOffset, setScrollOffset] = React14.useState(0);
|
|
4000
5678
|
const { height: terminalHeight } = useTerminalSize();
|
|
4001
5679
|
const visibleRows = useMemo4(() => {
|
|
4002
5680
|
return Math.max(2, Math.floor((terminalHeight - 9) / 3));
|
|
@@ -4032,10 +5710,10 @@ var OverviewScreen = () => {
|
|
|
4032
5710
|
setOverviewMonth({ ...overviewMonth, month: newMonth });
|
|
4033
5711
|
}
|
|
4034
5712
|
};
|
|
4035
|
-
|
|
5713
|
+
React14.useEffect(() => {
|
|
4036
5714
|
setScrollOffset(0);
|
|
4037
5715
|
}, [overviewMonth]);
|
|
4038
|
-
|
|
5716
|
+
useInput13((input, key) => {
|
|
4039
5717
|
if (key.escape) {
|
|
4040
5718
|
setShowOverview(false);
|
|
4041
5719
|
return;
|
|
@@ -4071,21 +5749,21 @@ var OverviewScreen = () => {
|
|
|
4071
5749
|
);
|
|
4072
5750
|
const canScrollUp = scrollOffset > 0;
|
|
4073
5751
|
const canScrollDown = scrollOffset + visibleRows < rows;
|
|
4074
|
-
return /* @__PURE__ */
|
|
4075
|
-
/* @__PURE__ */
|
|
4076
|
-
/* @__PURE__ */
|
|
4077
|
-
/* @__PURE__ */
|
|
5752
|
+
return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", padding: 1, width: "100%", height: "100%", children: [
|
|
5753
|
+
/* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", marginBottom: 1, children: [
|
|
5754
|
+
/* @__PURE__ */ jsx26(Text17, { bold: true, color: theme.colors.focusIndicator, children: "Overview" }),
|
|
5755
|
+
/* @__PURE__ */ jsx26(Text17, { color: theme.colors.foreground, children: monthName })
|
|
4078
5756
|
] }),
|
|
4079
|
-
/* @__PURE__ */
|
|
4080
|
-
canScrollUp && /* @__PURE__ */
|
|
5757
|
+
/* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", flexGrow: 1, children: [
|
|
5758
|
+
canScrollUp && /* @__PURE__ */ jsx26(Box20, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsx26(Text17, { color: theme.colors.keyboardHint, dimColor: true, children: "-- more above --" }) }),
|
|
4081
5759
|
visibleRowData.map((_, index) => {
|
|
4082
5760
|
const rowIndex = scrollOffset + index;
|
|
4083
|
-
return /* @__PURE__ */
|
|
5761
|
+
return /* @__PURE__ */ jsx26(Box20, { flexDirection: "row", marginBottom: 1, children: Array.from({ length: columns }).map((_2, colIndex) => {
|
|
4084
5762
|
const dateIndex = rowIndex * columns + colIndex;
|
|
4085
5763
|
const date = monthDates[dateIndex];
|
|
4086
5764
|
if (!date) {
|
|
4087
|
-
return /* @__PURE__ */
|
|
4088
|
-
|
|
5765
|
+
return /* @__PURE__ */ jsx26(
|
|
5766
|
+
Box20,
|
|
4089
5767
|
{
|
|
4090
5768
|
flexDirection: "column",
|
|
4091
5769
|
flexGrow: 1,
|
|
@@ -4105,17 +5783,17 @@ var OverviewScreen = () => {
|
|
|
4105
5783
|
}
|
|
4106
5784
|
};
|
|
4107
5785
|
traverse(dayTasks, 0);
|
|
4108
|
-
return /* @__PURE__ */
|
|
4109
|
-
|
|
5786
|
+
return /* @__PURE__ */ jsxs19(
|
|
5787
|
+
Box20,
|
|
4110
5788
|
{
|
|
4111
5789
|
flexDirection: "column",
|
|
4112
5790
|
flexGrow: 1,
|
|
4113
5791
|
flexBasis: 0,
|
|
4114
5792
|
marginRight: colIndex === columns - 1 ? 0 : 2,
|
|
4115
5793
|
children: [
|
|
4116
|
-
/* @__PURE__ */
|
|
4117
|
-
/* @__PURE__ */
|
|
4118
|
-
flatTasksWithDepth.length === 0 ? /* @__PURE__ */
|
|
5794
|
+
/* @__PURE__ */ jsx26(Text17, { bold: true, color: theme.colors.calendarSelected, children: formatDate(date, "do MMM") }),
|
|
5795
|
+
/* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
|
|
5796
|
+
flatTasksWithDepth.length === 0 ? /* @__PURE__ */ jsx26(Text17, { dimColor: true, color: theme.colors.keyboardHint, children: "No tasks" }) : flatTasksWithDepth.slice(0, 10).map(({ task, depth }) => /* @__PURE__ */ jsx26(
|
|
4119
5797
|
TaskItem,
|
|
4120
5798
|
{
|
|
4121
5799
|
task,
|
|
@@ -4124,7 +5802,7 @@ var OverviewScreen = () => {
|
|
|
4124
5802
|
},
|
|
4125
5803
|
task.id
|
|
4126
5804
|
)),
|
|
4127
|
-
flatTasksWithDepth.length > 10 && /* @__PURE__ */
|
|
5805
|
+
flatTasksWithDepth.length > 10 && /* @__PURE__ */ jsxs19(Text17, { dimColor: true, color: theme.colors.keyboardHint, children: [
|
|
4128
5806
|
"+",
|
|
4129
5807
|
flatTasksWithDepth.length - 10,
|
|
4130
5808
|
" more..."
|
|
@@ -4136,22 +5814,22 @@ var OverviewScreen = () => {
|
|
|
4136
5814
|
);
|
|
4137
5815
|
}) }, rowIndex);
|
|
4138
5816
|
}),
|
|
4139
|
-
canScrollDown && /* @__PURE__ */
|
|
5817
|
+
canScrollDown && /* @__PURE__ */ jsx26(Box20, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */ jsx26(Text17, { color: theme.colors.keyboardHint, dimColor: true, children: "-- more below --" }) })
|
|
4140
5818
|
] }),
|
|
4141
|
-
/* @__PURE__ */
|
|
5819
|
+
/* @__PURE__ */ jsx26(Box20, { marginTop: 1, children: /* @__PURE__ */ jsx26(Text17, { color: theme.colors.keyboardHint, dimColor: true, children: "n/p or \u2190/\u2192: month | j/k or \u2193/\u2191: scroll | Esc: close | Shift+;: toggle" }) })
|
|
4142
5820
|
] });
|
|
4143
5821
|
};
|
|
4144
5822
|
var TaskItem = ({ task, theme, depth }) => {
|
|
4145
5823
|
const checkbox = getCheckbox2(task.state);
|
|
4146
5824
|
const color = getStateColor2(task.state, theme);
|
|
4147
|
-
return /* @__PURE__ */
|
|
4148
|
-
/* @__PURE__ */
|
|
5825
|
+
return /* @__PURE__ */ jsxs19(Box20, { children: [
|
|
5826
|
+
/* @__PURE__ */ jsxs19(Text17, { color, children: [
|
|
4149
5827
|
checkbox,
|
|
4150
5828
|
" "
|
|
4151
5829
|
] }),
|
|
4152
|
-
depth > 0 && /* @__PURE__ */
|
|
4153
|
-
/* @__PURE__ */
|
|
4154
|
-
|
|
5830
|
+
depth > 0 && /* @__PURE__ */ jsx26(Text17, { children: " ".repeat(depth) }),
|
|
5831
|
+
/* @__PURE__ */ jsx26(
|
|
5832
|
+
Text17,
|
|
4155
5833
|
{
|
|
4156
5834
|
color,
|
|
4157
5835
|
strikethrough: task.state === "completed",
|
|
@@ -4184,7 +5862,7 @@ function getStateColor2(state, theme) {
|
|
|
4184
5862
|
}
|
|
4185
5863
|
|
|
4186
5864
|
// src/App.tsx
|
|
4187
|
-
import { jsx as
|
|
5865
|
+
import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
4188
5866
|
var AppContent = () => {
|
|
4189
5867
|
const {
|
|
4190
5868
|
showHelp,
|
|
@@ -4193,16 +5871,28 @@ var AppContent = () => {
|
|
|
4193
5871
|
activePane,
|
|
4194
5872
|
showThemeDialog,
|
|
4195
5873
|
showClearTimelineDialog,
|
|
5874
|
+
showSettingsDialog,
|
|
5875
|
+
showRecurringTaskDialog,
|
|
5876
|
+
showRecurringEditDialog,
|
|
4196
5877
|
showUpdateDialog,
|
|
4197
5878
|
setShowUpdateDialog,
|
|
4198
5879
|
updateInfo,
|
|
4199
|
-
skipVersion
|
|
5880
|
+
skipVersion,
|
|
5881
|
+
recurringTaskId,
|
|
5882
|
+
setRecurringTaskId,
|
|
5883
|
+
setShowRecurringTaskDialog,
|
|
5884
|
+
recurringEditConfig,
|
|
5885
|
+
setRecurringEditConfig,
|
|
5886
|
+
setShowRecurringEditDialog,
|
|
5887
|
+
tasks,
|
|
5888
|
+
setTasks,
|
|
5889
|
+
pushUndoableAction
|
|
4200
5890
|
} = useApp();
|
|
4201
5891
|
const { theme } = useTheme();
|
|
4202
5892
|
useKeyboardNav();
|
|
4203
5893
|
const { width, height } = useTerminalSize();
|
|
4204
5894
|
if (showUpdateDialog && updateInfo) {
|
|
4205
|
-
return /* @__PURE__ */
|
|
5895
|
+
return /* @__PURE__ */ jsx27(
|
|
4206
5896
|
UpdateDialog,
|
|
4207
5897
|
{
|
|
4208
5898
|
currentVersion: updateInfo.currentVersion,
|
|
@@ -4213,26 +5903,78 @@ var AppContent = () => {
|
|
|
4213
5903
|
);
|
|
4214
5904
|
}
|
|
4215
5905
|
if (showThemeDialog) {
|
|
4216
|
-
return /* @__PURE__ */
|
|
5906
|
+
return /* @__PURE__ */ jsx27(ThemeDialog, {});
|
|
4217
5907
|
}
|
|
4218
5908
|
if (showHelp) {
|
|
4219
|
-
return /* @__PURE__ */
|
|
5909
|
+
return /* @__PURE__ */ jsx27(HelpDialog, {});
|
|
4220
5910
|
}
|
|
4221
5911
|
if (showClearTimelineDialog) {
|
|
4222
|
-
return /* @__PURE__ */
|
|
5912
|
+
return /* @__PURE__ */ jsx27(ClearTimelineDialog, {});
|
|
5913
|
+
}
|
|
5914
|
+
if (showSettingsDialog) {
|
|
5915
|
+
return /* @__PURE__ */ jsx27(SettingsDialog, {});
|
|
5916
|
+
}
|
|
5917
|
+
if (showRecurringTaskDialog && recurringTaskId) {
|
|
5918
|
+
const handleConfirm = (pattern) => {
|
|
5919
|
+
try {
|
|
5920
|
+
pushUndoableAction("TASK_UPDATE");
|
|
5921
|
+
const updated = taskService.updateTask(tasks, recurringTaskId, {
|
|
5922
|
+
recurrence: pattern
|
|
5923
|
+
});
|
|
5924
|
+
setTasks(updated);
|
|
5925
|
+
setShowRecurringTaskDialog(false);
|
|
5926
|
+
setRecurringTaskId(null);
|
|
5927
|
+
} catch (err) {
|
|
5928
|
+
console.error("Error setting task recurrence:", err);
|
|
5929
|
+
}
|
|
5930
|
+
};
|
|
5931
|
+
const handleCancel = () => {
|
|
5932
|
+
setShowRecurringTaskDialog(false);
|
|
5933
|
+
setRecurringTaskId(null);
|
|
5934
|
+
};
|
|
5935
|
+
return /* @__PURE__ */ jsx27(RecurringTaskDialog, { onConfirm: handleConfirm, onCancel: handleCancel });
|
|
5936
|
+
}
|
|
5937
|
+
if (showRecurringEditDialog && recurringEditConfig) {
|
|
5938
|
+
const handleConfirm = (action) => {
|
|
5939
|
+
logger.log("[App] RecurringEditDialog handleConfirm called", { action, actionType: recurringEditConfig.actionType });
|
|
5940
|
+
if (action === "cancel") {
|
|
5941
|
+
logger.log("[App] User cancelled, closing dialog");
|
|
5942
|
+
setShowRecurringEditDialog(false);
|
|
5943
|
+
setRecurringEditConfig(null);
|
|
5944
|
+
return;
|
|
5945
|
+
}
|
|
5946
|
+
if (action === "this" || action === "all" || action === "from-today") {
|
|
5947
|
+
logger.log("[App] Calling onConfirm callback from TasksPane", { action });
|
|
5948
|
+
recurringEditConfig.onConfirm(action);
|
|
5949
|
+
}
|
|
5950
|
+
logger.log("[App] Scheduling dialog close");
|
|
5951
|
+
setTimeout(() => {
|
|
5952
|
+
logger.log("[App] Closing dialog now");
|
|
5953
|
+
setShowRecurringEditDialog(false);
|
|
5954
|
+
setRecurringEditConfig(null);
|
|
5955
|
+
}, 0);
|
|
5956
|
+
};
|
|
5957
|
+
return /* @__PURE__ */ jsx27(
|
|
5958
|
+
RecurringEditDialog,
|
|
5959
|
+
{
|
|
5960
|
+
taskTitle: recurringEditConfig.taskTitle,
|
|
5961
|
+
actionType: recurringEditConfig.actionType,
|
|
5962
|
+
onConfirm: handleConfirm
|
|
5963
|
+
}
|
|
5964
|
+
);
|
|
4223
5965
|
}
|
|
4224
|
-
return /* @__PURE__ */
|
|
4225
|
-
showOverview ? /* @__PURE__ */
|
|
5966
|
+
return /* @__PURE__ */ jsx27(FullscreenBackground, { backgroundColor: theme.colors.background || "black", children: /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", width, height, padding: 1, backgroundColor: theme.colors.background, children: [
|
|
5967
|
+
showOverview ? /* @__PURE__ */ jsx27(OverviewScreen, {}) : /* @__PURE__ */ jsx27(
|
|
4226
5968
|
ThreeColumnLayout,
|
|
4227
5969
|
{
|
|
4228
|
-
leftPane: /* @__PURE__ */
|
|
4229
|
-
centerPane: /* @__PURE__ */
|
|
4230
|
-
rightPane: /* @__PURE__ */
|
|
5970
|
+
leftPane: /* @__PURE__ */ jsx27(CalendarPane, {}),
|
|
5971
|
+
centerPane: /* @__PURE__ */ jsx27(TasksPane, {}),
|
|
5972
|
+
rightPane: /* @__PURE__ */ jsx27(TimelinePane, {}),
|
|
4231
5973
|
activePane,
|
|
4232
5974
|
height: height - 2
|
|
4233
5975
|
}
|
|
4234
5976
|
),
|
|
4235
|
-
exitConfirmation && /* @__PURE__ */
|
|
5977
|
+
exitConfirmation && /* @__PURE__ */ jsx27(Box21, { width: "100%", justifyContent: "center", paddingY: 1, children: /* @__PURE__ */ jsxs20(Text18, { backgroundColor: "red", color: "white", bold: true, children: [
|
|
4236
5978
|
" ",
|
|
4237
5979
|
"Press Ctrl+C again to exit Epoch",
|
|
4238
5980
|
" "
|
|
@@ -4240,12 +5982,12 @@ var AppContent = () => {
|
|
|
4240
5982
|
] }) });
|
|
4241
5983
|
};
|
|
4242
5984
|
var App = () => {
|
|
4243
|
-
return /* @__PURE__ */
|
|
5985
|
+
return /* @__PURE__ */ jsx27(StorageProvider, { children: /* @__PURE__ */ jsx27(ThemeProvider, { initialTheme: "dark", children: /* @__PURE__ */ jsx27(UndoProvider, { children: /* @__PURE__ */ jsx27(AppProvider, { children: /* @__PURE__ */ jsx27(AppContent, {}) }) }) }) });
|
|
4244
5986
|
};
|
|
4245
5987
|
var App_default = App;
|
|
4246
5988
|
|
|
4247
5989
|
// src/index.tsx
|
|
4248
|
-
import { jsx as
|
|
5990
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
4249
5991
|
console.clear();
|
|
4250
|
-
var app = render(/* @__PURE__ */
|
|
5992
|
+
var app = render(/* @__PURE__ */ jsx28(App_default, {}), { exitOnCtrlC: false });
|
|
4251
5993
|
global.__inkApp = app;
|