diff options
Diffstat (limited to 'apps/files/src/newMenu')
-rw-r--r-- | apps/files/src/newMenu/newFolder.ts | 91 | ||||
-rw-r--r-- | apps/files/src/newMenu/newFromTemplate.ts | 77 | ||||
-rw-r--r-- | apps/files/src/newMenu/newTemplatesFolder.ts | 83 |
3 files changed, 251 insertions, 0 deletions
diff --git a/apps/files/src/newMenu/newFolder.ts b/apps/files/src/newMenu/newFolder.ts new file mode 100644 index 00000000000..f0f854d2801 --- /dev/null +++ b/apps/files/src/newMenu/newFolder.ts @@ -0,0 +1,91 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { Entry, Node } from '@nextcloud/files' + +import { basename } from 'path' +import { emit } from '@nextcloud/event-bus' +import { getCurrentUser } from '@nextcloud/auth' +import { Permission, Folder } from '@nextcloud/files' +import { showError, showSuccess } from '@nextcloud/dialogs' +import { translate as t } from '@nextcloud/l10n' +import axios from '@nextcloud/axios' + +import FolderPlusSvg from '@mdi/svg/svg/folder-plus-outline.svg?raw' + +import { newNodeName } from '../utils/newNodeDialog' +import logger from '../logger' + +type createFolderResponse = { + fileid: number + source: string +} + +const createNewFolder = async (root: Folder, name: string): Promise<createFolderResponse> => { + const source = root.source + '/' + name + const encodedSource = root.encodedSource + '/' + encodeURIComponent(name) + + const response = await axios({ + method: 'MKCOL', + url: encodedSource, + headers: { + Overwrite: 'F', + }, + }) + return { + fileid: parseInt(response.headers['oc-fileid']), + source, + } +} + +export const entry = { + id: 'newFolder', + displayName: t('files', 'New folder'), + enabled: (context: Folder) => Boolean(context.permissions & Permission.CREATE) && Boolean(context.permissions & Permission.READ), + + // Make the svg icon color match the primary element color + iconSvgInline: FolderPlusSvg.replace(/viewBox/gi, 'style="color: var(--color-primary-element)" viewBox'), + order: 0, + + async handler(context: Folder, content: Node[]) { + const name = await newNodeName(t('files', 'New folder'), content) + if (name === null) { + return + } + try { + const { fileid, source } = await createNewFolder(context, name.trim()) + + // Create the folder in the store + const folder = new Folder({ + source, + id: fileid, + mtime: new Date(), + owner: context.owner, + permissions: Permission.ALL, + root: context?.root || '/files/' + getCurrentUser()?.uid, + // Include mount-type from parent folder as this is inherited + attributes: { + 'mount-type': context.attributes?.['mount-type'], + 'owner-id': context.attributes?.['owner-id'], + 'owner-display-name': context.attributes?.['owner-display-name'], + }, + }) + + // Show success + emit('files:node:created', folder) + showSuccess(t('files', 'Created new folder "{name}"', { name: basename(source) })) + logger.debug('Created new folder', { folder, source }) + + // Navigate to the new folder + window.OCP.Files.Router.goToRoute( + null, // use default route + { view: 'files', fileid: String(fileid) }, + { dir: context.path }, + ) + } catch (error) { + logger.error('Creating new folder failed', { error }) + showError('Creating new folder failed') + } + }, +} as Entry diff --git a/apps/files/src/newMenu/newFromTemplate.ts b/apps/files/src/newMenu/newFromTemplate.ts new file mode 100644 index 00000000000..356fc5e1611 --- /dev/null +++ b/apps/files/src/newMenu/newFromTemplate.ts @@ -0,0 +1,77 @@ +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +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 (context: Folder) => { + 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', + props: { + parent: context, + }, + }, + ), + 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, + iconClass: provider.iconClass || 'icon-file', + iconSvgInline: provider.iconSvgInline, + enabled(context: Folder): boolean { + return (context.permissions & Permission.CREATE) !== 0 + }, + order: 11, + async handler(context: Folder, content: Node[]) { + const templatePicker = getTemplatePicker(context) + 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.trim(), 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..bf6862bda08 --- /dev/null +++ b/apps/files/src/newMenu/newTemplatesFolder.ts @@ -0,0 +1,83 @@ +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +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.ts' + +const templatesEnabled = loadState<boolean>('files', 'templates_enabled', true) +let templatesPath = loadState<string|false>('files', 'templates_path', false) +logger.debug('Templates folder enabled', { templatesEnabled }) +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 templates folder'), + iconSvgInline: PlusSvg, + order: 30, + enabled(context: Folder): boolean { + // Templates disabled or templates folder already initialized + if (!templatesEnabled || 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 |