UNPKG

ngx-highlightjs

Version:

Instant code highlighting, auto-detect language, super easy to use.

437 lines (425 loc) 19.8 kB
import * as i0 from '@angular/core'; import { InjectionToken, makeEnvironmentProviders, inject, PLATFORM_ID, Injectable, signal, computed, ElementRef, afterRenderEffect, SecurityContext, Directive, input, booleanAttribute, output, NgModule } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { DOCUMENT, isPlatformBrowser } from '@angular/common'; import { BehaviorSubject, firstValueFrom, filter, switchMap, tap, EMPTY, catchError, throwError, forkJoin, map, from } from 'rxjs'; const HIGHLIGHT_OPTIONS = new InjectionToken('HIGHLIGHT_OPTIONS'); function provideHighlightOptions(options) { return makeEnvironmentProviders([ { provide: HIGHLIGHT_OPTIONS, useValue: options } ]); } var LoaderErrors; (function (LoaderErrors) { LoaderErrors["FULL_WITH_CORE_LIBRARY_IMPORTS"] = "The full library and the core library were imported, only one of them should be imported!"; LoaderErrors["FULL_WITH_LANGUAGE_IMPORTS"] = "The highlighting languages were imported they are not needed!"; LoaderErrors["CORE_WITHOUT_LANGUAGE_IMPORTS"] = "The highlighting languages were not imported!"; LoaderErrors["LANGUAGE_WITHOUT_CORE_IMPORTS"] = "The core library was not imported!"; LoaderErrors["NO_FULL_AND_NO_CORE_IMPORTS"] = "Highlight.js library was not imported!"; })(LoaderErrors || (LoaderErrors = {})); class HighlightLoader { constructor() { this.document = inject(DOCUMENT); this.isPlatformBrowser = isPlatformBrowser(inject(PLATFORM_ID)); this.options = inject(HIGHLIGHT_OPTIONS, { optional: true }); // Stream that emits when hljs library is loaded and ready to use this._ready = new BehaviorSubject(null); this.ready = firstValueFrom(this._ready.asObservable().pipe(filter((hljs) => !!hljs))); if (this.isPlatformBrowser) { // Check if hljs is already available if (this.document.defaultView['hljs']) { this._ready.next(this.document.defaultView['hljs']); } else { // Load hljs library this._loadLibrary().pipe(switchMap((hljs) => { if (this.options?.lineNumbersLoader) { // Make hljs available on window object (required for the line numbers library) this.document.defaultView['hljs'] = hljs; // Load line numbers library return this.loadLineNumbers().pipe(tap((plugin) => { plugin.activateLineNumbers(); this._ready.next(hljs); })); } else { this._ready.next(hljs); return EMPTY; } }), catchError((e) => { console.error('[HLJS] ', e); this._ready.error(e); return EMPTY; })).subscribe(); } // Load highlighting theme if (this.options?.themePath) { this.loadTheme(this.options.themePath); } } } /** * Lazy-Load highlight.js library */ _loadLibrary() { if (this.options) { if (this.options.fullLibraryLoader && this.options.coreLibraryLoader) { return throwError(() => LoaderErrors.FULL_WITH_CORE_LIBRARY_IMPORTS); } if (this.options.fullLibraryLoader && this.options.languages) { return throwError(() => LoaderErrors.FULL_WITH_LANGUAGE_IMPORTS); } if (this.options.coreLibraryLoader && !this.options.languages) { return throwError(() => LoaderErrors.CORE_WITHOUT_LANGUAGE_IMPORTS); } if (!this.options.coreLibraryLoader && this.options.languages) { return throwError(() => LoaderErrors.LANGUAGE_WITHOUT_CORE_IMPORTS); } if (this.options.fullLibraryLoader) { return this.loadFullLibrary(); } if (this.options.coreLibraryLoader && this.options.languages && Object.keys(this.options.languages).length) { return this.loadCoreLibrary().pipe(switchMap((hljs) => this._loadLanguages(hljs))); } } return throwError(() => LoaderErrors.NO_FULL_AND_NO_CORE_IMPORTS); } /** * Lazy-load highlight.js languages */ _loadLanguages(hljs) { const languages = Object.entries(this.options.languages).map(([langName, langLoader]) => importModule(langLoader()).pipe(tap((langFunc) => hljs.registerLanguage(langName, langFunc)))); return forkJoin(languages).pipe(map(() => hljs)); } /** * Import highlight.js core library */ loadCoreLibrary() { return importModule(this.options.coreLibraryLoader()); } /** * Import highlight.js library with all languages */ loadFullLibrary() { return importModule(this.options.fullLibraryLoader()); } /** * Import line numbers library */ loadLineNumbers() { return from(this.options.lineNumbersLoader()); } /** * Reload theme styles */ setTheme(path) { if (this.isPlatformBrowser) { if (this._themeLinkElement) { this._themeLinkElement.href = path; } else { this.loadTheme(path); } } } /** * Load theme */ loadTheme(path) { this._themeLinkElement = this.document.createElement('link'); this._themeLinkElement.href = path; this._themeLinkElement.type = 'text/css'; this._themeLinkElement.rel = 'stylesheet'; this._themeLinkElement.media = 'screen,print'; this.document.head.appendChild(this._themeLinkElement); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightLoader, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightLoader, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightLoader, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); /** * Map loader response to module object */ const importModule = (moduleLoader) => { return from(moduleLoader).pipe(filter((module) => !!module?.default), map((module) => module.default)); }; class HighlightJS { constructor() { this.loader = inject(HighlightLoader); this.options = inject(HIGHLIGHT_OPTIONS, { optional: true }); this.hljsSignal = signal(null); this.hljs = computed(() => this.hljsSignal()); // Load highlight.js library on init this.loader.ready.then((hljs) => { this.hljsSignal.set(hljs); if (this.options?.highlightOptions) { // Set global config if present hljs.configure(this.options.highlightOptions); } }); } /** * Core highlighting function. Accepts the code to highlight (string) and a list of options (object) */ async highlight(code, options) { const hljs = await this.loader.ready; return hljs.highlight(code, options); } /** * Highlighting with language detection. */ async highlightAuto(value, languageSubset) { const hljs = await this.loader.ready; return hljs.highlightAuto(value, languageSubset); } /** * Applies highlighting to a DOM node containing code. * This function is the one to use to apply highlighting dynamically after page load or within initialization code of third-party JavaScript frameworks. * The function uses language detection by default but you can specify the language in the class attribute of the DOM node. See the scopes reference for all available language names and scopes. */ async highlightElement(element) { const hljs = await this.loader.ready; hljs.highlightElement(element); } /** * Applies highlighting to all elements on a page matching the configured cssSelector. The default cssSelector value is 'pre code', * which highlights all code blocks. This can be called before or after the page’s onload event has fired. */ async highlightAll() { const hljs = await this.loader.ready; hljs.highlightAll(); } /** * @deprecated in version 12 * Configures global options: */ async configure(config) { const hljs = await this.loader.ready; hljs.configure(config); } /** * Adds new language to the library under the specified name. Used mostly internally. * The function is passed the hljs object to be able to use common regular expressions defined within it. */ async registerLanguage(languageName, languageDefinition) { const hljs = await this.loader.ready; hljs.registerLanguage(languageName, languageDefinition); } /** * Removes a language and its aliases from the library. Used mostly internally */ async unregisterLanguage(languageName) { const hljs = await this.loader.ready; hljs.unregisterLanguage(languageName); } /** * Adds new language alias or aliases to the library for the specified language name defined under languageName key. */ async registerAliases(alias, { languageName }) { const hljs = await this.loader.ready; hljs.registerAliases(alias, { languageName }); } /** * @return The languages names list. */ async listLanguages() { const hljs = await this.loader.ready; return hljs.listLanguages(); } /** * Looks up a language by name or alias. */ async getLanguage(name) { const hljs = await this.loader.ready; return hljs.getLanguage(name); } /** * Enables safe mode. This is the default mode, providing the most reliable experience for production usage. */ async safeMode() { const hljs = await this.loader.ready; hljs.safeMode(); } /** * Enables debug/development mode. */ async debugMode() { const hljs = await this.loader.ready; hljs.debugMode(); } /** * Display line numbers */ async lineNumbersBlock(el, options) { const hljs = await this.loader.ready; if (hljs.lineNumbersBlock) { hljs.lineNumbersBlock(el, options); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightJS, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightJS, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightJS, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); /** * Enable usage of the library together with "trusted-types" HTTP Content-Security-Policy (CSP) * * Can be added to angular.json -> serve -> options -> headers to try it out in DEV mode * "Content-Security-Policy": "trusted-types ngx-highlightjs; require-trusted-types-for 'script'" * * Read more... * Angular Security: https://angular.io/guide/security#enforcing-trusted-types * Trusted Types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/trusted-types */ let policy; function getPolicy() { if (!policy) { try { policy = window?.trustedTypes?.createPolicy('ngx-highlightjs', { createHTML: (s) => s, }); } catch { // trustedTypes.createPolicy throws if called with a name that is // already registered, even in report-only mode. Until the API changes, // catch the error not to break the applications functionally. In such // cases, the code will fall back to using strings. } } return policy; } function trustedHTMLFromStringBypass(html) { return getPolicy()?.createHTML(html) || html; } class HighlightBase { constructor() { this._hljs = inject(HighlightJS); this._nativeElement = inject((ElementRef)).nativeElement; this._sanitizer = inject(DomSanitizer); afterRenderEffect({ write: () => { const code = this.code(); // Set code text before highlighting this.setTextContent(code || ''); if (code) { this.highlightElement(code); } } }); afterRenderEffect({ write: () => { const res = this.highlightResult(); this.setInnerHTML(res?.value); // Forward highlight response to the highlighted output this.highlighted.emit(res); } }); } setTextContent(content) { requestAnimationFrame(() => this._nativeElement.textContent = content); } setInnerHTML(content) { requestAnimationFrame(() => this._nativeElement.innerHTML = trustedHTMLFromStringBypass(this._sanitizer.sanitize(SecurityContext.HTML, content) || '')); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.1", type: HighlightBase, isStandalone: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightBase, decorators: [{ type: Directive }], ctorParameters: () => [] }); class Highlight extends HighlightBase { constructor() { super(...arguments); // Code to highlight this.code = input(null, { alias: 'highlight' }); // Highlighted result this.highlightResult = signal(null); // The language name highlight only one language. this.language = input.required(); // An optional flag, when set to true it forces highlighting to finish even in case of detecting // illegal syntax for the language instead of throwing an exception. this.ignoreIllegals = input(undefined, { transform: booleanAttribute }); // Stream that emits when code string is highlighted this.highlighted = output(); } async highlightElement(code) { const res = await this._hljs.highlight(code, { language: this.language(), ignoreIllegals: this.ignoreIllegals() }); this.highlightResult.set(res); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: Highlight, deps: null, target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.1.1", type: Highlight, isStandalone: true, selector: "[highlight]", inputs: { code: { classPropertyName: "code", publicName: "highlight", isSignal: true, isRequired: false, transformFunction: null }, language: { classPropertyName: "language", publicName: "language", isSignal: true, isRequired: true, transformFunction: null }, ignoreIllegals: { classPropertyName: "ignoreIllegals", publicName: "ignoreIllegals", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { highlighted: "highlighted" }, host: { properties: { "class.hljs": "true" } }, providers: [{ provide: HighlightBase, useExisting: Highlight }], usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: Highlight, decorators: [{ type: Directive, args: [{ selector: '[highlight]', providers: [{ provide: HighlightBase, useExisting: Highlight }], host: { '[class.hljs]': 'true' } }] }] }); class HighlightAuto extends HighlightBase { constructor() { super(...arguments); // Code to highlight this.code = input(null, { alias: 'highlightAuto' }); // Highlighted result this.highlightResult = signal(null); // An optional array of language names and aliases restricting detection to only those languages. // The subset can also be set with configure, but the local parameter overrides the option if set. this.languages = input(); // Stream that emits when code string is highlighted this.highlighted = output(); } async highlightElement(code) { const res = await this._hljs.highlightAuto(code, this.languages()); this.highlightResult.set(res); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightAuto, deps: null, target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.1.1", type: HighlightAuto, isStandalone: true, selector: "[highlightAuto]", inputs: { code: { classPropertyName: "code", publicName: "highlightAuto", isSignal: true, isRequired: false, transformFunction: null }, languages: { classPropertyName: "languages", publicName: "languages", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { highlighted: "highlighted" }, host: { properties: { "class.hljs": "true" } }, providers: [{ provide: HighlightBase, useExisting: HighlightAuto }], usesInheritance: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightAuto, decorators: [{ type: Directive, args: [{ selector: '[highlightAuto]', providers: [{ provide: HighlightBase, useExisting: HighlightAuto }], host: { '[class.hljs]': 'true' } }] }] }); class HighlightModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: HighlightModule, imports: [Highlight, HighlightAuto], exports: [Highlight, HighlightAuto] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: HighlightModule, decorators: [{ type: NgModule, args: [{ imports: [Highlight, HighlightAuto], exports: [Highlight, HighlightAuto] }] }] }); /** * Generated bundle index. Do not edit. */ export { HIGHLIGHT_OPTIONS, Highlight, HighlightAuto, HighlightBase, HighlightJS, HighlightLoader, HighlightModule, provideHighlightOptions }; //# sourceMappingURL=ngx-highlightjs.mjs.map