Merge pull request #2846 from alecmerdler/QUAY-682

Optimize Webpack JS Bundle Size
This commit is contained in:
Alec Merdler 2017-09-18 16:47:30 -04:00 committed by GitHub
commit ad61df66c5
28 changed files with 383 additions and 366 deletions

View file

@ -20,12 +20,11 @@ RUN virtualenv --distribute venv \
# Install front-end dependencies # Install front-end dependencies
# JS depedencies # JS depedencies
COPY yarn.lock ./ COPY yarn.lock package.json tsconfig.json webpack.config.js tslint.json ./
RUN yarn install --ignore-engines RUN yarn install --ignore-engines
# JS compile # JS compile
COPY static static COPY static static
COPY package.json tsconfig.json webpack.config.js tslint.json ./
RUN yarn build \ RUN yarn build \
&& jpegoptim static/img/**/*.jpg \ && jpegoptim static/img/**/*.jpg \
&& optipng -clobber -quiet static/img/**/*.png && optipng -clobber -quiet static/img/**/*.png

View file

@ -24,6 +24,9 @@ from _init import __version__
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
JS_BUNDLE_NAME = 'bundle'
def common_login(user_uuid, permanent_session=True): def common_login(user_uuid, permanent_session=True):
""" Performs login of the given user, with optional non-permanence on the session. """ """ Performs login of the given user, with optional non-permanence on the session. """
user = model.get_user(user_uuid) user = model.get_user(user_uuid)
@ -56,10 +59,10 @@ def common_login(user_uuid, permanent_session=True):
return False return False
def _list_files(path, extension): def _list_files(path, extension, contains=""):
""" Returns a list of all the files with the given extension found under the given path. """ """ Returns a list of all the files with the given extension found under the given path. """
def matches(f): def matches(f):
return os.path.splitext(f)[1] == '.' + extension and f.split(os.path.extsep)[1] != 'spec' return os.path.splitext(f)[1] == '.' + extension and contains in os.path.splitext(f)[0]
def join_path(dp, f): def join_path(dp, f):
# Remove the static/ prefix. It is added in the template. # Remove the static/ prefix. It is added in the template.
@ -71,10 +74,7 @@ def _list_files(path, extension):
def render_page_template(name, route_data=None, **kwargs): def render_page_template(name, route_data=None, **kwargs):
""" Renders the page template with the given name as the response and returns its contents. """ """ Renders the page template with the given name as the response and returns its contents. """
library_styles = [] main_scripts = _list_files('build', 'js', JS_BUNDLE_NAME)
main_styles = []
library_scripts = []
main_scripts = _list_files('build', 'js')
use_cdn = app.config.get('USE_CDN', True) use_cdn = app.config.get('USE_CDN', True)
if request.args.get('use_cdn') is not None: if request.args.get('use_cdn') is not None:
@ -118,10 +118,7 @@ def render_page_template(name, route_data=None, **kwargs):
route_data=route_data, route_data=route_data,
external_styles=external_styles, external_styles=external_styles,
external_scripts=external_scripts, external_scripts=external_scripts,
main_styles=main_styles,
library_styles=library_styles,
main_scripts=main_scripts, main_scripts=main_scripts,
library_scripts=library_scripts,
feature_set=features.get_features(), feature_set=features.get_features(),
config_set=frontend_visible_config(app.config), config_set=frontend_visible_config(app.config),
oauth_set=get_oauth_config(), oauth_set=get_oauth_config(),

View file

@ -4,13 +4,15 @@
"private": true, "private": true,
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"dev": "./node_modules/.bin/karma start --browsers ChromeHeadless", "dev": "karma start --browsers ChromeHeadless",
"test": "./node_modules/.bin/karma start --single-run --browsers ChromeHeadless", "test": "karma start --single-run --browsers ChromeHeadless",
"test:node": "JASMINE_CONFIG_PATH=static/test/jasmine.json ./node_modules/.bin/jasmine-ts './static/js/**/*.spec.ts'", "test:node": "JASMINE_CONFIG_PATH=static/test/jasmine.json jasmine-ts './static/js/**/*.spec.ts'",
"e2e": "./node_modules/.bin/ts-node ./node_modules/.bin/protractor static/test/protractor.conf.ts", "e2e": "ts-node ./node_modules/.bin/protractor static/test/protractor.conf.ts",
"build": "NODE_ENV=production ./node_modules/.bin/webpack --progress", "build": "npm run clean && NODE_ENV=production webpack --progress",
"watch": "./node_modules/.bin/webpack --watch", "watch": "npm run clean && webpack --watch",
"lint": "./node_modules/.bin/tslint --type-check -p tsconfig.json -e **/*.spec.ts" "lint": "tslint --type-check -p tsconfig.json -e **/*.spec.ts",
"analyze": "NODE_ENV=production webpack --profile --json | awk '{if(NR>1)print}' > static/build/stats.json && webpack-bundle-analyzer --mode static -r static/build/report.html static/build/stats.json",
"clean": "rm -f static/build/*"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -32,13 +34,13 @@
"d3": "^3.3.3", "d3": "^3.3.3",
"eonasdan-bootstrap-datetimepicker": "^4.17.43", "eonasdan-bootstrap-datetimepicker": "^4.17.43",
"file-saver": "^1.3.3", "file-saver": "^1.3.3",
"highlight.js": "^9.12.0",
"jquery": "1.12.4", "jquery": "1.12.4",
"ng-metadata": "^4.0.1", "ng-metadata": "^4.0.1",
"raven-js": "^3.1.0", "raven-js": "^3.1.0",
"restangular": "^1.2.0", "restangular": "^1.2.0",
"rxjs": "^5.0.1", "rxjs": "^5.0.1",
"showdown": "^1.6.4", "showdown": "^1.6.4",
"showdown-highlightjs-extension": "^0.1.2",
"underscore": "^1.5.2", "underscore": "^1.5.2",
"urijs": "^1.18.10", "urijs": "^1.18.10",
"zeroclipboard": "^2.3.0" "zeroclipboard": "^2.3.0"
@ -51,8 +53,6 @@
"@types/core-js": "^0.9.39", "@types/core-js": "^0.9.39",
"@types/jasmine": "^2.5.41", "@types/jasmine": "^2.5.41",
"@types/jquery": "^2.0.40", "@types/jquery": "^2.0.40",
"@types/react": "0.14.39",
"@types/react-dom": "0.14.17",
"@types/showdown": "^1.4.32", "@types/showdown": "^1.4.32",
"angular-mocks": "1.6.2", "angular-mocks": "1.6.2",
"css-loader": "0.25.0", "css-loader": "0.25.0",
@ -75,6 +75,7 @@
"ts-node": "^3.0.6", "ts-node": "^3.0.6",
"tslint": "^5.4.3", "tslint": "^5.4.3",
"typescript": "^2.2.1", "typescript": "^2.2.1",
"webpack": "^2.2" "webpack": "^2.2",
"webpack-bundle-analyzer": "^2.8.3"
} }
} }

View file

@ -920,16 +920,6 @@ a:focus {
margin-top: 6px; margin-top: 6px;
} }
.realtime-area-chart, .realtime-line-chart {
margin: 10px;
text-align: center;
}
.rickshaw_graph {
overflow: hidden;
padding-bottom: 40px;
}
.cor-container { .cor-container {
padding-left: 15px; padding-left: 15px;
padding-right: 15px; padding-right: 15px;

View file

@ -1,6 +0,0 @@
<div class="realtime-area-chart-element">
<div ng-show="counter >= 1">
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div>

View file

@ -1,6 +0,0 @@
<div class="realtime-line-chart-element">
<div ng-show="counter >= 1">
<div class="chart"></div>
</div>
<div class="cor-loader-inline" ng-if="counter < 1"></div>
</div>

View file

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

View file

@ -302,236 +302,6 @@ angular.module("core-ui", [])
return directiveDefinitionObject; 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() { .directive('corProgressBar', function() {
var directiveDefinitionObject = { var directiveDefinitionObject = {
priority: 4, priority: 4,

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,37 @@ describe("MarkdownEditorComponent", () => {
var component: MarkdownEditorComponent; var component: MarkdownEditorComponent;
var textarea: Mock<ng.IAugmentedJQuery | any>; var textarea: Mock<ng.IAugmentedJQuery | any>;
var documentMock: Mock<HTMLElement & Document>; var documentMock: Mock<HTMLElement & Document>;
var $windowMock: Mock<ng.IWindowService>;
beforeEach(() => { beforeEach(() => {
textarea = new Mock<ng.IAugmentedJQuery | any>(); textarea = new Mock<ng.IAugmentedJQuery | any>();
documentMock = new Mock<HTMLElement & Document>(); documentMock = new Mock<HTMLElement & Document>();
$windowMock = new Mock<ng.IWindowService>();
const $documentMock: any = [documentMock.Object]; const $documentMock: any = [documentMock.Object];
component = new MarkdownEditorComponent($documentMock, 'chrome'); component = new MarkdownEditorComponent($documentMock, $windowMock.Object, 'chrome');
component.textarea = textarea.Object; 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", () => { describe("changeEditMode", () => {
it("sets component's edit mode to given mode", () => { it("sets component's edit mode to given mode", () => {
@ -135,7 +157,15 @@ describe("MarkdownEditorComponent", () => {
describe("discardChanges", () => { 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: {}) => { component.discard.subscribe((event: {}) => {
expect(event).toEqual({}); expect(event).toEqual({});
done(); done();
@ -143,5 +173,16 @@ describe("MarkdownEditorComponent", () => {
component.discardChanges(); 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 } from 'ng-metadata/core'; import { Component, Inject, Input, Output, EventEmitter, ViewChild, HostListener, OnDestroy } from 'ng-metadata/core';
import { MarkdownSymbol } from '../../../types/common.types'; import { MarkdownSymbol } from '../../../types/common.types';
import { BrowserPlatform } from '../../../constants/platform.constant'; import { BrowserPlatform } from '../../../constants/platform.constant';
import './markdown-editor.component.css'; import './markdown-editor.component.css';
@ -11,9 +11,10 @@ import './markdown-editor.component.css';
selector: 'markdown-editor', selector: 'markdown-editor',
templateUrl: '/static/js/directives/ui/markdown/markdown-editor.component.html' templateUrl: '/static/js/directives/ui/markdown/markdown-editor.component.html'
}) })
export class MarkdownEditorComponent { export class MarkdownEditorComponent implements OnDestroy {
@Input('<') public content: string; @Input('<') public content: string;
@Output() public save: EventEmitter<{editedContent: string}> = new EventEmitter(); @Output() public save: EventEmitter<{editedContent: string}> = new EventEmitter();
@Output() public discard: EventEmitter<any> = new EventEmitter(); @Output() public discard: EventEmitter<any> = new EventEmitter();
@ -23,8 +24,18 @@ export class MarkdownEditorComponent {
private editMode: EditMode = "write"; private editMode: EditMode = "write";
constructor(@Inject('$document') private $document: ng.IDocumentService, constructor(@Inject('$document') private $document: ng.IDocumentService,
@Inject('$window') private $window: ng.IWindowService,
@Inject('BrowserPlatform') private browserPlatform: BrowserPlatform) { @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 { public changeEditMode(newMode: EditMode): void {
@ -103,8 +114,10 @@ export class MarkdownEditorComponent {
} }
public discardChanges(): void { public discardChanges(): void {
if (this.$window.confirm(`Are you sure you want to discard your changes?`)) {
this.discard.emit({}); this.discard.emit({});
} }
}
public get currentEditMode(): EditMode { public get currentEditMode(): EditMode {
return this.editMode; return this.editMode;

View file

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

View file

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

View file

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

View 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(/&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) => {
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 {
}

View file

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

View file

@ -4,6 +4,7 @@ import { NAME_PATTERNS } from "./constants/name-patterns.constant";
import * as Raven from "raven-js"; import * as Raven from "raven-js";
var quayDependencies: string[] = [ var quayDependencies: string[] = [
'chieffancypants.loadingBar', 'chieffancypants.loadingBar',
'cfp.hotkeys', 'cfp.hotkeys',

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

View file

@ -1,3 +1,6 @@
import * as Raven from 'raven-js';
/** /**
* Service which monitors the current user session and provides methods for returning information * Service which monitors the current user session and provides methods for returning information
* about the user. * about the user.

View file

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

3
static/js/types/custom.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
declare var System: {
import: (module: string) => Promise<any>;
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
declare var require: any; declare var require: NodeRequire;
// Require all modules ending in ".spec.ts" from the js directory and all subdirectories // Require all modules ending in ".spec.ts" from the js directory and all subdirectories
var testsContext = (<any>require).context("../js", true, /\.spec\.ts$/); var testsContext = (<any>require).context("../js", true, /\.spec\.ts$/);

View file

@ -22,14 +22,6 @@
<meta property="og:image" content="{{ preferred_scheme }}://{{ hostname }}/static/img/quay_preview.png" /> <meta property="og:image" content="{{ preferred_scheme }}://{{ hostname }}/static/img/quay_preview.png" />
<!-- /Icons --> <!-- /Icons -->
{% for style_path in main_styles %}
<link rel="stylesheet" href="/static/{{ style_path }}" type="text/css">
{% endfor %}
{% for style_path in library_styles %}
<link rel="stylesheet" href="/static/{{ style_path }}" type="text/css">
{% endfor %}
{% block added_stylesheets %} {% block added_stylesheets %}
{% endblock %} {% endblock %}
@ -57,10 +49,6 @@
<script src="{{ script_url }}"></script> <script src="{{ script_url }}"></script>
{% endfor %} {% endfor %}
{% for script_path in library_scripts %}
<script src="/static/{{ script_path }}"></script>
{% endfor %}
{% block added_dependencies %} {% block added_dependencies %}
{% endblock %} {% endblock %}

View file

@ -1,22 +1,24 @@
var webpack = require('webpack'); const webpack = require('webpack');
var path = require("path"); const path = require('path');
var config = {
let config = {
entry: "./static/js/main.ts", entry: "./static/js/main.ts",
output: { output: {
path: path.resolve(__dirname, "static/build"), path: path.resolve(__dirname, "static/build"),
filename: 'quay-frontend.js' publicPath: "/static/build/",
filename: '[name]-quay-frontend.bundle.js',
chunkFilename: '[name]-quay-frontend.chunk.js'
}, },
resolve: { resolve: {
extensions: [".ts", ".tsx", ".js", ".scss"], extensions: [".ts", ".js"],
alias: {
sass: path.resolve(__dirname, 'static/css/directives/components/pages/')
}
}, },
// Use global variables to maintain compatibility with non-Webpack components // Use global variables to maintain compatibility with non-Webpack components
externals: { externals: {
angular: "angular", angular: "angular",
jquery: "$", jquery: "$",
moment: "moment",
"raven-js": "Raven",
}, },
module: { module: {
rules: [ rules: [
@ -47,7 +49,10 @@ var config = {
FileSaver: 'file-saver', FileSaver: 'file-saver',
angular: "angular", angular: "angular",
$: "jquery", $: "jquery",
moment: "moment",
}), }),
// Restrict the extra locales that moment.js can load; en is always included
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
], ],
devtool: "cheap-module-source-map", devtool: "cheap-module-source-map",
}; };
@ -57,14 +62,15 @@ var config = {
* Production settings * Production settings
*/ */
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
config.plugins.push( config.plugins.concat([
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
sourceMap: true, sourceMap: true,
// Disable mangle to prevent AngularJS errors // Disable mangle to prevent AngularJS errors
mangle: false mangle: false
}) }),
); new webpack.optimize.CommonsChunkPlugin({name: 'common'}),
config.output.filename = 'quay-frontend-[hash].js'; ]);
config.output.filename = '[name]-quay-frontend-[hash].bundle.js';
} }
module.exports = config; module.exports = config;

194
yarn.lock
View file

@ -70,7 +70,7 @@ abbrev@1, abbrev@1.0.x:
version "1.0.9" version "1.0.9"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
accepts@1.3.3: accepts@1.3.3, accepts@~1.3.3:
version "1.3.3" version "1.3.3"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
dependencies: dependencies:
@ -87,6 +87,10 @@ acorn@^4.0.3, acorn@^4.0.4:
version "4.0.11" version "4.0.11"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
acorn@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
adm-zip@0.4.4: adm-zip@0.4.4:
version "0.4.4" version "0.4.4"
resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.4.tgz#a61ed5ae6905c3aea58b3a657d25033091052736" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.4.tgz#a61ed5ae6905c3aea58b3a657d25033091052736"
@ -207,6 +211,10 @@ array-find-index@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
array-slice@^0.2.3: array-slice@^0.2.3:
version "0.2.3" version "0.2.3"
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
@ -779,10 +787,18 @@ constants-browserify@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
content-type@~1.0.2: content-type@~1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
cookie@0.3.1: cookie@0.3.1:
version "0.3.1" version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
@ -1016,6 +1032,10 @@ des.js@^1.0.0:
inherits "^2.0.1" inherits "^2.0.1"
minimalistic-assert "^1.0.0" minimalistic-assert "^1.0.0"
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
di@^0.0.1: di@^0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
@ -1045,6 +1065,10 @@ domain-browser@^1.1.1:
version "1.1.7" version "1.1.7"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
duplexer@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
ecc-jsbn@~0.1.1: ecc-jsbn@~0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@ -1055,6 +1079,10 @@ ee-first@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
ejs@^2.5.6:
version "2.5.7"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
electron-to-chromium@^1.2.3: electron-to-chromium@^1.2.3:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.2.4.tgz#9751cbea89fa120bf88c226ba41eb8d0b6f1b597" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.2.4.tgz#9751cbea89fa120bf88c226ba41eb8d0b6f1b597"
@ -1210,6 +1238,10 @@ esutils@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
etag@~1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051"
eventemitter3@1.x.x: eventemitter3@1.x.x:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508"
@ -1255,6 +1287,39 @@ expand-range@^1.8.1:
dependencies: dependencies:
fill-range "^2.1.0" fill-range "^2.1.0"
express@^4.15.2:
version "4.15.3"
resolved "https://registry.yarnpkg.com/express/-/express-4.15.3.tgz#bab65d0f03aa80c358408972fc700f916944b662"
dependencies:
accepts "~1.3.3"
array-flatten "1.1.1"
content-disposition "0.5.2"
content-type "~1.0.2"
cookie "0.3.1"
cookie-signature "1.0.6"
debug "2.6.7"
depd "~1.1.0"
encodeurl "~1.0.1"
escape-html "~1.0.3"
etag "~1.8.0"
finalhandler "~1.0.3"
fresh "0.5.0"
merge-descriptors "1.0.1"
methods "~1.1.2"
on-finished "~2.3.0"
parseurl "~1.3.1"
path-to-regexp "0.1.7"
proxy-addr "~1.1.4"
qs "6.4.0"
range-parser "~1.2.0"
send "0.15.3"
serve-static "1.12.3"
setprototypeof "1.0.3"
statuses "~1.3.1"
type-is "~1.6.15"
utils-merge "1.0.0"
vary "~1.1.1"
extend@3, extend@^3.0.0, extend@~3.0.0: extend@3, extend@^3.0.0, extend@~3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4"
@ -1285,6 +1350,10 @@ filename-regex@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775"
filesize@^3.5.9:
version "3.5.10"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.10.tgz#fc8fa23ddb4ef9e5e0ab6e1e64f679a24a56761f"
fill-range@^2.1.0: fill-range@^2.1.0:
version "2.2.3" version "2.2.3"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723"
@ -1295,7 +1364,7 @@ fill-range@^2.1.0:
repeat-element "^1.1.2" repeat-element "^1.1.2"
repeat-string "^1.5.2" repeat-string "^1.5.2"
finalhandler@1.0.3: finalhandler@1.0.3, finalhandler@~1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89"
dependencies: dependencies:
@ -1340,6 +1409,14 @@ form-data@~2.1.1:
combined-stream "^1.0.5" combined-stream "^1.0.5"
mime-types "^2.1.12" mime-types "^2.1.12"
forwarded@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
fresh@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
fs-access@^1.0.0: fs-access@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a"
@ -1474,6 +1551,12 @@ graceful-fs@^4.1.2:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
gzip-size@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520"
dependencies:
duplexer "^0.1.1"
handlebars@^4.0.1: handlebars@^4.0.1:
version "4.0.6" version "4.0.6"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7"
@ -1542,9 +1625,9 @@ he@1.1.x:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
highlightjs@^9.8.0: highlight.js@^9.12.0:
version "9.10.0" version "9.12.0"
resolved "https://registry.yarnpkg.com/highlightjs/-/highlightjs-9.10.0.tgz#fca9b78ddaa3b1abca89d6c3ee105ad270a80190" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e"
hmac-drbg@^1.0.0: hmac-drbg@^1.0.0:
version "1.0.0" version "1.0.0"
@ -1678,6 +1761,10 @@ invert-kv@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
ipaddr.js@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0"
is-absolute-url@^2.0.0: is-absolute-url@^2.0.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
@ -2118,7 +2205,7 @@ lodash@^3.8.0:
version "3.10.1" version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
lodash@^4.0.0, lodash@^4.14.0, lodash@^4.5.0, lodash@~4.17.0: lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.5.0, lodash@~4.17.0:
version "4.17.4" version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@ -2194,6 +2281,14 @@ meow@^3.3.0:
redent "^1.0.0" redent "^1.0.0"
trim-newlines "^1.0.0" trim-newlines "^1.0.0"
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
micromatch@^2.1.5: micromatch@^2.1.5:
version "2.3.11" version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
@ -2229,7 +2324,7 @@ mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7:
dependencies: dependencies:
mime-db "~1.27.0" mime-db "~1.27.0"
mime@^1.3.4: mime@1.3.4, mime@^1.3.4:
version "1.3.4" version "1.3.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
@ -2456,6 +2551,10 @@ once@~1.3.3:
dependencies: dependencies:
wrappy "1" wrappy "1"
opener@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
optimist@^0.6.1, optimist@~0.6.0: optimist@^0.6.1, optimist@~0.6.0:
version "0.6.1" version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
@ -2571,6 +2670,10 @@ path-parse@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
path-type@^1.0.0: path-type@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@ -2881,6 +2984,13 @@ protractor@^5.1.2:
webdriver-js-extender "^1.0.0" webdriver-js-extender "^1.0.0"
webdriver-manager "^12.0.6" webdriver-manager "^12.0.6"
proxy-addr@~1.1.4:
version "1.1.5"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918"
dependencies:
forwarded "~0.1.0"
ipaddr.js "1.4.0"
prr@~0.0.0: prr@~0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
@ -2945,7 +3055,7 @@ randombytes@^2.0.0, randombytes@^2.0.1:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
range-parser@^1.0.3, range-parser@^1.2.0: range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
@ -3199,6 +3309,10 @@ safe-buffer@^5.0.1:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.0.tgz#fe4c8460397f9eaaaa58e73be46273408a45e223" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.0.tgz#fe4c8460397f9eaaaa58e73be46273408a45e223"
safe-buffer@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
saucelabs@~1.3.0: saucelabs@~1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/saucelabs/-/saucelabs-1.3.0.tgz#d240e8009df7fa87306ec4578a69ba3b5c424fee" resolved "https://registry.yarnpkg.com/saucelabs/-/saucelabs-1.3.0.tgz#d240e8009df7fa87306ec4578a69ba3b5c424fee"
@ -3254,6 +3368,33 @@ semver@~5.0.1:
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a"
send@0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/send/-/send-0.15.3.tgz#5013f9f99023df50d1bd9892c19e3defd1d53309"
dependencies:
debug "2.6.7"
depd "~1.1.0"
destroy "~1.0.4"
encodeurl "~1.0.1"
escape-html "~1.0.3"
etag "~1.8.0"
fresh "0.5.0"
http-errors "~1.6.1"
mime "1.3.4"
ms "2.0.0"
on-finished "~2.3.0"
range-parser "~1.2.0"
statuses "~1.3.1"
serve-static@1.12.3:
version "1.12.3"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.3.tgz#9f4ba19e2f3030c547f8af99107838ec38d5b1e2"
dependencies:
encodeurl "~1.0.1"
escape-html "~1.0.3"
parseurl "~1.3.1"
send "0.15.3"
set-blocking@^2.0.0, set-blocking@~2.0.0: set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@ -3276,12 +3417,6 @@ sha.js@^2.3.6:
dependencies: dependencies:
inherits "^2.0.1" inherits "^2.0.1"
showdown-highlightjs-extension@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/showdown-highlightjs-extension/-/showdown-highlightjs-extension-0.1.2.tgz#0fc90190283c1ae03fc4cccce3f1be6a5a58e4ce"
dependencies:
highlightjs "^9.8.0"
showdown@^1.6.4: showdown@^1.6.4:
version "1.6.4" version "1.6.4"
resolved "https://registry.yarnpkg.com/showdown/-/showdown-1.6.4.tgz#056bbb654ecdb8d8643ae12d6d597893ccaf46c6" resolved "https://registry.yarnpkg.com/showdown/-/showdown-1.6.4.tgz#056bbb654ecdb8d8643ae12d6d597893ccaf46c6"
@ -3740,6 +3875,10 @@ ultron@1.0.x:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
ultron@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864"
underscore@^1.5.2: underscore@^1.5.2:
version "1.8.3" version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
@ -3819,6 +3958,10 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0" spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0" spdx-expression-parse "~1.0.0"
vary@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37"
vendors@^1.0.0: vendors@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"
@ -3870,6 +4013,22 @@ webdriver-manager@^12.0.6:
semver "^5.3.0" semver "^5.3.0"
xml2js "^0.4.17" xml2js "^0.4.17"
webpack-bundle-analyzer@^2.8.3:
version "2.8.3"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.8.3.tgz#8e7b3deb3832698c24b09c84dfe5b43902a83991"
dependencies:
acorn "^5.1.1"
chalk "^1.1.3"
commander "^2.9.0"
ejs "^2.5.6"
express "^4.15.2"
filesize "^3.5.9"
gzip-size "^3.0.0"
lodash "^4.17.4"
mkdirp "^0.5.1"
opener "^1.4.3"
ws "^2.3.1"
webpack-dev-middleware@^1.0.11: webpack-dev-middleware@^1.0.11:
version "1.10.1" version "1.10.1"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.1.tgz#c6b4cf428139cf1aefbe06a0c00fdb4f8da2f893" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.1.tgz#c6b4cf428139cf1aefbe06a0c00fdb4f8da2f893"
@ -3965,6 +4124,13 @@ ws@1.1.2, ws@^1.0.1:
options ">=0.0.5" options ">=0.0.5"
ultron "1.0.x" ultron "1.0.x"
ws@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-2.3.1.tgz#6b94b3e447cb6a363f785eaf94af6359e8e81c80"
dependencies:
safe-buffer "~5.0.1"
ultron "~1.1.0"
wtf-8@1.0.0: wtf-8@1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a" resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a"