@noteplanco/noteplan-mcp 1.1.23 → 1.1.25
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 +7 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/noteplan/attachments-paths.d.ts +13 -0
- package/dist/noteplan/attachments-paths.d.ts.map +1 -0
- package/dist/noteplan/attachments-paths.js +27 -0
- package/dist/noteplan/attachments-paths.js.map +1 -0
- package/dist/noteplan/embeddings.js +1 -1
- package/dist/noteplan/embeddings.js.map +1 -1
- package/dist/noteplan/file-reader.d.ts +37 -46
- package/dist/noteplan/file-reader.d.ts.map +1 -1
- package/dist/noteplan/file-reader.js +200 -202
- package/dist/noteplan/file-reader.js.map +1 -1
- package/dist/noteplan/file-reader.test.d.ts +2 -0
- package/dist/noteplan/file-reader.test.d.ts.map +1 -0
- package/dist/noteplan/file-reader.test.js +67 -0
- package/dist/noteplan/file-reader.test.js.map +1 -0
- package/dist/noteplan/file-writer.d.ts +35 -31
- package/dist/noteplan/file-writer.d.ts.map +1 -1
- package/dist/noteplan/file-writer.js +280 -164
- package/dist/noteplan/file-writer.js.map +1 -1
- package/dist/noteplan/file-writer.test.js +704 -191
- package/dist/noteplan/file-writer.test.js.map +1 -1
- package/dist/noteplan/filter-store.d.ts +5 -5
- package/dist/noteplan/filter-store.d.ts.map +1 -1
- package/dist/noteplan/filter-store.js +94 -79
- package/dist/noteplan/filter-store.js.map +1 -1
- package/dist/noteplan/ripgrep-search.d.ts +25 -2
- package/dist/noteplan/ripgrep-search.d.ts.map +1 -1
- package/dist/noteplan/ripgrep-search.js +75 -2
- package/dist/noteplan/ripgrep-search.js.map +1 -1
- package/dist/noteplan/space-row-utils.d.ts +20 -0
- package/dist/noteplan/space-row-utils.d.ts.map +1 -0
- package/dist/noteplan/space-row-utils.js +78 -0
- package/dist/noteplan/space-row-utils.js.map +1 -0
- package/dist/noteplan/space-row-utils.test.d.ts +2 -0
- package/dist/noteplan/space-row-utils.test.d.ts.map +1 -0
- package/dist/noteplan/space-row-utils.test.js +123 -0
- package/dist/noteplan/space-row-utils.test.js.map +1 -0
- package/dist/noteplan/sqlite-reader.d.ts +12 -27
- package/dist/noteplan/sqlite-reader.d.ts.map +1 -1
- package/dist/noteplan/sqlite-reader.js +315 -221
- package/dist/noteplan/sqlite-reader.js.map +1 -1
- package/dist/noteplan/sqlite-writer.d.ts +1 -1
- package/dist/noteplan/sqlite-writer.d.ts.map +1 -1
- package/dist/noteplan/sqlite-writer.js +2 -2
- package/dist/noteplan/sqlite-writer.js.map +1 -1
- package/dist/noteplan/unified-store.d.ts +41 -30
- package/dist/noteplan/unified-store.d.ts.map +1 -1
- package/dist/noteplan/unified-store.js +257 -159
- package/dist/noteplan/unified-store.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +142 -61
- package/dist/server.js.map +1 -1
- package/dist/tools/attachments.d.ts +9 -9
- package/dist/tools/attachments.d.ts.map +1 -1
- package/dist/tools/attachments.js +74 -83
- package/dist/tools/attachments.js.map +1 -1
- package/dist/tools/attachments.test.js +170 -129
- package/dist/tools/attachments.test.js.map +1 -1
- package/dist/tools/calendar.d.ts +16 -13
- package/dist/tools/calendar.d.ts.map +1 -1
- package/dist/tools/calendar.js +17 -16
- package/dist/tools/calendar.js.map +1 -1
- package/dist/tools/embeddings.d.ts +6 -6
- package/dist/tools/embeddings.d.ts.map +1 -1
- package/dist/tools/embeddings.js +6 -6
- package/dist/tools/embeddings.js.map +1 -1
- package/dist/tools/events.d.ts +7 -3
- package/dist/tools/events.d.ts.map +1 -1
- package/dist/tools/events.js +51 -16
- package/dist/tools/events.js.map +1 -1
- package/dist/tools/filters.d.ts +28 -33
- package/dist/tools/filters.d.ts.map +1 -1
- package/dist/tools/filters.js +42 -105
- package/dist/tools/filters.js.map +1 -1
- package/dist/tools/notes.d.ts +80 -218
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +180 -177
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notes.test.js +242 -21
- package/dist/tools/notes.test.js.map +1 -1
- package/dist/tools/search.d.ts +4 -3
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +9 -5
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/search.test.d.ts +2 -0
- package/dist/tools/search.test.d.ts.map +1 -0
- package/dist/tools/search.test.js +37 -0
- package/dist/tools/search.test.js.map +1 -0
- package/dist/tools/spaces.d.ts +20 -20
- package/dist/tools/spaces.d.ts.map +1 -1
- package/dist/tools/spaces.js +28 -28
- package/dist/tools/spaces.js.map +1 -1
- package/dist/tools/tasks.d.ts +22 -22
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +22 -22
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/templates.d.ts +7 -7
- package/dist/tools/templates.d.ts.map +1 -1
- package/dist/tools/templates.js +4 -4
- package/dist/tools/templates.js.map +1 -1
- package/dist/tools/themes.d.ts.map +1 -1
- package/dist/tools/themes.js +26 -35
- package/dist/tools/themes.js.map +1 -1
- package/dist/transport/bridge-availability.d.ts +5 -0
- package/dist/transport/bridge-availability.d.ts.map +1 -0
- package/dist/transport/bridge-availability.js +92 -0
- package/dist/transport/bridge-availability.js.map +1 -0
- package/dist/transport/bridge-cascade.d.ts +18 -0
- package/dist/transport/bridge-cascade.d.ts.map +1 -0
- package/dist/transport/bridge-cascade.js +78 -0
- package/dist/transport/bridge-cascade.js.map +1 -0
- package/dist/transport/bridge-cascade.test.d.ts +2 -0
- package/dist/transport/bridge-cascade.test.d.ts.map +1 -0
- package/dist/transport/bridge-cascade.test.js +160 -0
- package/dist/transport/bridge-cascade.test.js.map +1 -0
- package/dist/transport/bridge-client.d.ts +197 -0
- package/dist/transport/bridge-client.d.ts.map +1 -0
- package/dist/transport/bridge-client.js +288 -0
- package/dist/transport/bridge-client.js.map +1 -0
- package/dist/transport/bridge-client.test.d.ts +2 -0
- package/dist/transport/bridge-client.test.d.ts.map +1 -0
- package/dist/transport/bridge-client.test.js +384 -0
- package/dist/transport/bridge-client.test.js.map +1 -0
- package/dist/transport/bridge-context.d.ts +10 -0
- package/dist/transport/bridge-context.d.ts.map +1 -0
- package/dist/transport/bridge-context.js +18 -0
- package/dist/transport/bridge-context.js.map +1 -0
- package/dist/transport/bridge-fs.d.ts +25 -0
- package/dist/transport/bridge-fs.d.ts.map +1 -0
- package/dist/transport/bridge-fs.js +129 -0
- package/dist/transport/bridge-fs.js.map +1 -0
- package/dist/utils/date-utils.d.ts +24 -0
- package/dist/utils/date-utils.d.ts.map +1 -1
- package/dist/utils/date-utils.js +55 -0
- package/dist/utils/date-utils.js.map +1 -1
- package/dist/utils/date-utils.test.d.ts +2 -0
- package/dist/utils/date-utils.test.d.ts.map +1 -0
- package/dist/utils/date-utils.test.js +109 -0
- package/dist/utils/date-utils.test.js.map +1 -0
- package/dist/utils/folder-access.d.ts +23 -0
- package/dist/utils/folder-access.d.ts.map +1 -0
- package/dist/utils/folder-access.js +131 -0
- package/dist/utils/folder-access.js.map +1 -0
- package/dist/utils/folder-access.test.d.ts +2 -0
- package/dist/utils/folder-access.test.d.ts.map +1 -0
- package/dist/utils/folder-access.test.js +182 -0
- package/dist/utils/folder-access.test.js.map +1 -0
- package/dist/utils/folder-matcher.d.ts.map +1 -1
- package/dist/utils/folder-matcher.js +16 -0
- package/dist/utils/folder-matcher.js.map +1 -1
- package/dist/utils/folder-matcher.test.js +42 -0
- package/dist/utils/folder-matcher.test.js.map +1 -1
- package/dist/utils/server-config.d.ts +10 -2
- package/dist/utils/server-config.d.ts.map +1 -1
- package/dist/utils/server-config.js +16 -2
- package/dist/utils/server-config.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +5 -1
- package/dist/utils/version.js.map +1 -1
- package/package.json +4 -3
- package/scripts/calendar-helper +0 -0
- package/scripts/reminders-helper +0 -0
|
@@ -5,18 +5,26 @@ import * as fileWriter from './file-writer.js';
|
|
|
5
5
|
import * as sqliteReader from './sqlite-reader.js';
|
|
6
6
|
import * as sqliteWriter from './sqlite-writer.js';
|
|
7
7
|
import * as frontmatter from './frontmatter-parser.js';
|
|
8
|
+
import { extractTagsFromContent } from './markdown-parser.js';
|
|
8
9
|
import { getTodayDateString, parseFlexibleDate } from '../utils/date-utils.js';
|
|
9
10
|
import { matchFolder } from '../utils/folder-matcher.js';
|
|
10
11
|
import { searchWithRipgrep, isRipgrepAvailable } from './ripgrep-search.js';
|
|
11
12
|
import { fuzzySearch } from './fuzzy-search.js';
|
|
12
13
|
import { parseFlexibleDateFilter, isDateInRange } from '../utils/date-filters.js';
|
|
13
14
|
import { normalizeFilename } from '../utils/filename-normalize.js';
|
|
15
|
+
import { isFolderAllowed, hasFolderAccessRules } from '../utils/folder-access.js';
|
|
16
|
+
import { getBridgeClient } from '../transport/bridge-availability.js';
|
|
14
17
|
// Cache ripgrep availability check
|
|
15
18
|
let ripgrepAvailable = null;
|
|
16
19
|
const LIST_NOTES_CACHE_TTL_MS = 5000;
|
|
17
20
|
const LIST_FOLDERS_CACHE_TTL_MS = 15000;
|
|
21
|
+
// Tag listing crosses the bridge for local tags (fast via NoteCache) and
|
|
22
|
+
// then iterates every TeamSpace note's content via regex (slow). Cache
|
|
23
|
+
// the merged result per scope so repeat calls within the TTL are instant.
|
|
24
|
+
const LIST_TAGS_CACHE_TTL_MS = 60_000;
|
|
18
25
|
const listNotesCache = new Map();
|
|
19
26
|
const listFoldersCache = new Map();
|
|
27
|
+
const listTagsCache = new Map();
|
|
20
28
|
function getCachedValue(cache, key) {
|
|
21
29
|
const entry = cache.get(key);
|
|
22
30
|
if (!entry)
|
|
@@ -34,9 +42,10 @@ function setCachedValue(cache, key, value, ttlMs) {
|
|
|
34
42
|
});
|
|
35
43
|
return value;
|
|
36
44
|
}
|
|
37
|
-
function invalidateListingCaches() {
|
|
45
|
+
export function invalidateListingCaches() {
|
|
38
46
|
listNotesCache.clear();
|
|
39
47
|
listFoldersCache.clear();
|
|
48
|
+
listTagsCache.clear();
|
|
40
49
|
}
|
|
41
50
|
function normalizeLocalFolderFilter(folder) {
|
|
42
51
|
if (!folder)
|
|
@@ -60,13 +69,13 @@ function normalizeLocalFolderFilter(folder) {
|
|
|
60
69
|
* Returns undefined when the input is undefined/empty (pass-through for optional params).
|
|
61
70
|
* Throws when a non-empty value doesn't match any known space.
|
|
62
71
|
*/
|
|
63
|
-
export function resolveSpaceId(space) {
|
|
72
|
+
export async function resolveSpaceId(space) {
|
|
64
73
|
if (!space)
|
|
65
74
|
return undefined;
|
|
66
75
|
const trimmed = space.trim();
|
|
67
76
|
if (!trimmed)
|
|
68
77
|
return undefined;
|
|
69
|
-
const spaces = sqliteReader.listSpaces();
|
|
78
|
+
const spaces = await sqliteReader.listSpaces();
|
|
70
79
|
// Exact ID match takes priority (unambiguous)
|
|
71
80
|
const idMatch = spaces.find((s) => s.id === trimmed);
|
|
72
81
|
if (idMatch)
|
|
@@ -84,19 +93,30 @@ export function resolveSpaceId(space) {
|
|
|
84
93
|
throw new Error(`Space not found: "${space}". Available spaces: ${available.length > 0 ? available.join(', ') : 'none'}`);
|
|
85
94
|
}
|
|
86
95
|
/**
|
|
87
|
-
* Get a note by various identifiers
|
|
96
|
+
* Get a note by various identifiers. Outer wrapper applies the
|
|
97
|
+
* configured folder allow/deny rules — pretending the note doesn't
|
|
98
|
+
* exist if it sits inside a blocked folder, so the agent can't sneak
|
|
99
|
+
* around the filter via id/filename/date/title resolution.
|
|
88
100
|
*/
|
|
89
|
-
export function getNote(options) {
|
|
101
|
+
export async function getNote(options) {
|
|
102
|
+
const note = await getNoteUnchecked(options);
|
|
103
|
+
if (!note)
|
|
104
|
+
return null;
|
|
105
|
+
if (!isFolderAllowed(note.filename))
|
|
106
|
+
return null;
|
|
107
|
+
return note;
|
|
108
|
+
}
|
|
109
|
+
async function getNoteUnchecked(options) {
|
|
90
110
|
const { id, title, filename, date } = options;
|
|
91
|
-
const space = resolveSpaceId(options.space);
|
|
111
|
+
const space = await resolveSpaceId(options.space);
|
|
92
112
|
// If ID is specified, get directly (best for space notes)
|
|
93
113
|
if (id) {
|
|
94
114
|
const normalizedId = normalizeFilename(id);
|
|
95
|
-
const spaceNote = sqliteReader.getSpaceNote(normalizedId);
|
|
115
|
+
const spaceNote = await sqliteReader.getSpaceNote(normalizedId);
|
|
96
116
|
if (spaceNote)
|
|
97
117
|
return spaceNote;
|
|
98
118
|
// Fallback: for local notes, id === filename
|
|
99
|
-
const localNote = fileReader.readNoteFile(normalizedId);
|
|
119
|
+
const localNote = await fileReader.readNoteFile(normalizedId);
|
|
100
120
|
if (localNote)
|
|
101
121
|
return localNote;
|
|
102
122
|
return null;
|
|
@@ -105,7 +125,7 @@ export function getNote(options) {
|
|
|
105
125
|
if (date) {
|
|
106
126
|
const dateStr = parseFlexibleDate(date);
|
|
107
127
|
if (space) {
|
|
108
|
-
return sqliteReader.getSpaceCalendarNote(dateStr, space);
|
|
128
|
+
return await sqliteReader.getSpaceCalendarNote(dateStr, space);
|
|
109
129
|
}
|
|
110
130
|
return fileReader.getCalendarNote(dateStr);
|
|
111
131
|
}
|
|
@@ -113,46 +133,44 @@ export function getNote(options) {
|
|
|
113
133
|
if (filename) {
|
|
114
134
|
const normalizedFilename = normalizeFilename(filename.trim());
|
|
115
135
|
if (space) {
|
|
116
|
-
const directSpaceNote = sqliteReader.getSpaceNote(normalizedFilename);
|
|
136
|
+
const directSpaceNote = await sqliteReader.getSpaceNote(normalizedFilename);
|
|
117
137
|
if (directSpaceNote && directSpaceNote.spaceId === space) {
|
|
118
138
|
return directSpaceNote;
|
|
119
139
|
}
|
|
120
|
-
const scopedSpaceNote = sqliteReader
|
|
121
|
-
.listSpaceNotes(space)
|
|
122
|
-
.find((note) => note.filename === normalizedFilename || note.id === normalizedFilename);
|
|
140
|
+
const scopedSpaceNote = (await sqliteReader.listSpaceNotes(space)).find((note) => note.filename === normalizedFilename || note.id === normalizedFilename);
|
|
123
141
|
if (scopedSpaceNote) {
|
|
124
142
|
return scopedSpaceNote;
|
|
125
143
|
}
|
|
126
144
|
}
|
|
127
145
|
// Check if it's a space filename
|
|
128
146
|
if (normalizedFilename.includes('%%NotePlanCloud%%')) {
|
|
129
|
-
return sqliteReader.getSpaceNote(normalizedFilename);
|
|
147
|
+
return await sqliteReader.getSpaceNote(normalizedFilename);
|
|
130
148
|
}
|
|
131
|
-
const localNote = fileReader.readNoteFile(normalizedFilename);
|
|
149
|
+
const localNote = await fileReader.readNoteFile(normalizedFilename);
|
|
132
150
|
if (localNote)
|
|
133
151
|
return localNote;
|
|
134
|
-
return sqliteReader.getSpaceNote(normalizedFilename);
|
|
152
|
+
return await sqliteReader.getSpaceNote(normalizedFilename);
|
|
135
153
|
}
|
|
136
154
|
// If title is specified, search by title
|
|
137
155
|
if (title) {
|
|
138
156
|
if (space) {
|
|
139
|
-
return sqliteReader.getSpaceNoteByTitle(title, space);
|
|
157
|
+
return await sqliteReader.getSpaceNoteByTitle(title, space);
|
|
140
158
|
}
|
|
141
159
|
// Try local first
|
|
142
|
-
const localNote = fileReader.getNoteByTitle(title);
|
|
160
|
+
const localNote = await fileReader.getNoteByTitle(title);
|
|
143
161
|
if (localNote)
|
|
144
162
|
return localNote;
|
|
145
163
|
// Try space
|
|
146
|
-
return sqliteReader.getSpaceNoteByTitle(title);
|
|
164
|
+
return await sqliteReader.getSpaceNoteByTitle(title);
|
|
147
165
|
}
|
|
148
166
|
return null;
|
|
149
167
|
}
|
|
150
168
|
/**
|
|
151
169
|
* List all notes, optionally filtered
|
|
152
170
|
*/
|
|
153
|
-
export function listNotes(options = {}) {
|
|
171
|
+
export async function listNotes(options = {}) {
|
|
154
172
|
const { folder, type } = options;
|
|
155
|
-
const space = resolveSpaceId(options.space);
|
|
173
|
+
const space = await resolveSpaceId(options.space);
|
|
156
174
|
const normalizedFolder = normalizeLocalFolderFilter(folder);
|
|
157
175
|
const cacheKey = JSON.stringify({
|
|
158
176
|
folder: normalizedFolder || '',
|
|
@@ -168,30 +186,37 @@ export function listNotes(options = {}) {
|
|
|
168
186
|
// Get local notes
|
|
169
187
|
if (!space) {
|
|
170
188
|
if (!type || type === 'note') {
|
|
171
|
-
notes.push(...fileReader.listProjectNotes(normalizedFolder));
|
|
189
|
+
notes.push(...(await fileReader.listProjectNotes(normalizedFolder)));
|
|
172
190
|
}
|
|
173
191
|
if ((!type || type === 'calendar') && !hasFolderScope) {
|
|
174
|
-
notes.push(...fileReader.listCalendarNotes());
|
|
192
|
+
notes.push(...(await fileReader.listCalendarNotes()));
|
|
175
193
|
}
|
|
176
194
|
}
|
|
177
195
|
// Get space notes
|
|
178
196
|
if (space || !hasFolderScope) {
|
|
179
|
-
|
|
197
|
+
let spaceNotes = await sqliteReader.listSpaceNotes(space);
|
|
198
|
+
if (space && hasFolderScope) {
|
|
199
|
+
const resolved = await sqliteReader.resolveSpaceFolder(space, normalizedFolder);
|
|
200
|
+
spaceNotes = resolved ? spaceNotes.filter((n) => n.folder === resolved.id) : [];
|
|
201
|
+
}
|
|
202
|
+
notes.push(...spaceNotes);
|
|
180
203
|
}
|
|
181
|
-
|
|
182
|
-
|
|
204
|
+
const accessFiltered = notes.filter((n) => isFolderAllowed(n.filename));
|
|
205
|
+
// Sort by modified date (newest first) AFTER folder filtering — no
|
|
206
|
+
// point sorting entries we're about to discard.
|
|
207
|
+
accessFiltered.sort((a, b) => {
|
|
183
208
|
const dateA = a.modifiedAt?.getTime() || 0;
|
|
184
209
|
const dateB = b.modifiedAt?.getTime() || 0;
|
|
185
210
|
return dateB - dateA;
|
|
186
211
|
});
|
|
187
|
-
return setCachedValue(listNotesCache, cacheKey,
|
|
212
|
+
return setCachedValue(listNotesCache, cacheKey, accessFiltered, LIST_NOTES_CACHE_TTL_MS);
|
|
188
213
|
}
|
|
189
214
|
/**
|
|
190
215
|
* Search across all notes with enhanced options
|
|
191
216
|
*/
|
|
192
217
|
export async function searchNotes(query, options = {}) {
|
|
193
218
|
const { types, folder, limit = 50, fuzzy = false } = options;
|
|
194
|
-
const space = resolveSpaceId(options.space);
|
|
219
|
+
const space = await resolveSpaceId(options.space);
|
|
195
220
|
const normalizedFolder = normalizeLocalFolderFilter(folder);
|
|
196
221
|
const searchField = options.searchField ?? 'content';
|
|
197
222
|
const effectiveTypes = searchField === 'content' ? types : (types ?? ['note']);
|
|
@@ -238,18 +263,18 @@ export async function searchNotes(query, options = {}) {
|
|
|
238
263
|
paths: searchPaths,
|
|
239
264
|
maxResults: limit * 2, // Get extra for filtering
|
|
240
265
|
});
|
|
241
|
-
backend =
|
|
266
|
+
backend = rgResult.backend;
|
|
242
267
|
partialResults = rgResult.partialResults;
|
|
243
268
|
if (rgResult.warning)
|
|
244
269
|
warnings.push(rgResult.warning);
|
|
245
|
-
results.push(...convertRipgrepToSearchResults(rgResult.matches));
|
|
270
|
+
results.push(...(await convertRipgrepToSearchResults(rgResult.matches)));
|
|
246
271
|
}
|
|
247
272
|
catch (error) {
|
|
248
273
|
console.error('Ripgrep search failed:', error);
|
|
249
274
|
backend = 'fallback';
|
|
250
275
|
warnings.push('ripgrep failed; using fallback local search');
|
|
251
276
|
// Fall back to simple search
|
|
252
|
-
const localNotes = fileReader.searchLocalNotes(query, {
|
|
277
|
+
const localNotes = await fileReader.searchLocalNotes(query, {
|
|
253
278
|
types: effectiveTypes,
|
|
254
279
|
folder: normalizedFolder,
|
|
255
280
|
limit: limit * 2,
|
|
@@ -261,7 +286,7 @@ export async function searchNotes(query, options = {}) {
|
|
|
261
286
|
backend = 'simple';
|
|
262
287
|
warnings.push('ripgrep unavailable; using fallback local search');
|
|
263
288
|
// Fallback to original search method
|
|
264
|
-
const localNotes = fileReader.searchLocalNotes(query, {
|
|
289
|
+
const localNotes = await fileReader.searchLocalNotes(query, {
|
|
265
290
|
types: effectiveTypes,
|
|
266
291
|
folder: normalizedFolder,
|
|
267
292
|
limit: limit * 2,
|
|
@@ -270,7 +295,7 @@ export async function searchNotes(query, options = {}) {
|
|
|
270
295
|
}
|
|
271
296
|
}
|
|
272
297
|
// Search space notes
|
|
273
|
-
const spaceNotes = sqliteReader.searchSpaceNotesFTS(query, { spaceId: space, limit: limit * 2 });
|
|
298
|
+
const spaceNotes = await sqliteReader.searchSpaceNotesFTS(query, { spaceId: space, limit: limit * 2 });
|
|
274
299
|
for (const note of spaceNotes) {
|
|
275
300
|
results.push({
|
|
276
301
|
note,
|
|
@@ -278,18 +303,27 @@ export async function searchNotes(query, options = {}) {
|
|
|
278
303
|
score: 50, // Base score, will be enhanced below
|
|
279
304
|
});
|
|
280
305
|
}
|
|
306
|
+
// Space-only search: relabel backend so callers can tell whether the
|
|
307
|
+
// space query went through bridge or sqlite-direct. Local content
|
|
308
|
+
// search (ripgrep/fallback) didn't run in this branch.
|
|
309
|
+
if (space) {
|
|
310
|
+
backend = (await getBridgeClient()) ? 'bridge' : 'sqlite';
|
|
311
|
+
}
|
|
281
312
|
}
|
|
282
313
|
else {
|
|
283
314
|
backend = 'simple';
|
|
284
315
|
warnings.push(`searchField=${searchField} performs metadata matching on titles/filenames (not full-text content search).`);
|
|
285
|
-
const candidates = listNotes({
|
|
316
|
+
const candidates = (await listNotes({
|
|
286
317
|
folder: normalizedFolder,
|
|
287
318
|
space,
|
|
288
|
-
}).filter((note) => !effectiveTypes || effectiveTypes.includes(note.type));
|
|
319
|
+
})).filter((note) => !effectiveTypes || effectiveTypes.includes(note.type));
|
|
289
320
|
results = candidates
|
|
290
321
|
.map((note) => scoreMetadataMatch(note, query, searchField, options.caseSensitive === true))
|
|
291
322
|
.filter((entry) => entry !== null);
|
|
292
323
|
}
|
|
324
|
+
// Drop denied folders before any per-result work (frontmatter parsing,
|
|
325
|
+
// date math, scoring) — they're going out the window anyway.
|
|
326
|
+
results = results.filter((r) => isFolderAllowed(r.note.filename));
|
|
293
327
|
// Apply date filters
|
|
294
328
|
if (modifiedAfter || modifiedBefore || createdAfter || createdBefore) {
|
|
295
329
|
results = results.filter((r) => {
|
|
@@ -493,7 +527,7 @@ function noteToSearchResult(note, query) {
|
|
|
493
527
|
/**
|
|
494
528
|
* Convert ripgrep matches to SearchResults
|
|
495
529
|
*/
|
|
496
|
-
function convertRipgrepToSearchResults(matches) {
|
|
530
|
+
async function convertRipgrepToSearchResults(matches) {
|
|
497
531
|
// Group matches by file
|
|
498
532
|
const byFile = new Map();
|
|
499
533
|
for (const m of matches) {
|
|
@@ -503,7 +537,7 @@ function convertRipgrepToSearchResults(matches) {
|
|
|
503
537
|
}
|
|
504
538
|
const results = [];
|
|
505
539
|
for (const [file, fileMatches] of byFile) {
|
|
506
|
-
const note = fileReader.readNoteFile(file);
|
|
540
|
+
const note = await fileReader.readNoteFile(file);
|
|
507
541
|
if (note) {
|
|
508
542
|
results.push({
|
|
509
543
|
note,
|
|
@@ -622,8 +656,38 @@ function calculateScore(note, matches, query) {
|
|
|
622
656
|
/**
|
|
623
657
|
* Create a new note with smart folder matching
|
|
624
658
|
*/
|
|
625
|
-
export function createNote(title, content, options = {}) {
|
|
626
|
-
const { folder, space, createNewFolder = false } = options;
|
|
659
|
+
export async function createNote(title, content, options = {}) {
|
|
660
|
+
const { folder, space, createNewFolder = false, filename, calendarDate } = options;
|
|
661
|
+
// Calendar-note path: short-circuit BEFORE the project-note logic so
|
|
662
|
+
// we don't pass a missing title through sanitizeFilename, which used
|
|
663
|
+
// to surface as `Cannot read properties of undefined (reading 'replace')`.
|
|
664
|
+
if (calendarDate) {
|
|
665
|
+
if (space) {
|
|
666
|
+
throw new Error('Calendar notes cannot live inside a TeamSpace via this tool — omit `space` and target the local Calendar folder.');
|
|
667
|
+
}
|
|
668
|
+
if (filename) {
|
|
669
|
+
throw new Error('Calendar notes derive their filename from the date — pass `date` instead of `filename`.');
|
|
670
|
+
}
|
|
671
|
+
const writtenFilename = await fileWriter.createCalendarNoteIfNew(calendarDate, content ?? '');
|
|
672
|
+
const note = await fileReader.readNoteFile(writtenFilename);
|
|
673
|
+
if (!note)
|
|
674
|
+
throw new Error('Failed to create calendar note');
|
|
675
|
+
invalidateListingCaches();
|
|
676
|
+
const calendarFolderResolution = {
|
|
677
|
+
requested: 'Calendar',
|
|
678
|
+
resolved: 'Calendar',
|
|
679
|
+
matched: true,
|
|
680
|
+
ambiguous: false,
|
|
681
|
+
score: 1,
|
|
682
|
+
alternatives: [],
|
|
683
|
+
};
|
|
684
|
+
return { note, folderResolution: calendarFolderResolution };
|
|
685
|
+
}
|
|
686
|
+
// Project- and space-note path requires a non-empty title; reject up
|
|
687
|
+
// front with a clear error rather than crashing in sanitizeFilename.
|
|
688
|
+
if (!title?.trim()) {
|
|
689
|
+
throw new Error('title is required for project notes. To create a calendar note instead, pass `date` (e.g. "2026-W16", "2026-05-07", "today").');
|
|
690
|
+
}
|
|
627
691
|
// Initialize folder resolution info
|
|
628
692
|
const folderResolution = {
|
|
629
693
|
requested: folder,
|
|
@@ -654,10 +718,16 @@ export function createNote(title, content, options = {}) {
|
|
|
654
718
|
}
|
|
655
719
|
}
|
|
656
720
|
}
|
|
657
|
-
const resolvedSpace = resolveSpaceId(space);
|
|
721
|
+
const resolvedSpace = await resolveSpaceId(space);
|
|
658
722
|
if (resolvedSpace) {
|
|
659
|
-
|
|
660
|
-
|
|
723
|
+
// Space notes don't use filesystem filenames; the explicit-filename
|
|
724
|
+
// override is a local-note feature only. Surface that explicitly
|
|
725
|
+
// rather than silently dropping the parameter.
|
|
726
|
+
if (filename) {
|
|
727
|
+
throw new Error('filename is not supported for space notes — they are addressed by ID, not by filesystem path.');
|
|
728
|
+
}
|
|
729
|
+
const writtenFilename = sqliteWriter.createSpaceNote(resolvedSpace, title, effectiveContent);
|
|
730
|
+
const note = await sqliteReader.getSpaceNote(writtenFilename);
|
|
661
731
|
if (!note)
|
|
662
732
|
throw new Error('Failed to create space note');
|
|
663
733
|
invalidateListingCaches();
|
|
@@ -666,7 +736,7 @@ export function createNote(title, content, options = {}) {
|
|
|
666
736
|
// Smart folder matching for local notes
|
|
667
737
|
let resolvedFolder = folder;
|
|
668
738
|
if (folder && !createNewFolder) {
|
|
669
|
-
const folders = fileReader.listFolders();
|
|
739
|
+
const folders = await fileReader.listFolders();
|
|
670
740
|
const match = matchFolder(folder, folders);
|
|
671
741
|
if (match.matched && match.folder) {
|
|
672
742
|
resolvedFolder = match.folder.path;
|
|
@@ -677,8 +747,8 @@ export function createNote(title, content, options = {}) {
|
|
|
677
747
|
folderResolution.alternatives = match.alternatives.map((f) => f.path);
|
|
678
748
|
}
|
|
679
749
|
}
|
|
680
|
-
const
|
|
681
|
-
const note = fileReader.readNoteFile(
|
|
750
|
+
const writtenFilename = await fileWriter.createProjectNote(title, effectiveContent, resolvedFolder, filename);
|
|
751
|
+
const note = await fileReader.readNoteFile(writtenFilename);
|
|
682
752
|
if (!note)
|
|
683
753
|
throw new Error('Failed to create note');
|
|
684
754
|
invalidateListingCaches();
|
|
@@ -687,23 +757,23 @@ export function createNote(title, content, options = {}) {
|
|
|
687
757
|
/**
|
|
688
758
|
* Update a note's content
|
|
689
759
|
*/
|
|
690
|
-
export function updateNote(identifier, content, options = {}) {
|
|
691
|
-
const updateSpace = (spaceIdentifier) => {
|
|
692
|
-
const existing = sqliteReader.getSpaceNote(spaceIdentifier);
|
|
760
|
+
export async function updateNote(identifier, content, options = {}) {
|
|
761
|
+
const updateSpace = async (spaceIdentifier) => {
|
|
762
|
+
const existing = await sqliteReader.getSpaceNote(spaceIdentifier);
|
|
693
763
|
if (!existing) {
|
|
694
764
|
throw new Error(`Note not found: ${spaceIdentifier}`);
|
|
695
765
|
}
|
|
696
766
|
const writeIdentifier = existing.id || spaceIdentifier;
|
|
697
767
|
sqliteWriter.updateSpaceNote(writeIdentifier, content);
|
|
698
|
-
const note = sqliteReader.getSpaceNote(writeIdentifier);
|
|
768
|
+
const note = await sqliteReader.getSpaceNote(writeIdentifier);
|
|
699
769
|
if (!note)
|
|
700
770
|
throw new Error('Note not found after update');
|
|
701
771
|
invalidateListingCaches();
|
|
702
772
|
return note;
|
|
703
773
|
};
|
|
704
|
-
const updateLocal = (localIdentifier) => {
|
|
705
|
-
fileWriter.updateNote(localIdentifier, content);
|
|
706
|
-
const note = fileReader.readNoteFile(localIdentifier);
|
|
774
|
+
const updateLocal = async (localIdentifier) => {
|
|
775
|
+
await fileWriter.updateNote(localIdentifier, content);
|
|
776
|
+
const note = await fileReader.readNoteFile(localIdentifier);
|
|
707
777
|
if (!note)
|
|
708
778
|
throw new Error('Note not found after update');
|
|
709
779
|
invalidateListingCaches();
|
|
@@ -718,8 +788,8 @@ export function updateNote(identifier, content, options = {}) {
|
|
|
718
788
|
if (identifier.includes('%%NotePlanCloud%%')) {
|
|
719
789
|
return updateSpace(identifier);
|
|
720
790
|
}
|
|
721
|
-
const localNote = fileReader.readNoteFile(identifier);
|
|
722
|
-
const spaceNote = sqliteReader.getSpaceNote(identifier);
|
|
791
|
+
const localNote = await fileReader.readNoteFile(identifier);
|
|
792
|
+
const spaceNote = await sqliteReader.getSpaceNote(identifier);
|
|
723
793
|
if (localNote) {
|
|
724
794
|
return updateLocal(localNote.filename);
|
|
725
795
|
}
|
|
@@ -731,8 +801,8 @@ export function updateNote(identifier, content, options = {}) {
|
|
|
731
801
|
/**
|
|
732
802
|
* Delete a note
|
|
733
803
|
*/
|
|
734
|
-
export function deleteNote(identifier) {
|
|
735
|
-
const note = getNoteByIdentifierOrThrow(identifier);
|
|
804
|
+
export async function deleteNote(identifier) {
|
|
805
|
+
const note = await getNoteByIdentifierOrThrow(identifier);
|
|
736
806
|
if (note.source === 'space') {
|
|
737
807
|
const moved = sqliteWriter.deleteSpaceNote(note.id || note.filename);
|
|
738
808
|
invalidateListingCaches();
|
|
@@ -743,7 +813,7 @@ export function deleteNote(identifier) {
|
|
|
743
813
|
noteId: moved.noteId,
|
|
744
814
|
};
|
|
745
815
|
}
|
|
746
|
-
const trashedPath = fileWriter.deleteNote(note.filename);
|
|
816
|
+
const trashedPath = await fileWriter.deleteNote(note.filename);
|
|
747
817
|
invalidateListingCaches();
|
|
748
818
|
return {
|
|
749
819
|
source: 'local',
|
|
@@ -751,8 +821,8 @@ export function deleteNote(identifier) {
|
|
|
751
821
|
toIdentifier: trashedPath,
|
|
752
822
|
};
|
|
753
823
|
}
|
|
754
|
-
function getNoteByIdentifierOrThrow(identifier, options = {}) {
|
|
755
|
-
const note = getNote({ id: identifier }) ?? getNote({ filename: identifier });
|
|
824
|
+
async function getNoteByIdentifierOrThrow(identifier, options = {}) {
|
|
825
|
+
const note = (await getNote({ id: identifier })) ?? (await getNote({ filename: identifier }));
|
|
756
826
|
if (!note) {
|
|
757
827
|
throw new Error('Note not found');
|
|
758
828
|
}
|
|
@@ -761,8 +831,8 @@ function getNoteByIdentifierOrThrow(identifier, options = {}) {
|
|
|
761
831
|
}
|
|
762
832
|
return note;
|
|
763
833
|
}
|
|
764
|
-
function getLocalProjectNoteOrThrow(identifier, action) {
|
|
765
|
-
const note = getNoteByIdentifierOrThrow(identifier);
|
|
834
|
+
async function getLocalProjectNoteOrThrow(identifier, action) {
|
|
835
|
+
const note = await getNoteByIdentifierOrThrow(identifier);
|
|
766
836
|
if (note.source !== 'local') {
|
|
767
837
|
throw new Error(`${action} is currently supported for local notes only`);
|
|
768
838
|
}
|
|
@@ -771,7 +841,7 @@ function getLocalProjectNoteOrThrow(identifier, action) {
|
|
|
771
841
|
}
|
|
772
842
|
return note;
|
|
773
843
|
}
|
|
774
|
-
function resolveSpaceMoveDestination(note, destinationFolder, options = {}) {
|
|
844
|
+
async function resolveSpaceMoveDestination(note, destinationFolder, options = {}) {
|
|
775
845
|
const query = destinationFolder.trim();
|
|
776
846
|
if (!query) {
|
|
777
847
|
throw new Error('Destination folder is required');
|
|
@@ -786,7 +856,7 @@ function resolveSpaceMoveDestination(note, destinationFolder, options = {}) {
|
|
|
786
856
|
label: note.spaceId,
|
|
787
857
|
};
|
|
788
858
|
}
|
|
789
|
-
const folder = sqliteReader.resolveSpaceFolder(note.spaceId, query, { includeTrash: true });
|
|
859
|
+
const folder = await sqliteReader.resolveSpaceFolder(note.spaceId, query, { includeTrash: true });
|
|
790
860
|
if (!folder?.id) {
|
|
791
861
|
throw new Error(`Destination folder not found in space: ${destinationFolder}`);
|
|
792
862
|
}
|
|
@@ -798,13 +868,13 @@ function resolveSpaceMoveDestination(note, destinationFolder, options = {}) {
|
|
|
798
868
|
label: folder.path,
|
|
799
869
|
};
|
|
800
870
|
}
|
|
801
|
-
export function previewMoveNote(identifier, destinationFolder) {
|
|
802
|
-
const note = getNoteByIdentifierOrThrow(identifier);
|
|
871
|
+
export async function previewMoveNote(identifier, destinationFolder) {
|
|
872
|
+
const note = await getNoteByIdentifierOrThrow(identifier);
|
|
803
873
|
if (note.source === 'space') {
|
|
804
874
|
if (note.type !== 'note') {
|
|
805
875
|
throw new Error('Moving calendar notes in TeamSpaces is not supported');
|
|
806
876
|
}
|
|
807
|
-
const destination = resolveSpaceMoveDestination(note, destinationFolder);
|
|
877
|
+
const destination = await resolveSpaceMoveDestination(note, destinationFolder);
|
|
808
878
|
return {
|
|
809
879
|
note,
|
|
810
880
|
fromFilename: note.filename,
|
|
@@ -813,20 +883,20 @@ export function previewMoveNote(identifier, destinationFolder) {
|
|
|
813
883
|
destinationParentId: destination.id,
|
|
814
884
|
};
|
|
815
885
|
}
|
|
816
|
-
const preview = fileWriter.previewMoveLocalNote(note.filename, destinationFolder);
|
|
886
|
+
const preview = await fileWriter.previewMoveLocalNote(note.filename, destinationFolder);
|
|
817
887
|
return {
|
|
818
888
|
note,
|
|
819
889
|
...preview,
|
|
820
890
|
};
|
|
821
891
|
}
|
|
822
|
-
export function moveNote(identifier, destinationFolder) {
|
|
823
|
-
const preview = previewMoveNote(identifier, destinationFolder);
|
|
892
|
+
export async function moveNote(identifier, destinationFolder) {
|
|
893
|
+
const preview = await previewMoveNote(identifier, destinationFolder);
|
|
824
894
|
if (preview.note.source === 'space') {
|
|
825
895
|
if (!preview.note.id || !preview.destinationParentId) {
|
|
826
896
|
throw new Error('Could not resolve TeamSpace move target');
|
|
827
897
|
}
|
|
828
898
|
sqliteWriter.moveSpaceNote(preview.note.id, preview.destinationParentId);
|
|
829
|
-
const movedNote = sqliteReader.getSpaceNote(preview.note.id);
|
|
899
|
+
const movedNote = await sqliteReader.getSpaceNote(preview.note.id);
|
|
830
900
|
if (!movedNote) {
|
|
831
901
|
throw new Error('Failed to read note after move');
|
|
832
902
|
}
|
|
@@ -839,8 +909,8 @@ export function moveNote(identifier, destinationFolder) {
|
|
|
839
909
|
destinationParentId: preview.destinationParentId,
|
|
840
910
|
};
|
|
841
911
|
}
|
|
842
|
-
const nextFilename = fileWriter.moveLocalNote(preview.note.filename, destinationFolder);
|
|
843
|
-
const movedNote = fileReader.readNoteFile(nextFilename);
|
|
912
|
+
const nextFilename = await fileWriter.moveLocalNote(preview.note.filename, destinationFolder);
|
|
913
|
+
const movedNote = await fileReader.readNoteFile(nextFilename);
|
|
844
914
|
if (!movedNote) {
|
|
845
915
|
throw new Error('Failed to read note after move');
|
|
846
916
|
}
|
|
@@ -852,17 +922,17 @@ export function moveNote(identifier, destinationFolder) {
|
|
|
852
922
|
destinationFolder: preview.destinationFolder,
|
|
853
923
|
};
|
|
854
924
|
}
|
|
855
|
-
export function previewRestoreNote(identifier, destinationFolder) {
|
|
856
|
-
const note = getNoteByIdentifierOrThrow(identifier, { allowTrash: true });
|
|
925
|
+
export async function previewRestoreNote(identifier, destinationFolder) {
|
|
926
|
+
const note = await getNoteByIdentifierOrThrow(identifier, { allowTrash: true });
|
|
857
927
|
if (note.source === 'space') {
|
|
858
928
|
if (!note.id) {
|
|
859
929
|
throw new Error('Space note ID is required for restore');
|
|
860
930
|
}
|
|
861
|
-
if (!sqliteReader.isSpaceNoteInTrash(note.id)) {
|
|
931
|
+
if (!await sqliteReader.isSpaceNoteInTrash(note.id)) {
|
|
862
932
|
throw new Error('Note is not in TeamSpace @Trash');
|
|
863
933
|
}
|
|
864
934
|
const destination = destinationFolder
|
|
865
|
-
? resolveSpaceMoveDestination(note, destinationFolder)
|
|
935
|
+
? await resolveSpaceMoveDestination(note, destinationFolder)
|
|
866
936
|
: { id: note.spaceId || '', label: note.spaceId || '' };
|
|
867
937
|
if (!destination.id) {
|
|
868
938
|
throw new Error('Could not resolve TeamSpace restore destination');
|
|
@@ -877,8 +947,8 @@ export function previewRestoreNote(identifier, destinationFolder) {
|
|
|
877
947
|
if (note.type !== 'trash') {
|
|
878
948
|
throw new Error('Local note is not in @Trash');
|
|
879
949
|
}
|
|
880
|
-
const preview = fileWriter.previewRestoreLocalNoteFromTrash(note.filename, destinationFolder && destinationFolder.trim().length > 0 ? destinationFolder : 'Notes');
|
|
881
|
-
const restoredNote = fileReader.readNoteFile(preview.fromFilename);
|
|
950
|
+
const preview = await fileWriter.previewRestoreLocalNoteFromTrash(note.filename, destinationFolder && destinationFolder.trim().length > 0 ? destinationFolder : 'Notes');
|
|
951
|
+
const restoredNote = await fileReader.readNoteFile(preview.fromFilename);
|
|
882
952
|
if (!restoredNote) {
|
|
883
953
|
throw new Error('Failed to read local trash note');
|
|
884
954
|
}
|
|
@@ -889,11 +959,11 @@ export function previewRestoreNote(identifier, destinationFolder) {
|
|
|
889
959
|
toIdentifier: preview.toFilename,
|
|
890
960
|
};
|
|
891
961
|
}
|
|
892
|
-
export function restoreNote(identifier, destinationFolder) {
|
|
893
|
-
const preview = previewRestoreNote(identifier, destinationFolder);
|
|
962
|
+
export async function restoreNote(identifier, destinationFolder) {
|
|
963
|
+
const preview = await previewRestoreNote(identifier, destinationFolder);
|
|
894
964
|
if (preview.source === 'space') {
|
|
895
965
|
sqliteWriter.restoreSpaceNote(preview.fromIdentifier, preview.toIdentifier);
|
|
896
|
-
const restoredNote = sqliteReader.getSpaceNote(preview.fromIdentifier);
|
|
966
|
+
const restoredNote = await sqliteReader.getSpaceNote(preview.fromIdentifier);
|
|
897
967
|
if (!restoredNote) {
|
|
898
968
|
throw new Error('Failed to read TeamSpace note after restore');
|
|
899
969
|
}
|
|
@@ -905,8 +975,8 @@ export function restoreNote(identifier, destinationFolder) {
|
|
|
905
975
|
toIdentifier: preview.toIdentifier,
|
|
906
976
|
};
|
|
907
977
|
}
|
|
908
|
-
const restoredFilename = fileWriter.restoreLocalNoteFromTrash(preview.fromIdentifier, destinationFolder && destinationFolder.trim().length > 0 ? destinationFolder : 'Notes');
|
|
909
|
-
const restoredNote = fileReader.readNoteFile(restoredFilename);
|
|
978
|
+
const restoredFilename = await fileWriter.restoreLocalNoteFromTrash(preview.fromIdentifier, destinationFolder && destinationFolder.trim().length > 0 ? destinationFolder : 'Notes');
|
|
979
|
+
const restoredNote = await fileReader.readNoteFile(restoredFilename);
|
|
910
980
|
if (!restoredNote) {
|
|
911
981
|
throw new Error('Failed to read local note after restore');
|
|
912
982
|
}
|
|
@@ -918,18 +988,18 @@ export function restoreNote(identifier, destinationFolder) {
|
|
|
918
988
|
toIdentifier: preview.toIdentifier,
|
|
919
989
|
};
|
|
920
990
|
}
|
|
921
|
-
export function previewRenameNoteFile(filename, newFilename, keepExtension = true) {
|
|
922
|
-
const note = getLocalProjectNoteOrThrow(filename, 'Rename note file');
|
|
923
|
-
const preview = fileWriter.previewRenameLocalNoteFile(note.filename, newFilename, keepExtension);
|
|
991
|
+
export async function previewRenameNoteFile(filename, newFilename, keepExtension = true) {
|
|
992
|
+
const note = await getLocalProjectNoteOrThrow(filename, 'Rename note file');
|
|
993
|
+
const preview = await fileWriter.previewRenameLocalNoteFile(note.filename, newFilename, keepExtension);
|
|
924
994
|
return {
|
|
925
995
|
note,
|
|
926
996
|
...preview,
|
|
927
997
|
};
|
|
928
998
|
}
|
|
929
|
-
export function renameNoteFile(filename, newFilename, keepExtension = true) {
|
|
930
|
-
const preview = previewRenameNoteFile(filename, newFilename, keepExtension);
|
|
931
|
-
const nextFilename = fileWriter.renameLocalNoteFile(filename, newFilename, keepExtension);
|
|
932
|
-
const renamedNote = fileReader.readNoteFile(nextFilename);
|
|
999
|
+
export async function renameNoteFile(filename, newFilename, keepExtension = true) {
|
|
1000
|
+
const preview = await previewRenameNoteFile(filename, newFilename, keepExtension);
|
|
1001
|
+
const nextFilename = await fileWriter.renameLocalNoteFile(filename, newFilename, keepExtension);
|
|
1002
|
+
const renamedNote = await fileReader.readNoteFile(nextFilename);
|
|
933
1003
|
if (!renamedNote) {
|
|
934
1004
|
throw new Error('Failed to read note after rename');
|
|
935
1005
|
}
|
|
@@ -940,8 +1010,8 @@ export function renameNoteFile(filename, newFilename, keepExtension = true) {
|
|
|
940
1010
|
toFilename: preview.toFilename,
|
|
941
1011
|
};
|
|
942
1012
|
}
|
|
943
|
-
export function renameSpaceNote(identifier, newTitle) {
|
|
944
|
-
const note = getNoteByIdentifierOrThrow(identifier);
|
|
1013
|
+
export async function renameSpaceNote(identifier, newTitle) {
|
|
1014
|
+
const note = await getNoteByIdentifierOrThrow(identifier);
|
|
945
1015
|
if (note.source !== 'space') {
|
|
946
1016
|
throw new Error('renameSpaceNote is for TeamSpace notes only');
|
|
947
1017
|
}
|
|
@@ -951,7 +1021,7 @@ export function renameSpaceNote(identifier, newTitle) {
|
|
|
951
1021
|
const fromTitle = note.title;
|
|
952
1022
|
const writeId = note.id || note.filename;
|
|
953
1023
|
sqliteWriter.updateSpaceNoteTitle(writeId, newTitle);
|
|
954
|
-
const renamedNote = sqliteReader.getSpaceNote(writeId);
|
|
1024
|
+
const renamedNote = await sqliteReader.getSpaceNote(writeId);
|
|
955
1025
|
if (!renamedNote) {
|
|
956
1026
|
throw new Error('Failed to read note after rename');
|
|
957
1027
|
}
|
|
@@ -965,40 +1035,45 @@ export function renameSpaceNote(identifier, newTitle) {
|
|
|
965
1035
|
/**
|
|
966
1036
|
* Get today's daily note
|
|
967
1037
|
*/
|
|
968
|
-
export function getTodayNote(space) {
|
|
1038
|
+
export async function getTodayNote(space) {
|
|
969
1039
|
const dateStr = getTodayDateString();
|
|
970
1040
|
return getCalendarNote(dateStr, space);
|
|
971
1041
|
}
|
|
972
1042
|
/**
|
|
973
1043
|
* Get a calendar note by date
|
|
974
1044
|
*/
|
|
975
|
-
export function getCalendarNote(date, space) {
|
|
1045
|
+
export async function getCalendarNote(date, space) {
|
|
976
1046
|
const dateStr = parseFlexibleDate(date);
|
|
977
|
-
const resolvedSpace = resolveSpaceId(space);
|
|
1047
|
+
const resolvedSpace = await resolveSpaceId(space);
|
|
978
1048
|
if (resolvedSpace) {
|
|
979
|
-
return sqliteReader.getSpaceCalendarNote(dateStr, resolvedSpace);
|
|
1049
|
+
return await sqliteReader.getSpaceCalendarNote(dateStr, resolvedSpace);
|
|
980
1050
|
}
|
|
981
|
-
|
|
1051
|
+
const note = await fileReader.getCalendarNote(dateStr);
|
|
1052
|
+
if (!note)
|
|
1053
|
+
return null;
|
|
1054
|
+
if (!isFolderAllowed(note.filename))
|
|
1055
|
+
return null;
|
|
1056
|
+
return note;
|
|
982
1057
|
}
|
|
983
1058
|
/**
|
|
984
1059
|
* Ensure a calendar note exists, create if not
|
|
985
1060
|
*/
|
|
986
|
-
export function ensureCalendarNote(date, space) {
|
|
1061
|
+
export async function ensureCalendarNote(date, space) {
|
|
987
1062
|
const dateStr = parseFlexibleDate(date);
|
|
988
|
-
const resolvedSpace = resolveSpaceId(space);
|
|
1063
|
+
const resolvedSpace = await resolveSpaceId(space);
|
|
989
1064
|
if (resolvedSpace) {
|
|
990
|
-
let note = sqliteReader.getSpaceCalendarNote(dateStr, resolvedSpace);
|
|
1065
|
+
let note = await sqliteReader.getSpaceCalendarNote(dateStr, resolvedSpace);
|
|
991
1066
|
if (!note) {
|
|
992
1067
|
sqliteWriter.createSpaceCalendarNote(resolvedSpace, dateStr, '');
|
|
993
|
-
note = sqliteReader.getSpaceCalendarNote(dateStr, resolvedSpace);
|
|
1068
|
+
note = await sqliteReader.getSpaceCalendarNote(dateStr, resolvedSpace);
|
|
994
1069
|
invalidateListingCaches();
|
|
995
1070
|
}
|
|
996
1071
|
if (!note)
|
|
997
1072
|
throw new Error('Failed to create space calendar note');
|
|
998
1073
|
return note;
|
|
999
1074
|
}
|
|
1000
|
-
const filename = fileWriter.ensureCalendarNote(dateStr);
|
|
1001
|
-
const note = fileReader.readNoteFile(filename);
|
|
1075
|
+
const filename = await fileWriter.ensureCalendarNote(dateStr);
|
|
1076
|
+
const note = await fileReader.readNoteFile(filename);
|
|
1002
1077
|
if (!note)
|
|
1003
1078
|
throw new Error('Failed to create calendar note');
|
|
1004
1079
|
invalidateListingCaches();
|
|
@@ -1007,8 +1082,8 @@ export function ensureCalendarNote(date, space) {
|
|
|
1007
1082
|
/**
|
|
1008
1083
|
* Add content to today's note
|
|
1009
1084
|
*/
|
|
1010
|
-
export function addToToday(content, position = 'end', space) {
|
|
1011
|
-
const note = ensureCalendarNote('today', space);
|
|
1085
|
+
export async function addToToday(content, position = 'end', space) {
|
|
1086
|
+
const note = await ensureCalendarNote('today', space);
|
|
1012
1087
|
let newContent;
|
|
1013
1088
|
if (position === 'start') {
|
|
1014
1089
|
const lines = note.content.split('\n');
|
|
@@ -1034,16 +1109,16 @@ export function addToToday(content, position = 'end', space) {
|
|
|
1034
1109
|
/**
|
|
1035
1110
|
* List all spaces
|
|
1036
1111
|
*/
|
|
1037
|
-
export function listSpaces() {
|
|
1038
|
-
return sqliteReader.listSpaces();
|
|
1112
|
+
export async function listSpaces() {
|
|
1113
|
+
return await sqliteReader.listSpaces();
|
|
1039
1114
|
}
|
|
1040
1115
|
// Keep old name for backwards compatibility
|
|
1041
1116
|
export const listTeamspaces = listSpaces;
|
|
1042
1117
|
/**
|
|
1043
1118
|
* List folders with optional source/depth/query filtering
|
|
1044
1119
|
*/
|
|
1045
|
-
export function listFolders(options = {}) {
|
|
1046
|
-
const resolvedSpace = resolveSpaceId(options.space);
|
|
1120
|
+
export async function listFolders(options = {}) {
|
|
1121
|
+
const resolvedSpace = await resolveSpaceId(options.space);
|
|
1047
1122
|
const { includeLocal = !resolvedSpace, includeSpaces = Boolean(resolvedSpace), query, maxDepth, parentPath, recursive = true, } = options;
|
|
1048
1123
|
const space = resolvedSpace;
|
|
1049
1124
|
const normalizedQuery = query?.trim().toLowerCase() || '';
|
|
@@ -1063,22 +1138,27 @@ export function listFolders(options = {}) {
|
|
|
1063
1138
|
}
|
|
1064
1139
|
const folders = [];
|
|
1065
1140
|
if (includeLocal) {
|
|
1066
|
-
folders.push(...fileReader.listFolders(maxDepth));
|
|
1141
|
+
folders.push(...(await fileReader.listFolders(maxDepth)));
|
|
1067
1142
|
}
|
|
1068
1143
|
if (includeSpaces) {
|
|
1069
|
-
folders.push(...sqliteReader.listSpaceFolders(space));
|
|
1144
|
+
folders.push(...await sqliteReader.listSpaceFolders(space));
|
|
1070
1145
|
}
|
|
1071
1146
|
const deduped = folders.filter((folder, index, arr) => {
|
|
1072
1147
|
const key = `${folder.source}:${folder.spaceId || ''}:${folder.path}`;
|
|
1073
1148
|
return arr.findIndex((candidate) => `${candidate.source}:${candidate.spaceId || ''}:${candidate.path}` === key) === index;
|
|
1074
1149
|
});
|
|
1150
|
+
const accessFiltered = deduped.filter((folder) => {
|
|
1151
|
+
if (folder.source !== 'local')
|
|
1152
|
+
return true;
|
|
1153
|
+
return isFolderAllowed(`Notes/${folder.path}`);
|
|
1154
|
+
});
|
|
1075
1155
|
let filtered = normalizedQuery
|
|
1076
|
-
?
|
|
1156
|
+
? accessFiltered.filter((folder) => {
|
|
1077
1157
|
const path = folder.path.toLowerCase();
|
|
1078
1158
|
const name = folder.name.toLowerCase();
|
|
1079
1159
|
return path.includes(normalizedQuery) || name.includes(normalizedQuery);
|
|
1080
1160
|
})
|
|
1081
|
-
:
|
|
1161
|
+
: accessFiltered;
|
|
1082
1162
|
if (normalizedParentPath) {
|
|
1083
1163
|
const parentPrefix = `${normalizedParentPath}/`;
|
|
1084
1164
|
filtered = filtered.filter((folder) => {
|
|
@@ -1097,21 +1177,21 @@ export function listFolders(options = {}) {
|
|
|
1097
1177
|
filtered.sort((a, b) => a.path.localeCompare(b.path));
|
|
1098
1178
|
return setCachedValue(listFoldersCache, cacheKey, filtered, LIST_FOLDERS_CACHE_TTL_MS);
|
|
1099
1179
|
}
|
|
1100
|
-
function resolveSpaceFolderReference(spaceId, reference, options = {}) {
|
|
1180
|
+
async function resolveSpaceFolderReference(spaceId, reference, options = {}) {
|
|
1101
1181
|
const normalized = reference.trim();
|
|
1102
1182
|
if (!normalized) {
|
|
1103
1183
|
throw new Error('Folder reference is required');
|
|
1104
1184
|
}
|
|
1105
1185
|
const lower = normalized.toLowerCase();
|
|
1106
1186
|
if (options.allowRoot === true && (lower === 'root' || lower === 'space-root' || normalized === spaceId)) {
|
|
1107
|
-
const space = sqliteReader.listSpaces().find((candidate) => candidate.id === spaceId);
|
|
1187
|
+
const space = (await sqliteReader.listSpaces()).find((candidate) => candidate.id === spaceId);
|
|
1108
1188
|
return {
|
|
1109
1189
|
id: spaceId,
|
|
1110
1190
|
path: space?.name || spaceId,
|
|
1111
1191
|
name: space?.name || spaceId,
|
|
1112
1192
|
};
|
|
1113
1193
|
}
|
|
1114
|
-
const folder = sqliteReader.resolveSpaceFolder(spaceId, normalized, {
|
|
1194
|
+
const folder = await sqliteReader.resolveSpaceFolder(spaceId, normalized, {
|
|
1115
1195
|
includeTrash: options.includeTrash === true,
|
|
1116
1196
|
});
|
|
1117
1197
|
if (!folder?.id) {
|
|
@@ -1123,9 +1203,9 @@ function resolveSpaceFolderReference(spaceId, reference, options = {}) {
|
|
|
1123
1203
|
name: folder.name,
|
|
1124
1204
|
};
|
|
1125
1205
|
}
|
|
1126
|
-
export function previewCreateFolder(options) {
|
|
1206
|
+
export async function previewCreateFolder(options) {
|
|
1127
1207
|
if ('space' in options) {
|
|
1128
|
-
const spaceId = resolveSpaceId(options.space.trim());
|
|
1208
|
+
const spaceId = await resolveSpaceId(options.space.trim());
|
|
1129
1209
|
if (!spaceId) {
|
|
1130
1210
|
throw new Error('space is required');
|
|
1131
1211
|
}
|
|
@@ -1135,8 +1215,8 @@ export function previewCreateFolder(options) {
|
|
|
1135
1215
|
}
|
|
1136
1216
|
const parent = options.parent?.trim();
|
|
1137
1217
|
const destination = parent
|
|
1138
|
-
? resolveSpaceFolderReference(spaceId, parent, { allowRoot: true, includeTrash: true })
|
|
1139
|
-
: resolveSpaceFolderReference(spaceId, spaceId, { allowRoot: true, includeTrash: true });
|
|
1218
|
+
? await resolveSpaceFolderReference(spaceId, parent, { allowRoot: true, includeTrash: true })
|
|
1219
|
+
: await resolveSpaceFolderReference(spaceId, spaceId, { allowRoot: true, includeTrash: true });
|
|
1140
1220
|
if (destination.name.toLowerCase() === '@trash') {
|
|
1141
1221
|
throw new Error('Destination parent cannot be @Trash');
|
|
1142
1222
|
}
|
|
@@ -1149,21 +1229,21 @@ export function previewCreateFolder(options) {
|
|
|
1149
1229
|
parentId: destination.id,
|
|
1150
1230
|
};
|
|
1151
1231
|
}
|
|
1152
|
-
const folder = fileWriter.previewCreateFolder(options.path);
|
|
1232
|
+
const folder = await fileWriter.previewCreateFolder(options.path);
|
|
1153
1233
|
return {
|
|
1154
1234
|
source: 'local',
|
|
1155
1235
|
path: folder,
|
|
1156
1236
|
name: path.basename(folder),
|
|
1157
1237
|
};
|
|
1158
1238
|
}
|
|
1159
|
-
export function createFolder(options) {
|
|
1160
|
-
const preview = previewCreateFolder(options);
|
|
1239
|
+
export async function createFolder(options) {
|
|
1240
|
+
const preview = await previewCreateFolder(options);
|
|
1161
1241
|
if ('space' in options) {
|
|
1162
1242
|
if (preview.source !== 'space') {
|
|
1163
1243
|
throw new Error('Invalid folder create state');
|
|
1164
1244
|
}
|
|
1165
1245
|
const createdId = sqliteWriter.createSpaceFolder(preview.spaceId, preview.name, preview.parentId);
|
|
1166
|
-
const createdFolder = resolveSpaceFolderReference(preview.spaceId, createdId, {
|
|
1246
|
+
const createdFolder = await resolveSpaceFolderReference(preview.spaceId, createdId, {
|
|
1167
1247
|
allowRoot: false,
|
|
1168
1248
|
includeTrash: true,
|
|
1169
1249
|
});
|
|
@@ -1177,7 +1257,7 @@ export function createFolder(options) {
|
|
|
1177
1257
|
parentId: preview.parentId,
|
|
1178
1258
|
};
|
|
1179
1259
|
}
|
|
1180
|
-
const createdPath = fileWriter.createFolder(options.path);
|
|
1260
|
+
const createdPath = await fileWriter.createFolder(options.path);
|
|
1181
1261
|
invalidateListingCaches();
|
|
1182
1262
|
return {
|
|
1183
1263
|
source: 'local',
|
|
@@ -1185,24 +1265,24 @@ export function createFolder(options) {
|
|
|
1185
1265
|
name: path.basename(createdPath),
|
|
1186
1266
|
};
|
|
1187
1267
|
}
|
|
1188
|
-
export function previewMoveFolder(options) {
|
|
1268
|
+
export async function previewMoveFolder(options) {
|
|
1189
1269
|
if ('space' in options) {
|
|
1190
|
-
const spaceId = resolveSpaceId(options.space.trim());
|
|
1270
|
+
const spaceId = await resolveSpaceId(options.space.trim());
|
|
1191
1271
|
if (!spaceId) {
|
|
1192
1272
|
throw new Error('space is required');
|
|
1193
1273
|
}
|
|
1194
|
-
const source = resolveSpaceFolderReference(spaceId, options.source, {
|
|
1274
|
+
const source = await resolveSpaceFolderReference(spaceId, options.source, {
|
|
1195
1275
|
allowRoot: false,
|
|
1196
1276
|
includeTrash: true,
|
|
1197
1277
|
});
|
|
1198
|
-
const destination = resolveSpaceFolderReference(spaceId, options.destination, {
|
|
1278
|
+
const destination = await resolveSpaceFolderReference(spaceId, options.destination, {
|
|
1199
1279
|
allowRoot: true,
|
|
1200
1280
|
includeTrash: true,
|
|
1201
1281
|
});
|
|
1202
1282
|
if (destination.name.toLowerCase() === '@trash') {
|
|
1203
1283
|
throw new Error('Destination folder cannot be @Trash');
|
|
1204
1284
|
}
|
|
1205
|
-
const counts = sqliteReader.countSpaceFolderContents(source.id);
|
|
1285
|
+
const counts = await sqliteReader.countSpaceFolderContents(source.id);
|
|
1206
1286
|
return {
|
|
1207
1287
|
source: 'space',
|
|
1208
1288
|
spaceId,
|
|
@@ -1214,9 +1294,9 @@ export function previewMoveFolder(options) {
|
|
|
1214
1294
|
affectedFolderCount: counts.folderCount,
|
|
1215
1295
|
};
|
|
1216
1296
|
}
|
|
1217
|
-
const preview = fileWriter.previewMoveLocalFolder(options.sourcePath, options.destinationFolder);
|
|
1297
|
+
const preview = await fileWriter.previewMoveLocalFolder(options.sourcePath, options.destinationFolder);
|
|
1218
1298
|
const fullPath = path.join(fileReader.getNotesPath(), preview.fromFolder);
|
|
1219
|
-
const counts = fileReader.countNotesInDirectory(fullPath);
|
|
1299
|
+
const counts = await fileReader.countNotesInDirectory(fullPath);
|
|
1220
1300
|
return {
|
|
1221
1301
|
source: 'local',
|
|
1222
1302
|
fromPath: preview.fromFolder,
|
|
@@ -1226,14 +1306,14 @@ export function previewMoveFolder(options) {
|
|
|
1226
1306
|
affectedFolderCount: counts.folderCount,
|
|
1227
1307
|
};
|
|
1228
1308
|
}
|
|
1229
|
-
export function moveFolder(options) {
|
|
1230
|
-
const preview = previewMoveFolder(options);
|
|
1309
|
+
export async function moveFolder(options) {
|
|
1310
|
+
const preview = await previewMoveFolder(options);
|
|
1231
1311
|
if ('space' in options) {
|
|
1232
1312
|
if (preview.source !== 'space') {
|
|
1233
1313
|
throw new Error('Invalid folder move state');
|
|
1234
1314
|
}
|
|
1235
1315
|
sqliteWriter.moveSpaceFolder(preview.folderId, preview.destinationParentId);
|
|
1236
|
-
const moved = resolveSpaceFolderReference(preview.spaceId, preview.folderId, {
|
|
1316
|
+
const moved = await resolveSpaceFolderReference(preview.spaceId, preview.folderId, {
|
|
1237
1317
|
allowRoot: false,
|
|
1238
1318
|
includeTrash: true,
|
|
1239
1319
|
});
|
|
@@ -1243,7 +1323,7 @@ export function moveFolder(options) {
|
|
|
1243
1323
|
toPath: moved.path,
|
|
1244
1324
|
};
|
|
1245
1325
|
}
|
|
1246
|
-
const moved = fileWriter.moveLocalFolder(options.sourcePath, options.destinationFolder);
|
|
1326
|
+
const moved = await fileWriter.moveLocalFolder(options.sourcePath, options.destinationFolder);
|
|
1247
1327
|
invalidateListingCaches();
|
|
1248
1328
|
return {
|
|
1249
1329
|
source: 'local',
|
|
@@ -1252,17 +1332,17 @@ export function moveFolder(options) {
|
|
|
1252
1332
|
destinationFolder: moved.destinationFolder || options.destinationFolder,
|
|
1253
1333
|
};
|
|
1254
1334
|
}
|
|
1255
|
-
export function previewDeleteFolder(options) {
|
|
1335
|
+
export async function previewDeleteFolder(options) {
|
|
1256
1336
|
if ('space' in options) {
|
|
1257
|
-
const spaceId = resolveSpaceId(options.space.trim());
|
|
1337
|
+
const spaceId = await resolveSpaceId(options.space.trim());
|
|
1258
1338
|
if (!spaceId) {
|
|
1259
1339
|
throw new Error('space is required');
|
|
1260
1340
|
}
|
|
1261
|
-
const source = resolveSpaceFolderReference(spaceId, options.source, {
|
|
1341
|
+
const source = await resolveSpaceFolderReference(spaceId, options.source, {
|
|
1262
1342
|
allowRoot: false,
|
|
1263
1343
|
includeTrash: true,
|
|
1264
1344
|
});
|
|
1265
|
-
const counts = sqliteReader.countSpaceFolderContents(source.id);
|
|
1345
|
+
const counts = await sqliteReader.countSpaceFolderContents(source.id);
|
|
1266
1346
|
return {
|
|
1267
1347
|
source: 'space',
|
|
1268
1348
|
spaceId,
|
|
@@ -1273,9 +1353,9 @@ export function previewDeleteFolder(options) {
|
|
|
1273
1353
|
affectedFolderCount: counts.folderCount,
|
|
1274
1354
|
};
|
|
1275
1355
|
}
|
|
1276
|
-
const normalized = fileWriter.previewDeleteLocalFolder(options.path);
|
|
1356
|
+
const normalized = await fileWriter.previewDeleteLocalFolder(options.path);
|
|
1277
1357
|
const fullPath = path.join(fileReader.getNotesPath(), normalized);
|
|
1278
|
-
const counts = fileReader.countNotesInDirectory(fullPath);
|
|
1358
|
+
const counts = await fileReader.countNotesInDirectory(fullPath);
|
|
1279
1359
|
return {
|
|
1280
1360
|
source: 'local',
|
|
1281
1361
|
fromPath: normalized,
|
|
@@ -1284,8 +1364,8 @@ export function previewDeleteFolder(options) {
|
|
|
1284
1364
|
affectedFolderCount: counts.folderCount,
|
|
1285
1365
|
};
|
|
1286
1366
|
}
|
|
1287
|
-
export function deleteFolder(options) {
|
|
1288
|
-
const preview = previewDeleteFolder(options);
|
|
1367
|
+
export async function deleteFolder(options) {
|
|
1368
|
+
const preview = await previewDeleteFolder(options);
|
|
1289
1369
|
if ('space' in options) {
|
|
1290
1370
|
if (preview.source !== 'space') {
|
|
1291
1371
|
throw new Error('Invalid folder delete state');
|
|
@@ -1300,7 +1380,7 @@ export function deleteFolder(options) {
|
|
|
1300
1380
|
trashFolderId: deleted.trashFolderId,
|
|
1301
1381
|
};
|
|
1302
1382
|
}
|
|
1303
|
-
const trashedPath = fileWriter.deleteLocalFolder(options.path);
|
|
1383
|
+
const trashedPath = await fileWriter.deleteLocalFolder(options.path);
|
|
1304
1384
|
invalidateListingCaches();
|
|
1305
1385
|
return {
|
|
1306
1386
|
source: 'local',
|
|
@@ -1308,13 +1388,13 @@ export function deleteFolder(options) {
|
|
|
1308
1388
|
trashedPath,
|
|
1309
1389
|
};
|
|
1310
1390
|
}
|
|
1311
|
-
export function previewRenameFolder(options) {
|
|
1391
|
+
export async function previewRenameFolder(options) {
|
|
1312
1392
|
if ('space' in options) {
|
|
1313
|
-
const spaceId = resolveSpaceId(options.space.trim());
|
|
1393
|
+
const spaceId = await resolveSpaceId(options.space.trim());
|
|
1314
1394
|
if (!spaceId) {
|
|
1315
1395
|
throw new Error('space is required');
|
|
1316
1396
|
}
|
|
1317
|
-
const source = resolveSpaceFolderReference(spaceId, options.source, {
|
|
1397
|
+
const source = await resolveSpaceFolderReference(spaceId, options.source, {
|
|
1318
1398
|
allowRoot: false,
|
|
1319
1399
|
includeTrash: true,
|
|
1320
1400
|
});
|
|
@@ -1334,21 +1414,21 @@ export function previewRenameFolder(options) {
|
|
|
1334
1414
|
name: newName,
|
|
1335
1415
|
};
|
|
1336
1416
|
}
|
|
1337
|
-
const preview = fileWriter.previewRenameLocalFolder(options.sourcePath, options.newName);
|
|
1417
|
+
const preview = await fileWriter.previewRenameLocalFolder(options.sourcePath, options.newName);
|
|
1338
1418
|
return {
|
|
1339
1419
|
source: 'local',
|
|
1340
1420
|
fromPath: preview.fromFolder,
|
|
1341
1421
|
toPath: preview.toFolder,
|
|
1342
1422
|
};
|
|
1343
1423
|
}
|
|
1344
|
-
export function renameFolder(options) {
|
|
1345
|
-
const preview = previewRenameFolder(options);
|
|
1424
|
+
export async function renameFolder(options) {
|
|
1425
|
+
const preview = await previewRenameFolder(options);
|
|
1346
1426
|
if ('space' in options) {
|
|
1347
1427
|
if (preview.source !== 'space') {
|
|
1348
1428
|
throw new Error('Invalid folder rename state');
|
|
1349
1429
|
}
|
|
1350
1430
|
const renamed = sqliteWriter.renameSpaceFolder(preview.folderId, preview.name);
|
|
1351
|
-
const folder = resolveSpaceFolderReference(preview.spaceId, renamed.folderId, {
|
|
1431
|
+
const folder = await resolveSpaceFolderReference(preview.spaceId, renamed.folderId, {
|
|
1352
1432
|
allowRoot: false,
|
|
1353
1433
|
includeTrash: true,
|
|
1354
1434
|
});
|
|
@@ -1358,7 +1438,7 @@ export function renameFolder(options) {
|
|
|
1358
1438
|
toPath: folder.path,
|
|
1359
1439
|
};
|
|
1360
1440
|
}
|
|
1361
|
-
const renamed = fileWriter.renameLocalFolder(options.sourcePath, options.newName);
|
|
1441
|
+
const renamed = await fileWriter.renameLocalFolder(options.sourcePath, options.newName);
|
|
1362
1442
|
invalidateListingCaches();
|
|
1363
1443
|
return {
|
|
1364
1444
|
source: 'local',
|
|
@@ -1369,13 +1449,31 @@ export function renameFolder(options) {
|
|
|
1369
1449
|
/**
|
|
1370
1450
|
* List all tags
|
|
1371
1451
|
*/
|
|
1372
|
-
export function listTags(space) {
|
|
1373
|
-
const resolvedSpace = resolveSpaceId(space);
|
|
1452
|
+
export async function listTags(space) {
|
|
1453
|
+
const resolvedSpace = await resolveSpaceId(space);
|
|
1454
|
+
const cacheKey = resolvedSpace ?? '__all__';
|
|
1455
|
+
const cached = getCachedValue(listTagsCache, cacheKey);
|
|
1456
|
+
if (cached)
|
|
1457
|
+
return cached;
|
|
1374
1458
|
const tags = new Set();
|
|
1375
1459
|
if (!resolvedSpace) {
|
|
1376
|
-
|
|
1460
|
+
if (hasFolderAccessRules()) {
|
|
1461
|
+
// The fast paths (bridge tags endpoint, ripgrep) scan everything on
|
|
1462
|
+
// disk and can't tell us which file a tag came from — so they would
|
|
1463
|
+
// surface tag names that exist only inside a denied folder. Fall
|
|
1464
|
+
// back to iterating filtered notes so the rules apply.
|
|
1465
|
+
const notes = await listNotes();
|
|
1466
|
+
for (const note of notes) {
|
|
1467
|
+
for (const tag of extractTagsFromContent(note.content)) {
|
|
1468
|
+
tags.add(tag);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
else {
|
|
1473
|
+
(await fileReader.extractAllTags()).forEach((tag) => tags.add(tag));
|
|
1474
|
+
}
|
|
1377
1475
|
}
|
|
1378
|
-
sqliteReader.extractSpaceTags(resolvedSpace).forEach((tag) => tags.add(tag));
|
|
1379
|
-
return Array.from(tags).sort();
|
|
1476
|
+
(await sqliteReader.extractSpaceTags(resolvedSpace)).forEach((tag) => tags.add(tag));
|
|
1477
|
+
return setCachedValue(listTagsCache, cacheKey, Array.from(tags).sort(), LIST_TAGS_CACHE_TTL_MS);
|
|
1380
1478
|
}
|
|
1381
1479
|
//# sourceMappingURL=unified-store.js.map
|