Skip to content

Commit

Permalink
Merge pull request #781 from nextcloud-libraries/fix/newfilemenu
Browse files Browse the repository at this point in the history
  • Loading branch information
skjnldsv authored Oct 11, 2023
2 parents 999d5d0 + 37c3c61 commit cc0e5e5
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 37 deletions.
131 changes: 112 additions & 19 deletions __tests__/newFileMenu.spec.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit cc0e5e5

Please sign in to comment.