Skip to content

Commit dd506ab

Browse files
KasinhouMatus Kasakclaude
authored
Clarin9/Fix menu-links and legacy-bitstream-redirect UI test regressions (#876) (#1465)
* Clarin9/Point LINDAT header menu to the portal site links (#876) The dspace-theme header (the active LINDAT/CLARIAH-CZ theme) linked the main menu entries to in-app routes (routerLink="community-list", "education", "projects", "tools", "en/services", "partners"...). Those resolve under the /repository base href, so the top navigation no longer pointed at the LINDAT/CLARIAH-CZ portal as in the v7 production theme, failing: - homePage: "menu options should have correct target links" Restore the absolute portal targets (/services/catalog, /#education, /en/services, /partners, ...) with their Czech equivalents, matching the v7 theme. The "Repository" entry keeps routerLink="home" (it links back into the app). Re-add the locale helpers the template needs (getLangCode, getLangCodeIfCzech, translateSlug). In v9 LocaleService#getCurrentLanguageCode is asynchronous, so the current language is read synchronously from TranslateService#currentLang for use in the href bindings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Clarin9/Keep the /repository base path when redirecting legacy bitstream URLs (#876) legacyBitstreamURLRedirectGuard built the redirect target with `new URL(getBitstreamDownloadRoute(payload), HardRedirectService.getBaseUrl())`. getBitstreamDownloadRoute returns a leading-slash path (/bitstreams/<id>/download), so resolving it against a base URL keeps only the origin and drops the /repository namespace; getBaseUrl() (environment.ui.baseUrl) could also carry the internal SSR host. The old-style bitstream URL therefore redirected to a broken location (e.g. http://.../bitstreams/<id>/download without /repository, which returns 404), failing: - itemPage: "old bitstream url should redirect to the download page" Redirect to a namespace-prefixed, root-relative path instead (`<nameSpace>/bitstreams/<id>/download`) so the browser/SSR resolves it against the current origin and the target stays inside the app when it is mounted under a sub-path. Works for a root deployment too (empty namespace). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address Copilot review: normalize header UI language to en/cs getLangCode() returned TranslateService.currentLang verbatim. The deployment enables many languages (config.yml doesn't override the default list) and the language can be browser-negotiated, so a non-en/cs UI language (e.g. 'de'/'fr') produced malformed LINDAT portal links such as '/de/sluzby' (foreign locale + Czech-only slug from translateSlug). Normalize getLangCode() to the two languages the portal supports: 'cs' for Czech, 'en' for everything else. English and Czech behaviour is unchanged (so the menu-links test and v7 parity hold); other locales now fall back to well-formed English portal links. Also drops the now-unused environment import. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Trim header locale-helper comments to one-liners Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Move header language helpers to the base component (as in dtq-dev) Match the dtq-dev structure: getLangCode/getLangCodeIfCzech/translateSlug now live in the base header component (src/app/header/header.component.ts) and the themed dspace header just extends it, instead of carrying the helpers itself. getLangCode() again follows the current/system UI language (read synchronously from TranslateService, falling back to the configured language before one is set) rather than being pinned to en/cs. This restores the dtq-dev behaviour. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Shorten the legacy-bitstream-redirect comment Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Matus Kasak <matus.kasak@dataquest.sk> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 36ccc41 commit dd506ab

4 files changed

Lines changed: 52 additions & 15 deletions

File tree

src/app/bitstream-page/legacy-bitstream-url-redirect.guard.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ describe('legacyBitstreamURLRedirectGuard', () => {
151151
}));
152152
resolver(route, state, bitstreamDataService, hardRedirectService, router).subscribe(() => {
153153
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalled();
154-
expect(hardRedirectService.redirect).toHaveBeenCalledWith(new URL(`/bitstreams/${bitstream.uuid}/download`, environment.ui.baseUrl).href, 301);
154+
const nameSpace = environment.ui.nameSpace?.replace(/\/$/, '') || '';
155+
expect(hardRedirectService.redirect).toHaveBeenCalledWith(nameSpace + `/bitstreams/${bitstream.uuid}/download`, 301);
155156
});
156157
});
157158
});

src/app/bitstream-page/legacy-bitstream-url-redirect.guard.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { Observable } from 'rxjs';
1010
import { map } from 'rxjs/operators';
1111

12+
import { environment } from '../../environments/environment';
1213
import {
1314
getBitstreamDownloadRoute,
1415
PAGE_NOT_FOUND_PATH,
@@ -50,7 +51,10 @@ export const legacyBitstreamURLRedirectGuard: CanActivateFn = (
5051
getFirstCompletedRemoteData(),
5152
map((rd: RemoteData<Bitstream>) => {
5253
if (rd.hasSucceeded && !rd.hasNoContent) {
53-
serverHardRedirectService.redirect(new URL(getBitstreamDownloadRoute(rd.payload), serverHardRedirectService.getBaseUrl()).href, 301);
54+
// Prefix the UI namespace and redirect to a root-relative path so the target keeps the
55+
// '/repository' base path when the app is mounted under a sub-path.
56+
const nameSpace = environment.ui.nameSpace?.replace(/\/$/, '') || '';
57+
serverHardRedirectService.redirect(nameSpace + getBitstreamDownloadRoute(rd.payload), 301);
5458
return false;
5559
} else {
5660
return router.createUrlTree([PAGE_NOT_FOUND_PATH]);

src/app/header/header.component.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { AsyncPipe } from '@angular/common';
22
import {
33
Component,
4+
inject,
45
OnInit,
56
} from '@angular/core';
67
import { RouterLink } from '@angular/router';
78
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
8-
import { TranslateModule } from '@ngx-translate/core';
9+
import {
10+
TranslateModule,
11+
TranslateService,
12+
} from '@ngx-translate/core';
913
import { Observable } from 'rxjs';
1014

15+
import { environment } from '../../environments/environment';
1116
import { ThemedSearchNavbarComponent } from '../search-navbar/themed-search-navbar.component';
1217
import { ThemedAuthNavMenuComponent } from '../shared/auth-nav-menu/themed-auth-nav-menu.component';
1318
import {
@@ -50,6 +55,8 @@ export class HeaderComponent implements OnInit {
5055
menuID = MenuID.PUBLIC;
5156
maxMobileWidth = WidthCategory.SM;
5257

58+
private readonly translate = inject(TranslateService);
59+
5360
constructor(
5461
protected menuService: MenuService,
5562
protected windowService: HostWindowService,
@@ -63,4 +70,28 @@ export class HeaderComponent implements OnInit {
6370
public toggleNavbar(): void {
6471
this.menuService.toggleMenu(this.menuID);
6572
}
73+
74+
// Current UI language, read synchronously for the themed LINDAT portal links (in v9 LocaleService is async).
75+
getLangCode(): string {
76+
return this.translate.currentLang || environment.fallbackLanguage;
77+
}
78+
79+
// Locale segment for the portal links: 'cs' in Czech, '' otherwise.
80+
getLangCodeIfCzech(): string {
81+
return this.getLangCode() === 'cs' ? 'cs' : '';
82+
}
83+
84+
// Czech portal slug for an English slug; English keeps the original.
85+
translateSlug(slug: string): string {
86+
if (this.getLangCode() === 'en') {
87+
return slug;
88+
}
89+
const translations = {
90+
'partners': this.getLangCodeIfCzech() + '/' + 'partneri',
91+
'integration': this.getLangCodeIfCzech() + '/' + 'integrace',
92+
'partnership': this.getLangCodeIfCzech() + '/' + 'partnerstvi',
93+
'services': 'sluzby',
94+
};
95+
return translations[slug] || '';
96+
}
6697
}

src/themes/dspace/app/header/header.component.html

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,37 @@
2121
<div class="lindat-block lindat-block--clariah-theme-main-menu">
2222
<ul class="lindat-nav lindat-navbar-nav">
2323
<li class="lindat-nav-item ">
24-
<a routerLink="community-list" class="lindat-nav-link">{{'navbar.community-list' | translate}}</a>
24+
<a href="/services/catalog" class="lindat-nav-link">{{'navbar.community-list' | translate}}</a>
2525
</li>
2626
<li class="lindat-nav-item ">
2727
<a routerLink="home" class="lindat-nav-link">{{'navbar.repository' | translate}}</a>
2828
</li>
2929
<li class="lindat-nav-item ">
30-
<a routerLink="education" class="lindat-nav-link">{{'navbar.education' | translate}}</a>
30+
<a href="{{ '/' + getLangCodeIfCzech() + '#education' }}" class="lindat-nav-link">{{'navbar.education' | translate}}</a>
3131
</li>
3232
<li class="lindat-nav-item ">
33-
<a routerLink="projects" class="lindat-nav-link">{{'navbar.project' | translate}}</a>
33+
<a href="{{ '/' + getLangCodeIfCzech() + '#projects' }}" class="lindat-nav-link">{{'navbar.project' | translate}}</a>
3434
</li>
3535
<li class="lindat-nav-item ">
36-
<a routerLink="tools" class="lindat-nav-link ">{{'navbar.tools' | translate}}</a>
36+
<a href="{{ '/' + getLangCodeIfCzech() + '#tools' }}" class="lindat-nav-link ">{{'navbar.tools' | translate}}</a>
3737
</li>
3838
<li class="lindat-nav-item ">
39-
<a routerLink="en/services" class="lindat-nav-link ">{{'navbar.services' | translate}}</a>
39+
<a href="{{ '/' + getLangCode() + '/' + translateSlug('services') }}" class="lindat-nav-link ">{{'navbar.services' | translate}}</a>
4040
</li>
4141
<li class="lindat-nav-item lindat-dropdown">
42-
<!-- no routerLink here: Angular's RouterLink would navigate on every click even
43-
when the inline handler returns false, so the dropdown could never stay open -->
44-
<a href="javascript:void(0);" class="lindat-nav-link lindat-dropdown-toggle"
42+
<!-- LINDAT/CLARIAH-CZ portal links are absolute site paths, not in-app routes, so they
43+
use plain href (not routerLink). The inline handler returns false to toggle the
44+
dropdown without navigating away. -->
45+
<a href="{{ '/' + getLangCodeIfCzech() }}" class="lindat-nav-link lindat-dropdown-toggle"
4546
data-toggle="dropdown"
4647
onclick="this.parentNode.querySelector('.lindat-dropdown-toggle+div.lindat-dropdown-menu').classList.toggle('lindat-show'); return false;">{{'navbar.about' | translate}}</a>
4748
<div class="lindat-dropdown-menu">
48-
<a routerLink="partners" class="lindat-dropdown-item">{{'navbar.about.partners' | translate}}</a>
49-
<a routerLink="files/mission-en.pdf" class="lindat-dropdown-item">{{'navbar.about.mission-statement' | translate}}</a>
49+
<a href="{{ '/' + translateSlug('partners') }}" class="lindat-dropdown-item">{{'navbar.about.partners' | translate}}</a>
50+
<a href="/files/mission-en.pdf" class="lindat-dropdown-item">{{'navbar.about.mission-statement' | translate}}</a>
5051
<a href="https://www.clarin.eu/" class="lindat-dropdown-item">{{'navbar.about.clarin' | translate}}</a>
5152
<a href="https://www.dariah.eu/" class="lindat-dropdown-item">{{'navbar.about.dariah' | translate}}</a>
52-
<a routerLink="integration" class="lindat-dropdown-item">{{'navbar.about.service-integrations' | translate}}</a>
53-
<a routerLink="partnership" class="lindat-dropdown-item">{{'navbar.about.project-partnership' | translate}}</a>
53+
<a href="{{ '/' + translateSlug('integration') }}" class="lindat-dropdown-item">{{'navbar.about.service-integrations' | translate}}</a>
54+
<a href="{{ '/' + translateSlug('partnership') }}" class="lindat-dropdown-item">{{'navbar.about.project-partnership' | translate}}</a>
5455
</div>
5556
</li>
5657
</ul>

0 commit comments

Comments
 (0)