Skip to content
Open
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
65 changes: 64 additions & 1 deletion src/app/static-page/static-page.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('StaticPageComponent', () => {
...environment,
ui: {
...(environment as any).ui,
namespace: 'testNamespace'
nameSpace: '/testNamespace'
},
rest: {
...(environment as any).rest,
Expand All @@ -70,6 +70,23 @@ describe('StaticPageComponent', () => {
return { fixture, component, htmlContentService, responseService };
}

function createLinkEvent(href: string, useNestedTarget = false): Event {
const anchor = document.createElement('a');
anchor.setAttribute('href', href);

let target: EventTarget = anchor;
if (useNestedTarget) {
const nestedElement = document.createElement('span');
anchor.appendChild(nestedElement);
target = nestedElement;
}

return {
target,
preventDefault: jasmine.createSpy('preventDefault')
} as unknown as Event;
}

it('should create', async () => {
const { component } = await setupTest('<div>test</div>');
expect(component).toBeTruthy();
Expand Down Expand Up @@ -229,4 +246,50 @@ describe('StaticPageComponent', () => {
expect((component as any).changeDetector.detectChanges).toHaveBeenCalled();
});
});

describe('link handling', () => {
it('should intercept and navigate dot-relative links under the static route', async () => {
const { component } = await setupTest('<div>test</div>');
const navigateTo = spyOn<any>(component, 'navigateTo');
const event = createLinkEvent('./cite');

component.processLinks(event);

expect((event.preventDefault as jasmine.Spy)).toHaveBeenCalled();
expect(navigateTo).toHaveBeenCalledWith(`${window.location.origin}/testNamespace/static/cite`);
});

it('should resolve nested relative-link clicks inside anchors', async () => {
const { component } = await setupTest('<div>test</div>');
const navigateTo = spyOn<any>(component, 'navigateTo');
const event = createLinkEvent('../discover?query=test', true);

component.processLinks(event);

expect((event.preventDefault as jasmine.Spy)).toHaveBeenCalled();
expect(navigateTo).toHaveBeenCalledWith(`${window.location.origin}/testNamespace/discover?query=test`);
});

it('should not intercept explicit app-route links', async () => {
const { component } = await setupTest('<div>test</div>');
const navigateTo = spyOn<any>(component, 'navigateTo');
const event = createLinkEvent('contract');

component.processLinks(event);

expect((event.preventDefault as jasmine.Spy)).not.toHaveBeenCalled();
expect(navigateTo).not.toHaveBeenCalled();
});

it('should not intercept fragment links', async () => {
const { component } = await setupTest('<div>test</div>');
const navigateTo = spyOn<any>(component, 'navigateTo');
const event = createLinkEvent('#about-contracts');

component.processLinks(event);

expect((event.preventDefault as jasmine.Spy)).not.toHaveBeenCalled();
expect(navigateTo).not.toHaveBeenCalled();
});
});
});
65 changes: 21 additions & 44 deletions src/app/static-page/static-page.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { ServerResponseService } from '../core/services/server-response.service'
styleUrls: ['./static-page.component.scss']
})
export class StaticPageComponent implements OnInit {
static readonly no_static: string = 'no_static_';
htmlContent: BehaviorSubject<string> = new BehaviorSubject<string>('');
htmlFileName: string;
contentState: 'loading' | 'found' | 'not-found' = 'loading';
Expand Down Expand Up @@ -69,70 +68,48 @@ export class StaticPageComponent implements OnInit {
* @param event
*/
processLinks(event: Event): void {
const targetElement = event.target as HTMLElement;
const targetElement = event.target as HTMLElement | null;
const anchorElement = targetElement?.closest?.('a');
if (!anchorElement) {
return;
}

if (targetElement.nodeName !== 'A') {
const href = anchorElement.getAttribute('href');
if (!href || !this.isRelativeLink(href)) {
return;
}

event.preventDefault();
const namespacePrefix = this.getNamespacePrefix();
const staticPageBaseUrl = this.composeStaticPageBaseUrl(namespacePrefix);
this.redirectToRelativeLink(staticPageBaseUrl, href);
}

const href = targetElement.getAttribute('href');
const { nameSpace } = this.appConfig.ui;
const namespacePrefix = nameSpace === '/' ? '' : nameSpace;

const redirectUrl = this.composeRedirectUrl(href, namespacePrefix);

if (this.isFragmentLink(href)) {
this.redirectToFragment(redirectUrl, href);
} else if (this.isRelativeLink(href)) {
this.redirectToRelativeLink(redirectUrl, href);
} else if (this.isExternalLink(href)) {
this.redirectToExternalLink(href);
} else {
this.redirectToAbsoluteLink(redirectUrl, href, namespacePrefix);
}
private getNamespacePrefix(): string {
const nameSpace = this.appConfig?.ui?.nameSpace ?? '/';
return nameSpace === '/' ? '' : nameSpace.replace(/\/$/, '');
}

private composeRedirectUrl(href: string | null, namespacePrefix: string): string {
const staticPagePath = STATIC_PAGE_PATH;
private composeUrl(pathname: string): string {
const baseUrl = new URL(window.location.origin);
baseUrl.pathname = href.startsWith(StaticPageComponent.no_static)
? `${namespacePrefix}/`
: `${namespacePrefix}/${staticPagePath}/`;
baseUrl.pathname = pathname;
return baseUrl.href;
}

private isFragmentLink(href: string | null): boolean {
return href?.startsWith('#') ?? false;
}

private redirectToFragment(redirectUrl: string, href: string | null): void {
window.location.href = `${redirectUrl}${this.htmlFileName}${href}`;
private composeStaticPageBaseUrl(namespacePrefix: string): string {
return this.composeUrl(`${namespacePrefix}/${STATIC_PAGE_PATH}/`);
}

private isRelativeLink(href: string | null): boolean {
return href?.startsWith('.') ?? false;
}

private redirectToRelativeLink(redirectUrl: string, href: string | null): void {
window.location.href = new URL(href, redirectUrl).href;
}

private isExternalLink(href: string | null): boolean {
return (href?.startsWith('http') || href?.startsWith('www')) ?? false;
this.navigateTo(new URL(href, redirectUrl).href);
}

private redirectToExternalLink(href: string | null): void {
window.location.replace(href);
}

private redirectToAbsoluteLink(redirectUrl: string, href: string | null, namespacePrefix: string): void {
if (href.startsWith(StaticPageComponent.no_static)) {
href = href.replace(StaticPageComponent.no_static, '');
}
const absoluteUrl = new URL(href, redirectUrl.replace(namespacePrefix, ''));
window.location.href = absoluteUrl.href;
private navigateTo(url: string): void {
window.location.href = url;
}

/**
Expand Down
40 changes: 20 additions & 20 deletions src/static-files/about.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ <h2 id="ufal-point-faq">LINDAT/CLARIAH-CZ repository About and Policies</h2>

<hr />
<ul id="faqs-list">
<li><a href="#mission-statement">Mission Statement</a></li>
<li><a href="#terms-of-service">Terms of Service</a></li>
<li><a href="#about-repository">About Repository</a></li>
<li><a href="#about-ufal">About UFAL</a></li>
<li><a href="#about-contracts">License Agreement and Contracts</a></li>
<li><a href="#about-ipr">Intellectual Property Rights</a></li>
<li><a href="#privacy-policy">Privacy Policy</a></li>
<li><a href="#metadata-policy">Metadata Policy</a></li>
<li><a href="#preservation-policy">Preservation Policy</a></li>
<li><a href="./cite">Citing Data Policy</a></li>
<li><a href="static/about#mission-statement">Mission Statement</a></li>
<li><a href="static/about#terms-of-service">Terms of Service</a></li>
<li><a href="static/about#about-repository">About Repository</a></li>
<li><a href="static/about#about-ufal">About UFAL</a></li>
<li><a href="static/about#about-contracts">License Agreement and Contracts</a></li>
<li><a href="static/about#about-ipr">Intellectual Property Rights</a></li>
<li><a href="static/about#privacy-policy">Privacy Policy</a></li>
<li><a href="static/about#metadata-policy">Metadata Policy</a></li>
<li><a href="static/about#preservation-policy">Preservation Policy</a></li>
<li><a href="static/cite">Citing Data Policy</a></li>
</ul>
<hr />
<div>
Expand All @@ -33,12 +33,12 @@ <h3 id="mission-statement">Mission Statement</h3>
<div>
<h3 id="terms-of-service">Terms of Service</h3>
<p>
To achieve our mission statement,we set out some ground rules through the <a href="./terms-of-service">Terms of Service</a>. By accessing or using any kind of data or services provided by the Repository, you agree to abide by the Terms contained in the above mentioned document.
To achieve our mission statement,we set out some ground rules through the <a href="static/terms-of-service">Terms of Service</a>. By accessing or using any kind of data or services provided by the Repository, you agree to abide by the Terms contained in the above mentioned document.
</p>
<p>
Data in LINDAT/CLARIAH-CZ repository are made available under the licence attached to the resources. In case there is no licence, data is made freely available for access, printing and download for the purposes of non-commercial research or private study.

Users <strong>must</strong> acknowledge in any publication, the Deposited Work using a persistent identifier (see <a href="./cite">Citing Data</a>), its original author(s)/creator(s), and any publisher where applicable. Full items must not be harvested by robots except transiently for full-text indexing or citation analysis. Full items must not be sold commercially unless explicitly granted by the attached licence without formal permission of the copyright holders.
Users <strong>must</strong> acknowledge in any publication, the Deposited Work using a persistent identifier (see <a href="static/cite">Citing Data</a>), its original author(s)/creator(s), and any publisher where applicable. Full items must not be harvested by robots except transiently for full-text indexing or citation analysis. Full items must not be sold commercially unless explicitly granted by the attached licence without formal permission of the copyright holders.
</p>
</div>

Expand All @@ -65,15 +65,15 @@ <h3 id="about-contracts">License Agreement and Contracts</h3>
<p>At the moment, UFAL distinguishes three types of contracts.</p>
<ul>
<li>For every deposit, we enter into a standard contract with the submitter, the so-called <a id="distribution-link" href="contract">"Distribution License Agreement"</a>, in which we describe our rights and duties and the submitter acknowledges that they have the right to submit the data and gives us (the repository centre) right to distribute the data on their behalf.</li>
<li>Everyone who downloads data is bound by the licence assigned to the item - in order to download protected data, one has to be authenticated and needs to electronically sign the licence. A list of available licenses in our repository can be found <a href="no_static_licenses" id="available-licenses">here.</a></li>
<li>Everyone who downloads data is bound by the licence assigned to the item - in order to download protected data, one has to be authenticated and needs to electronically sign the licence. A list of available licenses in our repository can be found <a href="licenses" id="available-licenses">here.</a></li>
<li>For submitters, there is a possibility for setting custom licences to items during the submission workflow.</li>
</ul>
</div>
<br/>
<div>
<h3 id="about-ipr">Intellectual Property Rights</h3>
<p>
As mentioned in the section <a href="#about-contracts">License Agreement and Contracts</a>, we require the depositor of data or tools to sign a Distribution License Agreement, which specifies that they have the right to submit the data and gives us (the repository centre) right to distribute the data on their behalf. This means that depositors are solely responsible for taking care of IPR issues before publishing data or tools by submitting them to us.
As mentioned in the section <a href="static/about#about-contracts">License Agreement and Contracts</a>, we require the depositor of data or tools to sign a Distribution License Agreement, which specifies that they have the right to submit the data and gives us (the repository centre) right to distribute the data on their behalf. This means that depositors are solely responsible for taking care of IPR issues before publishing data or tools by submitting them to us.
<br/>
Should anyone have a suspicion that any of the datasets or tools in our repository violate Intellectual Property Rights, they should contact us immediately at our help desk.
</p>
Expand All @@ -90,7 +90,7 @@ <h3 id="privacy-policy">Privacy Policy</h3>
<div>
<h3 id="metadata-policy">Metadata Policy</h3>
<p>
Deposited content must be accompanied by <a href="metadata">sufficient metadata</a> describing its content, provenance and formats in order to support its preservation and dissemination. Metadata are freely accessible and are distributed in the public domain (under <a href="http://creativecommons.org/about/cc0">CC0</a>). However, we reserve the right to be informed about commercial usage of metadata from LINDAT/CLARIAH-CZ repository including a description of your use case at <a class="helpdesk-tolink" href="#">Help Desk</a>.
Deposited content must be accompanied by <a href="static/metadata">sufficient metadata</a> describing its content, provenance and formats in order to support its preservation and dissemination. Metadata are freely accessible and are distributed in the public domain (under <a href="http://creativecommons.org/about/cc0">CC0</a>). However, we reserve the right to be informed about commercial usage of metadata from LINDAT/CLARIAH-CZ repository including a description of your use case at <a class="helpdesk-tolink" href="#">Help Desk</a>.
</p>
</div>
<br/>
Expand All @@ -100,7 +100,7 @@ <h3 id="preservation-policy">Preservation Policy</h3>
<p>
LINDAT/CLARIAH-CZ is committed to the long-term care of items deposited in the repository, to preserve the
research and to help in keeping research replicable and strives to adopt the
current best practice in digital preservation. See the <a href="#mission-statement">Mission Statement</a>.
current best practice in digital preservation. See the <a href="static/about#mission-statement">Mission Statement</a>.
We follow best practice guidelines, standards and regulations set forth by CLARIN, OAIS and/or Charles
University.
</p>
Expand All @@ -110,12 +110,12 @@ <h3 id="preservation-policy">Preservation Policy</h3>
</p>
<p>
To fulfill the commitments, the repository ensures that datasets are ingested and distributed in
accordance with their license (see <a href="#about-contracts">agreements and contracts</a>). Sometimes
accordance with their license (see <a href="static/about#about-contracts">agreements and contracts</a>). Sometimes
(for licenses that do not permit public access) this means only authorized users can access the dataset.
</p>
<p>
The submission workflow as described in <a href="deposit">deposit</a> and the work of our editors ensures
discoverability (by requiring accurate <a href="#metadata-policy">metadata</a>) via our search engine,
The submission workflow as described in <a href="static/deposit">deposit</a> and the work of our editors ensures
discoverability (by requiring accurate <a href="static/about#metadata-policy">metadata</a>) via our search engine,
externally through OAI-PMH and in page metadata for certain web crawlers. Metadata are freely accessible.
</p>
<p>
Expand All @@ -126,7 +126,7 @@ <h3 id="preservation-policy">Preservation Policy</h3>
</p>
<p>
We view data and tools as primary research outputs, each submission receives a Persistent IDentifier for reference and
the users are <a href="cite">guided to</a> use them. Changes in a dataset after it has been published are
the users are <a href="static/cite">guided to</a> use them. Changes in a dataset after it has been published are
not permitted, new submission is required instead. The old and new submissions are linked through their
metadata (see <a href="https://github.com/ufal/clarin-dspace/wiki/New-Version-Guide">new version
guide</a> for more details).
Expand Down
Loading
Loading