|
|
|
@ -20,10 +20,12 @@ |
|
|
|
|
<form @submit.prevent="onCreate"> |
|
|
|
|
<NcTextField ref="input" |
|
|
|
|
data-cy-files-new-node-dialog-input |
|
|
|
|
class="dialog__input" |
|
|
|
|
:error="!isUniqueName" |
|
|
|
|
:helper-text="errorMessage" |
|
|
|
|
:label="label" |
|
|
|
|
:value.sync="localDefaultName" /> |
|
|
|
|
:value.sync="localDefaultName" |
|
|
|
|
@keyup="checkInputValidity" /> |
|
|
|
|
</form> |
|
|
|
|
</NcDialog> |
|
|
|
|
</template> |
|
|
|
@ -34,15 +36,19 @@ import type { PropType } from 'vue' |
|
|
|
|
import { defineComponent } from 'vue' |
|
|
|
|
import { translate as t } from '@nextcloud/l10n' |
|
|
|
|
import { getUniqueName } from '@nextcloud/files' |
|
|
|
|
import { loadState } from '@nextcloud/initial-state' |
|
|
|
|
|
|
|
|
|
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' |
|
|
|
|
import logger from '../logger.js' |
|
|
|
|
|
|
|
|
|
interface ICanFocus { |
|
|
|
|
focus: () => void |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const forbiddenCharacters = loadState<string[]>('files', 'forbiddenCharacters', []) |
|
|
|
|
|
|
|
|
|
export default defineComponent({ |
|
|
|
|
name: 'NewNodeDialog', |
|
|
|
|
components: { |
|
|
|
@ -147,6 +153,60 @@ export default defineComponent({ |
|
|
|
|
this.$emit('close', null) |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 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 |
|
|
|
|
const newName = this.localDefaultName.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() |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
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 })) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return true |
|
|
|
|
}, |
|
|
|
|
}, |
|
|
|
|
}) |
|
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped> |
|
|
|
|
.dialog__input { |
|
|
|
|
:deep(input:invalid) { |
|
|
|
|
// Show red border on invalid input |
|
|
|
|
border-color: var(--color-error); |
|
|
|
|
color: red; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
</style> |
|
|
|
|