Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support new file menu entry order #781

Merged
merged 1 commit into from Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
131 changes: 112 additions & 19 deletions __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', () => {
Expand Down Expand Up @@ -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: '<svg></svg>',
} as unknown as Entry)
}).toThrowError('Invalid entry')

expect(() => {
newFileMenu.registerEntry({
id: 123456,
displayName: '123456',
templateName: 'New file.txt',
iconClass: 'icon-filetype-text',
handler: () => {},
Expand All @@ -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({
Expand Down Expand Up @@ -163,28 +170,31 @@ 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({
id: 'empty-file',
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')
})
})

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
})
})
7 changes: 6 additions & 1 deletion lib/index.ts
Expand Up @@ -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)
})
}
36 changes: 19 additions & 17 deletions lib/newFileMenu.ts
Expand Up @@ -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. <svg><path fill="..." /></svg>
*/
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
}
Expand Down Expand Up @@ -83,7 +89,7 @@ export class NewFileMenu {
public getEntries(context?: Folder): Array<Entry> {
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
}
Expand All @@ -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')
}

Expand All @@ -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) {
Expand Down