dig-burrow 1.2.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/.claude/burrow/VERSION +1 -0
- package/.claude/burrow/burrow-tools.cjs +490 -0
- package/.claude/burrow/lib/core.cjs +26 -0
- package/.claude/burrow/lib/init.cjs +95 -0
- package/.claude/burrow/lib/installer.cjs +389 -0
- package/.claude/burrow/lib/mongoose.cjs +461 -0
- package/.claude/burrow/lib/render.cjs +330 -0
- package/.claude/burrow/lib/version.cjs +137 -0
- package/.claude/burrow/lib/warren.cjs +168 -0
- package/.claude/burrow/workflows/burrow.md +184 -0
- package/.claude/commands/burrow/add.md +12 -0
- package/.claude/commands/burrow/archive.md +12 -0
- package/.claude/commands/burrow/dump.md +12 -0
- package/.claude/commands/burrow/edit.md +12 -0
- package/.claude/commands/burrow/help.md +29 -0
- package/.claude/commands/burrow/move.md +12 -0
- package/.claude/commands/burrow/read.md +12 -0
- package/.claude/commands/burrow/remove.md +12 -0
- package/.claude/commands/burrow/unarchive.md +12 -0
- package/.claude/commands/burrow/update.md +15 -0
- package/.claude/commands/burrow.md +16 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/install.cjs +430 -0
- package/package.json +31 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { generateId } = require('./core.cjs');
|
|
4
|
+
|
|
5
|
+
// --- Helpers ---
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Recursively find a card by ID in the tree.
|
|
9
|
+
* @param {object} data - Root data object
|
|
10
|
+
* @param {string} id - Card ID to find
|
|
11
|
+
* @returns {object|null} The card, or null if not found
|
|
12
|
+
*/
|
|
13
|
+
function findById(data, id) {
|
|
14
|
+
function search(cards) {
|
|
15
|
+
for (const card of cards) {
|
|
16
|
+
if (card.id === id) return card;
|
|
17
|
+
if (card.children && card.children.length) {
|
|
18
|
+
const found = search(card.children);
|
|
19
|
+
if (found) return found;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return search(data.cards);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Find the parent of a card by ID.
|
|
29
|
+
* Uses a single recursive traversal (no separate root-level loop).
|
|
30
|
+
* @param {object} data - Root data object
|
|
31
|
+
* @param {string} id - Card ID to find parent of
|
|
32
|
+
* @returns {{parent: object|null, container: Array}|null} parent card (null for root), container (the array the card lives in)
|
|
33
|
+
*/
|
|
34
|
+
function findParent(data, id) {
|
|
35
|
+
function search(parentCard, container) {
|
|
36
|
+
for (const card of container) {
|
|
37
|
+
if (card.id === id) {
|
|
38
|
+
return { parent: parentCard, container };
|
|
39
|
+
}
|
|
40
|
+
if (card.children && card.children.length) {
|
|
41
|
+
const found = search(card, card.children);
|
|
42
|
+
if (found) return found;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return search(null, data.cards);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the array to push into for adding/listing cards.
|
|
52
|
+
* @param {object} data - Root data object
|
|
53
|
+
* @param {string|null|undefined} parentId - Parent ID, or null/undefined for root
|
|
54
|
+
* @returns {Array|null} The array to operate on, or null if parentId not found
|
|
55
|
+
*/
|
|
56
|
+
function getContainer(data, parentId) {
|
|
57
|
+
if (parentId == null) return data.cards;
|
|
58
|
+
const parent = findById(data, parentId);
|
|
59
|
+
if (!parent) return null;
|
|
60
|
+
return parent.children;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the ancestry path from root to the target card.
|
|
65
|
+
* @param {object} data - Root data object
|
|
66
|
+
* @param {string} id - Target card ID
|
|
67
|
+
* @returns {Array<object>|null} Array from root ancestor to target, or null
|
|
68
|
+
*/
|
|
69
|
+
function getPath(data, id) {
|
|
70
|
+
function search(cards, path) {
|
|
71
|
+
for (const card of cards) {
|
|
72
|
+
const currentPath = [...path, card];
|
|
73
|
+
if (card.id === id) return currentPath;
|
|
74
|
+
if (card.children && card.children.length) {
|
|
75
|
+
const found = search(card.children, currentPath);
|
|
76
|
+
if (found) return found;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
return search(data.cards, []);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Count descendants recursively.
|
|
86
|
+
* @param {object} card
|
|
87
|
+
* @param {object} [opts]
|
|
88
|
+
* @param {boolean} [opts.activeOnly=false] - When true, skip archived children and their subtrees
|
|
89
|
+
* @returns {number}
|
|
90
|
+
*/
|
|
91
|
+
function countDescendants(card, opts) {
|
|
92
|
+
const activeOnly = opts && opts.activeOnly;
|
|
93
|
+
let count = 0;
|
|
94
|
+
if (card.children && card.children.length) {
|
|
95
|
+
for (const child of card.children) {
|
|
96
|
+
if (activeOnly && child.archived) continue;
|
|
97
|
+
count += 1 + countDescendants(child, opts);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return count;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// --- Constants ---
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Maximum length for body preview snippets in renderTree output.
|
|
107
|
+
* Bodies longer than this are sliced before newline replacement to avoid
|
|
108
|
+
* processing thousands of characters unnecessarily.
|
|
109
|
+
* @type {number}
|
|
110
|
+
*/
|
|
111
|
+
const PREVIEW_TRUNCATE_LENGTH = 80;
|
|
112
|
+
|
|
113
|
+
// --- Public API ---
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Add a new card to the tree.
|
|
117
|
+
* @param {object} data - Root data object
|
|
118
|
+
* @param {object} opts - {title, parentId, body, position}
|
|
119
|
+
* @returns {{card: object, breadcrumbs: Array<{id, title}>}|null} The created card with ancestor breadcrumbs, or null if parent not found
|
|
120
|
+
*/
|
|
121
|
+
function addCard(data, { title, parentId, body, position }) {
|
|
122
|
+
const container = getContainer(data, parentId);
|
|
123
|
+
if (!container) return null;
|
|
124
|
+
|
|
125
|
+
const id = generateId();
|
|
126
|
+
|
|
127
|
+
const card = {
|
|
128
|
+
id,
|
|
129
|
+
title,
|
|
130
|
+
created: new Date().toISOString(),
|
|
131
|
+
archived: false,
|
|
132
|
+
body: body || '',
|
|
133
|
+
children: [],
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (position != null && position < container.length) {
|
|
137
|
+
container.splice(position, 0, card);
|
|
138
|
+
} else {
|
|
139
|
+
container.push(card);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const pathResult = getPath(data, card.id);
|
|
143
|
+
const breadcrumbs = pathResult ? pathResult.slice(0, -1).map((c) => ({ id: c.id, title: c.title })) : [];
|
|
144
|
+
|
|
145
|
+
return { card, breadcrumbs };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Edit an existing card's title or body.
|
|
150
|
+
* @param {object} data - Root data object
|
|
151
|
+
* @param {string} id - Card ID
|
|
152
|
+
* @param {object} changes - {title, body}
|
|
153
|
+
* @returns {{card: object, oldTitle: string, oldBody: string, breadcrumbs: Array<{id, title}>}|null} Result with old values and breadcrumbs, or null if not found
|
|
154
|
+
*/
|
|
155
|
+
function editCard(data, id, { title, body }) {
|
|
156
|
+
const card = findById(data, id);
|
|
157
|
+
if (!card) return null;
|
|
158
|
+
|
|
159
|
+
const oldTitle = card.title;
|
|
160
|
+
const oldBody = card.body;
|
|
161
|
+
|
|
162
|
+
if (title !== undefined) card.title = title;
|
|
163
|
+
if (body !== undefined) card.body = body;
|
|
164
|
+
|
|
165
|
+
const pathResult = getPath(data, id);
|
|
166
|
+
const breadcrumbs = pathResult ? pathResult.slice(0, -1).map((c) => ({ id: c.id, title: c.title })) : [];
|
|
167
|
+
|
|
168
|
+
return { card, oldTitle, oldBody, breadcrumbs };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Delete a card and all its descendants.
|
|
173
|
+
* @param {object} data - Root data object
|
|
174
|
+
* @param {string} id - Card ID to delete
|
|
175
|
+
* @returns {object|null} Full deleted card object with descendantCount added, or null if not found
|
|
176
|
+
*/
|
|
177
|
+
function deleteCard(data, id) {
|
|
178
|
+
const parentResult = findParent(data, id);
|
|
179
|
+
if (!parentResult) return null;
|
|
180
|
+
|
|
181
|
+
const { container } = parentResult;
|
|
182
|
+
const idx = container.findIndex((c) => c.id === id);
|
|
183
|
+
if (idx === -1) return null;
|
|
184
|
+
|
|
185
|
+
const card = container[idx];
|
|
186
|
+
const descendantCount = countDescendants(card);
|
|
187
|
+
|
|
188
|
+
container.splice(idx, 1);
|
|
189
|
+
|
|
190
|
+
return { ...card, descendantCount };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Internal helper: find a card and its ancestry (parent + container) in a single walk.
|
|
195
|
+
* @param {object} data - Root data object
|
|
196
|
+
* @param {string} cardId - Card ID to find
|
|
197
|
+
* @returns {{card: object, parent: object|null, container: Array, ancestorIds: Set}|null}
|
|
198
|
+
*/
|
|
199
|
+
function findCardWithAncestry(data, cardId) {
|
|
200
|
+
function search(container, parent, ancestorIds) {
|
|
201
|
+
for (const card of container) {
|
|
202
|
+
if (card.id === cardId) {
|
|
203
|
+
return { card, parent, container, ancestorIds };
|
|
204
|
+
}
|
|
205
|
+
if (card.children && card.children.length) {
|
|
206
|
+
const nextAncestors = new Set(ancestorIds);
|
|
207
|
+
nextAncestors.add(card.id);
|
|
208
|
+
const found = search(card.children, card, nextAncestors);
|
|
209
|
+
if (found) return found;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return search(data.cards, null, new Set());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Move a card to a new parent (or root).
|
|
219
|
+
* Uses at most 2 tree walks: one to find card + ancestry, one to get target container.
|
|
220
|
+
* @param {object} data - Root data object
|
|
221
|
+
* @param {string} cardId - ID of card to move
|
|
222
|
+
* @param {string|null} newParentId - Target parent ID, or null for root
|
|
223
|
+
* @param {number} [requestedPosition] - Optional index in target array
|
|
224
|
+
* @returns {{card: object, sourceParentTitle: string, breadcrumbs: Array<{id, title}>}|null} Result with source parent title and breadcrumbs, or null on error (not found, cycle)
|
|
225
|
+
*/
|
|
226
|
+
function moveCard(data, cardId, newParentId, requestedPosition) {
|
|
227
|
+
// Walk 1: find card, its container, and ancestor IDs for cycle detection
|
|
228
|
+
const found = findCardWithAncestry(data, cardId);
|
|
229
|
+
if (!found) return null;
|
|
230
|
+
|
|
231
|
+
const { card, parent: sourceParent, container: sourceContainer, ancestorIds } = found;
|
|
232
|
+
const sourceParentTitle = sourceParent ? sourceParent.title : 'root';
|
|
233
|
+
|
|
234
|
+
// Cycle check: newParentId cannot be the card itself or any ancestor's descendant
|
|
235
|
+
// ancestorIds contains all ancestors of card; card.id is card itself
|
|
236
|
+
if (newParentId != null && (newParentId === cardId || ancestorIds.has(newParentId))) {
|
|
237
|
+
// newParentId is an ancestor of card — but we need to check if newParentId is a descendant of card
|
|
238
|
+
// ancestorIds only has ancestors, not descendants. We need to check if newParentId is under card.
|
|
239
|
+
// If newParentId === cardId, that's a trivial cycle. Otherwise check via getPath.
|
|
240
|
+
if (newParentId === cardId) return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// For cycle detection: newParentId must not be a descendant of cardId
|
|
244
|
+
if (newParentId != null) {
|
|
245
|
+
const path = getPath(data, newParentId);
|
|
246
|
+
if (path && path.some((p) => p.id === cardId)) {
|
|
247
|
+
return null; // Would create a cycle
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Remove from source (using already-found container)
|
|
252
|
+
const sourceIdx = sourceContainer.findIndex((c) => c.id === cardId);
|
|
253
|
+
sourceContainer.splice(sourceIdx, 1);
|
|
254
|
+
|
|
255
|
+
// Walk 2: get target container
|
|
256
|
+
const targetContainer = getContainer(data, newParentId);
|
|
257
|
+
if (!targetContainer) return null;
|
|
258
|
+
|
|
259
|
+
if (requestedPosition != null) {
|
|
260
|
+
targetContainer.splice(requestedPosition, 0, card);
|
|
261
|
+
} else {
|
|
262
|
+
targetContainer.push(card);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const pathResult = getPath(data, cardId);
|
|
266
|
+
const breadcrumbs = pathResult ? pathResult.slice(0, -1).map((c) => ({ id: c.id, title: c.title })) : [];
|
|
267
|
+
|
|
268
|
+
return { card, sourceParentTitle, breadcrumbs };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Create a body preview: truncate at PREVIEW_TRUNCATE_LENGTH chars first, then replace newlines with spaces.
|
|
274
|
+
* Truncate-first avoids processing thousands of characters in huge bodies.
|
|
275
|
+
* @param {string} body
|
|
276
|
+
* @returns {string}
|
|
277
|
+
*/
|
|
278
|
+
function makePreview(body) {
|
|
279
|
+
if (!body) return '';
|
|
280
|
+
if (body.length > PREVIEW_TRUNCATE_LENGTH) {
|
|
281
|
+
return body.slice(0, PREVIEW_TRUNCATE_LENGTH).replace(/\n/g, ' ') + '...';
|
|
282
|
+
}
|
|
283
|
+
return body.replace(/\n/g, ' ');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Render a nested tree of cards with pre-computed metadata, breadcrumbs, and archive filtering.
|
|
288
|
+
* @param {object} data - Root data object
|
|
289
|
+
* @param {string|null} rootId - Focus card ID, or null for root view
|
|
290
|
+
* @param {object} [opts] - {depth, archiveFilter}
|
|
291
|
+
* @returns {{breadcrumbs: Array|null, cards: Array}|null} Render result, or null if rootId not found
|
|
292
|
+
* cards is a nested tree: [{id, title, descendantCount, hasBody, bodyPreview, created, archived, children: [...]}]
|
|
293
|
+
*/
|
|
294
|
+
function renderTree(data, rootId, opts) {
|
|
295
|
+
const { depth: depthArg, archiveFilter } = opts || {};
|
|
296
|
+
if (depthArg !== undefined && typeof depthArg !== 'number') {
|
|
297
|
+
throw new Error('renderTree: depth must be a number');
|
|
298
|
+
}
|
|
299
|
+
const maxDepth = depthArg === 0 ? Infinity : (depthArg !== undefined ? depthArg : 1);
|
|
300
|
+
const filter = archiveFilter || 'active';
|
|
301
|
+
|
|
302
|
+
// Archive filter function
|
|
303
|
+
const shouldInclude = filter === 'active'
|
|
304
|
+
? (card) => !card.archived
|
|
305
|
+
: filter === 'archived-only'
|
|
306
|
+
? (card) => card.archived
|
|
307
|
+
: () => true;
|
|
308
|
+
|
|
309
|
+
// Build breadcrumbs
|
|
310
|
+
let breadcrumbs = null;
|
|
311
|
+
if (rootId != null) {
|
|
312
|
+
const path = getPath(data, rootId);
|
|
313
|
+
if (!path) return null;
|
|
314
|
+
breadcrumbs = path.slice(0, -1).map((c) => ({ id: c.id, title: c.title }));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function buildNested(cards, currentDepth) {
|
|
318
|
+
const result = [];
|
|
319
|
+
for (const card of cards) {
|
|
320
|
+
if (shouldInclude(card)) {
|
|
321
|
+
const entry = {
|
|
322
|
+
id: card.id,
|
|
323
|
+
title: card.title,
|
|
324
|
+
descendantCount: countDescendants(card, { activeOnly: true }),
|
|
325
|
+
hasBody: !!(card.body && card.body.trim()),
|
|
326
|
+
bodyPreview: makePreview(card.body),
|
|
327
|
+
created: card.created,
|
|
328
|
+
archived: card.archived,
|
|
329
|
+
children: (currentDepth < maxDepth && card.children && card.children.length)
|
|
330
|
+
? buildNested(card.children, currentDepth + 1)
|
|
331
|
+
: [],
|
|
332
|
+
};
|
|
333
|
+
result.push(entry);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let cards;
|
|
340
|
+
if (rootId != null) {
|
|
341
|
+
const rootCard = findById(data, rootId);
|
|
342
|
+
if (!shouldInclude(rootCard)) return { breadcrumbs, cards: [] };
|
|
343
|
+
const builtChildren = (maxDepth > 0 && rootCard.children && rootCard.children.length)
|
|
344
|
+
? buildNested(rootCard.children, 1)
|
|
345
|
+
: [];
|
|
346
|
+
// Derive descendantCount from already-built children instead of a redundant tree walk
|
|
347
|
+
const descendantCount = builtChildren.reduce(
|
|
348
|
+
(sum, child) => sum + 1 + (child.descendantCount || 0), 0
|
|
349
|
+
);
|
|
350
|
+
const entry = {
|
|
351
|
+
id: rootCard.id,
|
|
352
|
+
title: rootCard.title,
|
|
353
|
+
descendantCount,
|
|
354
|
+
hasBody: !!(rootCard.body && rootCard.body.trim()),
|
|
355
|
+
bodyPreview: makePreview(rootCard.body),
|
|
356
|
+
created: rootCard.created,
|
|
357
|
+
archived: rootCard.archived,
|
|
358
|
+
children: builtChildren,
|
|
359
|
+
};
|
|
360
|
+
cards = [entry];
|
|
361
|
+
} else {
|
|
362
|
+
cards = buildNested(data.cards, 0);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return { breadcrumbs, cards };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Recursively set archived flag on a card and all descendants.
|
|
370
|
+
* Returns the count of descendant nodes visited (excludes the card itself).
|
|
371
|
+
* @param {object} card
|
|
372
|
+
* @param {boolean} value
|
|
373
|
+
* @returns {number} Count of descendants processed
|
|
374
|
+
*/
|
|
375
|
+
function setArchivedRecursive(card, value) {
|
|
376
|
+
card.archived = value;
|
|
377
|
+
let count = 0;
|
|
378
|
+
if (card.children && card.children.length) {
|
|
379
|
+
for (const child of card.children) {
|
|
380
|
+
count += 1 + setArchivedRecursive(child, value);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return count;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Archive a card and all its descendants.
|
|
388
|
+
* @param {object} data - Root data object
|
|
389
|
+
* @param {string} id - Card ID
|
|
390
|
+
* @returns {object|null} Full card object with descendantCount, or null if not found
|
|
391
|
+
*/
|
|
392
|
+
function archiveCard(data, id) {
|
|
393
|
+
const card = findById(data, id);
|
|
394
|
+
if (!card) return null;
|
|
395
|
+
const descendantCount = setArchivedRecursive(card, true);
|
|
396
|
+
return { ...card, descendantCount };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Unarchive a card and all its descendants.
|
|
401
|
+
* @param {object} data - Root data object
|
|
402
|
+
* @param {string} id - Card ID
|
|
403
|
+
* @returns {object|null} Full card object with descendantCount, or null if not found
|
|
404
|
+
*/
|
|
405
|
+
function unarchiveCard(data, id) {
|
|
406
|
+
const card = findById(data, id);
|
|
407
|
+
if (!card) return null;
|
|
408
|
+
const descendantCount = setArchivedRecursive(card, false);
|
|
409
|
+
return { ...card, descendantCount };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Search all active cards for a query string (case-insensitive title match).
|
|
414
|
+
* Returns matches with path breadcrumbs for display.
|
|
415
|
+
* @param {object} data - Root data object
|
|
416
|
+
* @param {string} query - Lowercase search query
|
|
417
|
+
* @returns {Array<{id: string, title: string, path: string}>} Matching cards with path
|
|
418
|
+
*/
|
|
419
|
+
function searchCards(data, query) {
|
|
420
|
+
const lowerQuery = query.toLowerCase();
|
|
421
|
+
const results = [];
|
|
422
|
+
|
|
423
|
+
function walk(cards, ancestors) {
|
|
424
|
+
for (const card of cards) {
|
|
425
|
+
if (card.archived) continue;
|
|
426
|
+
const crumbs = [...ancestors, { id: card.id, title: card.title }];
|
|
427
|
+
if ((card.title || '').toLowerCase().includes(lowerQuery)) {
|
|
428
|
+
results.push({
|
|
429
|
+
id: card.id,
|
|
430
|
+
title: card.title,
|
|
431
|
+
path: crumbs.map((c) => c.title).join(' › '),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
if (card.children && card.children.length) {
|
|
435
|
+
walk(card.children, crumbs);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
walk(data.cards, []);
|
|
441
|
+
return results;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
module.exports = {
|
|
445
|
+
findById,
|
|
446
|
+
findParent,
|
|
447
|
+
getContainer,
|
|
448
|
+
getPath,
|
|
449
|
+
addCard,
|
|
450
|
+
editCard,
|
|
451
|
+
deleteCard,
|
|
452
|
+
moveCard,
|
|
453
|
+
|
|
454
|
+
countDescendants,
|
|
455
|
+
makePreview,
|
|
456
|
+
PREVIEW_TRUNCATE_LENGTH,
|
|
457
|
+
renderTree,
|
|
458
|
+
searchCards,
|
|
459
|
+
archiveCard,
|
|
460
|
+
unarchiveCard,
|
|
461
|
+
};
|