Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { STATES } from '@core/constants';
import { provideTranslation } from '@core/helpers';

import { GlobalErrorHandler } from './core/handlers';
import { authInterceptor, errorInterceptor } from './core/interceptors';
import { authInterceptor, errorInterceptor, viewOnlyInterceptor } from './core/interceptors';
import CustomPreset from './core/theme/custom-preset';
import { routes } from './app.routes';

Expand All @@ -37,7 +37,7 @@ export const appConfig: ApplicationConfig = {
},
}),
provideAnimations(),
provideHttpClient(withInterceptors([authInterceptor, errorInterceptor])),
provideHttpClient(withInterceptors([authInterceptor, viewOnlyInterceptor, errorInterceptor])),
importProvidersFrom(TranslateModule.forRoot(provideTranslation())),
ConfirmationService,
MessageService,
Expand Down
1 change: 1 addition & 0 deletions src/app/core/components/nav-menu/nav-menu.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ng-template #item let-item>
<a
[routerLink]="item.routerLink ? item.routerLink : null"
[queryParams]="item.queryParams"
routerLinkActive="active"
[routerLinkActiveOptions]="item.routerLinkActiveOptions"
class="nav-link flex align-items-center"
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/components/nav-menu/nav-menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { AuthService } from '@osf/core/services';
import { UserSelectors } from '@osf/core/store/user';
import { IconComponent } from '@osf/shared/components';
import { CurrentResourceType } from '@osf/shared/enums';
import { getViewOnlyParam } from '@osf/shared/helpers';
import { WrapFnPipe } from '@osf/shared/pipes';
import { CurrentResourceSelectors } from '@osf/shared/stores';

Expand Down Expand Up @@ -54,6 +55,7 @@ export class NavMenuComponent {
preprintReviewsPageVisible: this.canUserViewReviews(),
isCollections: this.isCollectionsRoute() || false,
currentUrl: this.router.url,
isViewOnly: !!getViewOnlyParam(this.router),
};

const items = updateMenuItems(filtered, routeContext);
Expand Down
15 changes: 15 additions & 0 deletions src/app/core/constants/nav-items.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ export const AUTHENTICATED_MENU_ITEMS: string[] = [
'settings',
];

export const VIEW_ONLY_PROJECT_MENU_ITEMS: string[] = [
'project-overview',
'project-files',
'project-wiki',
'project-analytics',
];

export const VIEW_ONLY_REGISTRY_MENU_ITEMS: string[] = [
'registration-overview',
'registration-files',
'registration-wiki',
'registration-analytics',
'registration-components',
];

export const PROJECT_MENU_ITEMS: MenuItem[] = [
{
id: 'project-overview',
Expand Down
9 changes: 9 additions & 0 deletions src/app/core/constants/view-only-excluded-routes.const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const VIEW_ONLY_EXCLUDED_ROUTES = [
'metadata',
'registrations',
'contributors',
'addons',
'settings',
'resources',
'links',
];
5 changes: 5 additions & 0 deletions src/app/core/guards/auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';

import { GetCurrentUser, UserSelectors } from '@osf/core/store/user';
import { hasViewOnlyParam } from '@osf/shared/helpers';

export const authGuard: CanActivateFn = () => {
const store = inject(Store);
Expand All @@ -17,6 +18,10 @@ export const authGuard: CanActivateFn = () => {
return true;
}

if (hasViewOnlyParam(router)) {
return true;
}

return store.dispatch(GetCurrentUser).pipe(
switchMap(() => {
return store.select(UserSelectors.isAuthenticated).pipe(
Expand Down
1 change: 1 addition & 0 deletions src/app/core/guards/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { authGuard } from './auth.guard';
export { isProjectGuard } from './is-project.guard';
export { isRegistryGuard } from './is-registry.guard';
export { redirectIfLoggedInGuard } from './redirect-if-logged-in.guard';
export { viewOnlyGuard } from './view-only.guard';
37 changes: 37 additions & 0 deletions src/app/core/guards/view-only.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';

import { VIEW_ONLY_EXCLUDED_ROUTES } from '@core/constants/view-only-excluded-routes.const';
import { hasViewOnlyParam } from '@osf/shared/helpers';

export const viewOnlyGuard: CanActivateFn = (route) => {
const router = inject(Router);

if (!hasViewOnlyParam(router)) {
return true;
}

const routePath = route.routeConfig?.path || '';

const isBlocked = VIEW_ONLY_EXCLUDED_ROUTES.some(
(blockedRoute) => routePath === blockedRoute || routePath.startsWith(`${blockedRoute}/`)
);

if (!isBlocked) {
return true;
}

const urlSegments = router.url.split('/');
const resourceId = urlSegments[1];
const viewOnlyParam = new URLSearchParams(window.location.search).get('view_only');

if (resourceId && viewOnlyParam) {
router.navigate([resourceId, 'overview'], {
queryParams: { view_only: viewOnlyParam },
});
} else {
router.navigate(['/']);
}

return false;
};
25 changes: 23 additions & 2 deletions src/app/core/helpers/nav-menu.helper.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { MenuItem } from 'primeng/api';

import { getViewOnlyParamFromUrl } from '@osf/shared/helpers';

import {
AUTHENTICATED_MENU_ITEMS,
PREPRINT_MENU_ITEMS,
PROJECT_MENU_ITEMS,
REGISTRATION_MENU_ITEMS,
VIEW_ONLY_PROJECT_MENU_ITEMS,
VIEW_ONLY_REGISTRY_MENU_ITEMS,
} from '../constants';
import { RouteContext } from '../models';

Expand Down Expand Up @@ -82,13 +86,21 @@ function updateProjectMenuItem(item: MenuItem, ctx: RouteContext): MenuItem {
const items = (item.items || []).map((subItem) => {
if (subItem.id === 'project-details') {
if (hasProject) {
let menuItems = PROJECT_MENU_ITEMS;

if (ctx.isViewOnly) {
const allowedViewOnlyItems = VIEW_ONLY_PROJECT_MENU_ITEMS;
menuItems = PROJECT_MENU_ITEMS.filter((menuItem) => allowedViewOnlyItems.includes(menuItem.id || ''));
}

return {
...subItem,
visible: true,
expanded: true,
items: PROJECT_MENU_ITEMS.map((menuItem) => ({
items: menuItems.map((menuItem) => ({
...menuItem,
routerLink: [ctx.resourceId as string, menuItem.routerLink],
queryParams: ctx.isViewOnly ? { view_only: getViewOnlyParamFromUrl(ctx.currentUrl) } : undefined,
})),
};
}
Expand All @@ -105,13 +117,21 @@ function updateRegistryMenuItem(item: MenuItem, ctx: RouteContext): MenuItem {
const items = (item.items || []).map((subItem) => {
if (subItem.id === 'registry-details') {
if (hasRegistry) {
let menuItems = REGISTRATION_MENU_ITEMS;

if (ctx.isViewOnly) {
const allowedViewOnlyItems = VIEW_ONLY_REGISTRY_MENU_ITEMS;
menuItems = REGISTRATION_MENU_ITEMS.filter((menuItem) => allowedViewOnlyItems.includes(menuItem.id || ''));
}

return {
...subItem,
visible: true,
expanded: true,
items: REGISTRATION_MENU_ITEMS.map((menuItem) => ({
items: menuItems.map((menuItem) => ({
...menuItem,
routerLink: [ctx.resourceId as string, menuItem.routerLink],
queryParams: ctx.isViewOnly ? { view_only: getViewOnlyParamFromUrl(ctx.currentUrl) } : undefined,
})),
};
}
Expand All @@ -135,6 +155,7 @@ function updatePreprintMenuItem(item: MenuItem, ctx: RouteContext): MenuItem {
items: PREPRINT_MENU_ITEMS.map((menuItem) => ({
...menuItem,
routerLink: ['preprints', ctx.providerId, ctx.resourceId as string],
queryParams: ctx.isViewOnly ? { view_only: getViewOnlyParamFromUrl(ctx.currentUrl) } : undefined,
})),
};
}
Expand Down
5 changes: 4 additions & 1 deletion src/app/core/interceptors/error.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';

import { hasViewOnlyParam } from '@osf/shared/helpers';
import { LoaderService, ToastService } from '@osf/shared/services';

import { ERROR_MESSAGES } from '../constants';
Expand All @@ -31,7 +32,9 @@ export const errorInterceptor: HttpInterceptorFn = (req, next) => {
}

if (error.status === 401) {
authService.logout();
if (!hasViewOnlyParam(router)) {
authService.logout();
}
return throwError(() => error);
}

Expand Down
1 change: 1 addition & 0 deletions src/app/core/interceptors/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './auth.interceptor';
export * from './error.interceptor';
export * from './view-only.interceptor';
29 changes: 29 additions & 0 deletions src/app/core/interceptors/view-only.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Observable } from 'rxjs';

import { HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';

import { getViewOnlyParam } from '@osf/shared/helpers';

export const viewOnlyInterceptor: HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
const router = inject(Router);

const viewOnlyParam = getViewOnlyParam(router);

if (!req.url.includes('/api.crossref.org/funders') && viewOnlyParam) {
const separator = req.url.includes('?') ? '&' : '?';
const updatedUrl = `${req.url}${separator}view_only=${encodeURIComponent(viewOnlyParam)}`;

const viewOnlyReq = req.clone({
url: updatedUrl,
});

return next(viewOnlyReq);
} else {
return next(req);
}
};
1 change: 1 addition & 0 deletions src/app/core/models/route-context.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface RouteContext {
preprintReviewsPageVisible?: boolean;
isCollections: boolean;
currentUrl?: string;
isViewOnly?: boolean;
}
1 change: 1 addition & 0 deletions src/app/features/files/models/file-target.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ export interface OsfFileTarget {
wikiEnabled: boolean;
public: boolean;
type: string;
isAnonymous?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
</div>

<div class="flex gap-3 ml-auto sm-round-padding-btn">
<p-button
severity="secondary"
[label]="'files.detail.actions.addCommunityMetadata' | translate"
[routerLink]="['/metadata']"
></p-button>
@if (!isAnonymous()) {
<p-button
severity="secondary"
[label]="'files.detail.actions.addCommunityMetadata' | translate"
[routerLink]="['/metadata']"
></p-button>
}

@if (file()?.links?.download) {
<p-button severity="secondary" (click)="downloadFile(file()?.links?.download!)">
Expand Down Expand Up @@ -55,7 +57,7 @@
</p-menu>
</div>
}
@if (file()) {
@if (file() && !isAnonymous()) {
<p-button severity="danger" (click)="confirmDelete(file()!)">
<i class="fas fa-trash p-1"></i>
</p-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,16 @@ export class FileDetailComponent {

file = select(FilesSelectors.getOpenedFile);
isFileLoading = select(FilesSelectors.isOpenedFileLoading);
isAnonymous = select(FilesSelectors.isFilesAnonymous);
safeLink: SafeResourceUrl | null = null;
resourceId = '';
resourceType = '';

isIframeLoading = true;

protected readonly FileDetailTab = FileDetailTab;
readonly FileDetailTab = FileDetailTab;

protected selectedTab: FileDetailTab = FileDetailTab.Details;
selectedTab: FileDetailTab = FileDetailTab.Details;

fileGuid = '';

Expand Down Expand Up @@ -189,27 +190,27 @@ export class FileDetailComponent {
this.selectedTab = index;
}

protected handleEmailShare(): void {
handleEmailShare(): void {
const link = `mailto:?subject=${this.file()?.name ?? ''}&body=${this.file()?.links?.html ?? ''}`;
window.location.href = link;
}

protected handleXShare(): void {
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');
}

protected handleFacebookShare(): void {
handleFacebookShare(): void {
const link = `https://www.facebook.com/dialog/share?app_id=1022273774556662&display=popup&href=${this.file()?.links?.html ?? ''}&redirect_uri=${this.file()?.links?.html ?? ''}`;
window.open(link, '_blank', 'noopener,noreferrer');
}

protected handleCopyDynamicEmbed(): void {
handleCopyDynamicEmbed(): void {
const data = embedDynamicJs.replace('ENCODED_URL', this.file()?.links?.render ?? '');
this.copyToClipboard(data);
}

protected handleCopyStaticEmbed(): void {
handleCopyStaticEmbed(): void {
const data = embedStaticHtml.replace('ENCODED_URL', this.file()?.links?.render ?? '');
this.copyToClipboard(data);
}
Expand Down
6 changes: 5 additions & 1 deletion src/app/features/files/pages/files/files.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
<osf-loading-spinner></osf-loading-spinner>
} @else {
<div class="flex flex-column bg-white gap-4 h-full flex-1 p-4 p-dialog-align-center">
@if (hasViewOnly()) {
<osf-view-only-link-message />
}

<div class="flex flex-grow-0 w-full sm:w-30rem">
<p-floatlabel class="w-full md:w-56" variant="in">
<p-select
Expand Down Expand Up @@ -55,7 +59,7 @@

<p-button icon="fas fa-info-circle blue-icon" severity="secondary" text (onClick)="showInfoDialog()"></p-button>

@if (!isViewOnly()) {
@if (!isViewOnly() && !hasViewOnly()) {
<p-button
[disabled]="fileIsUploading() || isFilesLoading()"
outlined
Expand Down
Loading
Loading