diff --git a/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.html b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.html
index 2abb1b505..b052e04d4 100644
--- a/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.html
+++ b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.html
@@ -4,7 +4,7 @@
[control]="renameForm.controls['name']"
[label]="'files.dialogs.renameFile.newName'"
[placeholder]="'files.dialogs.renameFile.enterNewName'"
- [maxLength]="nameLimit"
+ [maxLength]="nameMaxLength"
[minLength]="nameMinLength"
>
@@ -14,7 +14,7 @@
type="button"
severity="secondary"
[label]="'common.buttons.cancel' | translate"
- (click)="onCancel()"
+ (onClick)="onCancel()"
>
diff --git a/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.ts b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.ts
index b9b4e5c48..01a04057b 100644
--- a/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.ts
+++ b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.ts
@@ -7,7 +7,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { TextInputComponent } from '@osf/shared/components';
-import { InputLimits } from '@osf/shared/constants';
+import { forbiddenFileNameCharacters, InputLimits } from '@osf/shared/constants';
import { CustomValidators } from '@osf/shared/helpers';
@Component({
@@ -20,13 +20,16 @@ export class RenameFileDialogComponent {
private readonly dialogRef = inject(DynamicDialogRef);
private readonly config = inject(DynamicDialogConfig);
- readonly nameLimit = InputLimits.name.maxLength;
- readonly nameMinLength = InputLimits.name.minLength;
+ readonly nameMaxLength = InputLimits.title.maxLength;
+ readonly nameMinLength = InputLimits.title.minLength;
readonly renameForm = new FormGroup({
name: new FormControl(this.config.data?.currentName ?? '', {
nonNullable: true,
- validators: [CustomValidators.requiredTrimmed()],
+ validators: [
+ CustomValidators.requiredTrimmed(),
+ CustomValidators.forbiddenCharactersValidator(forbiddenFileNameCharacters),
+ ],
}),
});
diff --git a/src/app/features/files/pages/files/files.component.ts b/src/app/features/files/pages/files/files.component.ts
index 4fccfa5b4..21fe23a36 100644
--- a/src/app/features/files/pages/files/files.component.ts
+++ b/src/app/features/files/pages/files/files.component.ts
@@ -457,6 +457,7 @@ export class FilesComponent {
finalize(() => {
this.updateFilesList();
this.fileIsUploading.set(false);
+ this.toastService.showSuccess('files.dialogs.createFolder.success');
})
)
.subscribe();
diff --git a/src/app/features/metadata/dialogs/license-dialog/license-dialog.component.html b/src/app/features/metadata/dialogs/license-dialog/license-dialog.component.html
index d36bed309..019867cbc 100644
--- a/src/app/features/metadata/dialogs/license-dialog/license-dialog.component.html
+++ b/src/app/features/metadata/dialogs/license-dialog/license-dialog.component.html
@@ -13,6 +13,7 @@
{{ 'project.metadata.license.dialog.chooseLicense.label' | tran
[isSubmitting]="isSubmitting()"
[showInternalButtons]="false"
[fullWidthSelect]="true"
+ [appendTo]="'body'"
(selectLicense)="onSelectLicense($event)"
(createLicense)="onCreateLicense($event)"
/>
diff --git a/src/app/features/settings/developer-apps/components/developer-app-add-edit-form/developer-app-add-edit-form.component.ts b/src/app/features/settings/developer-apps/components/developer-app-add-edit-form/developer-app-add-edit-form.component.ts
index 99932c866..5f57de536 100644
--- a/src/app/features/settings/developer-apps/components/developer-app-add-edit-form/developer-app-add-edit-form.component.ts
+++ b/src/app/features/settings/developer-apps/components/developer-app-add-edit-form/developer-app-add-edit-form.component.ts
@@ -23,6 +23,7 @@ import { CreateDeveloperApp, DeveloperAppsSelectors, UpdateDeveloperApp } from '
templateUrl: './developer-app-add-edit-form.component.html',
styleUrl: './developer-app-add-edit-form.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [DynamicDialogRef],
})
export class DeveloperAppAddEditFormComponent implements OnInit {
readonly isEditMode = input(false);
diff --git a/src/app/features/settings/profile-settings/components/name/name.component.ts b/src/app/features/settings/profile-settings/components/name/name.component.ts
index 5f299caef..17a114cce 100644
--- a/src/app/features/settings/profile-settings/components/name/name.component.ts
+++ b/src/app/features/settings/profile-settings/components/name/name.component.ts
@@ -9,6 +9,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder } from '@angular/forms';
import { UpdateProfileSettingsUser, UserSelectors } from '@osf/core/store/user';
+import { forbiddenFileNameCharacters } from '@osf/shared/constants';
import { CustomValidators } from '@osf/shared/helpers';
import { UserModel } from '@osf/shared/models';
import { CustomConfirmationService, LoaderService, ToastService } from '@osf/shared/services';
@@ -39,10 +40,25 @@ export class NameComponent {
readonly fb = inject(FormBuilder);
readonly form = this.fb.group({
- fullName: this.fb.control('', { nonNullable: true, validators: [CustomValidators.requiredTrimmed()] }),
- givenName: this.fb.control('', { nonNullable: true }),
- middleNames: this.fb.control('', { nonNullable: true }),
- familyName: this.fb.control('', { nonNullable: true }),
+ fullName: this.fb.control('', {
+ nonNullable: true,
+ validators: [
+ CustomValidators.requiredTrimmed(),
+ CustomValidators.forbiddenCharactersValidator(forbiddenFileNameCharacters),
+ ],
+ }),
+ givenName: this.fb.control('', {
+ nonNullable: true,
+ validators: CustomValidators.forbiddenCharactersValidator(forbiddenFileNameCharacters),
+ }),
+ middleNames: this.fb.control('', {
+ nonNullable: true,
+ validators: CustomValidators.forbiddenCharactersValidator(forbiddenFileNameCharacters),
+ }),
+ familyName: this.fb.control('', {
+ nonNullable: true,
+ validators: CustomValidators.forbiddenCharactersValidator(forbiddenFileNameCharacters),
+ }),
suffix: this.fb.control('', { nonNullable: true }),
});
diff --git a/src/app/shared/components/files-tree/files-tree.component.ts b/src/app/shared/components/files-tree/files-tree.component.ts
index 38d025481..f3e99c3ef 100644
--- a/src/app/shared/components/files-tree/files-tree.component.ts
+++ b/src/app/shared/components/files-tree/files-tree.component.ts
@@ -316,7 +316,9 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit {
deleteEntry(link: string): void {
this.actions().setFilesIsLoading?.(true);
- this.actions().deleteEntry?.(this.resourceId(), link);
+ this.actions()
+ .deleteEntry?.(this.resourceId(), link)
+ .subscribe(() => this.toastService.showSuccess('files.dialogs.deleteFile.success'));
}
confirmRename(file: OsfFile): void {
@@ -338,7 +340,10 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit {
renameEntry(newName: string, file: OsfFile): void {
if (newName.trim() && file.links.upload) {
this.actions().setFilesIsLoading?.(true);
- this.actions().renameEntry?.(this.resourceId(), file.links.upload, newName);
+
+ this.actions()
+ .renameEntry?.(this.resourceId(), file.links.upload, newName)
+ .subscribe(() => this.toastService.showSuccess('files.dialogs.renameFile.success'));
}
}
@@ -385,6 +390,10 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit {
this.resetPagination();
if (foldersStack) {
this.foldersStack = [...foldersStack];
+
+ if (action === 'copy') {
+ this.toastService.showSuccess('files.dialogs.copyFile.success');
+ }
}
});
});
diff --git a/src/app/shared/components/license/license.component.html b/src/app/shared/components/license/license.component.html
index ecd763e4d..0f2ea1718 100644
--- a/src/app/shared/components/license/license.component.html
+++ b/src/app/shared/components/license/license.component.html
@@ -1,12 +1,13 @@
@if (selectedLicense()) {
diff --git a/src/app/shared/components/license/license.component.ts b/src/app/shared/components/license/license.component.ts
index 730a105c6..f99a0a552 100644
--- a/src/app/shared/components/license/license.component.ts
+++ b/src/app/shared/components/license/license.component.ts
@@ -42,6 +42,7 @@ export class LicenseComponent {
isSubmitting = input(false);
showInternalButtons = input(true);
fullWidthSelect = input(false);
+ appendTo = input(null);
selectedLicense = model(null);
createLicense = output<{ id: string; licenseOptions: LicenseOptions }>();
selectLicense = output();
diff --git a/src/app/shared/components/wiki/wiki-list/wiki-list.component.html b/src/app/shared/components/wiki/wiki-list/wiki-list.component.html
index dd63ab679..565ba9352 100644
--- a/src/app/shared/components/wiki/wiki-list/wiki-list.component.html
+++ b/src/app/shared/components/wiki/wiki-list/wiki-list.component.html
@@ -11,15 +11,19 @@
} @else {
@if (expanded()) {
-
-
+
+ @if (canEdit()) {
+
+ }
~!@$&*:;,"'\\|/?]/;
diff --git a/src/app/shared/helpers/custom-form-validators.helper.ts b/src/app/shared/helpers/custom-form-validators.helper.ts
index 5991990c1..b4f665435 100644
--- a/src/app/shared/helpers/custom-form-validators.helper.ts
+++ b/src/app/shared/helpers/custom-form-validators.helper.ts
@@ -98,4 +98,16 @@ export class CustomValidators {
return null;
};
}
+
+ static forbiddenCharactersValidator(pattern: RegExp): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const value = control.value;
+ if (!value) {
+ return null;
+ }
+
+ const hasForbiddenCharacters = pattern.test(value);
+ return hasForbiddenCharacters ? { forbiddenCharacters: true } : null;
+ };
+ }
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 5be631526..bdef66c9f 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -195,7 +195,7 @@
"donate": "Donate",
"profileSettings": "Profile Settings",
"accountSettings": "Account Settings",
- "configureAddonAccounts": "Configure Addon Accounts",
+ "configureAddonAccounts": "Configure add-on & link service accounts",
"notifications": "Notifications",
"developerApps": "Developer Apps",
"personalAccessTokens": "Personal Access Tokens",
@@ -1082,32 +1082,39 @@
"createFolder": {
"title": "Create folder",
"folderName": "New folder name",
- "folderNamePlaceholder": "Please enter a folder name"
+ "folderNamePlaceholder": "Please enter a folder name",
+ "success": "Folder successfully created."
},
"renameFile": {
"title": "Rename file",
"newName": "New name",
"enterNewName": "Enter new name",
- "renameLabel": "Please rename the file"
+ "renameLabel": "Please rename the file",
+ "success": "File successfully renamed."
},
"moveFile": {
"cannotMove": "Cannot move to the same folder",
"title": "Move file",
"message": "Are you sure you want to move {{dragNodeName}} to {{dropNodeName}} ?",
"storage": "OSF Storage",
- "pathError": "Path is not specified!"
+ "pathError": "Path is not specified!",
+ "success": "File successfully moved."
},
"copyFile": {
- "title": "Select location to copy file to"
+ "title": "Select location to copy file to",
+ "success": "File successfully copied."
},
"deleteFile": {
"title": "Delete File",
- "message": "Are you sure you want to delete {{name}}?"
+ "message": "Are you sure you want to delete {{name}}?",
+ "success": "File successfully deleted."
},
"replaceFile": {
"single": "Replace file",
"multiple": "Replace files",
- "message": "Are you sure you want to replace {{name}}?"
+ "message": "Are you sure you want to replace {{name}}?",
+ "successOne": "File successfully replaced.",
+ "successMultiple": "File successfully replaced."
}
},
"filesBrowserDialog": {