diff --git a/src/app/features/files/pages/file-detail/file-detail.component.html b/src/app/features/files/pages/file-detail/file-detail.component.html index 29b2da220..7d8c7b3b3 100644 --- a/src/app/features/files/pages/file-detail/file-detail.component.html +++ b/src/app/features/files/pages/file-detail/file-detail.component.html @@ -67,7 +67,7 @@ (click)="shareMenu.toggle($event)" /> - + {{ item.label | translate }} diff --git a/src/app/features/files/pages/file-detail/file-detail.component.ts b/src/app/features/files/pages/file-detail/file-detail.component.ts index ec366758d..36669c57d 100644 --- a/src/app/features/files/pages/file-detail/file-detail.component.ts +++ b/src/app/features/files/pages/file-detail/file-detail.component.ts @@ -26,6 +26,7 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { ENVIRONMENT } from '@core/provider/environment.provider'; +import { UserSelectors } from '@osf/core/store/user/user.selectors'; import { CedarMetadataDataTemplateJsonApi, CedarMetadataRecordData, @@ -41,8 +42,10 @@ import { import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component'; import { MetadataTabsComponent } from '@osf/shared/components/metadata-tabs/metadata-tabs.component'; import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component'; +import { FileMenuType } from '@osf/shared/enums/file-menu-type.enum'; import { MetadataResourceEnum } from '@osf/shared/enums/metadata-resource.enum'; import { ResourceType } from '@osf/shared/enums/resource-type.enum'; +import { shouldShowItem } from '@osf/shared/helpers/file-menu.helper'; import { pathJoin } from '@osf/shared/helpers/path-join.helper'; import { getViewOnlyParam, hasViewOnlyParam } from '@osf/shared/helpers/view-only.helper'; import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service'; @@ -145,6 +148,7 @@ export class FileDetailComponent { hasWriteAccess = select(FilesSelectors.hasWriteAccess); hasViewOnly = computed(() => hasViewOnlyParam(this.router)); + private activeFlags = select(UserSelectors.getActiveFlags); safeLink: SafeResourceUrl | null = null; resourceId = ''; @@ -170,21 +174,34 @@ export class FileDetailComponent { }, ]; - shareItems = [ + private readonly allShareItems = [ { + id: `${FileMenuType.Share}-email`, label: 'files.detail.actions.share.email', command: () => this.handleEmailShare(), }, { + id: `${FileMenuType.Share}-twitter`, label: 'files.detail.actions.share.x', command: () => this.handleXShare(), }, { + id: `${FileMenuType.Share}-facebook`, label: 'files.detail.actions.share.facebook', command: () => this.handleFacebookShare(), }, + { + id: `${FileMenuType.Share}-copy-link`, + label: 'files.detail.actions.share.copyLink', + command: () => this.handleCopyLink(), + }, ]; + shareItems = computed(() => { + const flags = this.activeFlags(); + return this.allShareItems.filter((item) => shouldShowItem(item.id, flags)); + }); + tabs = signal([]); isLoading = computed(() => this.isFileLoading()); @@ -354,6 +371,10 @@ export class FileDetailComponent { window.location.href = link; } + handleCopyLink(): void { + this.copyToClipboard(this.file()?.links?.html ?? ''); + } + handleXShare(): void { const link = `https://x.com/intent/tweet?url=${this.file()?.links?.html ?? ''}&text=${this.file()?.name ?? ''}&via=OSFramework`; window.open(link, '_blank', 'noopener,noreferrer'); diff --git a/src/app/shared/components/file-menu/file-menu.component.ts b/src/app/shared/components/file-menu/file-menu.component.ts index 6135fb4bc..8b1cf96b9 100644 --- a/src/app/shared/components/file-menu/file-menu.component.ts +++ b/src/app/shared/components/file-menu/file-menu.component.ts @@ -1,3 +1,5 @@ +import { select } from '@ngxs/store'; + import { TranslatePipe } from '@ngx-translate/core'; import { MenuItem } from 'primeng/api'; @@ -7,7 +9,9 @@ import { TieredMenu } from 'primeng/tieredmenu'; import { Component, computed, inject, input, output, viewChild } from '@angular/core'; import { Router } from '@angular/router'; +import { UserSelectors } from '@osf/core/store/user/user.selectors'; import { FileMenuType } from '@osf/shared/enums/file-menu-type.enum'; +import { shouldShowItem } from '@osf/shared/helpers/file-menu.helper'; import { hasViewOnlyParam } from '@osf/shared/helpers/view-only.helper'; import { MenuManagerService } from '@osf/shared/services/menu-manager.service'; import { FileMenuAction, FileMenuData, FileMenuFlags } from '@shared/models/files/file-menu-action.model'; @@ -27,6 +31,7 @@ export class FileMenuComponent { action = output(); hasViewOnly = computed(() => hasViewOnlyParam(this.router)); + private activeFlags = select(UserSelectors.getActiveFlags); private readonly allMenuItems: MenuItem[] = [ { @@ -58,6 +63,12 @@ export class FileMenuComponent { icon: 'fab fa-facebook', command: () => this.emitAction(FileMenuType.Share, { type: 'facebook' }), }, + { + id: `${FileMenuType.Share}-copy-link`, + label: 'files.detail.actions.share.copyLink', + icon: 'fas fa-link', + command: () => this.emitAction(FileMenuType.Share, { type: 'copy-link' }), + }, ], }, { @@ -106,6 +117,14 @@ export class FileMenuComponent { ]; menuItems = computed(() => { + const flags = this.activeFlags(); + + const visibleItems = this.allMenuItems + .filter((item) => shouldShowItem(item.id, flags)) + .map((item) => + item.items ? { ...item, items: item.items.filter((sub) => shouldShowItem(sub.id, flags)) } : item + ); + if (this.hasViewOnly()) { const allowedActionsForFiles = [ FileMenuType.Download, @@ -120,7 +139,7 @@ export class FileMenuComponent { const allowedActions = this.isFolder() ? allowedActionsForFolders : allowedActionsForFiles; - return this.allMenuItems.filter((item) => { + return visibleItems.filter((item) => { if (item.command) { return allowedActions.includes(item.id as FileMenuType); } @@ -135,11 +154,11 @@ export class FileMenuComponent { if (this.isFolder()) { const disallowedActions = [FileMenuType.Share, FileMenuType.Embed]; - return this.allMenuItems.filter( + return visibleItems.filter( (item) => !disallowedActions.includes(item.id as FileMenuType) && this.allowedActions()[item.id as FileMenuType] ); } - return this.allMenuItems.filter((item) => this.allowedActions()[item.id as FileMenuType]); + return visibleItems.filter((item) => this.allowedActions()[item.id as FileMenuType]); }); onMenuToggle(event: Event): void { 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 083b19bdc..b46b11ef6 100644 --- a/src/app/shared/components/files-tree/files-tree.component.ts +++ b/src/app/shared/components/files-tree/files-tree.component.ts @@ -319,6 +319,9 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { case 'facebook': this.openLinkNewTab(facebookLink); break; + case 'copy-link': + this.copyToClipboard(file.links.html); + break; } } diff --git a/src/app/shared/helpers/file-menu.helper.ts b/src/app/shared/helpers/file-menu.helper.ts new file mode 100644 index 000000000..f52367396 --- /dev/null +++ b/src/app/shared/helpers/file-menu.helper.ts @@ -0,0 +1,8 @@ +import { FileMenuType } from '@osf/shared/enums/file-menu-type.enum'; + +export function shouldShowItem(id: string | undefined, activeFlags: string[]): boolean { + if (id === `${FileMenuType.Share}-twitter` || id === `${FileMenuType.Share}-facebook`) { + return !activeFlags.includes('disable_share_social_media'); + } + return true; +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 7c9090df4..b9827208a 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1247,7 +1247,8 @@ "share": { "email": "Email", "x": "X", - "facebook": "Facebook" + "facebook": "Facebook", + "copyLink": "Copy Link" } }, "fileMetadata": {