orgnote-api 0.41.15 → 0.41.17

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/api.d.ts CHANGED
@@ -27,9 +27,22 @@ import { CronStoreDefinition } from './models/cron-store.js';
27
27
  import { GitStoreDefinition } from './models/git-store.js';
28
28
  import { ExtensionRegistryStoreDefinition } from './models/extension-registry-store.js';
29
29
  import { parseToml, stringifyToml } from './utils/index.js';
30
+ /** @internal */
30
31
  type WithNodeType<T> = {
31
32
  nodeType: NodeType;
32
33
  } & T;
34
+ /**
35
+ * Discriminated union describing an editor widget registration.
36
+ *
37
+ * Use {@link WidgetType} to choose the variant:
38
+ * - `Inline` — decorates a single inline node (mark, replace, or widget decoration).
39
+ * - `Multiline` — replaces an entire block node with a custom view.
40
+ * - `LineClass` — attaches a CSS class to a line containing the target node.
41
+ *
42
+ * @see {@link InlineEmbeddedWidget}
43
+ * @see {@link MultilineEmbeddedWidget}
44
+ * @see {@link OrgLineClass}
45
+ */
33
46
  export type WidgetMeta = ({
34
47
  type: WidgetType.Inline;
35
48
  } & WithNodeType<InlineEmbeddedWidget>) | ({
@@ -37,41 +50,228 @@ export type WidgetMeta = ({
37
50
  } & WithNodeType<MultilineEmbeddedWidget>) | ({
38
51
  type: WidgetType.LineClass;
39
52
  } & WithNodeType<OrgLineClass>);
53
+ /**
54
+ * Low-level infrastructure layer: data repositories and real-time WebSocket connection.
55
+ *
56
+ * Prefer higher-level stores from {@link OrgNoteApi.core} unless you need
57
+ * direct database or socket access.
58
+ */
40
59
  export type Infrastructure = Repositories & {
41
60
  websocket: WebSocketClient;
42
61
  };
62
+ /**
63
+ * Main API facade provided to every OrgNote extension.
64
+ *
65
+ * Received as the sole argument of the {@link Extension.onMounted} lifecycle hook.
66
+ * Groups all available services into four namespaces:
67
+ * - `core` — application stores (commands, files, encryption, sync, buffers)
68
+ * - `utils` — platform helpers, CSS manipulation, clipboard, logging, parsers
69
+ * - `ui` — modals, sidebars, themes, fonts, screen detection
70
+ * - `vue` — Vue Router instance
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * import type { Extension, OrgNoteApi } from 'orgnote-api';
75
+ *
76
+ * const extension: Extension = {
77
+ * async onMounted(api: OrgNoteApi) {
78
+ * const commands = api.core.useCommands();
79
+ * commands.add({
80
+ * command: 'hello-world',
81
+ * title: 'Hello World',
82
+ * handler: () => {
83
+ * const notifications = api.core.useNotifications();
84
+ * notifications.notify({ message: 'Hello from extension!' });
85
+ * },
86
+ * });
87
+ * },
88
+ *
89
+ * async onUnmounted(api: OrgNoteApi) {
90
+ * // cleanup is automatic for commands registered via `add`
91
+ * },
92
+ * };
93
+ *
94
+ * export default extension;
95
+ * ```
96
+ */
43
97
  export interface OrgNoteApi {
44
98
  [key: string]: unknown;
99
+ /**
100
+ * Low-level infrastructure: data repositories and WebSocket client.
101
+ *
102
+ * Provides direct access to persistence layer ({@link Repositories}) and
103
+ * the real-time {@link WebSocketClient}. For most use cases prefer
104
+ * the higher-level stores in {@link OrgNoteApi.core}.
105
+ */
45
106
  infrastructure: Infrastructure;
107
+ /**
108
+ * Core application stores — the primary way extensions interact with OrgNote.
109
+ *
110
+ * Each property is a [Pinia](https://pinia.vuejs.org/) store factory.
111
+ * Call it to obtain a reactive store instance:
112
+ * ```typescript
113
+ * const commands = api.core.useCommands();
114
+ * const fs = api.core.useFileSystem();
115
+ * ```
116
+ */
46
117
  core: {
118
+ /**
119
+ * Register, remove, and execute commands (keyboard shortcuts, command palette actions).
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const commands = api.core.useCommands();
124
+ * commands.add({
125
+ * command: 'my-ext.greet',
126
+ * title: 'Greet User',
127
+ * handler: () => console.log('Hello!'),
128
+ * });
129
+ * await commands.execute('my-ext.greet');
130
+ * ```
131
+ */
47
132
  useCommands: CommandsStoreDefinition;
133
+ /**
134
+ * Manage command groups — logical collections of commands
135
+ * displayed together in the UI (e.g. "Editor", "Navigation").
136
+ */
48
137
  useCommandsGroup: CommandsGroupStoreDefinition;
138
+ /**
139
+ * Install, enable, disable, and delete extensions at runtime.
140
+ * Also provides the list of currently loaded extensions.
141
+ */
49
142
  useExtensions: ExtensionStoreDefinition;
143
+ /**
144
+ * Virtual file system abstraction (read, write, delete, mkdir, etc.).
145
+ * Works across platforms via [Capacitor Filesystem](https://capacitorjs.com/docs/apis/filesystem) under the hood.
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const fs = api.core.useFileSystem();
150
+ * const content = await fs.readFile(['notes', 'todo.org'], 'utf8');
151
+ * await fs.writeFile(['notes', 'new.org'], '* New note');
152
+ * ```
153
+ */
50
154
  useFileSystem: FileSystemStoreDefinition;
51
155
  useFileWatcher: FileWatcherStoreDefinition;
156
+ /**
157
+ * Encrypt and decrypt text content using [OpenPGP.js](https://openpgpjs.org/).
158
+ * Used internally for end-to-end encrypted notes.
159
+ */
52
160
  useEncryption: EncryptionStoreDefinition;
161
+ /**
162
+ * Read and manage user-facing application settings.
163
+ * Also handles API token lifecycle (create, list, revoke).
164
+ */
53
165
  useSettings: SettingsStoreDefinition;
54
166
  useConfig: ConfigStoreDefinition;
167
+ /**
168
+ * Access the [Quasar](https://quasar.dev/) framework globals ($q).
169
+ * Useful for Quasar-specific features like `$q.dark`, `$q.screen`, etc.
170
+ */
55
171
  useQuasar: () => QVueGlobals;
172
+ /**
173
+ * Open a searchable completion menu (command palette style).
174
+ * Supports custom item lists, filtering, and keyboard navigation.
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * const completion = api.core.useCompletion();
179
+ * const selected = await completion.open({
180
+ * items: ['Apple', 'Banana', 'Cherry'],
181
+ * itemLabel: (item) => item,
182
+ * });
183
+ * ```
184
+ */
56
185
  useCompletion: CompletionStoreDefinition;
57
186
  usePane: PaneStoreDefinition;
187
+ /**
188
+ * Manage layout tree structure (split, merge, resize panes).
189
+ * This is the primary coordinator for all pane-related operations.
190
+ */
58
191
  useLayout: LayoutStoreDefinition;
192
+ /**
193
+ * High-level file system manager that orchestrates file operations
194
+ * with metadata, indexing, and synchronization awareness.
195
+ */
59
196
  useFileSystemManager: FileSystemManagerStoreDefinition;
197
+ /**
198
+ * File management operations: open, create, rename, delete notes
199
+ * with full UI integration (buffer creation, navigation).
200
+ */
60
201
  useFileManager: FileManagerStoreDefinition;
61
202
  useBufferViewer: BufferViewerStoreDefinition;
203
+ /**
204
+ * Show toast and persistent notifications to the user.
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * const notifications = api.core.useNotifications();
209
+ * notifications.notify({
210
+ * message: 'Note saved successfully',
211
+ * type: 'positive',
212
+ * });
213
+ * ```
214
+ */
62
215
  useNotifications: NotificationsStoreDefinition;
216
+ /**
217
+ * Manage open buffers (documents loaded in memory).
218
+ * Buffers are identified by URI and support dirty-state tracking.
219
+ */
63
220
  useBuffers: BufferStoreDefinition;
221
+ /**
222
+ * Register and resolve buffer content providers.
223
+ * Providers define how different URI schemes load their content.
224
+ */
64
225
  useBufferProviders: BufferProviderStoreDefinition;
65
226
  useSystemInfo: UseSystemInfo;
66
227
  useLog: LogStoreDefinition;
228
+ /**
229
+ * Background task queue with concurrency control, locking, and retry.
230
+ * Used for long-running operations like sync and bulk file processing.
231
+ */
67
232
  useQueue: QueueStoreDefinition;
233
+ /**
234
+ * Schedule recurring tasks (cron-like).
235
+ * Tasks persist across sessions and run in the background.
236
+ */
68
237
  useCron: CronStoreDefinition;
69
238
  useGit: GitStoreDefinition;
239
+ /**
240
+ * Browse and install extensions from the official OrgNote extension registry.
241
+ */
70
242
  useExtensionRegistry: ExtensionRegistryStoreDefinition;
243
+ /**
244
+ * Guard files with validation rules before allowing operations.
245
+ * Prevents accidental overwrites and enforces naming conventions.
246
+ */
71
247
  useFileGuard: FileGuardStoreDefinition;
72
248
  useAuth: AuthStoreDefinition;
249
+ /**
250
+ * Bidirectional note synchronization between local storage and remote server.
251
+ * Creates a sync plan (diff), then executes upload/download/delete operations.
252
+ */
73
253
  useSync: SyncStoreDefinition;
254
+ /**
255
+ * Org-mode editor state: registered widgets, [CodeMirror](https://codemirror.net/) extensions,
256
+ * active editor context (selection, cursor position).
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * const editor = api.core.useEditor();
261
+ * editor.addWidgets({
262
+ * type: WidgetType.Inline,
263
+ * nodeType: 'link',
264
+ * id: 'my-link-widget',
265
+ * decorationType: 'replace',
266
+ * widgetBuilder: (params) => { ... },
267
+ * });
268
+ * ```
269
+ */
74
270
  useEditor: EditorStoreDefinition;
271
+ /**
272
+ * Babel-style code transformation store.
273
+ * Used for transpiling extension source code at runtime.
274
+ */
75
275
  useBabel: BabelStoreDefinition;
76
276
  useFileSearch: FileSearchStoreDefinition;
77
277
  useFileMeta: FileMetaStoreDefinition;
@@ -79,8 +279,36 @@ export interface OrgNoteApi {
79
279
  useFileContent: UseFileContent;
80
280
  app: App;
81
281
  };
282
+ /**
283
+ * Utility functions available to extensions.
284
+ *
285
+ * Includes platform detection, CSS manipulation, clipboard access,
286
+ * file upload dialogs, logging, TOML parsing, and URL building.
287
+ */
82
288
  utils: {
289
+ /**
290
+ * Detect the current platform (mobile, desktop, electron, browser, OS).
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * if (api.utils.platform.is.mobile) {
295
+ * // mobile-specific logic
296
+ * }
297
+ * ```
298
+ */
83
299
  platform: PlatformDetection;
300
+ /**
301
+ * Execute platform-specific code branches with a single call.
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * const result = await api.utils.platformMatch({
306
+ * mobile: () => 'compact layout',
307
+ * desktop: () => 'full layout',
308
+ * default: () => 'fallback layout',
309
+ * });
310
+ * ```
311
+ */
84
312
  platformMatch: PlatformMatch;
85
313
  clientOnly: PlatformSpecificFn;
86
314
  mobileOnly: PlatformSpecificFn;
@@ -92,18 +320,56 @@ export interface OrgNoteApi {
92
320
  getNumericCssVar: GetNumericCssVar;
93
321
  getCssProperty: GetCssProperty;
94
322
  getCssNumericProperty: GetCssNumericProperty;
323
+ /**
324
+ * Apply CSS custom properties to the document root.
325
+ * Commonly used by theme extensions to override colors.
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * api.utils.applyCSSVariables({
330
+ * '--primary': '#ff6600',
331
+ * '--bg': '#1a1a2e',
332
+ * });
333
+ * ```
334
+ */
95
335
  applyCSSVariables: ApplyCSSVariables<string>;
96
336
  resetCSSVariables: ResetCSSVariables<string>;
337
+ /**
338
+ * Inject a scoped `<style>` block identified by a unique scope name.
339
+ * Calling again with the same scope name replaces the previous styles.
340
+ */
97
341
  applyScopedStyles: ApplyScopedStyles;
98
342
  removeScopedStyles: RemoveScopedStyles;
99
343
  copyToClipboard: (text: string) => Promise<void>;
100
344
  uploadFiles: (params: MultipleUploadParams) => Promise<FileList>;
101
345
  uploadFile: (params?: UploadParams) => Promise<File | undefined>;
346
+ /**
347
+ * Structured logger scoped to the extension.
348
+ * Messages are persisted and visible in the OrgNote log viewer.
349
+ */
102
350
  logger: Logger;
351
+ /**
352
+ * Parse a [TOML](https://toml.io/) string into a JavaScript object.
353
+ * Used for reading `.toml` configuration files.
354
+ */
103
355
  parseToml: typeof parseToml;
104
356
  stringifyToml: typeof stringifyToml;
357
+ /**
358
+ * Build an OrgNote deep-link URL (for native app or web).
359
+ *
360
+ * @example
361
+ * ```typescript
362
+ * const url = api.utils.buildOrgNoteUrl('/notes/abc123', {
363
+ * target: 'native-app',
364
+ * });
365
+ * ```
366
+ */
105
367
  buildOrgNoteUrl: BuildOrgNoteUrl;
106
368
  };
369
+ /**
370
+ * UI-related stores for controlling visual elements: modals, sidebars,
371
+ * themes, fonts, context menus, and screen/keyboard state.
372
+ */
107
373
  ui: {
108
374
  useFonts: FontStoreDefinition;
109
375
  useSplashScreen: UseSplashScreen;
@@ -111,15 +377,55 @@ export interface OrgNoteApi {
111
377
  useSidebar: SidebarStoreDefinition;
112
378
  useRightSidebar: RightSidebarStoreDefinition;
113
379
  usePinnedCommands: PinnedCommandsStoreDefinition;
380
+ /**
381
+ * Open modal dialogs with custom Vue components.
382
+ *
383
+ * @example
384
+ * ```typescript
385
+ * const modal = api.ui.useModal();
386
+ * const result = await modal.open(MyDialogComponent, {
387
+ * title: 'Confirm action',
388
+ * });
389
+ * ```
390
+ */
114
391
  useModal: ModalStoreDefinition;
115
392
  useSettingsUi: SettingsUiStoreDefinition;
393
+ /**
394
+ * Show a confirmation dialog and await the user's response.
395
+ *
396
+ * @example
397
+ * ```typescript
398
+ * const { confirm } = api.ui.useConfirmationModal();
399
+ * const confirmed = await confirm({
400
+ * title: 'Delete note?',
401
+ * message: 'This action cannot be undone.',
402
+ * });
403
+ * if (confirmed) { ... }
404
+ * ```
405
+ */
116
406
  useConfirmationModal: UseConfirmationModal;
117
407
  useScreenDetection: UseScreenDetection;
118
408
  useKeyboardState: UseKeyboardState;
119
409
  useContextMenu: ContextMenuStoreDefinition;
410
+ /**
411
+ * Manage the active color theme.
412
+ * Theme extensions use this store to register and activate their themes.
413
+ */
120
414
  useTheme: ThemeStoreDefinition;
121
415
  };
416
+ /**
417
+ * Direct access to the Vue ecosystem.
418
+ */
122
419
  vue: {
420
+ /**
421
+ * The [Vue Router](https://router.vuejs.org/) instance.
422
+ * Use for programmatic navigation within the OrgNote application.
423
+ *
424
+ * @example
425
+ * ```typescript
426
+ * api.vue.router.push({ name: 'note', params: { id: 'abc123' } });
427
+ * ```
428
+ */
123
429
  router: Router;
124
430
  };
125
431
  }
@@ -0,0 +1,2 @@
1
+ import { FileSortConfig } from '../models/file-sort.js';
2
+ export declare const DEFAULT_FILE_SORT_CONFIG: FileSortConfig;
@@ -0,0 +1,5 @@
1
+ export const DEFAULT_FILE_SORT_CONFIG = {
2
+ field: 'name',
3
+ direction: 'asc',
4
+ directoriesFirst: true,
5
+ };
@@ -76,6 +76,12 @@ export declare enum i18n {
76
76
  DELETE = "delete",
77
77
  RENAME = "rename",
78
78
  COPY = "copy",
79
+ MOVE = "move",
80
+ SORT_BY_NAME = "sort by name",
81
+ SORT_BY_MODIFIED = "sort by modified",
82
+ SORT_BY_SIZE = "sort by size",
83
+ SORT_ASCENDING = "ascending",
84
+ SORT_DESCENDING = "descending",
79
85
  CREATE_FILE = "create file",
80
86
  FILE_NAME = "file name",
81
87
  DIR_NAME = "directory name",
@@ -236,6 +242,14 @@ export declare const I18N: {
236
242
  CREATE_FILE: DefaultCommands.CREATE_FILE;
237
243
  RENAME_FILE: DefaultCommands.RENAME_FILE;
238
244
  DELETE_FILE: DefaultCommands.DELETE_FILE;
245
+ COPY_FILE: DefaultCommands.COPY_FILE;
246
+ MOVE_FILE: DefaultCommands.MOVE_FILE;
247
+ SELECT_FILE: DefaultCommands.SELECT_FILE;
248
+ SELECT_ALL_FILES: DefaultCommands.SELECT_ALL_FILES;
249
+ DESELECT_ALL_FILES: DefaultCommands.DESELECT_ALL_FILES;
250
+ EXECUTE_PENDING_FILE_OPERATION: DefaultCommands.EXECUTE_PENDING_FILE_OPERATION;
251
+ CANCEL_PENDING_FILE_OPERATION: DefaultCommands.CANCEL_PENDING_FILE_OPERATION;
252
+ SORT_FILES: DefaultCommands.SORT_FILES;
239
253
  CONFIRM_FILE_DELETION: DefaultCommands.CONFIRM_FILE_DELETION;
240
254
  NEW_FILE_PATH: DefaultCommands.NEW_FILE_PATH;
241
255
  SHOW_MOBILE_FILE_SEARCH: DefaultCommands.SHOW_MOBILE_FILE_SEARCH;
@@ -357,6 +371,12 @@ export declare const I18N: {
357
371
  DELETE: i18n.DELETE;
358
372
  RENAME: i18n.RENAME;
359
373
  COPY: i18n.COPY;
374
+ MOVE: i18n.MOVE;
375
+ SORT_BY_NAME: i18n.SORT_BY_NAME;
376
+ SORT_BY_MODIFIED: i18n.SORT_BY_MODIFIED;
377
+ SORT_BY_SIZE: i18n.SORT_BY_SIZE;
378
+ SORT_ASCENDING: i18n.SORT_ASCENDING;
379
+ SORT_DESCENDING: i18n.SORT_DESCENDING;
360
380
  FILE_NAME: i18n.FILE_NAME;
361
381
  DIR_NAME: i18n.DIR_NAME;
362
382
  FINISH_SETUP: i18n.FINISH_SETUP;
@@ -77,6 +77,12 @@ export var i18n;
77
77
  i18n["DELETE"] = "delete";
78
78
  i18n["RENAME"] = "rename";
79
79
  i18n["COPY"] = "copy";
80
+ i18n["MOVE"] = "move";
81
+ i18n["SORT_BY_NAME"] = "sort by name";
82
+ i18n["SORT_BY_MODIFIED"] = "sort by modified";
83
+ i18n["SORT_BY_SIZE"] = "sort by size";
84
+ i18n["SORT_ASCENDING"] = "ascending";
85
+ i18n["SORT_DESCENDING"] = "descending";
80
86
  i18n["CREATE_FILE"] = "create file";
81
87
  i18n["FILE_NAME"] = "file name";
82
88
  i18n["DIR_NAME"] = "directory name";
@@ -7,3 +7,4 @@ export * from './git-errors.js';
7
7
  export * from './extension-errors.js';
8
8
  export * from './file-guard-errors.js';
9
9
  export * from './oauth-providers.js';
10
+ export * from './file-sort-defaults.js';
@@ -7,3 +7,4 @@ export * from "./git-errors.js";
7
7
  export * from "./extension-errors.js";
8
8
  export * from "./file-guard-errors.js";
9
9
  export * from "./oauth-providers.js";
10
+ export * from "./file-sort-defaults.js";
@@ -67,6 +67,14 @@ export declare enum DefaultCommands {
67
67
  CREATE_FILE = "create file",
68
68
  RENAME_FILE = "rename file",
69
69
  DELETE_FILE = "delete file",
70
+ COPY_FILE = "copy file",
71
+ MOVE_FILE = "move file",
72
+ SELECT_FILE = "select file",
73
+ SELECT_ALL_FILES = "select all files",
74
+ DESELECT_ALL_FILES = "deselect all files",
75
+ EXECUTE_PENDING_FILE_OPERATION = "paste files here",
76
+ CANCEL_PENDING_FILE_OPERATION = "cancel file operation",
77
+ SORT_FILES = "sort files",
70
78
  CONFIRM_FILE_DELETION = "are you sure you want to delete file?",
71
79
  NEW_FILE_PATH = "new file path",
72
80
  SHOW_MOBILE_FILE_SEARCH = "show mobile file search",
@@ -82,6 +82,14 @@ export var DefaultCommands;
82
82
  DefaultCommands["CREATE_FILE"] = "create file";
83
83
  DefaultCommands["RENAME_FILE"] = "rename file";
84
84
  DefaultCommands["DELETE_FILE"] = "delete file";
85
+ DefaultCommands["COPY_FILE"] = "copy file";
86
+ DefaultCommands["MOVE_FILE"] = "move file";
87
+ DefaultCommands["SELECT_FILE"] = "select file";
88
+ DefaultCommands["SELECT_ALL_FILES"] = "select all files";
89
+ DefaultCommands["DESELECT_ALL_FILES"] = "deselect all files";
90
+ DefaultCommands["EXECUTE_PENDING_FILE_OPERATION"] = "paste files here";
91
+ DefaultCommands["CANCEL_PENDING_FILE_OPERATION"] = "cancel file operation";
92
+ DefaultCommands["SORT_FILES"] = "sort files";
85
93
  DefaultCommands["CONFIRM_FILE_DELETION"] = "are you sure you want to delete file?";
86
94
  DefaultCommands["NEW_FILE_PATH"] = "new file path";
87
95
  DefaultCommands["SHOW_MOBILE_FILE_SEARCH"] = "show mobile file search";
@@ -1,13 +1,35 @@
1
1
  import { StoreDefinition } from './store.js';
2
- import { Ref, ShallowRef } from 'vue';
2
+ import { ComputedRef, Ref, ShallowRef } from 'vue';
3
3
  import { DiskFile } from './file-system.js';
4
+ import { FileSortConfig } from './file-sort.js';
5
+ export type PendingFileOperation = {
6
+ type: 'copy' | 'move';
7
+ paths: string[];
8
+ };
4
9
  export interface FileManagerStore {
5
10
  path: Ref<string>;
6
11
  focusFile: ShallowRef<DiskFile | undefined>;
7
12
  focusDirPath: Ref<string>;
8
13
  searchQuery: Ref<string>;
9
14
  mobileFileSearchActive: Ref<boolean>;
10
- deleteFile: (path?: string) => Promise<void>;
15
+ files: Ref<DiskFile[]>;
16
+ sortConfig: Ref<FileSortConfig>;
17
+ sortedFiles: ComputedRef<DiskFile[]>;
18
+ loadFiles: () => Promise<void>;
19
+ selectionMode: ComputedRef<boolean>;
20
+ selectedFiles: Ref<Set<string>>;
21
+ operationTargets: ComputedRef<string[]>;
22
+ pendingOperation: Ref<PendingFileOperation | undefined>;
23
+ toggleSelection: (path: string) => void;
24
+ selectFiles: (files: DiskFile[]) => void;
25
+ clearSelection: () => void;
26
+ startCopy: (paths: string[]) => void;
27
+ startMove: (paths: string[]) => void;
28
+ executePending: (dest: string) => Promise<void>;
29
+ cancelPending: () => void;
30
+ copyFiles: (paths: string[], dest: string) => Promise<void>;
31
+ moveFiles: (paths: string[], dest: string) => Promise<void>;
32
+ deleteFiles: (paths: string[]) => Promise<void>;
11
33
  createFolder: (path?: string) => Promise<void>;
12
34
  createFile: (path?: string) => Promise<void>;
13
35
  }
@@ -0,0 +1,8 @@
1
+ import { SortDirection } from './sort-direction.js';
2
+ import { DiskFile } from './file-system.js';
3
+ export type FileSortField = keyof Pick<DiskFile, 'name' | 'mtime' | 'size'>;
4
+ export interface FileSortConfig {
5
+ readonly field: FileSortField;
6
+ readonly direction: SortDirection;
7
+ readonly directoriesFirst: boolean;
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -13,6 +13,7 @@ export interface FileSystemStore {
13
13
  rmdir(path: string | string[]): Promise<void>;
14
14
  fileInfo(path: string | string[]): Promise<DiskFile | undefined>;
15
15
  readDir(path?: string | string[]): Promise<DiskFile[]>;
16
+ copyFile(src: string | string[], dest: string | string[]): Promise<void>;
16
17
  dropFileSystem: () => Promise<void>;
17
18
  prettyVault: Ref<string>;
18
19
  }
package/models/index.d.ts CHANGED
@@ -49,6 +49,8 @@ export * from './sync-store.js';
49
49
  export * from './files-store.js';
50
50
  export * from './buffer-viewer-store.js';
51
51
  export * from './file-manager-store.js';
52
+ export * from './sort-direction.js';
53
+ export * from './file-sort.js';
52
54
  export * from './auth-store.js';
53
55
  export * from './commands-store.js';
54
56
  export * from './commands-group-store.js';
package/models/index.js CHANGED
@@ -51,6 +51,8 @@ export * from "./sync-store.js";
51
51
  export * from "./files-store.js";
52
52
  export * from "./buffer-viewer-store.js";
53
53
  export * from "./file-manager-store.js";
54
+ export * from "./sort-direction.js";
55
+ export * from "./file-sort.js";
54
56
  export * from "./auth-store.js";
55
57
  export * from "./commands-store.js";
56
58
  export * from "./commands-group-store.js";
@@ -0,0 +1 @@
1
+ export type SortDirection = 'asc' | 'desc';
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "orgnote-api",
3
- "version": "0.41.15",
3
+ "version": "0.41.17",
4
4
  "description": "Official API for creating extensions for OrgNote app",
5
5
  "type": "module",
6
6
  "main": "./index.js",
7
7
  "packageManager": "bun@1.1.22",
8
8
  "scripts": {
9
9
  "test": "vitest",
10
+ "test:ci": "vitest run",
10
11
  "build": "npm run clear && tspc --project tsconfig.json",
11
12
  "clear": "rm -rf dist",
12
13
  "lint": "eslint -c ./eslint.config.js \"./src*/**/*.{ts,js,cjs,mjs}\"",
@@ -17,7 +18,10 @@
17
18
  "fix-esm-imports": "fix-esm-import-path ./dist",
18
19
  "prepub": "bun run build && bun run fix-esm-imports && cp -rf package.json package-lock.json README.md dist/",
19
20
  "pub": "bun run prepub && cd dist && npm publish",
20
- "pub:yalc": "bun run prepub && cd dist && yalc publish --push"
21
+ "pub:yalc": "bun run prepub && cd dist && yalc publish --push",
22
+ "docs:dev": "cd docs && bun run dev",
23
+ "docs:build": "cd docs && bun run build",
24
+ "docs:preview": "cd docs && bun run preview"
21
25
  },
22
26
  "repository": {
23
27
  "type": "git",
@@ -53,6 +57,7 @@
53
57
  "devDependencies": {
54
58
  "@eslint/js": "9.25.1",
55
59
  "@openapitools/openapi-generator-cli": "2.19.1",
60
+ "@rollup/rollup-linux-arm64-gnu": "4.14.2",
56
61
  "@types/node": "22.14.1",
57
62
  "eslint": "9.25.1",
58
63
  "eslint-plugin-sonarjs": "3.0.5",
package/utils/index.d.ts CHANGED
@@ -12,3 +12,4 @@ export * from './to-error.js';
12
12
  export * from './auth-state.js';
13
13
  export * from './nullable-guards.js';
14
14
  export * from './binary.js';
15
+ export * from './sort-files.js';
package/utils/index.js CHANGED
@@ -12,3 +12,4 @@ export * from "./to-error.js";
12
12
  export * from "./auth-state.js";
13
13
  export * from "./nullable-guards.js";
14
14
  export * from "./binary.js";
15
+ export * from "./sort-files.js";
@@ -0,0 +1,3 @@
1
+ import { DiskFile } from '../models/file-system.js';
2
+ import { FileSortConfig } from '../models/file-sort.js';
3
+ export declare const sortFiles: (files: readonly DiskFile[], config: FileSortConfig) => DiskFile[];
@@ -0,0 +1,26 @@
1
+ const DIRECTORY_TYPE = 'directory';
2
+ const fieldAccessors = {
3
+ name: (f) => f.name.toLowerCase(),
4
+ mtime: (f) => f.mtime,
5
+ size: (f) => f.size,
6
+ };
7
+ const compareValues = (a, b) => {
8
+ if (typeof a === 'string' && typeof b === 'string')
9
+ return a.localeCompare(b);
10
+ return a - b;
11
+ };
12
+ const compareByField = (a, b, config) => {
13
+ const accessor = fieldAccessors[config.field];
14
+ const result = compareValues(accessor(a), accessor(b));
15
+ return config.direction === 'asc' ? result : -result;
16
+ };
17
+ const compareWithDirectoriesFirst = (a, b, config) => {
18
+ if (!config.directoriesFirst)
19
+ return compareByField(a, b, config);
20
+ const aIsDir = a.type === DIRECTORY_TYPE;
21
+ const bIsDir = b.type === DIRECTORY_TYPE;
22
+ if (aIsDir !== bIsDir)
23
+ return aIsDir ? -1 : 1;
24
+ return compareByField(a, b, config);
25
+ };
26
+ export const sortFiles = (files, config) => [...files].sort((a, b) => compareWithDirectoriesFirst(a, b, config));
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,84 @@
1
+ import { test, expect } from 'vitest';
2
+ import { sortFiles } from "./sort-files.js";
3
+ import { DEFAULT_FILE_SORT_CONFIG } from "../constants/file-sort-defaults.js";
4
+ const createFile = (overrides) => ({
5
+ name: 'file.org',
6
+ path: '/file.org',
7
+ type: 'file',
8
+ size: 100,
9
+ mtime: 1000,
10
+ ...overrides,
11
+ });
12
+ const DIR_ALPHA = createFile({ name: 'alpha', path: '/alpha', type: 'directory', size: 0, mtime: 3000 });
13
+ const DIR_BETA = createFile({ name: 'beta', path: '/beta', type: 'directory', size: 0, mtime: 1000 });
14
+ const FILE_A = createFile({ name: 'a.org', path: '/a.org', size: 200, mtime: 2000 });
15
+ const FILE_B = createFile({ name: 'b.org', path: '/b.org', size: 50, mtime: 5000 });
16
+ const FILE_C = createFile({ name: 'c.org', path: '/c.org', size: 300, mtime: 1000 });
17
+ test('sortFiles returns empty array for empty input', () => {
18
+ expect(sortFiles([], DEFAULT_FILE_SORT_CONFIG)).toEqual([]);
19
+ });
20
+ test('sortFiles does not mutate original array', () => {
21
+ const original = [FILE_B, FILE_A];
22
+ const sorted = sortFiles(original, DEFAULT_FILE_SORT_CONFIG);
23
+ expect(sorted).not.toBe(original);
24
+ expect(original[0]).toBe(FILE_B);
25
+ });
26
+ test('sortFiles sorts by name ascending with directories first by default', () => {
27
+ const files = [FILE_B, DIR_BETA, FILE_A, DIR_ALPHA];
28
+ const sorted = sortFiles(files, DEFAULT_FILE_SORT_CONFIG);
29
+ expect(sorted.map((f) => f.name)).toEqual(['alpha', 'beta', 'a.org', 'b.org']);
30
+ });
31
+ test('sortFiles sorts by name descending with directories first', () => {
32
+ const config = { field: 'name', direction: 'desc', directoriesFirst: true };
33
+ const files = [FILE_A, DIR_ALPHA, FILE_B, DIR_BETA];
34
+ const sorted = sortFiles(files, config);
35
+ expect(sorted.map((f) => f.name)).toEqual(['beta', 'alpha', 'b.org', 'a.org']);
36
+ });
37
+ test('sortFiles sorts by name ascending without directories first', () => {
38
+ const config = { field: 'name', direction: 'asc', directoriesFirst: false };
39
+ const files = [FILE_B, DIR_BETA, FILE_A, DIR_ALPHA];
40
+ const sorted = sortFiles(files, config);
41
+ expect(sorted.map((f) => f.name)).toEqual(['a.org', 'alpha', 'b.org', 'beta']);
42
+ });
43
+ test('sortFiles sorts by mtime ascending', () => {
44
+ const config = { field: 'mtime', direction: 'asc', directoriesFirst: false };
45
+ const files = [FILE_B, FILE_A, FILE_C];
46
+ const sorted = sortFiles(files, config);
47
+ expect(sorted.map((f) => f.name)).toEqual(['c.org', 'a.org', 'b.org']);
48
+ });
49
+ test('sortFiles sorts by mtime descending with directories first', () => {
50
+ const config = { field: 'mtime', direction: 'desc', directoriesFirst: true };
51
+ const files = [FILE_A, DIR_BETA, FILE_B, DIR_ALPHA];
52
+ const sorted = sortFiles(files, config);
53
+ expect(sorted.map((f) => f.name)).toEqual(['alpha', 'beta', 'b.org', 'a.org']);
54
+ });
55
+ test('sortFiles sorts by size ascending', () => {
56
+ const config = { field: 'size', direction: 'asc', directoriesFirst: false };
57
+ const files = [FILE_C, FILE_A, FILE_B];
58
+ const sorted = sortFiles(files, config);
59
+ expect(sorted.map((f) => f.name)).toEqual(['b.org', 'a.org', 'c.org']);
60
+ });
61
+ test('sortFiles sorts by size descending', () => {
62
+ const config = { field: 'size', direction: 'desc', directoriesFirst: false };
63
+ const files = [FILE_A, FILE_C, FILE_B];
64
+ const sorted = sortFiles(files, config);
65
+ expect(sorted.map((f) => f.name)).toEqual(['c.org', 'a.org', 'b.org']);
66
+ });
67
+ test('sortFiles handles single file', () => {
68
+ const sorted = sortFiles([FILE_A], DEFAULT_FILE_SORT_CONFIG);
69
+ expect(sorted).toEqual([FILE_A]);
70
+ });
71
+ test('sortFiles handles all directories', () => {
72
+ const sorted = sortFiles([DIR_BETA, DIR_ALPHA], DEFAULT_FILE_SORT_CONFIG);
73
+ expect(sorted.map((f) => f.name)).toEqual(['alpha', 'beta']);
74
+ });
75
+ test('sortFiles handles all files (no directories)', () => {
76
+ const sorted = sortFiles([FILE_C, FILE_A, FILE_B], DEFAULT_FILE_SORT_CONFIG);
77
+ expect(sorted.map((f) => f.name)).toEqual(['a.org', 'b.org', 'c.org']);
78
+ });
79
+ test('sortFiles name comparison is case-insensitive', () => {
80
+ const upper = createFile({ name: 'Zebra.org', path: '/Zebra.org' });
81
+ const lower = createFile({ name: 'apple.org', path: '/apple.org' });
82
+ const sorted = sortFiles([upper, lower], DEFAULT_FILE_SORT_CONFIG);
83
+ expect(sorted.map((f) => f.name)).toEqual(['apple.org', 'Zebra.org']);
84
+ });