diff --git a/__tests__/actions/fileAction.spec.ts b/__tests__/actions/fileAction.spec.ts
index 409b6a1f5..39c62f25f 100644
--- a/__tests__/actions/fileAction.spec.ts
+++ b/__tests__/actions/fileAction.spec.ts
@@ -9,6 +9,7 @@ import type { Folder, Node } from '../../lib/node/index.ts'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { DefaultType, getFileActions, registerFileAction } from '../../lib/actions/index.ts'
+import { scopedGlobals } from '../../lib/globalScope.ts'
import { getRegistry } from '../../lib/registry.ts'
import logger from '../../lib/utils/logger.ts'
@@ -16,7 +17,7 @@ const folder = {} as Folder
const view = {} as View
describe('FileActions init', () => {
beforeEach(() => {
- delete window._nc_fileactions
+ delete scopedGlobals.fileActions
})
test('Getting empty uninitialized FileActions', () => {
@@ -39,7 +40,7 @@ describe('FileActions init', () => {
registerFileAction(action)
- expect(window._nc_fileactions).toHaveLength(1)
+ expect(scopedGlobals.fileActions).toHaveLength(1)
expect(getFileActions()).toHaveLength(1)
expect(getFileActions()[0]).toStrictEqual(action)
})
diff --git a/__tests__/actions/fileListAction.spec.ts b/__tests__/actions/fileListAction.spec.ts
index 0c800a206..e8f5fe92f 100644
--- a/__tests__/actions/fileListAction.spec.ts
+++ b/__tests__/actions/fileListAction.spec.ts
@@ -9,6 +9,7 @@ import type { Folder } from '../../lib/node/index.ts'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { getFileListActions, registerFileListAction } from '../../lib/actions/fileListAction.ts'
+import { scopedGlobals } from '../../lib/globalScope.ts'
import { getRegistry } from '../../lib/registry.ts'
import logger from '../../lib/utils/logger.ts'
@@ -28,11 +29,11 @@ function mockAction(id: string): IFileListAction {
describe('FileListActions init', () => {
beforeEach(() => {
- delete window._nc_filelistactions
+ delete scopedGlobals.fileListActions
})
test('Uninitialized file list actions', () => {
- expect(window._nc_filelistactions).toBe(undefined)
+ expect(scopedGlobals.fileListActions).toBe(undefined)
const actions = getFileListActions()
expect(actions).toHaveLength(0)
})
diff --git a/__tests__/dav/davProperties.spec.ts b/__tests__/dav/davProperties.spec.ts
index 0297ac16d..606869d77 100644
--- a/__tests__/dav/davProperties.spec.ts
+++ b/__tests__/dav/davProperties.spec.ts
@@ -15,26 +15,27 @@ import {
getRecentSearch,
registerDavProperty,
} from '../../lib/dav/davProperties.ts'
+import { scopedGlobals } from '../../lib/globalScope.ts'
import logger from '../../lib/utils/logger.ts'
describe('DAV Properties', () => {
beforeEach(() => {
- delete window._nc_dav_properties
- delete window._nc_dav_namespaces
+ delete scopedGlobals.davNamespaces
+ delete scopedGlobals.davProperties
logger.error = vi.fn()
logger.warn = vi.fn()
})
test('getDavNameSpaces fall back to defaults', () => {
- expect(window._nc_dav_namespaces).toBeUndefined()
+ expect(scopedGlobals.davNamespaces).toBeUndefined()
const namespace = getDavNameSpaces()
expect(namespace).toBeTruthy()
Object.keys(defaultDavNamespaces).forEach((n) => expect(namespace.includes(n) && namespace.includes(defaultDavNamespaces[n])).toBe(true))
})
test('getDavProperties fall back to defaults', () => {
- expect(window._nc_dav_properties).toBeUndefined()
+ expect(scopedGlobals.davProperties).toBeUndefined()
const props = getDavProperties()
expect(props).toBeTruthy()
defaultDavProperties.forEach((p) => expect(props.includes(p)).toBe(true))
@@ -56,8 +57,8 @@ describe('DAV Properties', () => {
})
test('registerDavProperty registers successfully', () => {
- expect(window._nc_dav_namespaces).toBeUndefined()
- expect(window._nc_dav_properties).toBeUndefined()
+ expect(scopedGlobals.davNamespaces).toBeUndefined()
+ expect(scopedGlobals.davProperties).toBeUndefined()
expect(registerDavProperty('my:prop', { my: 'https://example.com/ns' })).toBe(true)
expect(logger.warn).not.toBeCalled()
@@ -67,8 +68,8 @@ describe('DAV Properties', () => {
})
test('registerDavProperty fails when registered multiple times', () => {
- expect(window._nc_dav_namespaces).toBeUndefined()
- expect(window._nc_dav_properties).toBeUndefined()
+ expect(scopedGlobals.davNamespaces).toBeUndefined()
+ expect(scopedGlobals.davProperties).toBeUndefined()
expect(registerDavProperty('my:prop', { my: 'https://example.com/ns' })).toBe(true)
expect(registerDavProperty('my:prop')).toBe(false)
@@ -80,8 +81,8 @@ describe('DAV Properties', () => {
})
test('registerDavProperty fails with invalid props', () => {
- expect(window._nc_dav_namespaces).toBeUndefined()
- expect(window._nc_dav_properties).toBeUndefined()
+ expect(scopedGlobals.davNamespaces).toBeUndefined()
+ expect(scopedGlobals.davProperties).toBeUndefined()
expect(registerDavProperty('my:prop:invalid', { my: 'https://example.com/ns' })).toBe(false)
expect(logger.error).toBeCalled()
@@ -95,8 +96,8 @@ describe('DAV Properties', () => {
})
test('registerDavProperty fails with missing namespace', () => {
- expect(window._nc_dav_namespaces).toBeUndefined()
- expect(window._nc_dav_properties).toBeUndefined()
+ expect(scopedGlobals.davNamespaces).toBeUndefined()
+ expect(scopedGlobals.davProperties).toBeUndefined()
expect(registerDavProperty('my:prop', { other: 'https://example.com/ns' })).toBe(false)
expect(logger.error).toBeCalled()
diff --git a/__tests__/filters/listFilter.spec.ts b/__tests__/filters/listFilter.spec.ts
index 199cbc362..88ceac755 100644
--- a/__tests__/filters/listFilter.spec.ts
+++ b/__tests__/filters/listFilter.spec.ts
@@ -1,4 +1,4 @@
-/**
+/*
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -7,6 +7,7 @@ import type { IFileListFilterChip } from '../../lib/filters/index.ts'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { FileListFilter, getFileListFilters, registerFileListFilter, unregisterFileListFilter } from '../../lib/filters/index.ts'
+import { scopedGlobals } from '../../lib/globalScope.ts'
import { getRegistry } from '../../lib/registry.ts'
class TestFilter extends FileListFilter {
@@ -83,16 +84,16 @@ describe('File list filter class', () => {
describe('File list filter functions', () => {
beforeEach(() => {
- delete window._nc_filelist_filters
+ delete scopedGlobals.fileListFilters
})
test('can register a filter', () => {
const filter = new FileListFilter('my:id', 50)
registerFileListFilter(filter)
- expect(window._nc_filelist_filters).toBeTypeOf('object')
- expect(window._nc_filelist_filters!.has(filter.id)).toBe(true)
- expect(window._nc_filelist_filters!.get(filter.id)).toBe(filter)
+ expect(scopedGlobals.fileListFilters).toBeTypeOf('object')
+ expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(true)
+ expect(scopedGlobals.fileListFilters!.get(filter.id)).toBe(filter)
})
test('register a filter emits event', () => {
@@ -101,7 +102,7 @@ describe('File list filter functions', () => {
getRegistry().addEventListener('register:listFilter', spy)
- expect(window._nc_filelist_filters).toBe(undefined)
+ expect(scopedGlobals.fileListFilters).toBe(undefined)
registerFileListFilter(filter)
expect(spy).toHaveBeenCalled()
@@ -121,22 +122,22 @@ describe('File list filter functions', () => {
const filter = new FileListFilter('my:id')
registerFileListFilter(filter)
- expect(window._nc_filelist_filters!.has(filter.id)).toBe(true)
+ expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(true)
// test
unregisterFileListFilter(filter.id)
- expect(window._nc_filelist_filters!.has(filter.id)).toBe(false)
+ expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(false)
})
test('unregister a filter twice does not throw', () => {
const filter = new FileListFilter('my:id')
registerFileListFilter(filter)
- expect(window._nc_filelist_filters!.has(filter.id)).toBe(true)
+ expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(true)
// test
unregisterFileListFilter(filter.id)
- expect(window._nc_filelist_filters!.has(filter.id)).toBe(false)
+ expect(scopedGlobals.fileListFilters!.has(filter.id)).toBe(false)
expect(() => unregisterFileListFilter(filter.id)).not.toThrow()
})
@@ -150,7 +151,7 @@ describe('File list filter functions', () => {
unregisterFileListFilter(filter.id)
expect(spy).toHaveBeenCalled()
expect(spy.mock.calls[0][0]).toBeInstanceOf(CustomEvent)
- expect(spy.mock.calls[0][0].detail).toBe(filter)
+ expect(spy.mock.calls[0][0].detail).toBe(filter.id)
})
test('can get registered filters', () => {
diff --git a/__tests__/headers/listHeaders.spec.ts b/__tests__/headers/listHeaders.spec.ts
index dac6bc1e3..e72cbebaa 100644
--- a/__tests__/headers/listHeaders.spec.ts
+++ b/__tests__/headers/listHeaders.spec.ts
@@ -1,4 +1,4 @@
-/**
+/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -6,18 +6,19 @@
import type { IFileListHeader, IFolder, IView } from '../../lib/index.ts'
import { beforeEach, describe, expect, test, vi } from 'vitest'
+import { scopedGlobals } from '../../lib/globalScope.ts'
import { getFileListHeaders, registerFileListHeader } from '../../lib/headers/index.ts'
import { getRegistry } from '../../lib/registry.ts'
import logger from '../../lib/utils/logger.ts'
describe('FileListHeader init', () => {
beforeEach(() => {
- delete window._nc_filelistheader
+ delete scopedGlobals.fileListHeaders
})
test('Getting empty uninitialized FileListHeader', () => {
const headers = getFileListHeaders()
- expect(window._nc_filelistheader).toBeDefined()
+ expect(Array.isArray(headers)).toBe(true)
expect(headers).toHaveLength(0)
})
@@ -29,14 +30,9 @@ describe('FileListHeader init', () => {
render: () => {},
updated: () => {},
}
-
- expect(header.id).toBe('test')
- expect(header.order).toBe(1)
- expect(header.enabled!({} as IFolder, {} as IView)).toBe(true)
-
registerFileListHeader(header)
- expect(window._nc_filelistheader).toHaveLength(1)
+ expect(scopedGlobals.fileListHeaders).toHaveLength(1)
expect(getFileListHeaders()).toHaveLength(1)
expect(getFileListHeaders()[0]).toStrictEqual(header)
})
@@ -60,6 +56,25 @@ describe('FileListHeader init', () => {
expect(callback.mock.calls[0][0].detail).toBe(header)
})
+ test('getFileListHeaders() returns array', () => {
+ expect(getFileListHeaders()).toHaveLength(0)
+
+ const header: IFileListHeader = {
+ id: 'test',
+ order: 1,
+ enabled: () => true,
+ render: () => {},
+ updated: () => {},
+ }
+
+ registerFileListHeader(header)
+
+ const headers = getFileListHeaders()
+ expect(Array.isArray(headers)).toBe(true)
+ expect(headers).toHaveLength(1)
+ expect(headers[0]).toStrictEqual(header)
+ })
+
test('Duplicate Header gets rejected', () => {
logger.error = vi.fn()
const header: IFileListHeader = {
diff --git a/__tests__/index.spec.ts b/__tests__/index.spec.ts
index 0c04d9ff9..41c4f5b0c 100644
--- a/__tests__/index.spec.ts
+++ b/__tests__/index.spec.ts
@@ -3,8 +3,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-import type { NewMenuEntry } from '../lib/newMenu/NewMenu.ts'
-
import { describe, expect, test } from 'vitest'
import { getFileActions, registerFileAction } from '../lib/actions/fileAction.ts'
import {
@@ -18,7 +16,6 @@ import {
Permission,
removeNewFileMenuEntry,
} from '../lib/index.ts'
-import { NewMenu } from '../lib/newMenu/NewMenu.ts'
describe('Exports checks', () => {
test('formatFileSize', () => {
@@ -76,38 +73,3 @@ describe('Exports checks', () => {
expect(typeof Node).toBe('function')
})
})
-
-describe('NewFileMenu methods', () => {
- const entry = {
- id: 'empty-file',
- displayName: 'Create empty file',
- templateName: 'New file.txt',
- iconSvgInline: '',
- handler: () => {},
- } as NewMenuEntry
-
- test('Init NewFileMenu', () => {
- expect(window._nc_newfilemenu).toBeUndefined()
-
- const menuEntries = getNewFileMenuEntries()
- expect(menuEntries).toHaveLength(0)
-
- expect(window._nc_newfilemenu).toBeDefined()
- expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
- })
-
- test('Use existing initialized NewMenu', () => {
- expect(window._nc_newfilemenu).toBeDefined()
- expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
-
- addNewFileMenuEntry(entry)
-
- expect(window._nc_newfilemenu).toBeDefined()
- expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
-
- removeNewFileMenuEntry(entry)
-
- expect(window._nc_newfilemenu).toBeDefined()
- expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
- })
-})
diff --git a/__tests__/navigation.spec.ts b/__tests__/navigation.spec.ts
index fe356ccdd..9a7986d64 100644
--- a/__tests__/navigation.spec.ts
+++ b/__tests__/navigation.spec.ts
@@ -1,9 +1,10 @@
-/**
+/*
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { describe, expect, it, vi } from 'vitest'
+import { scopedGlobals } from '../lib/globalScope.ts'
import { View } from '../lib/index.ts'
import { getNavigation, Navigation } from '../lib/navigation/navigation.ts'
import { mockView } from './fixtures/view.ts'
@@ -18,21 +19,15 @@ describe('getNavigation', () => {
})
it('stores the navigation globally', () => {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- delete window._nc_navigation
+ delete scopedGlobals.navigation
const navigation = getNavigation()
expect(navigation).toBeInstanceOf(Navigation)
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- expect(window._nc_navigation).toBeInstanceOf(Navigation)
+ expect(scopedGlobals.navigation).toBeInstanceOf(Navigation)
})
it('reuses an existing navigation', () => {
const navigation = new Navigation()
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- window._nc_navigation = navigation
+ scopedGlobals.navigation = navigation
expect(getNavigation()).toBe(navigation)
})
})
diff --git a/__tests__/newMenu/newFileMenu.spec.ts b/__tests__/newMenu/newFileMenu.spec.ts
index cf6d417f8..c04faf8c6 100644
--- a/__tests__/newMenu/newFileMenu.spec.ts
+++ b/__tests__/newMenu/newFileMenu.spec.ts
@@ -6,7 +6,8 @@
import type { NewMenuEntry } from '../../lib/newMenu/index.ts'
import { afterEach, describe, expect, test, vi } from 'vitest'
-import { addNewFileMenuEntry, getNewFileMenu, getNewFileMenuEntries } from '../../lib/newMenu/index.ts'
+import { scopedGlobals } from '../../lib/globalScope.ts'
+import { addNewFileMenuEntry, getNewFileMenu, getNewFileMenuEntries, removeNewFileMenuEntry } from '../../lib/newMenu/index.ts'
import { NewMenu, NewMenuEntryCategory } from '../../lib/newMenu/NewMenu.ts'
import { Folder } from '../../lib/node/index.ts'
import { Permission } from '../../lib/permissions.ts'
@@ -14,18 +15,16 @@ import logger from '../../lib/utils/logger.ts'
describe('NewFileMenu init', () => {
test('Initializing NewFileMenu', () => {
- logger.debug = vi.fn()
const newFileMenu = getNewFileMenu()
- expect(window._nc_newfilemenu).toBeInstanceOf(NewMenu)
- expect(window._nc_newfilemenu).toBe(newFileMenu)
- expect(logger.debug).toHaveBeenCalled()
+ expect(scopedGlobals.newFileMenu).toBeInstanceOf(NewMenu)
+ expect(scopedGlobals.newFileMenu).toBe(newFileMenu)
})
test('Getting existing NewMenu', () => {
const newFileMenu = new NewMenu()
- Object.assign(window, { _nc_newfilemenu: newFileMenu })
+ Object.assign(scopedGlobals, { newFileMenu })
- expect(window._nc_newfilemenu).toBe(newFileMenu)
+ expect(scopedGlobals.newFileMenu).toBe(newFileMenu)
expect(getNewFileMenu()).toBe(newFileMenu)
})
})
@@ -399,7 +398,7 @@ describe('NewMenu getEntries filter', () => {
describe('NewMenu sort test', () => {
afterEach(() => {
- delete window._nc_newfilemenu
+ delete scopedGlobals.newFileMenu
})
test('Specified NewMenu order', () => {
@@ -490,3 +489,38 @@ describe('NewMenu sort test', () => {
expect(entries[3]).toBe(entry2)
})
})
+
+describe('NewFileMenu methods', () => {
+ const entry = {
+ id: 'empty-file',
+ displayName: 'Create empty file',
+ templateName: 'New file.txt',
+ iconSvgInline: '',
+ handler: () => {},
+ } as NewMenuEntry
+
+ test('Init NewFileMenu', () => {
+ expect(scopedGlobals.newFileMenu).toBeUndefined()
+
+ const menuEntries = getNewFileMenuEntries()
+ expect(menuEntries).toHaveLength(0)
+
+ expect(scopedGlobals.newFileMenu).toBeDefined()
+ expect(scopedGlobals.newFileMenu).toBeInstanceOf(NewMenu)
+ })
+
+ test('Use existing initialized NewMenu', () => {
+ expect(scopedGlobals.newFileMenu).toBeDefined()
+ expect(scopedGlobals.newFileMenu).toBeInstanceOf(NewMenu)
+
+ addNewFileMenuEntry(entry)
+
+ expect(scopedGlobals.newFileMenu).toBeDefined()
+ expect(scopedGlobals.newFileMenu).toBeInstanceOf(NewMenu)
+
+ removeNewFileMenuEntry(entry)
+
+ expect(scopedGlobals.newFileMenu).toBeDefined()
+ expect(scopedGlobals.newFileMenu).toBeInstanceOf(NewMenu)
+ })
+})
diff --git a/__tests__/sidebar/sidebarTab.spec.ts b/__tests__/sidebar/sidebarTab.spec.ts
index 4f84de36d..c4efd4ad5 100644
--- a/__tests__/sidebar/sidebarTab.spec.ts
+++ b/__tests__/sidebar/sidebarTab.spec.ts
@@ -6,6 +6,7 @@
import type { ISidebarTab } from '../../lib/sidebar/SidebarTab.ts'
import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { scopedGlobals } from '../../lib/globalScope.ts'
import { getSidebarTabs, registerSidebarTab } from '../../lib/sidebar/SidebarTab.ts'
// missing in JSDom but supported by every browser!
@@ -14,16 +15,16 @@ import 'css.escape'
describe('Sidebar tabs', () => {
beforeEach(() => {
vi.restoreAllMocks()
- delete window._nc_files_sidebar_tabs
+ delete scopedGlobals.filesSidebarTabs
})
it('can register a tab', () => {
const tab = getExampleTab()
registerSidebarTab(tab)
- expect(window._nc_files_sidebar_tabs).toBeInstanceOf(Map)
- expect(window._nc_files_sidebar_tabs!.has(tab.id)).toBe(true)
- expect(window._nc_files_sidebar_tabs!.get(tab.id)).toBe(tab)
+ expect(scopedGlobals.filesSidebarTabs).toBeInstanceOf(Map)
+ expect(scopedGlobals.filesSidebarTabs!.has(tab.id)).toBe(true)
+ expect(scopedGlobals.filesSidebarTabs!.get(tab.id)).toBe(tab)
})
it('can fetch empty list of sidebar tabs', () => {
diff --git a/lib/actions/fileAction.ts b/lib/actions/fileAction.ts
index 7771bf80e..8b9753322 100644
--- a/lib/actions/fileAction.ts
+++ b/lib/actions/fileAction.ts
@@ -5,6 +5,7 @@
import type { ActionContext, ActionContextSingle } from '../types.ts'
+import { scopedGlobals } from '../globalScope.ts'
import { getRegistry } from '../registry.ts'
import logger from '../utils/logger.ts'
@@ -128,13 +129,13 @@ export interface IFileAction {
export function registerFileAction(action: IFileAction): void {
validateAction(action)
- window._nc_fileactions ??= []
- if (window._nc_fileactions.find((search) => search.id === action.id)) {
+ scopedGlobals.fileActions ??= new Map()
+ if (scopedGlobals.fileActions.has(action.id)) {
logger.error(`FileAction ${action.id} already registered`, { action })
return
}
- window._nc_fileactions.push(action)
+ scopedGlobals.fileActions.set(action.id, action)
getRegistry()
.dispatchTypedEvent('register:action', new CustomEvent('register:action', { detail: action }))
}
@@ -143,7 +144,10 @@ export function registerFileAction(action: IFileAction): void {
* Get all registered file actions.
*/
export function getFileActions(): IFileAction[] {
- return window._nc_fileactions ?? []
+ if (scopedGlobals.fileActions) {
+ return [...scopedGlobals.fileActions.values()]
+ }
+ return []
}
/**
diff --git a/lib/actions/fileListAction.ts b/lib/actions/fileListAction.ts
index 247cbba65..8cfff0a32 100644
--- a/lib/actions/fileListAction.ts
+++ b/lib/actions/fileListAction.ts
@@ -5,6 +5,7 @@
import type { ViewActionContext } from '../types.ts'
+import { scopedGlobals } from '../globalScope.ts'
import { getRegistry } from '../registry.ts'
import logger from '../utils/logger.ts'
@@ -44,13 +45,13 @@ export interface IFileListAction {
export function registerFileListAction(action: IFileListAction) {
validateAction(action)
- window._nc_filelistactions ??= []
- if (window._nc_filelistactions.find((listAction) => listAction.id === action.id)) {
+ scopedGlobals.fileListActions ??= new Map()
+ if (scopedGlobals.fileListActions.has(action.id)) {
logger.error(`FileListAction with id "${action.id}" is already registered`, { action })
return
}
- window._nc_filelistactions.push(action)
+ scopedGlobals.fileListActions.set(action.id, action)
getRegistry()
.dispatchTypedEvent('register:listAction', new CustomEvent('register:listAction', { detail: action }))
}
@@ -59,7 +60,10 @@ export function registerFileListAction(action: IFileListAction) {
* Get all currently registered file list actions.
*/
export function getFileListActions(): IFileListAction[] {
- return [...(window._nc_filelistactions ?? [])]
+ if (scopedGlobals.fileListActions) {
+ return [...scopedGlobals.fileListActions.values()]
+ }
+ return []
}
/**
diff --git a/lib/dav/davProperties.ts b/lib/dav/davProperties.ts
index 3434468da..cda5d34a4 100644
--- a/lib/dav/davProperties.ts
+++ b/lib/dav/davProperties.ts
@@ -4,6 +4,7 @@
*/
import { getCurrentUser } from '@nextcloud/auth'
+import { scopedGlobals } from '../globalScope.ts'
import logger from '../utils/logger.ts'
export type DavProperty = { [key: string]: string }
@@ -45,15 +46,13 @@ export const defaultDavNamespaces = {
* @param namespace The namespace of the property
*/
export function registerDavProperty(prop: string, namespace: DavProperty = { nc: 'http://nextcloud.org/ns' }): boolean {
- if (typeof window._nc_dav_properties === 'undefined') {
- window._nc_dav_properties = [...defaultDavProperties]
- window._nc_dav_namespaces = { ...defaultDavNamespaces }
- }
+ scopedGlobals.davNamespaces ??= { ...defaultDavNamespaces }
+ scopedGlobals.davProperties ??= [...defaultDavProperties]
- const namespaces = { ...window._nc_dav_namespaces, ...namespace }
+ const namespaces = { ...scopedGlobals.davNamespaces, ...namespace }
// Check duplicates
- if (window._nc_dav_properties.find((search) => search === prop)) {
+ if (scopedGlobals.davProperties.find((search) => search === prop)) {
logger.warn(`${prop} already registered`, { prop })
return false
}
@@ -69,8 +68,8 @@ export function registerDavProperty(prop: string, namespace: DavProperty = { nc:
return false
}
- window._nc_dav_properties.push(prop)
- window._nc_dav_namespaces = namespaces
+ scopedGlobals.davProperties.push(prop)
+ scopedGlobals.davNamespaces = namespaces
return true
}
@@ -78,23 +77,17 @@ export function registerDavProperty(prop: string, namespace: DavProperty = { nc:
* Get the registered dav properties
*/
export function getDavProperties(): string {
- if (typeof window._nc_dav_properties === 'undefined') {
- window._nc_dav_properties = [...defaultDavProperties]
- }
-
- return window._nc_dav_properties.map((prop) => `<${prop} />`).join(' ')
+ scopedGlobals.davProperties ??= [...defaultDavProperties]
+ return scopedGlobals.davProperties.map((prop) => `<${prop} />`).join(' ')
}
/**
* Get the registered dav namespaces
*/
export function getDavNameSpaces(): string {
- if (typeof window._nc_dav_namespaces === 'undefined') {
- window._nc_dav_namespaces = { ...defaultDavNamespaces }
- }
-
- return Object.keys(window._nc_dav_namespaces)
- .map((ns) => `xmlns:${ns}="${window._nc_dav_namespaces?.[ns]}"`)
+ scopedGlobals.davNamespaces ??= { ...defaultDavNamespaces }
+ return Object.keys(scopedGlobals.davNamespaces)
+ .map((ns) => `xmlns:${ns}="${scopedGlobals.davNamespaces?.[ns]}"`)
.join(' ')
}
diff --git a/lib/filters/functions.ts b/lib/filters/functions.ts
index b5ee56c78..853907482 100644
--- a/lib/filters/functions.ts
+++ b/lib/filters/functions.ts
@@ -5,6 +5,7 @@
import type { IFileListFilter } from './listFilters.ts'
+import { scopedGlobals } from '../globalScope.ts'
import { getRegistry } from '../registry.ts'
/**
@@ -16,12 +17,12 @@ import { getRegistry } from '../registry.ts'
* @param filter The filter to register on the file list
*/
export function registerFileListFilter(filter: IFileListFilter): void {
- window._nc_filelist_filters ??= new Map()
- if (window._nc_filelist_filters.has(filter.id)) {
+ scopedGlobals.fileListFilters ??= new Map()
+ if (scopedGlobals.fileListFilters.has(filter.id)) {
throw new Error(`File list filter "${filter.id}" already registered`)
}
- window._nc_filelist_filters.set(filter.id, filter)
+ scopedGlobals.fileListFilters.set(filter.id, filter)
getRegistry()
.dispatchTypedEvent('register:listFilter', new CustomEvent('register:listFilter', { detail: filter }))
}
@@ -32,11 +33,10 @@ export function registerFileListFilter(filter: IFileListFilter): void {
* @param filterId The unique ID of the filter to remove
*/
export function unregisterFileListFilter(filterId: string): void {
- if (window._nc_filelist_filters && window._nc_filelist_filters.has(filterId)) {
- const filter = window._nc_filelist_filters.get(filterId)!
- window._nc_filelist_filters.delete(filterId)
+ if (scopedGlobals.fileListFilters && scopedGlobals.fileListFilters.has(filterId)) {
+ scopedGlobals.fileListFilters.delete(filterId)
getRegistry()
- .dispatchTypedEvent('unregister:listFilter', new CustomEvent('unregister:listFilter', { detail: filter }))
+ .dispatchTypedEvent('unregister:listFilter', new CustomEvent('unregister:listFilter', { detail: filterId }))
}
}
@@ -44,8 +44,8 @@ export function unregisterFileListFilter(filterId: string): void {
* Get all registered file list filters
*/
export function getFileListFilters(): IFileListFilter[] {
- if (!window._nc_filelist_filters) {
- return []
+ if (scopedGlobals.fileListFilters) {
+ return [...scopedGlobals.fileListFilters.values()]
}
- return [...window._nc_filelist_filters.values()]
+ return []
}
diff --git a/lib/globalScope.ts b/lib/globalScope.ts
new file mode 100644
index 000000000..9e26f7e08
--- /dev/null
+++ b/lib/globalScope.ts
@@ -0,0 +1,44 @@
+/*
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import type { IFileAction, IFileListAction } from './actions/index.ts'
+import type { DavProperty } from './dav/index.ts'
+import type {
+ IFileListFilter,
+ IFileListHeader,
+ Navigation,
+ NewMenu,
+} from './index.ts'
+import type { FilesRegistry } from './registry.ts'
+import type { ISidebarTab } from './sidebar/index.ts'
+import type { ISidebarAction } from './sidebar/SidebarAction.ts'
+
+interface InternalGlobalScope {
+ davNamespaces?: DavProperty
+ davProperties?: string[]
+
+ newFileMenu?: NewMenu
+ navigation?: Navigation
+ registry?: FilesRegistry
+
+ fileActions?: Map
+ fileListActions?: Map
+ fileListFilters?: Map
+ fileListHeaders?: Map
+
+ filesSidebarActions?: Map
+ filesSidebarTabs?: Map
+}
+
+window._nc_files_scope ??= {}
+window._nc_files_scope.v4_0 ??= {}
+
+/**
+ * Get the global scope for the files library.
+ * This is used to store global variables scoped to prevent breaking changes in the future.
+ *
+ * @internal
+ */
+export const scopedGlobals = window._nc_files_scope.v4_0 as InternalGlobalScope
diff --git a/lib/headers/listHeaders.ts b/lib/headers/listHeaders.ts
index 3d38a5d25..7727ce12a 100644
--- a/lib/headers/listHeaders.ts
+++ b/lib/headers/listHeaders.ts
@@ -6,6 +6,7 @@
import type { IView } from '../navigation/view.ts'
import type { IFolder } from '../node/folder.ts'
+import { scopedGlobals } from '../globalScope.ts'
import { getRegistry } from '../registry.ts'
import logger from '../utils/logger.ts'
@@ -30,13 +31,13 @@ export interface IFileListHeader {
export function registerFileListHeader(header: IFileListHeader): void {
validateHeader(header)
- window._nc_filelistheader ??= []
- if (window._nc_filelistheader.find((search) => search.id === header.id)) {
+ scopedGlobals.fileListHeaders ??= new Map()
+ if (scopedGlobals.fileListHeaders.has(header.id)) {
logger.error(`Header ${header.id} already registered`, { header })
return
}
- window._nc_filelistheader.push(header)
+ scopedGlobals.fileListHeaders.set(header.id, header)
getRegistry()
.dispatchTypedEvent('register:listHeader', new CustomEvent('register:listHeader', { detail: header }))
}
@@ -45,8 +46,10 @@ export function registerFileListHeader(header: IFileListHeader): void {
* Get all currently registered file list headers.
*/
export function getFileListHeaders(): IFileListHeader[] {
- window._nc_filelistheader ??= []
- return [...window._nc_filelistheader]
+ if (!scopedGlobals.fileListHeaders) {
+ return []
+ }
+ return [...scopedGlobals.fileListHeaders.values()]
}
/**
diff --git a/lib/navigation/navigation.ts b/lib/navigation/navigation.ts
index 9a9c3912b..c6cf05117 100644
--- a/lib/navigation/navigation.ts
+++ b/lib/navigation/navigation.ts
@@ -6,7 +6,7 @@
import type { IView } from './view.ts'
import { TypedEventTarget } from 'typescript-event-target'
-import logger from '../utils/logger.ts'
+import { scopedGlobals } from '../globalScope.ts'
import { validateView } from './view.ts'
/**
@@ -123,10 +123,6 @@ export class Navigation extends TypedEventTarget<{ updateActive: UpdateActiveVie
* Get the current files navigation
*/
export function getNavigation(): Navigation {
- if (typeof window._nc_navigation === 'undefined') {
- window._nc_navigation = new Navigation()
- logger.debug('Navigation service initialized')
- }
-
- return window._nc_navigation
+ scopedGlobals.navigation ??= new Navigation()
+ return scopedGlobals.navigation
}
diff --git a/lib/newMenu/functions.ts b/lib/newMenu/functions.ts
index 2a7077f2e..1ab5a9936 100644
--- a/lib/newMenu/functions.ts
+++ b/lib/newMenu/functions.ts
@@ -6,18 +6,15 @@
import type { IFolder } from '../node/index.ts'
import type { NewMenuEntry } from './NewMenu.ts'
-import logger from '../utils/logger.ts'
+import { scopedGlobals } from '../globalScope.ts'
import { NewMenu } from './NewMenu.ts'
/**
* Get the NewMenu instance used by the files app.
*/
export function getNewFileMenu(): NewMenu {
- if (typeof window._nc_newfilemenu === 'undefined') {
- window._nc_newfilemenu = new NewMenu()
- logger.debug('NewFileMenu initialized')
- }
- return window._nc_newfilemenu
+ scopedGlobals.newFileMenu ??= new NewMenu()
+ return scopedGlobals.newFileMenu
}
/**
diff --git a/lib/registry.ts b/lib/registry.ts
index deb513020..984aa8afb 100644
--- a/lib/registry.ts
+++ b/lib/registry.ts
@@ -8,31 +8,59 @@ import type { IFileListFilter } from './filters/index.ts'
import type { IFileListHeader } from './headers/index.ts'
import { TypedEventTarget } from 'typescript-event-target'
+import { scopedGlobals } from './globalScope.ts'
interface FilesRegistryEvents {
- 'register:action': CustomEvent
- 'register:listAction': CustomEvent
- 'register:listFilter': CustomEvent
- 'unregister:listFilter': CustomEvent
- 'register:listHeader': CustomEvent
+ 'register:action': RegistrationEvent
+ 'register:listAction': RegistrationEvent
+ 'register:listFilter': RegistrationEvent
+ 'register:listHeader': RegistrationEvent
+ 'unregister:listFilter': UnregisterEvent
}
-export class FilesRegistryV4 extends TypedEventTarget {}
+/**
+ * Custom event for registry events.
+ * The detail is the registered item.
+ */
+class RegistrationEvent extends CustomEvent {}
-export type PublicFilesRegistry = Pick
+/**
+ * Custom event for unregistering items from the registry.
+ * The detail is the id of the unregistered item.
+ */
+class UnregisterEvent extends RegistrationEvent {}
+
+/**
+ * The registry for files app.
+ * This is used to keep track of registered actions, filters, headers, etc. and to emit events when new items are registered.
+ * Allowing to keep a reactive state of the registered items in the UI without being coupled to one specific reactivity framework.
+ *
+ * This is an internal implementation detail and should not be used directly.
+ *
+ * @internal
+ * @see PublicFilesRegistry - for the public API to listen to registry events.
+ */
+export class FilesRegistry extends TypedEventTarget {}
+
+/**
+ * The registry for files app.
+ * This is used to keep track of registered actions, filters, headers, etc. and to emit events when new items are registered.
+ * Allowing to keep a reactive state of the registered items in the UI without being coupled to one specific reactivity framework.
+ */
+export type PublicFilesRegistry = Pick
/**
- * Get the global files registry
+ * Get the global files registry.
*
* @internal
*/
export function getRegistry() {
- window._nc_files_registry_v4 ??= new FilesRegistryV4()
- return window._nc_files_registry_v4
+ scopedGlobals.registry ??= new FilesRegistry()
+ return scopedGlobals.registry
}
/**
- * Get the global files registry
+ * Get the global files registry.
*
* This allows to listen for new registrations of actions, filters, headers, etc.
* Events are dispatched by the respective registration functions.
diff --git a/lib/sidebar/SidebarAction.ts b/lib/sidebar/SidebarAction.ts
index 2b29c523f..65fe9860e 100644
--- a/lib/sidebar/SidebarAction.ts
+++ b/lib/sidebar/SidebarAction.ts
@@ -5,6 +5,7 @@
import type { ISidebarContext } from './SidebarTab.ts'
+import { scopedGlobals } from '../globalScope.ts'
import logger from '../utils/logger.ts'
/**
@@ -61,12 +62,12 @@ export interface ISidebarAction {
export function registerSidebarAction(action: ISidebarAction): void {
validateSidebarAction(action)
- window._nc_files_sidebar_actions ??= new Map()
- if (window._nc_files_sidebar_actions.has(action.id)) {
+ scopedGlobals.filesSidebarActions ??= new Map()
+ if (scopedGlobals.filesSidebarActions.has(action.id)) {
logger.warn(`Sidebar action with id "${action.id}" already registered. Skipping.`)
return
}
- window._nc_files_sidebar_actions.set(action.id, action)
+ scopedGlobals.filesSidebarActions.set(action.id, action)
logger.debug(`New sidebar action with id "${action.id}" registered.`)
}
@@ -74,8 +75,8 @@ export function registerSidebarAction(action: ISidebarAction): void {
* Get all currently registered sidebar actions.
*/
export function getSidebarActions(): ISidebarAction[] {
- if (window._nc_files_sidebar_actions) {
- return [...window._nc_files_sidebar_actions.values()]
+ if (scopedGlobals.filesSidebarActions) {
+ return [...scopedGlobals.filesSidebarActions.values()]
}
return []
}
diff --git a/lib/sidebar/SidebarTab.ts b/lib/sidebar/SidebarTab.ts
index d1303b19e..f7f313c7c 100644
--- a/lib/sidebar/SidebarTab.ts
+++ b/lib/sidebar/SidebarTab.ts
@@ -7,6 +7,7 @@ import type { IView } from '../navigation/view.ts'
import type { IFolder, INode } from '../node/index.ts'
import isSvg from 'is-svg'
+import { scopedGlobals } from '../globalScope.ts'
import logger from '../utils/logger.ts'
export interface ISidebarContext {
@@ -111,12 +112,12 @@ export interface ISidebarTab {
export function registerSidebarTab(tab: ISidebarTab): void {
validateSidebarTab(tab)
- window._nc_files_sidebar_tabs ??= new Map()
- if (window._nc_files_sidebar_tabs.has(tab.id)) {
+ scopedGlobals.filesSidebarTabs ??= new Map()
+ if (scopedGlobals.filesSidebarTabs.has(tab.id)) {
logger.warn(`Sidebar tab with id "${tab.id}" already registered. Skipping.`)
return
}
- window._nc_files_sidebar_tabs.set(tab.id, tab)
+ scopedGlobals.filesSidebarTabs.set(tab.id, tab)
logger.debug(`New sidebar tab with id "${tab.id}" registered.`)
}
@@ -124,8 +125,8 @@ export function registerSidebarTab(tab: ISidebarTab): void {
* Get all currently registered sidebar tabs.
*/
export function getSidebarTabs(): ISidebarTab[] {
- if (window._nc_files_sidebar_tabs) {
- return [...window._nc_files_sidebar_tabs.values()]
+ if (scopedGlobals.filesSidebarTabs) {
+ return [...scopedGlobals.filesSidebarTabs.values()]
}
return []
}
diff --git a/lib/window.d.ts b/lib/window.d.ts
index f953a2892..519145e35 100644
--- a/lib/window.d.ts
+++ b/lib/window.d.ts
@@ -4,40 +4,16 @@
*/
///
-import type { IFileAction, IFileListAction } from './actions/index.ts'
-import type { DavProperty } from './dav/index.ts'
-import type {
- IFileListFilter,
- IFileListHeader,
- Navigation,
- NewMenu,
-} from './index.ts'
-import type { FilesRegistryV4 } from './registry.ts'
-import type { ISidebarTab } from './sidebar/index.ts'
-import type { ISidebarAction } from './sidebar/SidebarAction.ts'
-
-export {}
-
declare global {
interface Window {
OC: Nextcloud.v32.OC
// eslint-disable-next-line @typescript-eslint/no-explicit-any
OCA: any
- _nc_dav_namespaces?: DavProperty
- _nc_dav_properties?: string[]
- _nc_fileactions?: IFileAction[]
- _nc_filelistactions?: IFileListAction[]
- _nc_filelistheader?: IFileListHeader[]
- _nc_newfilemenu?: NewMenu
- _nc_navigation?: Navigation
- _nc_filelist_filters?: Map
- _nc_files_sidebar_actions?: Map
- _nc_files_sidebar_tabs?: Map
-
- _nc_files_registry_v4?: FilesRegistryV4
-
+ _nc_files_scope?: Record>
_oc_config?: {
forbidden_filenames_characters: string[]
}
}
}
+
+export {}