@covalent/markdown 0.0.0-COVALENT
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.browserslistrc +16 -0
- package/.eslintrc.json +36 -0
- package/README.md +103 -0
- package/_markdown-theme.scss +72 -0
- package/jest.config.js +23 -0
- package/ng-package.json +5 -0
- package/package.json +34 -0
- package/project.json +64 -0
- package/src/lib/markdown-loader/markdown-loader.service.spec.ts +101 -0
- package/src/lib/markdown-loader/markdown-loader.service.ts +35 -0
- package/src/lib/markdown-utils/markdown-utils.spec.ts +189 -0
- package/src/lib/markdown-utils/markdown-utils.ts +148 -0
- package/src/lib/markdown.component.html +1 -0
- package/src/lib/markdown.component.scss +664 -0
- package/src/lib/markdown.component.spec.ts +786 -0
- package/src/lib/markdown.component.ts +471 -0
- package/src/lib/markdown.module.ts +23 -0
- package/src/public_api.ts +4 -0
- package/src/test-setup.ts +1 -0
- package/tailwind.config.js +13 -0
- package/tsconfig.json +29 -0
- package/tsconfig.lib.json +12 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +10 -0
package/.browserslistrc
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
|
2
|
+
# For additional information regarding the format and rule options, please see:
|
|
3
|
+
# https://github.com/browserslist/browserslist#queries
|
|
4
|
+
|
|
5
|
+
# For the full list of supported browsers by the Angular framework, please see:
|
|
6
|
+
# https://angular.dev/reference/versions#browser-support
|
|
7
|
+
|
|
8
|
+
# You can see what browsers were selected by your queries by running:
|
|
9
|
+
# npx browserslist
|
|
10
|
+
|
|
11
|
+
last 1 Chrome version
|
|
12
|
+
last 1 Firefox version
|
|
13
|
+
last 2 Edge major versions
|
|
14
|
+
last 2 Safari major versions
|
|
15
|
+
last 2 iOS major versions
|
|
16
|
+
Firefox ESR
|
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../../.eslintrc.json"],
|
|
3
|
+
"ignorePatterns": ["!**/*"],
|
|
4
|
+
"overrides": [
|
|
5
|
+
{
|
|
6
|
+
"files": ["*.ts"],
|
|
7
|
+
"extends": [
|
|
8
|
+
"plugin:@nx/angular",
|
|
9
|
+
"plugin:@angular-eslint/template/process-inline-templates"
|
|
10
|
+
],
|
|
11
|
+
"rules": {
|
|
12
|
+
"@angular-eslint/directive-selector": [
|
|
13
|
+
"error",
|
|
14
|
+
{
|
|
15
|
+
"type": "attribute",
|
|
16
|
+
"prefix": "td",
|
|
17
|
+
"style": "camelCase"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"@angular-eslint/component-selector": [
|
|
21
|
+
"error",
|
|
22
|
+
{
|
|
23
|
+
"type": "element",
|
|
24
|
+
"prefix": "td",
|
|
25
|
+
"style": "kebab-case"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"files": ["*.html"],
|
|
32
|
+
"extends": ["plugin:@nx/angular-template"],
|
|
33
|
+
"rules": {}
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
## TdMarkdownComponent: td-markdown
|
|
2
|
+
|
|
3
|
+
`<td-markdown>` is a component that parses and renders Github flavored markdown. It is based on the [showdown](https://github.com/showdownjs/showdown/) library.
|
|
4
|
+
|
|
5
|
+
**Note:** This module uses the **DomSanitizer** service to sanitize the parsed html from the showdown lib to avoid **XSS** issues.
|
|
6
|
+
|
|
7
|
+
By default, `--dev` build will log the following message in the console to let you know:
|
|
8
|
+
|
|
9
|
+
`WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).`
|
|
10
|
+
|
|
11
|
+
## API Summary
|
|
12
|
+
|
|
13
|
+
#### Inputs
|
|
14
|
+
|
|
15
|
+
- content?: string
|
|
16
|
+
|
|
17
|
+
- Markdown format content to be parsed as html markup.
|
|
18
|
+
- Used to load data dynamically. e.g. `README.md` content.
|
|
19
|
+
|
|
20
|
+
- simpleLineBreaks?: string
|
|
21
|
+
|
|
22
|
+
- Sets whether newline characters inside paragraphs and spans are parsed as <br/>.
|
|
23
|
+
- Defaults to false.
|
|
24
|
+
|
|
25
|
+
- hostedUrl?: string
|
|
26
|
+
|
|
27
|
+
- If markdown contains relative paths, this is required to generate correct urls.
|
|
28
|
+
|
|
29
|
+
- anchor?: string
|
|
30
|
+
|
|
31
|
+
- Anchor to jump to.
|
|
32
|
+
|
|
33
|
+
- fileLinkExtensions?: string[]
|
|
34
|
+
- The file extensions to monitor within anchor tags.
|
|
35
|
+
- Clicking links that end with these extensions will prevent the default action and emit 'fileClicked' event.
|
|
36
|
+
|
|
37
|
+
#### Events
|
|
38
|
+
|
|
39
|
+
- contentReady: undefined
|
|
40
|
+
|
|
41
|
+
- Event emitted after the markdown content rendering is finished.
|
|
42
|
+
|
|
43
|
+
- fileLinkClicked: URL
|
|
44
|
+
- Emitted when an anchor tag is clicked and its 'href' matches one of the extensions in 'fileLinkExtensions'.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
This component can be installed as an npm package.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm i -save @covalent/markdown
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Setup
|
|
55
|
+
|
|
56
|
+
Then, import the **CovalentMarkdownModule** in your NgModule:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { CovalentMarkdownModule } from '@covalent/markdown';
|
|
60
|
+
@NgModule({
|
|
61
|
+
imports: [
|
|
62
|
+
CovalentMarkdownModule,
|
|
63
|
+
...
|
|
64
|
+
],
|
|
65
|
+
...
|
|
66
|
+
})
|
|
67
|
+
export class MyModule {}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Theming
|
|
71
|
+
|
|
72
|
+
This module comes with its own Covalent theme which uses the material theme which is used by importing our theme scss file.
|
|
73
|
+
|
|
74
|
+
```scss
|
|
75
|
+
@use '@angular/material/theming' as mat;
|
|
76
|
+
@use '@covalent/markdown/markdown-theme' as markdown;
|
|
77
|
+
|
|
78
|
+
@include mat.core();
|
|
79
|
+
|
|
80
|
+
$primary: mat.define-palette($mat-orange, 800);
|
|
81
|
+
$accent: mat.define-palette($mat-light-blue, 600, A100, A400);
|
|
82
|
+
$warn: mat.define-palette($mat-red, 600);
|
|
83
|
+
|
|
84
|
+
$theme: mat.define-light-theme($primary, $accent, $warn);
|
|
85
|
+
|
|
86
|
+
@include markdown.covalent-markdown-theme($theme);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Example
|
|
90
|
+
|
|
91
|
+
**Html:**
|
|
92
|
+
|
|
93
|
+
```html
|
|
94
|
+
<td-markdown> # Heading ## Sub Heading (H2) ### Steps (H2) </td-markdown>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Output:**
|
|
98
|
+
|
|
99
|
+
# Heading
|
|
100
|
+
|
|
101
|
+
## Sub Heading (H2)
|
|
102
|
+
|
|
103
|
+
### Steps (H2)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
@use '@angular/material' as mat;
|
|
2
|
+
|
|
3
|
+
@mixin covalent-markdown-theme($theme) {
|
|
4
|
+
$accent: map-get($theme, accent);
|
|
5
|
+
$foreground: map-get($theme, foreground);
|
|
6
|
+
$background: map-get($theme, background);
|
|
7
|
+
|
|
8
|
+
td-markdown {
|
|
9
|
+
a {
|
|
10
|
+
color: mat.m2-get-color-from-palette($accent);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
h1,
|
|
14
|
+
h2 {
|
|
15
|
+
border-bottom-color: mat.m2-get-color-from-palette($foreground, divider);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
h3,
|
|
19
|
+
h4,
|
|
20
|
+
h5,
|
|
21
|
+
h6 {
|
|
22
|
+
color: mat.m2-get-color-from-palette($foreground, secondary-text);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
hr {
|
|
26
|
+
border-color: mat.m2-get-color-from-palette($foreground, divider);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
blockquote {
|
|
30
|
+
color: mat.m2-get-color-from-palette($foreground, secondary-text);
|
|
31
|
+
border-left-color: mat.m2-get-color-from-palette($foreground, divider);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
table {
|
|
35
|
+
th,
|
|
36
|
+
td {
|
|
37
|
+
border-color: mat.m2-get-color-from-palette($foreground, dividers);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
tr {
|
|
41
|
+
border-top-color: mat.m2-get-color-from-palette($foreground, dividers);
|
|
42
|
+
|
|
43
|
+
&:nth-child(2n) {
|
|
44
|
+
background-color: mat.m2-get-color-from-palette(
|
|
45
|
+
$foreground,
|
|
46
|
+
dividers
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
img {
|
|
53
|
+
background-color: mat.m2-get-color-from-palette($background, card);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
code {
|
|
57
|
+
background-color: mat.m2-get-color-from-palette($background, hover);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.highlight pre,
|
|
61
|
+
pre {
|
|
62
|
+
background-color: mat.m2-get-color-from-palette($background, app-bar);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
kbd {
|
|
66
|
+
color: mat.m2-get-color-from-palette($foreground, secondary-text);
|
|
67
|
+
background-color: mat.m2-get-color-from-palette($background, app-bar);
|
|
68
|
+
border-color: mat.m2-get-color-from-palette($foreground, divider);
|
|
69
|
+
border-bottom-color: mat.m2-get-color-from-palette($foreground, disabled);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
displayName: 'markdown',
|
|
3
|
+
preset: '../../jest.preset.js',
|
|
4
|
+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
|
5
|
+
globals: {},
|
|
6
|
+
coverageDirectory: '../../coverage/libs/markdown',
|
|
7
|
+
testEnvironment: 'jsdom',
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.(ts|mjs|js|html)$': [
|
|
10
|
+
'jest-preset-angular',
|
|
11
|
+
{
|
|
12
|
+
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
13
|
+
stringifyContentPathRegex: '\\.(html|svg)$',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
|
18
|
+
snapshotSerializers: [
|
|
19
|
+
'jest-preset-angular/build/serializers/no-ng-attributes',
|
|
20
|
+
'jest-preset-angular/build/serializers/ng-snapshot',
|
|
21
|
+
'jest-preset-angular/build/serializers/html-comment',
|
|
22
|
+
],
|
|
23
|
+
};
|
package/ng-package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@covalent/markdown",
|
|
3
|
+
"version": "0.0.0-COVALENT",
|
|
4
|
+
"description": "Teradata UI Platform Markdown Module",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"components",
|
|
8
|
+
"reusable",
|
|
9
|
+
"markdown"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/teradata/covalent.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/teradata/covalent/issues"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Teradata UX",
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"showdown": "0.0.0-SHOWDOWN",
|
|
22
|
+
"@angular/common": "0.0.0-NG",
|
|
23
|
+
"@angular/core": "0.0.0-NG",
|
|
24
|
+
"@angular/platform-browser": "0.0.0-NG"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"tslib": "0.0.0-TSLIB"
|
|
28
|
+
},
|
|
29
|
+
"exports": {
|
|
30
|
+
"./markdown-theme": {
|
|
31
|
+
"sass": "./_markdown-theme.scss"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "markdown",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"projectType": "library",
|
|
5
|
+
"sourceRoot": "libs/markdown/src",
|
|
6
|
+
"prefix": "covalent",
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@angular-devkit/build-angular:ng-packagr",
|
|
10
|
+
"outputs": ["{workspaceRoot}/dist/libs/markdown"],
|
|
11
|
+
"options": {
|
|
12
|
+
"project": "libs/markdown/ng-package.json"
|
|
13
|
+
},
|
|
14
|
+
"configurations": {
|
|
15
|
+
"production": {
|
|
16
|
+
"tsConfig": "libs/markdown/tsconfig.lib.prod.json"
|
|
17
|
+
},
|
|
18
|
+
"development": {
|
|
19
|
+
"tsConfig": "libs/markdown/tsconfig.lib.json"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"defaultConfiguration": "production",
|
|
23
|
+
"dependsOn": [
|
|
24
|
+
{
|
|
25
|
+
"projects": ["angular", "angular-highlight"],
|
|
26
|
+
"target": "build",
|
|
27
|
+
"params": "ignore"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"test": {
|
|
32
|
+
"executor": "@nrwl/jest:jest",
|
|
33
|
+
"outputs": ["{workspaceRoot}/coverage/libs/markdown"],
|
|
34
|
+
"options": {
|
|
35
|
+
"jestConfig": "libs/markdown/jest.config.js",
|
|
36
|
+
"passWithNoTests": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"lint": {
|
|
40
|
+
"executor": "@nx/eslint:lint",
|
|
41
|
+
"options": {
|
|
42
|
+
"lintFilePatterns": [
|
|
43
|
+
"libs/markdown/src/**/*.ts",
|
|
44
|
+
"libs/markdown/src/**/*.html",
|
|
45
|
+
"libs/markdown/flavored/**/*.ts",
|
|
46
|
+
"libs/markdown/flavored/**/*.html",
|
|
47
|
+
"libs/markdown/navigator/**/*.ts",
|
|
48
|
+
"libs/markdown/navigator/**/*.html"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"scsslint": {
|
|
53
|
+
"executor": "nx:run-commands",
|
|
54
|
+
"options": {
|
|
55
|
+
"commands": [
|
|
56
|
+
{
|
|
57
|
+
"command": "./node_modules/.bin/stylelint --allow-empty-input 'libs/markdown/**/*.scss'"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"tags": []
|
|
64
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { TestBed, inject, waitForAsync } from '@angular/core/testing';
|
|
2
|
+
import { TdMarkdownLoaderService } from './markdown-loader.service';
|
|
3
|
+
import { CovalentMarkdownModule } from '../markdown.module';
|
|
4
|
+
|
|
5
|
+
const SAMPLE_HEADING = 'Covalent Design System';
|
|
6
|
+
|
|
7
|
+
const RAW_GH_URL =
|
|
8
|
+
'https://raw.githubusercontent.com/Teradata/covalent/main/README.md';
|
|
9
|
+
const GH_URL = 'https://github.com/Teradata/covalent/blob/main/README.md';
|
|
10
|
+
const BRANCH_GH_URL =
|
|
11
|
+
'https://github.com/Teradata/covalent/blob/main/README.md';
|
|
12
|
+
const RAW_GH_BRANCH_URL =
|
|
13
|
+
'https://raw.githubusercontent.com/Teradata/covalent/main/README.md';
|
|
14
|
+
const NON_MARKDOWN_URL = 'https://teradata.github.io/covalent/#/';
|
|
15
|
+
const UNREACHABLE_URL = 'https://github.com/Teradata/covalent/tree/main/src';
|
|
16
|
+
|
|
17
|
+
describe('Service: MarkdownLoader', () => {
|
|
18
|
+
beforeEach(waitForAsync(() => {
|
|
19
|
+
TestBed.configureTestingModule({
|
|
20
|
+
imports: [CovalentMarkdownModule],
|
|
21
|
+
});
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
it('should fetch from a raw github url', waitForAsync(
|
|
25
|
+
inject(
|
|
26
|
+
[TdMarkdownLoaderService],
|
|
27
|
+
async (_markdownLoaderService: TdMarkdownLoaderService) => {
|
|
28
|
+
const markdown: string = await _markdownLoaderService.load(RAW_GH_URL);
|
|
29
|
+
expect(markdown).toContain(SAMPLE_HEADING);
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
));
|
|
33
|
+
|
|
34
|
+
it('should fetch from a non-raw github url', waitForAsync(
|
|
35
|
+
inject(
|
|
36
|
+
[TdMarkdownLoaderService],
|
|
37
|
+
async (_markdownLoaderService: TdMarkdownLoaderService) => {
|
|
38
|
+
const markdown: string = await _markdownLoaderService.load(GH_URL);
|
|
39
|
+
expect(markdown).toContain(SAMPLE_HEADING);
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
));
|
|
43
|
+
|
|
44
|
+
it('should fetch from a raw branch github url', waitForAsync(
|
|
45
|
+
inject(
|
|
46
|
+
[TdMarkdownLoaderService],
|
|
47
|
+
async (_markdownLoaderService: TdMarkdownLoaderService) => {
|
|
48
|
+
const markdown: string = await _markdownLoaderService.load(
|
|
49
|
+
RAW_GH_BRANCH_URL
|
|
50
|
+
);
|
|
51
|
+
expect(markdown).toContain(SAMPLE_HEADING);
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
));
|
|
55
|
+
|
|
56
|
+
it('should fetch from a non-raw branch github url', waitForAsync(
|
|
57
|
+
inject(
|
|
58
|
+
[TdMarkdownLoaderService],
|
|
59
|
+
async (_markdownLoaderService: TdMarkdownLoaderService) => {
|
|
60
|
+
const markdown: string = await _markdownLoaderService.load(
|
|
61
|
+
BRANCH_GH_URL
|
|
62
|
+
);
|
|
63
|
+
expect(markdown).toContain(SAMPLE_HEADING);
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
));
|
|
67
|
+
|
|
68
|
+
it('should throw error if content response type is not plain or markdown', waitForAsync(
|
|
69
|
+
inject(
|
|
70
|
+
[TdMarkdownLoaderService],
|
|
71
|
+
async (_markdownLoaderService: TdMarkdownLoaderService) => {
|
|
72
|
+
let failed = false;
|
|
73
|
+
try {
|
|
74
|
+
await _markdownLoaderService.load(NON_MARKDOWN_URL);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
failed = true;
|
|
77
|
+
} finally {
|
|
78
|
+
expect(failed).toBeTruthy();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
));
|
|
83
|
+
|
|
84
|
+
it('should throw error if url is not reachable', waitForAsync(
|
|
85
|
+
inject(
|
|
86
|
+
[TdMarkdownLoaderService],
|
|
87
|
+
async (_markdownLoaderService: TdMarkdownLoaderService) => {
|
|
88
|
+
let failed = false;
|
|
89
|
+
try {
|
|
90
|
+
await _markdownLoaderService.load(UNREACHABLE_URL);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
failed = true;
|
|
93
|
+
} finally {
|
|
94
|
+
expect(failed).toBeTruthy();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
));
|
|
99
|
+
|
|
100
|
+
// TODO: test http params
|
|
101
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Injectable, SecurityContext } from '@angular/core';
|
|
2
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
3
|
+
import { HttpClient, HttpResponse } from '@angular/common/http';
|
|
4
|
+
import { isGithubHref, rawGithubHref } from '../markdown-utils/markdown-utils';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class TdMarkdownLoaderService {
|
|
8
|
+
constructor(private _http: HttpClient, private _sanitizer: DomSanitizer) {}
|
|
9
|
+
|
|
10
|
+
async load(url: string, httpOptions: object = {}): Promise<string> {
|
|
11
|
+
const sanitizedUrl: string =
|
|
12
|
+
this._sanitizer.sanitize(SecurityContext.URL, url) ?? '';
|
|
13
|
+
let urlToGet: string = sanitizedUrl;
|
|
14
|
+
if (isGithubHref(sanitizedUrl)) {
|
|
15
|
+
urlToGet = rawGithubHref(sanitizedUrl);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const response: HttpResponse<string> | undefined = await this._http
|
|
19
|
+
.get(urlToGet, {
|
|
20
|
+
...httpOptions,
|
|
21
|
+
responseType: 'text',
|
|
22
|
+
observe: 'response',
|
|
23
|
+
})
|
|
24
|
+
.toPromise();
|
|
25
|
+
const contentType: string = response?.headers.get('Content-Type') ?? '';
|
|
26
|
+
if (
|
|
27
|
+
contentType.includes('text/plain') ||
|
|
28
|
+
contentType.includes('text/markdown')
|
|
29
|
+
) {
|
|
30
|
+
return response?.body ?? '';
|
|
31
|
+
} else {
|
|
32
|
+
throw Error(`${contentType} is not a handled content type`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
removeLeadingHash,
|
|
3
|
+
removeTrailingHash,
|
|
4
|
+
genHeadingId,
|
|
5
|
+
isAnchorLink,
|
|
6
|
+
isGithubHref,
|
|
7
|
+
rawGithubHref,
|
|
8
|
+
scrollToAnchor,
|
|
9
|
+
isRawGithubHref,
|
|
10
|
+
} from './markdown-utils';
|
|
11
|
+
import { Component } from '@angular/core';
|
|
12
|
+
import { TestBed, ComponentFixture, waitForAsync } from '@angular/core/testing';
|
|
13
|
+
import { By } from '@angular/platform-browser';
|
|
14
|
+
|
|
15
|
+
// Implementing scrollIntoView since its not implemented JSDOM
|
|
16
|
+
window.HTMLElement.prototype.scrollIntoView = function () {
|
|
17
|
+
// noop
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'td-jump-to-anchor-test-component',
|
|
22
|
+
template: `
|
|
23
|
+
<main>
|
|
24
|
+
<section id="s1">
|
|
25
|
+
<article id="a1">
|
|
26
|
+
<h1 id="heading1">heading 1</h1>
|
|
27
|
+
<h1 id="heading2">heading 2</h1>
|
|
28
|
+
</article>
|
|
29
|
+
<article id="a2">
|
|
30
|
+
<h1 id="heading3">heading 3</h1>
|
|
31
|
+
<h1 id="heading4">heading 4</h1>
|
|
32
|
+
</article>
|
|
33
|
+
</section>
|
|
34
|
+
|
|
35
|
+
<section id="s2">
|
|
36
|
+
<article id="a3">
|
|
37
|
+
<h1 id="heading5">heading 5</h1>
|
|
38
|
+
<h1 id="heading6">heading 6</h1>
|
|
39
|
+
</article>
|
|
40
|
+
<article id="a4">
|
|
41
|
+
<h1 id="heading7">heading 7</h1>
|
|
42
|
+
<h1 id="heading8">heading 8</h1>
|
|
43
|
+
</article>
|
|
44
|
+
</section>
|
|
45
|
+
</main>
|
|
46
|
+
`,
|
|
47
|
+
})
|
|
48
|
+
class JumpToAnchorTestComponent {}
|
|
49
|
+
|
|
50
|
+
describe('Markdown utils', () => {
|
|
51
|
+
beforeEach(
|
|
52
|
+
waitForAsync(() => {
|
|
53
|
+
TestBed.configureTestingModule({
|
|
54
|
+
imports: [JumpToAnchorTestComponent],
|
|
55
|
+
}).compileComponents();
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
it('removeLeadingHash should remove leading hashes', () => {
|
|
60
|
+
expect(removeLeadingHash('')).toBe('');
|
|
61
|
+
expect(removeLeadingHash('#')).toBe('');
|
|
62
|
+
expect(removeLeadingHash('#anchor')).toBe('anchor');
|
|
63
|
+
expect(removeLeadingHash('##anchor')).toBe('anchor');
|
|
64
|
+
expect(removeLeadingHash('#anchor#')).toBe('anchor#');
|
|
65
|
+
expect(removeLeadingHash('#before#anchor')).toBe('before#anchor');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('removeTrailingHash should remove trailing hashes', () => {
|
|
69
|
+
expect(removeTrailingHash('')).toBe('');
|
|
70
|
+
expect(removeTrailingHash('#')).toBe('');
|
|
71
|
+
expect(removeTrailingHash('#anchor')).toBe('');
|
|
72
|
+
expect(removeTrailingHash('before#')).toBe('before');
|
|
73
|
+
expect(removeTrailingHash('even-before#before#')).toBe('even-before');
|
|
74
|
+
expect(removeTrailingHash('before##')).toBe('before');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('genHeadingId should generate proper heading id', () => {
|
|
78
|
+
expect(genHeadingId('')).toBe('');
|
|
79
|
+
expect(genHeadingId('Markdown Utils')).toBe('markdownutils');
|
|
80
|
+
expect(genHeadingId('Markdown-Utils')).toBe('markdownutils');
|
|
81
|
+
expect(genHeadingId('Markdown_Utils')).toBe('markdownutils');
|
|
82
|
+
expect(genHeadingId('(component/module/utility)')).toBe(
|
|
83
|
+
'componentmoduleutility'
|
|
84
|
+
);
|
|
85
|
+
expect(genHeadingId('Markdown Utils!?')).toBe('markdownutils');
|
|
86
|
+
expect(genHeadingId('#markdown_utils')).toBe('markdownutils');
|
|
87
|
+
expect(genHeadingId('#markdown-utils')).toBe('markdownutils');
|
|
88
|
+
expect(genHeadingId('#markdown utils')).toBe('markdownutils');
|
|
89
|
+
expect(genHeadingId('#(component/module/utility)')).toBe(
|
|
90
|
+
'componentmoduleutility'
|
|
91
|
+
);
|
|
92
|
+
expect(genHeadingId('#')).toBe('');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('isAnchorLink should generate proper heading id', () => {
|
|
96
|
+
expect(isAnchorLink(undefined)).toBe(false);
|
|
97
|
+
const anchor: HTMLAnchorElement = document.createElement('a');
|
|
98
|
+
expect(isAnchorLink(anchor)).toBe(false);
|
|
99
|
+
anchor.setAttribute('href', '');
|
|
100
|
+
expect(isAnchorLink(anchor)).toBe(false);
|
|
101
|
+
anchor.setAttribute('href', 'github.com');
|
|
102
|
+
expect(isAnchorLink(anchor)).toBe(false);
|
|
103
|
+
anchor.setAttribute('href', '#anchor');
|
|
104
|
+
expect(isAnchorLink(anchor)).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('rawGithubHref should generate raw github href', () => {
|
|
108
|
+
expect(rawGithubHref('')).toBe('');
|
|
109
|
+
expect(rawGithubHref(undefined)).toBe('');
|
|
110
|
+
expect(rawGithubHref('invalid-href')).toBe('');
|
|
111
|
+
expect(rawGithubHref('http://non-github-url.com')).toBe('');
|
|
112
|
+
|
|
113
|
+
const something = `https://github.com/Teradata/covalent/blob/`;
|
|
114
|
+
const somethingElse =
|
|
115
|
+
'https://raw.githubusercontent.com/Teradata/covalent/';
|
|
116
|
+
const path = '/docs/CONTRIBUTING.md';
|
|
117
|
+
const developBranch = 'develop';
|
|
118
|
+
const mainBranch = 'main';
|
|
119
|
+
const anchor = '#anchor';
|
|
120
|
+
|
|
121
|
+
const DEVELOP_NON_RAW_HREF = `${something}${developBranch}${path}`;
|
|
122
|
+
const DEVELOP_RAW_HREF = `${somethingElse}${developBranch}${path}`;
|
|
123
|
+
const MAIN_NON_RAW_HREF = `${something}${mainBranch}${path}`;
|
|
124
|
+
const MAIN_RAW_HREF = `${somethingElse}${mainBranch}${path}`;
|
|
125
|
+
const DEVELOP_NON_RAW_HREF_WITH_ANCHOR = `${something}${developBranch}${path}${anchor}`;
|
|
126
|
+
const DEVELOP_RAW_HREF_WITH_ANCHOR = `${somethingElse}${developBranch}${path}${anchor}`;
|
|
127
|
+
|
|
128
|
+
expect(rawGithubHref(anchor)).toBe('');
|
|
129
|
+
expect(rawGithubHref(DEVELOP_NON_RAW_HREF)).toBe(DEVELOP_RAW_HREF);
|
|
130
|
+
expect(rawGithubHref(DEVELOP_RAW_HREF)).toBe(DEVELOP_RAW_HREF);
|
|
131
|
+
expect(rawGithubHref(MAIN_NON_RAW_HREF)).toBe(MAIN_RAW_HREF);
|
|
132
|
+
expect(rawGithubHref(MAIN_RAW_HREF)).toBe(MAIN_RAW_HREF);
|
|
133
|
+
expect(rawGithubHref(DEVELOP_NON_RAW_HREF_WITH_ANCHOR)).toBe(
|
|
134
|
+
DEVELOP_RAW_HREF_WITH_ANCHOR
|
|
135
|
+
);
|
|
136
|
+
expect(rawGithubHref(DEVELOP_RAW_HREF_WITH_ANCHOR)).toBe(
|
|
137
|
+
DEVELOP_RAW_HREF_WITH_ANCHOR
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('isGithubHref should check whether an href is from github.com', () => {
|
|
142
|
+
expect(isGithubHref('')).toBe(false);
|
|
143
|
+
expect(isGithubHref(undefined)).toBe(false);
|
|
144
|
+
expect(isGithubHref('github.com')).toBe(false);
|
|
145
|
+
expect(isGithubHref('subdomain.github.com')).toBe(false);
|
|
146
|
+
expect(isGithubHref('https://raw.githubusercontent.com')).toBe(false);
|
|
147
|
+
expect(isGithubHref('http://github.com')).toBe(true);
|
|
148
|
+
expect(isGithubHref('https://github.com')).toBe(true);
|
|
149
|
+
expect(isGithubHref('https://github.com/something')).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('isRawGithubHref should check whether an href is from raw.githubusercontent.com', () => {
|
|
153
|
+
expect(isRawGithubHref('')).toBe(false);
|
|
154
|
+
expect(isRawGithubHref(undefined)).toBe(false);
|
|
155
|
+
expect(isRawGithubHref('raw.githubusercontent.com')).toBe(false);
|
|
156
|
+
expect(isRawGithubHref('https://raw.githubusercontent.com')).toBe(true);
|
|
157
|
+
expect(isRawGithubHref('http://raw.githubusercontent.com')).toBe(true);
|
|
158
|
+
expect(isRawGithubHref('https://raw.githubusercontent.com')).toBe(true);
|
|
159
|
+
expect(isRawGithubHref('https://raw.githubusercontent.com/something')).toBe(
|
|
160
|
+
true
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('scrollToAnchor should scroll to anchor within provided element, or parent if tryParent is true', async () => {
|
|
165
|
+
const fixture: ComponentFixture<JumpToAnchorTestComponent> =
|
|
166
|
+
TestBed.createComponent(JumpToAnchorTestComponent);
|
|
167
|
+
|
|
168
|
+
fixture.detectChanges();
|
|
169
|
+
await fixture.whenRenderingDone();
|
|
170
|
+
await fixture.whenStable();
|
|
171
|
+
|
|
172
|
+
const a1: HTMLDivElement = fixture.debugElement.query(
|
|
173
|
+
By.css('#a1')
|
|
174
|
+
).nativeElement;
|
|
175
|
+
|
|
176
|
+
expect(scrollToAnchor(a1, 'heading 1', false)).toBeTruthy();
|
|
177
|
+
expect(scrollToAnchor(a1, 'heading 1', true)).toBeTruthy();
|
|
178
|
+
expect(scrollToAnchor(a1, 'heading 3', false)).toBeFalsy();
|
|
179
|
+
expect(scrollToAnchor(a1, 'heading 3', true)).toBeTruthy();
|
|
180
|
+
|
|
181
|
+
expect(scrollToAnchor(a1, 'heading 5', false)).toBeFalsy();
|
|
182
|
+
expect(scrollToAnchor(a1, 'heading 5', true)).toBeFalsy();
|
|
183
|
+
|
|
184
|
+
expect(scrollToAnchor(a1, 'heading1', false)).toBeTruthy();
|
|
185
|
+
expect(scrollToAnchor(a1, 'heading-1', false)).toBeTruthy();
|
|
186
|
+
expect(scrollToAnchor(a1, 'heading_1', false)).toBeTruthy();
|
|
187
|
+
expect(scrollToAnchor(a1, 'heading 1!', false)).toBeTruthy();
|
|
188
|
+
});
|
|
189
|
+
});
|