use Webpack code-splitting to dynamically import Highlight.js languages as they are detected by Showdown Markdown extension

This commit is contained in:
Alec Merdler 2017-08-01 13:28:24 -04:00
parent 8dc2a99926
commit 41c12c853c
9 changed files with 70 additions and 33 deletions

View file

@ -29,6 +29,16 @@ describe("MarkdownEditorComponent", () => {
});
});
describe("ngOnDestroy", () => {
it("removes 'beforeunload' event listener", () => {
$windowMock.setup(mock => mock.onbeforeunload).is(() => 1);
component.ngOnDestroy();
expect($windowMock.Object.onbeforeunload.call(this)).toEqual(null);
});
});
describe("changeEditMode", () => {
it("sets component's edit mode to given mode", () => {
@ -147,7 +157,15 @@ describe("MarkdownEditorComponent", () => {
describe("discardChanges", () => {
it("emits output event with no content", (done) => {
it("prompts user to confirm discarding changes", () => {
const confirmSpy: Spy = $windowMock.setup(mock => mock.confirm).is((message) => false).Spy;
component.discardChanges();
expect(confirmSpy.calls.argsFor(0)[0]).toEqual(`Are you sure you want to discard your changes?`);
});
it("emits output event with no content if user confirms discarding changes", (done) => {
$windowMock.setup(mock => mock.confirm).is((message) => true);
component.discard.subscribe((event: {}) => {
expect(event).toEqual({});
done();
@ -155,5 +173,16 @@ describe("MarkdownEditorComponent", () => {
component.discardChanges();
});
it("does not emit output event if user declines confirmation of discarding changes", (done) => {
$windowMock.setup(mock => mock.confirm).is((message) => false);
component.discard.subscribe((event: {}) => {
fail(`Should not emit output event`);
done();
});
component.discardChanges();
done();
});
});
});

View file

@ -1,4 +1,4 @@
import { Component, Inject, Input, Output, EventEmitter, ViewChild, HostListener } from 'ng-metadata/core';
import { Component, Inject, Input, Output, EventEmitter, ViewChild, HostListener, OnDestroy } from 'ng-metadata/core';
import { MarkdownSymbol } from '../../../types/common.types';
import { BrowserPlatform } from '../../../constants/platform.constant';
import './markdown-editor.component.css';
@ -11,7 +11,7 @@ import './markdown-editor.component.css';
selector: 'markdown-editor',
templateUrl: '/static/js/directives/ui/markdown/markdown-editor.component.html'
})
export class MarkdownEditorComponent {
export class MarkdownEditorComponent implements OnDestroy {
@Input('<') public content: string;
@ -34,6 +34,10 @@ export class MarkdownEditorComponent {
return false;
}
public ngOnDestroy(): void {
this.$window.onbeforeunload = () => null;
}
public changeEditMode(newMode: EditMode): void {
this.editMode = newMode;
}
@ -110,7 +114,9 @@ export class MarkdownEditorComponent {
}
public discardChanges(): void {
this.discard.emit({});
if (this.$window.confirm(`Are you sure you want to discard your changes?`)) {
this.discard.emit({});
}
}
public get currentEditMode(): EditMode {

View file

@ -1,5 +1,5 @@
import { NgModule } from 'ng-metadata/core';
import { Converter, ConverterOptions } from 'showdown';
import { Converter } from 'showdown';
import * as showdown from 'showdown';
import { registerLanguage, highlightAuto } from 'highlight.js/lib/highlight';
import 'highlight.js/styles/vs.css';
@ -10,14 +10,15 @@ const highlightedLanguages: string[] = require('../../../constants/highlighted-l
* Dynamically fetch and register a new language with Highlight.js
*/
export const addHighlightedLanguage = (language: string): Promise<{}> => {
return new Promise((resolve, reject) => {
return new Promise(async(resolve, reject) => {
try {
// TODO(alecmerdler): Use `import()` here instead of `require()`
const langModule = require(`highlight.js/lib/languages/${language}`);
// TODO(alecmerdler): Use `import()` here instead of `System.import()` after upgrading to TypeScript 2.4
const langModule = await System.import(`highlight.js/lib/languages/${language}`);
registerLanguage(language, langModule);
console.debug(`Language ${language} registered for syntax highlighting`);
resolve();
} catch (error) {
console.log(`Language ${language} not supported for syntax highlighting`);
console.debug(`Language ${language} not supported for syntax highlighting`);
reject(error);
}
});
@ -25,7 +26,7 @@ export const addHighlightedLanguage = (language: string): Promise<{}> => {
/**
* Showdown JS extension for syntax highlighting using Highlight.js
* Showdown JS extension for syntax highlighting using Highlight.js. Will attempt to register detected languages.
*/
export const showdownHighlight = (): showdown.FilterExtension => {
const htmlunencode = (text: string) => {
@ -39,7 +40,10 @@ export const showdownHighlight = (): showdown.FilterExtension => {
const right = '</code></pre>';
const flags = 'g';
const replacement = (wholeMatch: string, match: string, leftSide: string, rightSide: string) => {
// TODO(alecmerdler): Call `addHighlightedLanguage` to load new languages that are detected using code-splitting
const language: string = leftSide.slice(leftSide.indexOf('language-') + ('language-').length,
leftSide.indexOf('"', leftSide.indexOf('language-')));
addHighlightedLanguage(language).catch(error => null);
match = htmlunencode(match);
return leftSide + highlightAuto(match).value + rightSide;
};
@ -53,7 +57,7 @@ export const showdownHighlight = (): showdown.FilterExtension => {
};
// Dynamically import syntax-highlighting supported languages
// Import default syntax-highlighting supported languages
highlightedLanguages.forEach((langName) => addHighlightedLanguage(langName));