Merge pull request #57352 from nextcloud/fix/active-files

fix(files): properly handle currently active node and files action hotkeys
This commit is contained in:
Ferdinand Thiessen 2026-01-16 20:47:44 +01:00 committed by GitHub
commit 60a43694cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 320 additions and 776 deletions

View file

@ -1,9 +1,9 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node, View } from '@nextcloud/files'
import type { INode, IView } from '@nextcloud/files'
import type { Capabilities } from '../types.ts'
import axios from '@nextcloud/axios'
@ -17,10 +17,9 @@ import { useUserConfigStore } from '../store/userconfig.ts'
export const isTrashbinEnabled = () => (getCapabilities() as Capabilities)?.files?.undelete === true
/**
*
* @param nodes
*/
export function canUnshareOnly(nodes: Node[]) {
export function canUnshareOnly(nodes: INode[]) {
return nodes.every((node) => node.attributes['is-mount-root'] === true
&& node.attributes['mount-type'] === 'shared')
}
@ -29,7 +28,7 @@ export function canUnshareOnly(nodes: Node[]) {
*
* @param nodes
*/
export function canDisconnectOnly(nodes: Node[]) {
export function canDisconnectOnly(nodes: INode[]) {
return nodes.every((node) => node.attributes['is-mount-root'] === true
&& node.attributes['mount-type'] === 'external')
}
@ -38,7 +37,7 @@ export function canDisconnectOnly(nodes: Node[]) {
*
* @param nodes
*/
export function isMixedUnshareAndDelete(nodes: Node[]) {
export function isMixedUnshareAndDelete(nodes: INode[]) {
if (nodes.length === 1) {
return false
}
@ -52,7 +51,7 @@ export function isMixedUnshareAndDelete(nodes: Node[]) {
*
* @param nodes
*/
export function isAllFiles(nodes: Node[]) {
export function isAllFiles(nodes: INode[]) {
return !nodes.some((node) => node.type !== FileType.File)
}
@ -60,17 +59,18 @@ export function isAllFiles(nodes: Node[]) {
*
* @param nodes
*/
export function isAllFolders(nodes: Node[]) {
export function isAllFolders(nodes: INode[]) {
return !nodes.some((node) => node.type !== FileType.Folder)
}
/**
* Get the display name for the delete action
*
* @param root0
* @param root0.nodes
* @param root0.view
* @param context - The context
* @param context.nodes - The nodes to delete
* @param context.view - The current view
*/
export function displayName({ nodes, view }: { nodes: Node[], view: View }) {
export function displayName({ nodes, view }: { nodes: INode[], view: IView }) {
/**
* If those nodes are all the root node of a
* share, we can only unshare them.
@ -143,7 +143,7 @@ export function shouldAskForConfirmation() {
* @param nodes
* @param view
*/
export async function askConfirmation(nodes: Node[], view: View) {
export async function askConfirmation(nodes: INode[], view: IView) {
const message = view.id === 'trashbin' || !isTrashbinEnabled()
? n('files', 'You are about to permanently delete {count} item', 'You are about to permanently delete {count} items', nodes.length, { count: nodes.length })
: n('files', 'You are about to delete {count} item', 'You are about to delete {count} items', nodes.length, { count: nodes.length })
@ -170,7 +170,7 @@ export async function askConfirmation(nodes: Node[], view: View) {
*
* @param node
*/
export async function deleteNode(node: Node) {
export async function deleteNode(node: INode) {
await axios.delete(node.encodedSource)
// Let's delete even if it's moved to the trashbin

View file

@ -10,7 +10,7 @@ import StarSvg from '@mdi/svg/svg/star.svg?raw'
import axios from '@nextcloud/axios'
import { emit } from '@nextcloud/event-bus'
import { FileAction, Permission } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { t } from '@nextcloud/l10n'
import { encodePath } from '@nextcloud/paths'
import { generateUrl } from '@nextcloud/router'
import { isPublicShare } from '@nextcloud/sharing/public'

View file

@ -54,4 +54,9 @@ export const action = new FileAction({
},
order: -50,
hotkey: {
key: 'D',
description: t('files', 'Open the details sidebar'),
},
})

View file

@ -37,7 +37,11 @@ function hotkeyToString(hotkey: IHotkeyConfig): string {
if (hotkey.shift) {
parts.push('Shift')
}
parts.push(hotkey.key)
if (hotkey.key.match(/^[a-z]$/)) {
parts.push(hotkey.key.toUpperCase())
} else {
parts.push(hotkey.key)
}
return parts.join(' ')
}
</script>
@ -71,7 +75,6 @@ function hotkeyToString(hotkey: IHotkeyConfig): string {
<NcHotkeyList :label="t('files', 'View')">
<NcHotkey :label="t('files', 'Toggle grid view')" hotkey="V" />
<NcHotkey :label="t('files', 'Open file sidebar')" hotkey="D" />
<NcHotkey :label="t('files', 'Show those shortcuts')" hotkey="?" />
</NcHotkeyList>
</NcAppSettingsShortcutsSection>

View file

@ -301,7 +301,11 @@ export default defineComponent({
}
if (this.fileId) {
this.scrollToFile(this.fileId, false)
const node = this.nodes.find((node) => node.fileid === this.fileId)
if (node) {
this.activeStore.activeNode = node
this.scrollToFile(this.fileId, false)
}
}
},
@ -342,13 +346,7 @@ export default defineComponent({
delete query.openfile
delete query.opendetails
this.activeStore.activeNode = undefined
window.OCP.Files.Router.goToRoute(
null,
{ ...this.$route.params, fileid: String(this.currentFolder.fileid ?? '') },
query,
true,
)
this.activeStore.activeNode = this.currentFolder
},
/**
@ -396,7 +394,7 @@ export default defineComponent({
logger.debug('Ignore `openfile` query and replacing with `opendetails` for ' + node.path, { node })
window.OCP.Files.Router.goToRoute(
null,
this.$route.params,
window.OCP.Files.Router.params,
{ ...this.$route.query, openfile: undefined, opendetails: '' },
true, // silent update of the URL
)
@ -431,10 +429,29 @@ export default defineComponent({
},
onKeyDown(event: KeyboardEvent) {
if (this.isEmpty) {
return
}
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown'
&& (!this.userConfig.grid_view || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight'))
) {
// not an arrow key we handle
return
}
if (!this.fileId || this.fileId === this.currentFolder.fileid) {
// no active node so use either first or last node
const index = event.key === 'ArrowUp' || event.key === 'ArrowLeft'
? this.nodes.length - 1
: 0
this.setActiveNode(this.nodes[index] as NcNode & { fileid: number })
}
const index = this.nodes.findIndex((node) => node.fileid === this.fileId) ?? 0
// Up and down arrow keys
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
const columnCount = this.$refs.table?.columnCount ?? 1
const index = this.nodes.findIndex((node) => node.fileid === this.fileId) ?? 0
const nextIndex = event.key === 'ArrowUp' ? index - columnCount : index + columnCount
if (nextIndex < 0 || nextIndex >= this.nodes.length) {
return
@ -450,7 +467,6 @@ export default defineComponent({
// if grid mode, left and right arrow keys
if (this.userConfig.grid_view && (event.key === 'ArrowLeft' || event.key === 'ArrowRight')) {
const index = this.nodes.findIndex((node) => node.fileid === this.fileId) ?? 0
const nextIndex = event.key === 'ArrowLeft' ? index - 1 : index + 1
if (nextIndex < 0 || nextIndex >= this.nodes.length) {
return
@ -465,7 +481,7 @@ export default defineComponent({
}
},
setActiveNode(node: NcNode & { fileid: number }) {
async setActiveNode(node: NcNode & { fileid: number }) {
logger.debug('Navigating to file ' + node.path, { node, fileid: node.fileid })
this.scrollToFile(node.fileid)
@ -473,16 +489,13 @@ export default defineComponent({
const query = { ...this.$route.query }
delete query.openfile
delete query.opendetails
this.activeStore.activeNode = node
// Silent update of the URL
window.OCP.Files.Router.goToRoute(
null,
{ ...this.$route.params, fileid: String(node.fileid) },
await this.$router.replace({
...this.$route,
query,
true,
)
})
// set the new file as active
this.activeStore.activeNode = node
},
},
})

View file

@ -4,18 +4,14 @@
*/
import type { View } from '@nextcloud/files'
import type { Mock } from 'vitest'
import type { Location } from 'vue-router'
import axios from '@nextcloud/axios'
import { File, Folder, Permission } from '@nextcloud/files'
import { File, Folder, Permission, registerFileAction } from '@nextcloud/files'
import { enableAutoDestroy, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { defineComponent, nextTick } from 'vue'
import { action as deleteAction } from '../actions/deleteAction.ts'
import { action as favoriteAction } from '../actions/favoriteAction.ts'
import { action as renameAction } from '../actions/renameAction.ts'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
import { useActiveStore } from '../store/active.ts'
import { useFilesStore } from '../store/files.ts'
import { getPinia } from '../store/index.ts'
@ -63,10 +59,23 @@ const TestComponent = defineComponent({
template: '<div />',
})
beforeAll(() => {
// @ts-expect-error mocking for tests
window.OCP ??= {}
// @ts-expect-error mocking for tests
window.OCP.Files ??= {}
// @ts-expect-error mocking for tests
window.OCP.Files.Router ??= {
...router,
goToRoute: vi.fn(),
}
})
describe('HotKeysService testing', () => {
const activeStore = useActiveStore(getPinia())
let initialState: HTMLInputElement
let component: ReturnType<typeof mount>
enableAutoDestroy(afterEach)
@ -114,54 +123,15 @@ describe('HotKeysService testing', () => {
})))
document.body.appendChild(initialState)
mount(TestComponent)
component = mount(TestComponent)
})
it('Pressing d should open the sidebar once', () => {
dispatchEvent({ key: 'd', code: 'KeyD' })
// tests for register action handling
// Modifier keys should not trigger the action
dispatchEvent({ key: 'd', code: 'KeyD', ctrlKey: true })
dispatchEvent({ key: 'd', code: 'KeyD', altKey: true })
dispatchEvent({ key: 'd', code: 'KeyD', shiftKey: true })
dispatchEvent({ key: 'd', code: 'KeyD', metaKey: true })
expect(sidebarAction.enabled).toHaveReturnedWith(true)
expect(sidebarAction.exec).toHaveBeenCalledOnce()
})
it('Pressing F2 should rename the file', () => {
dispatchEvent({ key: 'F2', code: 'F2' })
// Modifier keys should not trigger the action
dispatchEvent({ key: 'F2', code: 'F2', ctrlKey: true })
dispatchEvent({ key: 'F2', code: 'F2', altKey: true })
dispatchEvent({ key: 'F2', code: 'F2', shiftKey: true })
dispatchEvent({ key: 'F2', code: 'F2', metaKey: true })
expect(renameAction.enabled).toHaveReturnedWith(true)
expect(renameAction.exec).toHaveBeenCalledOnce()
})
it('Pressing s should toggle favorite', () => {
(favoriteAction.enabled as Mock).mockReturnValue(true);
(favoriteAction.exec as Mock).mockImplementationOnce(() => Promise.resolve(null))
vi.spyOn(axios, 'post').mockImplementationOnce(() => Promise.resolve())
dispatchEvent({ key: 's', code: 'KeyS' })
// Modifier keys should not trigger the action
dispatchEvent({ key: 's', code: 'KeyS', ctrlKey: true })
dispatchEvent({ key: 's', code: 'KeyS', altKey: true })
dispatchEvent({ key: 's', code: 'KeyS', shiftKey: true })
dispatchEvent({ key: 's', code: 'KeyS', metaKey: true })
expect(favoriteAction.exec).toHaveBeenCalledOnce()
})
it('Pressing Delete should delete the file', async () => {
// @ts-expect-error unit testing - private method access
vi.spyOn(deleteAction._action, 'exec').mockResolvedValue(() => true)
it('registeres actions', () => {
component.destroy()
registerFileAction(deleteAction)
component = mount(TestComponent)
dispatchEvent({ key: 'Delete', code: 'Delete' })
@ -175,6 +145,8 @@ describe('HotKeysService testing', () => {
expect(deleteAction.exec).toHaveBeenCalledOnce()
})
// actions implemented by the composable
it('Pressing alt+up should go to parent directory', () => {
expect(router.push).toHaveBeenCalledTimes(0)
dispatchEvent({ key: 'ArrowUp', code: 'ArrowUp', altKey: true })
@ -197,9 +169,8 @@ describe('HotKeysService testing', () => {
it.each([
['ctrlKey'],
['altKey'],
// those meta keys are still triggering...
// ['shiftKey'],
// ['metaKey']
['shiftKey'],
['metaKey'],
])('Pressing v with modifier key %s should not toggle grid view', async (modifier: string) => {
vi.spyOn(axios, 'put').mockImplementationOnce(() => Promise.resolve())

View file

@ -1,14 +1,12 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getFileActions } from '@nextcloud/files'
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { dirname } from 'path'
import { useRoute, useRouter } from 'vue-router/composables'
import { action as deleteAction } from '../actions/deleteAction.ts'
import { action as favoriteAction } from '../actions/favoriteAction.ts'
import { action as renameAction } from '../actions/renameAction.ts'
import { action as sidebarAction } from '../actions/sidebarAction.ts'
import logger from '../logger.ts'
import { useUserConfigStore } from '../store/userconfig.ts'
import { executeAction } from '../utils/actionUtils.ts'
@ -25,29 +23,24 @@ export function useHotKeys(): void {
const router = useRouter()
const route = useRoute()
// d opens the sidebar
useHotKey('d', () => executeAction(sidebarAction), {
stop: true,
prevent: true,
})
const actions = getFileActions()
for (const action of actions) {
if (!action.hotkey) {
continue
}
const key = action.hotkey.key.match(/^[a-z]$/)
? action.hotkey.key.toUpperCase()
: action.hotkey.key
// F2 renames the file
useHotKey('F2', () => executeAction(renameAction), {
stop: true,
prevent: true,
})
// s toggle favorite
useHotKey('s', () => executeAction(favoriteAction), {
stop: true,
prevent: true,
})
// Delete deletes the file
useHotKey('Delete', () => executeAction(deleteAction), {
stop: true,
prevent: true,
})
logger.debug(`Register hotkey for action "${action.id}"`)
useHotKey(key, () => executeAction(action), {
stop: true,
prevent: true,
alt: action.hotkey.alt,
ctrl: action.hotkey.ctrl,
shift: action.hotkey.shift,
})
}
// alt+up go to parent directory
useHotKey('ArrowUp', goToParentDir, {

View file

@ -8,7 +8,7 @@ import type { FileAction, IFolder, INode, IView } from '@nextcloud/files'
import { subscribe } from '@nextcloud/event-bus'
import { getNavigation } from '@nextcloud/files'
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { ref, watch } from 'vue'
import logger from '../logger.ts'
export const useActiveStore = defineStore('active', () => {
@ -32,6 +32,21 @@ export const useActiveStore = defineStore('active', () => {
*/
const activeView = ref<IView>()
// Set the active node on the router params
watch(activeNode, () => {
if (typeof activeNode.value?.fileid !== 'number' || activeNode.value.fileid === activeFolder.value?.fileid) {
return
}
logger.debug('Updating active fileid in URL query', { fileid: activeNode.value.fileid })
window.OCP.Files.Router.goToRoute(
null,
{ ...window.OCP.Files.Router.params, fileid: String(activeNode.value.fileid) },
{ ...window.OCP.Files.Router.query },
true,
)
})
initialize()
/**
@ -62,12 +77,10 @@ export const useActiveStore = defineStore('active', () => {
*/
function initialize() {
const navigation = getNavigation()
onChangedView(navigation.active)
// Make sure we only register the listeners once
subscribe('files:node:deleted', onDeletedNode)
onChangedView(navigation.active)
// Or you can react to changes of the current active view
navigation.addEventListener('updateActive', (event) => {
onChangedView(event.detail)

View file

@ -8,17 +8,17 @@ import type { INode, ISidebarContext } from '@nextcloud/files'
import { subscribe } from '@nextcloud/event-bus'
import { getSidebarActions, getSidebarTabs } from '@nextcloud/files'
import { defineStore } from 'pinia'
import { computed, ref, watch } from 'vue'
import { computed, readonly, ref, watch } from 'vue'
import logger from '../logger.ts'
import { useActiveStore } from './active.ts'
import { useFilesStore } from './files.ts'
export const useSidebarStore = defineStore('sidebar', () => {
const activeTab = ref<string>()
const currentNode = ref<INode>()
const isOpen = computed(() => !!currentNode.value)
const isOpen = ref(false)
const activeStore = useActiveStore()
const currentNode = computed(() => isOpen.value ? activeStore.activeNode : undefined)
const hasContext = computed(() => !!(currentNode.value && activeStore.activeFolder && activeStore.activeView))
const currentContext = computed<ISidebarContext | undefined>(() => {
if (!hasContext.value) {
@ -41,7 +41,6 @@ export const useSidebarStore = defineStore('sidebar', () => {
* @param tabId - Optional ID of the tab to activate.
*/
function open(node: INode, tabId?: string) {
const activeStore = useActiveStore()
if (!(node && activeStore.activeFolder && activeStore.activeView)) {
logger.debug('Cannot open sidebar because the active folder or view is not set.', {
node,
@ -64,14 +63,16 @@ export const useSidebarStore = defineStore('sidebar', () => {
} else {
activeTab.value = tabId ?? newTabs[0]?.id
}
currentNode.value = node
logger.debug(`Opening sidebar for ${node.displayname}`, { node })
activeStore.activeNode = node
isOpen.value = true
}
/**
* Close the sidebar.
*/
function close() {
currentNode.value = undefined
isOpen.value = false
}
/**
@ -117,7 +118,7 @@ export const useSidebarStore = defineStore('sidebar', () => {
// update the current node if updated
subscribe('files:node:updated', (node: INode) => {
if (node.source === currentNode.value?.source) {
currentNode.value = node
activeStore.activeNode = node
}
})
@ -144,8 +145,10 @@ export const useSidebarStore = defineStore('sidebar', () => {
subscribe('files:list:updated', () => {
if (!initialized) {
initialized = true
window.OCP.Files.Router._router.afterEach((to) => {
if (to.query && !('opendetails' in to.query)) {
window.OCP.Files.Router._router.afterEach((to, from) => {
if ((from.query && ('opendetails' in from.query))
&& (to.query && !('opendetails' in to.query))) {
logger.debug('Closing sidebar because "opendetails" query parameter was removed from URL.')
close()
}
})
@ -153,37 +156,31 @@ export const useSidebarStore = defineStore('sidebar', () => {
})
// watch open state and update URL query parameters
watch(currentNode, (node) => {
watch(isOpen, (isOpen) => {
const params = { ...(window.OCP?.Files?.Router?.params ?? {}) }
const query = { ...(window.OCP?.Files?.Router?.query ?? {}) }
if (!node && 'opendetails' in query) {
logger.debug(`Sidebar current node changed: ${isOpen ? 'open' : 'closed'}`, { query, params, node: activeStore.activeNode })
if (!isOpen && ('opendetails' in query)) {
delete query.opendetails
window.OCP.Files.Router.goToRoute(
null,
{ ...window.OCP.Files.Router.params },
{
...query,
},
params,
query,
true,
)
}
if (node) {
const fileid = String(node.fileid)
if (!('opendetails' in query) || window.OCP.Files.Router.params.fileid !== fileid) {
window.OCP.Files.Router.goToRoute(
null,
{
...window.OCP.Files.Router.params,
fileid,
},
{
...query,
opendetails: 'true',
},
true,
)
}
if (isOpen && !('opendetails' in query)) {
window.OCP.Files.Router.goToRoute(
null,
params,
{
...query,
opendetails: 'true',
},
true,
)
}
})
@ -194,7 +191,7 @@ export const useSidebarStore = defineStore('sidebar', () => {
currentNode,
currentTabs,
hasContext,
isOpen,
isOpen: readonly(isOpen),
open,
close,

View file

@ -1,9 +1,9 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { INode } from '@nextcloud/files'
import type { ActionContext, ActionContextSingle } from '@nextcloud/files'
import TagMultipleSvg from '@mdi/svg/svg/tag-multiple-outline.svg?raw'
import { FileAction, Permission } from '@nextcloud/files'
@ -15,10 +15,10 @@ import { defineAsyncComponent } from 'vue'
/**
* Spawn a dialog to add or remove tags from multiple nodes.
*
* @param nodes Nodes to modify tags for
* @param nodes.nodes
* @param context - The action context
* @param context.nodes - Nodes to modify tags for
*/
async function execBatch({ nodes }: { nodes: INode[] }): Promise<(null | boolean)[]> {
async function execBatch({ nodes }: ActionContext | ActionContextSingle): Promise<(null | boolean)[]> {
const response = await new Promise<null | boolean>((resolve) => {
spawnDialog(defineAsyncComponent(() => import('../components/SystemTagPicker.vue')), {
nodes,
@ -53,9 +53,15 @@ export const action = new FileAction({
return !nodes.some((node) => (node.permissions & Permission.UPDATE) === 0)
},
async exec({ nodes }) {
return execBatch({ nodes })[0]
async exec(context: ActionContextSingle) {
const [result] = await execBatch(context)
return result
},
execBatch,
hotkey: {
description: t('systemtags', 'Manage tags'),
key: 't',
},
})

View file

@ -1,9 +1,9 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
import type { INode } from '@nextcloud/files'
import type { TagWithId } from '../types.ts'
import { subscribe } from '@nextcloud/event-bus'
@ -44,11 +44,6 @@ export const action = new FileAction({
},
order: 0,
hotkey: {
description: t('files', 'Manage tags'),
key: 'T',
},
})
// Subscribe to the events
@ -62,7 +57,7 @@ subscribe('systemtags:tag:updated', updateTag)
*
* @param node - The updated node
*/
function updateSystemTagsHtml(node: Node) {
function updateSystemTagsHtml(node: INode) {
renderInline(node).then((systemTagsHtml) => {
document.querySelectorAll(`[data-systemtags-fileid="${node.fileid}"]`).forEach((element) => {
element.replaceWith(systemTagsHtml)
@ -113,9 +108,10 @@ function updateSystemTagsColorAttribute(tag: TagWithId) {
}
/**
* Render a single tag element
*
* @param tag
* @param isMore
* @param tag - The tag to render
* @param isMore - Whether this is a "more" tag
*/
function renderTag(tag: string, isMore = false): HTMLElement {
const tagElement = document.createElement('li')
@ -143,10 +139,11 @@ function renderTag(tag: string, isMore = false): HTMLElement {
}
/**
* Render the inline system tags for a node
*
* @param node
* @param node - The node to render the tags for
*/
async function renderInline(node: Node): Promise<HTMLElement> {
async function renderInline(node: INode): Promise<HTMLElement> {
// Ensure we have the system tags as an array
const tags = getNodeSystemTags(node)

View file

@ -10,7 +10,6 @@ import { registerFileSidebarAction } from './files_actions/filesSidebarAction.ts
import { action as inlineSystemTagsAction } from './files_actions/inlineSystemTagsAction.ts'
import { action as openInFilesAction } from './files_actions/openInFilesAction.ts'
import { registerSystemTagsView } from './files_views/systemtagsView.ts'
import { registerHotkeys } from './services/HotKeysService.ts'
registerDavProperty('nc:system-tags')
registerFileAction(bulkSystemTagsAction)
@ -19,7 +18,3 @@ registerFileAction(openInFilesAction)
registerSystemTagsView()
registerFileSidebarAction()
document.addEventListener('DOMContentLoaded', () => {
registerHotkeys()
})

View file

@ -1,75 +0,0 @@
import type { View } from '@nextcloud/files'
import { File, Folder, Permission } from '@nextcloud/files'
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { useActiveStore } from '../../../files/src/store/active.ts'
import { getPinia } from '../../../files/src/store/index.ts'
import { action as bulkSystemTagsAction } from '../files_actions/bulkSystemTagsAction.ts'
import { registerHotkeys } from './HotKeysService.ts'
let file: File
const view = {
id: 'files',
name: 'Files',
} as View
vi.mock('../files_actions/bulkSystemTagsAction.ts', { spy: true })
describe('HotKeysService testing', () => {
const activeStore = useActiveStore(getPinia())
beforeAll(() => {
registerHotkeys()
})
beforeEach(() => {
// Make sure the file is reset before each test
file = new File({
id: 2,
source: 'https://cloud.domain.com/remote.php/dav/files/admin/foobar.txt',
root: '/files/admin',
owner: 'admin',
mime: 'text/plain',
permissions: Permission.ALL,
})
const root = new Folder({
id: 1,
source: 'https://cloud.domain.com/remote.php/dav/files/admin/',
root: '/files/admin',
owner: 'admin',
permissions: Permission.CREATE,
})
// Setting the view first as it reset the active node
activeStore.activeView = view
activeStore.activeNode = file
activeStore.activeFolder = root
})
it('Pressing t should open the tag management dialog', () => {
dispatchEvent({ key: 't', code: 'KeyT' })
// Modifier keys should not trigger the action
dispatchEvent({ key: 't', code: 'KeyT', ctrlKey: true })
dispatchEvent({ key: 't', code: 'KeyT', altKey: true })
dispatchEvent({ key: 't', code: 'KeyT', shiftKey: true })
dispatchEvent({ key: 't', code: 'KeyT', metaKey: true })
expect(bulkSystemTagsAction.enabled).toHaveReturnedWith(true)
expect(bulkSystemTagsAction.exec).toHaveBeenCalledOnce()
})
})
/**
* Helper to dispatch the correct event.
*
* @param init - KeyboardEvent options
*/
function dispatchEvent(init: KeyboardEventInit) {
document.body.dispatchEvent(new KeyboardEvent('keydown', { ...init, bubbles: true }))
}

View file

@ -1,23 +0,0 @@
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { useHotKey } from '@nextcloud/vue/composables/useHotKey'
import { executeAction } from '../../../files/src/utils/actionUtils.ts'
import { action as manageTagAction } from '../files_actions/bulkSystemTagsAction.ts'
import logger from '../logger.ts'
/**
* This register the hotkeys for the Files app.
* As much as possible, we try to have all the hotkeys in one place.
* Please make sure to add tests for the hotkeys after adding a new one.
*/
export function registerHotkeys() {
// t opens the tag management dialog
useHotKey('t', () => executeAction(manageTagAction), {
stop: true,
prevent: true,
})
logger.debug('Hotkeys registered')
}

View file

@ -7,7 +7,7 @@ import type { User } from '@nextcloud/e2e-test-server/cypress'
const ACTION_COPY_MOVE = 'move-copy'
export const getRowForFileId = (fileid: number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"]`)
export const getRowForFileId = (fileid: string | number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"]`)
export const getRowForFile = (filename: string) => cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"]`)
export const getActionsForFileId = (fileid: number) => getRowForFileId(fileid).find('[data-cy-files-list-row-actions]')

View file

@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getRowForFileId } from './FilesUtils.ts'
describe('Files hotkey handling', () => {
before(() => {
cy.createRandomUser().then((user) => {
cy.mkdir(user, '/abcd')
cy.mkdir(user, '/zyx')
cy.rm(user, '/welcome.txt')
cy.login(user)
})
})
beforeEach(() => cy.visit('/apps/files'))
it('Pressing "arrow down" should go to first file', () => {
cy.get('[data-cy-files-list]')
.press(Cypress.Keyboard.Keys.DOWN)
cy.url()
.should('match', /\/apps\/files\/files\/\d+/)
.then((url) => new URL(url).pathname.split('/').at(-1))
.then((fileId) => getRowForFileId(fileId)
.should('exist')
.and('have.attr', 'data-cy-files-list-row-name', 'abcd'))
})
it('Pressing "arrow up" should go to first file', () => {
cy.get('[data-cy-files-list]')
.press(Cypress.Keyboard.Keys.UP)
cy.url()
.should('match', /\/apps\/files\/files\/\d+/)
.then((url) => new URL(url).pathname.split('/').at(-1))
.then((fileId) => getRowForFileId(fileId)
.should('exist')
.and('have.attr', 'data-cy-files-list-row-name', 'zyx'))
})
it('Pressing D should open the sidebar once', () => {
activateFirstRow()
cy.get('[data-cy-files-list]')
.press('d')
cy.get('[data-cy-sidebar]')
.should('exist')
.and('be.visible')
})
it('Pressing F2 should rename the file', () => {
activateFirstRow()
cy.get('[data-cy-files-list]')
.should('exist')
.then(($el) => {
const el = $el.get(0)
// manually dispatch as Cypress refuses to press F-keys for "security reasons"
cy.log('Dispatching F2 keydown/keyup events')
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'F2', code: 'F2', bubbles: true }))
el.dispatchEvent(new KeyboardEvent('keyup', { key: 'F2', code: 'F2', bubbles: true }))
el.dispatchEvent(new KeyboardEvent('keypress', { key: 'F2', code: 'F2', bubbles: true }))
})
cy.get('[data-cy-files-list-row-name]')
.first()
.findByRole('textbox', { name: /Folder name/ })
.should('exist')
})
it('Pressing S should toggle favorite', () => {
activateFirstRow()
cy.get('[data-cy-files-list]')
.press('s')
cy.get('[data-cy-files-list-row-name]')
.first()
.as('firstRow')
.findByRole('img', { name: /Favorite/ })
.should('exist')
cy.get('[data-cy-files-list]')
.press('s')
cy.get('@firstRow')
.findByRole('img', { name: /Favorite/ })
.should('not.exist')
})
it('Pressing DELETE should delete the folder', () => {
activateFirstRow()
cy.get('td[data-cy-files-list-row-name]')
.should('have.length', 2)
cy.get('[data-cy-files-list]')
.press(Cypress.Keyboard.Keys.DELETE)
cy.get('td[data-cy-files-list-row-name]')
.should('have.length', 1)
})
})
/**
* Activates the first row in the files list by simulating a press of the down arrow key.
*/
function activateFirstRow() {
cy.get('[data-cy-files-list]')
.press(Cypress.Keyboard.Keys.DOWN)
cy.url()
.should('match', /\/apps\/files\/files\/\d+/)
}

2
dist/2605-2605.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
2605-2605.js.license

2
dist/3442-3442.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/3442-3442.js.map.license vendored Symbolic link
View file

@ -0,0 +1 @@
3442-3442.js.license

4
dist/core-common.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/files-init.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/files-main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -5,59 +5,36 @@ SPDX-License-Identifier: BSD-3-Clause
SPDX-License-Identifier: BSD-2-Clause
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
SPDX-License-Identifier: (MIT AND BSD-3-Clause)
SPDX-FileCopyrightText: string_decoder developers
SPDX-FileCopyrightText: ripemd160 developers
SPDX-FileCopyrightText: rhysd <lin90162@yahoo.co.jp>
SPDX-FileCopyrightText: readable-stream developers
SPDX-FileCopyrightText: qs developers
SPDX-FileCopyrightText: p-queue developers
SPDX-FileCopyrightText: omahlama
SPDX-FileCopyrightText: jden <jason@denizac.org>
SPDX-FileCopyrightText: inline-style-parser developers
SPDX-FileCopyrightText: inherits developers
SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: defunctzombie
SPDX-FileCopyrightText: debounce developers
SPDX-FileCopyrightText: date-fns developers
SPDX-FileCopyrightText: chenkai
SPDX-FileCopyrightText: browserify-sign developers
SPDX-FileCopyrightText: browserify-rsa developers
SPDX-FileCopyrightText: atomiks
SPDX-FileCopyrightText: Varun A P
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
SPDX-FileCopyrightText: Stefan Thomas <justmoon@members.fsf.org> (http://www.justmoon.net)
SPDX-FileCopyrightText: Sindre Sorhus
SPDX-FileCopyrightText: Shuhei Kagawa
SPDX-FileCopyrightText: Scott Cooper <scttcper@gmail.com>
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
SPDX-FileCopyrightText: Raynos <raynos2@gmail.com>
SPDX-FileCopyrightText: Perry Mitchell <perry@perrymitchell.net>
SPDX-FileCopyrightText: Paul Vorbach <paul@vorba.ch> (http://paul.vorba.ch)
SPDX-FileCopyrightText: Paul Vorbach <paul@vorb.de> (http://vorb.de)
SPDX-FileCopyrightText: OpenJS Foundation and other contributors
SPDX-FileCopyrightText: Olivier Scherrer <pode.fr@gmail.com>
SPDX-FileCopyrightText: Nick Frasser (https://nfrasser.com)
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
SPDX-FileCopyrightText: Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)
SPDX-FileCopyrightText: Max <max@nextcloud.com>
SPDX-FileCopyrightText: Matt Zabriskie
SPDX-FileCopyrightText: Mathias Bynens
SPDX-FileCopyrightText: Mathias Buus (@mafintosh)
SPDX-FileCopyrightText: Mark <mark@remarkablemark.org>
SPDX-FileCopyrightText: Kirill Fomichev <fanatid@ya.ru> (https://github.com/fanatid)
SPDX-FileCopyrightText: Julian Gruber
SPDX-FileCopyrightText: Joyent
SPDX-FileCopyrightText: José F. Romaniello <jfromaniello@gmail.com> (http://joseoncode.com)
SPDX-FileCopyrightText: Jordan Humphreys <jordan@zurb.com>
SPDX-FileCopyrightText: Jordan Harband <ljharb@gmail.com>
SPDX-FileCopyrightText: Jordan Harband
SPDX-FileCopyrightText: Jordan Harbamd <ljharb@gmail.com>
SPDX-FileCopyrightText: Jonas Schade <derzade@gmail.com>
SPDX-FileCopyrightText: John Hiesey
SPDX-FileCopyrightText: Jeff Sagal <sagalbot@gmail.com>
SPDX-FileCopyrightText: James Halliday
SPDX-FileCopyrightText: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)
SPDX-FileCopyrightText: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)
@ -66,46 +43,25 @@ SPDX-FileCopyrightText: Guillaume Chau
SPDX-FileCopyrightText: GitHub Inc.
SPDX-FileCopyrightText: Feross Aboukhadijeh
SPDX-FileCopyrightText: Felix Boehm <me@feedic.com>
SPDX-FileCopyrightText: Fedor Indutny <fedor@indutny.com>
SPDX-FileCopyrightText: Fedor Indutny
SPDX-FileCopyrightText: Evan You
SPDX-FileCopyrightText: Eugene Sharygin <eush77@gmail.com>
SPDX-FileCopyrightText: Eric Norris (https://github.com/ericnorris)
SPDX-FileCopyrightText: Eduardo San Martin Morote
SPDX-FileCopyrightText: Dylan Piercey <pierceydylan@gmail.com>
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
SPDX-FileCopyrightText: Dominic Tarr <dominic.tarr@gmail.com> (dominictarr.com)
SPDX-FileCopyrightText: David Clark
SPDX-FileCopyrightText: Daniel Cousens
SPDX-FileCopyrightText: Christoph Wurst
SPDX-FileCopyrightText: Calvin Metcalf <calvin.metcalf@gmail.com>
SPDX-FileCopyrightText: Calvin Metcalf
SPDX-FileCopyrightText: Borys Serebrov
SPDX-FileCopyrightText: Ben Drucker
SPDX-FileCopyrightText: Arnout Kazemier
SPDX-FileCopyrightText: Antoni Andre <antoniandre.web@gmail.com>
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
SPDX-FileCopyrightText: Anthony Fu <anthonyfu117@hotmail.com>
SPDX-FileCopyrightText: Andrea Giammarchi
SPDX-FileCopyrightText: Amit Gupta (https://solothought.com)
SPDX-FileCopyrightText: Amit Gupta (https://amitkumargupta.work/)
SPDX-FileCopyrightText: @nextcloud/dialogs developers
SPDX-FileCopyrightText:
This file is generated from multiple sources. Included packages:
- @buttercup/fetch
- version: 0.2.1
- license: MIT
- @ctrl/tinycolor
- version: 3.6.1
- license: MIT
- @floating-ui/core
- version: 1.7.3
- license: MIT
- @floating-ui/dom
- version: 1.7.4
- license: MIT
- @floating-ui/utils
- version: 0.2.10
- license: MIT
@ -121,45 +77,6 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- @ckpack/vue-color
- version: 1.6.0
- license: MIT
- @nextcloud/paths
- version: 2.4.0
- license: GPL-3.0-or-later
- @nextcloud/browser-storage
- version: 0.4.0
- license: GPL-3.0-or-later
- @nextcloud/vue
- version: 9.0.1
- license: AGPL-3.0-or-later
- @vueuse/core
- version: 13.9.0
- license: MIT
- @vueuse/shared
- version: 13.9.0
- license: MIT
- debounce
- version: 2.2.0
- license: MIT
- rehype-react
- version: 8.0.0
- license: MIT
- splitpanes
- version: 4.0.4
- license: MIT
- vue-router
- version: 4.6.3
- license: MIT
- vue-select
- version: 4.0.0-beta.6
- license: MIT
- vue
- version: 3.5.22
- license: MIT
- @nextcloud/dialogs
- version: 7.1.0
- license: AGPL-3.0-or-later
- semver
- version: 7.7.2
- license: ISC
@ -214,36 +131,9 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/vue
- version: 8.35.0
- license: AGPL-3.0-or-later
- @ungap/structured-clone
- version: 1.3.0
- license: ISC
- @vue/devtools-api
- version: 6.6.4
- license: MIT
- @vue/reactivity
- version: 3.5.22
- license: MIT
- @vue/runtime-core
- version: 3.5.22
- license: MIT
- @vue/runtime-dom
- version: 3.5.22
- license: MIT
- @vue/shared
- version: 3.5.22
- license: MIT
- @vueuse/core
- version: 11.3.0
- license: MIT
- @vueuse/shared
- version: 11.3.0
- license: MIT
- bn.js
- version: 4.12.2
- license: MIT
- asn1.js
- version: 4.10.1
- license: MIT
- available-typed-arrays
- version: 1.0.7
- license: MIT
@ -259,36 +149,9 @@ This file is generated from multiple sources. Included packages:
- base64-js
- version: 1.5.1
- license: MIT
- blurhash
- version: 2.0.5
- license: MIT
- bn.js
- version: 5.2.2
- license: MIT
- brace-expansion
- version: 2.0.2
- license: MIT
- brorand
- version: 1.1.0
- license: MIT
- browserify-aes
- version: 1.2.0
- license: MIT
- browserify-cipher
- version: 1.0.1
- license: MIT
- browserify-des
- version: 1.0.2
- license: MIT
- browserify-rsa
- version: 4.1.1
- license: MIT
- browserify-sign
- version: 4.2.5
- license: ISC
- buffer-xor
- version: 1.0.3
- license: MIT
- buffer
- version: 5.7.1
- license: MIT
@ -313,75 +176,24 @@ This file is generated from multiple sources. Included packages:
- charenc
- version: 0.0.2
- license: BSD-3-Clause
- cipher-base
- version: 1.0.7
- license: MIT
- comma-separated-tokens
- version: 2.0.3
- license: MIT
- core-util-is
- version: 1.0.3
- license: MIT
- bn.js
- version: 4.12.2
- license: MIT
- create-ecdh
- version: 4.0.4
- license: MIT
- create-hash
- version: 1.2.0
- license: MIT
- create-hmac
- version: 1.1.7
- license: MIT
- crypt
- version: 0.0.2
- license: BSD-3-Clause
- crypto-browserify
- version: 3.12.1
- license: MIT
- css-loader
- version: 7.1.2
- license: MIT
- date-fns
- version: 4.1.0
- license: MIT
- debounce
- version: 3.0.0
- license: MIT
- decode-named-character-reference
- version: 1.2.0
- license: MIT
- define-data-property
- version: 1.1.4
- license: MIT
- des.js
- version: 1.1.0
- license: MIT
- devlop
- version: 1.1.0
- license: MIT
- bn.js
- version: 4.12.2
- license: MIT
- diffie-hellman
- version: 5.0.3
- license: MIT
- dompurify
- version: 3.3.1
- license: (MPL-2.0 OR Apache-2.0)
- dunder-proto
- version: 1.0.1
- license: MIT
- bn.js
- version: 4.12.2
- license: MIT
- elliptic
- version: 6.6.1
- license: MIT
- emoji-mart-vue-fast
- version: 15.0.5
- license: BSD-3-Clause
- es-define-property
- version: 1.0.1
- license: MIT
@ -394,18 +206,9 @@ This file is generated from multiple sources. Included packages:
- escape-html
- version: 1.0.3
- license: MIT
- estree-util-is-identifier-name
- version: 3.0.0
- license: MIT
- events
- version: 3.3.0
- license: MIT
- evp_bytestokey
- version: 1.0.3
- license: MIT
- extend
- version: 3.0.2
- license: MIT
- focus-trap
- version: 7.6.6
- license: MIT
@ -436,30 +239,9 @@ This file is generated from multiple sources. Included packages:
- has-tostringtag
- version: 1.0.2
- license: MIT
- hash-base
- version: 3.0.5
- license: MIT
- hash.js
- version: 1.1.7
- license: MIT
- hasown
- version: 2.0.2
- license: MIT
- hast-util-is-element
- version: 3.0.0
- license: MIT
- hast-util-whitespace
- version: 3.0.0
- license: MIT
- property-information
- version: 7.1.0
- license: MIT
- hast-util-to-jsx-runtime
- version: 2.3.6
- license: MIT
- hmac-drbg
- version: 1.0.1
- license: MIT
- hot-patcher
- version: 2.0.1
- license: MIT
@ -472,9 +254,6 @@ This file is generated from multiple sources. Included packages:
- inherits
- version: 2.0.4
- license: ISC
- is-absolute-url
- version: 4.0.1
- license: MIT
- is-arguments
- version: 1.2.0
- license: MIT
@ -496,120 +275,15 @@ This file is generated from multiple sources. Included packages:
- is-typed-array
- version: 1.1.15
- license: MIT
- isarray
- version: 1.0.0
- license: MIT
- jquery
- version: 3.7.1
- license: MIT
- layerr
- version: 3.0.0
- license: MIT
- linkifyjs
- version: 4.3.2
- license: MIT
- material-colors
- version: 1.2.6
- license: ISC
- math-intrinsics
- version: 1.1.0
- license: MIT
- md5.js
- version: 1.3.5
- license: MIT
- md5
- version: 2.3.0
- license: BSD-3-Clause
- mdast-squeeze-paragraphs
- version: 6.0.0
- license: MIT
- escape-string-regexp
- version: 5.0.0
- license: MIT
- mdast-util-find-and-replace
- version: 3.0.2
- license: MIT
- mdast-util-from-markdown
- version: 2.0.2
- license: MIT
- mdast-util-newline-to-break
- version: 2.0.0
- license: MIT
- mdast-util-to-hast
- version: 13.2.1
- license: MIT
- mdast-util-to-string
- version: 4.0.0
- license: MIT
- micromark-core-commonmark
- version: 2.0.3
- license: MIT
- micromark-factory-destination
- version: 2.0.1
- license: MIT
- micromark-factory-label
- version: 2.0.1
- license: MIT
- micromark-factory-space
- version: 2.0.1
- license: MIT
- micromark-factory-title
- version: 2.0.1
- license: MIT
- micromark-factory-whitespace
- version: 2.0.1
- license: MIT
- micromark-util-character
- version: 2.1.1
- license: MIT
- micromark-util-chunked
- version: 2.0.1
- license: MIT
- micromark-util-classify-character
- version: 2.0.1
- license: MIT
- micromark-util-combine-extensions
- version: 2.0.1
- license: MIT
- micromark-util-decode-numeric-character-reference
- version: 2.0.2
- license: MIT
- micromark-util-decode-string
- version: 2.0.1
- license: MIT
- micromark-util-encode
- version: 2.0.1
- license: MIT
- micromark-util-html-tag-name
- version: 2.0.1
- license: MIT
- micromark-util-normalize-identifier
- version: 2.0.1
- license: MIT
- micromark-util-resolve-all
- version: 2.0.1
- license: MIT
- micromark-util-sanitize-uri
- version: 2.0.1
- license: MIT
- micromark-util-subtokenize
- version: 2.1.0
- license: MIT
- micromark
- version: 4.0.2
- license: MIT
- bn.js
- version: 4.12.2
- license: MIT
- miller-rabin
- version: 4.0.1
- license: MIT
- minimalistic-assert
- version: 1.0.1
- license: ISC
- minimalistic-crypto-utils
- version: 1.0.1
- license: MIT
- nested-property
- version: 4.0.0
- license: MIT
@ -619,42 +293,15 @@ This file is generated from multiple sources. Included packages:
- object-inspect
- version: 1.13.4
- license: MIT
- eventemitter3
- version: 5.0.1
- license: MIT
- p-queue
- version: 9.0.1
- license: MIT
- p-timeout
- version: 7.0.1
- license: MIT
- parse-asn1
- version: 5.1.9
- license: ISC
- path-posix
- version: 1.0.0
- license: ISC
- pbkdf2
- version: 3.1.5
- license: MIT
- pinia
- version: 2.3.1
- license: MIT
- possible-typed-array-names
- version: 1.1.0
- license: MIT
- process-nextick-args
- version: 2.0.1
- license: MIT
- process
- version: 0.11.10
- license: MIT
- bn.js
- version: 4.12.2
- license: MIT
- public-encrypt
- version: 4.0.3
- license: MIT
- punycode
- version: 1.4.1
- license: MIT
@ -664,45 +311,9 @@ This file is generated from multiple sources. Included packages:
- querystringify
- version: 2.2.0
- license: MIT
- randombytes
- version: 2.1.0
- license: MIT
- randomfill
- version: 1.0.4
- license: MIT
- safe-buffer
- version: 5.1.2
- license: MIT
- string_decoder
- version: 1.1.1
- license: MIT
- readable-stream
- version: 2.3.8
- license: MIT
- rehype-external-links
- version: 3.0.0
- license: MIT
- remark-breaks
- version: 4.0.0
- license: MIT
- remark-parse
- version: 11.0.0
- license: MIT
- remark-rehype
- version: 11.1.2
- license: MIT
- remark-unlink-protocols
- version: 1.0.0
- license: MIT
- requires-port
- version: 1.0.0
- license: MIT
- hash-base
- version: 3.1.2
- license: MIT
- ripemd160
- version: 2.0.3
- license: MIT
- safe-buffer
- version: 5.2.1
- license: MIT
@ -715,9 +326,6 @@ This file is generated from multiple sources. Included packages:
- set-function-length
- version: 1.2.2
- license: MIT
- sha.js
- version: 2.4.12
- license: (MIT AND BSD-3-Clause)
- side-channel-list
- version: 1.0.0
- license: MIT
@ -730,9 +338,6 @@ This file is generated from multiple sources. Included packages:
- side-channel
- version: 1.1.0
- license: MIT
- space-separated-tokens
- version: 2.0.2
- license: MIT
- readable-stream
- version: 3.6.2
- license: MIT
@ -748,69 +353,15 @@ This file is generated from multiple sources. Included packages:
- string_decoder
- version: 1.3.0
- license: MIT
- striptags
- version: 3.2.0
- license: MIT
- style-loader
- version: 4.0.0
- license: MIT
- inline-style-parser
- version: 0.2.4
- license: MIT
- style-to-object
- version: 1.0.11
- license: MIT
- style-to-js
- version: 1.1.18
- license: MIT
- tabbable
- version: 6.3.0
- license: MIT
- isarray
- version: 2.0.5
- license: MIT
- to-buffer
- version: 1.2.2
- license: MIT
- toastify-js
- version: 1.12.0
- license: MIT
- tributejs
- version: 5.1.3
- license: MIT
- trim-lines
- version: 3.0.1
- license: MIT
- trough
- version: 2.2.0
- license: MIT
- typed-array-buffer
- version: 1.0.3
- license: MIT
- typescript-event-target
- version: 1.1.2
- license: MIT
- unified
- version: 11.0.5
- license: MIT
- unist-builder
- version: 4.0.0
- license: MIT
- unist-util-is
- version: 6.0.0
- license: MIT
- unist-util-position
- version: 5.0.0
- license: MIT
- unist-util-stringify-position
- version: 4.0.0
- license: MIT
- unist-util-visit-parents
- version: 6.0.1
- license: MIT
- unist-util-visit
- version: 5.0.0
- license: MIT
- url-join
- version: 5.0.0
- license: MIT
@ -826,21 +377,6 @@ This file is generated from multiple sources. Included packages:
- util
- version: 0.12.5
- license: MIT
- vfile-message
- version: 4.0.3
- license: MIT
- vfile
- version: 6.0.3
- license: MIT
- vm-browserify
- version: 1.1.2
- license: MIT
- vue-demi
- version: 0.14.10
- license: MIT
- vue-loader
- version: 15.11.1
- license: MIT
- vue
- version: 2.7.16
- license: MIT

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long