3434 </template >
3535 <form @submit.prevent =" onCreate" >
3636 <NcTextField ref =" input"
37+ class =" dialog__input"
3738 :error =" !isUniqueName"
3839 :helper-text =" errorMessage"
3940 :label =" label"
40- :value.sync =" localDefaultName" />
41+ :value.sync =" localDefaultName"
42+ @keyup =" checkInputValidity" />
4143 </form >
4244 </NcDialog >
4345</template >
@@ -48,15 +50,19 @@ import type { PropType } from 'vue'
4850import { defineComponent } from ' vue'
4951import { translate as t } from ' @nextcloud/l10n'
5052import { getUniqueName } from ' @nextcloud/files'
53+ import { loadState } from ' @nextcloud/initial-state'
5154
5255import NcButton from ' @nextcloud/vue/dist/Components/NcButton.js'
5356import NcDialog from ' @nextcloud/vue/dist/Components/NcDialog.js'
5457import NcTextField from ' @nextcloud/vue/dist/Components/NcTextField.js'
58+ import logger from ' ../logger.js'
5559
5660interface ICanFocus {
5761 focus: () => void
5862}
5963
64+ const forbiddenCharacters = loadState <string []>(' files' , ' forbiddenCharacters' , [])
65+
6066export default defineComponent ({
6167 name: ' NewNodeDialog' ,
6268 components: {
@@ -161,6 +167,60 @@ export default defineComponent({
161167 this .$emit (' close' , null )
162168 }
163169 },
170+
171+ /**
172+ * Check if the file name is valid and update the
173+ * input validity using browser's native validation.
174+ * @param event the keyup event
175+ */
176+ checkInputValidity(event : KeyboardEvent ) {
177+ const input = event .target as HTMLInputElement
178+ const newName = this .localDefaultName .trim ?.() || ' '
179+ logger .debug (' Checking input validity' , { newName })
180+ try {
181+ this .isFileNameValid (newName )
182+ input .setCustomValidity (' ' )
183+ input .title = ' '
184+ } catch (e ) {
185+ if (e instanceof Error ) {
186+ input .setCustomValidity (e .message )
187+ input .title = e .message
188+ } else {
189+ input .setCustomValidity (t (' files' , ' Invalid file name' ))
190+ }
191+ } finally {
192+ input .reportValidity ()
193+ }
194+ },
195+
196+ isFileNameValid(name : string ) {
197+ const trimmedName = name .trim ()
198+ const char = trimmedName .indexOf (' /' ) !== - 1
199+ ? ' /'
200+ : forbiddenCharacters .find ((char ) => trimmedName .includes (char ))
201+
202+ if (trimmedName === ' .' || trimmedName === ' ..' ) {
203+ throw new Error (t (' files' , ' "{name}" is an invalid file name.' , { name }))
204+ } else if (trimmedName .length === 0 ) {
205+ throw new Error (t (' files' , ' File name cannot be empty.' ))
206+ } else if (char ) {
207+ throw new Error (t (' files' , ' "{char}" is not allowed inside a file name.' , { char }))
208+ } else if (trimmedName .match (window .OC .config .blacklist_files_regex )) {
209+ throw new Error (t (' files' , ' "{name}" is not an allowed filetype.' , { name }))
210+ }
211+
212+ return true
213+ },
164214 },
165215})
166216 </script >
217+
218+ <style lang="scss" scoped>
219+ .dialog__input {
220+ :deep (input:invalid ) {
221+ // Show red border on invalid input
222+ border-color : var (--color-error );
223+ color : red ;
224+ }
225+ }
226+ </style >
0 commit comments