diff --git a/swh/web/assets/config/.eslintrc b/swh/web/assets/config/.eslintrc index 69e3dade..0dcf18df 100644 --- a/swh/web/assets/config/.eslintrc +++ b/swh/web/assets/config/.eslintrc @@ -1,307 +1,307 @@ { "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 2017, "ecmaFeatures": { "experimentalObjectRestSpread": true, "jsx": true }, "sourceType": "module", "allowImportExportEverywhere": true }, "env": { "es6": true, "node": true }, "plugins": [ "import", "node", "promise", "standard" ], "globals": { "document": false, "navigator": false, "window": false, "$": false, "jQuery": false, "history": false, "localStorage": false, "sessionStorage": false, "Urls": false, "hljs": false, "Waypoint": false, "swh": false, "fetch": false, "__STATIC__": false, "Image": false, "Cookies": false, "grecaptcha": false }, "rules": { "accessor-pairs": "error", "arrow-spacing": ["error", { "before": true, "after": true }], "block-spacing": ["error", "always"], "brace-style": ["error", "1tbs", { "allowSingleLine": true }], "camelcase": ["error", { "properties": "never" }], "comma-dangle": ["error", { "arrays": "never", "objects": "never", "imports": "never", "exports": "never", "functions": "never" }], "comma-spacing": ["error", { "before": false, "after": true }], "comma-style": ["error", "last"], "constructor-super": "error", "curly": ["error", "multi-line"], "dot-location": ["error", "property"], "eol-last": "error", "eqeqeq": ["error", "always", { "null": "ignore" }], "func-call-spacing": ["error", "never"], "generator-star-spacing": ["error", { "before": true, "after": true }], "handle-callback-err": ["error", "^(err|error)$"], "indent": ["error", 2, { "SwitchCase": 1, "VariableDeclarator": 1, "outerIIFEBody": 1, "MemberExpression": 1, "FunctionDeclaration": { "parameters": "first", "body": 1 }, "FunctionExpression": { "parameters": "first", "body": 1 }, "CallExpression": { "arguments": "first" }, - "ArrayExpression": 1, - "ObjectExpression": 1, - "ImportDeclaration": 1, + "ArrayExpression": "first", + "ObjectExpression": "first", + "ImportDeclaration": "first", "flatTernaryExpressions": false, "ignoreComments": false }], "key-spacing": ["error", { "beforeColon": false, "afterColon": true }], "keyword-spacing": ["error", { "before": true, "after": true }], "new-cap": ["error", { "newIsCap": true, "capIsNew": false }], "new-parens": "error", "no-array-constructor": "error", "no-caller": "error", "no-class-assign": "error", "no-compare-neg-zero": "error", "no-cond-assign": "error", "no-const-assign": "error", "no-constant-condition": ["error", { "checkLoops": false }], "no-control-regex": "error", "no-debugger": "error", "no-delete-var": "error", "no-dupe-args": "error", "no-dupe-class-members": "error", "no-dupe-keys": "error", "no-duplicate-case": "error", "no-empty-character-class": "error", "no-empty-pattern": "error", "no-eval": "error", "no-ex-assign": "error", "no-extend-native": "error", "no-extra-bind": "error", "no-extra-boolean-cast": "error", "no-extra-parens": ["error", "functions"], "no-fallthrough": "error", "no-floating-decimal": "error", "no-func-assign": "error", "no-global-assign": "error", "no-implied-eval": "error", "no-inner-declarations": ["error", "functions"], "no-invalid-regexp": "error", "no-irregular-whitespace": "error", "no-iterator": "error", "no-label-var": "error", "no-labels": ["error", { "allowLoop": false, "allowSwitch": false }], "no-lone-blocks": "error", "no-mixed-operators": ["error", { "groups": [ ["==", "!=", "===", "!==", ">", ">=", "<", "<="], ["&&", "||"], ["in", "instanceof"] ], "allowSamePrecedence": true }], "no-mixed-spaces-and-tabs": "error", "no-multi-spaces": "error", "no-multi-str": "error", "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], "no-negated-in-lhs": "error", "no-new": 0, "no-new-func": "error", "no-new-object": "error", "no-new-require": "error", "no-new-symbol": "error", "no-new-wrappers": "error", "no-obj-calls": "error", "no-octal": "error", "no-octal-escape": "error", "no-path-concat": "error", "no-proto": "error", "no-redeclare": "error", "no-regex-spaces": "error", "no-return-assign": ["error", "except-parens"], "no-return-await": "error", "no-self-assign": "error", "no-self-compare": "error", "no-sequences": "error", "no-shadow-restricted-names": "error", "no-sparse-arrays": "error", "no-tabs": "error", "no-template-curly-in-string": "error", "no-this-before-super": "error", "no-throw-literal": "error", "no-trailing-spaces": "error", "no-undef": "error", "no-undef-init": "error", "no-unexpected-multiline": "error", "no-unmodified-loop-condition": "error", "no-unneeded-ternary": ["error", { "defaultAssignment": false }], "no-unreachable": "error", "no-unsafe-finally": "error", "no-unsafe-negation": "error", "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }], "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], "no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], "no-useless-call": "error", "no-useless-computed-key": "error", "no-useless-constructor": "error", "no-useless-escape": "error", "no-useless-rename": "error", "no-useless-return": "error", "no-whitespace-before-property": "error", "no-with": "error", "object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }], "one-var": ["error", { "initialized": "never" }], "operator-linebreak": ["error", "after", { "overrides": { "?": "before", ":": "before" } }], "padded-blocks": ["off", { "blocks": "never", "switches": "never", "classes": "never" }], "prefer-promise-reject-errors": "error", "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], "rest-spread-spacing": ["error", "never"], "semi": ["error", "always"], "semi-spacing": ["error", { "before": false, "after": true }], "space-before-blocks": ["error", "always"], "space-before-function-paren": ["error", "never"], "space-in-parens": ["error", "never"], "space-infix-ops": "error", "space-unary-ops": ["error", { "words": true, "nonwords": false }], "spaced-comment": ["error", "always", { "line": { "markers": ["*package", "!", "/", ",", "="] }, "block": { "balanced": true, "markers": ["*package", "!", ",", ":", "::", "flow-include"], "exceptions": ["*"] } }], "symbol-description": "error", "template-curly-spacing": ["error", "never"], "template-tag-spacing": ["error", "never"], "unicode-bom": ["error", "never"], "use-isnan": "error", "valid-typeof": ["error", { "requireStringLiterals": true }], "wrap-iife": ["error", "any", { "functionPrototypeMethods": true }], "yield-star-spacing": ["error", "both"], "yoda": ["error", "never"], "import/export": "error", "import/first": "error", "import/no-duplicates": "error", "import/no-webpack-loader-syntax": "off", "node/no-deprecated-api": "error", "node/process-exit-as-throw": "error", "promise/param-names": "error", "standard/array-bracket-even-spacing": ["error", "either"], "standard/computed-property-even-spacing": ["error", "even"], "standard/no-callback-literal": "error", "standard/object-curly-even-spacing": ["error", "either"] } } \ No newline at end of file diff --git a/swh/web/assets/config/webpack.config.development.js b/swh/web/assets/config/webpack.config.development.js index e4e3da93..d4253fa1 100644 --- a/swh/web/assets/config/webpack.config.development.js +++ b/swh/web/assets/config/webpack.config.development.js @@ -1,370 +1,372 @@ /** - * Copyright (C) 2018 The Software Heritage developers + * Copyright (C) 2018-2019 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ // webpack configuration for compiling static assets in development mode // import required node modules and webpack plugins const fs = require('fs'); const path = require('path'); const webpack = require('webpack'); const BundleTracker = require('webpack-bundle-tracker'); const RobotstxtPlugin = require('robotstxt-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const RemoveSourceMapUrlPlugin = require('./webpack-plugins/remove-source-map-url-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); // are we running webpack-dev-server ? const isDevServer = process.argv.find(v => v.includes('webpack-dev-server')); // webpack-dev-server configuration const devServerPort = 3000; const devServerPublicPath = 'http://localhost:' + devServerPort + '/static/'; // set publicPath according if we are using webpack-dev-server to serve // our assets or not const publicPath = isDevServer ? devServerPublicPath : '/static/'; const nodeModules = path.resolve(__dirname, '../../../../node_modules/'); // collect all bundles we want to produce with webpack var bundles = {}; const bundlesDir = path.join(__dirname, '../src/bundles'); fs.readdirSync(bundlesDir).forEach(file => { bundles[file] = ['bundles/' + file + '/index.js']; }); // common loaders for css related assets (css, sass, less) let cssLoaders = [ MiniCssExtractPlugin.loader, { loader: 'cache-loader' }, { loader: 'css-loader', options: { sourceMap: !isDevServer } }, { loader: 'postcss-loader', options: { ident: 'postcss', sourceMap: !isDevServer, plugins: [ // lint swh-web stylesheets require('stylelint')({ 'config': { 'extends': 'stylelint-config-standard', 'rules': { 'indentation': 4, 'font-family-no-missing-generic-family-keyword': null, 'no-descending-specificity': null }, - 'ignoreFiles': ['node_modules/**/*.css', 'swh/web/assets/src/thirdparty/**/*.css'] + 'ignoreFiles': ['node_modules/**/*.css', + 'swh/web/assets/src/thirdparty/**/*.css'] } }), // automatically add vendor prefixes to css rules require('autoprefixer')(), require('postcss-normalize')() ] } } ]; // webpack development configuration module.exports = { // use caching to speedup incremental builds cache: true, // set mode to development mode: 'development', // use eval source maps when using webpack-dev-server for quick debugging, // otherwise generate source map files (more expensive) devtool: isDevServer ? 'eval' : 'source-map', // webpack-dev-server configuration devServer: { clientLogLevel: 'warning', port: devServerPort, publicPath: devServerPublicPath, // enable to serve static assets not managed by webpack contentBase: path.resolve('./swh/web/'), // we do not use hot reloading here (as a framework like React needs to be used in order to fully benefit from that feature) // and prefere to fully reload the frontend application in the browser instead hot: false, inline: true, historyApiFallback: true, headers: { 'Access-Control-Allow-Origin': '*' }, compress: true, stats: { colors: true }, overlay: { warnings: true, errors: true } }, // set entries to the bundles we want to produce entry: bundles, // assets output configuration output: { path: path.resolve('./swh/web/static/'), filename: 'js/[name].[chunkhash].js', chunkFilename: 'js/[name].[chunkhash].js', publicPath: publicPath, // each bundle will be compiled as a umd module with its own namespace // in order to easily use them in django templates library: ['swh', '[name]'], libraryTarget: 'umd' }, // module resolving configuration resolve: { // alias pdfjs to its minified version alias: { 'pdfjs-dist': 'pdfjs-dist/build/pdf.min.js' }, // configure base paths for resolving modules with webpack modules: [ 'node_modules', path.resolve(__dirname, '../src') ] }, // module import configuration module: { - rules: [{ + rules: [ + { // Preprocess all js files with eslint for consistent code style // and avoid bad js development practices. - enforce: 'pre', - test: /\.js$/, - exclude: /node_modules/, - use: [{ - loader: 'eslint-loader', - options: { - configFile: path.join(__dirname, '.eslintrc'), - ignorePath: path.join(__dirname, '.eslintignore'), - emitWarning: true - } - }] - }, - { + enforce: 'pre', + test: /\.js$/, + exclude: /node_modules/, + use: [{ + loader: 'eslint-loader', + options: { + configFile: path.join(__dirname, '.eslintrc'), + ignorePath: path.join(__dirname, '.eslintignore'), + emitWarning: true + } + }] + }, + { // Use babel-loader in order to use es6 syntax in js files // but also advanced js features like async/await syntax. // All code get transpiled to es5 in order to be executed // in a large majority of browsers. - test: /\.js$/, - exclude: /node_modules/, - use: [ - { - loader: 'cache-loader' - }, - { - loader: 'babel-loader', - options: { - presets: [ - // use env babel presets to benefit from es6 syntax - ['@babel/preset-env', { - // Do not transform es6 module syntax to another module type - // in order to benefit from dead code elimination (aka tree shaking) - // when running webpack in production mode - 'loose': true, - 'modules': false - }] - ], - plugins: [ + test: /\.js$/, + exclude: /node_modules/, + use: [ + { + loader: 'cache-loader' + }, + { + loader: 'babel-loader', + options: { + presets: [ + // use env babel presets to benefit from es6 syntax + ['@babel/preset-env', { + // Do not transform es6 module syntax to another module type + // in order to benefit from dead code elimination (aka tree shaking) + // when running webpack in production mode + 'loose': true, + 'modules': false + }] + ], + plugins: [ // use babel transform-runtime plugin in order to use aync/await syntax - ['@babel/plugin-transform-runtime', { - 'corejs': 2, - 'regenerator': true - }], - // use other babel plugins to benefit from advanced js features (es2017) - '@babel/plugin-syntax-dynamic-import' - ] + ['@babel/plugin-transform-runtime', { + 'corejs': 2, + 'regenerator': true + }], + // use other babel plugins to benefit from advanced js features (es2017) + '@babel/plugin-syntax-dynamic-import' + ] + } + }] + }, + // expose jquery to the global context as $ and jQuery when importing it + { + test: require.resolve('jquery'), + use: [{ + loader: 'expose-loader', + options: 'jQuery' + }, { + loader: 'expose-loader', + options: '$' + }] + }, + // expose highlightjs to the global context as hljs when importing it + { + test: require.resolve('highlight.js'), + use: [{ + loader: 'expose-loader', + options: 'hljs' + }] + }, + { + test: require.resolve('js-cookie'), + use: [{ + loader: 'expose-loader', + options: 'Cookies' + }] + }, + // css import configuration: + // - first process it with postcss + // - then extract it to a dedicated file associated to each bundle + { + test: /\.css$/, + use: cssLoaders + }, + // sass import configuration: + // - generate css with sass-loader + // - process it with postcss + // - then extract it to a dedicated file associated to each bundle + { + test: /\.scss$/, + use: cssLoaders.concat([ + { + loader: 'sass-loader', + options: { + sourceMap: !isDevServer + } + } + ]) + }, + // less import configuration: + // - generate css with less-loader + // - process it with postcss + // - then extract it to a dedicated file associated to each bundle + { + test: /\.less$/, + use: cssLoaders.concat([ + { + loader: 'less-loader', + options: { + sourceMap: !isDevServer + } + } + ]) + }, + // web fonts import configuration + { + test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, + use: [{ + loader: 'file-loader', + options: { + name: '[name].[ext]', + outputPath: 'fonts/' } }] - }, - // expose jquery to the global context as $ and jQuery when importing it - { - test: require.resolve('jquery'), - use: [{ - loader: 'expose-loader', - options: 'jQuery' }, { - loader: 'expose-loader', - options: '$' - }] - }, - // expose highlightjs to the global context as hljs when importing it - { - test: require.resolve('highlight.js'), - use: [{ - loader: 'expose-loader', - options: 'hljs' - }] - }, - { - test: require.resolve('js-cookie'), - use: [{ - loader: 'expose-loader', - options: 'Cookies' - }] - }, - // css import configuration: - // - first process it with postcss - // - then extract it to a dedicated file associated to each bundle - { - test: /\.css$/, - use: cssLoaders - }, - // sass import configuration: - // - generate css with sass-loader - // - process it with postcss - // - then extract it to a dedicated file associated to each bundle - { - test: /\.scss$/, - use: cssLoaders.concat([ - { - loader: 'sass-loader', + test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, + use: [{ + loader: 'file-loader', options: { - sourceMap: !isDevServer + name: '[name].[ext]', + outputPath: 'fonts/' } - } - ]) - }, - // less import configuration: - // - generate css with less-loader - // - process it with postcss - // - then extract it to a dedicated file associated to each bundle - { - test: /\.less$/, - use: cssLoaders.concat([ - { - loader: 'less-loader', + }] + }, { + test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, + use: [{ + loader: 'file-loader', options: { - sourceMap: !isDevServer + name: '[name].[ext]', + outputPath: 'fonts/' } - } - ]) - }, - // web fonts import configuration - { - test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, - use: [{ - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'fonts/' - } - }] - }, { - test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, - use: [{ - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'fonts/' - } - }] - }, { - test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, - use: [{ - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'fonts/' - } - }] - }, { - test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, - use: [{ - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'fonts/' - } - }] - }, { - test: /\.otf(\?v=\d+\.\d+\.\d+)?$/, - use: [{ - loader: 'file-loader', - options: { - name: '[name].[ext]', - outputPath: 'fonts/' - } - }] - } + }] + }, { + test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, + use: [{ + loader: 'file-loader', + options: { + name: '[name].[ext]', + outputPath: 'fonts/' + } + }] + }, { + test: /\.otf(\?v=\d+\.\d+\.\d+)?$/, + use: [{ + loader: 'file-loader', + options: { + name: '[name].[ext]', + outputPath: 'fonts/' + } + }] + } ], // tell webpack to not parse minified pdfjs file to speedup build process noParse: [path.resolve(nodeModules, 'pdfjs-dist/build/pdf.min.js')] }, // webpack plugins plugins: [ // cleanup previously generated assets new CleanWebpackPlugin(['static/css', 'static/js', 'static/fonts', 'static/*.*'], { root: path.resolve(__dirname, '../../') }), // needed in order to use django_webpack_loader new BundleTracker({ filename: './swh/web/static/webpack-stats.json' }), // for generating the robots.txt file new RobotstxtPlugin({ policy: [{ userAgent: '*', disallow: '/api/' }] }), // for extracting all stylesheets in separate css files new MiniCssExtractPlugin({ filename: 'css/[name].[chunkhash].css', chunkFilename: 'css/[name].[chunkhash].css' }), // for removing some warnings in js console about not found source maps new RemoveSourceMapUrlPlugin(), // define some global variables accessible from js code new webpack.DefinePlugin({ __STATIC__: JSON.stringify(publicPath) }), // needed in order to use bootstrap 4.x new webpack.ProvidePlugin({ Popper: ['popper.js', 'default'], Alert: 'exports-loader?Alert!bootstrap/js/dist/alert', Button: 'exports-loader?Button!bootstrap/js/dist/button', Carousel: 'exports-loader?Carousel!bootstrap/js/dist/carousel', Collapse: 'exports-loader?Collapse!bootstrap/js/dist/collapse', Dropdown: 'exports-loader?Dropdown!bootstrap/js/dist/dropdown', Modal: 'exports-loader?Modal!bootstrap/js/dist/modal', Popover: 'exports-loader?Popover!bootstrap/js/dist/popover', Scrollspy: 'exports-loader?Scrollspy!bootstrap/js/dist/scrollspy', Tab: 'exports-loader?Tab!bootstrap/js/dist/tab', Tooltip: 'exports-loader?Tooltip!bootstrap/js/dist/tooltip', Util: 'exports-loader?Util!bootstrap/js/dist/util' }), // needed in order to use pdf.js new webpack.IgnorePlugin(/^\.\/pdf.worker.js$/), new CopyWebpackPlugin([{ from: path.resolve(nodeModules, 'pdfjs-dist/build/pdf.worker.min.js'), to: path.resolve(__dirname, '../../static/js/') }]) ], // webpack optimizations optimization: { // ensure the vendors bundle gets emitted in a single chunk splitChunks: { cacheGroups: { vendors: { test: 'vendors', chunks: 'all', name: 'vendors', enforce: true } } } }, // disable webpack warnings about bundle sizes performance: { hints: false } }; diff --git a/swh/web/assets/src/bundles/vault/vault-create-tasks.js b/swh/web/assets/src/bundles/vault/vault-create-tasks.js index 924b590a..c3f673b9 100644 --- a/swh/web/assets/src/bundles/vault/vault-create-tasks.js +++ b/swh/web/assets/src/bundles/vault/vault-create-tasks.js @@ -1,78 +1,80 @@ /** - * Copyright (C) 2018 The Software Heritage developers + * Copyright (C) 2018-2019 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ import {handleFetchError, csrfPost} from 'utils/functions'; function addVaultCookingTask(cookingTask) { let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks')); if (!vaultCookingTasks) { vaultCookingTasks = []; } if (vaultCookingTasks.find(val => { return val.object_type === cookingTask.object_type && val.object_id === cookingTask.object_id; }) === undefined) { let cookingUrl; if (cookingTask.object_type === 'directory') { cookingUrl = Urls.api_vault_cook_directory(cookingTask.object_id); } else { cookingUrl = Urls.api_vault_cook_revision_gitfast(cookingTask.object_id); } if (cookingTask.email) { cookingUrl += '?email=' + cookingTask.email; } csrfPost(cookingUrl) .then(handleFetchError) .then(() => { vaultCookingTasks.push(cookingTask); localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks)); $('#vault-cook-directory-modal').modal('hide'); $('#vault-cook-revision-modal').modal('hide'); window.location = Urls.browse_vault(); }) .catch(() => { $('#vault-cook-directory-modal').modal('hide'); $('#vault-cook-revision-modal').modal('hide'); }); } else { window.location = Urls.browse_vault(); } } function validateEmail(email) { let re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(String(email).toLowerCase()); } export function cookDirectoryArchive(directoryId) { let email = $('#swh-vault-directory-email').val().trim(); if (!email || validateEmail(email)) { - let cookingTask = {'object_type': 'directory', + let cookingTask = { + 'object_type': 'directory', 'object_id': directoryId, 'email': email, - 'status': 'new'}; + 'status': 'new' + }; addVaultCookingTask(cookingTask); } else { $('#invalid-email-modal').modal('show'); } } export function cookRevisionArchive(revisionId) { let email = $('#swh-vault-revision-email').val().trim(); if (!email || validateEmail(email)) { let cookingTask = { 'object_type': 'revision', 'object_id': revisionId, 'email': email, 'status': 'new' }; addVaultCookingTask(cookingTask); } else { $('#invalid-email-modal').modal('show'); } }