diff --git a/.editorconfig b/.editorconfig index d11544c..0f550f2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true -[*.{less,js,json}] +[*.{less,js,vue,json,yml}] indent_size = 2 [*.md] diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..fe60b82 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,29 @@ +const settings = require('./client/config/webpack.resolve').forEsLint; + +module.exports = { + root: true, + parserOptions: { + parser: 'babel-eslint' + }, + extends: [ + 'airbnb-base', + 'plugin:vue/strongly-recommended' + ], + rules: { + 'no-new': 0, + 'comma-dangle': 0, + radix: 0, + 'no-prototype-builtins': 0, + 'no-restricted-globals': 0, + 'no-underscore-dangle': 0, + 'vue/html-closing-bracket-newline': 0, + 'no-param-reassign': 0 + }, + globals: { + cloudinary: false + }, + plugins: [ + 'vue' + ], + settings +}; diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba1e30..6b7ed8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ -[1.1.4] - 2019-06-12 +## [Unreleased] +### Changed +- Full client / FE refactor using vue.js component instead of jQuery/entwine + +## [1.1.4] - 2019-06-12 ### Added - Possibility to chain multiple transformations - "CropScale" method, crop to custom coordinates/gravity if available, then scale/crop to the exact dimensions. -[1.1.3] - 2019-06-03 +## [1.1.3] - 2019-06-03 ### Added - Option to make Cloudinary meta data less prominent - `root_folder` config value, allows to use a folder as root for all upload fields @@ -11,34 +15,34 @@ ### Changed - Updated routes config to fix a interference with default admin/graphql route -[1.1.2] - 2019-05-09 +## [1.1.2] - 2019-05-09 ### Changed - Proper classes for the actions/buttons -[1.1.1] - 2019-05-08 +## [1.1.1] - 2019-05-08 ### Added - Save file size, width and height of the initial upload (after incoming transormation) ### Fixed - Namespace in the field holder template -[1.1.0] - 2019-04-24 +## [1.1.0] - 2019-04-24 ### Changed - Namespace update to Level51\Cloudinary -[1.0.1] - 2019-04-16 +## [1.0.1] - 2019-04-16 ## Changed - Ensure that the upload controller route is defined before other admin routes -[1.0.0] - 2019-03-28 +## [1.0.0] - 2019-03-28 ## Changed - Refactor for SilverStripe 4 support - see develop-ss3 branch or 0.x.x releases for SS3 -[0.2.1] - 2019-03-19 +## [0.2.1] - 2019-03-19 ## Changed - Trigger Cloudinary delete/destroy only if a public id is set -[0.2.0] - 2019-02-22 +## [0.2.0] - 2019-02-22 ## Added - getCloudinaryUrl service function - Methods for a few image transformations like pad, limit, scale ... @@ -55,7 +59,7 @@ - Use the service function to get the cloudinary url within the CloudinaryImage Link() function (ensures proper credentials setup) - privateDownloadLink if the "$asDownload" param is set to false -[0.1.0] - 2018-05-09 +## [0.1.0] - 2018-05-09 ## First public release - CloudinaryImage: data object holding relevant data and provide the first manipulation methods - CloudinaryUploadField: image uploader using the upload widget including some options like cropping, aspect ratio, upload folder name diff --git a/_config/config.yml b/_config/config.yml index 60e46d6..38b1671 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -3,7 +3,7 @@ Name: cloudinary --- SilverStripe\Admin\LeftAndMain: extra_requirements_javascript: - - https://widget.cloudinary.com/global/all.js + - https://widget.cloudinary.com/v2.0/global/all.js Level51\Cloudinary\Cloudinary: theme: 'white' use_signed: true diff --git a/client/config/webpack.dev.js b/client/config/webpack.dev.js new file mode 100644 index 0000000..8d6fa24 --- /dev/null +++ b/client/config/webpack.dev.js @@ -0,0 +1,110 @@ +/* eslint-disable import/no-extraneous-dependencies */ +const path = require('path'); +const { VueLoaderPlugin } = require('vue-loader'); +const { DefinePlugin } = require('webpack'); +const MiniCSSExtractPlugin = require('mini-css-extract-plugin'); +const resolve = require('./webpack.resolve').forWebpack; + +module.exports = env => ({ + + entry: { + 'level51-cloudinary-upload-field': ['@babel/polyfill/noConflict', 'src/cloudinaryUploadField.js'] + }, + + output: { + path: path.resolve(__dirname, '../dist'), + filename: '[name].js', + publicPath: '' // TODO check + }, + + mode: 'development', + + devtool: 'source-map', + + resolve, + + module: { + rules: [ + { + test: /\.js$/, + exclude: file => ( + /node_modules/.test(file) + && !/\.vue\.js/.test(file) + ), + use: { + loader: 'babel-loader', + options: { + plugins: [ + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-syntax-dynamic-import' + ], + presets: [['@babel/preset-env', { modules: false }, '@babel/preset-stage-3']] + } + } + }, + { + test: /\.vue$/, + loader: 'vue-loader' + }, + { + test: /\.css$/, + use: [ + { + loader: 'vue-style-loader' + }, + { + loader: MiniCSSExtractPlugin.loader + }, + { + loader: 'css-loader' + } + ] + }, + { + test: /\.less$/, + use: [ + { + loader: 'vue-style-loader' + }, + { + loader: MiniCSSExtractPlugin.loader + }, + { + loader: 'css-loader' + }, + { + loader: 'less-loader' + } + ] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: '[name].[ext]?[hash]' + } + }, + { + test: /\.(eot|svg|ttf|woff|woff2)$/, + loader: 'file-loader', + options: { + name: '[name].[ext]' + } + } + ] + }, + + plugins: [ + new DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(env.NODE_ENV) + } + }), + + new VueLoaderPlugin(), + + new MiniCSSExtractPlugin({ + filename: '[name].css' + }) + ] +}); diff --git a/client/config/webpack.prod.js b/client/config/webpack.prod.js new file mode 100644 index 0000000..407359a --- /dev/null +++ b/client/config/webpack.prod.js @@ -0,0 +1,116 @@ +/* eslint-disable import/no-extraneous-dependencies */ +const path = require('path'); +const { VueLoaderPlugin } = require('vue-loader'); +const { DefinePlugin } = require('webpack'); +const MiniCSSExtractPlugin = require('mini-css-extract-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); +const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); + +const resolve = require('./webpack.resolve').forWebpack; + +module.exports = env => ({ + + entry: { + 'level51-cloudinary-upload-field': ['@babel/polyfill/noConflict', 'src/cloudinaryUploadField.js'] + }, + + output: { + path: path.resolve(__dirname, '../dist'), + filename: '[name].js', + publicPath: '' // TODO check + }, + + mode: 'production', + + resolve, + + module: { + rules: [ + { + test: /\.js$/, + exclude: file => ( + /node_modules/.test(file) + && !/\.vue\.js/.test(file) + ), + use: { + loader: 'babel-loader', + options: { + plugins: [ + '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-syntax-dynamic-import' + ], + presets: [['@babel/preset-env', { modules: false }, '@babel/preset-stage-3']] + } + } + }, + { + test: /\.vue$/, + loader: 'vue-loader' + }, + { + test: /\.css$/, + use: [ + { + loader: 'vue-style-loader' + }, + { + loader: MiniCSSExtractPlugin.loader + }, + { + loader: 'css-loader' + } + ] + }, + { + test: /\.less$/, + use: [ + { + loader: 'vue-style-loader' + }, + { + loader: MiniCSSExtractPlugin.loader + }, + { + loader: 'css-loader' + }, + { + loader: 'less-loader' + } + ] + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: '[name].[ext]?[hash]' + } + }, + { + test: /\.(eot|svg|ttf|woff|woff2)$/, + loader: 'file-loader', + options: { + name: '[name].[ext]' + } + } + ] + }, + + plugins: [ + new DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(env.NODE_ENV) + } + }), + + new VueLoaderPlugin(), + + new OptimizeCssAssetsPlugin(), + new MiniCSSExtractPlugin({ + filename: '[name].css' + }), + + new CompressionPlugin({ + algorithm: 'gzip' + }), + ] +}); diff --git a/client/config/webpack.resolve.js b/client/config/webpack.resolve.js new file mode 100644 index 0000000..40d978b --- /dev/null +++ b/client/config/webpack.resolve.js @@ -0,0 +1,21 @@ +const path = require('path'); + +const alias = { + vue$: 'vue/dist/vue.common.js', + src: path.resolve(__dirname, '../src/js'), + styles: path.resolve(__dirname, '../src/styles') +}; + +const extensions = ['.js', '.vue', '.json', '.less']; + +module.exports = { + forWebpack: { alias, extensions }, + forEsLint: { + 'import/resolver': { + alias: { + map: Object.keys(alias).map(key => [key, alias[key]]), + extensions + } + } + } +}; diff --git a/client/dist/cloudinary-upload-field.css b/client/dist/cloudinary-upload-field.css deleted file mode 100644 index 45e735f..0000000 --- a/client/dist/cloudinary-upload-field.css +++ /dev/null @@ -1 +0,0 @@ -.field.cloudinaryupload label{display:block;float:none;width:auto}.field.cloudinaryupload .form__field-holder{margin-left:0;max-width:696px;width:100%;clear:both}.field.cloudinaryupload .cloudinary-upload-field{background:#fff;border:1px solid #b3b3b3;padding:15px;display:flex;align-items:center;border-radius:4px}.field.cloudinaryupload .cloudinary-upload-field .cloudinary-upload-field-meta{margin-bottom:10px}.field.cloudinaryupload .cloudinary-upload-field .cloudinary-upload-field-thumbnail{margin-right:10px}.field.cloudinaryupload .cloudinary-metainfo{position:absolute;right:35px;bottom:10px;opacity:.75;font-size:.75rem}.field.cloudinaryupload .cloudinary-metainfo:hover{cursor:help} \ No newline at end of file diff --git a/client/dist/cloudinary-upload-field.js b/client/dist/cloudinary-upload-field.js deleted file mode 100644 index 7f9bc3d..0000000 --- a/client/dist/cloudinary-upload-field.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(t.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)t.d(o,r,function(t){return e[t]}.bind(null,r));return o},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,n){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}n(1),jQuery.entwine(function(e){e(".cms-edit-form .cloudinary-upload-field").entwine({onmatch:function(){var t=this,n=this.data("options"),r=this.find(".cloudinary-upload-field-actions"),i=r.find(".cloudinary-upload-field-upload"),a=r.find(".cloudinary-upload-field-remove"),u=r.find(".cloudinary-upload-field-delete"),l=this.find('input[name="'.concat(n.name,'"]')),c=this.find("img.cloudinary-upload-field-thumbnail"),d=0