@ -5,6 +5,7 @@
< template >
<!-- Rename input -- >
< form v -if = " isRenaming "
ref = "renameForm"
v - on - click - outside = "onRename"
: aria - label = "t('files', 'Rename file')"
class = "files-list__row-rename"
@ -16,7 +17,6 @@
: required = "true"
: value . sync = "newName"
enterkeyhint = "done"
@ keyup = "checkInputValidity"
@ keyup . esc = "stopRenaming" / >
< / form >
@ -40,22 +40,20 @@
import type { Node } from '@nextcloud/files'
import type { PropType } from 'vue'
import axios , { isAxiosError } from '@nextcloud/axios'
import { showError , showSuccess } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { FileType , NodeStatus , Permission } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
import axios , { isAxiosError } from '@nextcloud/axios'
import { defineComponent } from 'vue'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import { useNavigation } from '../../composables/useNavigation'
import { useRenamingStore } from '../../store/renaming.ts'
import { getFilenameValidity } from '../../utils/filenameValidity.ts'
import logger from '../../logger.js'
const forbiddenCharacters = loadState < string [ ] > ( 'files' , 'forbiddenCharacters' , [ ] )
export default defineComponent ( {
name : 'FileEntryName' ,
@ -187,55 +185,30 @@ export default defineComponent({
}
} ,
} ,
} ,
methods : {
/ * *
* Check if the file name is valid and update the
* input validity using browser ' s native validation .
* @ param event the keyup event
* /
checkInputValidity ( event : KeyboardEvent ) {
const input = event . target as HTMLInputElement
newName ( ) {
/ / C h e c k v a l i d i t y o f t h e n e w n a m e
const newName = this . newName . trim ? . ( ) || ''
logger . debug ( 'Checking input validity' , { newName } )
try {
this . isFileNameValid ( newName )
input . setCustomValidity ( '' )
input . title = ''
} catch ( e ) {
if ( e instanceof Error ) {
input . setCustomValidity ( e . message )
input . title = e . message
} else {
input . setCustomValidity ( t ( 'files' , 'Invalid file name' ) )
}
} finally {
input . reportValidity ( )
const input = ( this . $refs . renameInput as Vue | undefined ) ? . $el . querySelector ( 'input' )
if ( ! input ) {
return
}
} ,
isFileNameValid ( name : string ) {
const trimmedName = name . trim ( )
const char = trimmedName . indexOf ( '/' ) !== - 1
? '/'
: forbiddenCharacters . find ( ( char ) => trimmedName . includes ( char ) )
if ( trimmedName === '.' || trimmedName === '..' ) {
throw new Error ( t ( 'files' , '"{name}" is an invalid file name.' , { name } ) )
} else if ( trimmedName . length === 0 ) {
throw new Error ( t ( 'files' , 'File name cannot be empty.' ) )
} else if ( char ) {
throw new Error ( t ( 'files' , '"{char}" is not allowed inside a file name.' , { char } ) )
} else if ( trimmedName . match ( window . OC . config . blacklist _files _regex ) ) {
throw new Error ( t ( 'files' , '"{name}" is not an allowed filetype.' , { name } ) )
} else if ( this . checkIfNodeExists ( name ) ) {
throw new Error ( t ( 'files' , '{newName} already exists.' , { newName : name } ) )
let validity = getFilenameValidity ( newName )
/ / C h e c k i n g i f a l r e a d y e x i s t s
if ( validity === '' && this . checkIfNodeExists ( newName ) ) {
validity = t ( 'files' , 'Another entry with the same name already exists.' )
}
return true
this . $nextTick ( ( ) => {
if ( this . isRenaming ) {
input . setCustomValidity ( validity )
input . reportValidity ( )
}
} )
} ,
} ,
methods : {
checkIfNodeExists ( name : string ) {
return this . nodes . find ( node => node . basename === name && node !== this . source )
} ,
@ -243,20 +216,20 @@ export default defineComponent({
startRenaming ( ) {
this . $nextTick ( ( ) => {
/ / U s i n g s p l i t t o g e t t h e t r u e s t r i n g l e n g t h
const extLength = ( this . source . extension || '' ) . split ( '' ) . length
const length = this . source . basename . split ( '' ) . length - extLength
const input = this . $refs . renameInput ? . $refs ? . inputField ? . $refs ? . input
const input = ( this . $refs . renameInput as Vue | undefined ) ? . $el . querySelector ( 'input' )
if ( ! input ) {
logger . error ( 'Could not find the rename input' )
return
}
input . setSelectionRange ( 0 , length )
input . focus ( )
const length = this . source . basename . length - ( this . source . extension ? ? '' ) . length
input . setSelectionRange ( 0 , length )
/ / T r i g g e r a k e y u p e v e n t t o u p d a t e t h e i n p u t v a l i d i t y
input . dispatchEvent ( new Event ( 'keyup' ) )
} )
} ,
stopRenaming ( ) {
if ( ! this . isRenaming ) {
return
@ -268,25 +241,20 @@ export default defineComponent({
/ / R e n a m e a n d m o v e t h e f i l e
async onRename ( ) {
const oldName = this . source . basename
const oldEncodedSource = this . source . encodedSource
const newName = this . newName . trim ? . ( ) || ''
if ( newName === '' ) {
showError ( t ( 'files' , 'Name cannot be empty' ) )
const form = this . $refs . renameForm as HTMLFormElement
if ( ! form . checkValidity ( ) ) {
showError ( t ( 'files' , 'Invalid filename.' ) + ' ' + getFilenameValidity ( newName ) )
return
}
const oldName = this . source . basename
const oldEncodedSource = this . source . encodedSource
if ( oldName === newName ) {
this . stopRenaming ( )
return
}
/ / C h e c k i n g i f a l r e a d y e x i s t s
if ( this . checkIfNodeExists ( newName ) ) {
showError ( t ( 'files' , 'Another entry with the same name already exists' ) )
return
}
/ / S e t l o a d i n g s t a t e
this . $set ( this . source , 'status' , NodeStatus . LOADING )