@covalent/highlight 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 +67 -0
- package/_highlight-theme.scss +93 -0
- package/jest.config.js +23 -0
- package/ng-package.json +5 -0
- package/package.json +37 -0
- package/project.json +53 -0
- package/src/lib/copy-code-button/copy-code-button.component.html +32 -0
- package/src/lib/copy-code-button/copy-code-button.component.scss +16 -0
- package/src/lib/copy-code-button/copy-code-button.component.spec.ts +70 -0
- package/src/lib/copy-code-button/copy-code-button.component.ts +93 -0
- package/src/lib/highlight.component.html +16 -0
- package/src/lib/highlight.component.scss +63 -0
- package/src/lib/highlight.component.spec.ts +304 -0
- package/src/lib/highlight.component.ts +253 -0
- package/src/lib/highlight.module.ts +13 -0
- package/src/public_api.ts +3 -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,67 @@
|
|
|
1
|
+
## TdTextEditorComponent: td-text-editor
|
|
2
|
+
|
|
3
|
+
`<td-text-editor>` element generates an easymde markdown editor.
|
|
4
|
+
|
|
5
|
+
## API Summary
|
|
6
|
+
|
|
7
|
+
#### Inputs
|
|
8
|
+
|
|
9
|
+
- value?: string
|
|
10
|
+
- value of text in editor
|
|
11
|
+
- options?: object
|
|
12
|
+
- Options Object of valid Configurations listed here: <a href="https://github.com/Ionaru/easy-markdown-editor#configuration">https://github.com/Ionaru/easy-markdown-editor#configuration</a>
|
|
13
|
+
|
|
14
|
+
#### Properties
|
|
15
|
+
|
|
16
|
+
- isPreviewActive?: function()
|
|
17
|
+
- is the Preview Active. Returns boolean
|
|
18
|
+
- isSideBySideActive?: function()
|
|
19
|
+
- is the Side By Side Active. Returns boolean
|
|
20
|
+
- isFullscreenActive?: function()
|
|
21
|
+
- is Full Screen Active. Returns boolean
|
|
22
|
+
- clearAutosavedValue?: function()
|
|
23
|
+
- clears Auto Saved Value. Returns void
|
|
24
|
+
- toTextArea?: function()
|
|
25
|
+
- reverts to the Initial textarea. Returns void
|
|
26
|
+
- easyMDE?: function()
|
|
27
|
+
- getter function for the underlying easyMDE Object. Returns EasyMDE
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
This component can be installed as npm package.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @covalent/text-editor
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Setup
|
|
38
|
+
|
|
39
|
+
Import the **CovalentTextEditorModule** in your NgModule:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { CovalentTextEditorModule } from '@covalent/text-editor';
|
|
43
|
+
@NgModule({
|
|
44
|
+
imports: [
|
|
45
|
+
CovalentTextEditorModule,
|
|
46
|
+
...
|
|
47
|
+
],
|
|
48
|
+
...
|
|
49
|
+
})
|
|
50
|
+
export class MyModule {}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Usage
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<td-text-editor [value]="Some Text" [options]="options"></td-text-editor>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
class MyComponent {
|
|
61
|
+
options: any = {
|
|
62
|
+
lineWrapping: true,
|
|
63
|
+
toolbar: false,
|
|
64
|
+
...
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
@mixin covalent-highlight-theme($theme) {
|
|
2
|
+
$foreground: map-get($theme, foreground);
|
|
3
|
+
$background: map-get($theme, background);
|
|
4
|
+
$is-dark: map-get($theme, is-dark);
|
|
5
|
+
|
|
6
|
+
.raw-and-copy-buttons {
|
|
7
|
+
border-color: map-get($foreground, divider);
|
|
8
|
+
|
|
9
|
+
.mat-button-toggle {
|
|
10
|
+
background-color: inherit;
|
|
11
|
+
color: map-get($foreground, text);
|
|
12
|
+
|
|
13
|
+
mat-icon {
|
|
14
|
+
color: map-get($foreground, icon);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
& + .mat-button-toggle {
|
|
18
|
+
border-left-color: map-get($foreground, divider);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
td-highlight {
|
|
24
|
+
background-color: if($is-dark, #1a1c1d, #eeeeee);
|
|
25
|
+
|
|
26
|
+
.highlight {
|
|
27
|
+
color: if($is-dark, #abb2bf, #383a42);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.raw {
|
|
31
|
+
color: if($is-dark, rgba(255, 255, 255, 87%), rgba(0, 0, 0, 87%));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.hljs-comment,
|
|
35
|
+
.hljs-quote {
|
|
36
|
+
color: if($is-dark, #5c6370, #a0a1a7);
|
|
37
|
+
font-style: italic;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.hljs-doctag,
|
|
41
|
+
.hljs-formula,
|
|
42
|
+
.hljs-keyword {
|
|
43
|
+
color: if($is-dark, #c678dd, #a626a4);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.hljs-deletion,
|
|
47
|
+
.hljs-name,
|
|
48
|
+
.hljs-tag,
|
|
49
|
+
.hljs-section,
|
|
50
|
+
.hljs-selector-tag,
|
|
51
|
+
.hljs-subst {
|
|
52
|
+
color: if($is-dark, #e06c75, #e45649);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.hljs-literal {
|
|
56
|
+
color: if($is-dark, #56b6c2, #0184bb);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.hljs-addition,
|
|
60
|
+
.hljs-attribute,
|
|
61
|
+
.hljs-meta .hljs-string,
|
|
62
|
+
.hljs-regexp,
|
|
63
|
+
.hljs-string {
|
|
64
|
+
color: if($is-dark, #98c379, #50a14f);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.hljs-attr,
|
|
68
|
+
.hljs-number,
|
|
69
|
+
.hljs-selector-attr,
|
|
70
|
+
.hljs-selector-class,
|
|
71
|
+
.hljs-selector-pseudo,
|
|
72
|
+
.hljs-template-variable,
|
|
73
|
+
.hljs-type,
|
|
74
|
+
.hljs-variable {
|
|
75
|
+
color: if($is-dark, #d19a66, #986801);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.hljs-bullet,
|
|
79
|
+
.hljs-link,
|
|
80
|
+
.hljs-meta,
|
|
81
|
+
.hljs-selector-id,
|
|
82
|
+
.hljs-symbol,
|
|
83
|
+
.hljs-title {
|
|
84
|
+
color: if($is-dark, #61aeee, #4078f2);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.hljs-built_in,
|
|
88
|
+
.hljs-class .hljs-title,
|
|
89
|
+
.hljs-title.class_ {
|
|
90
|
+
color: if($is-dark, #e6c07b, #c18401);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
displayName: 'angular-highlight',
|
|
3
|
+
preset: '../../jest.preset.js',
|
|
4
|
+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
|
5
|
+
globals: {},
|
|
6
|
+
coverageDirectory: '../../coverage/libs/angular-highlight',
|
|
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,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@covalent/highlight",
|
|
3
|
+
"version": "0.0.0-COVALENT",
|
|
4
|
+
"description": "Teradata UI Platform Highlight Module",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"components",
|
|
8
|
+
"reusable",
|
|
9
|
+
"highlight",
|
|
10
|
+
"highlightjs"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/teradata/covalent.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/teradata/covalent/issues"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Teradata UX",
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"highlight.js": "0.0.0-HIGHLIGHT",
|
|
23
|
+
"@angular/common": "0.0.0-NG",
|
|
24
|
+
"@angular/core": "0.0.0-NG",
|
|
25
|
+
"@angular/platform-browser": "0.0.0-NG",
|
|
26
|
+
"@angular/cdk": "0.0.0-NG",
|
|
27
|
+
"@angular/material": "0.0.0-MATERIAL"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"tslib": "0.0.0-TSLIB"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
"./highlight-theme": {
|
|
34
|
+
"sass": "./_highlight-theme.scss"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "angular-highlight",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"projectType": "library",
|
|
5
|
+
"sourceRoot": "libs/angular-highlight/src",
|
|
6
|
+
"prefix": "covalent",
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@angular-devkit/build-angular:ng-packagr",
|
|
10
|
+
"outputs": ["{workspaceRoot}/dist/libs/angular-highlight"],
|
|
11
|
+
"options": {
|
|
12
|
+
"project": "libs/angular-highlight/ng-package.json"
|
|
13
|
+
},
|
|
14
|
+
"configurations": {
|
|
15
|
+
"production": {
|
|
16
|
+
"tsConfig": "libs/angular-highlight/tsconfig.lib.prod.json"
|
|
17
|
+
},
|
|
18
|
+
"development": {
|
|
19
|
+
"tsConfig": "libs/angular-highlight/tsconfig.lib.json"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"defaultConfiguration": "production"
|
|
23
|
+
},
|
|
24
|
+
"test": {
|
|
25
|
+
"executor": "@nrwl/jest:jest",
|
|
26
|
+
"outputs": ["{workspaceRoot}/coverage/libs/angular-highlight"],
|
|
27
|
+
"options": {
|
|
28
|
+
"jestConfig": "libs/angular-highlight/jest.config.js",
|
|
29
|
+
"passWithNoTests": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"lint": {
|
|
33
|
+
"executor": "@nx/eslint:lint",
|
|
34
|
+
"options": {
|
|
35
|
+
"lintFilePatterns": [
|
|
36
|
+
"libs/angular-highlight/src/**/*.ts",
|
|
37
|
+
"libs/angular-highlight/src/**/*.html"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"scsslint": {
|
|
42
|
+
"executor": "nx:run-commands",
|
|
43
|
+
"options": {
|
|
44
|
+
"commands": [
|
|
45
|
+
{
|
|
46
|
+
"command": "./node_modules/.bin/stylelint --allow-empty-input 'libs/angular-highlight/**/*.scss'"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"tags": []
|
|
53
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<mat-button-toggle-group
|
|
2
|
+
multiple
|
|
3
|
+
class="raw-and-copy-buttons"
|
|
4
|
+
*ngIf="toggleRawButton; else button"
|
|
5
|
+
>
|
|
6
|
+
<mat-button-toggle (click)="toggleRawClicked()" #rawButton>{{
|
|
7
|
+
rawToggleText
|
|
8
|
+
}}</mat-button-toggle>
|
|
9
|
+
<mat-button-toggle
|
|
10
|
+
[cdkCopyToClipboard]="copiedContent"
|
|
11
|
+
[matTooltip]="copyTooltip"
|
|
12
|
+
#tooltip="matTooltip"
|
|
13
|
+
#copyButton
|
|
14
|
+
(click)="copyClicked()"
|
|
15
|
+
(cdkCopyToClipboardCopied)="textCopied($event)"
|
|
16
|
+
>
|
|
17
|
+
<mat-icon width>content_copy</mat-icon>
|
|
18
|
+
</mat-button-toggle>
|
|
19
|
+
</mat-button-toggle-group>
|
|
20
|
+
|
|
21
|
+
<ng-template #button>
|
|
22
|
+
<button
|
|
23
|
+
mat-icon-button
|
|
24
|
+
[cdkCopyToClipboard]="copiedContent"
|
|
25
|
+
class="copy-button"
|
|
26
|
+
[matTooltip]="copyTooltip"
|
|
27
|
+
#tooltip="matTooltip"
|
|
28
|
+
(cdkCopyToClipboardCopied)="textCopied($event)"
|
|
29
|
+
>
|
|
30
|
+
<mat-icon role="img">content_copy</mat-icon>
|
|
31
|
+
</button>
|
|
32
|
+
</ng-template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
mat-button-toggle-group.raw-and-copy-buttons {
|
|
2
|
+
margin-top: -8px;
|
|
3
|
+
margin-right: -8px;
|
|
4
|
+
margin-left: 8px;
|
|
5
|
+
|
|
6
|
+
::ng-deep .mat-button-toggle-label-content {
|
|
7
|
+
font-size: 12px;
|
|
8
|
+
line-height: 28px;
|
|
9
|
+
|
|
10
|
+
mat-icon {
|
|
11
|
+
width: 16px;
|
|
12
|
+
height: 16px;
|
|
13
|
+
font-size: 16px;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { TdCopyCodeButtonComponent } from './copy-code-button.component';
|
|
4
|
+
import { ClipboardModule } from '@angular/cdk/clipboard';
|
|
5
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
6
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
7
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
8
|
+
import { By } from '@angular/platform-browser';
|
|
9
|
+
|
|
10
|
+
describe('CopyCodeButtonComponent', () => {
|
|
11
|
+
let component: TdCopyCodeButtonComponent;
|
|
12
|
+
let fixture: ComponentFixture<TdCopyCodeButtonComponent>;
|
|
13
|
+
|
|
14
|
+
beforeEach(
|
|
15
|
+
waitForAsync(() => {
|
|
16
|
+
TestBed.configureTestingModule({
|
|
17
|
+
imports: [
|
|
18
|
+
ClipboardModule,
|
|
19
|
+
MatIconModule,
|
|
20
|
+
MatTooltipModule,
|
|
21
|
+
MatButtonModule,
|
|
22
|
+
TdCopyCodeButtonComponent,
|
|
23
|
+
],
|
|
24
|
+
}).compileComponents();
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
fixture = TestBed.createComponent(TdCopyCodeButtonComponent);
|
|
30
|
+
component = fixture.componentInstance;
|
|
31
|
+
fixture.detectChanges();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should create', () => {
|
|
35
|
+
expect(component).toBeTruthy();
|
|
36
|
+
expect(
|
|
37
|
+
fixture.debugElement.query(By.css('button')).nativeElement
|
|
38
|
+
).toBeTruthy();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should display default tooltip', () => {
|
|
42
|
+
component.copyCodeTooltips = undefined;
|
|
43
|
+
component.copyCodeToClipboard = true;
|
|
44
|
+
expect(component).toBeTruthy();
|
|
45
|
+
expect(
|
|
46
|
+
fixture.debugElement.query(By.css('button')).nativeElement
|
|
47
|
+
).toBeTruthy();
|
|
48
|
+
expect(
|
|
49
|
+
fixture.debugElement
|
|
50
|
+
.query(By.css('button'))
|
|
51
|
+
.nativeElement.getAttribute('ng-reflect-message')
|
|
52
|
+
).toEqual('Copy');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should override tooltip', () => {
|
|
56
|
+
component.copyCodeTooltips = { copy: 'CC', copied: 'CC Copied' };
|
|
57
|
+
component.copyCodeToClipboard = true;
|
|
58
|
+
fixture.detectChanges();
|
|
59
|
+
fixture.whenStable();
|
|
60
|
+
expect(component).toBeTruthy();
|
|
61
|
+
expect(
|
|
62
|
+
fixture.debugElement.query(By.css('button')).nativeElement
|
|
63
|
+
).toBeTruthy();
|
|
64
|
+
expect(
|
|
65
|
+
fixture.debugElement
|
|
66
|
+
.query(By.css('button'))
|
|
67
|
+
.nativeElement.getAttribute('ng-reflect-message')
|
|
68
|
+
).toEqual('CC');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
2
|
+
import {
|
|
3
|
+
Component,
|
|
4
|
+
Input,
|
|
5
|
+
ViewChild,
|
|
6
|
+
HostListener,
|
|
7
|
+
EventEmitter,
|
|
8
|
+
Output,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { MatButtonToggle, MatButtonToggleGroup } from '@angular/material/button-toggle';
|
|
11
|
+
import { MatIcon } from '@angular/material/icon';
|
|
12
|
+
import { MatTooltip } from '@angular/material/tooltip';
|
|
13
|
+
import { ClipboardModule } from '@angular/cdk/clipboard';
|
|
14
|
+
|
|
15
|
+
export interface ICopyCodeTooltips {
|
|
16
|
+
copy?: string;
|
|
17
|
+
copied?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IRawToggleLabels {
|
|
21
|
+
viewRaw?: string;
|
|
22
|
+
viewCode?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'td-copy-code-button',
|
|
27
|
+
templateUrl: './copy-code-button.component.html',
|
|
28
|
+
styleUrls: ['./copy-code-button.component.scss'],
|
|
29
|
+
imports: [CommonModule, MatButtonToggle, MatButtonToggleGroup, MatIcon, MatTooltip, ClipboardModule],
|
|
30
|
+
})
|
|
31
|
+
export class TdCopyCodeButtonComponent {
|
|
32
|
+
// private _copyCodeTooltips: ICopyCodeTooltips = {};
|
|
33
|
+
@Input() copiedContent!: string;
|
|
34
|
+
@Input() copyCodeToClipboard = false;
|
|
35
|
+
/**
|
|
36
|
+
* copyCodeTooltips?: ICopyCodeTooltips
|
|
37
|
+
*
|
|
38
|
+
* Tooltips for copy button to copy and upon copying.
|
|
39
|
+
*/
|
|
40
|
+
@Input() copyCodeTooltips?: ICopyCodeTooltips = {};
|
|
41
|
+
|
|
42
|
+
@Input() toggleRawButton = false;
|
|
43
|
+
@Input() rawToggleLabels?: IRawToggleLabels = {};
|
|
44
|
+
|
|
45
|
+
rawToggle = false;
|
|
46
|
+
|
|
47
|
+
@Output() toggleRaw = new EventEmitter<boolean>();
|
|
48
|
+
|
|
49
|
+
@ViewChild('copyButton') copyButton!: MatButtonToggle;
|
|
50
|
+
@ViewChild('rawButton') rawButton!: MatButtonToggle;
|
|
51
|
+
|
|
52
|
+
get copyTooltip(): string {
|
|
53
|
+
return (this.copyCodeTooltips && this.copyCodeTooltips.copy) || 'Copy';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get copiedTooltip(): string {
|
|
57
|
+
return (this.copyCodeTooltips && this.copyCodeTooltips.copied) || 'Copied';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get rawToggleText(): string {
|
|
61
|
+
if (this.rawToggle) {
|
|
62
|
+
return this.rawToggleLabels?.viewCode || 'View code';
|
|
63
|
+
} else {
|
|
64
|
+
return this.rawToggleLabels?.viewRaw || 'Raw';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@ViewChild('tooltip') tooltip!: MatTooltip;
|
|
69
|
+
|
|
70
|
+
textCopied(event: boolean): void {
|
|
71
|
+
if (event) {
|
|
72
|
+
this.tooltip.hide();
|
|
73
|
+
this.tooltip.message = this.copiedTooltip;
|
|
74
|
+
this.tooltip.show();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
@HostListener('mouseleave')
|
|
78
|
+
initializeTooltip(): void {
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
this.tooltip.message = this.copyTooltip;
|
|
81
|
+
}, 200);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
toggleRawClicked(): void {
|
|
85
|
+
this.rawToggle = !this.rawToggle;
|
|
86
|
+
this.toggleRaw.emit();
|
|
87
|
+
this.rawButton.checked = false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
copyClicked(): void {
|
|
91
|
+
this.copyButton.checked = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<div #highlightComponent>
|
|
3
|
+
<ng-content></ng-content>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div #copyComponent *ngIf="copyCodeToClipboard">
|
|
7
|
+
<td-copy-code-button
|
|
8
|
+
[toggleRawButton]="toggleRawButton"
|
|
9
|
+
[rawToggleLabels]="rawToggleLabels"
|
|
10
|
+
(toggleRaw)="toggleRawClicked()"
|
|
11
|
+
[copiedContent]="copyContent"
|
|
12
|
+
[copyCodeToClipboard]="copyCodeToClipboard"
|
|
13
|
+
[copyCodeTooltips]="copyCodeTooltips"
|
|
14
|
+
></td-copy-code-button>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@use '@covalent/tokens' as tokens;
|
|
2
|
+
$typography: map-get(tokens.$tokens, typography);
|
|
3
|
+
|
|
4
|
+
$code-font: 'Menlo', 'Monaco', 'Andale Mono', 'lucida console', 'Courier New',
|
|
5
|
+
monospace;
|
|
6
|
+
$padding: 16px;
|
|
7
|
+
|
|
8
|
+
:host ::ng-deep {
|
|
9
|
+
overflow-x: auto;
|
|
10
|
+
padding: $padding;
|
|
11
|
+
display: flex;
|
|
12
|
+
position: relative;
|
|
13
|
+
|
|
14
|
+
pre,
|
|
15
|
+
code {
|
|
16
|
+
font-family: $code-font;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pre {
|
|
20
|
+
display: block;
|
|
21
|
+
overflow-x: auto;
|
|
22
|
+
padding: 0;
|
|
23
|
+
margin: 0;
|
|
24
|
+
background: transparent;
|
|
25
|
+
font-family: $code-font;
|
|
26
|
+
line-height: 1.45;
|
|
27
|
+
tab-size: 2;
|
|
28
|
+
-webkit-font-smoothing: auto;
|
|
29
|
+
text-size-adjust: none;
|
|
30
|
+
position: relative;
|
|
31
|
+
border-radius: 2px;
|
|
32
|
+
font-size: 0.8rem;
|
|
33
|
+
width: 100%;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
code {
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0;
|
|
39
|
+
overflow-wrap: break-word;
|
|
40
|
+
white-space: pre-wrap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
div.raw {
|
|
44
|
+
flex-grow: 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.highlight {
|
|
48
|
+
display: block;
|
|
49
|
+
overflow-wrap: break-word;
|
|
50
|
+
margin: 0;
|
|
51
|
+
font-family: map-get($typography, code-font-family);
|
|
52
|
+
font-size: map-get($typography, code-font-size);
|
|
53
|
+
font-weight: map-get($typography, code-font-weight);
|
|
54
|
+
line-height: map-get($typography, code-line-height);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.copy-button {
|
|
58
|
+
border: none;
|
|
59
|
+
background: inherit;
|
|
60
|
+
margin-top: -8px;
|
|
61
|
+
margin-right: -8px;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { TestBed, waitForAsync, fakeAsync } from '@angular/core/testing';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { By } from '@angular/platform-browser';
|
|
4
|
+
import { TdHighlightComponent } from './highlight.component';
|
|
5
|
+
|
|
6
|
+
describe('Component: Highlight', () => {
|
|
7
|
+
beforeEach(waitForAsync(() => {
|
|
8
|
+
TestBed.configureTestingModule({
|
|
9
|
+
imports: [
|
|
10
|
+
TdHighlightEmptyStaticTestRenderingComponent,
|
|
11
|
+
TdHighlightStaticHtmlTestRenderingComponent,
|
|
12
|
+
TdHighlightDynamicCssTestRenderingComponent,
|
|
13
|
+
TdHighlightUndefinedLangTestRenderingComponent,
|
|
14
|
+
|
|
15
|
+
TdHighlightEmptyStaticTestEventsComponent,
|
|
16
|
+
TdHighlightStaticHtmlTestEventsComponent,
|
|
17
|
+
TdHighlightDynamicCssTestEventsComponent,
|
|
18
|
+
TdHighlightUndefinedLangTestEventsComponent,
|
|
19
|
+
],
|
|
20
|
+
});
|
|
21
|
+
TestBed.compileComponents();
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe('Rendering: ', () => {
|
|
25
|
+
it('should render empty', waitForAsync(() => {
|
|
26
|
+
fakeAsync(() => {
|
|
27
|
+
const fixture = TestBed.createComponent(
|
|
28
|
+
TdHighlightEmptyStaticTestRenderingComponent
|
|
29
|
+
);
|
|
30
|
+
const element: HTMLElement = fixture.nativeElement;
|
|
31
|
+
|
|
32
|
+
expect(
|
|
33
|
+
fixture.debugElement
|
|
34
|
+
.query(By.css('td-highlight'))
|
|
35
|
+
.nativeElement.textContent.trim()
|
|
36
|
+
).toBe(``);
|
|
37
|
+
expect(element.querySelector('td-highlight pre code')).toBeFalsy();
|
|
38
|
+
fixture.detectChanges();
|
|
39
|
+
fixture.whenStable().then(() => {
|
|
40
|
+
fixture.detectChanges();
|
|
41
|
+
expect(element.querySelector('td-highlight pre code')).toBeFalsy();
|
|
42
|
+
expect(
|
|
43
|
+
fixture.debugElement
|
|
44
|
+
.query(By.css('td-highlight > div > div'))
|
|
45
|
+
.nativeElement.textContent.trim()
|
|
46
|
+
).toBe('');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
it('should render code from static content', waitForAsync(() => {
|
|
52
|
+
const fixture = TestBed.createComponent(
|
|
53
|
+
TdHighlightStaticHtmlTestRenderingComponent
|
|
54
|
+
);
|
|
55
|
+
const element: HTMLElement = fixture.nativeElement;
|
|
56
|
+
|
|
57
|
+
expect(element.querySelector('td-highlight pre code')).toBeFalsy();
|
|
58
|
+
fixture.detectChanges();
|
|
59
|
+
fixture.whenStable().then(() => {
|
|
60
|
+
fixture.detectChanges();
|
|
61
|
+
expect(
|
|
62
|
+
fixture.debugElement
|
|
63
|
+
.query(By.css('td-highlight'))
|
|
64
|
+
.nativeElement.textContent.trim()
|
|
65
|
+
).toContain(`{{property}}`.trim());
|
|
66
|
+
expect(element.querySelector('td-highlight pre code')).toBeTruthy();
|
|
67
|
+
expect(
|
|
68
|
+
element.querySelector('td-highlight pre code')?.textContent?.trim()
|
|
69
|
+
).toContain(`{{property}}`);
|
|
70
|
+
expect(element.querySelectorAll('.hljs-tag').length).toBe(6);
|
|
71
|
+
});
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
it('should render code from dynamic content', waitForAsync(() => {
|
|
75
|
+
const fixture = TestBed.createComponent(
|
|
76
|
+
TdHighlightDynamicCssTestRenderingComponent
|
|
77
|
+
);
|
|
78
|
+
const component: TdHighlightDynamicCssTestRenderingComponent =
|
|
79
|
+
fixture.debugElement.componentInstance;
|
|
80
|
+
component.content = `
|
|
81
|
+
pre {
|
|
82
|
+
background: #002451;
|
|
83
|
+
border-radius: 2px;
|
|
84
|
+
}`;
|
|
85
|
+
const element: HTMLElement = fixture.nativeElement;
|
|
86
|
+
|
|
87
|
+
expect(
|
|
88
|
+
fixture.debugElement
|
|
89
|
+
.query(By.css('td-highlight > div > div'))
|
|
90
|
+
.nativeElement.textContent.trim()
|
|
91
|
+
).toBe('');
|
|
92
|
+
expect(element.querySelector('td-highlight pre code')).toBeFalsy();
|
|
93
|
+
fixture.detectChanges();
|
|
94
|
+
fixture.whenStable().then(() => {
|
|
95
|
+
fixture.detectChanges();
|
|
96
|
+
expect(element.querySelector('td-highlight pre code')).toBeTruthy();
|
|
97
|
+
expect(element.querySelectorAll('.hljs-number').length).toBe(2);
|
|
98
|
+
});
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
it('should throw error for undefined language', waitForAsync(() => {
|
|
102
|
+
const fixture = TestBed.createComponent(
|
|
103
|
+
TdHighlightUndefinedLangTestRenderingComponent
|
|
104
|
+
);
|
|
105
|
+
expect(function (): void {
|
|
106
|
+
fixture.detectChanges();
|
|
107
|
+
}).toThrowError();
|
|
108
|
+
}));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('Event bindings: ', () => {
|
|
112
|
+
describe('contentReady event: ', () => {
|
|
113
|
+
it('should be fired only once after display renders empty static content', waitForAsync(() => {
|
|
114
|
+
const fixture = TestBed.createComponent(
|
|
115
|
+
TdHighlightEmptyStaticTestEventsComponent
|
|
116
|
+
);
|
|
117
|
+
const component: TdHighlightEmptyStaticTestEventsComponent =
|
|
118
|
+
fixture.debugElement.componentInstance;
|
|
119
|
+
jest.spyOn(component, 'tdHighlightContentIsReady');
|
|
120
|
+
|
|
121
|
+
fixture.detectChanges();
|
|
122
|
+
fixture.whenStable().then(() => {
|
|
123
|
+
fixture.detectChanges();
|
|
124
|
+
expect(component.tdHighlightContentIsReady).toHaveBeenCalledTimes(1);
|
|
125
|
+
});
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
it('should be fired only once after display renders highlight from static html', waitForAsync(() => {
|
|
129
|
+
const fixture = TestBed.createComponent(
|
|
130
|
+
TdHighlightStaticHtmlTestEventsComponent
|
|
131
|
+
);
|
|
132
|
+
const component: TdHighlightStaticHtmlTestEventsComponent =
|
|
133
|
+
fixture.debugElement.componentInstance;
|
|
134
|
+
jest.spyOn(component, 'tdHighlightContentIsReady');
|
|
135
|
+
|
|
136
|
+
fixture.detectChanges();
|
|
137
|
+
fixture.whenStable().then(() => {
|
|
138
|
+
fixture.detectChanges();
|
|
139
|
+
expect(component.tdHighlightContentIsReady).toHaveBeenCalledTimes(1);
|
|
140
|
+
});
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
it('should be fired only once after display renders inital highlight from dynamic css content', waitForAsync(() => {
|
|
144
|
+
const fixture = TestBed.createComponent(
|
|
145
|
+
TdHighlightDynamicCssTestEventsComponent
|
|
146
|
+
);
|
|
147
|
+
const component: TdHighlightDynamicCssTestEventsComponent =
|
|
148
|
+
fixture.debugElement.componentInstance;
|
|
149
|
+
jest.spyOn(component, 'tdHighlightContentIsReady');
|
|
150
|
+
|
|
151
|
+
// Inital dynamic css content
|
|
152
|
+
component.content = `
|
|
153
|
+
pre {
|
|
154
|
+
background: #002451;
|
|
155
|
+
border-radius: 2px;
|
|
156
|
+
}`;
|
|
157
|
+
|
|
158
|
+
fixture.detectChanges();
|
|
159
|
+
fixture.whenStable().then(() => {
|
|
160
|
+
fixture.detectChanges();
|
|
161
|
+
expect(component.tdHighlightContentIsReady).toHaveBeenCalledTimes(1);
|
|
162
|
+
});
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
it('should be fired twice after changing the inital rendered highlight dynamic css content', waitForAsync(() => {
|
|
166
|
+
const fixture = TestBed.createComponent(
|
|
167
|
+
TdHighlightDynamicCssTestEventsComponent
|
|
168
|
+
);
|
|
169
|
+
const component: TdHighlightDynamicCssTestEventsComponent =
|
|
170
|
+
fixture.debugElement.componentInstance;
|
|
171
|
+
jest.spyOn(component, 'tdHighlightContentIsReady');
|
|
172
|
+
|
|
173
|
+
component.content = `
|
|
174
|
+
pre {
|
|
175
|
+
background: #002451;
|
|
176
|
+
border-radius: 2px;
|
|
177
|
+
}`;
|
|
178
|
+
|
|
179
|
+
fixture.detectChanges();
|
|
180
|
+
|
|
181
|
+
component.content = `
|
|
182
|
+
pre {
|
|
183
|
+
color: red;
|
|
184
|
+
background: #000000;
|
|
185
|
+
border-radius: 1em;
|
|
186
|
+
}`;
|
|
187
|
+
|
|
188
|
+
fixture.detectChanges();
|
|
189
|
+
fixture.whenStable().then(() => {
|
|
190
|
+
fixture.detectChanges();
|
|
191
|
+
expect(component.tdHighlightContentIsReady).toBeCalledTimes(2);
|
|
192
|
+
});
|
|
193
|
+
}));
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Use the 4 components below to test the rendering requirements of the TdHighlight component.
|
|
199
|
+
@Component({
|
|
200
|
+
template: ` <td-highlight></td-highlight> `,
|
|
201
|
+
imports: [TdHighlightComponent],
|
|
202
|
+
})
|
|
203
|
+
class TdHighlightEmptyStaticTestRenderingComponent {}
|
|
204
|
+
|
|
205
|
+
@Component({
|
|
206
|
+
template: `
|
|
207
|
+
<td-highlight codeLang="html">
|
|
208
|
+
{{ dataHtml0 }}
|
|
209
|
+
</td-highlight>
|
|
210
|
+
`,
|
|
211
|
+
imports: [TdHighlightComponent],
|
|
212
|
+
})
|
|
213
|
+
class TdHighlightStaticHtmlTestRenderingComponent {
|
|
214
|
+
dataHtml0 = `
|
|
215
|
+
<td-highlight codeLang="html">
|
|
216
|
+
<h1>hello world!</h1>
|
|
217
|
+
<span>{{property}}</span>
|
|
218
|
+
</td-highlight>
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@Component({
|
|
223
|
+
template: `
|
|
224
|
+
<td-highlight codeLang="css" [content]="content"></td-highlight>
|
|
225
|
+
`,
|
|
226
|
+
imports: [TdHighlightComponent],
|
|
227
|
+
})
|
|
228
|
+
class TdHighlightDynamicCssTestRenderingComponent {
|
|
229
|
+
content!: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@Component({
|
|
233
|
+
template: ` <td-highlight [codeLang]="lang"></td-highlight> `,
|
|
234
|
+
imports: [TdHighlightComponent],
|
|
235
|
+
})
|
|
236
|
+
class TdHighlightUndefinedLangTestRenderingComponent {
|
|
237
|
+
lang!: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Use the 4 components below to test event binding requirements of the TdHighlight component.
|
|
241
|
+
@Component({
|
|
242
|
+
template: `
|
|
243
|
+
<td-highlight (contentReady)="tdHighlightContentIsReady()"></td-highlight>
|
|
244
|
+
`,
|
|
245
|
+
imports: [TdHighlightComponent],
|
|
246
|
+
})
|
|
247
|
+
class TdHighlightEmptyStaticTestEventsComponent {
|
|
248
|
+
tdHighlightContentIsReady(): void {
|
|
249
|
+
/* Stub */
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@Component({
|
|
254
|
+
template: `
|
|
255
|
+
<td-highlight codeLang="html" (contentReady)="tdHighlightContentIsReady()">
|
|
256
|
+
{{ dataHtml }}
|
|
257
|
+
</td-highlight>
|
|
258
|
+
`,
|
|
259
|
+
imports: [TdHighlightComponent],
|
|
260
|
+
})
|
|
261
|
+
class TdHighlightStaticHtmlTestEventsComponent {
|
|
262
|
+
dataHtml = `
|
|
263
|
+
<td-highlight codeLang="html">
|
|
264
|
+
<h1>hello world!</h1>
|
|
265
|
+
<span>{ {property} }</span>
|
|
266
|
+
</td-highlight>
|
|
267
|
+
`;
|
|
268
|
+
tdHighlightContentIsReady(): void {
|
|
269
|
+
/* Stub */
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@Component({
|
|
274
|
+
template: `
|
|
275
|
+
<td-highlight
|
|
276
|
+
codeLang="css"
|
|
277
|
+
[content]="content"
|
|
278
|
+
(contentReady)="tdHighlightContentIsReady()"
|
|
279
|
+
></td-highlight>
|
|
280
|
+
`,
|
|
281
|
+
imports: [TdHighlightComponent],
|
|
282
|
+
})
|
|
283
|
+
class TdHighlightDynamicCssTestEventsComponent {
|
|
284
|
+
content!: string;
|
|
285
|
+
tdHighlightContentIsReady(): void {
|
|
286
|
+
/* Stub */
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@Component({
|
|
291
|
+
template: `
|
|
292
|
+
<td-highlight
|
|
293
|
+
[codeLang]="lang"
|
|
294
|
+
(contentReady)="tdHighlightContentIsReady()"
|
|
295
|
+
></td-highlight>
|
|
296
|
+
`,
|
|
297
|
+
imports: [TdHighlightComponent],
|
|
298
|
+
})
|
|
299
|
+
class TdHighlightUndefinedLangTestEventsComponent {
|
|
300
|
+
lang!: string;
|
|
301
|
+
tdHighlightContentIsReady(): void {
|
|
302
|
+
/* Stub */
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
AfterViewInit,
|
|
4
|
+
ElementRef,
|
|
5
|
+
Input,
|
|
6
|
+
Output,
|
|
7
|
+
EventEmitter,
|
|
8
|
+
Renderer2,
|
|
9
|
+
SecurityContext,
|
|
10
|
+
ViewChild,
|
|
11
|
+
ChangeDetectorRef,
|
|
12
|
+
AfterViewChecked,
|
|
13
|
+
} from '@angular/core';
|
|
14
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
15
|
+
import { MatTooltip } from '@angular/material/tooltip';
|
|
16
|
+
import {
|
|
17
|
+
ICopyCodeTooltips,
|
|
18
|
+
IRawToggleLabels,
|
|
19
|
+
TdCopyCodeButtonComponent,
|
|
20
|
+
} from './copy-code-button/copy-code-button.component';
|
|
21
|
+
|
|
22
|
+
import hljs, { HighlightResult } from 'highlight.js';
|
|
23
|
+
import { CommonModule } from '@angular/common';
|
|
24
|
+
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'td-highlight',
|
|
27
|
+
styleUrls: ['./highlight.component.scss'],
|
|
28
|
+
templateUrl: './highlight.component.html',
|
|
29
|
+
imports: [CommonModule, TdCopyCodeButtonComponent],
|
|
30
|
+
})
|
|
31
|
+
export class TdHighlightComponent implements AfterViewInit, AfterViewChecked {
|
|
32
|
+
private _initialized = false;
|
|
33
|
+
|
|
34
|
+
private _content!: string;
|
|
35
|
+
private _lang = 'typescript';
|
|
36
|
+
|
|
37
|
+
private _showRaw = false;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* content?: string
|
|
41
|
+
*
|
|
42
|
+
* Code content to be parsed as highlighted html.
|
|
43
|
+
* Used to load data dynamically.
|
|
44
|
+
*
|
|
45
|
+
* e.g. `.html`, `.ts` , etc.
|
|
46
|
+
*/
|
|
47
|
+
@Input()
|
|
48
|
+
set content(content: string) {
|
|
49
|
+
this._content = content;
|
|
50
|
+
if (this._initialized) {
|
|
51
|
+
this._loadContent(this._content);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* copyCodeToClipboard?: boolean
|
|
57
|
+
*
|
|
58
|
+
* Display copy button on code snippets to copy code to clipboard.
|
|
59
|
+
*/
|
|
60
|
+
@Input() copyCodeToClipboard? = false;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* copyCodeTooltips?: ICopyCodeTooltips
|
|
64
|
+
*
|
|
65
|
+
* Tooltips for copy button to copy and upon copying.
|
|
66
|
+
*/
|
|
67
|
+
@Input() copyCodeTooltips?: ICopyCodeTooltips = {};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* toggleRawButton?: boolean
|
|
71
|
+
*
|
|
72
|
+
* Display button to toggle raw code.
|
|
73
|
+
*/
|
|
74
|
+
@Input() toggleRawButton = false;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* rawToggleLabels?: IRawToggleLabels
|
|
78
|
+
*
|
|
79
|
+
* Labels for raw toggle button.
|
|
80
|
+
*/
|
|
81
|
+
@Input() rawToggleLabels?: IRawToggleLabels = {};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* lang?: string
|
|
85
|
+
*
|
|
86
|
+
* Language of the code content to be parsed as highlighted html.
|
|
87
|
+
* Defaults to `typescript`
|
|
88
|
+
*
|
|
89
|
+
* e.g. `typescript`, `html` , etc.
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
@Input()
|
|
93
|
+
set codeLang(lang: string) {
|
|
94
|
+
this.setLanguage(lang);
|
|
95
|
+
}
|
|
96
|
+
/** @deprecated - removed completely @4.0.0 */
|
|
97
|
+
@Input()
|
|
98
|
+
set lang(lang: string) {
|
|
99
|
+
// tslint:disable-next-line: no-console
|
|
100
|
+
console.warn(
|
|
101
|
+
'DEPRECATION WARNING: switch to codeLang attribute as lang attribute is deprecated.'
|
|
102
|
+
);
|
|
103
|
+
this.setLanguage(lang);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
copyContent!: string;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* contentReady?: function
|
|
110
|
+
* Event emitted after the highlight content rendering is finished.
|
|
111
|
+
*/
|
|
112
|
+
@Output() contentReady: EventEmitter<void> = new EventEmitter<void>();
|
|
113
|
+
@ViewChild('highlightComponent') highlightComp!: ElementRef;
|
|
114
|
+
@ViewChild('copyComponent') copyComp!: ElementRef;
|
|
115
|
+
|
|
116
|
+
@ViewChild('tooltip') tooltip!: MatTooltip;
|
|
117
|
+
|
|
118
|
+
constructor(
|
|
119
|
+
private _renderer: Renderer2,
|
|
120
|
+
private _elementRef: ElementRef,
|
|
121
|
+
private _domSanitizer: DomSanitizer,
|
|
122
|
+
private cdr: ChangeDetectorRef
|
|
123
|
+
) {}
|
|
124
|
+
|
|
125
|
+
ngAfterViewChecked(): void {
|
|
126
|
+
this.cdr.detectChanges();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ngAfterViewInit(): void {
|
|
130
|
+
if (!this._content) {
|
|
131
|
+
this._content =
|
|
132
|
+
(<HTMLElement>this.highlightComp.nativeElement).textContent || '';
|
|
133
|
+
}
|
|
134
|
+
this._loadContent(this._content);
|
|
135
|
+
|
|
136
|
+
this._initialized = true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
setLanguage(lang: string): void {
|
|
140
|
+
if (!lang) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
'Error: language attribute must be defined in TdHighlightComponent.'
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
this._lang = lang;
|
|
146
|
+
if (this._initialized) {
|
|
147
|
+
this._loadContent(this._content);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
toggleRawClicked(): void {
|
|
152
|
+
this._showRaw = !this._showRaw;
|
|
153
|
+
this._elementRef.nativeElement.querySelector('pre').style.display = this
|
|
154
|
+
._showRaw
|
|
155
|
+
? 'none'
|
|
156
|
+
: 'block';
|
|
157
|
+
this._elementRef.nativeElement.querySelector('.raw').style.display = this
|
|
158
|
+
._showRaw
|
|
159
|
+
? 'block'
|
|
160
|
+
: 'none';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* General method to parse a string of code into HTML Elements and load them into the container
|
|
165
|
+
*/
|
|
166
|
+
private _loadContent(code: string | null): void {
|
|
167
|
+
if (code && code.trim().length > 0) {
|
|
168
|
+
// Clean container
|
|
169
|
+
this._renderer.setProperty(
|
|
170
|
+
this._elementRef.nativeElement,
|
|
171
|
+
'innerHTML',
|
|
172
|
+
''
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
this._elementFromString(code);
|
|
176
|
+
|
|
177
|
+
if (this.copyCodeToClipboard) {
|
|
178
|
+
this._renderer.appendChild(
|
|
179
|
+
this._elementRef.nativeElement,
|
|
180
|
+
this.copyComp.nativeElement
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
this.contentReady.emit();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private _elementFromString(codeStr: string): void {
|
|
188
|
+
// Renderer2 doesnt have a parsing method, so we have to sanitize and use [innerHTML]
|
|
189
|
+
// to parse the string into DOM element for now.
|
|
190
|
+
const preElement: HTMLPreElement = this._renderer.createElement('pre');
|
|
191
|
+
this._renderer.appendChild(this._elementRef.nativeElement, preElement);
|
|
192
|
+
const codeElement: HTMLElement = this._renderer.createElement('code');
|
|
193
|
+
this._renderer.appendChild(preElement, codeElement);
|
|
194
|
+
// Set .highlight class into <code> element
|
|
195
|
+
this._renderer.addClass(codeElement, 'highlight');
|
|
196
|
+
|
|
197
|
+
const highlightedCode = this._render(codeStr);
|
|
198
|
+
|
|
199
|
+
codeElement.innerHTML =
|
|
200
|
+
this._domSanitizer.sanitize(SecurityContext.HTML, highlightedCode) ?? '';
|
|
201
|
+
|
|
202
|
+
if (this.toggleRawButton) {
|
|
203
|
+
const divElement: HTMLDivElement = this._renderer.createElement('div');
|
|
204
|
+
divElement.className = 'raw';
|
|
205
|
+
this._renderer.appendChild(this._elementRef.nativeElement, divElement);
|
|
206
|
+
divElement.innerHTML =
|
|
207
|
+
this._domSanitizer.sanitize(SecurityContext.HTML, codeStr) ?? '';
|
|
208
|
+
this._renderer.setStyle(divElement, 'display', 'none');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private _render(contents: string): string {
|
|
213
|
+
// Trim leading and trailing newlines
|
|
214
|
+
contents = contents
|
|
215
|
+
.replace(/^(\s|\t)*\n+/g, '')
|
|
216
|
+
.replace(/(\s|\t)*\n+(\s|\t)*$/g, '');
|
|
217
|
+
// Split markup by line characters
|
|
218
|
+
let lines: string[] = contents.split('\n');
|
|
219
|
+
|
|
220
|
+
// check how much indentation is used by the first actual code line
|
|
221
|
+
const firstLineWhitespaceMatch = lines[0].match(/^(\s|\t)*/);
|
|
222
|
+
const firstLineWhitespace = firstLineWhitespaceMatch
|
|
223
|
+
? firstLineWhitespaceMatch[0]
|
|
224
|
+
: null;
|
|
225
|
+
|
|
226
|
+
// Remove all indentation spaces so code can be parsed correctly
|
|
227
|
+
const startingWhitespaceRegex = new RegExp('^' + firstLineWhitespace);
|
|
228
|
+
lines = lines.map(function (line: string): string {
|
|
229
|
+
return line
|
|
230
|
+
.replace('=""', '') // remove empty values
|
|
231
|
+
.replace(startingWhitespaceRegex, '')
|
|
232
|
+
.replace(/\s+$/, ''); // remove trailing white spaces
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const codeToParse: string = lines
|
|
236
|
+
.join('\n')
|
|
237
|
+
.replace(/\{ \{/gi, '{{')
|
|
238
|
+
.replace(/\} \}/gi, '}}')
|
|
239
|
+
.replace(/</gi, '<')
|
|
240
|
+
.replace(/>/gi, '>'); // replace with < and > to render HTML in Angular
|
|
241
|
+
this.copyContent = codeToParse;
|
|
242
|
+
// Parse code with highlight.js depending on language
|
|
243
|
+
const highlightedCode: HighlightResult = hljs.highlight(codeToParse, {
|
|
244
|
+
language: this._lang,
|
|
245
|
+
ignoreIllegals: true,
|
|
246
|
+
});
|
|
247
|
+
highlightedCode.value = highlightedCode.value
|
|
248
|
+
.replace(/=<span class="hljs-value">""<\/span>/gi, '')
|
|
249
|
+
.replace('<head>', '')
|
|
250
|
+
.replace('<head/>', '');
|
|
251
|
+
return highlightedCode.value;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { TdHighlightComponent } from './highlight.component';
|
|
3
|
+
import { TdCopyCodeButtonComponent } from './copy-code-button/copy-code-button.component';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated This module is deprecated and will be removed in future versions.
|
|
7
|
+
* Please migrate to using standalone components as soon as possible.
|
|
8
|
+
*/
|
|
9
|
+
@NgModule({
|
|
10
|
+
imports: [TdHighlightComponent, TdCopyCodeButtonComponent],
|
|
11
|
+
exports: [TdHighlightComponent],
|
|
12
|
+
})
|
|
13
|
+
export class CovalentHighlightModule {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'jest-preset-angular/setup-jest';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
|
|
2
|
+
const { join } = require('path');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
content: [
|
|
6
|
+
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
|
|
7
|
+
...createGlobPatternsForDependencies(__dirname),
|
|
8
|
+
],
|
|
9
|
+
theme: {
|
|
10
|
+
extend: {},
|
|
11
|
+
},
|
|
12
|
+
plugins: [],
|
|
13
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.base.json",
|
|
3
|
+
"files": [],
|
|
4
|
+
"include": [],
|
|
5
|
+
"references": [
|
|
6
|
+
{
|
|
7
|
+
"path": "./tsconfig.lib.json"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "./tsconfig.lib.prod.json"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "./tsconfig.spec.json"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"compilerOptions": {
|
|
17
|
+
"forceConsistentCasingInFileNames": true,
|
|
18
|
+
"strict": true,
|
|
19
|
+
"noImplicitOverride": true,
|
|
20
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
21
|
+
"noImplicitReturns": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true
|
|
23
|
+
},
|
|
24
|
+
"angularCompilerOptions": {
|
|
25
|
+
"strictInjectionParameters": true,
|
|
26
|
+
"strictInputAccessModifiers": true,
|
|
27
|
+
"strictTemplates": true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../dist/out-tsc",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": true,
|
|
7
|
+
"inlineSources": true,
|
|
8
|
+
"types": []
|
|
9
|
+
},
|
|
10
|
+
"exclude": ["src/test-setup.ts", "**/*.spec.ts", "**/*.test.ts"],
|
|
11
|
+
"include": ["**/*.ts"]
|
|
12
|
+
}
|