orgnote-api 0.41.16 → 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 +306 -0
- package/constants/file-sort-defaults.d.ts +2 -0
- package/constants/file-sort-defaults.js +5 -0
- package/constants/i18n-keys.d.ts +11 -0
- package/constants/i18n-keys.js +5 -0
- package/constants/index.d.ts +1 -0
- package/constants/index.js +1 -0
- package/models/default-commands.d.ts +1 -0
- package/models/default-commands.js +1 -0
- package/models/file-manager-store.d.ts +5 -0
- package/models/file-sort.d.ts +8 -0
- package/models/file-sort.js +1 -0
- package/models/index.d.ts +2 -0
- package/models/index.js +2 -0
- package/models/sort-direction.d.ts +1 -0
- package/models/sort-direction.js +1 -0
- package/package.json +7 -2
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/sort-files.d.ts +3 -0
- package/utils/sort-files.js +26 -0
- package/utils/sort-files.spec.d.ts +1 -0
- package/utils/sort-files.spec.js +84 -0
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
|
}
|
package/constants/i18n-keys.d.ts
CHANGED
|
@@ -77,6 +77,11 @@ export declare enum i18n {
|
|
|
77
77
|
RENAME = "rename",
|
|
78
78
|
COPY = "copy",
|
|
79
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",
|
|
80
85
|
CREATE_FILE = "create file",
|
|
81
86
|
FILE_NAME = "file name",
|
|
82
87
|
DIR_NAME = "directory name",
|
|
@@ -244,6 +249,7 @@ export declare const I18N: {
|
|
|
244
249
|
DESELECT_ALL_FILES: DefaultCommands.DESELECT_ALL_FILES;
|
|
245
250
|
EXECUTE_PENDING_FILE_OPERATION: DefaultCommands.EXECUTE_PENDING_FILE_OPERATION;
|
|
246
251
|
CANCEL_PENDING_FILE_OPERATION: DefaultCommands.CANCEL_PENDING_FILE_OPERATION;
|
|
252
|
+
SORT_FILES: DefaultCommands.SORT_FILES;
|
|
247
253
|
CONFIRM_FILE_DELETION: DefaultCommands.CONFIRM_FILE_DELETION;
|
|
248
254
|
NEW_FILE_PATH: DefaultCommands.NEW_FILE_PATH;
|
|
249
255
|
SHOW_MOBILE_FILE_SEARCH: DefaultCommands.SHOW_MOBILE_FILE_SEARCH;
|
|
@@ -366,6 +372,11 @@ export declare const I18N: {
|
|
|
366
372
|
RENAME: i18n.RENAME;
|
|
367
373
|
COPY: i18n.COPY;
|
|
368
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;
|
|
369
380
|
FILE_NAME: i18n.FILE_NAME;
|
|
370
381
|
DIR_NAME: i18n.DIR_NAME;
|
|
371
382
|
FINISH_SETUP: i18n.FINISH_SETUP;
|
package/constants/i18n-keys.js
CHANGED
|
@@ -78,6 +78,11 @@ export var i18n;
|
|
|
78
78
|
i18n["RENAME"] = "rename";
|
|
79
79
|
i18n["COPY"] = "copy";
|
|
80
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";
|
|
81
86
|
i18n["CREATE_FILE"] = "create file";
|
|
82
87
|
i18n["FILE_NAME"] = "file name";
|
|
83
88
|
i18n["DIR_NAME"] = "directory name";
|
package/constants/index.d.ts
CHANGED
package/constants/index.js
CHANGED
|
@@ -74,6 +74,7 @@ export declare enum DefaultCommands {
|
|
|
74
74
|
DESELECT_ALL_FILES = "deselect all files",
|
|
75
75
|
EXECUTE_PENDING_FILE_OPERATION = "paste files here",
|
|
76
76
|
CANCEL_PENDING_FILE_OPERATION = "cancel file operation",
|
|
77
|
+
SORT_FILES = "sort files",
|
|
77
78
|
CONFIRM_FILE_DELETION = "are you sure you want to delete file?",
|
|
78
79
|
NEW_FILE_PATH = "new file path",
|
|
79
80
|
SHOW_MOBILE_FILE_SEARCH = "show mobile file search",
|
|
@@ -89,6 +89,7 @@ export var DefaultCommands;
|
|
|
89
89
|
DefaultCommands["DESELECT_ALL_FILES"] = "deselect all files";
|
|
90
90
|
DefaultCommands["EXECUTE_PENDING_FILE_OPERATION"] = "paste files here";
|
|
91
91
|
DefaultCommands["CANCEL_PENDING_FILE_OPERATION"] = "cancel file operation";
|
|
92
|
+
DefaultCommands["SORT_FILES"] = "sort files";
|
|
92
93
|
DefaultCommands["CONFIRM_FILE_DELETION"] = "are you sure you want to delete file?";
|
|
93
94
|
DefaultCommands["NEW_FILE_PATH"] = "new file path";
|
|
94
95
|
DefaultCommands["SHOW_MOBILE_FILE_SEARCH"] = "show mobile file search";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { StoreDefinition } from './store.js';
|
|
2
2
|
import { ComputedRef, Ref, ShallowRef } from 'vue';
|
|
3
3
|
import { DiskFile } from './file-system.js';
|
|
4
|
+
import { FileSortConfig } from './file-sort.js';
|
|
4
5
|
export type PendingFileOperation = {
|
|
5
6
|
type: 'copy' | 'move';
|
|
6
7
|
paths: string[];
|
|
@@ -11,6 +12,10 @@ export interface FileManagerStore {
|
|
|
11
12
|
focusDirPath: Ref<string>;
|
|
12
13
|
searchQuery: Ref<string>;
|
|
13
14
|
mobileFileSearchActive: Ref<boolean>;
|
|
15
|
+
files: Ref<DiskFile[]>;
|
|
16
|
+
sortConfig: Ref<FileSortConfig>;
|
|
17
|
+
sortedFiles: ComputedRef<DiskFile[]>;
|
|
18
|
+
loadFiles: () => Promise<void>;
|
|
14
19
|
selectionMode: ComputedRef<boolean>;
|
|
15
20
|
selectedFiles: Ref<Set<string>>;
|
|
16
21
|
operationTargets: ComputedRef<string[]>;
|
|
@@ -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 {};
|
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.
|
|
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
package/utils/index.js
CHANGED
|
@@ -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
|
+
});
|