@dinoxx/dinox-cli 1.0.11 → 1.0.13
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/README.md +132 -4
- package/dist/cli.js +2 -0
- package/dist/commands/notes/index.js +27 -4
- package/dist/commands/notes/repo.d.ts +2 -0
- package/dist/commands/notes/repo.js +55 -9
- package/dist/commands/todo/contentModel.d.ts +30 -0
- package/dist/commands/todo/contentModel.js +325 -0
- package/dist/commands/todo/index.d.ts +2 -0
- package/dist/commands/todo/index.js +349 -0
- package/dist/commands/todo/repo.d.ts +85 -0
- package/dist/commands/todo/repo.js +286 -0
- package/dist/commands/todo/taskModel.d.ts +37 -0
- package/dist/commands/todo/taskModel.js +349 -0
- package/package.json +1 -1
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
const DEFAULT_TASK_TIME_DISPLAY_FORMAT = 'datetime';
|
|
2
|
+
export function buildTaskKey(noteId, taskId) {
|
|
3
|
+
return `${noteId}:${taskId}`;
|
|
4
|
+
}
|
|
5
|
+
export function extractFlattenedTasks(rows) {
|
|
6
|
+
const extracted = [];
|
|
7
|
+
for (const row of rows) {
|
|
8
|
+
const noteId = normalizeString(row.id);
|
|
9
|
+
if (!noteId) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const taskItems = parseImageDetailTaskItems(row.image_detail);
|
|
13
|
+
if (taskItems.length === 0) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const { createdAt, createdAtRaw } = parseCreatedAt(row.created_at, row.updated_at);
|
|
17
|
+
extracted.push(...extractTasksFromDetailWithHierarchy({
|
|
18
|
+
noteId,
|
|
19
|
+
noteTitle: normalizeString(row.title),
|
|
20
|
+
createdAt,
|
|
21
|
+
createdAtRaw,
|
|
22
|
+
noteTags: parseNoteTags(row.tags),
|
|
23
|
+
taskItems,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
extracted.sort((left, right) => right.createdAt.getTime() - left.createdAt.getTime());
|
|
27
|
+
return flattenTasks(extracted);
|
|
28
|
+
}
|
|
29
|
+
export function filterTasksForTodoList(allTasks, query, taskStatus, taskTagFilter, taskDateRange) {
|
|
30
|
+
let filteredTasks = allTasks;
|
|
31
|
+
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
32
|
+
if (normalizedQuery) {
|
|
33
|
+
filteredTasks = filteredTasks.filter((task) => task.text.toLocaleLowerCase().includes(normalizedQuery));
|
|
34
|
+
}
|
|
35
|
+
if (taskStatus === 'completed') {
|
|
36
|
+
filteredTasks = filteredTasks.filter((task) => task.checked);
|
|
37
|
+
}
|
|
38
|
+
else if (taskStatus === 'uncompleted') {
|
|
39
|
+
filteredTasks = filteredTasks.filter((task) => !task.checked);
|
|
40
|
+
}
|
|
41
|
+
if (taskTagFilter.length > 0) {
|
|
42
|
+
filteredTasks = filteredTasks.filter((task) => taskTagFilter.every((tag) => task.tags?.some((taskTag) => taskTag.trim() === tag)));
|
|
43
|
+
}
|
|
44
|
+
const range = resolveTaskDateRange(taskDateRange);
|
|
45
|
+
if (range) {
|
|
46
|
+
filteredTasks = filteredTasks.filter((task) => isTaskInDateRange(task, range));
|
|
47
|
+
}
|
|
48
|
+
return filteredTasks;
|
|
49
|
+
}
|
|
50
|
+
function parseImageDetailTaskItems(raw) {
|
|
51
|
+
if (Array.isArray(raw)) {
|
|
52
|
+
return pickTaskItems(raw);
|
|
53
|
+
}
|
|
54
|
+
if (typeof raw !== 'string') {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const trimmed = raw.trim();
|
|
58
|
+
if (!trimmed) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(trimmed);
|
|
63
|
+
if (!Array.isArray(parsed)) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return pickTaskItems(parsed);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function pickTaskItems(values) {
|
|
73
|
+
const result = [];
|
|
74
|
+
for (const value of values) {
|
|
75
|
+
if (!value || typeof value !== 'object') {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const node = value;
|
|
79
|
+
if (node.type !== 'taskItem') {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
result.push(node);
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
function parseCreatedAt(createdAtRaw, updatedAtRaw) {
|
|
87
|
+
const createdAtValue = normalizeString(createdAtRaw) || null;
|
|
88
|
+
const fallback = normalizeString(updatedAtRaw) || null;
|
|
89
|
+
const source = createdAtValue ?? fallback;
|
|
90
|
+
if (!source) {
|
|
91
|
+
return { createdAt: new Date(0), createdAtRaw: createdAtValue };
|
|
92
|
+
}
|
|
93
|
+
const parsed = new Date(source);
|
|
94
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
95
|
+
return { createdAt: new Date(0), createdAtRaw: createdAtValue };
|
|
96
|
+
}
|
|
97
|
+
return { createdAt: parsed, createdAtRaw: createdAtValue };
|
|
98
|
+
}
|
|
99
|
+
function parseNoteTags(raw) {
|
|
100
|
+
const value = normalizeString(raw);
|
|
101
|
+
if (!value) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(value);
|
|
106
|
+
if (!Array.isArray(parsed)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const tags = uniqueTrimmedStrings(parsed);
|
|
110
|
+
return tags.length > 0 ? tags : undefined;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function uniqueTrimmedStrings(values) {
|
|
117
|
+
const result = [];
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
for (const value of values) {
|
|
120
|
+
const normalized = normalizeString(String(value));
|
|
121
|
+
if (!normalized || seen.has(normalized)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
seen.add(normalized);
|
|
125
|
+
result.push(normalized);
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
function normalizeTaskTimeDisplayFormat(value) {
|
|
130
|
+
if (value === 'date')
|
|
131
|
+
return 'date';
|
|
132
|
+
if (value === 'datetime')
|
|
133
|
+
return 'datetime';
|
|
134
|
+
if (value === 'datetime-seconds')
|
|
135
|
+
return 'datetime-seconds';
|
|
136
|
+
if (value === 'time')
|
|
137
|
+
return 'time';
|
|
138
|
+
return DEFAULT_TASK_TIME_DISPLAY_FORMAT;
|
|
139
|
+
}
|
|
140
|
+
function getTaskText(node) {
|
|
141
|
+
if (!node || typeof node !== 'object')
|
|
142
|
+
return '';
|
|
143
|
+
const taskNode = node;
|
|
144
|
+
if (typeof taskNode.text === 'string') {
|
|
145
|
+
return (taskNode.text ?? '').trim();
|
|
146
|
+
}
|
|
147
|
+
if (taskNode.type === 'taskList') {
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
if (!Array.isArray(taskNode.content)) {
|
|
151
|
+
return '';
|
|
152
|
+
}
|
|
153
|
+
return taskNode.content.map((child) => getTaskText(child)).join('');
|
|
154
|
+
}
|
|
155
|
+
function getTaskTags(node) {
|
|
156
|
+
if (!node || typeof node !== 'object')
|
|
157
|
+
return [];
|
|
158
|
+
const taskNode = node;
|
|
159
|
+
if (taskNode.type === 'hashtag') {
|
|
160
|
+
const label = normalizeString(taskNode.attrs?.label);
|
|
161
|
+
return label ? [label] : [];
|
|
162
|
+
}
|
|
163
|
+
if (taskNode.type === 'taskList') {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
if (!Array.isArray(taskNode.content)) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
return taskNode.content.flatMap((child) => getTaskTags(child));
|
|
170
|
+
}
|
|
171
|
+
function collectTaskTimeNodes(node, out) {
|
|
172
|
+
if (!node || typeof node !== 'object' || out.length >= 2) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const taskNode = node;
|
|
176
|
+
if (taskNode.type === 'taskList') {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (taskNode.type === 'time' || taskNode.type === 'timeblock') {
|
|
180
|
+
const iso = normalizeString(taskNode.attrs?.iso);
|
|
181
|
+
if (iso) {
|
|
182
|
+
out.push({
|
|
183
|
+
iso,
|
|
184
|
+
displayFormat: normalizeTaskTimeDisplayFormat(taskNode.attrs?.displayFormat),
|
|
185
|
+
});
|
|
186
|
+
if (out.length >= 2) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (!Array.isArray(taskNode.content)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
for (const child of taskNode.content) {
|
|
195
|
+
collectTaskTimeNodes(child, out);
|
|
196
|
+
if (out.length >= 2) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function getTaskTimeRange(node) {
|
|
202
|
+
const points = [];
|
|
203
|
+
collectTaskTimeNodes(node, points);
|
|
204
|
+
if (points.length === 0) {
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
if (points.length === 1) {
|
|
208
|
+
return { dueTime: points[0] };
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
startTime: points[0],
|
|
212
|
+
dueTime: points[1],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function extractNestedTasks(content) {
|
|
216
|
+
if (!Array.isArray(content)) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
const nested = [];
|
|
220
|
+
for (const value of content) {
|
|
221
|
+
if (!value || typeof value !== 'object') {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const node = value;
|
|
225
|
+
if (node.type === 'taskList' && Array.isArray(node.content)) {
|
|
226
|
+
nested.push(...pickTaskItems(node.content));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (node.type === 'taskItem') {
|
|
230
|
+
nested.push(node);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return nested;
|
|
234
|
+
}
|
|
235
|
+
function extractTasksFromDetailWithHierarchy(input) {
|
|
236
|
+
const { noteId, noteTitle, createdAt, createdAtRaw, noteTags, taskItems, depth = 0 } = input;
|
|
237
|
+
const result = [];
|
|
238
|
+
for (const item of taskItems) {
|
|
239
|
+
const taskId = normalizeString(item.id) ||
|
|
240
|
+
normalizeString(item.attrs?.id) ||
|
|
241
|
+
'';
|
|
242
|
+
if (!taskId) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const checked = item.checked === true || item.checked === 'true' || item.attrs?.checked === true;
|
|
246
|
+
const text = getTaskText(item).trim();
|
|
247
|
+
const tags = getTaskTags(item);
|
|
248
|
+
const { startTime, dueTime } = getTaskTimeRange(item);
|
|
249
|
+
const children = extractTasksFromDetailWithHierarchy({
|
|
250
|
+
noteId,
|
|
251
|
+
noteTitle,
|
|
252
|
+
createdAt,
|
|
253
|
+
createdAtRaw,
|
|
254
|
+
noteTags,
|
|
255
|
+
taskItems: extractNestedTasks(item.content),
|
|
256
|
+
depth: depth + 1,
|
|
257
|
+
});
|
|
258
|
+
result.push({
|
|
259
|
+
noteId,
|
|
260
|
+
noteTitle,
|
|
261
|
+
taskId,
|
|
262
|
+
text,
|
|
263
|
+
checked,
|
|
264
|
+
createdAt,
|
|
265
|
+
createdAtRaw,
|
|
266
|
+
startTime,
|
|
267
|
+
dueTime,
|
|
268
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
269
|
+
noteTags,
|
|
270
|
+
children,
|
|
271
|
+
depth,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
function flattenTasks(tasks, parentId = null) {
|
|
277
|
+
const result = [];
|
|
278
|
+
for (const task of tasks) {
|
|
279
|
+
result.push({
|
|
280
|
+
noteId: task.noteId,
|
|
281
|
+
noteTitle: task.noteTitle,
|
|
282
|
+
taskId: task.taskId,
|
|
283
|
+
text: task.text,
|
|
284
|
+
checked: task.checked,
|
|
285
|
+
createdAt: task.createdAt,
|
|
286
|
+
createdAtRaw: task.createdAtRaw,
|
|
287
|
+
startTime: task.startTime,
|
|
288
|
+
dueTime: task.dueTime,
|
|
289
|
+
tags: task.tags,
|
|
290
|
+
noteTags: task.noteTags,
|
|
291
|
+
depth: task.depth,
|
|
292
|
+
hasChildren: task.children.length > 0,
|
|
293
|
+
parentId,
|
|
294
|
+
});
|
|
295
|
+
if (task.children.length > 0) {
|
|
296
|
+
result.push(...flattenTasks(task.children, task.taskId));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
function resolveTaskDateRange(taskDateRange) {
|
|
302
|
+
if (!taskDateRange) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
const startSource = taskDateRange.startDate ?? taskDateRange.endDate;
|
|
306
|
+
const endSource = taskDateRange.endDate ?? taskDateRange.startDate;
|
|
307
|
+
if (!startSource || !endSource) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
const start = new Date(startSource);
|
|
311
|
+
const end = new Date(endSource);
|
|
312
|
+
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
start.setHours(0, 0, 0, 0);
|
|
316
|
+
end.setHours(23, 59, 59, 999);
|
|
317
|
+
return {
|
|
318
|
+
startMs: Math.min(start.getTime(), end.getTime()),
|
|
319
|
+
endMs: Math.max(start.getTime(), end.getTime()),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function isTaskInDateRange(task, range) {
|
|
323
|
+
const dueMs = parseTaskPointMs(task.dueTime);
|
|
324
|
+
if (dueMs !== null && dueMs >= range.startMs && dueMs <= range.endMs) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
const startMs = parseTaskPointMs(task.startTime);
|
|
328
|
+
if (startMs !== null && startMs >= range.startMs && startMs <= range.endMs) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
const createdAtMs = task.createdAt.getTime();
|
|
332
|
+
if (!Number.isNaN(createdAtMs) && createdAtMs >= range.startMs && createdAtMs <= range.endMs) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
function parseTaskPointMs(point) {
|
|
338
|
+
if (!point?.iso) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const timestamp = Date.parse(point.iso);
|
|
342
|
+
return Number.isNaN(timestamp) ? null : timestamp;
|
|
343
|
+
}
|
|
344
|
+
function normalizeString(value) {
|
|
345
|
+
if (typeof value !== 'string') {
|
|
346
|
+
return '';
|
|
347
|
+
}
|
|
348
|
+
return value.trim();
|
|
349
|
+
}
|