@dinoxx/dinox-cli 1.0.22 → 1.1.0
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 +29 -9
- package/dist/auth/userInfo.d.ts +2 -0
- package/dist/auth/userInfo.js +55 -14
- package/dist/cli.js +36 -2
- package/dist/commands/auth/index.d.ts +35 -0
- package/dist/commands/auth/index.js +382 -83
- package/dist/commands/boxes/index.js +2 -6
- package/dist/commands/boxes/model.d.ts +3 -0
- package/dist/commands/boxes/model.js +16 -1
- package/dist/commands/boxes/operations.js +18 -8
- package/dist/commands/boxes/repo.js +13 -11
- package/dist/commands/boxes/store.js +1 -1
- package/dist/commands/config/index.js +7 -5
- package/dist/commands/descriptor/register.js +9 -0
- package/dist/commands/doctor.d.ts +123 -0
- package/dist/commands/doctor.js +539 -0
- package/dist/commands/graph/index.d.ts +1 -1
- package/dist/commands/graph/index.js +35 -21
- package/dist/commands/graph/repo.js +92 -17
- package/dist/commands/meta/index.js +4 -4
- package/dist/commands/meta/repo.js +4 -3
- package/dist/commands/notes/actions.d.ts +1 -1
- package/dist/commands/notes/actions.js +121 -78
- package/dist/commands/notes/bulk.d.ts +1 -0
- package/dist/commands/notes/bulk.js +23 -17
- package/dist/commands/notes/contentEngine.js +69 -11
- package/dist/commands/notes/contentPatch.d.ts +4 -1
- package/dist/commands/notes/contentPatch.js +105 -41
- package/dist/commands/notes/contentPatchCommand.js +28 -11
- package/dist/commands/notes/descriptors.js +50 -20
- package/dist/commands/notes/dryRun.d.ts +1 -0
- package/dist/commands/notes/dryRun.js +4 -7
- package/dist/commands/notes/export.d.ts +6 -0
- package/dist/commands/notes/export.js +26 -6
- package/dist/commands/notes/metadataMutation.d.ts +1 -0
- package/dist/commands/notes/metadataMutation.js +14 -3
- package/dist/commands/notes/repo.d.ts +46 -4
- package/dist/commands/notes/repo.js +113 -28
- package/dist/commands/notes/runtime.d.ts +2 -1
- package/dist/commands/notes/runtime.js +17 -1
- package/dist/commands/notes/searchSql.js +13 -6
- package/dist/commands/notes/writeReceipt.d.ts +20 -0
- package/dist/commands/notes/writeReceipt.js +82 -0
- package/dist/commands/prompt/index.js +3 -3
- package/dist/commands/prompt/repo.d.ts +4 -1
- package/dist/commands/prompt/repo.js +17 -7
- package/dist/commands/runtime.d.ts +14 -2
- package/dist/commands/runtime.js +74 -15
- package/dist/commands/schema/renderSkills.js +5 -2
- package/dist/commands/schema/spec.js +4 -0
- package/dist/commands/storage/index.js +8 -2
- package/dist/commands/storage/repo.d.ts +4 -0
- package/dist/commands/storage/repo.js +356 -92
- package/dist/commands/sync.js +16 -12
- package/dist/commands/tags/content.js +9 -0
- package/dist/commands/tags/index.js +2 -6
- package/dist/commands/tags/operations.js +15 -10
- package/dist/commands/tags/repo.d.ts +2 -0
- package/dist/commands/tags/repo.js +18 -5
- package/dist/commands/tags/store.js +1 -1
- package/dist/commands/todo/contentModel.d.ts +4 -3
- package/dist/commands/todo/contentModel.js +78 -28
- package/dist/commands/todo/dryRun.d.ts +1 -0
- package/dist/commands/todo/dryRun.js +17 -13
- package/dist/commands/todo/index.d.ts +6 -0
- package/dist/commands/todo/index.js +89 -123
- package/dist/commands/todo/repo.d.ts +36 -1
- package/dist/commands/todo/repo.js +146 -37
- package/dist/commands/todo/taskIdentity.d.ts +2 -0
- package/dist/commands/todo/taskIdentity.js +64 -0
- package/dist/commands/todo/taskModel.js +27 -17
- package/dist/commands/update.d.ts +13 -1
- package/dist/commands/update.js +135 -23
- package/dist/commands/writeDryRun.d.ts +3 -2
- package/dist/commands/writeDryRun.js +4 -3
- package/dist/commands/writeDurability.d.ts +5 -0
- package/dist/commands/writeDurability.js +22 -0
- package/dist/config/paths.js +6 -1
- package/dist/config/resolve.d.ts +2 -0
- package/dist/config/resolve.js +23 -9
- package/dist/config/store.d.ts +3 -0
- package/dist/config/store.js +61 -14
- package/dist/config/types.d.ts +4 -0
- package/dist/daemon/client.d.ts +26 -1
- package/dist/daemon/client.js +236 -12
- package/dist/daemon/ensure.js +74 -12
- package/dist/daemon/index.d.ts +42 -0
- package/dist/daemon/index.js +202 -52
- package/dist/daemon/lifecycle.d.ts +42 -0
- package/dist/daemon/lifecycle.js +115 -0
- package/dist/daemon/pid.d.ts +19 -6
- package/dist/daemon/pid.js +147 -13
- package/dist/daemon/route.d.ts +14 -0
- package/dist/daemon/route.js +40 -0
- package/dist/daemon/server.d.ts +11 -0
- package/dist/daemon/server.js +201 -50
- package/dist/daemon/types.d.ts +29 -1
- package/dist/daemon/types.js +1 -0
- package/dist/dinox.js +25 -1
- package/dist/powersync/connector.js +31 -16
- package/dist/powersync/exclusiveRuntime.d.ts +19 -0
- package/dist/powersync/exclusiveRuntime.js +42 -0
- package/dist/powersync/runtime.d.ts +24 -4
- package/dist/powersync/runtime.js +234 -36
- package/dist/powersync/syncGate.d.ts +3 -1
- package/dist/powersync/syncGate.js +7 -1
- package/dist/powersync/tokenIndex.d.ts +11 -0
- package/dist/powersync/tokenIndex.js +159 -22
- package/dist/powersync/uploader.js +107 -52
- package/dist/utils/abort.js +0 -4
- package/dist/utils/asyncRelease.d.ts +2 -0
- package/dist/utils/asyncRelease.js +25 -0
- package/dist/utils/bidlinkCanonicalization.js +3 -0
- package/dist/utils/dryRun.d.ts +1 -1
- package/dist/utils/errors.d.ts +15 -1
- package/dist/utils/errors.js +223 -5
- package/dist/utils/output.d.ts +1 -0
- package/dist/utils/output.js +51 -15
- package/dist/utils/redact.js +1 -1
- package/dist/utils/sqliteLease.d.ts +8 -0
- package/dist/utils/sqliteLease.js +59 -0
- package/dist/utils/syncTimeout.d.ts +7 -1
- package/dist/utils/syncTimeout.js +8 -7
- package/dist/utils/terminalSanitization.js +6 -2
- package/dist/utils/tiptapMarkdown.js +49 -3
- package/dist/utils/tiptapMarkdownSyntax.d.ts +9 -0
- package/dist/utils/tiptapMarkdownSyntax.js +249 -0
- package/dist/utils/tokenize.js +24 -2
- package/dist/utils/updateNotice.js +60 -12
- package/package.json +12 -10
- package/dist/commands/mcp/auth.d.ts +0 -4
- package/dist/commands/mcp/auth.js +0 -75
- package/dist/commands/mcp/index.d.ts +0 -2
- package/dist/commands/mcp/index.js +0 -86
- package/dist/commands/mcp/noteContentTools.d.ts +0 -222
- package/dist/commands/mcp/noteContentTools.js +0 -390
- package/dist/commands/mcp/server.d.ts +0 -34
- package/dist/commands/mcp/server.js +0 -424
- package/dist/commands/mcp/tools.d.ts +0 -2
- package/dist/commands/mcp/tools.js +0 -32
- package/dist/commands/mcp/types.d.ts +0 -39
- package/dist/commands/mcp/types.js +0 -1
- package/dist/utils/noteContentEngine.d.ts +0 -333
- package/dist/utils/noteContentEngine.js +0 -740
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalizeTagPath, isTagPathWithinPrefix, renameTagPathWithPrefix, } from './model.js';
|
|
2
|
+
import { findMarkdownLiteralRanges, isMarkdownOffsetInSortedRanges, } from '../../utils/tiptapMarkdownSyntax.js';
|
|
2
3
|
function dedupeTags(values) {
|
|
3
4
|
const result = [];
|
|
4
5
|
const seen = new Set();
|
|
@@ -191,7 +192,15 @@ function rewriteHashtagsInMarkdown(raw, oldPrefix, newPrefix, preserveSuffix) {
|
|
|
191
192
|
}
|
|
192
193
|
let output = '';
|
|
193
194
|
let changed = false;
|
|
195
|
+
const protectedRanges = findMarkdownLiteralRanges(raw);
|
|
194
196
|
for (let index = 0; index < raw.length;) {
|
|
197
|
+
if (isMarkdownOffsetInSortedRanges(index, protectedRanges)) {
|
|
198
|
+
const range = protectedRanges.find((candidate) => index >= candidate.start && index < candidate.end);
|
|
199
|
+
const end = range?.end ?? index + 1;
|
|
200
|
+
output += raw.slice(index, end);
|
|
201
|
+
index = end;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
195
204
|
if (raw.charCodeAt(index) !== 0x23) {
|
|
196
205
|
output += raw[index];
|
|
197
206
|
index += 1;
|
|
@@ -5,7 +5,7 @@ import { buildTagAddDryRunPreview, buildTagMutationDryRunPreview } from '../writ
|
|
|
5
5
|
import { printDryRunPreview } from '../../utils/dryRun.js';
|
|
6
6
|
import { DinoxError } from '../../utils/errors.js';
|
|
7
7
|
import { getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, } from '../../utils/output.js';
|
|
8
|
-
import { commandStale, printStaleCacheWarning,
|
|
8
|
+
import { commandStale, printStaleCacheWarning, syncBeforeCommand, waitForMutationUpload, withCommandRuntime, } from '../runtime.js';
|
|
9
9
|
import { addTag, inspectAddTag, listTags } from './repo.js';
|
|
10
10
|
import { getTagStats, getTagTree, inspectTagCleanup, mergeTag, moveTag, renameTag, suggestTagMerges, } from './operations.js';
|
|
11
11
|
const TAG_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...REQUIRE_SYNC_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
|
|
@@ -75,7 +75,6 @@ async function runTagAdd(context) {
|
|
|
75
75
|
userId: identity.userId,
|
|
76
76
|
emoji,
|
|
77
77
|
});
|
|
78
|
-
printUploadDisabledWarning(runtime);
|
|
79
78
|
await waitForMutationUpload(runtime);
|
|
80
79
|
const payload = { ok: true, stale: commandStale(runtime), ...result };
|
|
81
80
|
if (structuredOutput) {
|
|
@@ -118,7 +117,6 @@ async function runTagRename(context) {
|
|
|
118
117
|
}));
|
|
119
118
|
return;
|
|
120
119
|
}
|
|
121
|
-
printUploadDisabledWarning(runtime);
|
|
122
120
|
await waitForMutationUpload(runtime);
|
|
123
121
|
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
124
122
|
});
|
|
@@ -144,7 +142,6 @@ async function runTagMove(context) {
|
|
|
144
142
|
}));
|
|
145
143
|
return;
|
|
146
144
|
}
|
|
147
|
-
printUploadDisabledWarning(runtime);
|
|
148
145
|
await waitForMutationUpload(runtime);
|
|
149
146
|
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
150
147
|
});
|
|
@@ -170,7 +167,6 @@ async function runTagMerge(context) {
|
|
|
170
167
|
}));
|
|
171
168
|
return;
|
|
172
169
|
}
|
|
173
|
-
printUploadDisabledWarning(runtime);
|
|
174
170
|
await waitForMutationUpload(runtime);
|
|
175
171
|
printSuccess({ ok: true, stale: commandStale(runtime), ...result });
|
|
176
172
|
});
|
|
@@ -193,7 +189,7 @@ async function runTagCleanup(context) {
|
|
|
193
189
|
offline: runtime.offline,
|
|
194
190
|
command: 'dino tag cleanup',
|
|
195
191
|
action: 'cleanup_tags',
|
|
196
|
-
risk: '
|
|
192
|
+
risk: 'read',
|
|
197
193
|
summary: `Inspect tag cleanup candidates (${result.issues.length} issues).`,
|
|
198
194
|
inputs: {},
|
|
199
195
|
targets: result.issues,
|
|
@@ -2,14 +2,15 @@ import { withPowerSyncWriteTransaction } from '../../powersync/runtime.js';
|
|
|
2
2
|
import { DinoxError } from '../../utils/errors.js';
|
|
3
3
|
import { nowIsoUtc } from '../../utils/time.js';
|
|
4
4
|
import { mergeTagReferencesInNote, renameTagReferencesInNote } from './content.js';
|
|
5
|
-
import {
|
|
5
|
+
import { MAX_TAG_SEGMENT_LENGTH, REQUIRED_NOTE_TAG_COLUMNS, REQUIRED_TAG_NODE_COLUMNS, canonicalizeTagPath, getLeafName, isActiveTagNode, isActualTagNode, isTagPathWithinPrefix, normalizeTagLookupKey, parseStoredTags, parentPathOf, renameTagPathWithPrefix, trimString, } from './model.js';
|
|
6
6
|
import { activeTagNodes, actualActiveTagNodes, assertCanWriteTag, assertRequiredColumns, assertWritableTagNodes, descendantsOf, ensureNoTagPathConflict, getTableColumns, loadTagNodes, parentIdForPath, requireActiveTagPath, toTagListItem, } from './store.js';
|
|
7
|
+
import { assertTagPathLimits, assertTagTextLimits } from './repo.js';
|
|
7
8
|
function parseSingleSegment(value, label) {
|
|
8
9
|
const normalized = canonicalizeTagPath(value);
|
|
9
10
|
if (!normalized || normalized.includes('/')) {
|
|
10
11
|
throw new DinoxError(`${label} must be a single path segment`);
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
assertTagTextLimits(label, normalized, MAX_TAG_SEGMENT_LENGTH);
|
|
13
14
|
return normalized;
|
|
14
15
|
}
|
|
15
16
|
function assertPath(value, label) {
|
|
@@ -17,16 +18,20 @@ function assertPath(value, label) {
|
|
|
17
18
|
if (!normalized) {
|
|
18
19
|
throw new DinoxError(`${label} is required`);
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
assertTagPathLimits(normalized, label);
|
|
21
22
|
return normalized;
|
|
22
23
|
}
|
|
23
24
|
function buildSubtreeChanges(nodes, root, newRootPath) {
|
|
24
25
|
const targetPath = assertPath(newRootPath, 'Target tag path');
|
|
25
|
-
return descendantsOf(nodes, root).map((node) =>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
return descendantsOf(nodes, root).map((node) => {
|
|
27
|
+
const toPath = renameTagPathWithPrefix(node.fullPath, root.fullPath, targetPath);
|
|
28
|
+
assertTagPathLimits(toPath, 'Target tag path');
|
|
29
|
+
return {
|
|
30
|
+
id: node.id,
|
|
31
|
+
fromPath: node.fullPath,
|
|
32
|
+
toPath,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
30
35
|
}
|
|
31
36
|
function toTreeNode(node) {
|
|
32
37
|
return {
|
|
@@ -75,13 +80,13 @@ async function loadNoteTagRows(db, userId) {
|
|
|
75
80
|
FROM c_note
|
|
76
81
|
WHERE TRIM(COALESCE(id, '')) <> ''
|
|
77
82
|
AND (is_del IS NULL OR is_del = 0)
|
|
78
|
-
AND
|
|
83
|
+
AND TRIM(COALESCE(user_no, '')) = ?
|
|
79
84
|
`, [userId]);
|
|
80
85
|
}
|
|
81
86
|
async function loadNoteTagStats(db, userId) {
|
|
82
87
|
const normalizedUserId = trimString(userId);
|
|
83
88
|
const userFilter = normalizedUserId
|
|
84
|
-
? `AND
|
|
89
|
+
? `AND TRIM(COALESCE(n.user_no, '')) = ?`
|
|
85
90
|
: '';
|
|
86
91
|
const rows = await db.getAll(`
|
|
87
92
|
SELECT
|
|
@@ -20,6 +20,8 @@ export type InspectAddTagResult = {
|
|
|
20
20
|
restoredPaths: string[];
|
|
21
21
|
emoji: string;
|
|
22
22
|
};
|
|
23
|
+
export declare function assertTagTextLimits(label: string, value: string, maxLength: number): void;
|
|
24
|
+
export declare function assertTagPathLimits(path: string, label?: string): void;
|
|
23
25
|
export declare function listTags(db: AbstractPowerSyncDatabase, input?: ListTagsInput): Promise<string[]>;
|
|
24
26
|
export declare function addTag(db: AbstractPowerSyncDatabase, input: AddTagInput): Promise<AddTagResult>;
|
|
25
27
|
export declare function inspectAddTag(db: AbstractPowerSyncDatabase, input: AddTagInput): Promise<InspectAddTagResult>;
|
|
@@ -14,6 +14,18 @@ function tagNodeMapByPath(nodes) {
|
|
|
14
14
|
}
|
|
15
15
|
return map;
|
|
16
16
|
}
|
|
17
|
+
export function assertTagTextLimits(label, value, maxLength) {
|
|
18
|
+
assertMaxLength(label, value, maxLength);
|
|
19
|
+
if (Buffer.byteLength(value, 'utf8') > maxLength) {
|
|
20
|
+
throw new DinoxError(`${label} must be at most ${maxLength} UTF-8 bytes`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function assertTagPathLimits(path, label = 'Tag path') {
|
|
24
|
+
assertTagTextLimits(label, path, MAX_TAG_PATH_LENGTH);
|
|
25
|
+
for (const segment of path.split('/')) {
|
|
26
|
+
assertTagTextLimits('Tag path segment', segment, MAX_TAG_SEGMENT_LENGTH);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
17
29
|
function assertAddTagInput(input) {
|
|
18
30
|
const userId = trimString(input.userId);
|
|
19
31
|
const emoji = trimString(input.emoji);
|
|
@@ -21,15 +33,15 @@ function assertAddTagInput(input) {
|
|
|
21
33
|
if (!tag) {
|
|
22
34
|
throw new DinoxError('Tag name is required');
|
|
23
35
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
assertTagPathLimits(tag);
|
|
37
|
+
assertTagTextLimits('Tag name', getLeafName(tag), MAX_TAG_SEGMENT_LENGTH);
|
|
38
|
+
assertTagTextLimits('Tag emoji', emoji, MAX_TAG_EMOJI_LENGTH);
|
|
27
39
|
return { tag, userId, emoji };
|
|
28
40
|
}
|
|
29
41
|
export async function listTags(db, input = {}) {
|
|
30
42
|
const userId = trimString(input.userId);
|
|
31
43
|
const userFilter = userId
|
|
32
|
-
? `AND
|
|
44
|
+
? `AND TRIM(COALESCE(user_id, '')) = ?`
|
|
33
45
|
: '';
|
|
34
46
|
const rows = await db.getAll(`
|
|
35
47
|
SELECT DISTINCT TRIM(COALESCE(full_path, path_key, '')) AS tag
|
|
@@ -112,7 +124,8 @@ export async function addTag(db, input) {
|
|
|
112
124
|
UPDATE c_tag_node
|
|
113
125
|
SET ${assignments.join(', ')}
|
|
114
126
|
WHERE id = ?
|
|
115
|
-
|
|
127
|
+
AND TRIM(COALESCE(user_id, '')) = ?
|
|
128
|
+
`, [...params, userId]);
|
|
116
129
|
if (existing.isDel === 1) {
|
|
117
130
|
restoredNodes += 1;
|
|
118
131
|
existing.isDel = 0;
|
|
@@ -20,7 +20,7 @@ export function assertCanWriteTag(columnNames, userId, action) {
|
|
|
20
20
|
export async function loadTagNodes(db, columnNames, options = {}) {
|
|
21
21
|
const userId = trimString(options.userId);
|
|
22
22
|
const userFilter = userId
|
|
23
|
-
? `AND
|
|
23
|
+
? `AND TRIM(COALESCE(user_id, '')) = ?`
|
|
24
24
|
: '';
|
|
25
25
|
const rows = await db.getAll(`
|
|
26
26
|
SELECT
|
|
@@ -14,17 +14,18 @@ export type ApplyTaskCheckedStatusResult = {
|
|
|
14
14
|
nextDoc: JsonRecord;
|
|
15
15
|
taskOrder: string[];
|
|
16
16
|
matchedIndices: number[];
|
|
17
|
-
|
|
17
|
+
matchedCheckedStates: boolean[];
|
|
18
|
+
statusChangedCount: number;
|
|
18
19
|
};
|
|
19
20
|
export declare function parseTiptapDocFromDb(rawContentJson: string | null): JsonRecord;
|
|
20
21
|
export declare function createTodoDocFromTasks(taskTexts: string[]): JsonRecord;
|
|
21
22
|
export declare function appendTasksToDoc(doc: JsonRecord, taskTexts: string[]): JsonRecord;
|
|
22
|
-
export declare function deriveImageDetailFromContentJson(doc: JsonRecord): TodoTaskItemNode[];
|
|
23
|
+
export declare function deriveImageDetailFromContentJson(doc: JsonRecord, noteId: string): TodoTaskItemNode[];
|
|
23
24
|
export declare function buildAppendMarkdown(existingMarkdown: string | null, taskTexts: string[]): string;
|
|
24
25
|
export declare function buildCreateMarkdown(taskTexts: string[]): string;
|
|
25
26
|
export declare function buildContentTextPreview(contentMd: string): string;
|
|
26
27
|
export declare function buildSummaryTitle(taskTexts: string[]): string;
|
|
27
28
|
export declare function normalizeTaskTexts(values: string[]): string[];
|
|
28
|
-
export declare function applyTaskCheckedStatus(doc: JsonRecord, taskId: string, checked: boolean): ApplyTaskCheckedStatusResult;
|
|
29
|
+
export declare function applyTaskCheckedStatus(doc: JsonRecord, noteId: string, taskId: string, checked: boolean): ApplyTaskCheckedStatusResult;
|
|
29
30
|
export declare function patchTaskMarkdownByOrder(contentMd: string | null, taskOrder: string[], matchedIndices: number[], checked: boolean): string;
|
|
30
31
|
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DinoxError } from '../../utils/errors.js';
|
|
2
2
|
import { generateId } from '../../utils/id.js';
|
|
3
3
|
import { stripMarkdown } from '../../utils/text.js';
|
|
4
|
+
import { escapeLiteralMarkdownText, findMarkdownCodeRanges, isMarkdownOffsetInSortedRanges, } from '../../utils/tiptapMarkdownSyntax.js';
|
|
5
|
+
import { buildLegacyTaskId } from './taskIdentity.js';
|
|
4
6
|
const CONTENT_TEXT_PREVIEW_LIMIT = 300;
|
|
5
7
|
const DEFAULT_TITLE_PREFIX = '待办清单';
|
|
6
8
|
export function parseTiptapDocFromDb(rawContentJson) {
|
|
@@ -46,10 +48,15 @@ export function appendTasksToDoc(doc, taskTexts) {
|
|
|
46
48
|
});
|
|
47
49
|
return nextDoc;
|
|
48
50
|
}
|
|
49
|
-
export function deriveImageDetailFromContentJson(doc) {
|
|
51
|
+
export function deriveImageDetailFromContentJson(doc, noteId) {
|
|
50
52
|
const root = assertTiptapDoc(doc);
|
|
53
|
+
const normalizedNoteId = noteId.trim();
|
|
54
|
+
if (!normalizedNoteId) {
|
|
55
|
+
throw new DinoxError('Note id is required to derive todo task details');
|
|
56
|
+
}
|
|
51
57
|
const rootContent = ensureNodeContentArray(root);
|
|
52
58
|
const result = [];
|
|
59
|
+
const preorderState = { nextIndex: 0 };
|
|
53
60
|
const nodeStack = [];
|
|
54
61
|
const insideTaskStack = [];
|
|
55
62
|
for (let index = rootContent.length - 1; index >= 0; index -= 1) {
|
|
@@ -66,7 +73,7 @@ export function deriveImageDetailFromContentJson(doc) {
|
|
|
66
73
|
const type = typeof node.type === 'string' ? node.type : '';
|
|
67
74
|
const content = Array.isArray(node.content) ? node.content : [];
|
|
68
75
|
if (type === 'taskItem' && !insideTaskItem) {
|
|
69
|
-
result.push(normalizeTaskItemNode(node));
|
|
76
|
+
result.push(normalizeTaskItemNode(node, normalizedNoteId, preorderState));
|
|
70
77
|
}
|
|
71
78
|
const nextInsideTaskItem = insideTaskItem || type === 'taskItem';
|
|
72
79
|
for (let index = content.length - 1; index >= 0; index -= 1) {
|
|
@@ -77,9 +84,9 @@ export function deriveImageDetailFromContentJson(doc) {
|
|
|
77
84
|
return result;
|
|
78
85
|
}
|
|
79
86
|
export function buildAppendMarkdown(existingMarkdown, taskTexts) {
|
|
80
|
-
const trimmed = (existingMarkdown ?? '').
|
|
87
|
+
const trimmed = (existingMarkdown ?? '').replace(/(?:\r\n|\n|\r)+$/, '');
|
|
81
88
|
const taskBlock = buildTaskMarkdown(taskTexts);
|
|
82
|
-
if (!trimmed) {
|
|
89
|
+
if (!trimmed.trim()) {
|
|
83
90
|
return taskBlock;
|
|
84
91
|
}
|
|
85
92
|
return `${trimmed}\n\n${taskBlock}`;
|
|
@@ -111,14 +118,23 @@ export function buildSummaryTitle(taskTexts) {
|
|
|
111
118
|
}
|
|
112
119
|
export function normalizeTaskTexts(values) {
|
|
113
120
|
const normalized = values
|
|
114
|
-
.map((value) => value
|
|
121
|
+
.map((value) => value
|
|
122
|
+
.replace(/\r\n?/g, '\n')
|
|
123
|
+
.split('\n')
|
|
124
|
+
.map((line) => line.trim())
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join(' '))
|
|
115
127
|
.filter((value) => value.length > 0);
|
|
116
128
|
if (normalized.length === 0) {
|
|
117
129
|
throw new DinoxError('At least one non-empty task is required');
|
|
118
130
|
}
|
|
119
131
|
return normalized;
|
|
120
132
|
}
|
|
121
|
-
export function applyTaskCheckedStatus(doc, taskId, checked) {
|
|
133
|
+
export function applyTaskCheckedStatus(doc, noteId, taskId, checked) {
|
|
134
|
+
const normalizedNoteId = noteId.trim();
|
|
135
|
+
if (!normalizedNoteId) {
|
|
136
|
+
throw new DinoxError('Note id is required');
|
|
137
|
+
}
|
|
122
138
|
const normalizedTaskId = taskId.trim();
|
|
123
139
|
if (!normalizedTaskId) {
|
|
124
140
|
throw new DinoxError('taskId is required');
|
|
@@ -126,7 +142,9 @@ export function applyTaskCheckedStatus(doc, taskId, checked) {
|
|
|
126
142
|
const nextDoc = cloneJson(assertTiptapDoc(doc));
|
|
127
143
|
const taskOrder = [];
|
|
128
144
|
const matchedIndices = [];
|
|
129
|
-
|
|
145
|
+
const matchedCheckedStates = [];
|
|
146
|
+
let statusChangedCount = 0;
|
|
147
|
+
let preorderIndex = 0;
|
|
130
148
|
const visitNode = (value) => {
|
|
131
149
|
if (!value || typeof value !== 'object') {
|
|
132
150
|
return;
|
|
@@ -134,22 +152,29 @@ export function applyTaskCheckedStatus(doc, taskId, checked) {
|
|
|
134
152
|
const node = value;
|
|
135
153
|
const type = typeof node.type === 'string' ? node.type : '';
|
|
136
154
|
if (type === 'taskItem') {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
const explicitTaskId = resolveTaskId(node);
|
|
156
|
+
const currentTaskId = explicitTaskId || buildLegacyTaskId(normalizedNoteId, preorderIndex, node);
|
|
157
|
+
preorderIndex += 1;
|
|
158
|
+
const currentIndex = taskOrder.length;
|
|
159
|
+
taskOrder.push(currentTaskId);
|
|
160
|
+
let attrs = null;
|
|
161
|
+
if (!explicitTaskId) {
|
|
162
|
+
attrs = ensureNodeAttrs(node);
|
|
163
|
+
node.id = currentTaskId;
|
|
164
|
+
attrs.id = currentTaskId;
|
|
165
|
+
}
|
|
166
|
+
if (currentTaskId === normalizedTaskId) {
|
|
167
|
+
matchedIndices.push(currentIndex);
|
|
168
|
+
attrs ??= ensureNodeAttrs(node);
|
|
169
|
+
const previousChecked = normalizeChecked(node.checked) ?? normalizeChecked(attrs.checked) ?? false;
|
|
170
|
+
matchedCheckedStates.push(previousChecked);
|
|
171
|
+
if (previousChecked !== checked) {
|
|
172
|
+
statusChangedCount += 1;
|
|
152
173
|
}
|
|
174
|
+
node.id = currentTaskId;
|
|
175
|
+
node.checked = checked;
|
|
176
|
+
attrs.id = currentTaskId;
|
|
177
|
+
attrs.checked = checked;
|
|
153
178
|
}
|
|
154
179
|
}
|
|
155
180
|
const content = Array.isArray(node.content) ? node.content : [];
|
|
@@ -162,7 +187,8 @@ export function applyTaskCheckedStatus(doc, taskId, checked) {
|
|
|
162
187
|
nextDoc,
|
|
163
188
|
taskOrder,
|
|
164
189
|
matchedIndices,
|
|
165
|
-
|
|
190
|
+
matchedCheckedStates,
|
|
191
|
+
statusChangedCount,
|
|
166
192
|
};
|
|
167
193
|
}
|
|
168
194
|
export function patchTaskMarkdownByOrder(contentMd, taskOrder, matchedIndices, checked) {
|
|
@@ -174,11 +200,15 @@ export function patchTaskMarkdownByOrder(contentMd, taskOrder, matchedIndices, c
|
|
|
174
200
|
throw new DinoxError('Cannot update task markdown: note has task entries in content_json but empty content_md.');
|
|
175
201
|
}
|
|
176
202
|
const lines = markdown.split('\n');
|
|
203
|
+
const codeRanges = findMarkdownCodeRanges(markdown);
|
|
177
204
|
const checklistLineIndexes = [];
|
|
205
|
+
let lineOffset = 0;
|
|
178
206
|
for (let index = 0; index < lines.length; index += 1) {
|
|
179
|
-
|
|
207
|
+
const line = lines[index] ?? '';
|
|
208
|
+
if (!isMarkdownOffsetInSortedRanges(lineOffset, codeRanges) && TASK_MARKDOWN_LINE_RE.test(line)) {
|
|
180
209
|
checklistLineIndexes.push(index);
|
|
181
210
|
}
|
|
211
|
+
lineOffset += line.length + (index < lines.length - 1 ? 1 : 0);
|
|
182
212
|
}
|
|
183
213
|
if (checklistLineIndexes.length !== taskOrder.length) {
|
|
184
214
|
throw new DinoxError(`Cannot update task markdown: checklist line count (${checklistLineIndexes.length}) does not match content_json task count (${taskOrder.length}).`);
|
|
@@ -220,7 +250,7 @@ function buildTaskItemNodes(taskTexts) {
|
|
|
220
250
|
}
|
|
221
251
|
function buildTaskMarkdown(taskTexts) {
|
|
222
252
|
const normalizedTasks = normalizeTaskTexts(taskTexts);
|
|
223
|
-
return normalizedTasks.map((task) => `- [ ] ${task}`).join('\n');
|
|
253
|
+
return normalizedTasks.map((task) => `- [ ] ${escapeLiteralMarkdownText(task)}`).join('\n');
|
|
224
254
|
}
|
|
225
255
|
function assertTiptapDoc(value) {
|
|
226
256
|
if (!value || typeof value !== 'object') {
|
|
@@ -269,11 +299,13 @@ function resolveTaskId(node) {
|
|
|
269
299
|
const attrsId = normalizeString(attrs?.id);
|
|
270
300
|
return attrsId;
|
|
271
301
|
}
|
|
272
|
-
function normalizeTaskItemNode(node) {
|
|
302
|
+
function normalizeTaskItemNode(node, noteId, preorderState) {
|
|
273
303
|
const attrsSource = isObject(node.attrs) ? node.attrs : {};
|
|
274
304
|
const nodeId = normalizeString(node.id);
|
|
275
305
|
const attrsId = normalizeString(attrsSource.id);
|
|
276
|
-
const
|
|
306
|
+
const preorderIndex = preorderState.nextIndex;
|
|
307
|
+
preorderState.nextIndex += 1;
|
|
308
|
+
const taskId = nodeId || attrsId || buildLegacyTaskId(noteId, preorderIndex, node);
|
|
277
309
|
const checked = normalizeChecked(node.checked) ?? normalizeChecked(attrsSource.checked) ?? false;
|
|
278
310
|
return {
|
|
279
311
|
type: 'taskItem',
|
|
@@ -284,9 +316,27 @@ function normalizeTaskItemNode(node) {
|
|
|
284
316
|
id: taskId,
|
|
285
317
|
checked,
|
|
286
318
|
},
|
|
287
|
-
content: Array.isArray(node.content)
|
|
319
|
+
content: Array.isArray(node.content)
|
|
320
|
+
? node.content.map((child) => normalizeTaskDetailContent(child, noteId, preorderState))
|
|
321
|
+
: [],
|
|
288
322
|
};
|
|
289
323
|
}
|
|
324
|
+
function normalizeTaskDetailContent(value, noteId, preorderState) {
|
|
325
|
+
if (Array.isArray(value)) {
|
|
326
|
+
return value.map((item) => normalizeTaskDetailContent(item, noteId, preorderState));
|
|
327
|
+
}
|
|
328
|
+
if (!isObject(value)) {
|
|
329
|
+
return value;
|
|
330
|
+
}
|
|
331
|
+
if (value.type === 'taskItem') {
|
|
332
|
+
return normalizeTaskItemNode(value, noteId, preorderState);
|
|
333
|
+
}
|
|
334
|
+
const normalized = {};
|
|
335
|
+
for (const [key, child] of Object.entries(value)) {
|
|
336
|
+
normalized[key] = normalizeTaskDetailContent(child, noteId, preorderState);
|
|
337
|
+
}
|
|
338
|
+
return normalized;
|
|
339
|
+
}
|
|
290
340
|
function normalizeChecked(value) {
|
|
291
341
|
if (value === true)
|
|
292
342
|
return true;
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { DinoxError } from '../../utils/errors.js';
|
|
2
|
-
import { applyTaskCheckedStatus, buildSummaryTitle, normalizeTaskTexts, parseTiptapDocFromDb, } from './contentModel.js';
|
|
3
|
-
import { getLatestTodoAppendTarget, getTodoAppendTargetById, loadTodoTaskCandidatesByTaskId, } from './repo.js';
|
|
2
|
+
import { applyTaskCheckedStatus, buildSummaryTitle, normalizeTaskTexts, patchTaskMarkdownByOrder, parseTiptapDocFromDb, } from './contentModel.js';
|
|
3
|
+
import { getLatestTodoAppendTarget, getTodoAppendTargetById, loadTodoTaskCandidatesByTaskId, validateTodoAppendTarget, } from './repo.js';
|
|
4
4
|
function buildExecutionWarnings(config, offline) {
|
|
5
5
|
const warnings = [];
|
|
6
6
|
if (offline) {
|
|
7
7
|
warnings.push('Execution would run in offline mode and only use the local cache.');
|
|
8
8
|
}
|
|
9
|
-
if (!offline && !config.powersync.uploadBaseUrl) {
|
|
10
|
-
warnings.push('Execution would be local-only because powersync.uploadBaseUrl is unset.');
|
|
11
|
-
}
|
|
12
9
|
return warnings;
|
|
13
10
|
}
|
|
14
11
|
export async function buildTodoAppendDryRunPreview(args) {
|
|
@@ -24,6 +21,7 @@ export async function buildTodoAppendDryRunPreview(args) {
|
|
|
24
21
|
}
|
|
25
22
|
throw new DinoxError('No eligible note found for append. Try `dino todo create ...` first.');
|
|
26
23
|
}
|
|
24
|
+
validateTodoAppendTarget(target);
|
|
27
25
|
return {
|
|
28
26
|
dry_run: true,
|
|
29
27
|
command: 'dino todo append',
|
|
@@ -77,23 +75,25 @@ export async function buildTodoUpdateDryRunPreview(args) {
|
|
|
77
75
|
if (!taskId) {
|
|
78
76
|
throw new DinoxError('taskId is required');
|
|
79
77
|
}
|
|
80
|
-
const candidates = await loadTodoTaskCandidatesByTaskId(args.db, taskId);
|
|
78
|
+
const candidates = await loadTodoTaskCandidatesByTaskId(args.db, taskId, { noteId: args.noteId });
|
|
81
79
|
const matches = [];
|
|
82
80
|
for (const candidate of candidates) {
|
|
83
81
|
try {
|
|
84
|
-
const doc = parseTiptapDocFromDb(candidate.content_json);
|
|
85
|
-
const applied = applyTaskCheckedStatus(doc, taskId, args.checked);
|
|
86
|
-
if (applied.matchedIndices.length === 0) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
82
|
const noteId = (candidate.id ?? '').trim();
|
|
90
83
|
if (!noteId) {
|
|
91
84
|
continue;
|
|
92
85
|
}
|
|
86
|
+
const doc = parseTiptapDocFromDb(candidate.content_json);
|
|
87
|
+
const applied = applyTaskCheckedStatus(doc, noteId, taskId, args.checked);
|
|
88
|
+
if (applied.matchedIndices.length === 0) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
93
91
|
matches.push({
|
|
92
|
+
applied,
|
|
93
|
+
contentMd: candidate.content_md,
|
|
94
94
|
noteId,
|
|
95
95
|
title: (candidate.title ?? '').trim(),
|
|
96
|
-
checked: applied.
|
|
96
|
+
checked: applied.matchedCheckedStates[0] ?? args.checked,
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
99
|
catch {
|
|
@@ -107,6 +107,9 @@ export async function buildTodoUpdateDryRunPreview(args) {
|
|
|
107
107
|
throw new DinoxError(`Task id is not unique; found in multiple notes: ${matches.map((item) => item.noteId).join(', ')}`);
|
|
108
108
|
}
|
|
109
109
|
const match = matches[0];
|
|
110
|
+
const nextContentMd = patchTaskMarkdownByOrder(match.contentMd, match.applied.taskOrder, match.applied.matchedIndices, args.checked);
|
|
111
|
+
const updated = match.applied.statusChangedCount > 0
|
|
112
|
+
|| nextContentMd !== (match.contentMd ?? '');
|
|
110
113
|
return {
|
|
111
114
|
dry_run: true,
|
|
112
115
|
command: 'dino todo update',
|
|
@@ -116,6 +119,7 @@ export async function buildTodoUpdateDryRunPreview(args) {
|
|
|
116
119
|
warnings: buildExecutionWarnings(args.config, args.offline),
|
|
117
120
|
inputs: {
|
|
118
121
|
task_id: taskId,
|
|
122
|
+
note_id: args.noteId?.trim() || null,
|
|
119
123
|
status_to: args.checked ? 'completed' : 'uncompleted',
|
|
120
124
|
},
|
|
121
125
|
targets: [
|
|
@@ -127,7 +131,7 @@ export async function buildTodoUpdateDryRunPreview(args) {
|
|
|
127
131
|
changes: {
|
|
128
132
|
checked_from: match.checked,
|
|
129
133
|
checked_to: args.checked,
|
|
130
|
-
updated
|
|
134
|
+
updated,
|
|
131
135
|
},
|
|
132
136
|
};
|
|
133
137
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import type { CommandGroupDescriptor } from '../descriptor/types.js';
|
|
3
|
+
import { type TaskDateRangeFilter } from './repo.js';
|
|
4
|
+
export declare function parseTaskDateRange(input: {
|
|
5
|
+
from?: unknown;
|
|
6
|
+
to?: unknown;
|
|
7
|
+
days?: unknown;
|
|
8
|
+
}, now?: Date): TaskDateRangeFilter | undefined;
|
|
3
9
|
export declare const todoCommandGroup: CommandGroupDescriptor;
|
|
4
10
|
export declare function registerTodoCommands(program: Command): void;
|