mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-17 10:41:06 +01:00
Merge with upstream
This commit is contained in:
@@ -5,7 +5,8 @@
|
||||
"scm-plugins/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "webpack --mode=development --config=scm-ui/scripts/webpack.config.js",
|
||||
"build": "webpack --mode=production --config=scm-ui/scripts/webpack.config.js",
|
||||
"build:dev": "webpack --mode=development --config=scm-ui/scripts/webpack.config.js",
|
||||
"test": "jest --config=scm-ui/scripts/jest.config.js",
|
||||
"serve": "webpack-dev-server --mode=development --config=scm-ui/scripts/webpack.config.js"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"version": "2.0.0-SNAPSHOT",
|
||||
"license": "BSD-3-Clause",
|
||||
"scripts": {
|
||||
"build": "webpack --mode=development"
|
||||
"build": "webpack --mode=production",
|
||||
"test": "jest --config=../../scm-ui/scripts/jest-plugin.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"flow-bin": "^0.109.0",
|
||||
@@ -11,7 +12,7 @@
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.js$": "../../scm-ui/scripts/babelMonoRepoTransformer.js"
|
||||
"^.+\\.js$": "../../scm-ui/scripts/babelPluginTransformer.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,6 @@
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<corePlugin>true</corePlugin>
|
||||
<links>
|
||||
<link>@scm-manager/ui-types</link>
|
||||
<link>@scm-manager/ui-components</link>
|
||||
</links>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import RepositoryConfig from "./RepositoryConfig";
|
||||
|
||||
// @visibleForTesting
|
||||
export const gitPredicate = (props: Object) => {
|
||||
return props.repository && props.repository.type === "git";
|
||||
return !!(props && props.repository && props.repository.type === "git");
|
||||
};
|
||||
|
||||
binder.bind(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import { gitPredicate } from "./index";
|
||||
|
||||
describe("test gi predicate", () => {
|
||||
describe("test git predicate", () => {
|
||||
it("should return false", () => {
|
||||
expect(gitPredicate()).toBe(false);
|
||||
expect(gitPredicate({})).toBe(false);
|
||||
@@ -10,6 +10,6 @@ describe("test gi predicate", () => {
|
||||
});
|
||||
|
||||
it("should return true", () => {
|
||||
expect(gitPredicate({ repository: { type: "fir" } })).toBe(true);
|
||||
expect(gitPredicate({ repository: { type: "git" } })).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "src/main/js/index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --mode=development"
|
||||
"build": "webpack --mode=production"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.js$": "../../scm-ui/scripts/babelPluginTransformer.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,6 @@
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<corePlugin>true</corePlugin>
|
||||
<links>
|
||||
<link>@scm-manager/ui-types</link>
|
||||
<link>@scm-manager/ui-components</link>
|
||||
</links>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "src/main/js/index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --mode=development"
|
||||
"build": "webpack --mode=production"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.js$": "../../scm-ui/scripts/babelPluginTransformer.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
"version": "2.0.0-SNAPSHOT",
|
||||
"license": "BSD-3-Clause",
|
||||
"scripts": {
|
||||
"build": "webpack --mode=development"
|
||||
"build": "webpack --mode=production"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.js$": "../../scm-ui/scripts/babelPluginTransformer.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,6 @@
|
||||
<artifactId>smp-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<corePlugin>true</corePlugin>
|
||||
<links>
|
||||
<link>@scm-manager/ui-types</link>
|
||||
<link>@scm-manager/ui-components</link>
|
||||
</links>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
@@ -75,17 +75,17 @@
|
||||
<script>build</script>
|
||||
</configuration>
|
||||
</execution>
|
||||
<!--execution>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>run</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<script>test-ci</script>
|
||||
<script>test</script>
|
||||
<skip>${skipTests}</skip>
|
||||
</configuration>
|
||||
</execution-->
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Read and use .babelrc from packages
|
||||
*/
|
||||
const { join, resolve } = require("path");
|
||||
const path = require("path");
|
||||
const { createTransformer } = require("babel-jest");
|
||||
|
||||
const packagePath = resolve(__dirname, "../");
|
||||
const packageGlob = join(packagePath, "*");
|
||||
const packagePath = path.resolve(__dirname, "../");
|
||||
const packageGlob = path.join(packagePath, "*");
|
||||
|
||||
module.exports = createTransformer({
|
||||
babelrcRoots: packageGlob
|
||||
|
||||
16
scm-ui/scripts/babelPluginTransformer.js
Normal file
16
scm-ui/scripts/babelPluginTransformer.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Read and use .babelrc from packages
|
||||
*/
|
||||
const path = require("path");
|
||||
const { createTransformer } = require("babel-jest");
|
||||
|
||||
const packagePath = path.resolve(__dirname, "..");
|
||||
const packageGlob = path.join(packagePath, "*");
|
||||
const currentDirectory = path.join(process.cwd());
|
||||
|
||||
module.exports = createTransformer({
|
||||
babelrcRoots: [
|
||||
packageGlob,
|
||||
currentDirectory
|
||||
]
|
||||
});
|
||||
30
scm-ui/scripts/jest-plugin.config.js
Normal file
30
scm-ui/scripts/jest-plugin.config.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const transformer = path.resolve(__dirname, "babelPluginTransformer.js");
|
||||
const rootDir = path.resolve(process.cwd());
|
||||
const packageJsonPath = path.join(rootDir, "package.json");
|
||||
const packageJson = JSON.parse(
|
||||
fs.readFileSync(packageJsonPath, { encoding: "UTF-8" })
|
||||
);
|
||||
|
||||
const reportDirectory = path.join(rootDir, "target", "jest-reports");
|
||||
|
||||
module.exports = {
|
||||
rootDir,
|
||||
transform: { "^.+\\.js$": transformer },
|
||||
transformIgnorePatterns: [".*/node_modules/.*"],
|
||||
collectCoverage: true,
|
||||
coverageDirectory: path.join(reportDirectory, "coverage"),
|
||||
coveragePathIgnorePatterns: ["src/main/js/tests/.*"],
|
||||
reporters: [
|
||||
"default",
|
||||
[
|
||||
"jest-junit",
|
||||
{
|
||||
suiteName: packageJson.name + " tests",
|
||||
outputDirectory: reportDirectory,
|
||||
outputName: "TEST-plugin.xml"
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
@@ -12,7 +12,11 @@ module.exports = {
|
||||
"default",
|
||||
[
|
||||
"jest-junit",
|
||||
{ outputDirectory: reportDirectory, outputName: "TEST-all.xml" }
|
||||
{
|
||||
suiteName: "SCM-UI Package tests",
|
||||
outputDirectory: reportDirectory,
|
||||
outputName: "TEST-scm-ui.xml"
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
@@ -29,11 +29,14 @@ module.exports = {
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [{
|
||||
use: [
|
||||
{
|
||||
loader: "cache-loader"
|
||||
},{
|
||||
},
|
||||
{
|
||||
loader: "thread-loader"
|
||||
}, {
|
||||
},
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
@@ -44,7 +47,8 @@ module.exports = {
|
||||
],
|
||||
plugins: ["@babel/plugin-proposal-class-properties"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(css|scss|sass)$/i,
|
||||
@@ -77,7 +81,13 @@ module.exports = {
|
||||
app.use(createContextPathMiddleware("/scm"));
|
||||
},
|
||||
after: function(app) {
|
||||
const templatePath = path.join(__dirname, "..", "ui-webapp", "public", "index.mustache");
|
||||
const templatePath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"ui-webapp",
|
||||
"public",
|
||||
"index.mustache"
|
||||
);
|
||||
const renderParams = {
|
||||
contextPath: "/scm"
|
||||
};
|
||||
@@ -88,12 +98,16 @@ module.exports = {
|
||||
optimization: {
|
||||
runtimeChunk: "single",
|
||||
splitChunks: {
|
||||
chunks: "all",
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "vendors",
|
||||
enforce: true,
|
||||
chunks: "all"
|
||||
priority: -10
|
||||
},
|
||||
default: {
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
scm-ui/ui-components/.storybook/addons.js
Normal file
1
scm-ui/ui-components/.storybook/addons.js
Normal file
@@ -0,0 +1 @@
|
||||
import 'storybook-addon-i18next/register';
|
||||
28
scm-ui/ui-components/.storybook/config.js
Normal file
28
scm-ui/ui-components/.storybook/config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import i18n from "i18next";
|
||||
import { reactI18nextModule } from "react-i18next";
|
||||
import { addDecorator, configure } from "@storybook/react";
|
||||
import { withI18next } from "storybook-addon-i18next";
|
||||
|
||||
import "!style-loader!css-loader!sass-loader!../../ui-webapp/src/style/scm.scss";
|
||||
|
||||
i18n.use(reactI18nextModule).init({
|
||||
whitelist: ["en", "de", "es"],
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
});
|
||||
|
||||
addDecorator(
|
||||
withI18next({
|
||||
i18n,
|
||||
languages: {
|
||||
en: "English",
|
||||
de: "Deutsch",
|
||||
es: "Spanisch"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
configure(require.context("../src", true, /\.stories\.js$/), module);
|
||||
@@ -11,34 +11,39 @@
|
||||
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",
|
||||
"license": "BSD-3-Clause",
|
||||
"scripts": {
|
||||
"update-index": "create-index -r src",
|
||||
"eslint-fix": "eslint src --fix"
|
||||
"eslint-fix": "eslint src --fix",
|
||||
"test": "jest",
|
||||
"storybook": "start-storybook"
|
||||
},
|
||||
"devDependencies": {
|
||||
"create-index": "^2.3.0",
|
||||
"@storybook/addon-actions": "^5.2.3",
|
||||
"@storybook/addon-storyshots": "^5.2.3",
|
||||
"@storybook/react": "^5.2.3",
|
||||
"enzyme": "^3.5.0",
|
||||
"enzyme-adapter-react-16": "^1.3.1",
|
||||
"fetch-mock": "^7.2.5",
|
||||
"fetch-mock": "^7.5.1",
|
||||
"flow-bin": "^0.109.0",
|
||||
"flow-typed": "^2.5.1",
|
||||
"raf": "^3.4.0",
|
||||
"react-router-enzyme-context": "^1.2.0"
|
||||
"react-router-enzyme-context": "^1.2.0",
|
||||
"storybook-addon-i18next": "^1.2.1",
|
||||
"storybook-react-router": "^1.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scm-manager/ui-extensions": "^0.1.2",
|
||||
"@scm-manager/ui-types": "2.0.0-SNAPSHOT",
|
||||
"classnames": "^2.2.6",
|
||||
"moment": "^2.22.2",
|
||||
"date-fns": "^2.4.1",
|
||||
"query-string": "5",
|
||||
"react": "^16.8.6",
|
||||
"react-diff-view": "^1.8.1",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-i18next": "^7.11.0",
|
||||
"react-i18next": "^7.9.0",
|
||||
"react-jss": "^8.6.1",
|
||||
"react-markdown": "^4.0.6",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-select": "^2.1.2",
|
||||
"react-syntax-highlighter": "^10.2.0"
|
||||
"react-syntax-highlighter": "^11.0.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"gitdiff-parser": "https://github.com/cloudogu/gitdiff-parser#3a72da4a8e3d9bfb4b9e01a43e85628c19f26cc4"
|
||||
@@ -46,6 +51,9 @@
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.js$": "../scripts/babelMonoRepoTransformer.js"
|
||||
},
|
||||
"moduleNameMapper": {
|
||||
"\\.(css|scss|sass)$": "<rootDir>/src/tests/styleMock.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
//@flow
|
||||
import React from "react";
|
||||
import moment from "moment";
|
||||
import { translate } from "react-i18next";
|
||||
import { formatDistance, format, parseISO } from "date-fns";
|
||||
import { enUS, de, es } from "date-fns/locale";
|
||||
import styled from "styled-components";
|
||||
|
||||
// fix german locale
|
||||
// https://momentjscom.readthedocs.io/en/latest/moment/00-use-it/07-browserify/
|
||||
import "moment/locale/de";
|
||||
const supportedLocales = {
|
||||
enUS,
|
||||
de,
|
||||
es
|
||||
};
|
||||
|
||||
type Props = {
|
||||
date?: string,
|
||||
@@ -15,25 +18,37 @@ type Props = {
|
||||
i18n: any
|
||||
};
|
||||
|
||||
const Date = styled.time`
|
||||
const DateElement = styled.time`
|
||||
border-bottom: 1px dotted rgba(219, 219, 219);
|
||||
cursor: help;
|
||||
`;
|
||||
|
||||
class DateFromNow extends React.Component<Props> {
|
||||
render() {
|
||||
const { i18n, date } = this.props;
|
||||
|
||||
if (date) {
|
||||
const dateWithLocale = moment(date).locale(i18n.language);
|
||||
|
||||
return (
|
||||
<Date title={dateWithLocale.format()}>
|
||||
{dateWithLocale.fromNow()}
|
||||
</Date>
|
||||
);
|
||||
getLocale = () => {
|
||||
const { i18n } = this.props;
|
||||
const locale = supportedLocales[i18n.language];
|
||||
if (!locale) {
|
||||
return enUS;
|
||||
}
|
||||
return locale;
|
||||
};
|
||||
|
||||
createOptions = () => {
|
||||
const locale = this.getLocale();
|
||||
return {
|
||||
locale
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
const { date } = this.props;
|
||||
if (date) {
|
||||
const isoDate = parseISO(date);
|
||||
const options = this.createOptions();
|
||||
const distance = formatDistance(isoDate, new Date(), options);
|
||||
const formatted = format(isoDate, "yyyy-MM-dd HH:mm:ss", options);
|
||||
return <DateElement title={formatted}>{distance}</DateElement>;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
14
scm-ui/ui-components/src/DateFromNow.stories.js
Normal file
14
scm-ui/ui-components/src/DateFromNow.stories.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import DateFromNow from "./DateFromNow";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
|
||||
storiesOf("DateFromNow", module).add("Default", () => (
|
||||
<div>
|
||||
<p>
|
||||
<DateFromNow date="2009-06-30T18:30:00+02:00" />
|
||||
</p>
|
||||
<p>
|
||||
<DateFromNow date="2019-06-30T18:30:00+02:00" />
|
||||
</p>
|
||||
</div>
|
||||
));
|
||||
@@ -3,8 +3,8 @@ import FileSize from "./FileSize";
|
||||
it("should format bytes", () => {
|
||||
expect(FileSize.format(0)).toBe("0 B");
|
||||
expect(FileSize.format(160)).toBe("160 B");
|
||||
expect(FileSize.format(6304)).toBe("6.16 K");
|
||||
expect(FileSize.format(28792588)).toBe("27.46 M");
|
||||
expect(FileSize.format(1369510189)).toBe("1.28 G");
|
||||
expect(FileSize.format(42949672960)).toBe("40.00 G");
|
||||
expect(FileSize.format(6304)).toBe("6.30 K");
|
||||
expect(FileSize.format(28792588)).toBe("28.79 M");
|
||||
expect(FileSize.format(1369510189)).toBe("1.37 G");
|
||||
expect(FileSize.format(42949672960)).toBe("42.95 G");
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import React from "react";
|
||||
import ReactSyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { arduinoLight } from "react-syntax-highlighter/dist/styles/hljs";
|
||||
import { LightAsync as ReactSyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { arduinoLight } from "react-syntax-highlighter/dist/cjs/styles/hljs";
|
||||
|
||||
type Props = {
|
||||
language: string,
|
||||
|
||||
23
scm-ui/ui-components/src/buttons/Button.stories.js
Normal file
23
scm-ui/ui-components/src/buttons/Button.stories.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import Button from "./Button";
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import StoryRouter from "storybook-react-router";
|
||||
import styled from "styled-components";
|
||||
|
||||
const colors = ["primary", "success", "info", "warning", "danger", "black"];
|
||||
|
||||
const Spacing = styled.div`
|
||||
padding: 1em;
|
||||
`;
|
||||
|
||||
storiesOf("Button", module)
|
||||
.addDecorator(StoryRouter())
|
||||
.add("Colors", () => (
|
||||
<div>
|
||||
{colors.map(color => (
|
||||
<Spacing>
|
||||
<Button color={color} label={color} />
|
||||
</Spacing>
|
||||
))}
|
||||
</div>
|
||||
));
|
||||
@@ -11,7 +11,8 @@ describe("test createBackendError", () => {
|
||||
context: [{
|
||||
type: "planet",
|
||||
id: "earth"
|
||||
}]
|
||||
}],
|
||||
violations: []
|
||||
};
|
||||
|
||||
it("should return a default backend error", () => {
|
||||
@@ -20,7 +21,8 @@ describe("test createBackendError", () => {
|
||||
expect(err.name).toBe("BackendError");
|
||||
});
|
||||
|
||||
it("should return an unauthorized error for status code 403", () => {
|
||||
// 403 is no backend error
|
||||
xit("should return an unauthorized error for status code 403", () => {
|
||||
const err = createBackendError(earthNotFoundError, 403);
|
||||
expect(err).toBeInstanceOf(UnauthorizedError);
|
||||
expect(err.name).toBe("UnauthorizedError");
|
||||
|
||||
1
scm-ui/ui-components/src/tests/styleMock.js
Normal file
1
scm-ui/ui-components/src/tests/styleMock.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -19,7 +19,6 @@
|
||||
"i18next-fetch-backend": "^0.1.0",
|
||||
"jss-nested": "^6.0.1",
|
||||
"memoize-one": "^5.0.4",
|
||||
"moment": "^2.22.2",
|
||||
"query-string": "5",
|
||||
"react": "^16.8.6",
|
||||
"react-diff-view": "^1.8.1",
|
||||
@@ -31,7 +30,6 @@
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-router-redux": "^5.0.0-alpha.9",
|
||||
"react-select": "^2.1.2",
|
||||
"react-syntax-highlighter": "^9.0.1",
|
||||
"redux": "^4.0.0",
|
||||
"redux-devtools-extension": "^2.13.5",
|
||||
"redux-logger": "^3.0.6",
|
||||
@@ -49,8 +47,7 @@
|
||||
"build-js": "ui-bundler bundle --mode=production target/scm-ui/scm-ui.bundle.js",
|
||||
"build-vendor": "ui-bundler vendor --mode=production target/scm-ui/vendor.bundle.js",
|
||||
"build": "npm-run-all -s webfonts build-css polyfills build-vendor build-js",
|
||||
"test": "ui-bundler test",
|
||||
"test-ci": "ui-bundler test --ci",
|
||||
"test": "jest",
|
||||
"flow": "flow",
|
||||
"pre-commit": "jest && flow && eslint src"
|
||||
},
|
||||
@@ -59,7 +56,7 @@
|
||||
"copyfiles": "^2.0.0",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"fetch-mock": "^6.5.0",
|
||||
"fetch-mock": "^7.5.1",
|
||||
"flow-bin": "^0.109.0",
|
||||
"flow-typed": "^2.6.1",
|
||||
"node-sass": "^4.9.3",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
window.ctxPath = "{{ contextPath }}";
|
||||
</script>
|
||||
<script src="{{ contextPath }}/assets/runtime.bundle.js"></script>
|
||||
<script src="{{ contextPath }}/assets/vendors.bundle.js"></script>
|
||||
<script src="{{ contextPath }}/assets/vendors~webapp.bundle.js"></script>
|
||||
<script src="{{ contextPath }}/assets/webapp.bundle.js"></script>
|
||||
|
||||
{{#liveReloadURL}}
|
||||
|
||||
Reference in New Issue
Block a user