Merge pull request #42993 from nextcloud/fix/files-new-menu

fix(files): Allow to set node name before creating + bring back new-file highlingting
pull/43426/head
Ferdinand Thiessen 4 months ago committed by GitHub
commit 7ff6cbc1b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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: {

@ -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))

@ -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>

@ -243,6 +243,11 @@ export default Vue.extend({
methods: {
scrollTo(index: number) {
const targetRow = Math.ceil(this.dataSources.length / this.columnCount)
if (targetRow < this.rowCount) {
logger.debug('VirtualList: Skip scrolling. nothing to scroll', { index, targetRow, rowCount: this.rowCount })
return
}
this.index = index
// Scroll to one row and a half before the index
const scrollTop = (Math.floor(index / this.columnCount) - 0.5) * this.itemHeight + this.beforeHeight

@ -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'))
}
}

@ -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()

@ -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

@ -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)
})
}

@ -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

@ -119,4 +119,5 @@ export interface TemplateFile {
iconClass?: string
mimetypes: string[]
ratio?: number
templates?: Record<string, unknown>[]
}

@ -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)
})
})
}

@ -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')
}

@ -61,23 +61,27 @@
</template>
<script lang="ts">
import { emit, subscribe } from '@nextcloud/event-bus'
import type { TemplateFile } from '../types.ts'
import { getCurrentUser } from '@nextcloud/auth'
import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { File } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
import { normalize, extname, join } from 'path'
import { showError } from '@nextcloud/dialogs'
import { defineComponent } from 'vue'
import { createFromTemplate, getTemplates } from '../services/Templates.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import Vue from 'vue'
import { createFromTemplate, getTemplates } from '../services/Templates.js'
import TemplatePreview from '../components/TemplatePreview.vue'
import logger from '../logger.js'
const border = 2
const margin = 8
export default Vue.extend({
export default defineComponent({
name: 'TemplatePicker',
components: {
@ -86,40 +90,34 @@ export default Vue.extend({
TemplatePreview,
},
props: {
logger: {
type: Object,
required: true,
},
},
data() {
return {
// Check empty template by default
checked: -1,
loading: false,
name: null,
name: null as string|null,
opened: false,
provider: null,
provider: null as TemplateFile|null,
}
},
computed: {
extension() {
return extname(this.name)
return extname(this.name ?? '')
},
nameWithoutExt() {
// Strip extension from name if defined
return !this.extension
? this.name
: this.name.slice(0, 0 - this.extension.length)
: this.name!.slice(0, 0 - this.extension.length)
},
emptyTemplate() {
return {
basename: t('files', 'Blank'),
fileid: -1,
filename: this.t('files', 'Blank'),
filename: t('files', 'Blank'),
hasPreview: false,
mime: this.provider?.mimetypes[0] || this.provider?.mimetypes,
}
@ -130,7 +128,7 @@ export default Vue.extend({
return null
}
return this.provider.templates.find(template => template.fileid === this.checked)
return this.provider.templates!.find((template) => template.fileid === this.checked)
},
/**
@ -159,6 +157,8 @@ export default Vue.extend({
},
methods: {
t,
/**
* Open the picker
*
@ -201,9 +201,9 @@ export default Vue.extend({
/**
* Manages the radio template picker change
*
* @param {number} fileid the selected template file id
* @param fileid the selected template file id
*/
onCheck(fileid) {
onCheck(fileid: number) {
this.checked = fileid
},
@ -213,22 +213,22 @@ export default Vue.extend({
// If the file doesn't have an extension, add the default one
if (this.nameWithoutExt === this.name) {
this.logger.warn('Fixed invalid filename', { name: this.name, extension: this.provider?.extension })
this.name = this.name + this.provider?.extension
logger.warn('Fixed invalid filename', { name: this.name, extension: this.provider?.extension })
this.name = `${this.name}${this.provider?.extension ?? ''}`
}
try {
const fileInfo = await createFromTemplate(
normalize(`${currentDirectory}/${this.name}`),
this.selectedTemplate?.filename,
this.selectedTemplate?.templateType,
this.selectedTemplate?.filename as string ?? '',
this.selectedTemplate?.templateType as string ?? '',
)
this.logger.debug('Created new file', fileInfo)
logger.debug('Created new file', fileInfo)
const owner = getCurrentUser()?.uid || null
const node = new File({
id: fileInfo.fileid,
source: generateRemoteUrl(join('dav/files', owner, fileInfo.filename)),
source: generateRemoteUrl(join(`dav/files/${owner}`, fileInfo.filename)),
root: `/files/${owner}`,
mime: fileInfo.mime,
mtime: new Date(fileInfo.lastmod * 1000),
@ -243,19 +243,13 @@ export default Vue.extend({
// Update files list
emit('files:node:created', node)
// Open the new file
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: node.fileid },
{ dir: node.dirname, openfile: true },
)
emit('files:node:focus', node)
// Close the picker
this.close()
} catch (error) {
this.logger.error('Error while creating the new file from template', { error })
showError(this.t('files', 'Unable to create new file from template'))
logger.error('Error while creating the new file from template', { error })
showError(t('files', 'Unable to create new file from template'))
} finally {
this.loading = false
}

3
dist/3222-3222.js vendored

File diff suppressed because one or more lines are too long

@ -0,0 +1,21 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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/>.
*
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -564,6 +564,28 @@
*
*/
/**
* @copyright Copyright (c) 2023 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/>.
*
*/
/**
* @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
*

File diff suppressed because one or more lines are too long

4
dist/core-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

@ -44,7 +44,7 @@
*/
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
* @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
@ -66,7 +66,7 @@
*/
/**
* @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
@ -88,7 +88,7 @@
*/
/**
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
@ -113,6 +113,7 @@
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Ferdinand Thiessen <opensource@fthiessen.de>
*
* @license AGPL-3.0-or-later
*
@ -132,9 +133,8 @@
*/
/**
* @copyright Copyright (c) 2023 John Molakvoæ <skjnldsv@protonmail.com>
* @copyright Copyright (c) 2024 Ferdinand Thiessen <opensource@fthiessen.de>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Ferdinand Thiessen <opensource@fthiessen.de>
*
* @license AGPL-3.0-or-later

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
Loading…
Cancel
Save