Skip to content

Commit

Permalink
fix(markdown): allow relative paths (#2185)
Browse files Browse the repository at this point in the history
  • Loading branch information
JulieMarie authored Jul 12, 2024
1 parent 207630d commit 3aeae73
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 78 deletions.
133 changes: 71 additions & 62 deletions libs/markdown/src/lib/markdown.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ describe('Component: Markdown', () => {
it(
'should render empty static content',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownEmptyStaticContentTestRenderingComponent
);
const fixture: ComponentFixture<TdMarkdownEmptyStaticContentTestRenderingComponent> =
TestBed.createComponent(
TdMarkdownEmptyStaticContentTestRenderingComponent
);
const element: HTMLElement = fixture.nativeElement;

expect(
Expand All @@ -108,9 +109,10 @@ describe('Component: Markdown', () => {
it(
'should render markup from static content',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownStaticContentTestRenderingComponent
);
const fixture: ComponentFixture<TdMarkdownStaticContentTestRenderingComponent> =
TestBed.createComponent(
TdMarkdownStaticContentTestRenderingComponent
);
const element: HTMLElement = fixture.nativeElement;

expect(
Expand Down Expand Up @@ -141,9 +143,10 @@ describe('Component: Markdown', () => {
it(
'should render newlines as <br/> if simpleLineBreaks is true',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownSimpleLineBreaksTestRenderingComponent
);
const fixture: ComponentFixture<TdMarkdownSimpleLineBreaksTestRenderingComponent> =
TestBed.createComponent(
TdMarkdownSimpleLineBreaksTestRenderingComponent
);
const component: TdMarkdownSimpleLineBreaksTestRenderingComponent =
fixture.debugElement.componentInstance;
component.simpleLineBreaks = true;
Expand Down Expand Up @@ -179,9 +182,10 @@ describe('Component: Markdown', () => {
it(
'should not render newlines as <br/> if simpleLineBreaks is false',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownSimpleLineBreaksTestRenderingComponent
);
const fixture: ComponentFixture<TdMarkdownSimpleLineBreaksTestRenderingComponent> =
TestBed.createComponent(
TdMarkdownSimpleLineBreaksTestRenderingComponent
);
const component: TdMarkdownSimpleLineBreaksTestRenderingComponent =
fixture.debugElement.componentInstance;
component.simpleLineBreaks = false;
Expand Down Expand Up @@ -217,9 +221,10 @@ describe('Component: Markdown', () => {
it(
'should render markup from dynamic content',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownDymanicContentTestRenderingComponent
);
const fixture: ComponentFixture<TdMarkdownDymanicContentTestRenderingComponent> =
TestBed.createComponent(
TdMarkdownDymanicContentTestRenderingComponent
);
const component: TdMarkdownDymanicContentTestRenderingComponent =
fixture.debugElement.componentInstance;
component.content = `
Expand Down Expand Up @@ -258,9 +263,10 @@ describe('Component: Markdown', () => {
it(
'should render markup from dynamic content incorrectly',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownDymanicContentTestRenderingComponent
);
const fixture: ComponentFixture<TdMarkdownDymanicContentTestRenderingComponent> =
TestBed.createComponent(
TdMarkdownDymanicContentTestRenderingComponent
);
const component: TdMarkdownDymanicContentTestRenderingComponent =
fixture.debugElement.componentInstance;
component.content = `
Expand Down Expand Up @@ -293,9 +299,8 @@ describe('Component: Markdown', () => {
it(
'should jump to anchor when anchor input is changed',
waitForAsync(async () => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownAnchorsTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownAnchorsTestEventsComponent> =
TestBed.createComponent(TdMarkdownAnchorsTestEventsComponent);
const component: TdMarkdownAnchorsTestEventsComponent =
fixture.debugElement.componentInstance;
component.content = anchorTestMarkdown();
Expand Down Expand Up @@ -333,9 +338,8 @@ describe('Component: Markdown', () => {
it(
'should jump to anchor if an anchor link is clicked',
waitForAsync(async () => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownAnchorsTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownAnchorsTestEventsComponent> =
TestBed.createComponent(TdMarkdownAnchorsTestEventsComponent);
const component: TdMarkdownAnchorsTestEventsComponent =
fixture.debugElement.componentInstance;
component.content = anchorTestMarkdown();
Expand Down Expand Up @@ -375,9 +379,8 @@ describe('Component: Markdown', () => {
);

it('should jump to anchor if an anchor link is clicked but should not run change detection', async () => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownAnchorsTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownAnchorsTestEventsComponent> =
TestBed.createComponent(TdMarkdownAnchorsTestEventsComponent);
const component: TdMarkdownAnchorsTestEventsComponent =
fixture.debugElement.componentInstance;
component.content = anchorTestMarkdown();
Expand Down Expand Up @@ -407,9 +410,8 @@ describe('Component: Markdown', () => {

it('should emit `contentReady` and should not run change detection', fakeAsync(() => {
const contentReadySpy: jest.Mock = jest.fn();
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownAnchorsTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownAnchorsTestEventsComponent> =
TestBed.createComponent(TdMarkdownAnchorsTestEventsComponent);
const component: TdMarkdownAnchorsTestEventsComponent =
fixture.debugElement.componentInstance;
component.anchor = 'heading 1';
Expand All @@ -435,9 +437,8 @@ describe('Component: Markdown', () => {
it(
'should jump to anchor if an anchor link is clicked regardless of lang',
waitForAsync(async () => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownAnchorsTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownAnchorsTestEventsComponent> =
TestBed.createComponent(TdMarkdownAnchorsTestEventsComponent);
const component: TdMarkdownAnchorsTestEventsComponent =
fixture.debugElement.componentInstance;
component.content = anchorTestNonEnglishMarkdown();
Expand Down Expand Up @@ -479,9 +480,8 @@ describe('Component: Markdown', () => {
it(
'should generate the proper urls',
waitForAsync(async () => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownLinksTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownLinksTestEventsComponent> =
TestBed.createComponent(TdMarkdownLinksTestEventsComponent);
const component: TdMarkdownLinksTestEventsComponent =
fixture.debugElement.componentInstance;

Expand All @@ -491,6 +491,7 @@ describe('Component: Markdown', () => {
const NON_RAW_LINK = 'https://github.com/Teradata/covalent/blob/main/';
const RAW_LINK =
'https://raw.githubusercontent.com/Teradata/covalent/main/';
const RELATIVE_LINK = 'assets/covalent/';
const EXTERNAL_URL = 'https://angular.io/';
const SUB_DIRECTORY = 'docs/';
const links: string[][] = [
Expand Down Expand Up @@ -521,11 +522,6 @@ describe('Component: Markdown', () => {
markdown += `* [${link[0]}](${link[0]}) \n`;
});
component.content = markdown;
component.hostedUrl = `${RAW_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;

fixture.detectChanges();
await fixture.whenStable();

const anchorElements: HTMLAnchorElement[] =
fixture.debugElement.nativeElement.querySelectorAll('a');
function checkAnchors(): void {
Expand All @@ -538,21 +534,28 @@ describe('Component: Markdown', () => {
);
}

component.hostedUrl = `${RAW_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;
fixture.detectChanges();
await fixture.whenStable();
checkAnchors();
component.hostedUrl = `${NON_RAW_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;

component.hostedUrl = `${NON_RAW_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;
fixture.detectChanges();
await fixture.whenStable();
checkAnchors();

component.hostedUrl = `${RELATIVE_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;
fixture.detectChanges();
await fixture.whenStable();
checkAnchors();
})
);

it(
'should generate the proper image urls',
waitForAsync(async () => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownLinksTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownLinksTestEventsComponent> =
TestBed.createComponent(TdMarkdownLinksTestEventsComponent);
const component: TdMarkdownLinksTestEventsComponent =
fixture.debugElement.componentInstance;

Expand All @@ -562,6 +565,7 @@ describe('Component: Markdown', () => {
const NON_RAW_LINK = 'https://github.com/Teradata/covalent/blob/main/';
const RAW_LINK =
'https://raw.githubusercontent.com/Teradata/covalent/main/';
const RELATIVE_LINK = 'assets/covalent/';
const EXTERNAL_IMG =
'https://angular.io/assets/images/logos/angular/angular.svg';
const SUB_DIRECTORY = 'dir/';
Expand All @@ -587,10 +591,6 @@ describe('Component: Markdown', () => {
markdown += `* ![${link[0]}](${link[0]}) \n`;
});
component.content = markdown;
component.hostedUrl = `${RAW_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;

fixture.detectChanges();
await fixture.whenStable();

const imageElements: HTMLImageElement[] =
fixture.debugElement.nativeElement.querySelectorAll('img');
Expand All @@ -604,12 +604,19 @@ describe('Component: Markdown', () => {
);
}

component.hostedUrl = `${RAW_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;
fixture.detectChanges();
await fixture.whenStable();
checkImages();
component.hostedUrl = `${NON_RAW_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;

component.hostedUrl = `${NON_RAW_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;
fixture.detectChanges();
await fixture.whenStable();
checkImages();

component.hostedUrl = `${RELATIVE_LINK}${SUB_DIRECTORY}${CURRENT_MD_FILE}`;
fixture.detectChanges();
await fixture.whenStable();
checkImages();
})
);
Expand All @@ -620,9 +627,10 @@ describe('Component: Markdown', () => {
it(
'should be fired only once after display renders empty static content',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownEmptyStaticContentTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownEmptyStaticContentTestEventsComponent> =
TestBed.createComponent(
TdMarkdownEmptyStaticContentTestEventsComponent
);
const component: TdMarkdownEmptyStaticContentTestEventsComponent =
fixture.debugElement.componentInstance;

Expand All @@ -639,9 +647,8 @@ describe('Component: Markdown', () => {
it(
'should be fired only once after display renders markup from static content',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownStaticContentTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownStaticContentTestEventsComponent> =
TestBed.createComponent(TdMarkdownStaticContentTestEventsComponent);
const component: TdMarkdownStaticContentTestEventsComponent =
fixture.debugElement.componentInstance;

Expand All @@ -658,9 +665,10 @@ describe('Component: Markdown', () => {
it(
'should be fired only once after display renders initial markup from dynamic content',
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownDynamicContentTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownDynamicContentTestEventsComponent> =
TestBed.createComponent(
TdMarkdownDynamicContentTestEventsComponent
);
const component: TdMarkdownDynamicContentTestEventsComponent =
fixture.debugElement.componentInstance;
jest.spyOn(component, 'tdMarkdownContentIsReady');
Expand All @@ -686,9 +694,10 @@ describe('Component: Markdown', () => {
it(
`should be fired twice after changing the initial rendered markup dynamic content`,
waitForAsync(() => {
const fixture: ComponentFixture<any> = TestBed.createComponent(
TdMarkdownDynamicContentTestEventsComponent
);
const fixture: ComponentFixture<TdMarkdownDynamicContentTestEventsComponent> =
TestBed.createComponent(
TdMarkdownDynamicContentTestEventsComponent
);
const component: TdMarkdownDynamicContentTestEventsComponent =
fixture.debugElement.componentInstance;
jest.spyOn(component, 'tdMarkdownContentIsReady');
Expand Down
48 changes: 32 additions & 16 deletions libs/markdown/src/lib/markdown.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,41 @@ import {

import * as showdown from 'showdown';

function isAbsoluteUrl(currentHref: string): boolean {
// Regular Expression to check url
const RgExp = new RegExp('^(?:[a-z]+:)?//', 'i');
return RgExp.test(currentHref);
}

// TODO: assumes it is a github url
// allow override somehow
function generateAbsoluteHref(
currentHref: string,
relativeHref: string
): string {
function generateHref(currentHref: string, relativeHref: string): string {
if (currentHref && relativeHref) {
const currentUrl: URL = new URL(currentHref);
const path: string = currentUrl.pathname.split('/').slice(1, -1).join('/');
const correctUrl: URL = new URL(currentHref);

if (relativeHref.startsWith('/')) {
// url is relative to top level
const orgAndRepo: string = path.split('/').slice(0, 3).join('/');
correctUrl.pathname = `${orgAndRepo}${relativeHref}`;
if (isAbsoluteUrl(currentHref)) {
const currentUrl: URL = new URL(currentHref);
const path: string = currentUrl.pathname
.split('/')
.slice(1, -1)
.join('/');
const correctUrl: URL = new URL(currentHref);

if (relativeHref.startsWith('/')) {
// url is relative to top level
const orgAndRepo: string = path.split('/').slice(0, 3).join('/');
correctUrl.pathname = `${orgAndRepo}${relativeHref}`;
} else {
correctUrl.pathname = `${path}/${relativeHref}`;
}
return correctUrl.href;
} else {
correctUrl.pathname = `${path}/${relativeHref}`;
const path: string = currentHref.split('/').slice(0, -1).join('/');

if (relativeHref.startsWith('/')) {
return `${path}${relativeHref}`;
} else {
return `${path}/${relativeHref}`;
}
}
return correctUrl.href;
}
return '';
}
Expand Down Expand Up @@ -76,7 +92,7 @@ function normalizeHtmlHrefs(html: string, currentHref: string): string {
link.getAttribute('href')
);

url.href = generateAbsoluteHref(currentHref, hrefWithoutHash);
url.href = generateHref(currentHref, hrefWithoutHash);

if (originalHash) {
url.hash = genHeadingId(originalHash);
Expand Down Expand Up @@ -118,7 +134,7 @@ function normalizeImageSrcs(html: string, currentHref: string): string {
image.src = rawGithubHref(src);
}
} catch {
image.src = generateAbsoluteHref(
image.src = generateHref(
isGithubHref(currentHref)
? rawGithubHref(currentHref)
: currentHref,
Expand Down

0 comments on commit 3aeae73

Please sign in to comment.