refactored Markdown components to reduce bundle size

This commit is contained in:
Alec Merdler 2017-07-31 12:40:37 -04:00
parent 66e85ec63c
commit 8dc2a99926
16 changed files with 315 additions and 73 deletions

View file

@ -0,0 +1,8 @@
[
"javascript",
"python",
"bash",
"nginx",
"xml",
"shell"
]

View file

@ -1,17 +0,0 @@
import { Inject } from './inject.decorator';
describe("Decorator: Inject", () => {
describe("parameter injection", () => {
it("adds given string to the 'inject' property of the annotated class", () => {
expect(ValidService.$inject).toContain('$scope');
});
});
});
class ValidService {
constructor(@Inject('$scope') private $scope: any) {}
}

View file

@ -1,11 +0,0 @@
/**
* Adds the given value to the inject property of the annotated class.
* Used to annotate the constructor parameters of an AngularJS service/component class.
* @param value The string name of the dependency.
*/
export function Inject(value: string) {
return (target: any, propertyKey: string | symbol, parameterIndex: number): void => {
target.$inject = target.$inject = [];
target.$inject[parameterIndex] = value;
};
}

View file

@ -8,15 +8,27 @@ describe("MarkdownEditorComponent", () => {
var component: MarkdownEditorComponent;
var textarea: Mock<ng.IAugmentedJQuery | any>;
var documentMock: Mock<HTMLElement & Document>;
var $windowMock: Mock<ng.IWindowService>;
beforeEach(() => {
textarea = new Mock<ng.IAugmentedJQuery | any>();
documentMock = new Mock<HTMLElement & Document>();
$windowMock = new Mock<ng.IWindowService>();
const $documentMock: any = [documentMock.Object];
component = new MarkdownEditorComponent($documentMock, 'chrome');
component = new MarkdownEditorComponent($documentMock, $windowMock.Object, 'chrome');
component.textarea = textarea.Object;
});
describe("onBeforeUnload", () => {
it("returns false to alert user about losing current changes", () => {
component.changeEditMode("write");
const allow: boolean = component.onBeforeUnload();
expect(allow).toBe(false);
});
});
describe("changeEditMode", () => {
it("sets component's edit mode to given mode", () => {

View file

@ -1,4 +1,4 @@
import { Component, Inject, Input, Output, EventEmitter, ViewChild } from 'ng-metadata/core';
import { Component, Inject, Input, Output, EventEmitter, ViewChild, HostListener } from 'ng-metadata/core';
import { MarkdownSymbol } from '../../../types/common.types';
import { BrowserPlatform } from '../../../constants/platform.constant';
import './markdown-editor.component.css';
@ -14,6 +14,7 @@ import './markdown-editor.component.css';
export class MarkdownEditorComponent {
@Input('<') public content: string;
@Output() public save: EventEmitter<{editedContent: string}> = new EventEmitter();
@Output() public discard: EventEmitter<any> = new EventEmitter();
@ -23,8 +24,14 @@ export class MarkdownEditorComponent {
private editMode: EditMode = "write";
constructor(@Inject('$document') private $document: ng.IDocumentService,
@Inject('$window') private $window: ng.IWindowService,
@Inject('BrowserPlatform') private browserPlatform: BrowserPlatform) {
this.$window.onbeforeunload = this.onBeforeUnload.bind(this);
}
@HostListener('window:beforeunload', [])
public onBeforeUnload(): boolean {
return false;
}
public changeEditMode(newMode: EditMode): void {

View file

@ -14,7 +14,9 @@ export class MarkdownInputComponent {
@Input('<') public content: string;
@Input('<') public canWrite: boolean;
@Input('@') public fieldTitle: string;
@Output() public contentChanged: EventEmitter<{content: string}> = new EventEmitter();
private isEditing: boolean = false;
public editContent(): void {

View file

@ -15,9 +15,7 @@ describe("MarkdownViewComponent", () => {
markdownConverterMock = new Mock<Converter>();
$sceMock = new Mock<ng.ISCEService>();
$sanitizeMock = jasmine.createSpy('$sanitizeSpy').and.callFake((html: string) => html);
component = new MarkdownViewComponent((options: ConverterOptions) => markdownConverterMock.Object,
$sceMock.Object,
$sanitizeMock);
component = new MarkdownViewComponent(markdownConverterMock.Object, $sceMock.Object, $sanitizeMock);
});
describe("ngOnChanges", () => {

View file

@ -1,7 +1,5 @@
import { Component, Input, Inject, OnChanges, SimpleChanges } from 'ng-metadata/core';
import { Converter, ConverterOptions } from 'showdown';
import 'showdown-highlightjs-extension';
import 'highlightjs/styles/vs.css';
import './markdown-view.component.css';
@ -17,15 +15,15 @@ export class MarkdownViewComponent implements OnChanges {
@Input('<') public content: string;
@Input('<') public firstLineOnly: boolean = false;
@Input('<') public placeholderNeeded: boolean = false;
private convertedHTML: string = '';
private readonly placeholder: string = `<p style="visibility:hidden">placeholder</p>`;
private readonly markdownChars: string[] = ['#', '-', '>', '`'];
private markdownConverter: Converter;
constructor(@Inject('markdownConverterFactory') private makeConverter: (options?: ConverterOptions) => Converter,
constructor(@Inject('markdownConverter') private markdownConverter: Converter,
@Inject('$sce') private $sce: ng.ISCEService,
@Inject('$sanitize') private $sanitize: ng.sanitize.ISanitizeService) {
this.markdownConverter = makeConverter({extensions: ['highlightjs']});
}
public ngOnChanges(changes: SimpleChanges): void {

View file

@ -0,0 +1,72 @@
import { NgModule } from 'ng-metadata/core';
import { Converter, ConverterOptions } from 'showdown';
import * as showdown from 'showdown';
import { registerLanguage, highlightAuto } from 'highlight.js/lib/highlight';
import 'highlight.js/styles/vs.css';
const highlightedLanguages: string[] = require('../../../constants/highlighted-languages.constant.json');
/**
* Dynamically fetch and register a new language with Highlight.js
*/
export const addHighlightedLanguage = (language: string): Promise<{}> => {
return new Promise((resolve, reject) => {
try {
// TODO(alecmerdler): Use `import()` here instead of `require()`
const langModule = require(`highlight.js/lib/languages/${language}`);
registerLanguage(language, langModule);
resolve();
} catch (error) {
console.log(`Language ${language} not supported for syntax highlighting`);
reject(error);
}
});
};
/**
* Showdown JS extension for syntax highlighting using Highlight.js
*/
export const showdownHighlight = (): showdown.FilterExtension => {
const htmlunencode = (text: string) => {
return (text
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>'));
};
const left = '<pre><code\\b[^>]*>';
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
match = htmlunencode(match);
return leftSide + highlightAuto(match).value + rightSide;
};
return {
type: 'output',
filter: (text, converter, options) => {
return (<any>showdown).helper.replaceRecursiveRegExp(text, replacement, left, right, flags);
}
};
};
// Dynamically import syntax-highlighting supported languages
highlightedLanguages.forEach((langName) => addHighlightedLanguage(langName));
/**
* Markdown editor and view module.
*/
@NgModule({
imports: [],
declarations: [],
providers: [
{provide: 'markdownConverter', useValue: new Converter({extensions: [<any>showdownHighlight]})},
],
})
export class MarkdownModule {
}

View file

@ -4,6 +4,7 @@ import { QuayModule } from './quay.module';
import { provideRun } from './quay-run';
import * as angular from 'angular';
// Load all JS/CSS files into bundle: http://stackoverflow.com/a/30652110
declare var require: any;
function requireAll(r) {

View file

@ -37,7 +37,7 @@ import { ManageTriggerComponent } from './directives/ui/manage-trigger/manage-tr
import { ClipboardCopyDirective } from './directives/ui/clipboard-copy/clipboard-copy.directive';
import { CorTabsModule } from './directives/ui/cor-tabs/cor-tabs.module';
import { TriggerDescriptionComponent } from './directives/ui/trigger-description/trigger-description.component';
import { Converter, ConverterOptions } from 'showdown';
import { MarkdownModule } from './directives/ui/markdown/markdown.module';
import * as Clipboard from 'clipboard';
@ -49,6 +49,7 @@ import * as Clipboard from 'clipboard';
QuayRoutesModule,
QuayConfigModule,
CorTabsModule,
MarkdownModule,
],
declarations: [
RegexMatchViewComponent,
@ -86,7 +87,6 @@ import * as Clipboard from 'clipboard';
DockerfileServiceImpl,
DataFileServiceImpl,
{provide: 'fileReaderFactory', useValue: () => new FileReader()},
{provide: 'markdownConverterFactory', useValue: (options?: ConverterOptions) => new Converter(options)},
{provide: 'BrowserPlatform', useValue: browserPlatform},
{provide: 'clipboardFactory', useValue: (trigger, options) => new Clipboard(trigger, options)},
],

View file

@ -1,8 +1,8 @@
/**
* Service which exposes various utility methods.
*/
angular.module('quay').factory('UtilService', ['$sanitize', 'markdownConverterFactory',
function($sanitize, markdownConverterFactory) {
angular.module('quay').factory('UtilService', ['$sanitize', 'markdownConverter',
function($sanitize, markdownConverter) {
var utilService = {};
var adBlockEnabled = null;
@ -34,7 +34,7 @@ angular.module('quay').factory('UtilService', ['$sanitize', 'markdownConverterFa
};
utilService.getMarkedDown = function(string) {
return markdownConverterFactory().makeHtml(string || '');
return markdownConverter.makeHtml(string || '');
};
utilService.getFirstMarkdownLineAsText = function(commentString, placeholderNeeded) {