todoosy 0.3.3 → 0.3.5
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/cjs/formatter.d.ts +5 -0
- package/dist/cjs/formatter.js +202 -0
- package/dist/cjs/index.d.ts +13 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/linter.d.ts +6 -0
- package/dist/cjs/linter.js +518 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/parser.d.ts +11 -0
- package/dist/cjs/parser.js +719 -0
- package/dist/cjs/query.d.ts +22 -0
- package/dist/cjs/query.js +153 -0
- package/dist/cjs/scheme.d.ts +7 -0
- package/dist/cjs/scheme.js +14 -0
- package/dist/cjs/sequence.d.ts +53 -0
- package/dist/cjs/sequence.js +233 -0
- package/dist/cjs/settings.d.ts +65 -0
- package/dist/cjs/settings.js +262 -0
- package/dist/cjs/types.d.ts +102 -0
- package/dist/cjs/types.js +5 -0
- package/dist/settings.js +1 -1
- package/dist/settings.js.map +1 -1
- package/package.json +10 -4
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todoosy Query Engine
|
|
3
|
+
*/
|
|
4
|
+
import type { UpcomingResult, MiscResult, Scheme } from './types.js';
|
|
5
|
+
export declare function queryUpcoming(text: string, scheme?: Scheme): UpcomingResult;
|
|
6
|
+
export declare function queryMisc(text: string, scheme?: Scheme): MiscResult;
|
|
7
|
+
export interface HashtagItem {
|
|
8
|
+
id: string;
|
|
9
|
+
title_text: string;
|
|
10
|
+
path: string;
|
|
11
|
+
item_span: [number, number];
|
|
12
|
+
hashtags: string[];
|
|
13
|
+
effective_hashtags: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface HashtagResult {
|
|
16
|
+
items: HashtagItem[];
|
|
17
|
+
}
|
|
18
|
+
export interface HashtagListResult {
|
|
19
|
+
hashtags: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function queryByHashtag(text: string, hashtag: string): HashtagResult;
|
|
22
|
+
export declare function listHashtags(text: string): HashtagListResult;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Todoosy Query Engine
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.queryUpcoming = queryUpcoming;
|
|
7
|
+
exports.queryMisc = queryMisc;
|
|
8
|
+
exports.queryByHashtag = queryByHashtag;
|
|
9
|
+
exports.listHashtags = listHashtags;
|
|
10
|
+
const parser_js_1 = require("./parser.js");
|
|
11
|
+
function buildPath(itemId, ast) {
|
|
12
|
+
const itemMap = new Map();
|
|
13
|
+
const parentMap = new Map();
|
|
14
|
+
for (const item of ast.items) {
|
|
15
|
+
itemMap.set(item.id, item);
|
|
16
|
+
for (const childId of item.children) {
|
|
17
|
+
parentMap.set(childId, item.id);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const parts = [];
|
|
21
|
+
let currentId = itemId;
|
|
22
|
+
while (currentId !== undefined) {
|
|
23
|
+
const item = itemMap.get(currentId);
|
|
24
|
+
if (item) {
|
|
25
|
+
parts.unshift(item.title_text);
|
|
26
|
+
}
|
|
27
|
+
currentId = parentMap.get(currentId);
|
|
28
|
+
}
|
|
29
|
+
return parts.join(' > ');
|
|
30
|
+
}
|
|
31
|
+
function queryUpcoming(text, scheme) {
|
|
32
|
+
const { ast } = (0, parser_js_1.parse)(text);
|
|
33
|
+
const items = [];
|
|
34
|
+
// Find all items with due dates
|
|
35
|
+
for (const item of ast.items) {
|
|
36
|
+
if (item.metadata.due) {
|
|
37
|
+
const upcomingItem = {
|
|
38
|
+
id: item.id,
|
|
39
|
+
due: item.metadata.due,
|
|
40
|
+
priority: item.metadata.priority,
|
|
41
|
+
path: buildPath(item.id, ast),
|
|
42
|
+
item_span: item.item_span,
|
|
43
|
+
};
|
|
44
|
+
// Add priority label if scheme is provided
|
|
45
|
+
if (scheme && item.metadata.priority !== null) {
|
|
46
|
+
const label = scheme.priorities[String(item.metadata.priority)];
|
|
47
|
+
if (label) {
|
|
48
|
+
upcomingItem.priority_label = label;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
items.push(upcomingItem);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Sort by:
|
|
55
|
+
// 1. Due date ascending
|
|
56
|
+
// 2. Priority ascending (lower is higher priority, nulls last)
|
|
57
|
+
// 3. Document order (by item_span start)
|
|
58
|
+
items.sort((a, b) => {
|
|
59
|
+
// Due date comparison
|
|
60
|
+
const dateCompare = a.due.localeCompare(b.due);
|
|
61
|
+
if (dateCompare !== 0)
|
|
62
|
+
return dateCompare;
|
|
63
|
+
// Priority comparison (null treated as infinity)
|
|
64
|
+
const aPri = a.priority ?? Infinity;
|
|
65
|
+
const bPri = b.priority ?? Infinity;
|
|
66
|
+
if (aPri !== bPri)
|
|
67
|
+
return aPri - bPri;
|
|
68
|
+
// Document order
|
|
69
|
+
return a.item_span[0] - b.item_span[0];
|
|
70
|
+
});
|
|
71
|
+
return { items };
|
|
72
|
+
}
|
|
73
|
+
function parseMiscLocation(misc) {
|
|
74
|
+
const slashIndex = misc.indexOf('/');
|
|
75
|
+
if (slashIndex === -1) {
|
|
76
|
+
return { filename: misc, heading: 'Misc' };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
filename: misc.substring(0, slashIndex),
|
|
80
|
+
heading: misc.substring(slashIndex + 1),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function queryMisc(text, scheme) {
|
|
84
|
+
const { ast } = (0, parser_js_1.parse)(text);
|
|
85
|
+
const items = [];
|
|
86
|
+
// Determine the heading name from scheme or use default
|
|
87
|
+
const miscLocation = parseMiscLocation(scheme?.misc ?? 'todoosy.md/Misc');
|
|
88
|
+
// Find the Misc section
|
|
89
|
+
let miscSectionId = null;
|
|
90
|
+
for (const item of ast.items) {
|
|
91
|
+
if (item.type === 'heading' && item.title_text === miscLocation.heading && item.level === 1) {
|
|
92
|
+
miscSectionId = item.id;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (miscSectionId === null) {
|
|
97
|
+
return { items };
|
|
98
|
+
}
|
|
99
|
+
// Get all direct children of the Misc section
|
|
100
|
+
const miscSection = ast.items.find(i => i.id === miscSectionId);
|
|
101
|
+
if (!miscSection) {
|
|
102
|
+
return { items };
|
|
103
|
+
}
|
|
104
|
+
const itemMap = new Map();
|
|
105
|
+
for (const item of ast.items) {
|
|
106
|
+
itemMap.set(item.id, item);
|
|
107
|
+
}
|
|
108
|
+
for (const childId of miscSection.children) {
|
|
109
|
+
const child = itemMap.get(childId);
|
|
110
|
+
if (child) {
|
|
111
|
+
items.push({
|
|
112
|
+
id: child.id,
|
|
113
|
+
title_text: child.title_text,
|
|
114
|
+
item_span: child.item_span,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { items };
|
|
119
|
+
}
|
|
120
|
+
function queryByHashtag(text, hashtag) {
|
|
121
|
+
const { ast } = (0, parser_js_1.parse)(text);
|
|
122
|
+
const items = [];
|
|
123
|
+
// Normalize hashtag (lowercase, remove # if present)
|
|
124
|
+
const normalizedHashtag = hashtag.replace(/^#/, '').toLowerCase();
|
|
125
|
+
// Find all items with the hashtag (using effective_hashtags for inheritance)
|
|
126
|
+
for (const item of ast.items) {
|
|
127
|
+
if (item.metadata.effective_hashtags.includes(normalizedHashtag)) {
|
|
128
|
+
items.push({
|
|
129
|
+
id: item.id,
|
|
130
|
+
title_text: item.title_text,
|
|
131
|
+
path: buildPath(item.id, ast),
|
|
132
|
+
item_span: item.item_span,
|
|
133
|
+
hashtags: item.metadata.hashtags,
|
|
134
|
+
effective_hashtags: item.metadata.effective_hashtags,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Sort by document order
|
|
139
|
+
items.sort((a, b) => a.item_span[0] - b.item_span[0]);
|
|
140
|
+
return { items };
|
|
141
|
+
}
|
|
142
|
+
function listHashtags(text) {
|
|
143
|
+
const { ast } = (0, parser_js_1.parse)(text);
|
|
144
|
+
const hashtagSet = new Set();
|
|
145
|
+
// Collect all unique hashtags from all items
|
|
146
|
+
for (const item of ast.items) {
|
|
147
|
+
for (const tag of item.metadata.hashtags) {
|
|
148
|
+
hashtagSet.add(tag);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Return sorted list
|
|
152
|
+
return { hashtags: [...hashtagSet].sort() };
|
|
153
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todoosy Scheme Parser
|
|
3
|
+
*
|
|
4
|
+
* @deprecated This module is deprecated. Use settings.ts instead.
|
|
5
|
+
* This file is maintained for backwards compatibility.
|
|
6
|
+
*/
|
|
7
|
+
export { parseScheme, parseSettings, VALID_CALENDAR_FORMATS, VALID_FORMATTING_STYLES, } from './settings.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Todoosy Scheme Parser
|
|
4
|
+
*
|
|
5
|
+
* @deprecated This module is deprecated. Use settings.ts instead.
|
|
6
|
+
* This file is maintained for backwards compatibility.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.VALID_FORMATTING_STYLES = exports.VALID_CALENDAR_FORMATS = exports.parseSettings = exports.parseScheme = void 0;
|
|
10
|
+
var settings_js_1 = require("./settings.js");
|
|
11
|
+
Object.defineProperty(exports, "parseScheme", { enumerable: true, get: function () { return settings_js_1.parseScheme; } });
|
|
12
|
+
Object.defineProperty(exports, "parseSettings", { enumerable: true, get: function () { return settings_js_1.parseSettings; } });
|
|
13
|
+
Object.defineProperty(exports, "VALID_CALENDAR_FORMATS", { enumerable: true, get: function () { return settings_js_1.VALID_CALENDAR_FORMATS; } });
|
|
14
|
+
Object.defineProperty(exports, "VALID_FORMATTING_STYLES", { enumerable: true, get: function () { return settings_js_1.VALID_FORMATTING_STYLES; } });
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todoosy Sequence Utilities
|
|
3
|
+
*
|
|
4
|
+
* Functions for working with sequenced (numbered) task lists.
|
|
5
|
+
*/
|
|
6
|
+
import type { AST, ItemNode } from './types.js';
|
|
7
|
+
export interface SequenceInfo {
|
|
8
|
+
parentId: string;
|
|
9
|
+
hasSequence: boolean;
|
|
10
|
+
numberedChildren: {
|
|
11
|
+
id: string;
|
|
12
|
+
sequenceNumber: number;
|
|
13
|
+
}[];
|
|
14
|
+
gaps: {
|
|
15
|
+
position: number;
|
|
16
|
+
expected: number;
|
|
17
|
+
actual: number;
|
|
18
|
+
}[];
|
|
19
|
+
duplicates: {
|
|
20
|
+
number: number;
|
|
21
|
+
ids: string[];
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Analyze the sequence of numbered children under a parent item.
|
|
26
|
+
* Returns information about gaps and duplicates in the sequence.
|
|
27
|
+
*/
|
|
28
|
+
export declare function analyzeSequence(ast: AST, parentId: string): SequenceInfo;
|
|
29
|
+
/**
|
|
30
|
+
* Renumber children of a parent to have consecutive sequence numbers starting from 1.
|
|
31
|
+
* Returns a new AST with the renumbered items.
|
|
32
|
+
*/
|
|
33
|
+
export declare function renumberChildren(ast: AST, parentId: string): AST;
|
|
34
|
+
/**
|
|
35
|
+
* Insert a new item into a sequenced list at a specific position and renumber.
|
|
36
|
+
* Position is 0-indexed. Returns a new AST with the inserted and renumbered items.
|
|
37
|
+
*/
|
|
38
|
+
export declare function insertSequencedItem(ast: AST, parentId: string, position: number, newItem: Omit<ItemNode, 'id' | 'sequence_number'>): AST;
|
|
39
|
+
/**
|
|
40
|
+
* Remove an item from a sequenced list and renumber siblings.
|
|
41
|
+
* Returns a new AST with the item removed and siblings renumbered.
|
|
42
|
+
*/
|
|
43
|
+
export declare function removeSequencedItem(ast: AST, itemId: string): AST;
|
|
44
|
+
/**
|
|
45
|
+
* Convert bullet children of a parent to numbered items.
|
|
46
|
+
* Returns a new AST with bullet items converted to numbered.
|
|
47
|
+
*/
|
|
48
|
+
export declare function convertToSequence(ast: AST, parentId: string): AST;
|
|
49
|
+
/**
|
|
50
|
+
* Convert numbered children of a parent to bullet items.
|
|
51
|
+
* Returns a new AST with numbered items converted to bullets.
|
|
52
|
+
*/
|
|
53
|
+
export declare function convertToBullets(ast: AST, parentId: string): AST;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Todoosy Sequence Utilities
|
|
4
|
+
*
|
|
5
|
+
* Functions for working with sequenced (numbered) task lists.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.analyzeSequence = analyzeSequence;
|
|
9
|
+
exports.renumberChildren = renumberChildren;
|
|
10
|
+
exports.insertSequencedItem = insertSequencedItem;
|
|
11
|
+
exports.removeSequencedItem = removeSequencedItem;
|
|
12
|
+
exports.convertToSequence = convertToSequence;
|
|
13
|
+
exports.convertToBullets = convertToBullets;
|
|
14
|
+
/**
|
|
15
|
+
* Analyze the sequence of numbered children under a parent item.
|
|
16
|
+
* Returns information about gaps and duplicates in the sequence.
|
|
17
|
+
*/
|
|
18
|
+
function analyzeSequence(ast, parentId) {
|
|
19
|
+
const itemMap = new Map();
|
|
20
|
+
for (const item of ast.items) {
|
|
21
|
+
itemMap.set(item.id, item);
|
|
22
|
+
}
|
|
23
|
+
const parent = itemMap.get(parentId);
|
|
24
|
+
if (!parent) {
|
|
25
|
+
return {
|
|
26
|
+
parentId,
|
|
27
|
+
hasSequence: false,
|
|
28
|
+
numberedChildren: [],
|
|
29
|
+
gaps: [],
|
|
30
|
+
duplicates: [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Collect numbered children
|
|
34
|
+
const numberedChildren = [];
|
|
35
|
+
for (const childId of parent.children) {
|
|
36
|
+
const child = itemMap.get(childId);
|
|
37
|
+
if (child && child.marker_type === 'numbered' && child.sequence_number !== undefined) {
|
|
38
|
+
numberedChildren.push({ id: childId, sequenceNumber: child.sequence_number });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (numberedChildren.length === 0) {
|
|
42
|
+
return {
|
|
43
|
+
parentId,
|
|
44
|
+
hasSequence: false,
|
|
45
|
+
numberedChildren: [],
|
|
46
|
+
gaps: [],
|
|
47
|
+
duplicates: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Find gaps - numbers should be consecutive starting from 1
|
|
51
|
+
const gaps = [];
|
|
52
|
+
for (let i = 0; i < numberedChildren.length; i++) {
|
|
53
|
+
const expected = i + 1;
|
|
54
|
+
const actual = numberedChildren[i].sequenceNumber;
|
|
55
|
+
if (actual !== expected) {
|
|
56
|
+
gaps.push({ position: i, expected, actual });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Find duplicates
|
|
60
|
+
const numberCounts = new Map();
|
|
61
|
+
for (const child of numberedChildren) {
|
|
62
|
+
const ids = numberCounts.get(child.sequenceNumber) || [];
|
|
63
|
+
ids.push(child.id);
|
|
64
|
+
numberCounts.set(child.sequenceNumber, ids);
|
|
65
|
+
}
|
|
66
|
+
const duplicates = [];
|
|
67
|
+
for (const [num, ids] of numberCounts) {
|
|
68
|
+
if (ids.length > 1) {
|
|
69
|
+
duplicates.push({ number: num, ids });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
parentId,
|
|
74
|
+
hasSequence: true,
|
|
75
|
+
numberedChildren,
|
|
76
|
+
gaps,
|
|
77
|
+
duplicates,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Renumber children of a parent to have consecutive sequence numbers starting from 1.
|
|
82
|
+
* Returns a new AST with the renumbered items.
|
|
83
|
+
*/
|
|
84
|
+
function renumberChildren(ast, parentId) {
|
|
85
|
+
const itemMap = new Map();
|
|
86
|
+
for (const item of ast.items) {
|
|
87
|
+
itemMap.set(item.id, { ...item });
|
|
88
|
+
}
|
|
89
|
+
const parent = itemMap.get(parentId);
|
|
90
|
+
if (!parent) {
|
|
91
|
+
return ast;
|
|
92
|
+
}
|
|
93
|
+
// Renumber numbered children
|
|
94
|
+
let nextNum = 1;
|
|
95
|
+
for (const childId of parent.children) {
|
|
96
|
+
const child = itemMap.get(childId);
|
|
97
|
+
if (child && child.marker_type === 'numbered') {
|
|
98
|
+
child.sequence_number = nextNum++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
items: Array.from(itemMap.values()),
|
|
103
|
+
root_ids: [...ast.root_ids],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Insert a new item into a sequenced list at a specific position and renumber.
|
|
108
|
+
* Position is 0-indexed. Returns a new AST with the inserted and renumbered items.
|
|
109
|
+
*/
|
|
110
|
+
function insertSequencedItem(ast, parentId, position, newItem) {
|
|
111
|
+
const itemMap = new Map();
|
|
112
|
+
for (const item of ast.items) {
|
|
113
|
+
itemMap.set(item.id, { ...item });
|
|
114
|
+
}
|
|
115
|
+
const parent = itemMap.get(parentId);
|
|
116
|
+
if (!parent) {
|
|
117
|
+
return ast;
|
|
118
|
+
}
|
|
119
|
+
// Generate new ID
|
|
120
|
+
const maxId = Math.max(...ast.items.map(item => parseInt(item.id, 10)));
|
|
121
|
+
const newId = String(maxId + 1);
|
|
122
|
+
// Create new item
|
|
123
|
+
const item = {
|
|
124
|
+
...newItem,
|
|
125
|
+
id: newId,
|
|
126
|
+
sequence_number: position + 1,
|
|
127
|
+
children: newItem.children || [],
|
|
128
|
+
};
|
|
129
|
+
// Insert into parent's children
|
|
130
|
+
const newChildren = [...parent.children];
|
|
131
|
+
newChildren.splice(position, 0, newId);
|
|
132
|
+
parent.children = newChildren;
|
|
133
|
+
// Add to items
|
|
134
|
+
itemMap.set(newId, item);
|
|
135
|
+
// Renumber all numbered children
|
|
136
|
+
let nextNum = 1;
|
|
137
|
+
for (const childId of parent.children) {
|
|
138
|
+
const child = itemMap.get(childId);
|
|
139
|
+
if (child && child.marker_type === 'numbered') {
|
|
140
|
+
child.sequence_number = nextNum++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
items: Array.from(itemMap.values()),
|
|
145
|
+
root_ids: [...ast.root_ids],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Remove an item from a sequenced list and renumber siblings.
|
|
150
|
+
* Returns a new AST with the item removed and siblings renumbered.
|
|
151
|
+
*/
|
|
152
|
+
function removeSequencedItem(ast, itemId) {
|
|
153
|
+
const itemMap = new Map();
|
|
154
|
+
for (const item of ast.items) {
|
|
155
|
+
if (item.id !== itemId) {
|
|
156
|
+
itemMap.set(item.id, { ...item });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Find and update parent
|
|
160
|
+
for (const [id, item] of itemMap) {
|
|
161
|
+
if (item.children.includes(itemId)) {
|
|
162
|
+
item.children = item.children.filter(c => c !== itemId);
|
|
163
|
+
// Renumber numbered children
|
|
164
|
+
let nextNum = 1;
|
|
165
|
+
for (const childId of item.children) {
|
|
166
|
+
const child = itemMap.get(childId);
|
|
167
|
+
if (child && child.marker_type === 'numbered') {
|
|
168
|
+
child.sequence_number = nextNum++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Update root_ids if necessary
|
|
175
|
+
const newRootIds = ast.root_ids.filter(id => id !== itemId);
|
|
176
|
+
return {
|
|
177
|
+
items: Array.from(itemMap.values()),
|
|
178
|
+
root_ids: newRootIds,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Convert bullet children of a parent to numbered items.
|
|
183
|
+
* Returns a new AST with bullet items converted to numbered.
|
|
184
|
+
*/
|
|
185
|
+
function convertToSequence(ast, parentId) {
|
|
186
|
+
const itemMap = new Map();
|
|
187
|
+
for (const item of ast.items) {
|
|
188
|
+
itemMap.set(item.id, { ...item });
|
|
189
|
+
}
|
|
190
|
+
const parent = itemMap.get(parentId);
|
|
191
|
+
if (!parent) {
|
|
192
|
+
return ast;
|
|
193
|
+
}
|
|
194
|
+
// Convert bullet children to numbered
|
|
195
|
+
let nextNum = 1;
|
|
196
|
+
for (const childId of parent.children) {
|
|
197
|
+
const child = itemMap.get(childId);
|
|
198
|
+
if (child && child.type === 'list') {
|
|
199
|
+
child.marker_type = 'numbered';
|
|
200
|
+
child.sequence_number = nextNum++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
items: Array.from(itemMap.values()),
|
|
205
|
+
root_ids: [...ast.root_ids],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Convert numbered children of a parent to bullet items.
|
|
210
|
+
* Returns a new AST with numbered items converted to bullets.
|
|
211
|
+
*/
|
|
212
|
+
function convertToBullets(ast, parentId) {
|
|
213
|
+
const itemMap = new Map();
|
|
214
|
+
for (const item of ast.items) {
|
|
215
|
+
itemMap.set(item.id, { ...item });
|
|
216
|
+
}
|
|
217
|
+
const parent = itemMap.get(parentId);
|
|
218
|
+
if (!parent) {
|
|
219
|
+
return ast;
|
|
220
|
+
}
|
|
221
|
+
// Convert numbered children to bullets
|
|
222
|
+
for (const childId of parent.children) {
|
|
223
|
+
const child = itemMap.get(childId);
|
|
224
|
+
if (child && child.marker_type === 'numbered') {
|
|
225
|
+
child.marker_type = 'bullet';
|
|
226
|
+
child.sequence_number = undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
items: Array.from(itemMap.values()),
|
|
231
|
+
root_ids: [...ast.root_ids],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todoosy Settings Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses todoosy.settings.md files using a canonical Markdown-compatible format.
|
|
5
|
+
*
|
|
6
|
+
* ## Canonical Format
|
|
7
|
+
*
|
|
8
|
+
* Settings files use standard Markdown with level-1 headings as setting names:
|
|
9
|
+
*
|
|
10
|
+
* ```markdown
|
|
11
|
+
* # Setting Name
|
|
12
|
+
*
|
|
13
|
+
* value
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* ## Value Types
|
|
17
|
+
*
|
|
18
|
+
* 1. **Single Value** - First non-empty line after heading
|
|
19
|
+
* ```markdown
|
|
20
|
+
* # Timezone
|
|
21
|
+
*
|
|
22
|
+
* America/Denver
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* 2. **List Value** - Bulleted list items (- or *)
|
|
26
|
+
* ```markdown
|
|
27
|
+
* # Tags
|
|
28
|
+
*
|
|
29
|
+
* - work
|
|
30
|
+
* - personal
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* 3. **Key-Value Map** - Lines with `key - value` format
|
|
34
|
+
* ```markdown
|
|
35
|
+
* # Priorities
|
|
36
|
+
*
|
|
37
|
+
* P0 - Critical
|
|
38
|
+
* P1 - High
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* ## Known Settings
|
|
42
|
+
*
|
|
43
|
+
* - Timezone: Single value (IANA timezone identifier)
|
|
44
|
+
* - Priorities: Key-value map (P0 - Label)
|
|
45
|
+
* - Misc: Single value (filename/headingname)
|
|
46
|
+
* - Calendar Format: Single value (yyyy-mm-dd, mm/dd/yyyy, etc.)
|
|
47
|
+
* - Formatting Style: Single value (roomy, balanced, tight)
|
|
48
|
+
*
|
|
49
|
+
* ## Extended Settings
|
|
50
|
+
*
|
|
51
|
+
* Any heading not matching known settings is captured as an extended setting,
|
|
52
|
+
* with automatic value type inference.
|
|
53
|
+
*/
|
|
54
|
+
import type { Settings, Scheme } from './types.js';
|
|
55
|
+
export declare const VALID_CALENDAR_FORMATS: Set<string>;
|
|
56
|
+
export declare const VALID_FORMATTING_STYLES: Set<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Parse a settings file and return structured settings
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseSettings(text: string): Settings;
|
|
61
|
+
/**
|
|
62
|
+
* Parse a settings file and return legacy Scheme format
|
|
63
|
+
* @deprecated Use parseSettings instead
|
|
64
|
+
*/
|
|
65
|
+
export declare function parseScheme(text: string): Scheme;
|