11import { NgxGistService } from './ngx-gist.service' ;
22import { isNonEmptyValue } from './ngx-gist.utilities' ;
33import { NgxGist } from './ngx-gist.model' ;
4- import { Component , Input , OnInit } from '@angular/core' ;
4+ import { Component , HostBinding , Inject , Input , OnInit } from '@angular/core' ;
55import { Language , default as hljs } from 'highlight.js' ;
6- import { BehaviorSubject , filter , firstValueFrom } from 'rxjs' ;
6+ import { filter , firstValueFrom , ReplaySubject } from 'rxjs' ;
77import { UntilDestroy , untilDestroyed } from '@ngneat/until-destroy' ;
8+ import { DOCUMENT } from '@angular/common' ;
89
910@UntilDestroy ( )
1011@Component ( {
1112 selector : 'ngx-gist' ,
1213 template : `
1314 <mat-card class="code-container">
15+ <!-- TODO: LOADING ICON OR MESSAGE -->
1416 <mat-tab-group *ngIf="gist as g">
1517 <mat-tab
1618 *ngFor="
@@ -27,9 +29,9 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
2729 </mat-tab-group>
2830 <mat-card-footer>
2931 <a
30- *ngIf="gistId && gist "
32+ *ngIf="gistIdChanges | async as gid "
3133 target="_blank"
32- [href]="'https://gist.github.com/' + gistId "
34+ [href]="'https://gist.github.com/' + gid "
3335 >
3436 <mat-icon>link</mat-icon> Open Gist on GitHub
3537 </a>
@@ -39,70 +41,140 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
3941 styleUrls : [ './ngx-gist.component.scss' ] ,
4042} )
4143export class NgxGistComponent implements OnInit {
42- public constructor ( public readonly ngxGistService : NgxGistService ) { }
44+ public constructor (
45+ @Inject ( DOCUMENT )
46+ private readonly document : Document ,
47+ private readonly ngxGistService : NgxGistService ,
48+ ) { }
4349
4450 public codeSnippet : string | null = null ;
51+ private htmlLinkElement : HTMLLinkElement | null = null ;
4552
4653 /**
47- * Cache the GitHub gist request in local memory. GitHub has a request
48- * limit so using this is wise in making sure users always get the content,
49- * especially on quick refreshes or having multiple gist snippets on page.
50- */
51- @Input ( ) public cacheForMin ?: number ;
52-
53- /**
54- * Display in the DOM only the selected filename.
54+ * Display in the DOM only the selected filename from the gists files array.
55+ *
56+ * TODO: Make this possible for string array input.
57+ *
58+ * Default: `undefined`
5559 */
5660 @Input ( ) public displayOnlyFileName ?: string ;
57-
5861 /**
5962 * Provide a static gist model here directly which will be displayed if
6063 * no `gistId` is provided for remote fetching. Also this model will be
6164 * displayed should a fetch fail when retrieving `gistId`, or overwritten
6265 * once the pertaining `gistId` data is fetched.
66+ *
67+ * Default: `undefined`
6368 */
6469 @Input ( ) public gist ?: NgxGist ;
65-
6670 // We want reactive behavior for `gistId` so we can update gists asynchronously
67- private readonly gistIdSubject = new BehaviorSubject <
71+ private readonly gistIdSubject = new ReplaySubject <
6872 NgxGistComponent [ 'gistId' ]
69- > ( null ) ;
70-
73+ > ( 1 ) ;
74+ public readonly gistIdChanges = this . gistIdSubject . asObservable ( ) ;
7175 /**
7276 * Provide the GitHub gist id to be fetched and loaded. This can be found in
7377 * URL of the gists you create. For example the id `TH1515th31DT0C0PY` in:
7478 * https://gist.github.com/FakeUserName/TH1515th31DT0C0PY
7579 *
7680 * Alternatively, provide a value directly in the sibling input `gist`.
7781 */
78- @Input ( ) public set gistId ( value : string | null ) {
82+ @Input ( ) public set gistId ( value : string ) {
7983 this . gistIdSubject . next ( value ) ;
8084 }
81-
85+ /**
86+ * When defined, override automatic language detection [and styling] and
87+ * treat all gists as this lanuage.
88+ *
89+ * See supported languages here:
90+ * https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md
91+ *
92+ * Default: `undefined`
93+ */
8294 @Input ( ) public languageName ?: Language [ 'name' ] ;
95+ /**
96+ * Define a material core theme to apply. Ideally, you should already have
97+ * your global material theme set at the root of your project so try to
98+ * avoid using this if possible. Note: These are also loaded from a CDN.
99+ *
100+ * See theming Angular Material: https://material.angular.io/guide/theming
101+ *
102+ * CDN used: `https://unpkg.com`
103+ *
104+ * Default: `undefined`
105+ */
106+ @Input ( ) @HostBinding ( 'class' ) public materialTheme :
107+ | 'deeppurple-amber'
108+ | 'indigo-pink'
109+ | 'pink-bluegrey'
110+ | 'purple-green'
111+ | undefined = undefined ;
112+ /**
113+ * Cache the GitHub gist request in local memory for 24 hours. GitHub has a
114+ * request limit, so this helps in reducing bandwidth. Loads previously
115+ * fetched gist content from the users machine.
116+ *
117+ * Default: `true`
118+ */
119+ @Input ( ) public useCache = true ;
83120
84121 public async ngOnInit ( ) : Promise < void > {
85- this . gistIdSubject
122+ this . setTheme ( ) ;
123+
124+ this . gistIdChanges
86125 . pipe ( filter ( isNonEmptyValue ) , untilDestroyed ( this ) )
87126 . subscribe ( async ( gistId ) => {
88- // Use the initial gist model as a fallback for a failed fetch. This
89- // enables us to have a fallback gist snippet should we be offline or
90- // the data is unavailable for some reason.
91- const initialGist = this . gist ? { ...this . gist } : undefined ;
92- // Fetch and hydrate model or fallback to initial gist.
93- this . gist =
94- ( await firstValueFrom ( this . ngxGistService . get ( gistId ) ) ) ??
95- initialGist ;
127+ if ( this . useCache ) {
128+ const cachedValue = this . ngxGistService . getFromCache ( gistId ) ;
129+ if ( cachedValue ) {
130+ // Value is cached and not previously expired, use it.
131+ this . gist = cachedValue ;
132+ return ;
133+ }
134+ }
135+
136+ await this . fetchAndSetGist ( gistId ) ;
96137 } ) ;
97138 }
98139
140+ // TODO: Work on speeding this call up. Or possibly pre-render instead.
99141 public getHighlightJsContent ( value : string ) : string {
100142 const userSpecifiedLanguage = this . languageName ;
101-
102143 if ( userSpecifiedLanguage ) {
103144 return hljs . highlight ( value , { language : userSpecifiedLanguage } ) . value ;
104145 }
105146
106147 return hljs . highlightAuto ( value ) . value ;
107148 }
149+
150+ 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+
156+ // Fetch and hydrate model or fallback to initial gist.
157+ this . gist =
158+ ( await firstValueFrom ( this . ngxGistService . get ( gistId ) ) ) ?? initialGist ;
159+
160+ if ( this . useCache && this . gist ) {
161+ // Set value in cache for reuse saving on the amount of HTTP requests.
162+ // Set refresh time to be a hard coded 24 hours. This was once configurable
163+ // but I decided against it for simplicities sake on ease of use.
164+ this . ngxGistService . setToCache ( this . gist , 1440 ) ;
165+ }
166+ }
167+
168+ private setTheme ( ) : void {
169+ if ( ! this . materialTheme ) {
170+ return ;
171+ }
172+
173+ this . htmlLinkElement = this . document . createElement ( 'link' ) ;
174+ this . htmlLinkElement . href = `https://unpkg.com/@angular/material@14.1.0/prebuilt-themes/${ this . materialTheme } .css` ;
175+ this . htmlLinkElement . media = 'screen,print' ;
176+ this . htmlLinkElement . rel = 'stylesheet' ;
177+ this . htmlLinkElement . type = 'text/css' ;
178+ this . document . head . appendChild ( this . htmlLinkElement ) ;
179+ }
108180}
0 commit comments