diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/files/src/components/FileEntry.vue | 15 | ||||
-rw-r--r-- | apps/files/src/components/FilesListVirtual.vue | 35 | ||||
-rw-r--r-- | apps/files/src/components/NewNodeDialog.vue | 149 | ||||
-rw-r--r-- | apps/files/src/init-templates.ts | 149 | ||||
-rw-r--r-- | apps/files/src/init.ts | 7 | ||||
-rw-r--r-- | apps/files/src/newMenu/newFolder.ts | 40 | ||||
-rw-r--r-- | apps/files/src/newMenu/newFromTemplate.ts | 88 | ||||
-rw-r--r-- | apps/files/src/newMenu/newTemplatesFolder.ts | 100 | ||||
-rw-r--r-- | apps/files/src/utils/newNodeDialog.ts | 57 | ||||
-rw-r--r-- | apps/files/src/views/FilesList.vue | 9 |
10 files changed, 469 insertions, 180 deletions
diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue index 0ccd5622a5e..973e1de667f 100644 --- a/apps/files/src/components/FileEntry.vue +++ b/apps/files/src/components/FileEntry.vue @@ -21,7 +21,11 @@ --> <template> - <tr :class="{'files-list__row--dragover': dragover, 'files-list__row--loading': isLoading}" + <tr :class="{ + 'files-list__row--dragover': dragover, + 'files-list__row--loading': isLoading, + 'files-list__row--active': isActive, + }" data-cy-files-list-row :data-cy-files-list-row-fileid="fileid" :data-cy-files-list-row-name="source.basename" @@ -97,7 +101,7 @@ <script lang="ts"> import { defineComponent } from 'vue' -import { formatFileSize } from '@nextcloud/files' +import { Permission, formatFileSize } from '@nextcloud/files' import moment from '@nextcloud/moment' import { useActionsMenuStore } from '../store/actionsmenu.ts' @@ -232,6 +236,13 @@ export default defineComponent({ } return '' }, + + /** + * This entry is the current active node + */ + isActive() { + return this.fileid === this.currentFileId?.toString?.() + }, }, methods: { diff --git a/apps/files/src/components/FilesListVirtual.vue b/apps/files/src/components/FilesListVirtual.vue index afb2dbd888a..b6a11391dc1 100644 --- a/apps/files/src/components/FilesListVirtual.vue +++ b/apps/files/src/components/FilesListVirtual.vue @@ -139,6 +139,7 @@ export default defineComponent({ FileEntryGrid, headers: getFileListHeaders(), scrollToIndex: 0, + openFileId: null as number|null, } }, @@ -151,6 +152,14 @@ export default defineComponent({ return parseInt(this.$route.params.fileid) || null }, + /** + * If the current `fileId` should be opened + * The state of the `openfile` query param + */ + openFile() { + return !!this.$route.query.openfile + }, + summary() { return getSummaryFor(this.nodes) }, @@ -199,6 +208,12 @@ export default defineComponent({ fileId(fileId) { this.scrollToFile(fileId, false) }, + + openFile(open: boolean) { + if (open) { + this.$nextTick(() => this.handleOpenFile(this.fileId)) + } + }, }, mounted() { @@ -206,9 +221,11 @@ export default defineComponent({ const mainContent = window.document.querySelector('main.app-content') as HTMLElement mainContent.addEventListener('dragover', this.onDragOver) - this.scrollToFile(this.fileId) - this.openSidebarForFile(this.fileId) - this.handleOpenFile() + // handle initially opening a given file + const { id } = loadState<{ id?: number }>('files', 'openFileInfo', {}) + this.scrollToFile(id ?? this.fileId) + this.openSidebarForFile(id ?? this.fileId) + this.handleOpenFile(id ?? null) }, beforeDestroy() { @@ -241,18 +258,22 @@ export default defineComponent({ } }, - handleOpenFile() { - const openFileInfo = loadState('files', 'openFileInfo', {}) as ({ id?: number }) - if (openFileInfo === undefined) { + /** + * Handle opening a file (e.g. by ?openfile=true) + * @param fileId File to open + */ + handleOpenFile(fileId: number|null) { + if (fileId === null || this.openFileId === fileId) { return } - const node = this.nodes.find(n => n.fileid === openFileInfo.id) as NcNode + const node = this.nodes.find(n => n.fileid === fileId) as NcNode if (node === undefined || node.type === FileType.Folder) { return } logger.debug('Opening file ' + node.path, { node }) + this.openFileId = fileId getFileActions() .filter(action => !action.enabled || action.enabled([node], this.currentView)) .sort((a, b) => (a.order || 0) - (b.order || 0)) diff --git a/apps/files/src/components/NewNodeDialog.vue b/apps/files/src/components/NewNodeDialog.vue new file mode 100644 index 00000000000..38337ddf4b8 --- /dev/null +++ b/apps/files/src/components/NewNodeDialog.vue @@ -0,0 +1,149 @@ +<!-- + - @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de> + - + - @author Ferdinand Thiessen <opensource@fthiessen.de> + - + - @license AGPL-3.0-or-later + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> +<template> + <NcDialog :name="name" + :open="open" + close-on-click-outside + out-transition + @update:open="onClose"> + <template #actions> + <NcButton type="primary" + :disabled="!isUniqueName" + @click="onCreate"> + {{ t('files', 'Create') }} + </NcButton> + </template> + <form @submit.prevent="onCreate"> + <NcTextField ref="input" + :error="!isUniqueName" + :helper-text="errorMessage" + :label="label" + :value.sync="localDefaultName" /> + </form> + </NcDialog> +</template> + +<script lang="ts"> +import type { PropType } from 'vue' + +import { defineComponent } from 'vue' +import { translate as t } from '@nextcloud/l10n' +import { getUniqueName } from '../utils/fileUtils' + +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js' +import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' + +interface ICanFocus { + focus: () => void +} + +export default defineComponent({ + name: 'NewNodeDialog', + components: { + NcButton, + NcDialog, + NcTextField, + }, + props: { + /** + * The name to be used by default + */ + defaultName: { + type: String, + default: t('files', 'New folder'), + }, + /** + * Other files that are in the current directory + */ + otherNames: { + type: Array as PropType<string[]>, + default: () => [], + }, + /** + * Open state of the dialog + */ + open: { + type: Boolean, + default: true, + }, + /** + * Dialog name + */ + name: { + type: String, + default: t('files', 'Create new folder'), + }, + /** + * Input label + */ + label: { + type: String, + default: t('files', 'Folder name'), + }, + }, + emits: { + close: (name: string|null) => name === null || name, + }, + data() { + return { + localDefaultName: this.defaultName || t('files', 'New folder'), + } + }, + computed: { + errorMessage() { + if (this.isUniqueName) { + return '' + } else { + return t('files', 'A file or folder with that name already exists.') + } + }, + uniqueName() { + return getUniqueName(this.localDefaultName, this.otherNames) + }, + isUniqueName() { + return this.localDefaultName === this.uniqueName + }, + }, + watch: { + defaultName() { + this.localDefaultName = this.defaultName || t('files', 'New folder') + }, + }, + mounted() { + // on mounted lets use the unique name + this.localDefaultName = this.uniqueName + this.$nextTick(() => (this.$refs.input as unknown as ICanFocus)?.focus?.()) + }, + methods: { + t, + onCreate() { + this.$emit('close', this.localDefaultName) + }, + onClose(state: boolean) { + if (!state) { + this.$emit('close', null) + } + }, + }, +}) +</script> diff --git a/apps/files/src/init-templates.ts b/apps/files/src/init-templates.ts deleted file mode 100644 index 6803143d4b2..00000000000 --- a/apps/files/src/init-templates.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Julius Härtl <jus@bitgrid.net> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ -import type { Entry } from '@nextcloud/files' -import type { TemplateFile } from './types' - -import { Folder, Node, Permission, addNewFileMenuEntry, removeNewFileMenuEntry } from '@nextcloud/files' -import { generateOcsUrl } from '@nextcloud/router' -import { getLoggerBuilder } from '@nextcloud/logger' -import { join } from 'path' -import { loadState } from '@nextcloud/initial-state' -import { showError } from '@nextcloud/dialogs' -import { translate as t, translatePlural as n } from '@nextcloud/l10n' -import axios from '@nextcloud/axios' -import Vue from 'vue' - -import PlusSvg from '@mdi/svg/svg/plus.svg?raw' - -import TemplatePickerView from './views/TemplatePicker.vue' -import { getUniqueName } from './utils/fileUtils.ts' -import { getCurrentUser } from '@nextcloud/auth' - -// Set up logger -const logger = getLoggerBuilder() - .setApp('files') - .detectUser() - .build() - -// Add translates functions -Vue.mixin({ - methods: { - t, - n, - }, -}) - -// Create document root -const TemplatePickerRoot = document.createElement('div') -TemplatePickerRoot.id = 'template-picker' -document.body.appendChild(TemplatePickerRoot) - -// Retrieve and init templates -let templates = loadState<TemplateFile[]>('files', 'templates', []) -let templatesPath = loadState('files', 'templates_path', false) -logger.debug('Templates providers', { templates }) -logger.debug('Templates folder', { templatesPath }) - -// Init vue app -const View = Vue.extend(TemplatePickerView) -const TemplatePicker = new View({ - name: 'TemplatePicker', - propsData: { - logger, - }, -}) -TemplatePicker.$mount('#template-picker') -if (!templatesPath) { - logger.debug('Templates folder not initialized') - addNewFileMenuEntry({ - id: 'template-picker', - displayName: t('files', 'Create new templates folder'), - iconSvgInline: PlusSvg, - order: 10, - enabled(context: Folder): boolean { - // Allow creation on your own folders only - if (context.owner !== getCurrentUser()?.uid) { - return false - } - return (context.permissions & Permission.CREATE) !== 0 - }, - handler(context: Folder, content: Node[]) { - // Check for conflicts - const contentNames = content.map((node: Node) => node.basename) - const name = getUniqueName(t('files', 'Templates'), contentNames) - - // Create the template folder - initTemplatesFolder(context, name) - - // Remove the menu entry - removeNewFileMenuEntry('template-picker') - }, - } as Entry) -} - -// Init template files menu -templates.forEach((provider, index) => { - addNewFileMenuEntry({ - id: `template-new-${provider.app}-${index}`, - displayName: provider.label, - // TODO: migrate to inline svg - iconClass: provider.iconClass || 'icon-file', - enabled(context: Folder): boolean { - return (context.permissions & Permission.CREATE) !== 0 - }, - order: 11, - handler(context: Folder, content: Node[]) { - // Check for conflicts - const contentNames = content.map((node: Node) => node.basename) - const name = getUniqueName(provider.label + provider.extension, contentNames) - - // Create the file - TemplatePicker.open(name, provider) - }, - } as Entry) -}) - -// Init template folder -const initTemplatesFolder = async function(directory: Folder, name: string) { - const templatePath = join(directory.path, name) - try { - logger.debug('Initializing the templates directory', { templatePath }) - const response = await axios.post(generateOcsUrl('apps/files/api/v1/templates/path'), { - templatePath, - copySystemTemplates: true, - }) - - // Go to template directory - window.OCP.Files.Router.goToRoute( - null, // use default route - { view: 'files', fileid: undefined }, - { dir: templatePath }, - ) - - templates = response.data.ocs.data.templates - templatesPath = response.data.ocs.data.template_path - } catch (error) { - logger.error('Unable to initialize the templates directory') - showError(t('files', 'Unable to initialize the templates directory')) - } -} diff --git a/apps/files/src/init.ts b/apps/files/src/init.ts index 9f463244d91..c3b4b570e12 100644 --- a/apps/files/src/init.ts +++ b/apps/files/src/init.ts @@ -31,14 +31,15 @@ import { action as openInFilesAction } from './actions/openInFilesAction' import { action as renameAction } from './actions/renameAction' import { action as sidebarAction } from './actions/sidebarAction' import { action as viewInFolderAction } from './actions/viewInFolderAction' -import { entry as newFolderEntry } from './newMenu/newFolder' +import { entry as newFolderEntry } from './newMenu/newFolder.ts' +import { entry as newTemplatesFolder } from './newMenu/newTemplatesFolder.ts' +import { registerTemplateEntries } from './newMenu/newFromTemplate.ts' import registerFavoritesView from './views/favorites' import registerRecentView from './views/recent' import registerFilesView from './views/files' import registerPreviewServiceWorker from './services/ServiceWorker.js' -import './init-templates' import { initLivePhotos } from './services/LivePhotos' @@ -56,6 +57,8 @@ registerFileAction(viewInFolderAction) // Register new menu entry addNewFileMenuEntry(newFolderEntry) +addNewFileMenuEntry(newTemplatesFolder) +registerTemplateEntries() // Register files views registerFavoritesView() diff --git a/apps/files/src/newMenu/newFolder.ts b/apps/files/src/newMenu/newFolder.ts index 37dcf6d3d89..64ab8004e78 100644 --- a/apps/files/src/newMenu/newFolder.ts +++ b/apps/files/src/newMenu/newFolder.ts @@ -31,7 +31,7 @@ import axios from '@nextcloud/axios' import FolderPlusSvg from '@mdi/svg/svg/folder-plus.svg?raw' -import { getUniqueName } from '../utils/fileUtils.ts' +import { newNodeName } from '../utils/newNodeDialog' import logger from '../logger' type createFolderResponse = { @@ -63,23 +63,27 @@ export const entry = { iconSvgInline: FolderPlusSvg, order: 0, async handler(context: Folder, content: Node[]) { - const contentNames = content.map((node: Node) => node.basename) - const name = getUniqueName(t('files', 'New folder'), contentNames) - const { fileid, source } = await createNewFolder(context, name) + const name = await newNodeName(t('files', 'New folder'), content) + if (name !== null) { + const { fileid, source } = await createNewFolder(context, name) + // Create the folder in the store + const folder = new Folder({ + source, + id: fileid, + mtime: new Date(), + owner: getCurrentUser()?.uid || null, + permissions: Permission.ALL, + root: context?.root || '/files/' + getCurrentUser()?.uid, + }) - // Create the folder in the store - const folder = new Folder({ - source, - id: fileid, - mtime: new Date(), - owner: getCurrentUser()?.uid || null, - permissions: Permission.ALL, - root: context?.root || '/files/' + getCurrentUser()?.uid, - }) - - showSuccess(t('files', 'Created new folder "{name}"', { name: basename(source) })) - logger.debug('Created new folder', { folder, source }) - emit('files:node:created', folder) - emit('files:node:rename', folder) + showSuccess(t('files', 'Created new folder "{name}"', { name: basename(source) })) + logger.debug('Created new folder', { folder, source }) + emit('files:node:created', folder) + window.OCP.Files.Router.goToRoute( + null, // use default route + { view: 'files', fileid: folder.fileid }, + { dir: context.path }, + ) + } }, } as Entry diff --git a/apps/files/src/newMenu/newFromTemplate.ts b/apps/files/src/newMenu/newFromTemplate.ts new file mode 100644 index 00000000000..5e69181995e --- /dev/null +++ b/apps/files/src/newMenu/newFromTemplate.ts @@ -0,0 +1,88 @@ +/** + * @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * @author Julius Härtl <jus@bitgrid.net> + * @author Ferdinand Thiessen <opensource@fthiessen.de> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import type { Entry } from '@nextcloud/files' +import type { ComponentInstance } from 'vue' +import type { TemplateFile } from '../types.ts' + +import { Folder, Node, Permission, addNewFileMenuEntry } from '@nextcloud/files' +import { loadState } from '@nextcloud/initial-state' +import { newNodeName } from '../utils/newNodeDialog' +import { translate as t } from '@nextcloud/l10n' +import Vue, { defineAsyncComponent } from 'vue' + +// async to reduce bundle size +const TemplatePickerVue = defineAsyncComponent(() => import('../views/TemplatePicker.vue')) +let TemplatePicker: ComponentInstance & { open: (n: string, t: TemplateFile) => void } | null = null + +const getTemplatePicker = async () => { + if (TemplatePicker === null) { + // Create document root + const mountingPoint = document.createElement('div') + mountingPoint.id = 'template-picker' + document.body.appendChild(mountingPoint) + + // Init vue app + TemplatePicker = new Vue({ + render: (h) => h(TemplatePickerVue, { ref: 'picker' }), + methods: { open(...args) { this.$refs.picker.open(...args) } }, + el: mountingPoint, + }) + } + return TemplatePicker +} + +/** + * Register all new-file-menu entries for all template providers + */ +export function registerTemplateEntries() { + const templates = loadState<TemplateFile[]>('files', 'templates', []) + + // Init template files menu + templates.forEach((provider, index) => { + addNewFileMenuEntry({ + id: `template-new-${provider.app}-${index}`, + displayName: provider.label, + // TODO: migrate to inline svg + iconClass: provider.iconClass || 'icon-file', + enabled(context: Folder): boolean { + return (context.permissions & Permission.CREATE) !== 0 + }, + order: 11, + async handler(context: Folder, content: Node[]) { + const templatePicker = getTemplatePicker() + const name = await newNodeName(`${provider.label}${provider.extension}`, content, { + label: t('files', 'Filename'), + name: provider.label, + }) + + if (name !== null) { + // Create the file + const picker = await templatePicker + picker.open(name, provider) + } + }, + } as Entry) + }) +} diff --git a/apps/files/src/newMenu/newTemplatesFolder.ts b/apps/files/src/newMenu/newTemplatesFolder.ts new file mode 100644 index 00000000000..fafee553a10 --- /dev/null +++ b/apps/files/src/newMenu/newTemplatesFolder.ts @@ -0,0 +1,100 @@ +/** + * @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * @author Julius Härtl <jus@bitgrid.net> + * @author Ferdinand Thiessen <opensource@fthiessen.de> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +import type { Entry, Folder, Node } from '@nextcloud/files' + +import { getCurrentUser } from '@nextcloud/auth' +import { showError } from '@nextcloud/dialogs' +import { Permission, removeNewFileMenuEntry } from '@nextcloud/files' +import { loadState } from '@nextcloud/initial-state' +import { translate as t } from '@nextcloud/l10n' +import { generateOcsUrl } from '@nextcloud/router' +import { join } from 'path' +import { newNodeName } from '../utils/newNodeDialog' + +import PlusSvg from '@mdi/svg/svg/plus.svg?raw' +import axios from '@nextcloud/axios' +import logger from '../logger.js' + +let templatesPath = loadState<string|false>('files', 'templates_path', false) +logger.debug('Initial templates folder', { templatesPath }) + +/** + * Init template folder + * @param directory Folder where to create the templates folder + * @param name Name to use or the templates folder + */ +const initTemplatesFolder = async function(directory: Folder, name: string) { + const templatePath = join(directory.path, name) + try { + logger.debug('Initializing the templates directory', { templatePath }) + const { data } = await axios.post(generateOcsUrl('apps/files/api/v1/templates/path'), { + templatePath, + copySystemTemplates: true, + }) + + // Go to template directory + window.OCP.Files.Router.goToRoute( + null, // use default route + { view: 'files', fileid: undefined }, + { dir: templatePath }, + ) + + logger.info('Created new templates folder', { + ...data.ocs.data, + }) + templatesPath = data.ocs.data.templates_path as string + } catch (error) { + logger.error('Unable to initialize the templates directory') + showError(t('files', 'Unable to initialize the templates directory')) + } +} + +export const entry = { + id: 'template-picker', + displayName: t('files', 'Create new templates folder'), + iconSvgInline: PlusSvg, + order: 10, + enabled(context: Folder): boolean { + // Templates folder already initialized + if (templatesPath) { + return false + } + // Allow creation on your own folders only + if (context.owner !== getCurrentUser()?.uid) { + return false + } + return (context.permissions & Permission.CREATE) !== 0 + }, + async handler(context: Folder, content: Node[]) { + const name = await newNodeName(t('files', 'Templates'), content, { name: t('files', 'New template folder') }) + + if (name !== null) { + // Create the template folder + initTemplatesFolder(context, name) + + // Remove the menu entry + removeNewFileMenuEntry('template-picker') + } + }, +} as Entry diff --git a/apps/files/src/utils/newNodeDialog.ts b/apps/files/src/utils/newNodeDialog.ts new file mode 100644 index 00000000000..f53694fc68c --- /dev/null +++ b/apps/files/src/utils/newNodeDialog.ts @@ -0,0 +1,57 @@ +/** + * @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de> + * + * @author Ferdinand Thiessen <opensource@fthiessen.de> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import type { Node } from '@nextcloud/files' +import { spawnDialog } from '@nextcloud/dialogs' +import NewNodeDialog from '../components/NewNodeDialog.vue' + +interface ILabels { + /** + * Dialog heading, defaults to "New folder name" + */ + name?: string + /** + * Label for input box, defaults to "New folder" + */ + label?: string +} + +/** + * Ask user for file or folder name + * @param defaultName Default name to use + * @param folderContent Nodes with in the current folder to check for unique name + * @param labels Labels to set on the dialog + * @return string if successfull otherwise null if aborted + */ +export function newNodeName(defaultName: string, folderContent: Node[], labels: ILabels = {}) { + const contentNames = folderContent.map((node: Node) => node.basename) + + return new Promise<string|null>((resolve) => { + spawnDialog(NewNodeDialog, { + ...labels, + defaultName, + otherNames: contentNames, + }, (folderName) => { + resolve(folderName as string|null) + }) + }) +} diff --git a/apps/files/src/views/FilesList.vue b/apps/files/src/views/FilesList.vue index 19e9b8e86d2..4e80379f632 100644 --- a/apps/files/src/views/FilesList.vue +++ b/apps/files/src/views/FilesList.vue @@ -566,15 +566,20 @@ export default defineComponent({ /** * Refreshes the current folder on update. * - * @param {Node} node is the file/folder being updated. + * @param node is the file/folder being updated. */ - onUpdatedNode(node) { + onUpdatedNode(node?: Node) { if (node?.fileid === this.currentFolder?.fileid) { this.fetchContent() } }, openSharingSidebar() { + if (!this.currentFolder) { + logger.debug('No current folder found for opening sharing sidebar') + return + } + if (window?.OCA?.Files?.Sidebar?.setActiveTab) { window.OCA.Files.Sidebar.setActiveTab('sharing') } |