diff --git a/__tests__/newFileMenu.spec.ts b/__tests__/newFileMenu.spec.ts index 8a7054ad..f463b6ea 100644 --- a/__tests__/newFileMenu.spec.ts +++ b/__tests__/newFileMenu.spec.ts @@ -1,8 +1,8 @@ -import { describe, expect, test, vi } from 'vitest' +import { describe, expect, test, vi, afterEach } from 'vitest' import { NewFileMenu, getNewFileMenu, type Entry } from '../lib/newFileMenu' import logger from '../lib/utils/logger' -import { Folder, Permission, View } from '../lib' +import { Folder, Permission, addNewFileMenuEntry, getNewFileMenuEntries } from '../lib' describe('NewFileMenu init', () => { test('Initializing NewFileMenu', () => { @@ -112,15 +112,22 @@ describe('NewFileMenu addEntry', () => { id: 123456, displayName: '123456', templateName: 'New file.txt', - iconClass: 'icon-filetype-text', handler: () => {}, } as unknown as Entry) - }).toThrowError('Invalid id or displayName property') + }).toThrowError('Invalid entry') expect(() => { newFileMenu.registerEntry({ - id: 'empty-file', - displayName: 123456, + id: 123456, + displayName: '123456', + iconSvgInline: '', + } as unknown as Entry) + }).toThrowError('Invalid entry') + + expect(() => { + newFileMenu.registerEntry({ + id: 123456, + displayName: '123456', templateName: 'New file.txt', iconClass: 'icon-filetype-text', handler: () => {}, @@ -130,12 +137,12 @@ describe('NewFileMenu addEntry', () => { expect(() => { newFileMenu.registerEntry({ id: 'empty-file', - displayName: '123456', - templateName: 123465, + displayName: 123456, + templateName: 'New file.txt', iconClass: 'icon-filetype-text', handler: () => {}, } as unknown as Entry) - }).toThrowError('Invalid templateName property') + }).toThrowError('Invalid id or displayName property') expect(() => { newFileMenu.registerEntry({ @@ -163,10 +170,10 @@ describe('NewFileMenu addEntry', () => { displayName: '123456', templateName: 'New file.txt', iconClass: 'icon-filetype-text', - if: true, + enabled: true, handler: () => {}, } as unknown as Entry) - }).toThrowError('Invalid if property') + }).toThrowError('Invalid enabled property') expect(() => { newFileMenu.registerEntry({ @@ -174,17 +181,20 @@ describe('NewFileMenu addEntry', () => { displayName: '123456', templateName: 'New file.txt', iconClass: 'icon-filetype-text', - handler: 'handler', + order: true, + handler: () => {}, } as unknown as Entry) - }).toThrowError('Invalid handler property') + }).toThrowError('Invalid order property') expect(() => { newFileMenu.registerEntry({ id: 'empty-file', displayName: '123456', + templateName: 'New file.txt', iconClass: 'icon-filetype-text', + handler: 'handler', } as unknown as Entry) - }).toThrowError('At least a templateName or a handler must be provided') + }).toThrowError('Invalid handler property') }) }) @@ -245,7 +255,7 @@ describe('NewFileMenu getEntries filter', () => { displayName: 'Create empty file', templateName: 'New file', iconClass: 'icon-file', - if: folder => (folder.permissions & Permission.CREATE) !== 0, + enabled: folder => (folder.permissions & Permission.CREATE) !== 0, handler: () => {}, } newFileMenu.registerEntry(entry1) @@ -255,7 +265,7 @@ describe('NewFileMenu getEntries filter', () => { displayName: 'Create new markdown file', templateName: 'New text.md', iconClass: 'icon-filetype-text', - if: folder => (folder.permissions & Permission.CREATE) !== 0, + enabled: folder => (folder.permissions & Permission.CREATE) !== 0, handler: () => {}, } newFileMenu.registerEntry(entry2) @@ -281,7 +291,7 @@ describe('NewFileMenu getEntries filter', () => { displayName: 'Create empty file', templateName: 'New file', iconClass: 'icon-file', - if: folder => (folder.permissions & Permission.CREATE) !== 0, + enabled: folder => (folder.permissions & Permission.CREATE) !== 0, handler: () => {}, } newFileMenu.registerEntry(entry1) @@ -291,7 +301,7 @@ describe('NewFileMenu getEntries filter', () => { displayName: 'Create new markdown file', templateName: 'New text.md', iconClass: 'icon-filetype-text', - if: folder => (folder.permissions & Permission.CREATE) !== 0, + enabled: folder => (folder.permissions & Permission.CREATE) !== 0, handler: () => {}, } newFileMenu.registerEntry(entry2) @@ -324,7 +334,7 @@ describe('NewFileMenu getEntries filter', () => { displayName: 'Create new markdown file', templateName: 'New text.md', iconClass: 'icon-filetype-text', - if: folder => (folder.permissions & Permission.CREATE) !== 0, + enabled: folder => (folder.permissions & Permission.CREATE) !== 0, handler: () => {}, } newFileMenu.registerEntry(entry2) @@ -343,3 +353,86 @@ describe('NewFileMenu getEntries filter', () => { expect(entries[0]).toBe(entry1) }) }) + +describe('NewFileMenu sort test', () => { + afterEach(() => { + delete window._nc_newfilemenu + }) + + test('Specified NewFileMenu order', () => { + const entry1 = { + id: 'empty-file', + displayName: 'Create empty file', + templateName: 'New file.txt', + iconClass: 'icon-filetype-text', + order: 3, + handler: () => {}, + } + + const entry2 = { + id: 'image', + displayName: 'Create new image', + templateName: 'New drawing.png', + iconClass: 'icon-filetype-image', + order: 2, + handler: () => {}, + } + + const entry3 = { + id: 'folder', + displayName: 'New folder', + templateName: 'New folder', + iconClass: 'icon-folder', + order: 1, + handler: () => {}, + } + + addNewFileMenuEntry(entry1) + addNewFileMenuEntry(entry2) + addNewFileMenuEntry(entry3) + + const entries = getNewFileMenuEntries() + expect(entries).toHaveLength(3) + expect(entries[0]).toBe(entry3) + expect(entries[1]).toBe(entry2) + expect(entries[2]).toBe(entry1) + }) + + test('Fallback to displayName', () => { + const entry1 = { + id: 'empty-file', + displayName: 'Create empty file', + templateName: 'New file.txt', + iconClass: 'icon-filetype-text', + handler: () => {}, + } + + const entry2 = { + id: 'image', + displayName: 'Create new image', + templateName: 'New drawing.png', + iconClass: 'icon-filetype-image', + order: 1, + handler: () => {}, + } + + const entry3 = { + id: 'folder', + displayName: 'New folder', + templateName: 'New folder', + iconClass: 'icon-folder', + order: 0, + handler: () => {}, + } + + addNewFileMenuEntry(entry1) + addNewFileMenuEntry(entry2) + addNewFileMenuEntry(entry3) + + const entries = getNewFileMenuEntries() + expect(entries).toHaveLength(3) + expect(entries[0]).toBe(entry1) + expect(entries[1]).toBe(entry3) + expect(entries[2]).toBe(entry2) + }) +}) diff --git a/lib/index.ts b/lib/index.ts index 88f5770f..2c280c0d 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -70,5 +70,10 @@ export const removeNewFileMenuEntry = function(entry: Entry | string) { */ export const getNewFileMenuEntries = function(context?: Folder) { const newFileMenu = getNewFileMenu() - return newFileMenu.getEntries(context) + return newFileMenu.getEntries(context).sort((a: Entry, b: Entry) => { + if (a.order !== undefined && b.order !== undefined) { + return a.order - b.order + } + return a.displayName.localeCompare(b.displayName) + }) } diff --git a/lib/newFileMenu.ts b/lib/newFileMenu.ts index b0287512..f88c310f 100644 --- a/lib/newFileMenu.ts +++ b/lib/newFileMenu.ts @@ -26,29 +26,35 @@ import logger from './utils/logger' export interface Entry { /** Unique ID */ id: string + /** Translatable string displayed in the menu */ displayName: string - /** Default new file name */ - templateName?: string + /** * Condition wether this entry is shown or not - * @param {Folder} context the creation context. Usually the current folder + * @param context the creation context. Usually the current folder */ - if?: (context: Folder) => boolean + enabled?: (context: Folder) => boolean + /** * Either iconSvgInline or iconClass must be defined * Svg as inline string. */ iconSvgInline?: string + /** * Existing icon css class * @deprecated use iconSvgInline instead */ iconClass?: string + + /** Order of the entry in the menu */ + order?: number + /** * Function to be run after creation - * @param {Folder} context the creation context. Usually the current folder - * @param {Node[]} content list of file/folders present in the context folder + * @param context the creation context. Usually the current folder + * @param content list of file/folders present in the context folder */ handler: (context: Folder, content: Node[]) => void } @@ -83,7 +89,7 @@ export class NewFileMenu { public getEntries(context?: Folder): Array { if (context) { return this._entries - .filter(entry => typeof entry.if === 'function' ? entry.if(context) : true) + .filter(entry => typeof entry.enabled === 'function' ? entry.enabled(context) : true) } return this._entries } @@ -93,7 +99,7 @@ export class NewFileMenu { } private validateEntry(entry: Entry) { - if (!entry.id || !entry.displayName || !(entry.iconSvgInline || entry.iconClass || entry.handler)) { + if (!entry.id || !entry.displayName || !(entry.iconSvgInline || entry.iconClass) || !entry.handler) { throw new Error('Invalid entry') } @@ -107,20 +113,16 @@ export class NewFileMenu { throw new Error('Invalid icon provided') } - if (entry.if !== undefined && typeof entry.if !== 'function') { - throw new Error('Invalid if property') - } - - if (entry.templateName && typeof entry.templateName !== 'string') { - throw new Error('Invalid templateName property') + if (entry.enabled !== undefined && typeof entry.enabled !== 'function') { + throw new Error('Invalid enabled property') } - if (entry.handler && typeof entry.handler !== 'function') { + if (typeof entry.handler !== 'function') { throw new Error('Invalid handler property') } - if (!entry.templateName && !entry.handler) { - throw new Error('At least a templateName or a handler must be provided') + if ('order' in entry && typeof entry.order !== 'number') { + throw new Error('Invalid order property') } if (this.getEntryIndex(entry.id) !== -1) {