@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.
@@ -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
+ };
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/libs/angular-highlight",
4
+ "assets": ["./**/*theme.scss"]
5
+ }
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(/&lt;/gi, '<')
240
+ .replace(/&gt;/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,3 @@
1
+ export * from './lib/highlight.component';
2
+ export * from './lib/highlight.module';
3
+ export * from './lib/copy-code-button/copy-code-button.component';
@@ -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
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.lib.json",
3
+ "compilerOptions": {
4
+ "declarationMap": false
5
+ },
6
+ "angularCompilerOptions": {
7
+ "compilationMode": "full"
8
+ }
9
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "types": ["jest", "node"]
7
+ },
8
+ "files": ["src/test-setup.ts"],
9
+ "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"]
10
+ }