watching with webpack and starting python server after compilation
parent
8411c53f13
commit
7f41ff4ed2
|
|
@ -0,0 +1,271 @@
|
|||
'use strict';
|
||||
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
|
||||
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
|
||||
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const getClientEnvironment = require('./env');
|
||||
const paths = require('./paths');
|
||||
// Webpack uses `publicPath` to determine where the app is being served from.
|
||||
// It requires a trailing slash, or the file assets will get an incorrect path.
|
||||
const publicPath = paths.servedPath;
|
||||
// Some apps do not use client-side routing with pushState.
|
||||
// For these, "homepage" can be set to "." to enable relative asset paths.
|
||||
const shouldUseRelativeAssetPaths = publicPath === './';
|
||||
// `publicUrl` is just like `publicPath`, but we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
|
||||
const publicUrl = publicPath.slice(0, -1);
|
||||
// Get environment variables to inject into our app.
|
||||
const env = getClientEnvironment(publicUrl);
|
||||
|
||||
// Assert this just to be safe.
|
||||
// Development builds of React are slow and not intended for production.
|
||||
if (env.stringified['process.env'].NODE_ENV !== '"production"') {
|
||||
throw new Error('Production builds must have NODE_ENV=production.');
|
||||
}
|
||||
|
||||
// Note: defined here because it will be used more than once.
|
||||
const cssFilename = 'static/css/[name].[contenthash:8].css';
|
||||
|
||||
// ExtractTextPlugin expects the build output to be flat.
|
||||
// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
|
||||
// However, our output is structured with css, js and media folders.
|
||||
// To have this structure working with relative paths, we have to use custom options.
|
||||
const extractTextPluginOptions = shouldUseRelativeAssetPaths
|
||||
? // Making sure that the publicPath goes back to to build folder.
|
||||
{ publicPath: Array(cssFilename.split('/').length).join('../') }
|
||||
: {};
|
||||
|
||||
module.exports = {
|
||||
// Don't attempt to continue if there are any errors.
|
||||
bail: true,
|
||||
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
|
||||
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
|
||||
devtool: 'cheap-module-source-map',
|
||||
// These are the "entry points" to our application.
|
||||
// This means they will be the "root" imports that are included in JS bundle.
|
||||
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
|
||||
entry: [
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
// of CSS changes), or refresh the page (in case of JS changes). When you
|
||||
// make a syntax error, this client will display a syntax error overlay.
|
||||
// Note: instead of the default WebpackDevServer client, we use a custom one
|
||||
// to bring better experience for Create React App users. You can replace
|
||||
// the line below with these two lines if you prefer the stock client:
|
||||
// require.resolve('webpack-dev-server/client') + '?/',
|
||||
// require.resolve('webpack/hot/dev-server'),
|
||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
||||
// We ship a few polyfills by default:
|
||||
require.resolve('./polyfills'),
|
||||
// Errors should be considered fatal in development
|
||||
require.resolve('react-error-overlay'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appIndexJs,
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
// initialization, it doesn't blow up the WebpackDevServer client, and
|
||||
// changing JS code would still trigger a refresh.
|
||||
],
|
||||
output: {
|
||||
// The build folder.
|
||||
path: paths.appBuild,
|
||||
// Generated JS file names (with nested folders).
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
// We don't currently advertise code splitting but Webpack supports it.
|
||||
filename: 'static/js/[name].[hash].js',
|
||||
chunkFilename: 'static/js/[name].[hash].js',
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
publicPath: publicPath,
|
||||
// Point sourcemap entries to original disk location
|
||||
devtoolModuleFilenameTemplate: info =>
|
||||
path.relative(paths.appSrc, info.absoluteResourcePath)
|
||||
},
|
||||
watch:true,
|
||||
resolve: {
|
||||
// This allows you to set a fallback for where Webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/253
|
||||
modules: ['node_modules', paths.appNodeModules].concat(
|
||||
// It is guaranteed to exist because we tweak it in `env.js`
|
||||
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)),
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
// https://github.com/facebookincubator/create-react-app/issues/290
|
||||
extensions: [
|
||||
'.ts', '.tsx', '.js', '.json', '.jsx'
|
||||
],
|
||||
alias: {
|
||||
|
||||
// Support React Native Web
|
||||
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
|
||||
'react-native': 'react-native-web'
|
||||
},
|
||||
plugins: [// Prevents users from importing files from outside of src/ (or node_modules/).
|
||||
// This often causes confusion because we only process files within src/ with babel.
|
||||
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
|
||||
// please link the files into your node_modules/ and let module-resolution kick in.
|
||||
// Make sure your source files are compiled, as they will not be processed in any way.
|
||||
new ModuleScopePlugin(paths.appSrc)]
|
||||
},
|
||||
module: {
|
||||
strictExportPresence: true,
|
||||
rules: [
|
||||
// TODO: Disable require.ensure as it's not a standard language feature.
|
||||
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
|
||||
// { parser: { requireEnsure: false } },
|
||||
|
||||
// First, run the linter.
|
||||
// It's important to do this before Babel processes the JS.
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
loader: require.resolve('tslint-loader'),
|
||||
enforce: 'pre',
|
||||
include: paths.appSrc
|
||||
}, {
|
||||
test: /\.js$/,
|
||||
loader: require.resolve('source-map-loader'),
|
||||
enforce: 'pre',
|
||||
include: paths.appSrc
|
||||
},
|
||||
// ** ADDING/UPDATING LOADERS **
|
||||
// The "file" loader handles all assets unless explicitly excluded.
|
||||
// The `exclude` list *must* be updated with every change to loader extensions.
|
||||
// When adding a new loader, you must add its `test`
|
||||
// as a new entry in the `exclude` list for "file" loader.
|
||||
|
||||
// "file" loader makes sure those assets get served by WebpackDevServer.
|
||||
// When you `import` an asset, you get its (virtual) filename.
|
||||
// In production, they would get copied to the `build` folder.
|
||||
{
|
||||
exclude: [
|
||||
/\.html$/,
|
||||
// We have to write /\.(js|jsx)(\?.*)?$/ rather than just /\.(js|jsx)$/
|
||||
// because you might change the hot reloading server from the custom one
|
||||
// to Webpack's built-in webpack-dev-server/client?/, which would not
|
||||
// get properly excluded by /\.(js|jsx)$/ because of the query string.
|
||||
// Webpack 2 fixes this, but for now we include this hack.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/1713
|
||||
/\.(js|jsx)(\?.*)?$/,
|
||||
/\.(ts|tsx)(\?.*)?$/,
|
||||
/\.css$/,
|
||||
/\.json$/,
|
||||
/\.bmp$/,
|
||||
/\.gif$/,
|
||||
/\.jpe?g$/,
|
||||
/\.png$/
|
||||
],
|
||||
loader: require.resolve('file-loader'),
|
||||
options: {
|
||||
name: 'static/media/[name].[hash:8].[ext]'
|
||||
}
|
||||
},
|
||||
// "url" loader works like "file" loader except that it embeds assets
|
||||
// smaller than specified limit in bytes as data URLs to avoid requests.
|
||||
// A missing `test` is equivalent to a match.
|
||||
{
|
||||
test: [
|
||||
/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/
|
||||
],
|
||||
loader: require.resolve('url-loader'),
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: 'static/media/[name].[hash:8].[ext]'
|
||||
}
|
||||
},
|
||||
// Compile .tsx?
|
||||
{
|
||||
test: /\.(ts|tsx)$/,
|
||||
include: paths.appSrc,
|
||||
loader: require.resolve('ts-loader')
|
||||
},
|
||||
// "postcss" loader applies autoprefixer to our CSS.
|
||||
// "css" loader resolves paths in CSS and adds assets as dependencies.
|
||||
// "style" loader turns CSS into JS modules that inject <style> tags.
|
||||
// In production, we use a plugin to extract that CSS to a file, but
|
||||
// in development "style" loader enables hot editing of CSS.
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
require.resolve('style-loader'), {
|
||||
loader: require.resolve('css-loader'),
|
||||
options: {
|
||||
importLoaders: 1
|
||||
}
|
||||
}, {
|
||||
loader: require.resolve('postcss-loader'),
|
||||
options: {
|
||||
ident: 'postcss', // https://webpack.js.org/guides/migrating/#complex-options
|
||||
plugins: () => [
|
||||
require('postcss-flexbugs-fixes'),
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
'>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9', // React doesn't support IE8 anyway
|
||||
],
|
||||
flexbox: 'no-2009'
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// ** STOP ** Are you adding a new loader?
|
||||
// Remember to add the new extension(s) to the "url" loader exclusion list.
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
// Makes some environment variables available in index.html.
|
||||
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
|
||||
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
// In development, this will be an empty string.
|
||||
new InterpolateHtmlPlugin(env.raw),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({inject: true, template: paths.appHtml}),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
|
||||
new webpack.DefinePlugin(env.stringified),
|
||||
// This is necessary to emit hot updates (currently CSS only):
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// Watcher doesn't work well if you mistype casing in a path so we use
|
||||
// a plugin that prints an error when you attempt to do this.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/240
|
||||
// If you require a missing module and then `npm install` it, you still have
|
||||
// to restart the development server for Webpack to discover it. This plugin
|
||||
// makes the discovery automatic so you don't have to restart.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/186
|
||||
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
|
||||
// Moment.js is an extremely popular library that bundles large locale files
|
||||
// by default due to how Webpack interprets its code. This is a practical
|
||||
// solution that requires the user to opt into importing specific locales.
|
||||
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
|
||||
// You can remove this if you don't use Moment.js:
|
||||
new ManifestPlugin({
|
||||
fileName: 'asset-manifest.json',
|
||||
}),
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
|
||||
],
|
||||
// Some libraries import Node modules but don't use them in the browser.
|
||||
// Tell Webpack to provide empty mocks for them so importing them works.
|
||||
node: {
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty'
|
||||
},
|
||||
// Turn off performance hints during development because we don't do any
|
||||
// splitting or minification in interest of speed. These warnings become
|
||||
// cumbersome.
|
||||
performance: {
|
||||
hints: false
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,6 +3,8 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"eslint": "^4.2.0",
|
||||
"eslint-config-react-app": "^1.0.5",
|
||||
"react": "^15.5.4",
|
||||
"react-addons-css-transition-group": "^15.5.2",
|
||||
"react-dom": "^15.5.4",
|
||||
|
|
@ -38,6 +40,7 @@
|
|||
"fs-extra": "3.0.1",
|
||||
"html-webpack-plugin": "2.28.0",
|
||||
"jest": "20.0.3",
|
||||
"js-base64": "^2.1.9",
|
||||
"object-assign": "4.1.1",
|
||||
"postcss-flexbugs-fixes": "3.0.0",
|
||||
"postcss-loader": "2.0.5",
|
||||
|
|
@ -55,12 +58,14 @@
|
|||
"url-loader": "0.5.8",
|
||||
"webpack": "2.6.0",
|
||||
"webpack-dev-server": "2.4.5",
|
||||
"webpack-manifest-plugin": "1.1.0",
|
||||
"webpack-manifest-plugin": "^1.1.0",
|
||||
"whatwg-fetch": "2.0.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js",
|
||||
"flask": "node scripts/flask.js",
|
||||
"build": "node scripts/build.js",
|
||||
"shell": "bash",
|
||||
"test": "node scripts/test.js --env=jsdom"
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,202 @@
|
|||
'use strict';
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
// Ensure environment variables are read.
|
||||
require('../config/env');
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const chalk = require('chalk');
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const clearConsole = require('react-dev-utils/clearConsole');
|
||||
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
const {
|
||||
choosePort,
|
||||
createCompiler,
|
||||
prepareProxy,
|
||||
prepareUrls,
|
||||
} = require('react-dev-utils/WebpackDevServerUtils');
|
||||
const openBrowser = require('react-dev-utils/openBrowser');
|
||||
const paths = require('../config/paths');
|
||||
const config = require('../config/webpack.config.flask');
|
||||
const createDevServerConfig = require('../config/webpackDevServer.config');
|
||||
|
||||
const child_process = require('child_process');
|
||||
|
||||
const useYarn = fs.existsSync(paths.yarnLockFile);
|
||||
const isInteractive = process.stdout.isTTY;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
|
||||
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
|
||||
|
||||
const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild;
|
||||
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// First, read the current file sizes in build directory.
|
||||
// This lets us display how much they changed later.
|
||||
measureFileSizesBeforeBuild(paths.appBuild)
|
||||
.then(previousFileSizes => {
|
||||
// Remove all content but keep the directory so that
|
||||
// if you're in it, you don't end up in Trash
|
||||
fs.emptyDirSync(paths.appBuild);
|
||||
// Merge with the public folder
|
||||
copyPublicFolder();
|
||||
// Start the webpack build
|
||||
return build(previousFileSizes);
|
||||
})
|
||||
.then(
|
||||
({ stats, previousFileSizes, warnings }) => {
|
||||
if (warnings.length) {
|
||||
console.log(chalk.yellow('Compiled with warnings.\n'));
|
||||
console.log(warnings.join('\n\n'));
|
||||
console.log(
|
||||
'\nSearch for the ' +
|
||||
chalk.underline(chalk.yellow('keywords')) +
|
||||
' to learn more about each warning.'
|
||||
);
|
||||
console.log(
|
||||
'To ignore, add ' +
|
||||
chalk.cyan('// eslint-disable-next-line') +
|
||||
' to the line before.\n'
|
||||
);
|
||||
} else {
|
||||
console.log(chalk.green('Compiled successfully.\n'));
|
||||
}
|
||||
|
||||
console.log('File sizes after gzip:\n');
|
||||
printFileSizesAfterBuild(stats, previousFileSizes);
|
||||
console.log();
|
||||
child_process.spawn('python',['walle-server.py'],{
|
||||
stdio:'inherit'
|
||||
});
|
||||
const appPackage = require(paths.appPackageJson);
|
||||
const publicUrl = paths.publicUrl;
|
||||
const publicPath = config.output.publicPath;
|
||||
const buildFolder = path.relative(process.cwd(), paths.appBuild);
|
||||
printHostingInstructions(
|
||||
appPackage,
|
||||
publicUrl,
|
||||
publicPath,
|
||||
buildFolder,
|
||||
useYarn
|
||||
);
|
||||
},
|
||||
err => {
|
||||
console.log(chalk.red('Failed to compile.\n'));
|
||||
console.log((err.message || err) + '\n');
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
|
||||
// Create the production build and print the deployment instructions.
|
||||
function build(previousFileSizes) {
|
||||
console.log('Creating an optimized production build...');
|
||||
|
||||
let compiler = webpack(config);
|
||||
return new Promise((resolve, reject) => {
|
||||
// compiler.run((err, stats) => {
|
||||
compiler.watch({ // watch options:
|
||||
aggregateTimeout: 300, // wait so long for more changes
|
||||
poll: true // use polling instead of native watchers
|
||||
// pass a number to set the polling interval
|
||||
},(err, stats) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
const messages = formatWebpackMessages(stats.toJson({}, true));
|
||||
if (messages.errors.length) {
|
||||
return reject(new Error(messages.errors.join('\n\n')));
|
||||
}
|
||||
if (process.env.CI && messages.warnings.length) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'\nTreating warnings as errors because process.env.CI = true.\n' +
|
||||
'Most CI servers set it automatically.\n'
|
||||
)
|
||||
);
|
||||
return reject(new Error(messages.warnings.join('\n\n')));
|
||||
}
|
||||
return resolve({
|
||||
stats,
|
||||
previousFileSizes,
|
||||
warnings: messages.warnings,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function copyPublicFolder() {
|
||||
fs.copySync(paths.appPublic, paths.appBuild, {
|
||||
dereference: true,
|
||||
filter: file => file !== paths.appHtml,
|
||||
});
|
||||
}
|
||||
|
||||
// Tools like Cloud9 rely on this.
|
||||
|
||||
// function startWebPackDevServer(){
|
||||
// const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
|
||||
// const HOST = process.env.HOST || '0.0.0.0';
|
||||
//
|
||||
// // We attempt to use the default port but if it is busy, we offer the user to
|
||||
// // run on a different port. `detect()` Promise resolves to the next free port.
|
||||
// choosePort(HOST, DEFAULT_PORT)
|
||||
// .then(port => {
|
||||
// if (port == null) {
|
||||
// // We have not found a port.
|
||||
// return;
|
||||
// }
|
||||
// const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
|
||||
// const appName = require(paths.appPackageJson).name;
|
||||
// const urls = prepareUrls(protocol, HOST, port);
|
||||
// // Create a webpack compiler that is configured with custom messages.
|
||||
// const compiler = createCompiler(webpack, config, appName, urls, useYarn);
|
||||
// // Load proxy config
|
||||
// const proxySetting = require(paths.appPackageJson).proxy;
|
||||
// const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
|
||||
// // Serve webpack assets generated by the compiler over a web sever.
|
||||
// const serverConfig = createDevServerConfig(
|
||||
// proxyConfig,
|
||||
// urls.lanUrlForConfig
|
||||
// );
|
||||
// const devServer = new WebpackDevServer(compiler, serverConfig);
|
||||
// devServer.listen(port, HOST, err => {
|
||||
// if (err) {
|
||||
// return console.log(err);
|
||||
// }
|
||||
// if (isInteractive) {
|
||||
// clearConsole();
|
||||
// }
|
||||
// child_process.exec('python',['walle-server.py'],{
|
||||
// stdio:'inherit'
|
||||
// });
|
||||
// });
|
||||
// // openBrowser(urls.localUrlForBrowser);
|
||||
// })
|
||||
// .catch(err => {
|
||||
// if (err && err.message) {
|
||||
// console.log(err.message);
|
||||
// }
|
||||
// process.exit(1);
|
||||
// });
|
||||
// }
|
||||
|
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||
import { Dimmer, Loader } from 'semantic-ui-react';
|
||||
import * as XML from 'xml2js';
|
||||
import * as _ from 'lodash';
|
||||
import { LexEditor } from './LexComponents';
|
||||
import { LexEditor } from './LexEditor';
|
||||
|
||||
const fieldMetaMap = {
|
||||
label: { lens: 'label[0]', type: 'text' },
|
||||
|
|
|
|||
Loading…
Reference in New Issue