Skip to content

Commit da92026

Browse files
committed
Pre-render content in model before display to speed things up. Also add loading template
1 parent e066a40 commit da92026

File tree

8 files changed

+114
-82
lines changed

8 files changed

+114
-82
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@proangular/ngx-gist",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "An Angular Material and HighlighJs styled display box for GitHub gist and local code snippets.",
55
"author": "Pro Angular <webmaster@proangular.com>",
66
"homepage": "https://www.proangular.com",

src/app/public/ngx-gist-content.pipe.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
11
import { NgxGist } from './ngx-gist.model';
22
import { Pipe, PipeTransform } from '@angular/core';
3+
import { isStringArray } from './ngx-gist.utilities';
4+
import { isNonEmptyString } from 'dist/npm';
35

46
@Pipe({ name: 'gistFileFilter' })
57
export class GistFileFilterPipe implements PipeTransform {
68
public transform(
7-
files: NgxGist['files'] | null,
8-
displayOnlyFileName?: string | null,
9-
): NgxGist['files'] {
9+
files: NgxGist['highlightedFiles'] | null,
10+
displayOnlyFileNames?: string | readonly string[] | null,
11+
): NgxGist['highlightedFiles'] {
1012
if (!files) {
11-
return {};
13+
return [];
1214
}
1315

14-
if (!displayOnlyFileName) {
16+
if (!displayOnlyFileNames || displayOnlyFileNames === '') {
1517
return files;
1618
}
1719

18-
return {
19-
[displayOnlyFileName]: files[displayOnlyFileName],
20-
};
20+
if (isNonEmptyString(displayOnlyFileNames)) {
21+
return (
22+
files.filter(({ filename }) => displayOnlyFileNames === filename) ?? []
23+
);
24+
}
25+
26+
if (isStringArray(displayOnlyFileNames)) {
27+
return files.filter(({ filename }) =>
28+
displayOnlyFileNames.includes(filename),
29+
);
30+
}
31+
32+
return files;
2133
}
2234
}

src/app/public/ngx-gist.component.ts

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { NgxGistService } from './ngx-gist.service';
22
import { isNonEmptyValue } from './ngx-gist.utilities';
33
import { NgxGist } from './ngx-gist.model';
44
import { Component, Inject, Input, OnInit } from '@angular/core';
5-
import { Language, default as hljs } from 'highlight.js';
6-
import { filter, firstValueFrom, ReplaySubject } from 'rxjs';
5+
import { Language } from 'highlight.js';
6+
import { BehaviorSubject, filter, firstValueFrom, ReplaySubject } from 'rxjs';
77
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
88
import { DOCUMENT } from '@angular/common';
99

@@ -12,18 +12,16 @@ import { DOCUMENT } from '@angular/common';
1212
selector: 'ngx-gist',
1313
template: `
1414
<mat-card class="code-container">
15-
<!-- TODO: LOADING ICON OR MESSAGE -->
16-
<mat-tab-group *ngIf="gist as g">
15+
<mat-tab-group *ngIf="gistChanges | async as gist; else loading">
1716
<mat-tab
1817
*ngFor="
19-
let file of g.files | gistFileFilter: displayOnlyFileName | keyvalue
18+
let file of gist.highlightedFiles
19+
| gistFileFilter: displayOnlyFileName
2020
"
21-
[label]="file.key"
21+
[label]="file.filename"
2222
>
2323
<pre>
24-
<code
25-
[innerHTML]="getHighlightJsContent(g | gistContent: file.key)"
26-
></code>
24+
<code [innerHTML]="file.highlightedContent"></code>
2725
</pre>
2826
</mat-tab>
2927
</mat-tab-group>
@@ -36,6 +34,7 @@ import { DOCUMENT } from '@angular/common';
3634
<mat-icon>link</mat-icon> Open Gist on GitHub
3735
</a>
3836
</mat-card-footer>
37+
<ng-template #loading> Loading Code Snippet... </ng-template>
3938
</mat-card>
4039
`,
4140
styleUrls: ['./ngx-gist.component.scss'],
@@ -66,12 +65,13 @@ export class NgxGistComponent implements OnInit {
6665
*
6766
* Default: `undefined`
6867
*/
69-
@Input() public gist?: NgxGist;
70-
// We want reactive behavior for `gistId` so we can update gists asynchronously
71-
private readonly gistIdSubject = new ReplaySubject<
72-
NgxGistComponent['gistId']
73-
>(1);
74-
public readonly gistIdChanges = this.gistIdSubject.asObservable();
68+
@Input() public set gist(value: NgxGist | undefined) {
69+
this.gistSubject.next(value);
70+
}
71+
private readonly gistSubject = new BehaviorSubject<NgxGistComponent['gist']>(
72+
undefined,
73+
);
74+
public readonly gistChanges = this.gistSubject.asObservable();
7575
/**
7676
* Provide the GitHub gist id to be fetched and loaded. This can be found in
7777
* URL of the gists you create. For example the id `TH1515th31DT0C0PY` in:
@@ -82,6 +82,11 @@ export class NgxGistComponent implements OnInit {
8282
@Input() public set gistId(value: string) {
8383
this.gistIdSubject.next(value);
8484
}
85+
// We want reactive behavior for `gistId` so we can update gists asynchronously
86+
private readonly gistIdSubject = new ReplaySubject<
87+
NgxGistComponent['gistId']
88+
>(1);
89+
public readonly gistIdChanges = this.gistIdSubject.asObservable();
8590
/**
8691
* When defined, override automatic language detection [and styling] and
8792
* treat all gists as this lanuage.
@@ -137,31 +142,17 @@ export class NgxGistComponent implements OnInit {
137142
});
138143
}
139144

140-
// TODO: Work on speeding this call up. Or possibly pre-render instead.
141-
public getHighlightJsContent(value: string): string {
142-
const userSpecifiedLanguage = this.languageName;
143-
if (userSpecifiedLanguage) {
144-
return hljs.highlight(value, { language: userSpecifiedLanguage }).value;
145-
}
146-
147-
return hljs.highlightAuto(value).value;
148-
}
149-
150145
private async fetchAndSetGist(gistId: string): Promise<void> {
151-
// Use the initial gist model as a fallback for a failed fetch. This
152-
// enables us to have a fallback gist snippet should we be offline or
153-
// the data is unavailable for some reason.
154-
const initialGist = this.gist ? { ...this.gist } : undefined;
155-
156146
// Fetch and hydrate model or fallback to initial gist.
157-
this.gist =
158-
(await firstValueFrom(this.ngxGistService.get(gistId))) ?? initialGist;
147+
const fetcheGist =
148+
(await firstValueFrom(this.ngxGistService.get(gistId))) ?? undefined;
149+
this.gist = fetcheGist;
159150

160-
if (this.useCache && this.gist) {
151+
if (this.useCache && fetcheGist) {
161152
// Set value in cache for reuse saving on the amount of HTTP requests.
162153
// Set refresh time to be a hard coded 24 hours. This was once configurable
163154
// but I decided against it for simplicities sake on ease of use.
164-
this.ngxGistService.setToCache(this.gist, 1440);
155+
this.ngxGistService.setToCache(fetcheGist, 1440);
165156
}
166157
}
167158

src/app/public/ngx-gist.model.ts

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import {
55
isNonEmptyString,
66
parsedJsonFromStringCodec,
77
} from './ngx-gist.utilities';
8+
import { default as hljs } from 'highlight.js';
89

910
export class NgxGist implements Gist {
10-
public constructor(args: Gist) {
11+
public constructor(args: Gist & Pick<NgxGist, 'languageOverride'>) {
1112
this.comments = args.comments;
1213
this.comments_url = args.comments_url;
1314
this.commits_url = args.commits_url;
@@ -28,8 +29,23 @@ export class NgxGist implements Gist {
2829
this.updated_at = new Date(args.updated_at);
2930
this.url = args.url;
3031
this.user = args.user;
32+
33+
// Additional properties
34+
this.languageOverride = args.languageOverride;
35+
const highlightedFiles: NgxGist['highlightedFiles'] = [];
36+
for (const key in this.files) {
37+
if (this.files[key]) {
38+
const file = this.files[key];
39+
highlightedFiles.push({
40+
...file,
41+
highlightedContent: getHighlightedContent(file.content),
42+
});
43+
}
44+
}
45+
this.highlightedFiles = highlightedFiles;
3146
}
3247

48+
/** Core gist properties */
3349
/* eslint-disable @typescript-eslint/naming-convention */
3450
public readonly comments: number;
3551
public readonly comments_url: string;
@@ -53,23 +69,36 @@ export class NgxGist implements Gist {
5369
public readonly user?: unknown;
5470
/* eslint-enable @typescript-eslint/naming-convention */
5571

72+
/** Additional properties */
73+
public readonly highlightedFiles: Array<
74+
io.TypeOf<typeof gistFilesContent> & HighlightedContent
75+
>;
76+
public readonly languageOverride?: string;
77+
5678
/**
5779
* Create a local, static gist object. Do not use this for fetched data.
5880
* Used for creating and displaying local code samples.
5981
*
82+
* Use `deserialize` sibling function for remote "unknown" responses.
83+
*
6084
* @param args Minimally necessary paramaters for displaying local code.
6185
* @returns A 'partial' model in which unnecessary fields are dummny data.
6286
*/
6387
public static create(
64-
args: Pick<Gist, 'files' | 'created_at' | 'description'>,
88+
args: Pick<Gist, 'files' | 'created_at' | 'description'> &
89+
Pick<NgxGist, 'languageOverride'>,
6590
): NgxGist {
6691
return new NgxGist({
67-
comments: 0,
68-
comments_url: '',
69-
commits_url: '',
92+
// Properties with settable values. These are the mimimum values needed
93+
// for displaying non "remote".
7094
created_at: new Date(args.created_at),
7195
description: args.description,
7296
files: args.files,
97+
languageOverride: args.languageOverride,
98+
// Empty properties that aren't needed for displaying non "remote" gists
99+
comments: 0,
100+
comments_url: '',
101+
commits_url: '',
73102
forks: [],
74103
forks_url: '',
75104
git_pull_url: '',
@@ -88,7 +117,7 @@ export class NgxGist implements Gist {
88117
}
89118

90119
/**
91-
* Deserialize and decode fetched/unkown data into the full model.
120+
* Deserialize and decode fetched/unknown data into the full model.
92121
*
93122
* @param value Unknown value, but expects a full model either by object or JSON string.
94123
* @returns Either the full model or null if deserialization fails.
@@ -102,15 +131,30 @@ export class NgxGist implements Gist {
102131
? decodeValueElseNull(gistFromJsonStringCodec)(value)
103132
: null);
104133
return decoded
105-
? {
134+
? new NgxGist({
106135
...decoded,
107136
created_at: new Date(decoded.created_at),
108137
updated_at: new Date(decoded.updated_at),
109-
}
138+
})
110139
: null;
111140
}
112141
}
113142

143+
interface HighlightedContent {
144+
highlightedContent: string;
145+
}
146+
147+
function getHighlightedContent(
148+
baseContent: string,
149+
languageOverride?: string,
150+
): string {
151+
if (languageOverride) {
152+
return hljs.highlight(baseContent, { language: languageOverride }).value;
153+
}
154+
155+
return hljs.highlightAuto(baseContent).value;
156+
}
157+
114158
const gitHubUserCodec = io.readonly(
115159
io.type({
116160
avatar_url: io.string,
@@ -155,21 +199,17 @@ const gistHistoryCodec = io.readonly(
155199
'GistHistory',
156200
);
157201

158-
const gistFilesCodec = io.readonly(
159-
io.record(
160-
io.string,
161-
io.type({
162-
content: io.string,
163-
filename: io.string,
164-
language: io.string,
165-
raw_url: io.string,
166-
size: io.number,
167-
truncated: io.boolean,
168-
type: io.string,
169-
}),
170-
),
171-
'GistFiles',
172-
);
202+
const gistFilesContent = io.type({
203+
content: io.string,
204+
filename: io.string,
205+
language: io.string,
206+
raw_url: io.string,
207+
size: io.number,
208+
truncated: io.boolean,
209+
type: io.string,
210+
});
211+
212+
const gistFilesCodec = io.record(io.string, gistFilesContent);
173213

174214
export const gistCodec = io.readonly(
175215
io.intersection([
@@ -207,7 +247,7 @@ const gistFromJsonStringCodec = parsedJsonFromStringCodec.pipe(
207247
);
208248

209249
/**
210-
* Official Gist objecio.
250+
* Official Gist object
211251
* https://docs.github.com/en/rest/gists
212252
*/
213253
type Gist = io.TypeOf<typeof gistCodec>;

src/app/public/ngx-gist.module.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import { NgxGistComponent } from './ngx-gist.component';
44
import { NgxGistService } from './ngx-gist.service';
55
import { MatCardModule } from '@angular/material/card';
66
import { MatTabsModule } from '@angular/material/tabs';
7-
import { GistContentPipe } from './ngx-gist-content.pipe';
87
import { GistFileFilterPipe } from './ngx-gist-file-filter.pipe';
98
import { MatIconModule } from '@angular/material/icon';
109

1110
@NgModule({
12-
declarations: [NgxGistComponent, GistContentPipe, GistFileFilterPipe],
11+
declarations: [NgxGistComponent, GistFileFilterPipe],
1312
imports: [
1413
// Needs to be imported at root.
1514
// BrowserAnimationsModule,

src/app/public/ngx-gist.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ export class NgxGistService {
6161

6262
const gist = storedGist.value;
6363
// All is good, return unexpired gist
64-
return {
64+
return new NgxGist({
6565
...gist,
6666
created_at: new Date(gist.created_at),
6767
updated_at: new Date(gist.updated_at),
68-
};
68+
});
6969
}
7070

7171
public setToCache(gist: NgxGist, expiresInMin?: number): void {

0 commit comments

Comments
 (0)