Merge pull request #2846 from alecmerdler/QUAY-682
Optimize Webpack JS Bundle Size
This commit is contained in:
commit
ad61df66c5
28 changed files with 383 additions and 366 deletions
8
static/js/constants/highlighted-languages.constant.json
Normal file
8
static/js/constants/highlighted-languages.constant.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
"javascript",
|
||||
"python",
|
||||
"bash",
|
||||
"nginx",
|
||||
"xml",
|
||||
"shell"
|
||||
]
|
|
@ -302,236 +302,6 @@ angular.module("core-ui", [])
|
|||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('realtimeAreaChart', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/realtime-area-chart.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'data': '=data',
|
||||
'labels': '=labels',
|
||||
'colors': '=colors',
|
||||
'counter': '=counter'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
var graph = null;
|
||||
var series = [];
|
||||
var palette = new Rickshaw.Color.Palette( { scheme: 'spectrum14' } );
|
||||
var colors = $scope.colors || [];
|
||||
|
||||
var setupGraph = function() {
|
||||
for (var i = 0; i < $scope.labels.length; ++i) {
|
||||
series.push({
|
||||
name: $scope.labels[i],
|
||||
color: i >= colors.length ? palette.color(): $scope.colors[i],
|
||||
stroke: 'rgba(0,0,0,0.15)',
|
||||
data: []
|
||||
});
|
||||
}
|
||||
|
||||
var options = {
|
||||
element: $element.find('.chart')[0],
|
||||
renderer: 'area',
|
||||
stroke: true,
|
||||
series: series,
|
||||
min: 0,
|
||||
padding: {
|
||||
'top': 0.3,
|
||||
'left': 0,
|
||||
'right': 0,
|
||||
'bottom': 0.3
|
||||
}
|
||||
};
|
||||
|
||||
if ($scope.minimum != null) {
|
||||
options['min'] = $scope.minimum == 'auto' ? 'auto' : $scope.minimum * 1;
|
||||
} else {
|
||||
options['min'] = 0;
|
||||
}
|
||||
|
||||
if ($scope.maximum != null) {
|
||||
options['max'] = $scope.maximum == 'auto' ? 'auto' : $scope.maximum * 1;
|
||||
}
|
||||
|
||||
graph = new Rickshaw.Graph(options);
|
||||
|
||||
xaxes = new Rickshaw.Graph.Axis.Time({
|
||||
graph: graph,
|
||||
timeFixture: new Rickshaw.Fixtures.Time.Local()
|
||||
});
|
||||
|
||||
yaxes = new Rickshaw.Graph.Axis.Y({
|
||||
graph: graph,
|
||||
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
|
||||
});
|
||||
|
||||
hoverDetail = new Rickshaw.Graph.HoverDetail({
|
||||
graph: graph,
|
||||
xFormatter: function(x) {
|
||||
return new Date(x * 1000).toString();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var refresh = function(data) {
|
||||
if (!data || $scope.counter < 0) { return; }
|
||||
if (!graph) {
|
||||
setupGraph();
|
||||
}
|
||||
|
||||
var timecode = new Date().getTime() / 1000;
|
||||
for (var i = 0; i < $scope.data.length; ++i) {
|
||||
var arr = series[i].data;
|
||||
arr.push(
|
||||
{'x': timecode, 'y': $scope.data[i] }
|
||||
);
|
||||
|
||||
if (arr.length > 10) {
|
||||
series[i].data = arr.slice(arr.length - 10, arr.length);
|
||||
}
|
||||
}
|
||||
|
||||
graph.renderer.unstack = true;
|
||||
graph.update();
|
||||
};
|
||||
|
||||
$scope.$watch('counter', function() {
|
||||
refresh($scope.data_raw);
|
||||
});
|
||||
|
||||
$scope.$watch('data', function(data) {
|
||||
$scope.data_raw = data;
|
||||
refresh($scope.data_raw);
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
|
||||
.directive('realtimeLineChart', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/realtime-line-chart.html',
|
||||
replace: false,
|
||||
transclude: false,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'data': '=data',
|
||||
'labels': '=labels',
|
||||
'counter': '=counter',
|
||||
'labelTemplate': '@labelTemplate',
|
||||
'minimum': '@minimum',
|
||||
'maximum': '@maximum'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
var graph = null;
|
||||
var xaxes = null;
|
||||
var yaxes = null;
|
||||
var hoverDetail = null;
|
||||
var series = [];
|
||||
var counter = 0;
|
||||
var palette = new Rickshaw.Color.Palette( { scheme: 'spectrum14' } );
|
||||
|
||||
var setupGraph = function() {
|
||||
var options = {
|
||||
element: $element.find('.chart')[0],
|
||||
renderer: 'line',
|
||||
series: series,
|
||||
padding: {
|
||||
'top': 0.3,
|
||||
'left': 0,
|
||||
'right': 0,
|
||||
'bottom': 0.3
|
||||
}
|
||||
};
|
||||
|
||||
if ($scope.minimum != null) {
|
||||
options['min'] = $scope.minimum == 'auto' ? 'auto' : $scope.minimum * 1;
|
||||
} else {
|
||||
options['min'] = 0;
|
||||
}
|
||||
|
||||
if ($scope.maximum != null) {
|
||||
options['max'] = $scope.maximum == 'auto' ? 'auto' : $scope.maximum * 1;
|
||||
}
|
||||
|
||||
graph = new Rickshaw.Graph(options);
|
||||
xaxes = new Rickshaw.Graph.Axis.Time({
|
||||
graph: graph,
|
||||
timeFixture: new Rickshaw.Fixtures.Time.Local()
|
||||
});
|
||||
|
||||
yaxes = new Rickshaw.Graph.Axis.Y({
|
||||
graph: graph,
|
||||
tickFormat: Rickshaw.Fixtures.Number.formatKMBT
|
||||
});
|
||||
|
||||
hoverDetail = new Rickshaw.Graph.HoverDetail({
|
||||
graph: graph,
|
||||
xFormatter: function(x) {
|
||||
return new Date(x * 1000).toString();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var refresh = function(data) {
|
||||
if (data == null) { return; }
|
||||
if (!graph) {
|
||||
setupGraph();
|
||||
}
|
||||
|
||||
if (typeof data == 'number') {
|
||||
data = [data];
|
||||
}
|
||||
|
||||
if ($scope.labels) {
|
||||
data = data.slice(0, $scope.labels.length);
|
||||
}
|
||||
|
||||
if (series.length == 0){
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
var title = $scope.labels ? $scope.labels[i] : $scope.labelTemplate.replace('{x}', i + 1);
|
||||
series.push({
|
||||
'color': palette.color(),
|
||||
'data': [],
|
||||
'name': title
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
counter++;
|
||||
var timecode = new Date().getTime() / 1000;
|
||||
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
var arr = series[i].data;
|
||||
arr.push({
|
||||
'x': timecode,
|
||||
'y': data[i]
|
||||
})
|
||||
|
||||
if (arr.length > 10) {
|
||||
series[i].data = arr.slice(arr.length - 10, arr.length);
|
||||
}
|
||||
}
|
||||
|
||||
graph.update();
|
||||
};
|
||||
|
||||
$scope.$watch('counter', function(counter) {
|
||||
refresh($scope.data_raw);
|
||||
});
|
||||
|
||||
$scope.$watch('data', function(data) {
|
||||
$scope.data_raw = data;
|
||||
});
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
})
|
||||
|
||||
.directive('corProgressBar', function() {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 4,
|
||||
|
|
|
@ -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) {}
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -8,15 +8,37 @@ 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("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", () => {
|
||||
|
@ -135,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();
|
||||
|
@ -143,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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, Inject, Input, Output, EventEmitter, ViewChild } 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,9 +11,10 @@ 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;
|
||||
|
||||
@Output() public save: EventEmitter<{editedContent: string}> = new EventEmitter();
|
||||
@Output() public discard: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
|
@ -23,8 +24,18 @@ 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 ngOnDestroy(): void {
|
||||
this.$window.onbeforeunload = () => null;
|
||||
}
|
||||
|
||||
public changeEditMode(newMode: EditMode): void {
|
||||
|
@ -103,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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
76
static/js/directives/ui/markdown/markdown.module.ts
Normal file
76
static/js/directives/ui/markdown/markdown.module.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { NgModule } from 'ng-metadata/core';
|
||||
import { Converter } 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(async(resolve, reject) => {
|
||||
try {
|
||||
// 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.debug(`Language ${language} not supported for syntax highlighting`);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Showdown JS extension for syntax highlighting using Highlight.js. Will attempt to register detected languages.
|
||||
*/
|
||||
export const showdownHighlight = (): showdown.FilterExtension => {
|
||||
const htmlunencode = (text: string) => {
|
||||
return (text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>'));
|
||||
};
|
||||
|
||||
const left = '<pre><code\\b[^>]*>';
|
||||
const right = '</code></pre>';
|
||||
const flags = 'g';
|
||||
const replacement = (wholeMatch: string, match: string, leftSide: string, rightSide: string) => {
|
||||
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;
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'output',
|
||||
filter: (text, converter, options) => {
|
||||
return (<any>showdown).helper.replaceRecursiveRegExp(text, replacement, left, right, flags);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// Import default 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 {
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { NAME_PATTERNS } from "./constants/name-patterns.constant";
|
|||
import * as Raven from "raven-js";
|
||||
|
||||
|
||||
|
||||
var quayDependencies: string[] = [
|
||||
'chieffancypants.loadingBar',
|
||||
'cfp.hotkeys',
|
||||
|
|
|
@ -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)},
|
||||
],
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import * as Raven from 'raven-js';
|
||||
|
||||
|
||||
/**
|
||||
* Service which monitors the current user session and provides methods for returning information
|
||||
* about the user.
|
||||
|
|
|
@ -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) {
|
||||
|
|
3
static/js/types/custom.d.ts
vendored
Normal file
3
static/js/types/custom.d.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
declare var System: {
|
||||
import: (module: string) => Promise<any>;
|
||||
};
|
Reference in a new issue