commit f5e6fd57cdd7a1c577eb3f622b6b90b939a23ed1 Author: Sam Liu Date: Fri Mar 29 14:22:19 2024 +0700 first diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..b1622b5 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,2 @@ +last 2 major versions +Firefox ESR diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d4a31d9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*{js,pug,scss,json}] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +quote_type = single diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e69de29 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..3d75c73 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,197 @@ +module.exports = { + root: true, + env: { + browser: true, + es6: true, + node: true + }, + parser: 'babel-eslint', + parserOptions: { + ecmaFeatures: { + legacyDecorators: true + } + }, + globals: { + $: true, + jQuery: true, + Plugin: true, + __webpack_require__: true + }, + rules: { + // recommended + 'for-direction': 2, + 'getter-return': 2, + 'no-compare-neg-zero': 2, + 'no-cond-assign': [2, 'always'], + 'no-console': [1, { + allow: ['warn', 'error'] + }], + 'no-constant-condition': 2, + 'no-control-regex': 1, + 'no-debugger': 1, + 'no-dupe-args': 2, + 'no-dupe-keys': 2, + 'no-duplicate-case': 2, + 'no-empty': 1, + 'no-empty-character-class': 1, + 'no-ex-assign': 2, + 'no-extra-boolean-cast': 1, + 'no-extra-semi': 2, + 'no-func-assign': 2, + 'no-inner-declarations': 2, + 'no-invalid-regexp': 2, + 'no-irregular-whitespace': 2, + 'no-obj-calls': 2, + 'no-regex-spaces': 1, + 'no-sparse-arrays': 2, + 'no-unexpected-multiline': 2, + 'no-unreachable': 2, + 'no-unsafe-finally': 2, + 'no-unsafe-negation': 2, + 'use-isnan': 1, + 'valid-typeof': [2, { + "requireStringLiterals": true + }], + 'no-case-declarations': 2, + 'no-empty-pattern': 2, + 'no-fallthrough': 2, + 'no-global-assign': 2, + 'no-octal': 2, + 'no-redeclare': [2, { + 'builtinGlobals': true + }], + 'no-self-assign': 2, + 'no-unused-labels': 1, + 'no-useless-escape': 1, + 'no-delete-var': 2, + 'no-undef': 2, + 'no-unused-vars': 1, + 'no-mixed-spaces-and-tabs': 1, + 'constructor-super': 2, + 'no-class-assign': 2, + 'no-const-assign': 2, + 'no-dupe-class-members': 2, + 'no-new-symbol': 2, + 'no-this-before-super': 2, + 'require-yield': 2, + + // error + 'no-async-promise-executor': 2, + 'no-misleading-character-class': 2, + 'require-atomic-updates': 2, + 'accessor-pairs': 2, + 'block-scoped-var': 2, + 'curly': 2, + 'dot-location': [2, 'property'], + 'eqeqeq': [2, 'always'], + 'no-caller': 2, + 'no-eq-null': 2, + 'no-eval': 2, + 'no-implied-eval': 2, + 'no-iterator': 2, + 'no-labels': 2, + 'no-lone-blocks': 2, + 'no-loop-func': 2, + 'no-multi-str': 2, + 'no-new-func': 2, + 'no-new-wrappers': 2, + 'no-octal-escape': 2, + 'no-param-reassign': 2, + 'no-proto': 2, + 'no-return-assign': [2, 'always'], + 'no-return-await': 2, + 'no-self-compare': 2, + 'no-sequences': 2, + 'no-with': 2, + 'vars-on-top': 2, + 'wrap-iife': [2, 'inside', { + functionPrototypeMethods: true + }], + 'no-label-var': 2, + 'no-use-before-define': 2, + + // Warning + 'no-await-in-loop': 1, + 'array-callback-return': 1, + 'class-methods-use-this': 1, + 'consistent-return': [1, { + treatUndefinedAsUnspecified: true + }], + 'dot-notation': [0, { + allowKeywords: false + }], + 'guard-for-in': 1, + 'no-alert': 1, + 'no-div-regex': 1, + 'no-else-return': 1, + 'no-empty-function': 1, + 'no-extra-bind': 1, + 'no-extra-label': 1, + 'no-floating-decimal': 1, + 'no-multi-spaces': [1, { + ignoreEOLComments: true, + exceptions: { + Property: false, + BinaryExpression: true, + VariableDeclarator: true, + ImportDeclaration: true + } + }], + 'no-script-url': 1, + 'no-useless-call': 1, + 'no-useless-concat': 1, + 'no-useless-return': 1, + 'no-void': 1, + 'require-await': 1, + 'no-undef-init': 1, + 'no-undefined': 1, + + // Stylist + 'block-spacing': 1, + 'brace-style': 1, + 'comma-spacing': 1, + 'comma-style': 1, + 'computed-property-spacing': 1, + 'func-call-spacing': 1, + 'indent': [1, 2, { + SwitchCase: 1, + MemberExpression: 'off' + }], + 'key-spacing': 1, + 'keyword-spacing': 1, + 'lines-between-class-members': 1, + 'max-len': [1, { + code: 80, + ignoreRegExpLiterals: true, + ignoreTemplateLiterals: true, + ignoreStrings: true + }], + 'no-lonely-if': 1, + 'no-multi-assign': 1, + 'no-multiple-empty-lines': [1, { + max: 2, + maxEOF: 1, + maxBOF: 0 + }], + 'no-trailing-spaces': 1, + 'no-unneeded-ternary': [1, { + defaultAssignment: false + }], + 'no-whitespace-before-property': 1, + 'quotes': [1, 'single', { + allowTemplateLiterals: true + }], + 'semi-spacing': 1, + 'semi': 1, + 'space-infix-ops': 1, + 'switch-colon-spacing': 1, + 'no-duplicate-imports': [1, { + includeExports: true + }], + 'no-useless-computed-key': 1, + 'no-var': 1, + 'object-shorthand': 1, + 'prefer-template': 1, + 'template-curly-spacing': 1 + } +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a4cb017 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.json text eol=lf +*.js text eol=lf +*.pug text eol=lf +*.scss text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af86d0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.git +npm-debug.log +.publish +.vscode +static +dist +node_modules +src/locales/*.json +package-lock.json +yarn.lock +.idea/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0b9c1e --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +WEB TEMPLATE +=================== + +Web template use es6, pug, scss + +## 1. Installation +```bash +npm i +``` + +## 2. Configuration +- Every configuration is stored in `./gulpfile.js/config/**` + +## 3. Constructor +```bash +src/ +| assets/ +| | # All files and folders (except site folder) will be copied to /dist +| | favicon/ +| | # All content in this folder will be copied direct to /dist +| | +| | +| scrips/ +| | _lib/ +| | | # Store custom libraries, +| | | # which cannot be downloaded through npm +| | | +| | | +| | | +| | *.js # These files is the entry file to compile with webpack +| | +| | +| styles/ +| | _*/ +| | | # Every files in folders have start name, is _, +| | | # won't be compiled. +| | | # It only use to store many libraries or variables +| | | +| | | +| | $*/ +| | | # Every files in folders have start name, is $, +| | | # won't be compiled. +| | | # It only use to merge into apps.css +| | | +| | | +| | ... # Others will be compiled to /dist/css/ +| | +| | +| views/ +| _*/ +| | # Every files in folders have start name, is _, +| | # won't be compiled. +| | # It only use to store many reusable components +| | +| | +| $*/ +| | # Every files in folders have start name, is $, +| | # won't be compiled. +| | # It only use to store many main/page components +| | +| | +| ... # Others will be compiled to /dist/ +| +| +gulpfile.js/ +| .... # Manage build tasks and configuration +| +| +server/ +| .... # Manage server side render views +| +| +dist/ +| # Store compiled html, css, js (production build) +| +| +static/ +| # Store compiled css, js (work on dev) +| +| +.browserslistrc +.eslintrc.js +.gitignore +.gitattributes +index.html +package.json +README.md +``` + + +## 4. Tasks +- `npm start`: Shorthand for `npm run dev` +- `npm run dev`: Build Project + Watch + Node Server +- `npm run build`: Build Project (min) +- `npm run serve`: Run production local server + + +## 5. Pug global variables +- `$translator`: object variable get value from `app/locales/#{lang}.json` +- `$localeName`: variable get value equal name of current using locale json + + +## 6. JS Dynamic import path (chunks files) +At `src/views/_layouts/layout.pug`, there is a script with variable `staticJsAssetsPath`, update that variable to server js path, and it will load correctly chunks files + + +## 7. Single language mode +Just remove folder `src/locales` + + +## 8. Site favicon +Use this online tool: https://realfavicongenerator.net/ to get the site favicon package and put it in `app/assets/site` diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000..dc1ebf2 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,13 @@ +{ + "presets": ["@babel/preset-env"], + "plugins": [ + ["@babel/plugin-transform-runtime", { + "helpers": true, + "regenerator": true, + "version": "^7.7.4" + }], + ["@babel/plugin-proposal-decorators", { "legacy": true }], + ["@babel/plugin-proposal-class-properties", { "loose": false }], + "@babel/plugin-proposal-private-methods" + ] +} diff --git a/gulpfile.js/config/browser-sync-prod.js b/gulpfile.js/config/browser-sync-prod.js new file mode 100644 index 0000000..480a1b0 --- /dev/null +++ b/gulpfile.js/config/browser-sync-prod.js @@ -0,0 +1,20 @@ +process.env.NODE_ENV = 'production'; + +const compression = require('compression'); + +const { output } = require('./directories'); +const server = require('./server'); + +module.exports = { + server: output, + port: server.PROD_PORT, + ui: { + port: server.PROD_DASHBOARD_PORT, + }, + open: 'local', + ghostMode: false, + logPrefix: 'SYNC', + middleware: [ + compression(), + ], +}; diff --git a/gulpfile.js/config/browser-sync.js b/gulpfile.js/config/browser-sync.js new file mode 100644 index 0000000..d1a0604 --- /dev/null +++ b/gulpfile.js/config/browser-sync.js @@ -0,0 +1,12 @@ +const server = require('./server'); + +module.exports = { + port: server.DEV_PORT, + proxy: `http://localhost:${server.STATIC_PORT}`, + ui: { + port: server.DASHBOARD_PORT, + }, + open: true, + ghostMode: false, + logPrefix: 'SYNC', +}; diff --git a/gulpfile.js/config/directories.js b/gulpfile.js/config/directories.js new file mode 100644 index 0000000..632c921 --- /dev/null +++ b/gulpfile.js/config/directories.js @@ -0,0 +1,77 @@ +const { addPath } = require('../utils'); + +const isProduction = process.env.NODE_ENV === 'production'; +const ignoreTemplate = ['$*/**/*', '_*/**/*']; + +const nodeModules = 'node_modules/'; +exports.nodeModules = nodeModules; + +const src = 'src/'; +exports.src = src; + +const dest = 'static/'; +const dist = 'dist/'; +const output = isProduction ? dist : dest; +exports.output = output; + +const srcLocales = `${src}locales/`; +exports.srcLocales = srcLocales; + +const srcAsset = `${src}assets/`; +exports.srcAsset = srcAsset; + +const filesCopy = addPath(srcAsset, '**', 'favicon/**'); +const filesFavicon = addPath(srcAsset, 'favicon/**'); +const filesAssets = filesCopy + .concat(filesFavicon) + .concat(isProduction ? 'index.html' : []); +exports.filesAssets = filesAssets; + +const srcScript = `${src}scripts/`; +exports.srcScript = srcScript; + +const filesJs = addPath(srcScript, '**/*.js', ignoreTemplate); +exports.filesJs = filesJs; + +const filesJsES6 = `${srcScript}*.js`; +exports.filesJsES6 = filesJsES6; + +const outputScript = `${output}js/`; +exports.outputScript = outputScript; + +const outputChunkScripts = `${outputScript}chunks/`; +exports.outputChunkScripts = outputChunkScripts; + +const filesChunkJs = addPath(outputChunkScripts, '*.js', '*.backup.js'); +exports.filesChunkJs = filesChunkJs; + +const srcStyle = `${src}styles/`; +exports.srcStyle = srcStyle; + +const filesScssBuilt = addPath( + srcStyle, + '**/*.scss', + ['$*/**/*.scss', '_*/**/*.scss'], +); +exports.filesScssBuilt = filesScssBuilt; + +const filesScssPartial = addPath( + srcStyle, + ['$*/**/*.scss', '_*/**/*.scss'], +); +exports.filesScssPartial = filesScssPartial; + +const outputStyle = `${output}css/`; +exports.outputStyle = outputStyle; + +const filesCssBuilt = addPath(outputStyle, '*.css', '*-rtl.css'); +exports.filesCssBuilt = filesCssBuilt; + +const srcView = `${src}views/`; +exports.srcView = srcView; + +const filesPugBuilt = addPath(srcView, '**/*.pug', ['$*/**/*', '_*/**/*']); +exports.filesPugBuilt = filesPugBuilt; + +const filesPug = addPath(srcView, '**/*.pug'); +exports.filesPug = filesPug; diff --git a/gulpfile.js/config/externals-js.js b/gulpfile.js/config/externals-js.js new file mode 100644 index 0000000..51c10a2 --- /dev/null +++ b/gulpfile.js/config/externals-js.js @@ -0,0 +1,8 @@ +const { srcScript } = require('./directories'); + +const libPath = `${srcScript}_libs/`; + +module.exports = [ + `${libPath}modernizr-custom-3.6.0.js`, + `node_modules/detectizr/dist/detectizr.js`, +]; diff --git a/gulpfile.js/config/nodemon.js b/gulpfile.js/config/nodemon.js new file mode 100644 index 0000000..abeca76 --- /dev/null +++ b/gulpfile.js/config/nodemon.js @@ -0,0 +1,6 @@ +module.exports = { + script: 'server/index.js', + watch: [ + 'server/', + ], +}; diff --git a/gulpfile.js/config/pug.js b/gulpfile.js/config/pug.js new file mode 100644 index 0000000..8ca1f17 --- /dev/null +++ b/gulpfile.js/config/pug.js @@ -0,0 +1,4 @@ +module.exports = { + pretty: true, + doctype: 'html', +}; diff --git a/gulpfile.js/config/rename.js b/gulpfile.js/config/rename.js new file mode 100644 index 0000000..986284b --- /dev/null +++ b/gulpfile.js/config/rename.js @@ -0,0 +1,11 @@ +module.exports = { + rtl: { + suffix: '-rtl', + }, + min: { + suffix: '.min', + }, + backup: { + suffix: '.backup', + }, +}; diff --git a/gulpfile.js/config/sass.js b/gulpfile.js/config/sass.js new file mode 100644 index 0000000..3418c00 --- /dev/null +++ b/gulpfile.js/config/sass.js @@ -0,0 +1,3 @@ +module.exports = { + outputStyle: 'expanded', +}; diff --git a/gulpfile.js/config/server.js b/gulpfile.js/config/server.js new file mode 100644 index 0000000..6e4dbef --- /dev/null +++ b/gulpfile.js/config/server.js @@ -0,0 +1,20 @@ +const PORT = 4999; + +module.exports = { + PROD_PORT: 8080, + STATIC_PORT: PORT, + + get DEV_PORT() { + return this.STATIC_PORT + 1; + }, + + get DASHBOARD_PORT() { + return this.STATIC_PORT - 1; + }, + + get PROD_DASHBOARD_PORT() { + return this.PROD_PORT - 1; + }, + + DEFAULT_LANG: 'en', +}; diff --git a/gulpfile.js/config/usemin.js b/gulpfile.js/config/usemin.js new file mode 100644 index 0000000..36947eb --- /dev/null +++ b/gulpfile.js/config/usemin.js @@ -0,0 +1,9 @@ +// Change this value to false if you don't wanna use cache +const { DEFAULT_LANG } = require('./server'); + +module.exports = { + css: `dist/css/styles.min.css?v=${Date.now()}`, + 'css-rtl': `dist/css/styles-rtl.min.css?v=${Date.now()}`, + js: `dist/js/scripts.min.js?v=${Date.now()}`, + redirect: ``, +}; diff --git a/gulpfile.js/config/webpack.js b/gulpfile.js/config/webpack.js new file mode 100644 index 0000000..9ef92c4 --- /dev/null +++ b/gulpfile.js/config/webpack.js @@ -0,0 +1,51 @@ +/* eslint-disable-next-line */ +const { resolve, join } = require('path'); +const { ProvidePlugin } = require('webpack'); + +const { srcScript, output, outputScript } = require('./directories'); + +const nodeEnv = process.env.NODE_ENV; +const isDevelopment = nodeEnv === 'development'; + +module.exports = { + mode: 'none', + context: join(__dirname, '../../', output), + output: { + path: join(__dirname, '../../', outputScript), + filename: '[name].js', + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + options: { + cacheDirectory: true, + }, + }, + ], + }, + resolve: { + alias: { + '@': resolve(srcScript), + }, + }, + plugins: [ + new ProvidePlugin({ + Plugin: ['@/cores/plugin', 'default'], + jQuery: 'jquery', // replace by using external jquery + $: ['jquery'], + }), + ], + optimization: { + nodeEnv, + chunkIds: 'total-size', + concatenateModules: true, + flagIncludedChunks: true, + moduleIds: 'size', + sideEffects: true, + splitChunks: false, + }, + devtool: isDevelopment && 'source-map', +}; diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js new file mode 100644 index 0000000..81095d6 --- /dev/null +++ b/gulpfile.js/index.js @@ -0,0 +1,11 @@ +const requireDir = require('require-dir'); + +const task = process.argv[2]; + +process.env.NODE_ENV = ~['build'].indexOf(task) ? 'production' : 'development'; + +const tasks = requireDir('./tasks'); + +Object.keys(tasks).forEach((name) => { + exports[name] = tasks[name]; +}); diff --git a/gulpfile.js/private-tasks/backup--chunk-scripts.js b/gulpfile.js/private-tasks/backup--chunk-scripts.js new file mode 100644 index 0000000..1cd9e6e --- /dev/null +++ b/gulpfile.js/private-tasks/backup--chunk-scripts.js @@ -0,0 +1,15 @@ +const { src, dest } = require('gulp'); +const rename = require('gulp-rename'); + +const { filesChunkJs, outputChunkScripts } = require('../config/directories'); +const { backup } = require('../config/rename'); + +function backupChunkScripts() { + return src(filesChunkJs) + .pipe(rename(backup)) + .pipe(dest(outputChunkScripts)); +} + +backupChunkScripts.displayName = 'backup:chunk-scripts'; + +module.exports = backupChunkScripts; diff --git a/gulpfile.js/private-tasks/build--assets.js b/gulpfile.js/private-tasks/build--assets.js new file mode 100644 index 0000000..e7b60b7 --- /dev/null +++ b/gulpfile.js/private-tasks/build--assets.js @@ -0,0 +1,20 @@ +const { series } = require('gulp'); +const buildLocales = require('./build--locales'); +const copyAssets = require('./copy--assets'); +const lintScripts = require('./lint--scripts'); +const buildScriptsES6 = require('./build--scripts-es6'); +const buildScriptsExternal = require('./build--scripts-external'); +const buildStyles = require('./build--styles'); +const buildStylesRtl = require('./build--styles-rtl'); + +const buildAssets = series( + buildLocales, + copyAssets, + lintScripts, + buildScriptsES6, + buildScriptsExternal, + buildStyles, + buildStylesRtl, +); + +module.exports = buildAssets; diff --git a/gulpfile.js/private-tasks/build--locales.js b/gulpfile.js/private-tasks/build--locales.js new file mode 100644 index 0000000..e3c4963 --- /dev/null +++ b/gulpfile.js/private-tasks/build--locales.js @@ -0,0 +1,37 @@ +const { src, dest, parallel } = require('gulp'); +const merge = require('gulp-merge-json'); + +const { srcLocales } = require('../config/directories'); +const { getFolders } = require('../utils'); +const { handleError } = require('../utils/errors'); + +function generateBuildTmpFn(folder) { + function buildLocale() { + return src(`${srcLocales}/${folder}/**/*.json`) + .pipe(merge({ + fileName: `${folder}.json`, + jsonSpace: ' ', + })) + .on('error', handleError) + .pipe(dest(srcLocales)); + } + + buildLocale.displayName = `build:locale:${folder}`; + + return buildLocale; +} + +function buildLocales(cb) { + const folders = getFolders(srcLocales); + + if (folders[0]) { + process.env.MULTI_LANGUAGE = folders; + return parallel(...folders.map(generateBuildTmpFn))(cb); + } + + return cb(); +} + +buildLocales.displayName = 'build:locales'; + +module.exports = buildLocales; diff --git a/gulpfile.js/private-tasks/build--scripts-es6.js b/gulpfile.js/private-tasks/build--scripts-es6.js new file mode 100644 index 0000000..963d5bf --- /dev/null +++ b/gulpfile.js/private-tasks/build--scripts-es6.js @@ -0,0 +1,24 @@ +const { src, dest } = require('gulp'); +const gulpWebpack = require('webpack-stream'); +const webpack = require('webpack'); +const vinylNamed = require('vinyl-named'); + +const { filesJsES6, outputScript } = require('../config/directories'); +const option = require('../config/webpack'); +const { list, handleError } = require('../utils/errors'); + +function buildScriptsES6(cb) { + if (!list.isJSValid) { + return cb(); + } + + return src(filesJsES6) + .pipe(vinylNamed()) + .pipe(gulpWebpack(option, webpack)) + .on('error', handleError) + .pipe(dest(outputScript)); +} + +buildScriptsES6.displayName = 'build:scripts-es6'; + +module.exports = buildScriptsES6; diff --git a/gulpfile.js/private-tasks/build--scripts-external.js b/gulpfile.js/private-tasks/build--scripts-external.js new file mode 100644 index 0000000..3602c4f --- /dev/null +++ b/gulpfile.js/private-tasks/build--scripts-external.js @@ -0,0 +1,22 @@ +const { src, dest } = require('gulp'); +const concat = require('gulp-concat'); + +const jsExternalPaths = require('../config/externals-js'); +const { outputScript } = require('../config/directories'); +const { handleError } = require('../utils/errors'); + +const isDevelopment = process.env.NODE_ENV !== 'production'; + +/** + * Building libraries scripts + */ +function buildScriptsExternal () { + return src(jsExternalPaths, { sourcemaps: isDevelopment }) + .pipe(concat('externals.js')) + .on('error', handleError) + .pipe(dest(outputScript, { sourcemaps: isDevelopment && '.' })); +} + +buildScriptsExternal.displayName = 'build:scripts-external'; + +module.exports = buildScriptsExternal; diff --git a/gulpfile.js/private-tasks/build--styles-rtl.js b/gulpfile.js/private-tasks/build--styles-rtl.js new file mode 100644 index 0000000..e93e6a4 --- /dev/null +++ b/gulpfile.js/private-tasks/build--styles-rtl.js @@ -0,0 +1,27 @@ +const { src, dest, lastRun } = require('gulp'); +const rtlcss = require('gulp-rtlcss'); +const rename = require('gulp-rename'); + +const { + outputStyle, + filesCssBuilt, +} = require('../config/directories'); +const { rtl: renameOpts } = require('../config/rename'); +const browserSync = require('../utils/browser-sync'); +const { handleError } = require('../utils/errors'); + +function buildStylesRtl() { + return src(filesCssBuilt, { + since: lastRun(buildStylesRtl), + }) + .pipe(rtlcss()) + .on('error', handleError) + .pipe(rename(renameOpts)) + .on('error', handleError) + .pipe(dest(outputStyle)) + .pipe(browserSync.stream()); +} + +buildStylesRtl.displayName = 'build:styles-rtl'; + +module.exports = buildStylesRtl; diff --git a/gulpfile.js/private-tasks/build--styles.js b/gulpfile.js/private-tasks/build--styles.js new file mode 100644 index 0000000..9ac8604 --- /dev/null +++ b/gulpfile.js/private-tasks/build--styles.js @@ -0,0 +1,35 @@ +const { src, dest } = require('gulp'); +// const sass = require('sass'); +// const gulpSass = require('gulp-sass'); +const sass = require('gulp-sass')(require('sass')); +const cached = require('gulp-cached'); +const sassUnicode = require('gulp-sass-unicode'); +const autoprefixer = require('gulp-autoprefixer'); +// const header = require('gulp-header'); + +// gulpSass.compiler = sass; + +const { + filesScssBuilt, + outputStyle, +} = require('../config/directories'); +const sassOpts = require('../config/sass'); +const browserSync = require('../utils/browser-sync'); +const { handleError } = require('../utils/errors'); + +function buildStyles() { + return src(filesScssBuilt) + .pipe(cached('scss')) + .pipe(sass(sassOpts)) + .on('error', handleError) + .pipe(sassUnicode()) + .on('error', handleError) + .pipe(autoprefixer()) + .on('error', handleError) + .pipe(dest(outputStyle)) + .pipe(browserSync.stream()); +} + +buildStyles.displayName = 'build:styles'; + +module.exports = buildStyles; diff --git a/gulpfile.js/private-tasks/build--views-min.js b/gulpfile.js/private-tasks/build--views-min.js new file mode 100644 index 0000000..22d3ef8 --- /dev/null +++ b/gulpfile.js/private-tasks/build--views-min.js @@ -0,0 +1,20 @@ +const gulp = require('gulp'); +const htmlReplace = require('gulp-html-replace'); + +const useminOpts = require('../config/usemin'); +const { output } = require('../config/directories'); +const { handleError } = require('../utils/errors'); + +function buildViewsMin() { + return gulp + .src(`${output}**/*.html`) + .pipe(htmlReplace(useminOpts, { + resolvePaths: true, + })) + .on('error', handleError) + .pipe(gulp.dest(output)); +} + +buildViewsMin.displayName = 'build:views-min'; + +module.exports = buildViewsMin; diff --git a/gulpfile.js/private-tasks/build--views.js b/gulpfile.js/private-tasks/build--views.js new file mode 100644 index 0000000..fc992c0 --- /dev/null +++ b/gulpfile.js/private-tasks/build--views.js @@ -0,0 +1,47 @@ +const { src, dest, parallel } = require('gulp'); +const pug = require('gulp-pug'); + +const { srcLocales, filesPugBuilt, output } = require('../config/directories'); +const options = require('../config/pug'); +const { handleError } = require('../utils/errors'); + +function generateBuildTmpFn(lang) { + let outputPath = output; + const pugOpts = { + ...options, + locals: { + $translator: {}, + $localeName: lang, + }, + }; + + if (lang) { + outputPath = `${output + lang}/`; + /* eslint-disable-next-line */ + pugOpts.locals.$translator = require(`../../${srcLocales + lang}.json`); + } + + function buildView() { + return src(filesPugBuilt) + .pipe(pug(pugOpts)) + .on('error', handleError) + .pipe(dest(outputPath)); + } + + buildView.displayName = `build:views:${lang || 'single-lang'}`; + return buildView; +} + +function buildViews(cb) { + const folders = process.env.MULTI_LANGUAGE; + + if (folders) { + return parallel(...folders.split(',').map(generateBuildTmpFn))(cb); + } + + return generateBuildTmpFn()(); +} + +buildViews.displayName = 'build:views'; + +module.exports = buildViews; diff --git a/gulpfile.js/private-tasks/bundle--scripts.js b/gulpfile.js/private-tasks/bundle--scripts.js new file mode 100644 index 0000000..87dcba0 --- /dev/null +++ b/gulpfile.js/private-tasks/bundle--scripts.js @@ -0,0 +1,27 @@ +const { src, dest } = require('gulp'); +const concat = require('gulp-concat'); +const rename = require('gulp-rename'); +const uglify = require('gulp-uglify'); + +const { outputScript } = require('../config/directories'); +const { handleError } = require('../utils/errors'); +const { min } = require('../config/rename'); + +function bundleScripts() { + return src([`${outputScript}externals.js`, `${outputScript}*.js`]) + .pipe(concat('scripts.js')) + .on('error', handleError) + .pipe(dest(outputScript)) + .pipe(rename(min)) + .pipe(uglify({ + mangle: { + keep_fnames: true, + }, + })) + .on('error', handleError) + .pipe(dest(outputScript)); +} + +bundleScripts.displayName = 'bundle:scripts'; + +module.exports = bundleScripts; diff --git a/gulpfile.js/private-tasks/bundle--styles.js b/gulpfile.js/private-tasks/bundle--styles.js new file mode 100644 index 0000000..cbe976f --- /dev/null +++ b/gulpfile.js/private-tasks/bundle--styles.js @@ -0,0 +1,25 @@ +const { src, dest } = require('gulp'); +const concat = require('gulp-concat'); + +const { outputStyle } = require('../config/directories'); +const { handleError } = require('../utils/errors'); + +function bundleStyles(isRTL) { + const suffix = isRTL ? '-rtl' : ''; + const libsCss = `${outputStyle}$libs${suffix}.css`; + const appsCss = `${outputStyle}apps${suffix}.css`; + const concatCss = `styles${suffix}.css`; + + function bundleStyle() { + return src([libsCss, appsCss]) + .pipe(concat(concatCss)) + .on('error', handleError) + .pipe(dest(outputStyle)); + } + + bundleStyle.displayName = `bundle:styles:${isRTL ? 'rtl' : 'origin'}`; + + return bundleStyle; +} + +module.exports = bundleStyles; diff --git a/gulpfile.js/private-tasks/clean--output.js b/gulpfile.js/private-tasks/clean--output.js new file mode 100644 index 0000000..e375ced --- /dev/null +++ b/gulpfile.js/private-tasks/clean--output.js @@ -0,0 +1,9 @@ +const del = require('del'); + +const { output } = require('../config/directories'); + +const cleanOutput = () => del(output); + +cleanOutput.displayName = 'clean:output'; + +module.exports = cleanOutput; diff --git a/gulpfile.js/private-tasks/clean--temp-scripts.js b/gulpfile.js/private-tasks/clean--temp-scripts.js new file mode 100644 index 0000000..930d79b --- /dev/null +++ b/gulpfile.js/private-tasks/clean--temp-scripts.js @@ -0,0 +1,14 @@ +const del = require('del'); + +const { outputScript } = require('../config/directories'); + +const cleanTempScripts = () => del([ + `${outputScript}*.js`, + `${outputScript}*.map`, + `!${outputScript}scripts.js`, + `!${outputScript}scripts.min.js`, +]); + +cleanTempScripts.displayName = 'clean:temp-scripts'; + +module.exports = cleanTempScripts; diff --git a/gulpfile.js/private-tasks/clean--temp-styles.js b/gulpfile.js/private-tasks/clean--temp-styles.js new file mode 100644 index 0000000..5d4976b --- /dev/null +++ b/gulpfile.js/private-tasks/clean--temp-styles.js @@ -0,0 +1,14 @@ +const del = require('del'); + +const { outputStyle } = require('../config/directories'); + +const cleanTempStyles = () => del([ + `${outputStyle}$libs.css`, + `${outputStyle}$libs-rtl.css`, + `${outputStyle}apps.css`, + `${outputStyle}apps-rtl.css`, +]); + +cleanTempStyles.displayName = 'clean:temp-styles'; + +module.exports = cleanTempStyles; diff --git a/gulpfile.js/private-tasks/copy--assets.js b/gulpfile.js/private-tasks/copy--assets.js new file mode 100644 index 0000000..de59add --- /dev/null +++ b/gulpfile.js/private-tasks/copy--assets.js @@ -0,0 +1,12 @@ +const { src, dest, lastRun } = require('gulp'); +const { filesAssets, output } = require('../config/directories'); + +function copyAssets() { + return src(filesAssets, { + since: lastRun(copyAssets), + }).pipe(dest(output)); +} + +copyAssets.displayName = 'copy:assets'; + +module.exports = copyAssets; diff --git a/gulpfile.js/private-tasks/lint--scripts.js b/gulpfile.js/private-tasks/lint--scripts.js new file mode 100644 index 0000000..6095589 --- /dev/null +++ b/gulpfile.js/private-tasks/lint--scripts.js @@ -0,0 +1,26 @@ +const { src } = require('gulp'); +const eslint = require('gulp-eslint'); +const cached = require('gulp-cached'); + +const { filesJs } = require('../config/directories'); +const { handleESLintError } = require('../utils/errors'); +const stream = require('../utils/browser-sync'); + +function lintScripts() { + const gulpInstance = src(filesJs) + .pipe(cached('eslint')) + .pipe(eslint()) + .pipe(eslint.results(handleESLintError)); + + if (stream.isStreaming) { + return gulpInstance; + } + + return gulpInstance + .pipe(eslint.format()) + .pipe(eslint.failOnError()); +} + +lintScripts.displayName = 'lint:scripts'; + +module.exports = lintScripts; diff --git a/gulpfile.js/private-tasks/minify--chunk-scripts.js b/gulpfile.js/private-tasks/minify--chunk-scripts.js new file mode 100644 index 0000000..8bcbf01 --- /dev/null +++ b/gulpfile.js/private-tasks/minify--chunk-scripts.js @@ -0,0 +1,20 @@ +const { src, dest } = require('gulp'); +const uglify = require('gulp-uglify'); + +const { filesChunkJs, outputChunkScripts } = require('../config/directories'); +const { handleError } = require('../utils/errors'); + +function minifyChunkScripts() { + return src(filesChunkJs) + .pipe(uglify({ + mangle: { + keep_fnames: true, + }, + })) + .on('error', handleError) + .pipe(dest(outputChunkScripts)); +} + +minifyChunkScripts.displayName = 'minify:chunk-scripts'; + +module.exports = minifyChunkScripts; diff --git a/gulpfile.js/private-tasks/minify--styles.js b/gulpfile.js/private-tasks/minify--styles.js new file mode 100644 index 0000000..5d89fe6 --- /dev/null +++ b/gulpfile.js/private-tasks/minify--styles.js @@ -0,0 +1,19 @@ +const { src, dest } = require('gulp'); +const rename = require('gulp-rename'); +const cleanCss = require('gulp-clean-css'); + +const { outputStyle } = require('../config/directories'); +const { min } = require('../config/rename'); +const { handleError } = require('../utils/errors'); + +function minifyStyles() { + return src(`${outputStyle}**/*.css`) + .pipe(rename(min)) + .pipe(cleanCss()) + .on('error', handleError) + .pipe(dest(outputStyle)); +} + +minifyStyles.displayName = 'minify:styles'; + +module.exports = minifyStyles; diff --git a/gulpfile.js/private-tasks/print--results.js b/gulpfile.js/private-tasks/print--results.js new file mode 100644 index 0000000..4bfe960 --- /dev/null +++ b/gulpfile.js/private-tasks/print--results.js @@ -0,0 +1,64 @@ +const notify = require('gulp-notify'); +const stripIndent = require('strip-indent'); + +const { pluralText } = require('../utils'); +const errors = require('../utils/errors'); + +const { log } = console; + +notify.logLevel(0); + +function printResults(cb) { + const errList = errors.list; + const { + totalError, totalWarning, totalIssue, data, + } = errList; + const errorString = `${totalError} ${pluralText('error', totalError)}`; + const warningString = `${totalWarning} ${pluralText('warning', totalWarning)}`; + const resultString = `The project has ${errorString} & ${warningString}`; + const resultStr = resultString; // Improve Performance Node + const dashChar = ''.padEnd(resultStr.length + 4, '='); + + log(stripIndent(` + ${dashChar} + + ${resultStr} + + ${dashChar} + `)); + + if (!totalIssue) { + return cb(); + } + + const infoLogs = stripIndent(data.map(({ + message, code, title, type, + }, i) => { + if (type !== 'Warning') { + notify.onError({ + title, + message, + })(); + } + + return ` + ---[ ${type} ${i + 1} ]------------------------- + | Path : ${message} + | ${code} + `; + }).join(` + `)); + + log((` + ${infoLogs} + ${dashChar} + `).replace(/^ {2}/gm, '')); + + errors.resetError(); + + return cb(); +} + +printResults.displayName = 'print:results'; + +module.exports = printResults; diff --git a/gulpfile.js/private-tasks/reload.js b/gulpfile.js/private-tasks/reload.js new file mode 100644 index 0000000..a45c895 --- /dev/null +++ b/gulpfile.js/private-tasks/reload.js @@ -0,0 +1,10 @@ +const browserSync = require('../utils/browser-sync'); + +function reload(cb) { + browserSync.reload(); + cb(); +} + +reload.displayName = 'reload'; + +module.exports = reload; diff --git a/gulpfile.js/private-tasks/run--dev-server.js b/gulpfile.js/private-tasks/run--dev-server.js new file mode 100644 index 0000000..130d23a --- /dev/null +++ b/gulpfile.js/private-tasks/run--dev-server.js @@ -0,0 +1,8 @@ +const { parallel } = require('gulp'); + +const runViews = require('./run--views'); +const runWatchers = require('./run--watchers'); + +const runDevServer = parallel(runViews, runWatchers); + +module.exports = runDevServer; diff --git a/gulpfile.js/private-tasks/run--views.js b/gulpfile.js/private-tasks/run--views.js new file mode 100644 index 0000000..4465ab6 --- /dev/null +++ b/gulpfile.js/private-tasks/run--views.js @@ -0,0 +1,35 @@ +const nodemon = require('gulp-nodemon'); + +const browserSync = require('../utils/browser-sync'); +const options = require('../config/nodemon'); +const browserSyncOpts = require('../config/browser-sync'); + +function runViews(cb) { + let started = false; + + nodemon({ + ...options, + env: { + MULTI_LANGUAGE: process.env.MULTI_LANGUAGE, + }, + }) + .on('start', () => { + if (started) { + cb(); + return; + } + + started = true; + + setTimeout(() => { + browserSync.isStreaming = true; + browserSync.init(browserSyncOpts); + + cb(); + }, 1000); + }); +} + +runViews.displayName = 'run:views'; + +module.exports = runViews; diff --git a/gulpfile.js/private-tasks/run--watchers.js b/gulpfile.js/private-tasks/run--watchers.js new file mode 100644 index 0000000..e827339 --- /dev/null +++ b/gulpfile.js/private-tasks/run--watchers.js @@ -0,0 +1,71 @@ +const { series, parallel, watch } = require('gulp'); +const cached = require('gulp-cached'); + +const reload = require('./reload'); +const buildLocales = require('./build--locales'); +const printResults = require('./print--results'); +const buildStyles = require('./build--styles'); +const buildScriptsExternal = require('./build--scripts-external'); +const lintScripts = require('./lint--scripts'); +const buildScriptsES6 = require('./build--scripts-es6'); +const copyAssets = require('./copy--assets'); + +const { + filesPug, + srcLocales, + filesScssBuilt, + filesScssPartial, + filesJs, + filesAssets, +} = require('../config/directories'); +const jsExternalPaths = require('../config/externals-js'); + +function runWatchers(cb) { + const reloadAndShowResults = parallel(printResults, reload); + + // HTML -------------------- + watch(filesPug, reload); + + // LOCALE -------------------- + watch(`${srcLocales}*/*.json`, series( + buildLocales, + reloadAndShowResults, + )); + + // CSS -------------------- + watch(filesScssBuilt, series( + buildStyles, + printResults, + )); + + watch(filesScssPartial, series( + buildStyles, + printResults, + )).on('change', () => delete cached.caches.scss); + + // JS -------------------- + watch(jsExternalPaths, series( + buildScriptsExternal, + reloadAndShowResults + )); + + watch(filesJs, series( + lintScripts, + buildScriptsES6, + reloadAndShowResults, + )); + + // ASSETS -------------------- + watch(filesAssets, series( + copyAssets, + reloadAndShowResults, + )); + + if (typeof cb === 'function') { + cb(); + } +} + +runWatchers.displayName = 'run:watchers'; + +module.exports = runWatchers; diff --git a/gulpfile.js/private-tasks/versionfy.js b/gulpfile.js/private-tasks/versionfy.js new file mode 100644 index 0000000..3e03a63 --- /dev/null +++ b/gulpfile.js/private-tasks/versionfy.js @@ -0,0 +1,15 @@ +const { src, dest } = require('gulp'); +const header = require('gulp-header'); +const dayjs = require('dayjs'); + +const { outputScript, outputStyle } = require('../config/directories'); + +function versionfy() { + return src([`${outputScript}/**/*.js`, `${outputStyle}/**/*.css`]) + .pipe(header(`/* version: ${dayjs().format('DD-MM-YYYY HH:mm:ss')} */`)) + .pipe(dest((file) => file.base)); +} + +versionfy.displayName = 'versionfy'; + +module.exports = versionfy; diff --git a/gulpfile.js/tasks/build.js b/gulpfile.js/tasks/build.js new file mode 100644 index 0000000..e3b07d9 --- /dev/null +++ b/gulpfile.js/tasks/build.js @@ -0,0 +1,34 @@ +const { series } = require('gulp'); + +const cleanOutput = require('../private-tasks/clean--output'); +const buildAssets = require('../private-tasks/build--assets'); +const bundleScripts = require('../private-tasks/bundle--scripts'); +const cleanTempScripts = require('../private-tasks/clean--temp-scripts'); +const backupChunkScripts = require('../private-tasks/backup--chunk-scripts'); +const minifyChunkScripts = require('../private-tasks/minify--chunk-scripts'); +const bundleStyles = require('../private-tasks/bundle--styles'); +const cleanTempStyles = require('../private-tasks/clean--temp-styles'); +const minifyStyles = require('../private-tasks/minify--styles'); +const versionfy = require('../private-tasks/versionfy'); +const buildViews = require('../private-tasks/build--views'); +const buildViewsMin = require('../private-tasks/build--views-min'); +const printResults = require('../private-tasks/print--results'); + +const tasks = [ + cleanOutput, + buildAssets, + bundleScripts, + cleanTempScripts, + backupChunkScripts, + minifyChunkScripts, + bundleStyles(), + bundleStyles(true), + cleanTempStyles, + minifyStyles, + versionfy, + buildViews, + buildViewsMin, + printResults, +]; + +module.exports = series(...tasks); diff --git a/gulpfile.js/tasks/default.js b/gulpfile.js/tasks/default.js new file mode 100644 index 0000000..d9b6883 --- /dev/null +++ b/gulpfile.js/tasks/default.js @@ -0,0 +1,15 @@ +const { series } = require('gulp'); + +const cleanOutput = require('../private-tasks/clean--output'); +const buildAssets = require('../private-tasks/build--assets'); +const printResults = require('../private-tasks/print--results'); +const runDevServer = require('../private-tasks/run--dev-server'); + +const tasks = [ + cleanOutput, + buildAssets, + printResults, + runDevServer, +]; + +module.exports = series(...tasks); diff --git a/gulpfile.js/utils/browser-sync.js b/gulpfile.js/utils/browser-sync.js new file mode 100644 index 0000000..3c6c5bb --- /dev/null +++ b/gulpfile.js/utils/browser-sync.js @@ -0,0 +1,7 @@ +const browserSync = require('browser-sync'); + +const browserSyncInstance = browserSync.create(); + +browserSyncInstance.isStreaming = false; + +module.exports = browserSyncInstance; diff --git a/gulpfile.js/utils/errors.js b/gulpfile.js/utils/errors.js new file mode 100644 index 0000000..4951bfc --- /dev/null +++ b/gulpfile.js/utils/errors.js @@ -0,0 +1,63 @@ +const cached = require('gulp-cached'); + +const list = { + get totalIssue() { + return this.totalError + this.totalWarning; + }, +}; +exports.list = list; + +function pushError(info) { + list.data.push(info); + list[info.type === 'Error' ? 'totalError' : 'totalWarning'] += 1; +} + +function handleError({ plugin, message, codeFrame = '' } = {}) { + pushError({ + type: 'Error', + plugin: plugin.toUpperCase(), + message: message.trim(), + code: codeFrame, + }); + + if (typeof this.emit === 'function') { + this.emit('end'); + } else if (typeof this === 'function') { + this(); + } +} + +exports.handleError = handleError; + +exports.handleESLintError = (results) => { + results.forEach(({ filePath, messages } = {}) => { + messages.forEach(({ + severity, line, column, message, + } = {}) => { + const type = severity === 2 ? 'Error' : 'Warning'; + + pushError({ + type, + plugin: `ES Lint ${type}`, + message: filePath, + code: `[${line}:${column}] ${message}`, + }); + }); + }); + + list.isJSValid = !results.errorCount; + + if (results.errorCount || results.warningCount) { + delete cached.caches.eslint; + } +}; + +function resetError() { + list.data = []; + list.totalError = 0; + list.totalWarning = 0; + list.isJSValid = true; +} +exports.resetError = resetError; + +resetError(); diff --git a/gulpfile.js/utils/index.js b/gulpfile.js/utils/index.js new file mode 100644 index 0000000..6109088 --- /dev/null +++ b/gulpfile.js/utils/index.js @@ -0,0 +1,46 @@ +const { readdirSync, lstatSync } = require('fs'); + +const pushPath = (src, paths, wontInclude) => { + const not = wontInclude ? '!' : ''; + + return [].concat(paths || []).map((item) => not + src + item); +}; + +exports.addPath = (src, paths, notIncludePaths) => { + if (typeof src === 'undefined') { + return '**/*'; + } + + if (typeof paths === 'undefined') { + return `${src}**/*`; + } + + const includePaths = pushPath(src, paths, false); + const notICPath = pushPath(src, notIncludePaths, true); + + return includePaths.concat(notICPath); +}; + +exports.getFolders = (dir) => { + try { + return readdirSync(dir) + .filter((folder) => lstatSync(dir + folder).isDirectory()); + } catch (err) { + return []; + } +}; + +exports.pluralText = (text, number) => text + (number > 1 ? 's' : ''); + +exports.renderErrorHTML = (msg) => ` + + +
${msg}
+ +`; diff --git a/index.html b/index.html new file mode 100644 index 0000000..2544814 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ + diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..3d611c9 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "baseUrl": "./", + "paths": { + "@/*": ["./src/scripts/*"] + } + }, + "exclude": ["node_modules"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d700b6e --- /dev/null +++ b/package.json @@ -0,0 +1,96 @@ +{ + "name": "web-template", + "title": "Web Template", + "description": "Web template use es6, pug, scss, bootstrap", + "version": "4.0.0", + "author": "", + "homepage": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "" + }, + "keywords": [ + "web", + "template", + "es6", + "pug", + "scss", + "bootstrap" + ], + "engines": { + "node": ">=10", + "npm": ">=7" + }, + "scripts": { + "list-tasks": "gulp --tasks", + "start": "gulp", + "dev": "gulp", + "build": "gulp build", + "serve": "browser-sync start -c 'gulpfile.js/config/browser-sync-prod.js'", + "upload": "gulp build && gh-pages -d dist" + }, + "dependencies": { + "@babel/runtime": "7.13.17", + "bootstrap": "^5.1.3", + "bootstrap-datepicker": "^1.9.0", + "bootstrap-select": "^1.13.18", + "dayjs": "1.10.4", + "debounce": "^1.2.1", + "detectizr": "^2.2.0", + "html-to-image": "^1.9.0", + "jquery": "^3.6.0", + "jquery-mask-plugin": "^1.14.16", + "jquery-validation": "^1.19.3", + "moment": "^2.29.1", + "parsleyjs": "^2.9.2", + "slick-carousel": "^1.8.1" + }, + "devDependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-pipeline-operator": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.6.2", + "@babel/preset-env": "^7.16.4", + "@babel/runtime": "^7.6.2", + "babel-eslint": "^10.0.3", + "babel-loader": "^8.2.3", + "browser-sync": "^2.27.7", + "compression": "^1.7.4", + "del": "^6.0.0", + "eslint-config-airbnb-base": "14.2.1", + "eslint-import-resolver-webpack": "0.13.0", + "eslint-plugin-import": "2.22.1", + "express": "4.17.1", + "gh-pages": "^3.2.3", + "gulp": "^4.0.2", + "gulp-autoprefixer": "^6.1.0", + "gulp-cached": "1.1.1", + "gulp-clean-css": "4.3.0", + "gulp-cli": "2.3.0", + "gulp-concat": "2.6.1", + "gulp-eslint": "6.0.0", + "gulp-header": "2.0.9", + "gulp-html-replace": "1.6.2", + "gulp-merge-json": "2.1.1", + "gulp-nodemon": "^2.2.1", + "gulp-notify": "^4.0.0", + "gulp-pug": "^5.0.0", + "gulp-rename": "2.0.0", + "gulp-rtlcss": "^2.0.0", + "gulp-sass": "^5.1.0", + "gulp-sass-unicode": "^1.0.2", + "gulp-uglify": "3.0.2", + "pug": "3.0.2", + "require-dir": "1.2.0", + "sass": "^1.51.0", + "strip-indent": "3.0.0", + "vinyl-named": "1.1.0", + "webpack": "^5.65.0", + "webpack-stream": "6.1.2" + } +} diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..563c686 --- /dev/null +++ b/server/index.js @@ -0,0 +1,35 @@ +const express = require('express'); +const pug = require('pug'); + +const route = require('./route'); +const { STATIC_PORT } = require('../gulpfile.js/config/server'); +const { srcView, output } = require('../gulpfile.js/config/directories'); +const pugOptions = require('../gulpfile.js/config/pug'); +const { renderErrorHTML } = require('../gulpfile.js/utils'); + +const app = express(); +app.locals.moment = require('moment'); +const { log } = console; + +app.engine('pug', (path, options, callback) => { + const opts = { ...pugOptions, ...options }; + + pug.renderFile(path, opts, (err, result) => { + const data = result || renderErrorHTML(err.message); + + callback(null, data); + }); +}); + +app.set('views', srcView); +app.set('view engine', 'pug'); +app.use(express.static(output)); +app.use(express.json()); +app.use(express.urlencoded({ + extended: true, +})); +app.use('/', route); + +const logPort = `----- View server is running at http://localhost:${STATIC_PORT} -----`; + +app.listen(STATIC_PORT, () => log(logPort)); diff --git a/server/route.js b/server/route.js new file mode 100644 index 0000000..c6487fd --- /dev/null +++ b/server/route.js @@ -0,0 +1,83 @@ +/* eslint-disable-next-line */ +const { join } = require('path'); +const express = require('express'); + +const router = express.Router(); +const { output, srcLocales } = require('../gulpfile.js/config/directories'); +const { renderErrorHTML } = require('../gulpfile.js/utils'); +const { DEFAULT_LANG } = require('../gulpfile.js/config/server'); + +const multiLang = process.env.MULTI_LANGUAGE; + +function forceRequire(path) { + const realPath = join(__dirname, path); + + delete require.cache[realPath]; + + /* eslint-disable-next-line */ + return require(path); +} + +router.get('/', (_, res) => { + const defautLangPath = multiLang ? `/${DEFAULT_LANG}` : ''; + + res.redirect(`${defautLangPath}/index.html`); +}); + +router.get('/*.html', (req, res) => { + try { + let lang; + let match; + let localeLang; + let { path: url } = req; + + if (multiLang) { + const testLang = /^\/([^/]+)\//.exec(url); + + if (!testLang) { + throw new Error('No language in the url'); + } + + [match, lang] = testLang; + localeLang = forceRequire(`../${srcLocales + lang}.json`); + + url = url.replace(match, ''); + } + + const testFile = /[/]?(.+)\.html/.exec(url); + + if (!testFile) { + throw new Error('Not found'); + } + + res.render(testFile[1], { + $translator: localeLang || {}, + $localeName: lang, + $path: req.url, + }, (err, html) => { + if (err) { + throw err; + } + + res.send(html); + }); + } catch (err) { + res.send(renderErrorHTML(err)).status(404); + } +}); + +router.get(/^\/.*[^(.html)]$/, (req, res) => { + res.redirect(join(req.path, 'index.html')); +}); + +router.post('*', (req, res) => { + try { + const json = forceRequire(join(__dirname, '..', output, req.url)); + + res.send(json); + } catch (err) { + res.send(renderErrorHTML(err)).status(404); + } +}); + +module.exports = router; diff --git a/src/assets/data/.gitkeep b/src/assets/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/assets/favicon/android-chrome-192x192.png b/src/assets/favicon/android-chrome-192x192.png new file mode 100644 index 0000000..71ed165 Binary files /dev/null and b/src/assets/favicon/android-chrome-192x192.png differ diff --git a/src/assets/favicon/android-chrome-512x512.png b/src/assets/favicon/android-chrome-512x512.png new file mode 100644 index 0000000..f1e1d3e Binary files /dev/null and b/src/assets/favicon/android-chrome-512x512.png differ diff --git a/src/assets/favicon/apple-touch-icon.png b/src/assets/favicon/apple-touch-icon.png new file mode 100644 index 0000000..a9bd9bd Binary files /dev/null and b/src/assets/favicon/apple-touch-icon.png differ diff --git a/src/assets/favicon/browserconfig.xml b/src/assets/favicon/browserconfig.xml new file mode 100644 index 0000000..249c5c1 --- /dev/null +++ b/src/assets/favicon/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #ffc40d + + + diff --git a/src/assets/favicon/favicon-16x16.png b/src/assets/favicon/favicon-16x16.png new file mode 100644 index 0000000..c5a2c6d Binary files /dev/null and b/src/assets/favicon/favicon-16x16.png differ diff --git a/src/assets/favicon/favicon-32x32.png b/src/assets/favicon/favicon-32x32.png new file mode 100644 index 0000000..cbf52f0 Binary files /dev/null and b/src/assets/favicon/favicon-32x32.png differ diff --git a/src/assets/favicon/favicon.ico b/src/assets/favicon/favicon.ico new file mode 100644 index 0000000..8b3e01a Binary files /dev/null and b/src/assets/favicon/favicon.ico differ diff --git a/src/assets/favicon/mstile-150x150.png b/src/assets/favicon/mstile-150x150.png new file mode 100644 index 0000000..55d6af8 Binary files /dev/null and b/src/assets/favicon/mstile-150x150.png differ diff --git a/src/assets/favicon/safari-pinned-tab.svg b/src/assets/favicon/safari-pinned-tab.svg new file mode 100644 index 0000000..64bd4ba --- /dev/null +++ b/src/assets/favicon/safari-pinned-tab.svg @@ -0,0 +1,53 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + + + diff --git a/src/assets/favicon/site.webmanifest b/src/assets/favicon/site.webmanifest new file mode 100644 index 0000000..b20abb7 --- /dev/null +++ b/src/assets/favicon/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/src/assets/fonts/.gitkeep b/src/assets/fonts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/assets/fonts/icomoon/icomoon.eot b/src/assets/fonts/icomoon/icomoon.eot new file mode 100644 index 0000000..da45244 Binary files /dev/null and b/src/assets/fonts/icomoon/icomoon.eot differ diff --git a/src/assets/fonts/icomoon/icomoon.svg b/src/assets/fonts/icomoon/icomoon.svg new file mode 100644 index 0000000..6635d1c --- /dev/null +++ b/src/assets/fonts/icomoon/icomoon.svg @@ -0,0 +1,12 @@ + + + +Generated by IcoMoon + + + + + + + + \ No newline at end of file diff --git a/src/assets/fonts/icomoon/icomoon.ttf b/src/assets/fonts/icomoon/icomoon.ttf new file mode 100644 index 0000000..418739e Binary files /dev/null and b/src/assets/fonts/icomoon/icomoon.ttf differ diff --git a/src/assets/fonts/icomoon/icomoon.woff b/src/assets/fonts/icomoon/icomoon.woff new file mode 100644 index 0000000..94be51a Binary files /dev/null and b/src/assets/fonts/icomoon/icomoon.woff differ diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000..4639b3d Binary files /dev/null and b/src/assets/images/logo.png differ diff --git a/src/assets/images/transparent.png b/src/assets/images/transparent.png new file mode 100644 index 0000000..909c66d Binary files /dev/null and b/src/assets/images/transparent.png differ diff --git a/src/scripts/_libs/.gitkeep b/src/scripts/_libs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/scripts/_libs/jquery.fullPage.min.js b/src/scripts/_libs/jquery.fullPage.min.js new file mode 100644 index 0000000..1484139 --- /dev/null +++ b/src/scripts/_libs/jquery.fullPage.min.js @@ -0,0 +1,55 @@ +/** + * fullPage 2.6.6 + * https://github.com/alvarotrigo/fullPage.js + * MIT licensed + * + * Copyright (C) 2015 alvarotrigo.com - A project by Alvaro Trigo + */ +(function(c,l){"function"===typeof define&&define.amd?define(["jquery"],function(k){return l(k,c,c.document,c.Math)}):"undefined"!==typeof exports?module.exports=l(require("jquery"),c,c.document,c.Math):l(jQuery,c,c.document,c.Math)})("undefined"!==typeof window?window:this,function(c,l,k,n,H){var m=c(l),r=c(k);c.fn.fullpage=function(d){function Ia(a){a.find(".fp-slides").after('
');"#fff"!=d.controlArrowColor&&(a.find(".fp-controlArrow.fp-next").css("border-color", +"transparent transparent transparent "+d.controlArrowColor),a.find(".fp-controlArrow.fp-prev").css("border-color","transparent "+d.controlArrowColor+" transparent transparent"));d.loopHorizontal||a.find(".fp-controlArrow.fp-prev").hide()}function Ja(){p.append('
');z=c("#fp-nav");z.addClass(function(){return d.showActiveTooltip?"fp-show-active "+d.navigationPosition:d.navigationPosition});for(var a=0;a',g=d.navigationTooltips[a];"undefined"!==typeof g&&""!==g&&(b+='
'+g+"
");b+="";z.find("ul").append(b)}}function da(){c(".fp-section").each(function(){var a=c(this).find(".fp-slide");a.length?a.each(function(){I(c(this))}):I(c(this))});ea()}function ea(){var a=c(".fp-section.active"),b=a.find("SLIDES_WRAPPER"),g=a.find(".fp-scrollable");b.length&&(g=b.find(".fp-slide.active"));g.mouseover();fa(a); +c.isFunction(d.afterLoad)&&d.afterLoad.call(a,a.data("anchor"),a.index(".fp-section")+1);c.isFunction(d.afterRender)&&d.afterRender.call(this)}function ga(){var a;if(!d.autoScrolling||d.scrollBar){for(var b=m.scrollTop(),g=0,J=n.abs(b-k.querySelectorAll(".fp-section")[0].offsetTop),e=k.querySelectorAll(".fp-section"),f=0;f=b[0].scrollHeight:void 0,d)c();else return!0;else c()}}function Ka(a){var b=a.originalEvent;if(!ka(a.target)&&X(b)){d.autoScrolling&&a.preventDefault();a=c(".fp-section.active");var g= +ja(a);u&&!w&&(b=la(b),D=b.y,M=b.x,a.find(".fp-slides").length&&n.abs(N-M)>n.abs(E-D)?n.abs(N-M)>m.width()/100*d.touchSensitivity&&(N>M?h.m.right&&e.moveSlideRight():h.m.left&&e.moveSlideLeft()):d.autoScrolling&&n.abs(E-D)>m.height()/100*d.touchSensitivity&&(E>D?L("down",g):D>E&&L("up",g)))}}function ka(a,b){b=b||0;var g=c(a).parent();return b=g&&(0>e?L("down",a):L("up",a)));return!1}d.fitToSection&&x.stop()}function oa(a){var b=c(".fp-section.active").find(".fp-slides"),g=b.find(".fp-slide").length;if(!(!b.length||w||2>g)){var g=b.find(".fp-slide.active"),e=null,e="prev"===a?g.prev(".fp-slide"):g.next(".fp-slide");if(!e.length){if(!d.loopHorizontal)return;e="prev"===a?g.siblings(":last"):g.siblings(":first")}w=!0;F(b,e)}}function pa(){c(".fp-slide.active").each(function(){Y(c(this), +"internal")})}function B(a,b,g){var e=a.position();if("undefined"!==typeof e&&(b={element:a,callback:b,isMovementUp:g,dest:e,dtop:e.top,yMovement:V(a),anchorLink:a.data("anchor"),sectionIndex:a.index(".fp-section"),activeSlide:a.find(".fp-slide.active"),activeSection:c(".fp-section.active"),leavingSection:c(".fp-section.active").index(".fp-section")+1,localIsResizing:v},!(b.activeSection.is(a)&&!v||d.scrollBar&&m.scrollTop()===b.dtop))){if(b.activeSlide.length)var f=b.activeSlide.data("anchor"),h= +b.activeSlide.index();d.autoScrolling&&d.continuousVertical&&"undefined"!==typeof b.isMovementUp&&(!b.isMovementUp&&"up"==b.yMovement||b.isMovementUp&&"down"==b.yMovement)&&(b.isMovementUp?c(".fp-section.active").before(b.activeSection.nextAll(".fp-section")):c(".fp-section.active").after(b.activeSection.prevAll(".fp-section").get().reverse()),y(c(".fp-section.active").position().top),pa(),b.wrapAroundElements=b.activeSection,b.dest=b.element.position(),b.dtop=b.dest.top,b.yMovement=V(b.element)); +if(c.isFunction(d.onLeave)&&!b.localIsResizing){if(!1===d.onLeave.call(b.activeSection,b.leavingSection,b.sectionIndex+1,b.yMovement))return;Ma(b.activeSection)}a.addClass("active").siblings().removeClass("active");u=!1;W(h,f,b.anchorLink,b.sectionIndex);Na(b);A=b.anchorLink;K(b.anchorLink,b.sectionIndex)}}function Na(a){if(d.css3&&d.autoScrolling&&!d.scrollBar)qa("translate3d(0px, -"+a.dtop+"px, 0px)",!0),d.scrollingSpeed?setTimeout(function(){Z(a)},d.scrollingSpeed):Z(a);else{var b=Oa(a);c(b.element).animate(b.options, +d.scrollingSpeed,d.easing).promise().done(function(){Z(a)})}}function Oa(a){var b={};d.autoScrolling&&!d.scrollBar?(b.options={top:-a.dtop},b.element=".fullpage-wrapper"):(b.options={scrollTop:a.dtop},b.element="html, body");return b}function Z(a){a.wrapAroundElements&&a.wrapAroundElements.length&&(a.isMovementUp?c(".fp-section:first").before(a.wrapAroundElements):c(".fp-section:last").after(a.wrapAroundElements),y(c(".fp-section.active").position().top),pa());a.element.find(".fp-scrollable").mouseover(); +c.isFunction(d.afterLoad)&&!a.localIsResizing&&d.afterLoad.call(a.element,a.anchorLink,a.sectionIndex+1);fa(a.element);Pa(a.element);u=!0;c.isFunction(a.callback)&&a.callback.call(this)}function fa(a){a.find("img[data-src], video[data-src], audio[data-src]").each(function(){c(this).attr("src",c(this).data("src"));c(this).removeAttr("data-src")})}function Pa(a){a.find("video, audio").each(function(){var a=c(this).get(0);a.hasAttribute("autoplay")&&"function"===typeof a.play&&a.play()})}function Ma(a){a.find("video, audio").each(function(){var a= +c(this).get(0);a.hasAttribute("data-ignore")||"function"!==typeof a.pause||a.pause()})}function ra(){if(!U&&!d.lockAnchors){var a=l.location.hash.replace("#","").split("/"),b=a[0],a=a[1];if(b.length){var c="undefined"===typeof A,e="undefined"===typeof A&&"undefined"===typeof a&&!w;(b&&b!==A&&!c||e||!w&&aa!=a)&&ba(b,a)}}}function Qa(a){u&&(a.pageYP&&e.moveSectionDown());P=a.pageY}function F(a,b){var g=b.position(),e=b.index(),f=a.closest(".fp-section"),h=f.index(".fp-section"), +k=f.data("anchor"),l=f.find(".fp-slidesNav"),m=sa(b),p=v;if(d.onSlideLeave){var t=f.find(".fp-slide.active"),q=t.index(),r;r=q==e?"none":q>e?"left":"right";if(!p&&"none"!==r&&c.isFunction(d.onSlideLeave)&&!1===d.onSlideLeave.call(t,k,h+1,q,r,e)){w=!1;return}}b.addClass("active").siblings().removeClass("active");!d.loopHorizontal&&d.controlArrows&&(f.find(".fp-controlArrow.fp-prev").toggle(0!==e),f.find(".fp-controlArrow.fp-next").toggle(!b.is(":last-child")));f.hasClass("active")&&W(e,m,k,h);var u= +function(){p||c.isFunction(d.afterSlideLoad)&&d.afterSlideLoad.call(b,k,h+1,m,e);w=!1};d.css3?(g="translate3d(-"+n.round(g.left)+"px, 0px, 0px)",ta(a.find(".fp-slidesContainer"),020*n.max(ca,a)/100&&(e.reBuild(!0),ca=a))}else clearTimeout(xa),xa=setTimeout(function(){e.reBuild(!0)},350)}function wa(){var a=d.responsive||d.responsiveWidth,b=d.responsiveHeight;a&&e.setResponsive(m.width() +a||900>b){var d=n.min(100*a/825,100*b/900).toFixed(2);p.css("font-size",d+"%")}else p.css("font-size","100%")}function K(a,b){d.menu&&(c(d.menu).find(".active").removeClass("active"),c(d.menu).find('[data-menuanchor="'+a+'"]').addClass("active"));d.navigation&&(c("#fp-nav").find(".active").removeClass("active"),a?c("#fp-nav").find('a[href="#'+a+'"]').addClass("active"):c("#fp-nav").find("li").eq(b).find("a").addClass("active"))}function V(a){var b=c(".fp-section.active").index(".fp-section");a=a.index(".fp-section"); +return b==a?"none":b>a?"up":"down"}function I(a){a.css("overflow","hidden");var b=a.closest(".fp-section"),c=a.find(".fp-scrollable"),e;c.length?e=c.get(0).scrollHeight:(e=a.get(0).scrollHeight,d.verticalCentered&&(e=a.find(".fp-tableCell").get(0).scrollHeight));b=q-parseInt(b.css("padding-bottom"))-parseInt(b.css("padding-top"));e>b?c.length?c.css("height",b+"px").parent().css("height",b+"px"):(d.verticalCentered?a.find(".fp-tableCell").wrapInner('
'):a.wrapInner('
'), +a.find(".fp-scrollable").slimScroll({allowPageScroll:!0,height:b+"px",size:"10px",alwaysVisible:!0})):ya(a);a.css("overflow","")}function ya(a){a.find(".fp-scrollable").children().first().unwrap().unwrap();a.find(".slimScrollBar").remove();a.find(".slimScrollRail").remove()}function za(a){a.addClass("fp-table").wrapInner('
')}function Aa(a){var b=q;if(d.paddingTop||d.paddingBottom)b=a,b.hasClass("fp-section")||(b=a.closest(".fp-section")),a=parseInt(b.css("padding-top"))+ +parseInt(b.css("padding-bottom")),b=q-a;return b}function qa(a,b){b?ta(f):f.addClass("fp-notransition");f.css(ua(a));setTimeout(function(){f.removeClass("fp-notransition")},10)}function Ba(a){var b=c('.fp-section[data-anchor="'+a+'"]');b.length||(b=c(".fp-section").eq(a-1));return b}function ba(a,b){var d=Ba(a);"undefined"===typeof b&&(b=0);a===A||d.hasClass("active")?Ca(d,b):B(d,function(){Ca(d,b)})}function Ca(a,b){if("undefined"!==typeof b){var d=a.find(".fp-slides"),c;c=a.find(".fp-slides");var e= +c.find('.fp-slide[data-anchor="'+b+'"]');e.length||(e=c.find(".fp-slide").eq(b));c=e;c.length&&F(d,c)}}function Sa(a,b){a.append('
    ');var c=a.find(".fp-slidesNav");c.addClass(d.slidesNavPosition);for(var e=0;e');c.css("margin-left","-"+c.width()/2+"px");c.find("li").first().find("a").addClass("active")}function W(a,b,c,e){e="";d.anchors.length&&!d.lockAnchors&&(a?("undefined"!==typeof c&&(e=c), +"undefined"===typeof b&&(b=a),aa=b,Da(e+"/"+b)):("undefined"!==typeof a&&(aa=b),Da(c)));Ea()}function Da(a){if(d.recordHistory)location.hash=a;else if(Q||R)history.replaceState(H,H,"#"+a);else{var b=l.location.href.split("#")[0];l.location.replace(b+"#"+a)}}function sa(a){var b=a.data("anchor");a=a.index();"undefined"===typeof b&&(b=a);return b}function Ea(){var a=c(".fp-section.active"),b=a.find(".fp-slide.active"),e=a.data("anchor"),f=sa(b),a=a.index(".fp-section"),a=String(a);d.anchors.length&& +(a=e);b.length&&(a=a+"-"+f);a=a.replace("/","-").replace("#","");p[0].className=p[0].className.replace(RegExp("\\b\\s?fp-viewing-[^\\s]+\\b","g"),"");p.addClass("fp-viewing-"+a)}function Ta(){var a=k.createElement("p"),b,c={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};k.body.insertBefore(a,null);for(var d in c)a.style[d]!==H&&(a.style[d]="translate3d(1px,1px,1px)",b=l.getComputedStyle(a).getPropertyValue(c[d])); +k.body.removeChild(a);return b!==H&&0(b/=e/2)?d/2*b*b*b+c:d/2*((b-=2)*b*b+2)+c}});c.extend(c.easing,{easeInQuart:function(a, +b,c,d,e){return d*(b/=e)*b*b*b+c}});e.setAutoScrolling=function(a,b){S("autoScrolling",a,b);var g=c(".fp-section.active");d.autoScrolling&&!d.scrollBar?(x.css({overflow:"hidden",height:"100%"}),e.setRecordHistory(d.recordHistory,"internal"),f.css({"-ms-touch-action":"none","touch-action":"none"}),g.length&&y(g.position().top)):(x.css({overflow:"visible",height:"initial"}),e.setRecordHistory(!1,"internal"),f.css({"-ms-touch-action":"","touch-action":""}),y(0),g.length&&x.scrollTop(g.position().top))}; +e.setRecordHistory=function(a,b){S("recordHistory",a,b)};e.setScrollingSpeed=function(a,b){S("scrollingSpeed",a,b)};e.setFitToSection=function(a,b){S("fitToSection",a,b)};e.setLockAnchors=function(a){d.lockAnchors=a};e.setMouseWheelScrolling=function(a){a?k.addEventListener?(k.addEventListener("mousewheel",t,!1),k.addEventListener("wheel",t,!1),k.addEventListener("DOMMouseScroll",t,!1)):k.attachEvent("onmousewheel",t):k.addEventListener?(k.removeEventListener("mousewheel",t,!1),k.removeEventListener("wheel", +t,!1),k.removeEventListener("DOMMouseScroll",t,!1)):k.detachEvent("onmousewheel",t)};e.setAllowScrolling=function(a,b){"undefined"!==typeof b?(b=b.replace(/ /g,"").split(","),c.each(b,function(b,c){Ga(a,c,"m")})):a?(e.setMouseWheelScrolling(!0),Ua()):(e.setMouseWheelScrolling(!1),Va())};e.setKeyboardScrolling=function(a,b){"undefined"!==typeof b?(b=b.replace(/ /g,"").split(","),c.each(b,function(b,c){Ga(a,c,"k")})):d.keyboardScrolling=a};e.moveSectionUp=function(){var a=c(".fp-section.active").prev(".fp-section"); +a.length||!d.loopTop&&!d.continuousVertical||(a=c(".fp-section").last());a.length&&B(a,null,!0)};e.moveSectionDown=function(){var a=c(".fp-section.active").next(".fp-section");a.length||!d.loopBottom&&!d.continuousVertical||(a=c(".fp-section").first());!a.length||d.onBeforeMoveSection&&c.isFunction(d.onBeforeMoveSection)&&!1===d.onBeforeMoveSection.call(this,direction,currentSlide,destiny,slides,activeSection)||B(a,null,!1)};e.silentMoveTo=function(a,b){e.setScrollingSpeed(0,"internal");e.moveTo(a, +b);e.setScrollingSpeed(G.scrollingSpeed,"internal")};e.moveTo=function(a,b){var c=Ba(a);"undefined"!==typeof b?ba(a,b):0'); +e.parent().wrap('
    ');c(this).find(".fp-slidesContainer").css("width",a+"%");1` element. This + * information allows you to progressively enhance your pages with a granular level + * of control over the experience. +*/ + +;(function(window, document, undefined){ + var classes = []; + + + var tests = []; + + + /** + * + * ModernizrProto is the constructor for Modernizr + * + * @class + * @access public + */ + + var ModernizrProto = { + // The current version, dummy + _version: '3.6.0', + + // Any settings that don't work as separate modules + // can go in here as configuration. + _config: { + 'classPrefix': '', + 'enableClasses': true, + 'enableJSClass': true, + 'usePrefixes': true + }, + + // Queue of tests + _q: [], + + // Stub these for people who are listening + on: function(test, cb) { + // I don't really think people should do this, but we can + // safe guard it a bit. + // -- NOTE:: this gets WAY overridden in src/addTest for actual async tests. + // This is in case people listen to synchronous tests. I would leave it out, + // but the code to *disallow* sync tests in the real version of this + // function is actually larger than this. + var self = this; + setTimeout(function() { + cb(self[test]); + }, 0); + }, + + addTest: function(name, fn, options) { + tests.push({name: name, fn: fn, options: options}); + }, + + addAsyncTest: function(fn) { + tests.push({name: null, fn: fn}); + } + }; + + + + // Fake some of Object.create so we can force non test results to be non "own" properties. + var Modernizr = function() {}; + Modernizr.prototype = ModernizrProto; + + // Leak modernizr globally when you `require` it rather than force it here. + // Overwrite name so constructor name is nicer :D + Modernizr = new Modernizr(); + + + + /** + * is returns a boolean if the typeof an obj is exactly type. + * + * @access private + * @function is + * @param {*} obj - A thing we want to check the type of + * @param {string} type - A string to compare the typeof against + * @returns {boolean} + */ + + function is(obj, type) { + return typeof obj === type; + } + ; + + /** + * Run through all tests and detect their support in the current UA. + * + * @access private + */ + + function testRunner() { + var featureNames; + var feature; + var aliasIdx; + var result; + var nameIdx; + var featureName; + var featureNameSplit; + + for (var featureIdx in tests) { + if (tests.hasOwnProperty(featureIdx)) { + featureNames = []; + feature = tests[featureIdx]; + // run the test, throw the return value into the Modernizr, + // then based on that boolean, define an appropriate className + // and push it into an array of classes we'll join later. + // + // If there is no name, it's an 'async' test that is run, + // but not directly added to the object. That should + // be done with a post-run addTest call. + if (feature.name) { + featureNames.push(feature.name.toLowerCase()); + + if (feature.options && feature.options.aliases && feature.options.aliases.length) { + // Add all the aliases into the names list + for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) { + featureNames.push(feature.options.aliases[aliasIdx].toLowerCase()); + } + } + } + + // Run the test, or use the raw value if it's not a function + result = is(feature.fn, 'function') ? feature.fn() : feature.fn; + + + // Set each of the names on the Modernizr object + for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) { + featureName = featureNames[nameIdx]; + // Support dot properties as sub tests. We don't do checking to make sure + // that the implied parent tests have been added. You must call them in + // order (either in the test, or make the parent test a dependency). + // + // Cap it to TWO to make the logic simple and because who needs that kind of subtesting + // hashtag famous last words + featureNameSplit = featureName.split('.'); + + if (featureNameSplit.length === 1) { + Modernizr[featureNameSplit[0]] = result; + } else { + // cast to a Boolean, if not one already + if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) { + Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]); + } + + Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result; + } + + classes.push((result ? '' : 'no-') + featureNameSplit.join('-')); + } + } + } + } + ; + + /** + * docElement is a convenience wrapper to grab the root element of the document + * + * @access private + * @returns {HTMLElement|SVGElement} The root element of the document + */ + + var docElement = document.documentElement; + + + /** + * A convenience helper to check if the document we are running in is an SVG document + * + * @access private + * @returns {boolean} + */ + + var isSVG = docElement.nodeName.toLowerCase() === 'svg'; + + + /** + * setClasses takes an array of class names and adds them to the root element + * + * @access private + * @function setClasses + * @param {string[]} classes - Array of class names + */ + + // Pass in an and array of class names, e.g.: + // ['no-webp', 'borderradius', ...] + function setClasses(classes) { + var className = docElement.className; + var classPrefix = Modernizr._config.classPrefix || ''; + + if (isSVG) { + className = className.baseVal; + } + + // Change `no-js` to `js` (independently of the `enableClasses` option) + // Handle classPrefix on this too + if (Modernizr._config.enableJSClass) { + var reJS = new RegExp('(^|\\s)' + classPrefix + 'no-js(\\s|$)'); + className = className.replace(reJS, '$1' + classPrefix + 'js$2'); + } + + if (Modernizr._config.enableClasses) { + // Add the new classes + className += ' ' + classPrefix + classes.join(' ' + classPrefix); + if (isSVG) { + docElement.className.baseVal = className; + } else { + docElement.className = className; + } + } + + } + + ; + + /** + * hasOwnProp is a shim for hasOwnProperty that is needed for Safari 2.0 support + * + * @author kangax + * @access private + * @function hasOwnProp + * @param {object} object - The object to check for a property + * @param {string} property - The property to check for + * @returns {boolean} + */ + + // hasOwnProperty shim by kangax needed for Safari 2.0 support + var hasOwnProp; + + (function() { + var _hasOwnProperty = ({}).hasOwnProperty; + /* istanbul ignore else */ + /* we have no way of testing IE 5.5 or safari 2, + * so just assume the else gets hit */ + if (!is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined')) { + hasOwnProp = function(object, property) { + return _hasOwnProperty.call(object, property); + }; + } + else { + hasOwnProp = function(object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ + return ((property in object) && is(object.constructor.prototype[property], 'undefined')); + }; + } + })(); + + + + + // _l tracks listeners for async tests, as well as tests that execute after the initial run + ModernizrProto._l = {}; + + /** + * Modernizr.on is a way to listen for the completion of async tests. Being + * asynchronous, they may not finish before your scripts run. As a result you + * will get a possibly false negative `undefined` value. + * + * @memberof Modernizr + * @name Modernizr.on + * @access public + * @function on + * @param {string} feature - String name of the feature detect + * @param {function} cb - Callback function returning a Boolean - true if feature is supported, false if not + * @example + * + * ```js + * Modernizr.on('flash', function( result ) { + * if (result) { + * // the browser has flash + * } else { + * // the browser does not have flash + * } + * }); + * ``` + */ + + ModernizrProto.on = function(feature, cb) { + // Create the list of listeners if it doesn't exist + if (!this._l[feature]) { + this._l[feature] = []; + } + + // Push this test on to the listener list + this._l[feature].push(cb); + + // If it's already been resolved, trigger it on next tick + if (Modernizr.hasOwnProperty(feature)) { + // Next Tick + setTimeout(function() { + Modernizr._trigger(feature, Modernizr[feature]); + }, 0); + } + }; + + /** + * _trigger is the private function used to signal test completion and run any + * callbacks registered through [Modernizr.on](#modernizr-on) + * + * @memberof Modernizr + * @name Modernizr._trigger + * @access private + * @function _trigger + * @param {string} feature - string name of the feature detect + * @param {function|boolean} [res] - A feature detection function, or the boolean = + * result of a feature detection function + */ + + ModernizrProto._trigger = function(feature, res) { + if (!this._l[feature]) { + return; + } + + var cbs = this._l[feature]; + + // Force async + setTimeout(function() { + var i, cb; + for (i = 0; i < cbs.length; i++) { + cb = cbs[i]; + cb(res); + } + }, 0); + + // Don't trigger these again + delete this._l[feature]; + }; + + /** + * addTest allows you to define your own feature detects that are not currently + * included in Modernizr (under the covers it's the exact same code Modernizr + * uses for its own [feature detections](https://github.com/Modernizr/Modernizr/tree/master/feature-detects)). Just like the offical detects, the result + * will be added onto the Modernizr object, as well as an appropriate className set on + * the html element when configured to do so + * + * @memberof Modernizr + * @name Modernizr.addTest + * @optionName Modernizr.addTest() + * @optionProp addTest + * @access public + * @function addTest + * @param {string|object} feature - The string name of the feature detect, or an + * object of feature detect names and test + * @param {function|boolean} test - Function returning true if feature is supported, + * false if not. Otherwise a boolean representing the results of a feature detection + * @example + * + * The most common way of creating your own feature detects is by calling + * `Modernizr.addTest` with a string (preferably just lowercase, without any + * punctuation), and a function you want executed that will return a boolean result + * + * ```js + * Modernizr.addTest('itsTuesday', function() { + * var d = new Date(); + * return d.getDay() === 2; + * }); + * ``` + * + * When the above is run, it will set Modernizr.itstuesday to `true` when it is tuesday, + * and to `false` every other day of the week. One thing to notice is that the names of + * feature detect functions are always lowercased when added to the Modernizr object. That + * means that `Modernizr.itsTuesday` will not exist, but `Modernizr.itstuesday` will. + * + * + * Since we only look at the returned value from any feature detection function, + * you do not need to actually use a function. For simple detections, just passing + * in a statement that will return a boolean value works just fine. + * + * ```js + * Modernizr.addTest('hasJquery', 'jQuery' in window); + * ``` + * + * Just like before, when the above runs `Modernizr.hasjquery` will be true if + * jQuery has been included on the page. Not using a function saves a small amount + * of overhead for the browser, as well as making your code much more readable. + * + * Finally, you also have the ability to pass in an object of feature names and + * their tests. This is handy if you want to add multiple detections in one go. + * The keys should always be a string, and the value can be either a boolean or + * function that returns a boolean. + * + * ```js + * var detects = { + * 'hasjquery': 'jQuery' in window, + * 'itstuesday': function() { + * var d = new Date(); + * return d.getDay() === 2; + * } + * } + * + * Modernizr.addTest(detects); + * ``` + * + * There is really no difference between the first methods and this one, it is + * just a convenience to let you write more readable code. + */ + + function addTest(feature, test) { + + if (typeof feature == 'object') { + for (var key in feature) { + if (hasOwnProp(feature, key)) { + addTest(key, feature[ key ]); + } + } + } else { + + feature = feature.toLowerCase(); + var featureNameSplit = feature.split('.'); + var last = Modernizr[featureNameSplit[0]]; + + // Again, we don't check for parent test existence. Get that right, though. + if (featureNameSplit.length == 2) { + last = last[featureNameSplit[1]]; + } + + if (typeof last != 'undefined') { + // we're going to quit if you're trying to overwrite an existing test + // if we were to allow it, we'd do this: + // var re = new RegExp("\\b(no-)?" + feature + "\\b"); + // docElement.className = docElement.className.replace( re, '' ); + // but, no rly, stuff 'em. + return Modernizr; + } + + test = typeof test == 'function' ? test() : test; + + // Set the value (this is the magic, right here). + if (featureNameSplit.length == 1) { + Modernizr[featureNameSplit[0]] = test; + } else { + // cast to a Boolean, if not one already + if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) { + Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]); + } + + Modernizr[featureNameSplit[0]][featureNameSplit[1]] = test; + } + + // Set a single class (either `feature` or `no-feature`) + setClasses([(!!test && test != false ? '' : 'no-') + featureNameSplit.join('-')]); + + // Trigger the event + Modernizr._trigger(feature, test); + } + + return Modernizr; // allow chaining. + } + + // After all the tests are run, add self to the Modernizr prototype + Modernizr._q.push(function() { + ModernizrProto.addTest = addTest; + }); + + + + + /** + * List of property values to set for css tests. See ticket #21 + * http://git.io/vUGl4 + * + * @memberof Modernizr + * @name Modernizr._prefixes + * @optionName Modernizr._prefixes + * @optionProp prefixes + * @access public + * @example + * + * Modernizr._prefixes is the internal list of prefixes that we test against + * inside of things like [prefixed](#modernizr-prefixed) and [prefixedCSS](#-code-modernizr-prefixedcss). It is simply + * an array of kebab-case vendor prefixes you can use within your code. + * + * Some common use cases include + * + * Generating all possible prefixed version of a CSS property + * ```js + * var rule = Modernizr._prefixes.join('transform: rotate(20deg); '); + * + * rule === 'transform: rotate(20deg); webkit-transform: rotate(20deg); moz-transform: rotate(20deg); o-transform: rotate(20deg); ms-transform: rotate(20deg);' + * ``` + * + * Generating all possible prefixed version of a CSS value + * ```js + * rule = 'display:' + Modernizr._prefixes.join('flex; display:') + 'flex'; + * + * rule === 'display:flex; display:-webkit-flex; display:-moz-flex; display:-o-flex; display:-ms-flex; display:flex' + * ``` + */ + + // we use ['',''] rather than an empty array in order to allow a pattern of .`join()`ing prefixes to test + // values in feature detects to continue to work + var prefixes = (ModernizrProto._config.usePrefixes ? ' -webkit- -moz- -o- -ms- '.split(' ') : ['','']); + + // expose these for the plugin API. Look in the source for how to join() them against your input + ModernizrProto._prefixes = prefixes; + + + + /** + * createElement is a convenience wrapper around document.createElement. Since we + * use createElement all over the place, this allows for (slightly) smaller code + * as well as abstracting away issues with creating elements in contexts other than + * HTML documents (e.g. SVG documents). + * + * @access private + * @function createElement + * @returns {HTMLElement|SVGElement} An HTML or SVG element + */ + + function createElement() { + if (typeof document.createElement !== 'function') { + // This is the case in IE7, where the type of createElement is "object". + // For this reason, we cannot call apply() as Object is not a Function. + return document.createElement(arguments[0]); + } else if (isSVG) { + return document.createElementNS.call(document, 'http://www.w3.org/2000/svg', arguments[0]); + } else { + return document.createElement.apply(document, arguments); + } + } + + ; + + /** + * getBody returns the body of a document, or an element that can stand in for + * the body if a real body does not exist + * + * @access private + * @function getBody + * @returns {HTMLElement|SVGElement} Returns the real body of a document, or an + * artificially created element that stands in for the body + */ + + function getBody() { + // After page load injecting a fake body doesn't work so check if body exists + var body = document.body; + + if (!body) { + // Can't use the real body create a fake one. + body = createElement(isSVG ? 'svg' : 'body'); + body.fake = true; + } + + return body; + } + + ; + + /** + * injectElementWithStyles injects an element with style element and some CSS rules + * + * @access private + * @function injectElementWithStyles + * @param {string} rule - String representing a css rule + * @param {function} callback - A function that is used to test the injected element + * @param {number} [nodes] - An integer representing the number of additional nodes you want injected + * @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes + * @returns {boolean} + */ + + function injectElementWithStyles(rule, callback, nodes, testnames) { + var mod = 'modernizr'; + var style; + var ret; + var node; + var docOverflow; + var div = createElement('div'); + var body = getBody(); + + if (parseInt(nodes, 10)) { + // In order not to give false positives we create a node for each test + // This also allows the method to scale for unspecified uses + while (nodes--) { + node = createElement('div'); + node.id = testnames ? testnames[nodes] : mod + (nodes + 1); + div.appendChild(node); + } + } + + style = createElement('style'); + style.type = 'text/css'; + style.id = 's' + mod; + + // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody. + // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 + (!body.fake ? div : body).appendChild(style); + body.appendChild(div); + + if (style.styleSheet) { + style.styleSheet.cssText = rule; + } else { + style.appendChild(document.createTextNode(rule)); + } + div.id = mod; + + if (body.fake) { + //avoid crashing IE8, if background image is used + body.style.background = ''; + //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible + body.style.overflow = 'hidden'; + docOverflow = docElement.style.overflow; + docElement.style.overflow = 'hidden'; + docElement.appendChild(body); + } + + ret = callback(div, rule); + // If this is done after page load we don't want to remove the body so check if body exists + if (body.fake) { + body.parentNode.removeChild(body); + docElement.style.overflow = docOverflow; + // Trigger layout so kinetic scrolling isn't disabled in iOS6+ + // eslint-disable-next-line + docElement.offsetHeight; + } else { + div.parentNode.removeChild(div); + } + + return !!ret; + + } + + ; + + /** + * testStyles injects an element with style element and some CSS rules + * + * @memberof Modernizr + * @name Modernizr.testStyles + * @optionName Modernizr.testStyles() + * @optionProp testStyles + * @access public + * @function testStyles + * @param {string} rule - String representing a css rule + * @param {function} callback - A function that is used to test the injected element + * @param {number} [nodes] - An integer representing the number of additional nodes you want injected + * @param {string[]} [testnames] - An array of strings that are used as ids for the additional nodes + * @returns {boolean} + * @example + * + * `Modernizr.testStyles` takes a CSS rule and injects it onto the current page + * along with (possibly multiple) DOM elements. This lets you check for features + * that can not be detected by simply checking the [IDL](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Interface_development_guide/IDL_interface_rules). + * + * ```js + * Modernizr.testStyles('#modernizr { width: 9px; color: papayawhip; }', function(elem, rule) { + * // elem is the first DOM node in the page (by default #modernizr) + * // rule is the first argument you supplied - the CSS rule in string form + * + * addTest('widthworks', elem.style.width === '9px') + * }); + * ``` + * + * If your test requires multiple nodes, you can include a third argument + * indicating how many additional div elements to include on the page. The + * additional nodes are injected as children of the `elem` that is returned as + * the first argument to the callback. + * + * ```js + * Modernizr.testStyles('#modernizr {width: 1px}; #modernizr2 {width: 2px}', function(elem) { + * document.getElementById('modernizr').style.width === '1px'; // true + * document.getElementById('modernizr2').style.width === '2px'; // true + * elem.firstChild === document.getElementById('modernizr2'); // true + * }, 1); + * ``` + * + * By default, all of the additional elements have an ID of `modernizr[n]`, where + * `n` is its index (e.g. the first additional, second overall is `#modernizr2`, + * the second additional is `#modernizr3`, etc.). + * If you want to have more meaningful IDs for your function, you can provide + * them as the fourth argument, as an array of strings + * + * ```js + * Modernizr.testStyles('#foo {width: 10px}; #bar {height: 20px}', function(elem) { + * elem.firstChild === document.getElementById('foo'); // true + * elem.lastChild === document.getElementById('bar'); // true + * }, 2, ['foo', 'bar']); + * ``` + * + */ + + var testStyles = ModernizrProto.testStyles = injectElementWithStyles; + +/*! +{ + "name": "Touch Events", + "property": "touchevents", + "caniuse" : "touch", + "tags": ["media", "attribute"], + "notes": [{ + "name": "Touch Events spec", + "href": "https://www.w3.org/TR/2013/WD-touch-events-20130124/" + }], + "warnings": [ + "Indicates if the browser supports the Touch Events spec, and does not necessarily reflect a touchscreen device" + ], + "knownBugs": [ + "False-positive on some configurations of Nokia N900", + "False-positive on some BlackBerry 6.0 builds – https://github.com/Modernizr/Modernizr/issues/372#issuecomment-3112695" + ] +} +!*/ +/* DOC +Indicates if the browser supports the W3C Touch Events API. + +This *does not* necessarily reflect a touchscreen device: + +* Older touchscreen devices only emulate mouse events +* Modern IE touch devices implement the Pointer Events API instead: use `Modernizr.pointerevents` to detect support for that +* Some browsers & OS setups may enable touch APIs when no touchscreen is connected +* Future browsers may implement other event models for touch interactions + +See this article: [You Can't Detect A Touchscreen](http://www.stucox.com/blog/you-cant-detect-a-touchscreen/). + +It's recommended to bind both mouse and touch/pointer events simultaneously – see [this HTML5 Rocks tutorial](http://www.html5rocks.com/en/mobile/touchandmouse/). + +This test will also return `true` for Firefox 4 Multitouch support. +*/ + + // Chrome (desktop) used to lie about its support on this, but that has since been rectified: http://crbug.com/36415 + Modernizr.addTest('touchevents', function() { + var bool; + if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { + bool = true; + } else { + // include the 'heartz' as a way to have a non matching MQ to help terminate the join + // https://git.io/vznFH + var query = ['@media (', prefixes.join('touch-enabled),('), 'heartz', ')', '{#modernizr{top:9px;position:absolute}}'].join(''); + testStyles(query, function(node) { + bool = node.offsetTop === 9; + }); + } + return bool; + }); + + + // Run each test + testRunner(); + + // Remove the "no-js" class if it exists + setClasses(classes); + + delete ModernizrProto.addTest; + delete ModernizrProto.addAsyncTest; + + // Run the things that are supposed to run after the tests + for (var i = 0; i < Modernizr._q.length; i++) { + Modernizr._q[i](); + } + + // Leak Modernizr namespace + window.Modernizr = Modernizr; + + +; + +})(window, document); \ No newline at end of file diff --git a/src/scripts/_libs/select2.js b/src/scripts/_libs/select2.js new file mode 100644 index 0000000..0168034 --- /dev/null +++ b/src/scripts/_libs/select2.js @@ -0,0 +1,6210 @@ +/*! + * Select2 4.1.0-rc.0 + * https://select2.github.io + * + * Released under the MIT license + * https://github.com/select2/select2/blob/master/LICENSE.md + */ +;(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === 'object' && module.exports) { + // Node/CommonJS + module.exports = function (root, jQuery) { + if (jQuery === undefined) { + // require('jQuery') returns a factory that requires window to + // build a jQuery instance, we normalize how we use modules + // that require this pattern but the window provided is a noop + // if it's defined (how jquery works) + if (typeof window !== 'undefined') { + jQuery = require('jquery'); + } + else { + jQuery = require('jquery')(root); + } + } + factory(jQuery); + return jQuery; + }; + } else { + // Browser globals + factory(jQuery); + } +} (function (jQuery) { + // This is needed so we can catch the AMD loader configuration and use it + // The inner file should be wrapped (by `banner.start.js`) in a function that + // returns the AMD loader references. + var S2 =(function () { + // Restore the Select2 AMD loader so it can be used + // Needed mostly in the language files, where the loader is not inserted + if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) { + var S2 = jQuery.fn.select2.amd; + } +var S2;(function () { if (!S2 || !S2.requirejs) { +if (!S2) { S2 = {}; } else { require = S2; } +/** + * @license almond 0.3.3 Copyright jQuery Foundation and other contributors. + * Released under MIT license, http://github.com/requirejs/almond/LICENSE + */ +//Going sloppy to avoid 'use strict' string cost, but strict practices should +//be followed. +/*global setTimeout: false */ + +var requirejs, require, define; +(function (undef) { + var main, req, makeMap, handlers, + defined = {}, + waiting = {}, + config = {}, + defining = {}, + hasOwn = Object.prototype.hasOwnProperty, + aps = [].slice, + jsSuffixRegExp = /\.js$/; + + function hasProp(obj, prop) { + return hasOwn.call(obj, prop); + } + + /** + * Given a relative module name, like ./something, normalize it to + * a real name that can be mapped to a path. + * @param {String} name the relative name + * @param {String} baseName a real name that the name arg is relative + * to. + * @returns {String} normalized name + */ + function normalize(name, baseName) { + var nameParts, nameSegment, mapValue, foundMap, lastIndex, + foundI, foundStarMap, starI, i, j, part, normalizedBaseParts, + baseParts = baseName && baseName.split("/"), + map = config.map, + starMap = (map && map['*']) || {}; + + //Adjust any relative paths. + if (name) { + name = name.split('/'); + lastIndex = name.length - 1; + + // If wanting node ID compatibility, strip .js from end + // of IDs. Have to do this here, and not in nameToUrl + // because node allows either .js or non .js to map + // to same file. + if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { + name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); + } + + // Starts with a '.' so need the baseName + if (name[0].charAt(0) === '.' && baseParts) { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + name = normalizedBaseParts.concat(name); + } + + //start trimDots + for (i = 0; i < name.length; i++) { + part = name[i]; + if (part === '.') { + name.splice(i, 1); + i -= 1; + } else if (part === '..') { + // If at the start, or previous value is still .., + // keep them so that when converted to a path it may + // still work when converted to a path, even though + // as an ID it is less than ideal. In larger point + // releases, may be better to just kick out an error. + if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') { + continue; + } else if (i > 0) { + name.splice(i - 1, 2); + i -= 2; + } + } + } + //end trimDots + + name = name.join('/'); + } + + //Apply map config if available. + if ((baseParts || starMap) && map) { + nameParts = name.split('/'); + + for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join("/"); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = map[baseParts.slice(0, j).join('/')]; + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = mapValue[nameSegment]; + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break; + } + } + } + } + + if (foundMap) { + break; + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && starMap[nameSegment]) { + foundStarMap = starMap[nameSegment]; + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + return name; + } + + function makeRequire(relName, forceSync) { + return function () { + //A version of a require function that passes a moduleName + //value for items that may need to + //look up paths relative to the moduleName + var args = aps.call(arguments, 0); + + //If first arg is not require('string'), and there is only + //one arg, it is the array form without a callback. Insert + //a null so that the following concat is correct. + if (typeof args[0] !== 'string' && args.length === 1) { + args.push(null); + } + return req.apply(undef, args.concat([relName, forceSync])); + }; + } + + function makeNormalize(relName) { + return function (name) { + return normalize(name, relName); + }; + } + + function makeLoad(depName) { + return function (value) { + defined[depName] = value; + }; + } + + function callDep(name) { + if (hasProp(waiting, name)) { + var args = waiting[name]; + delete waiting[name]; + defining[name] = true; + main.apply(undef, args); + } + + if (!hasProp(defined, name) && !hasProp(defining, name)) { + throw new Error('No ' + name); + } + return defined[name]; + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + //Creates a parts array for a relName where first part is plugin ID, + //second part is resource ID. Assumes relName has already been normalized. + function makeRelParts(relName) { + return relName ? splitPrefix(relName) : []; + } + + /** + * Makes a name map, normalizing the name, and using a plugin + * for normalization if necessary. Grabs a ref to plugin + * too, as an optimization. + */ + makeMap = function (name, relParts) { + var plugin, + parts = splitPrefix(name), + prefix = parts[0], + relResourceName = relParts[1]; + + name = parts[1]; + + if (prefix) { + prefix = normalize(prefix, relResourceName); + plugin = callDep(prefix); + } + + //Normalize according + if (prefix) { + if (plugin && plugin.normalize) { + name = plugin.normalize(name, makeNormalize(relResourceName)); + } else { + name = normalize(name, relResourceName); + } + } else { + name = normalize(name, relResourceName); + parts = splitPrefix(name); + prefix = parts[0]; + name = parts[1]; + if (prefix) { + plugin = callDep(prefix); + } + } + + //Using ridiculous property names for space reasons + return { + f: prefix ? prefix + '!' + name : name, //fullName + n: name, + pr: prefix, + p: plugin + }; + }; + + function makeConfig(name) { + return function () { + return (config && config.config && config.config[name]) || {}; + }; + } + + handlers = { + require: function (name) { + return makeRequire(name); + }, + exports: function (name) { + var e = defined[name]; + if (typeof e !== 'undefined') { + return e; + } else { + return (defined[name] = {}); + } + }, + module: function (name) { + return { + id: name, + uri: '', + exports: defined[name], + config: makeConfig(name) + }; + } + }; + + main = function (name, deps, callback, relName) { + var cjsModule, depName, ret, map, i, relParts, + args = [], + callbackType = typeof callback, + usingExports; + + //Use name if no relName + relName = relName || name; + relParts = makeRelParts(relName); + + //Call the callback to define the module, if necessary. + if (callbackType === 'undefined' || callbackType === 'function') { + //Pull out the defined dependencies and pass the ordered + //values to the callback. + //Default to [require, exports, module] if no deps + deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps; + for (i = 0; i < deps.length; i += 1) { + map = makeMap(deps[i], relParts); + depName = map.f; + + //Fast path CommonJS standard dependencies. + if (depName === "require") { + args[i] = handlers.require(name); + } else if (depName === "exports") { + //CommonJS module spec 1.1 + args[i] = handlers.exports(name); + usingExports = true; + } else if (depName === "module") { + //CommonJS module spec 1.1 + cjsModule = args[i] = handlers.module(name); + } else if (hasProp(defined, depName) || + hasProp(waiting, depName) || + hasProp(defining, depName)) { + args[i] = callDep(depName); + } else if (map.p) { + map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {}); + args[i] = defined[depName]; + } else { + throw new Error(name + ' missing ' + depName); + } + } + + ret = callback ? callback.apply(defined[name], args) : undefined; + + if (name) { + //If setting exports via "module" is in play, + //favor that over return value and exports. After that, + //favor a non-undefined return value over exports use. + if (cjsModule && cjsModule.exports !== undef && + cjsModule.exports !== defined[name]) { + defined[name] = cjsModule.exports; + } else if (ret !== undef || !usingExports) { + //Use the return value from the function. + defined[name] = ret; + } + } + } else if (name) { + //May just be an object definition for the module. Only + //worry about defining if have a module name. + defined[name] = callback; + } + }; + + requirejs = require = req = function (deps, callback, relName, forceSync, alt) { + if (typeof deps === "string") { + if (handlers[deps]) { + //callback in this case is really relName + return handlers[deps](callback); + } + //Just return the module wanted. In this scenario, the + //deps arg is the module name, and second arg (if passed) + //is just the relName. + //Normalize module name, if it contains . or .. + return callDep(makeMap(deps, makeRelParts(callback)).f); + } else if (!deps.splice) { + //deps is a config object, not an array. + config = deps; + if (config.deps) { + req(config.deps, config.callback); + } + if (!callback) { + return; + } + + if (callback.splice) { + //callback is an array, which means it is a dependency list. + //Adjust args if there are dependencies + deps = callback; + callback = relName; + relName = null; + } else { + deps = undef; + } + } + + //Support require(['a']) + callback = callback || function () {}; + + //If relName is a function, it is an errback handler, + //so remove it. + if (typeof relName === 'function') { + relName = forceSync; + forceSync = alt; + } + + //Simulate async callback; + if (forceSync) { + main(undef, deps, callback, relName); + } else { + //Using a non-zero value because of concern for what old browsers + //do, and latest browsers "upgrade" to 4 if lower value is used: + //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout: + //If want a value immediately, use require('id') instead -- something + //that works in almond on the global level, but not guaranteed and + //unlikely to work in other AMD implementations. + setTimeout(function () { + main(undef, deps, callback, relName); + }, 4); + } + + return req; + }; + + /** + * Just drops the config on the floor, but returns req in case + * the config return value is used. + */ + req.config = function (cfg) { + return req(cfg); + }; + + /** + * Expose module registry for debugging and tooling + */ + requirejs._defined = defined; + + define = function (name, deps, callback) { + if (typeof name !== 'string') { + throw new Error('See almond README: incorrect module build, no module name'); + } + + //This module may not have dependencies + if (!deps.splice) { + //deps is not an array, so probably means + //an object literal or factory function for + //the value. Adjust args. + callback = deps; + deps = []; + } + + if (!hasProp(defined, name) && !hasProp(waiting, name)) { + waiting[name] = [name, deps, callback]; + } + }; + + define.amd = { + jQuery: true + }; +}()); + +S2.requirejs = requirejs;S2.require = require;S2.define = define; +} +}()); +S2.define("almond", function(){}); + +/* global jQuery:false, $:false */ +S2.define('jquery',[],function () { + var _$ = jQuery || $; + + if (_$ == null && console && console.error) { + console.error( + 'Select2: An instance of jQuery or a jQuery-compatible library was not ' + + 'found. Make sure that you are including jQuery before Select2 on your ' + + 'web page.' + ); + } + + return _$; +}); + +S2.define('select2/utils',[ + 'jquery' +], function ($) { + var Utils = {}; + + Utils.Extend = function (ChildClass, SuperClass) { + var __hasProp = {}.hasOwnProperty; + + function BaseConstructor () { + this.constructor = ChildClass; + } + + for (var key in SuperClass) { + if (__hasProp.call(SuperClass, key)) { + ChildClass[key] = SuperClass[key]; + } + } + + BaseConstructor.prototype = SuperClass.prototype; + ChildClass.prototype = new BaseConstructor(); + ChildClass.__super__ = SuperClass.prototype; + + return ChildClass; + }; + + function getMethods (theClass) { + var proto = theClass.prototype; + + var methods = []; + + for (var methodName in proto) { + var m = proto[methodName]; + + if (typeof m !== 'function') { + continue; + } + + if (methodName === 'constructor') { + continue; + } + + methods.push(methodName); + } + + return methods; + } + + Utils.Decorate = function (SuperClass, DecoratorClass) { + var decoratedMethods = getMethods(DecoratorClass); + var superMethods = getMethods(SuperClass); + + function DecoratedClass () { + var unshift = Array.prototype.unshift; + + var argCount = DecoratorClass.prototype.constructor.length; + + var calledConstructor = SuperClass.prototype.constructor; + + if (argCount > 0) { + unshift.call(arguments, SuperClass.prototype.constructor); + + calledConstructor = DecoratorClass.prototype.constructor; + } + + calledConstructor.apply(this, arguments); + } + + DecoratorClass.displayName = SuperClass.displayName; + + function ctr () { + this.constructor = DecoratedClass; + } + + DecoratedClass.prototype = new ctr(); + + for (var m = 0; m < superMethods.length; m++) { + var superMethod = superMethods[m]; + + DecoratedClass.prototype[superMethod] = + SuperClass.prototype[superMethod]; + } + + var calledMethod = function (methodName) { + // Stub out the original method if it's not decorating an actual method + var originalMethod = function () {}; + + if (methodName in DecoratedClass.prototype) { + originalMethod = DecoratedClass.prototype[methodName]; + } + + var decoratedMethod = DecoratorClass.prototype[methodName]; + + return function () { + var unshift = Array.prototype.unshift; + + unshift.call(arguments, originalMethod); + + return decoratedMethod.apply(this, arguments); + }; + }; + + for (var d = 0; d < decoratedMethods.length; d++) { + var decoratedMethod = decoratedMethods[d]; + + DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod); + } + + return DecoratedClass; + }; + + var Observable = function () { + this.listeners = {}; + }; + + Observable.prototype.on = function (event, callback) { + this.listeners = this.listeners || {}; + + if (event in this.listeners) { + this.listeners[event].push(callback); + } else { + this.listeners[event] = [callback]; + } + }; + + Observable.prototype.trigger = function (event) { + var slice = Array.prototype.slice; + var params = slice.call(arguments, 1); + + this.listeners = this.listeners || {}; + + // Params should always come in as an array + if (params == null) { + params = []; + } + + // If there are no arguments to the event, use a temporary object + if (params.length === 0) { + params.push({}); + } + + // Set the `_type` of the first object to the event + params[0]._type = event; + + if (event in this.listeners) { + this.invoke(this.listeners[event], slice.call(arguments, 1)); + } + + if ('*' in this.listeners) { + this.invoke(this.listeners['*'], arguments); + } + }; + + Observable.prototype.invoke = function (listeners, params) { + for (var i = 0, len = listeners.length; i < len; i++) { + listeners[i].apply(this, params); + } + }; + + Utils.Observable = Observable; + + Utils.generateChars = function (length) { + var chars = ''; + + for (var i = 0; i < length; i++) { + var randomChar = Math.floor(Math.random() * 36); + chars += randomChar.toString(36); + } + + return chars; + }; + + Utils.bind = function (func, context) { + return function () { + func.apply(context, arguments); + }; + }; + + Utils._convertData = function (data) { + for (var originalKey in data) { + var keys = originalKey.split('-'); + + var dataLevel = data; + + if (keys.length === 1) { + continue; + } + + for (var k = 0; k < keys.length; k++) { + var key = keys[k]; + + // Lowercase the first letter + // By default, dash-separated becomes camelCase + key = key.substring(0, 1).toLowerCase() + key.substring(1); + + if (!(key in dataLevel)) { + dataLevel[key] = {}; + } + + if (k == keys.length - 1) { + dataLevel[key] = data[originalKey]; + } + + dataLevel = dataLevel[key]; + } + + delete data[originalKey]; + } + + return data; + }; + + Utils.hasScroll = function (index, el) { + // Adapted from the function created by @ShadowScripter + // and adapted by @BillBarry on the Stack Exchange Code Review website. + // The original code can be found at + // http://codereview.stackexchange.com/q/13338 + // and was designed to be used with the Sizzle selector engine. + + var $el = $(el); + var overflowX = el.style.overflowX; + var overflowY = el.style.overflowY; + + //Check both x and y declarations + if (overflowX === overflowY && + (overflowY === 'hidden' || overflowY === 'visible')) { + return false; + } + + if (overflowX === 'scroll' || overflowY === 'scroll') { + return true; + } + + return ($el.innerHeight() < el.scrollHeight || + $el.innerWidth() < el.scrollWidth); + }; + + Utils.escapeMarkup = function (markup) { + var replaceMap = { + '\\': '\', + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' + }; + + // Do not try to escape the markup if it's not a string + if (typeof markup !== 'string') { + return markup; + } + + return String(markup).replace(/[&<>"'\/\\]/g, function (match) { + return replaceMap[match]; + }); + }; + + // Cache objects in Utils.__cache instead of $.data (see #4346) + Utils.__cache = {}; + + var id = 0; + Utils.GetUniqueElementId = function (element) { + // Get a unique element Id. If element has no id, + // creates a new unique number, stores it in the id + // attribute and returns the new id with a prefix. + // If an id already exists, it simply returns it with a prefix. + + var select2Id = element.getAttribute('data-select2-id'); + + if (select2Id != null) { + return select2Id; + } + + // If element has id, use it. + if (element.id) { + select2Id = 'select2-data-' + element.id; + } else { + select2Id = 'select2-data-' + (++id).toString() + + '-' + Utils.generateChars(4); + } + + element.setAttribute('data-select2-id', select2Id); + + return select2Id; + }; + + Utils.StoreData = function (element, name, value) { + // Stores an item in the cache for a specified element. + // name is the cache key. + var id = Utils.GetUniqueElementId(element); + if (!Utils.__cache[id]) { + Utils.__cache[id] = {}; + } + + Utils.__cache[id][name] = value; + }; + + Utils.GetData = function (element, name) { + // Retrieves a value from the cache by its key (name) + // name is optional. If no name specified, return + // all cache items for the specified element. + // and for a specified element. + var id = Utils.GetUniqueElementId(element); + if (name) { + if (Utils.__cache[id]) { + if (Utils.__cache[id][name] != null) { + return Utils.__cache[id][name]; + } + return $(element).data(name); // Fallback to HTML5 data attribs. + } + return $(element).data(name); // Fallback to HTML5 data attribs. + } else { + return Utils.__cache[id]; + } + }; + + Utils.RemoveData = function (element) { + // Removes all cached items for a specified element. + var id = Utils.GetUniqueElementId(element); + if (Utils.__cache[id] != null) { + delete Utils.__cache[id]; + } + + element.removeAttribute('data-select2-id'); + }; + + Utils.copyNonInternalCssClasses = function (dest, src) { + var classes; + + var destinationClasses = dest.getAttribute('class').trim().split(/\s+/); + + destinationClasses = destinationClasses.filter(function (clazz) { + // Save all Select2 classes + return clazz.indexOf('select2-') === 0; + }); + + var sourceClasses = src.getAttribute('class').trim().split(/\s+/); + + sourceClasses = sourceClasses.filter(function (clazz) { + // Only copy non-Select2 classes + return clazz.indexOf('select2-') !== 0; + }); + + var replacements = destinationClasses.concat(sourceClasses); + + dest.setAttribute('class', replacements.join(' ')); + }; + + return Utils; +}); + +S2.define('select2/results',[ + 'jquery', + './utils' +], function ($, Utils) { + function Results ($element, options, dataAdapter) { + this.$element = $element; + this.data = dataAdapter; + this.options = options; + + Results.__super__.constructor.call(this); + } + + Utils.Extend(Results, Utils.Observable); + + Results.prototype.render = function () { + var $results = $( + '
      ' + ); + + if (this.options.get('multiple')) { + $results.attr('aria-multiselectable', 'true'); + } + + this.$results = $results; + + return $results; + }; + + Results.prototype.clear = function () { + this.$results.empty(); + }; + + Results.prototype.displayMessage = function (params) { + var escapeMarkup = this.options.get('escapeMarkup'); + + this.clear(); + this.hideLoading(); + + var $message = $( + '' + ); + + var message = this.options.get('translations').get(params.message); + + $message.append( + escapeMarkup( + message(params.args) + ) + ); + + $message[0].className += ' select2-results__message'; + + this.$results.append($message); + }; + + Results.prototype.hideMessages = function () { + this.$results.find('.select2-results__message').remove(); + }; + + Results.prototype.append = function (data) { + this.hideLoading(); + + var $options = []; + + if (data.results == null || data.results.length === 0) { + if (this.$results.children().length === 0) { + this.trigger('results:message', { + message: 'noResults' + }); + } + + return; + } + + data.results = this.sort(data.results); + + for (var d = 0; d < data.results.length; d++) { + var item = data.results[d]; + + var $option = this.option(item); + + $options.push($option); + } + + this.$results.append($options); + }; + + Results.prototype.position = function ($results, $dropdown) { + var $resultsContainer = $dropdown.find('.select2-results'); + $resultsContainer.append($results); + }; + + Results.prototype.sort = function (data) { + var sorter = this.options.get('sorter'); + + return sorter(data); + }; + + Results.prototype.highlightFirstItem = function () { + var $options = this.$results + .find('.select2-results__option--selectable'); + + var $selected = $options.filter('.select2-results__option--selected'); + + // Check if there are any selected options + if ($selected.length > 0) { + // If there are selected options, highlight the first + $selected.first().trigger('mouseenter'); + } else { + // If there are no selected options, highlight the first option + // in the dropdown + $options.first().trigger('mouseenter'); + } + + this.ensureHighlightVisible(); + }; + + Results.prototype.setClasses = function () { + var self = this; + + this.data.current(function (selected) { + var selectedIds = selected.map(function (s) { + return s.id.toString(); + }); + + var $options = self.$results + .find('.select2-results__option--selectable'); + + $options.each(function () { + var $option = $(this); + + var item = Utils.GetData(this, 'data'); + + // id needs to be converted to a string when comparing + var id = '' + item.id; + + if ((item.element != null && item.element.selected) || + (item.element == null && selectedIds.indexOf(id) > -1)) { + this.classList.add('select2-results__option--selected'); + $option.attr('aria-selected', 'true'); + } else { + this.classList.remove('select2-results__option--selected'); + $option.attr('aria-selected', 'false'); + } + }); + + }); + }; + + Results.prototype.showLoading = function (params) { + this.hideLoading(); + + var loadingMore = this.options.get('translations').get('searching'); + + var loading = { + disabled: true, + loading: true, + text: loadingMore(params) + }; + var $loading = this.option(loading); + $loading.className += ' loading-results'; + + this.$results.prepend($loading); + }; + + Results.prototype.hideLoading = function () { + this.$results.find('.loading-results').remove(); + }; + + Results.prototype.option = function (data) { + var option = document.createElement('li'); + option.classList.add('select2-results__option'); + option.classList.add('select2-results__option--selectable'); + + var attrs = { + 'role': 'option' + }; + + var matches = window.Element.prototype.matches || + window.Element.prototype.msMatchesSelector || + window.Element.prototype.webkitMatchesSelector; + + if ((data.element != null && matches.call(data.element, ':disabled')) || + (data.element == null && data.disabled)) { + attrs['aria-disabled'] = 'true'; + + option.classList.remove('select2-results__option--selectable'); + option.classList.add('select2-results__option--disabled'); + } + + if (data.id == null) { + option.classList.remove('select2-results__option--selectable'); + } + + if (data._resultId != null) { + option.id = data._resultId; + } + + if (data.title) { + option.title = data.title; + } + + if (data.children) { + attrs.role = 'group'; + attrs['aria-label'] = data.text; + + option.classList.remove('select2-results__option--selectable'); + option.classList.add('select2-results__option--group'); + } + + for (var attr in attrs) { + var val = attrs[attr]; + + option.setAttribute(attr, val); + } + + if (data.children) { + var $option = $(option); + + var label = document.createElement('strong'); + label.className = 'select2-results__group'; + + this.template(data, label); + + var $children = []; + + for (var c = 0; c < data.children.length; c++) { + var child = data.children[c]; + + var $child = this.option(child); + + $children.push($child); + } + + var $childrenContainer = $('
        ', { + 'class': 'select2-results__options select2-results__options--nested', + 'role': 'none' + }); + + $childrenContainer.append($children); + + $option.append(label); + $option.append($childrenContainer); + } else { + this.template(data, option); + } + + Utils.StoreData(option, 'data', data); + + return option; + }; + + Results.prototype.bind = function (container, $container) { + var self = this; + + var id = container.id + '-results'; + + this.$results.attr('id', id); + + container.on('results:all', function (params) { + self.clear(); + self.append(params.data); + + if (container.isOpen()) { + self.setClasses(); + self.highlightFirstItem(); + } + }); + + container.on('results:append', function (params) { + self.append(params.data); + + if (container.isOpen()) { + self.setClasses(); + } + }); + + container.on('query', function (params) { + self.hideMessages(); + self.showLoading(params); + }); + + container.on('select', function () { + if (!container.isOpen()) { + return; + } + + self.setClasses(); + + if (self.options.get('scrollAfterSelect')) { + self.highlightFirstItem(); + } + }); + + container.on('unselect', function () { + if (!container.isOpen()) { + return; + } + + self.setClasses(); + + if (self.options.get('scrollAfterSelect')) { + self.highlightFirstItem(); + } + }); + + container.on('open', function () { + // When the dropdown is open, aria-expended="true" + self.$results.attr('aria-expanded', 'true'); + self.$results.attr('aria-hidden', 'false'); + + self.setClasses(); + self.ensureHighlightVisible(); + }); + + container.on('close', function () { + // When the dropdown is closed, aria-expended="false" + self.$results.attr('aria-expanded', 'false'); + self.$results.attr('aria-hidden', 'true'); + self.$results.removeAttr('aria-activedescendant'); + }); + + container.on('results:toggle', function () { + var $highlighted = self.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + $highlighted.trigger('mouseup'); + }); + + container.on('results:select', function () { + var $highlighted = self.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + var data = Utils.GetData($highlighted[0], 'data'); + + if ($highlighted.hasClass('select2-results__option--selected')) { + self.trigger('close', {}); + } else { + self.trigger('select', { + data: data + }); + } + }); + + container.on('results:previous', function () { + var $highlighted = self.getHighlightedResults(); + + var $options = self.$results.find('.select2-results__option--selectable'); + + var currentIndex = $options.index($highlighted); + + // If we are already at the top, don't move further + // If no options, currentIndex will be -1 + if (currentIndex <= 0) { + return; + } + + var nextIndex = currentIndex - 1; + + // If none are highlighted, highlight the first + if ($highlighted.length === 0) { + nextIndex = 0; + } + + var $next = $options.eq(nextIndex); + + $next.trigger('mouseenter'); + + var currentOffset = self.$results.offset().top; + var nextTop = $next.offset().top; + var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset); + + if (nextIndex === 0) { + self.$results.scrollTop(0); + } else if (nextTop - currentOffset < 0) { + self.$results.scrollTop(nextOffset); + } + }); + + container.on('results:next', function () { + var $highlighted = self.getHighlightedResults(); + + var $options = self.$results.find('.select2-results__option--selectable'); + + var currentIndex = $options.index($highlighted); + + var nextIndex = currentIndex + 1; + + // If we are at the last option, stay there + if (nextIndex >= $options.length) { + return; + } + + var $next = $options.eq(nextIndex); + + $next.trigger('mouseenter'); + + var currentOffset = self.$results.offset().top + + self.$results.outerHeight(false); + var nextBottom = $next.offset().top + $next.outerHeight(false); + var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset; + + if (nextIndex === 0) { + self.$results.scrollTop(0); + } else if (nextBottom > currentOffset) { + self.$results.scrollTop(nextOffset); + } + }); + + container.on('results:focus', function (params) { + params.element[0].classList.add('select2-results__option--highlighted'); + params.element[0].setAttribute('aria-selected', 'true'); + }); + + container.on('results:message', function (params) { + self.displayMessage(params); + }); + + if ($.fn.mousewheel) { + this.$results.on('mousewheel', function (e) { + var top = self.$results.scrollTop(); + + var bottom = self.$results.get(0).scrollHeight - top + e.deltaY; + + var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0; + var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height(); + + if (isAtTop) { + self.$results.scrollTop(0); + + e.preventDefault(); + e.stopPropagation(); + } else if (isAtBottom) { + self.$results.scrollTop( + self.$results.get(0).scrollHeight - self.$results.height() + ); + + e.preventDefault(); + e.stopPropagation(); + } + }); + } + + this.$results.on('mouseup', '.select2-results__option--selectable', + function (evt) { + var $this = $(this); + + var data = Utils.GetData(this, 'data'); + + if ($this.hasClass('select2-results__option--selected')) { + if (self.options.get('multiple')) { + self.trigger('unselect', { + originalEvent: evt, + data: data + }); + } else { + self.trigger('close', {}); + } + + return; + } + + self.trigger('select', { + originalEvent: evt, + data: data + }); + }); + + this.$results.on('mouseenter', '.select2-results__option--selectable', + function (evt) { + var data = Utils.GetData(this, 'data'); + + self.getHighlightedResults() + .removeClass('select2-results__option--highlighted') + .attr('aria-selected', 'false'); + + self.trigger('results:focus', { + data: data, + element: $(this) + }); + }); + }; + + Results.prototype.getHighlightedResults = function () { + var $highlighted = this.$results + .find('.select2-results__option--highlighted'); + + return $highlighted; + }; + + Results.prototype.destroy = function () { + this.$results.remove(); + }; + + Results.prototype.ensureHighlightVisible = function () { + var $highlighted = this.getHighlightedResults(); + + if ($highlighted.length === 0) { + return; + } + + var $options = this.$results.find('.select2-results__option--selectable'); + + var currentIndex = $options.index($highlighted); + + var currentOffset = this.$results.offset().top; + var nextTop = $highlighted.offset().top; + var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset); + + var offsetDelta = nextTop - currentOffset; + nextOffset -= $highlighted.outerHeight(false) * 2; + + if (currentIndex <= 2) { + this.$results.scrollTop(0); + } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) { + this.$results.scrollTop(nextOffset); + } + }; + + Results.prototype.template = function (result, container) { + var template = this.options.get('templateResult'); + var escapeMarkup = this.options.get('escapeMarkup'); + + var content = template(result, container); + + if (content == null) { + container.style.display = 'none'; + } else if (typeof content === 'string') { + container.innerHTML = escapeMarkup(content); + } else { + $(container).append(content); + } + }; + + return Results; +}); + +S2.define('select2/keys',[ + +], function () { + var KEYS = { + BACKSPACE: 8, + TAB: 9, + ENTER: 13, + SHIFT: 16, + CTRL: 17, + ALT: 18, + ESC: 27, + SPACE: 32, + PAGE_UP: 33, + PAGE_DOWN: 34, + END: 35, + HOME: 36, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + DELETE: 46 + }; + + return KEYS; +}); + +S2.define('select2/selection/base',[ + 'jquery', + '../utils', + '../keys' +], function ($, Utils, KEYS) { + function BaseSelection ($element, options) { + this.$element = $element; + this.options = options; + + BaseSelection.__super__.constructor.call(this); + } + + Utils.Extend(BaseSelection, Utils.Observable); + + BaseSelection.prototype.render = function () { + var $selection = $( + '' + ); + + this._tabindex = 0; + + if (Utils.GetData(this.$element[0], 'old-tabindex') != null) { + this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex'); + } else if (this.$element.attr('tabindex') != null) { + this._tabindex = this.$element.attr('tabindex'); + } + + $selection.attr('title', this.$element.attr('title')); + $selection.attr('tabindex', this._tabindex); + $selection.attr('aria-disabled', 'false'); + + this.$selection = $selection; + + return $selection; + }; + + BaseSelection.prototype.bind = function (container, $container) { + var self = this; + + var resultsId = container.id + '-results'; + + this.container = container; + + this.$selection.on('focus', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('blur', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', function (evt) { + self.trigger('keypress', evt); + + if (evt.which === KEYS.SPACE) { + evt.preventDefault(); + } + }); + + container.on('results:focus', function (params) { + self.$selection.attr('aria-activedescendant', params.data._resultId); + }); + + container.on('selection:update', function (params) { + self.update(params.data); + }); + + container.on('open', function () { + // When the dropdown is open, aria-expanded="true" + self.$selection.attr('aria-expanded', 'true'); + self.$selection.attr('aria-owns', resultsId); + + self._attachCloseHandler(container); + }); + + container.on('close', function () { + // When the dropdown is closed, aria-expanded="false" + self.$selection.attr('aria-expanded', 'false'); + self.$selection.removeAttr('aria-activedescendant'); + self.$selection.removeAttr('aria-owns'); + + self.$selection.trigger('focus'); + + self._detachCloseHandler(container); + }); + + container.on('enable', function () { + self.$selection.attr('tabindex', self._tabindex); + self.$selection.attr('aria-disabled', 'false'); + }); + + container.on('disable', function () { + self.$selection.attr('tabindex', '-1'); + self.$selection.attr('aria-disabled', 'true'); + }); + }; + + BaseSelection.prototype._handleBlur = function (evt) { + var self = this; + + // This needs to be delayed as the active element is the body when the tab + // key is pressed, possibly along with others. + window.setTimeout(function () { + // Don't trigger `blur` if the focus is still in the selection + if ( + (document.activeElement == self.$selection[0]) || + ($.contains(self.$selection[0], document.activeElement)) + ) { + return; + } + + self.trigger('blur', evt); + }, 1); + }; + + BaseSelection.prototype._attachCloseHandler = function (container) { + + $(document.body).on('mousedown.select2.' + container.id, function (e) { + var $target = $(e.target); + + var $select = $target.closest('.select2'); + + var $all = $('.select2.select2-container--open'); + + $all.each(function () { + if (this == $select[0]) { + return; + } + + var $element = Utils.GetData(this, 'element'); + + $element.select2('close'); + }); + }); + }; + + BaseSelection.prototype._detachCloseHandler = function (container) { + $(document.body).off('mousedown.select2.' + container.id); + }; + + BaseSelection.prototype.position = function ($selection, $container) { + var $selectionContainer = $container.find('.selection'); + $selectionContainer.append($selection); + }; + + BaseSelection.prototype.destroy = function () { + this._detachCloseHandler(this.container); + }; + + BaseSelection.prototype.update = function (data) { + throw new Error('The `update` method must be defined in child classes.'); + }; + + /** + * Helper method to abstract the "enabled" (not "disabled") state of this + * object. + * + * @return {true} if the instance is not disabled. + * @return {false} if the instance is disabled. + */ + BaseSelection.prototype.isEnabled = function () { + return !this.isDisabled(); + }; + + /** + * Helper method to abstract the "disabled" state of this object. + * + * @return {true} if the disabled option is true. + * @return {false} if the disabled option is false. + */ + BaseSelection.prototype.isDisabled = function () { + return this.options.get('disabled'); + }; + + return BaseSelection; +}); + +S2.define('select2/selection/single',[ + 'jquery', + './base', + '../utils', + '../keys' +], function ($, BaseSelection, Utils, KEYS) { + function SingleSelection () { + SingleSelection.__super__.constructor.apply(this, arguments); + } + + Utils.Extend(SingleSelection, BaseSelection); + + SingleSelection.prototype.render = function () { + var $selection = SingleSelection.__super__.render.call(this); + + $selection[0].classList.add('select2-selection--single'); + + $selection.html( + ''+ this.placeholder.text +'' + + '' + + '' + + '' + + '' + ); + + return $selection; + }; + + SingleSelection.prototype.bind = function (container, $container) { + var self = this; + + SingleSelection.__super__.bind.apply(this, arguments); + + var id = container.id + '-container'; + + this.$selection.find('.select2-selection__rendered') + .attr('id', id) + .attr('role', 'textbox') + .attr('aria-readonly', 'true'); + this.$selection.attr('aria-labelledby', id); + this.$selection.attr('aria-controls', id); + + this.$selection.on('mousedown', function (evt) { + // Only respond to left clicks + if (evt.which !== 1) { + return; + } + + self.trigger('toggle', { + originalEvent: evt + }); + }); + + this.$selection.on('focus', function (evt) { + // User focuses on the container + }); + + this.$selection.on('blur', function (evt) { + // User exits the container + }); + + container.on('focus', function (evt) { + if (!container.isOpen()) { + self.$selection.trigger('focus'); + } + }); + }; + + SingleSelection.prototype.clear = function () { + var $rendered = this.$selection.find('.select2-selection__rendered'); + $rendered.empty(); + $rendered.removeAttr('title'); // clear tooltip on empty + }; + + SingleSelection.prototype.display = function (data, container) { + var template = this.options.get('templateSelection'); + var escapeMarkup = this.options.get('escapeMarkup'); + + return escapeMarkup(template(data, container)); + }; + + SingleSelection.prototype.selectionContainer = function () { + return $(''); + }; + + SingleSelection.prototype.update = function (data) { + if (data.length === 0) { + this.clear(); + return; + } + + var selection = data[0]; + + var $rendered = this.$selection.find('.select2-selection__rendered'); + var formatted = this.display(selection, $rendered); + + $rendered.empty().append(formatted); + + var title = selection.title || selection.text; + + if (title) { + $rendered.attr('title', title); + } else { + $rendered.removeAttr('title'); + } + }; + + return SingleSelection; +}); + +S2.define('select2/selection/multiple',[ + 'jquery', + './base', + '../utils' +], function ($, BaseSelection, Utils) { + function MultipleSelection ($element, options) { + MultipleSelection.__super__.constructor.apply(this, arguments); + } + + Utils.Extend(MultipleSelection, BaseSelection); + + MultipleSelection.prototype.render = function () { + var $selection = MultipleSelection.__super__.render.call(this); + + $selection[0].classList.add('select2-selection--multiple'); + + $selection.html( + '
          ' + ); + + return $selection; + }; + + MultipleSelection.prototype.bind = function (container, $container) { + var self = this; + + MultipleSelection.__super__.bind.apply(this, arguments); + + var id = container.id + '-container'; + this.$selection.find('.select2-selection__rendered').attr('id', id); + + this.$selection.on('click', function (evt) { + self.trigger('toggle', { + originalEvent: evt + }); + }); + + this.$selection.on( + 'click', + '.select2-selection__choice__remove', + function (evt) { + // Ignore the event if it is disabled + if (self.isDisabled()) { + return; + } + + var $remove = $(this); + var $selection = $remove.parent(); + + var data = Utils.GetData($selection[0], 'data'); + + self.trigger('unselect', { + originalEvent: evt, + data: data + }); + } + ); + + this.$selection.on( + 'keydown', + '.select2-selection__choice__remove', + function (evt) { + // Ignore the event if it is disabled + if (self.isDisabled()) { + return; + } + + evt.stopPropagation(); + } + ); + }; + + MultipleSelection.prototype.clear = function () { + var $rendered = this.$selection.find('.select2-selection__rendered'); + $rendered.empty(); + $rendered.removeAttr('title'); + }; + + MultipleSelection.prototype.display = function (data, container) { + var template = this.options.get('templateSelection'); + var escapeMarkup = this.options.get('escapeMarkup'); + + return escapeMarkup(template(data, container)); + }; + + MultipleSelection.prototype.selectionContainer = function () { + var $container = $( + '
        • ' + + '' + + '' + + '
        • ' + ); + + return $container; + }; + + MultipleSelection.prototype.update = function (data) { + this.clear(); + + if (data.length === 0) { + return; + } + + var $selections = []; + + var selectionIdPrefix = this.$selection.find('.select2-selection__rendered') + .attr('id') + '-choice-'; + + for (var d = 0; d < data.length; d++) { + var selection = data[d]; + + var $selection = this.selectionContainer(); + var formatted = this.display(selection, $selection); + + var selectionId = selectionIdPrefix + Utils.generateChars(4) + '-'; + + if (selection.id) { + selectionId += selection.id; + } else { + selectionId += Utils.generateChars(4); + } + + $selection.find('.select2-selection__choice__display') + .append(formatted) + .attr('id', selectionId); + + var title = selection.title || selection.text; + + if (title) { + $selection.attr('title', title); + } + + var removeItem = this.options.get('translations').get('removeItem'); + + var $remove = $selection.find('.select2-selection__choice__remove'); + + $remove.attr('title', removeItem()); + $remove.attr('aria-label', removeItem()); + $remove.attr('aria-describedby', selectionId); + + Utils.StoreData($selection[0], 'data', selection); + + $selections.push($selection); + } + + var $rendered = this.$selection.find('.select2-selection__rendered'); + + $rendered.append($selections); + }; + + return MultipleSelection; +}); + +S2.define('select2/selection/placeholder',[ + +], function () { + function Placeholder (decorated, $element, options) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options); + } + + Placeholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + Placeholder.prototype.createPlaceholder = function (decorated, placeholder) { + var $placeholder = this.selectionContainer(); + + $placeholder.html(this.display(placeholder)); + $placeholder[0].classList.add('select2-selection__placeholder'); + $placeholder[0].classList.remove('select2-selection__choice'); + + var placeholderTitle = placeholder.title || + placeholder.text || + $placeholder.text(); + + this.$selection.find('.select2-selection__rendered').attr( + 'title', + placeholderTitle + ); + + return $placeholder; + }; + + Placeholder.prototype.update = function (decorated, data) { + var singlePlaceholder = ( + data.length == 1 && data[0].id != this.placeholder.id + ); + var multipleSelections = data.length > 1; + + if (multipleSelections || singlePlaceholder) { + return decorated.call(this, data); + } + + this.clear(); + + var $placeholder = this.createPlaceholder(this.placeholder); + + this.$selection.find('.select2-selection__rendered').append($placeholder); + }; + + return Placeholder; +}); + +S2.define('select2/selection/allowClear',[ + 'jquery', + '../keys', + '../utils' +], function ($, KEYS, Utils) { + function AllowClear () { } + + AllowClear.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + if (this.placeholder == null) { + if (this.options.get('debug') && window.console && console.error) { + console.error( + 'Select2: The `allowClear` option should be used in combination ' + + 'with the `placeholder` option.' + ); + } + } + + this.$selection.on('mousedown', '.select2-selection__clear', + function (evt) { + self._handleClear(evt); + }); + + container.on('keypress', function (evt) { + self._handleKeyboardClear(evt, container); + }); + }; + + AllowClear.prototype._handleClear = function (_, evt) { + // Ignore the event if it is disabled + if (this.isDisabled()) { + return; + } + + var $clear = this.$selection.find('.select2-selection__clear'); + + // Ignore the event if nothing has been selected + if ($clear.length === 0) { + return; + } + + evt.stopPropagation(); + + var data = Utils.GetData($clear[0], 'data'); + + var previousVal = this.$element.val(); + this.$element.val(this.placeholder.id); + + var unselectData = { + data: data + }; + this.trigger('clear', unselectData); + if (unselectData.prevented) { + this.$element.val(previousVal); + return; + } + + for (var d = 0; d < data.length; d++) { + unselectData = { + data: data[d] + }; + + // Trigger the `unselect` event, so people can prevent it from being + // cleared. + this.trigger('unselect', unselectData); + + // If the event was prevented, don't clear it out. + if (unselectData.prevented) { + this.$element.val(previousVal); + return; + } + } + + this.$element.trigger('input').trigger('change'); + + this.trigger('toggle', {}); + }; + + AllowClear.prototype._handleKeyboardClear = function (_, evt, container) { + if (container.isOpen()) { + return; + } + + if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) { + this._handleClear(evt); + } + }; + + AllowClear.prototype.update = function (decorated, data) { + decorated.call(this, data); + + this.$selection.find('.select2-selection__clear').remove(); + this.$selection[0].classList.remove('select2-selection--clearable'); + + if (this.$selection.find('.select2-selection__placeholder').length > 0 || + data.length === 0) { + return; + } + + var selectionId = this.$selection.find('.select2-selection__rendered') + .attr('id'); + + var removeAll = this.options.get('translations').get('removeAllItems'); + + var $remove = $( + '' + ); + $remove.attr('title', removeAll()); + $remove.attr('aria-label', removeAll()); + $remove.attr('aria-describedby', selectionId); + Utils.StoreData($remove[0], 'data', data); + + this.$selection.prepend($remove); + this.$selection[0].classList.add('select2-selection--clearable'); + }; + + return AllowClear; +}); + +S2.define('select2/selection/search',[ + 'jquery', + '../utils', + '../keys' +], function ($, Utils, KEYS) { + function Search (decorated, $element, options) { + decorated.call(this, $element, options); + } + + Search.prototype.render = function (decorated) { + var searchLabel = this.options.get('translations').get('search'); + var $search = $( + '' + + '' + + '' + ); + + this.$searchContainer = $search; + this.$search = $search.find('textarea'); + + this.$search.prop('autocomplete', this.options.get('autocomplete')); + this.$search.attr('aria-label', searchLabel()); + + var $rendered = decorated.call(this); + + this._transferTabIndex(); + $rendered.append(this.$searchContainer); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + var resultsId = container.id + '-results'; + var selectionId = container.id + '-container'; + + decorated.call(this, container, $container); + + self.$search.attr('aria-describedby', selectionId); + + container.on('open', function () { + self.$search.attr('aria-controls', resultsId); + self.$search.trigger('focus'); + }); + + container.on('close', function () { + self.$search.val(''); + self.resizeSearch(); + self.$search.removeAttr('aria-controls'); + self.$search.removeAttr('aria-activedescendant'); + self.$search.trigger('focus'); + }); + + container.on('enable', function () { + self.$search.prop('disabled', false); + + self._transferTabIndex(); + }); + + container.on('disable', function () { + self.$search.prop('disabled', true); + }); + + container.on('focus', function (evt) { + self.$search.trigger('focus'); + }); + + container.on('results:focus', function (params) { + if (params.data._resultId) { + self.$search.attr('aria-activedescendant', params.data._resultId); + } else { + self.$search.removeAttr('aria-activedescendant'); + } + }); + + this.$selection.on('focusin', '.select2-search--inline', function (evt) { + self.trigger('focus', evt); + }); + + this.$selection.on('focusout', '.select2-search--inline', function (evt) { + self._handleBlur(evt); + }); + + this.$selection.on('keydown', '.select2-search--inline', function (evt) { + evt.stopPropagation(); + + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + + var key = evt.which; + + if (key === KEYS.BACKSPACE && self.$search.val() === '') { + var $previousChoice = self.$selection + .find('.select2-selection__choice').last(); + + if ($previousChoice.length > 0) { + var item = Utils.GetData($previousChoice[0], 'data'); + + self.searchRemoveChoice(item); + + evt.preventDefault(); + } + } + }); + + this.$selection.on('click', '.select2-search--inline', function (evt) { + if (self.$search.val()) { + evt.stopPropagation(); + } + }); + + // Try to detect the IE version should the `documentMode` property that + // is stored on the document. This is only implemented in IE and is + // slightly cleaner than doing a user agent check. + // This property is not available in Edge, but Edge also doesn't have + // this bug. + var msie = document.documentMode; + var disableInputEvents = msie && msie <= 11; + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$selection.on( + 'input.searchcheck', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents) { + self.$selection.off('input.search input.searchcheck'); + return; + } + + // Unbind the duplicated `keyup` event + self.$selection.off('keyup.search'); + } + ); + + this.$selection.on( + 'keyup.search input.search', + '.select2-search--inline', + function (evt) { + // IE will trigger the `input` event when a placeholder is used on a + // search box. To get around this issue, we are forced to ignore all + // `input` events in IE and keep using `keyup`. + if (disableInputEvents && evt.type === 'input') { + self.$selection.off('input.search input.searchcheck'); + return; + } + + var key = evt.which; + + // We can freely ignore events from modifier keys + if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) { + return; + } + + // Tabbing will be handled during the `keydown` phase + if (key == KEYS.TAB) { + return; + } + + self.handleSearch(evt); + } + ); + }; + + /** + * This method will transfer the tabindex attribute from the rendered + * selection to the search box. This allows for the search box to be used as + * the primary focus instead of the selection container. + * + * @private + */ + Search.prototype._transferTabIndex = function (decorated) { + this.$search.attr('tabindex', this.$selection.attr('tabindex')); + this.$selection.attr('tabindex', '-1'); + }; + + Search.prototype.createPlaceholder = function (decorated, placeholder) { + this.$search.attr('placeholder', placeholder.text); + }; + + Search.prototype.update = function (decorated, data) { + var searchHadFocus = this.$search[0] == document.activeElement; + + this.$search.attr('placeholder', ''); + + decorated.call(this, data); + + this.resizeSearch(); + if (searchHadFocus) { + this.$search.trigger('focus'); + } + }; + + Search.prototype.handleSearch = function () { + this.resizeSearch(); + + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.searchRemoveChoice = function (decorated, item) { + this.trigger('unselect', { + data: item + }); + + this.$search.val(item.text); + this.handleSearch(); + }; + + Search.prototype.resizeSearch = function () { + this.$search.css('width', '25px'); + + var width = '100%'; + + if (this.$search.attr('placeholder') === '') { + var minimumWidth = this.$search.val().length + 1; + + width = (minimumWidth * 0.75) + 'em'; + } + + this.$search.css('width', width); + }; + + return Search; +}); + +S2.define('select2/selection/selectionCss',[ + '../utils' +], function (Utils) { + function SelectionCSS () { } + + SelectionCSS.prototype.render = function (decorated) { + var $selection = decorated.call(this); + + var selectionCssClass = this.options.get('selectionCssClass') || ''; + + if (selectionCssClass.indexOf(':all:') !== -1) { + selectionCssClass = selectionCssClass.replace(':all:', ''); + + Utils.copyNonInternalCssClasses($selection[0], this.$element[0]); + } + + $selection.addClass(selectionCssClass); + + return $selection; + }; + + return SelectionCSS; +}); + +S2.define('select2/selection/eventRelay',[ + 'jquery' +], function ($) { + function EventRelay () { } + + EventRelay.prototype.bind = function (decorated, container, $container) { + var self = this; + var relayEvents = [ + 'open', 'opening', + 'close', 'closing', + 'select', 'selecting', + 'unselect', 'unselecting', + 'clear', 'clearing' + ]; + + var preventableEvents = [ + 'opening', 'closing', 'selecting', 'unselecting', 'clearing' + ]; + + decorated.call(this, container, $container); + + container.on('*', function (name, params) { + // Ignore events that should not be relayed + if (relayEvents.indexOf(name) === -1) { + return; + } + + // The parameters should always be an object + params = params || {}; + + // Generate the jQuery event for the Select2 event + var evt = $.Event('select2:' + name, { + params: params + }); + + self.$element.trigger(evt); + + // Only handle preventable events if it was one + if (preventableEvents.indexOf(name) === -1) { + return; + } + + params.prevented = evt.isDefaultPrevented(); + }); + }; + + return EventRelay; +}); + +S2.define('select2/translation',[ + 'jquery', + 'require' +], function ($, require) { + function Translation (dict) { + this.dict = dict || {}; + } + + Translation.prototype.all = function () { + return this.dict; + }; + + Translation.prototype.get = function (key) { + return this.dict[key]; + }; + + Translation.prototype.extend = function (translation) { + this.dict = $.extend({}, translation.all(), this.dict); + }; + + // Static functions + + Translation._cache = {}; + + Translation.loadPath = function (path) { + if (!(path in Translation._cache)) { + var translations = require(path); + + Translation._cache[path] = translations; + } + + return new Translation(Translation._cache[path]); + }; + + return Translation; +}); + +S2.define('select2/diacritics',[ + +], function () { + var diacritics = { + '\u24B6': 'A', + '\uFF21': 'A', + '\u00C0': 'A', + '\u00C1': 'A', + '\u00C2': 'A', + '\u1EA6': 'A', + '\u1EA4': 'A', + '\u1EAA': 'A', + '\u1EA8': 'A', + '\u00C3': 'A', + '\u0100': 'A', + '\u0102': 'A', + '\u1EB0': 'A', + '\u1EAE': 'A', + '\u1EB4': 'A', + '\u1EB2': 'A', + '\u0226': 'A', + '\u01E0': 'A', + '\u00C4': 'A', + '\u01DE': 'A', + '\u1EA2': 'A', + '\u00C5': 'A', + '\u01FA': 'A', + '\u01CD': 'A', + '\u0200': 'A', + '\u0202': 'A', + '\u1EA0': 'A', + '\u1EAC': 'A', + '\u1EB6': 'A', + '\u1E00': 'A', + '\u0104': 'A', + '\u023A': 'A', + '\u2C6F': 'A', + '\uA732': 'AA', + '\u00C6': 'AE', + '\u01FC': 'AE', + '\u01E2': 'AE', + '\uA734': 'AO', + '\uA736': 'AU', + '\uA738': 'AV', + '\uA73A': 'AV', + '\uA73C': 'AY', + '\u24B7': 'B', + '\uFF22': 'B', + '\u1E02': 'B', + '\u1E04': 'B', + '\u1E06': 'B', + '\u0243': 'B', + '\u0182': 'B', + '\u0181': 'B', + '\u24B8': 'C', + '\uFF23': 'C', + '\u0106': 'C', + '\u0108': 'C', + '\u010A': 'C', + '\u010C': 'C', + '\u00C7': 'C', + '\u1E08': 'C', + '\u0187': 'C', + '\u023B': 'C', + '\uA73E': 'C', + '\u24B9': 'D', + '\uFF24': 'D', + '\u1E0A': 'D', + '\u010E': 'D', + '\u1E0C': 'D', + '\u1E10': 'D', + '\u1E12': 'D', + '\u1E0E': 'D', + '\u0110': 'D', + '\u018B': 'D', + '\u018A': 'D', + '\u0189': 'D', + '\uA779': 'D', + '\u01F1': 'DZ', + '\u01C4': 'DZ', + '\u01F2': 'Dz', + '\u01C5': 'Dz', + '\u24BA': 'E', + '\uFF25': 'E', + '\u00C8': 'E', + '\u00C9': 'E', + '\u00CA': 'E', + '\u1EC0': 'E', + '\u1EBE': 'E', + '\u1EC4': 'E', + '\u1EC2': 'E', + '\u1EBC': 'E', + '\u0112': 'E', + '\u1E14': 'E', + '\u1E16': 'E', + '\u0114': 'E', + '\u0116': 'E', + '\u00CB': 'E', + '\u1EBA': 'E', + '\u011A': 'E', + '\u0204': 'E', + '\u0206': 'E', + '\u1EB8': 'E', + '\u1EC6': 'E', + '\u0228': 'E', + '\u1E1C': 'E', + '\u0118': 'E', + '\u1E18': 'E', + '\u1E1A': 'E', + '\u0190': 'E', + '\u018E': 'E', + '\u24BB': 'F', + '\uFF26': 'F', + '\u1E1E': 'F', + '\u0191': 'F', + '\uA77B': 'F', + '\u24BC': 'G', + '\uFF27': 'G', + '\u01F4': 'G', + '\u011C': 'G', + '\u1E20': 'G', + '\u011E': 'G', + '\u0120': 'G', + '\u01E6': 'G', + '\u0122': 'G', + '\u01E4': 'G', + '\u0193': 'G', + '\uA7A0': 'G', + '\uA77D': 'G', + '\uA77E': 'G', + '\u24BD': 'H', + '\uFF28': 'H', + '\u0124': 'H', + '\u1E22': 'H', + '\u1E26': 'H', + '\u021E': 'H', + '\u1E24': 'H', + '\u1E28': 'H', + '\u1E2A': 'H', + '\u0126': 'H', + '\u2C67': 'H', + '\u2C75': 'H', + '\uA78D': 'H', + '\u24BE': 'I', + '\uFF29': 'I', + '\u00CC': 'I', + '\u00CD': 'I', + '\u00CE': 'I', + '\u0128': 'I', + '\u012A': 'I', + '\u012C': 'I', + '\u0130': 'I', + '\u00CF': 'I', + '\u1E2E': 'I', + '\u1EC8': 'I', + '\u01CF': 'I', + '\u0208': 'I', + '\u020A': 'I', + '\u1ECA': 'I', + '\u012E': 'I', + '\u1E2C': 'I', + '\u0197': 'I', + '\u24BF': 'J', + '\uFF2A': 'J', + '\u0134': 'J', + '\u0248': 'J', + '\u24C0': 'K', + '\uFF2B': 'K', + '\u1E30': 'K', + '\u01E8': 'K', + '\u1E32': 'K', + '\u0136': 'K', + '\u1E34': 'K', + '\u0198': 'K', + '\u2C69': 'K', + '\uA740': 'K', + '\uA742': 'K', + '\uA744': 'K', + '\uA7A2': 'K', + '\u24C1': 'L', + '\uFF2C': 'L', + '\u013F': 'L', + '\u0139': 'L', + '\u013D': 'L', + '\u1E36': 'L', + '\u1E38': 'L', + '\u013B': 'L', + '\u1E3C': 'L', + '\u1E3A': 'L', + '\u0141': 'L', + '\u023D': 'L', + '\u2C62': 'L', + '\u2C60': 'L', + '\uA748': 'L', + '\uA746': 'L', + '\uA780': 'L', + '\u01C7': 'LJ', + '\u01C8': 'Lj', + '\u24C2': 'M', + '\uFF2D': 'M', + '\u1E3E': 'M', + '\u1E40': 'M', + '\u1E42': 'M', + '\u2C6E': 'M', + '\u019C': 'M', + '\u24C3': 'N', + '\uFF2E': 'N', + '\u01F8': 'N', + '\u0143': 'N', + '\u00D1': 'N', + '\u1E44': 'N', + '\u0147': 'N', + '\u1E46': 'N', + '\u0145': 'N', + '\u1E4A': 'N', + '\u1E48': 'N', + '\u0220': 'N', + '\u019D': 'N', + '\uA790': 'N', + '\uA7A4': 'N', + '\u01CA': 'NJ', + '\u01CB': 'Nj', + '\u24C4': 'O', + '\uFF2F': 'O', + '\u00D2': 'O', + '\u00D3': 'O', + '\u00D4': 'O', + '\u1ED2': 'O', + '\u1ED0': 'O', + '\u1ED6': 'O', + '\u1ED4': 'O', + '\u00D5': 'O', + '\u1E4C': 'O', + '\u022C': 'O', + '\u1E4E': 'O', + '\u014C': 'O', + '\u1E50': 'O', + '\u1E52': 'O', + '\u014E': 'O', + '\u022E': 'O', + '\u0230': 'O', + '\u00D6': 'O', + '\u022A': 'O', + '\u1ECE': 'O', + '\u0150': 'O', + '\u01D1': 'O', + '\u020C': 'O', + '\u020E': 'O', + '\u01A0': 'O', + '\u1EDC': 'O', + '\u1EDA': 'O', + '\u1EE0': 'O', + '\u1EDE': 'O', + '\u1EE2': 'O', + '\u1ECC': 'O', + '\u1ED8': 'O', + '\u01EA': 'O', + '\u01EC': 'O', + '\u00D8': 'O', + '\u01FE': 'O', + '\u0186': 'O', + '\u019F': 'O', + '\uA74A': 'O', + '\uA74C': 'O', + '\u0152': 'OE', + '\u01A2': 'OI', + '\uA74E': 'OO', + '\u0222': 'OU', + '\u24C5': 'P', + '\uFF30': 'P', + '\u1E54': 'P', + '\u1E56': 'P', + '\u01A4': 'P', + '\u2C63': 'P', + '\uA750': 'P', + '\uA752': 'P', + '\uA754': 'P', + '\u24C6': 'Q', + '\uFF31': 'Q', + '\uA756': 'Q', + '\uA758': 'Q', + '\u024A': 'Q', + '\u24C7': 'R', + '\uFF32': 'R', + '\u0154': 'R', + '\u1E58': 'R', + '\u0158': 'R', + '\u0210': 'R', + '\u0212': 'R', + '\u1E5A': 'R', + '\u1E5C': 'R', + '\u0156': 'R', + '\u1E5E': 'R', + '\u024C': 'R', + '\u2C64': 'R', + '\uA75A': 'R', + '\uA7A6': 'R', + '\uA782': 'R', + '\u24C8': 'S', + '\uFF33': 'S', + '\u1E9E': 'S', + '\u015A': 'S', + '\u1E64': 'S', + '\u015C': 'S', + '\u1E60': 'S', + '\u0160': 'S', + '\u1E66': 'S', + '\u1E62': 'S', + '\u1E68': 'S', + '\u0218': 'S', + '\u015E': 'S', + '\u2C7E': 'S', + '\uA7A8': 'S', + '\uA784': 'S', + '\u24C9': 'T', + '\uFF34': 'T', + '\u1E6A': 'T', + '\u0164': 'T', + '\u1E6C': 'T', + '\u021A': 'T', + '\u0162': 'T', + '\u1E70': 'T', + '\u1E6E': 'T', + '\u0166': 'T', + '\u01AC': 'T', + '\u01AE': 'T', + '\u023E': 'T', + '\uA786': 'T', + '\uA728': 'TZ', + '\u24CA': 'U', + '\uFF35': 'U', + '\u00D9': 'U', + '\u00DA': 'U', + '\u00DB': 'U', + '\u0168': 'U', + '\u1E78': 'U', + '\u016A': 'U', + '\u1E7A': 'U', + '\u016C': 'U', + '\u00DC': 'U', + '\u01DB': 'U', + '\u01D7': 'U', + '\u01D5': 'U', + '\u01D9': 'U', + '\u1EE6': 'U', + '\u016E': 'U', + '\u0170': 'U', + '\u01D3': 'U', + '\u0214': 'U', + '\u0216': 'U', + '\u01AF': 'U', + '\u1EEA': 'U', + '\u1EE8': 'U', + '\u1EEE': 'U', + '\u1EEC': 'U', + '\u1EF0': 'U', + '\u1EE4': 'U', + '\u1E72': 'U', + '\u0172': 'U', + '\u1E76': 'U', + '\u1E74': 'U', + '\u0244': 'U', + '\u24CB': 'V', + '\uFF36': 'V', + '\u1E7C': 'V', + '\u1E7E': 'V', + '\u01B2': 'V', + '\uA75E': 'V', + '\u0245': 'V', + '\uA760': 'VY', + '\u24CC': 'W', + '\uFF37': 'W', + '\u1E80': 'W', + '\u1E82': 'W', + '\u0174': 'W', + '\u1E86': 'W', + '\u1E84': 'W', + '\u1E88': 'W', + '\u2C72': 'W', + '\u24CD': 'X', + '\uFF38': 'X', + '\u1E8A': 'X', + '\u1E8C': 'X', + '\u24CE': 'Y', + '\uFF39': 'Y', + '\u1EF2': 'Y', + '\u00DD': 'Y', + '\u0176': 'Y', + '\u1EF8': 'Y', + '\u0232': 'Y', + '\u1E8E': 'Y', + '\u0178': 'Y', + '\u1EF6': 'Y', + '\u1EF4': 'Y', + '\u01B3': 'Y', + '\u024E': 'Y', + '\u1EFE': 'Y', + '\u24CF': 'Z', + '\uFF3A': 'Z', + '\u0179': 'Z', + '\u1E90': 'Z', + '\u017B': 'Z', + '\u017D': 'Z', + '\u1E92': 'Z', + '\u1E94': 'Z', + '\u01B5': 'Z', + '\u0224': 'Z', + '\u2C7F': 'Z', + '\u2C6B': 'Z', + '\uA762': 'Z', + '\u24D0': 'a', + '\uFF41': 'a', + '\u1E9A': 'a', + '\u00E0': 'a', + '\u00E1': 'a', + '\u00E2': 'a', + '\u1EA7': 'a', + '\u1EA5': 'a', + '\u1EAB': 'a', + '\u1EA9': 'a', + '\u00E3': 'a', + '\u0101': 'a', + '\u0103': 'a', + '\u1EB1': 'a', + '\u1EAF': 'a', + '\u1EB5': 'a', + '\u1EB3': 'a', + '\u0227': 'a', + '\u01E1': 'a', + '\u00E4': 'a', + '\u01DF': 'a', + '\u1EA3': 'a', + '\u00E5': 'a', + '\u01FB': 'a', + '\u01CE': 'a', + '\u0201': 'a', + '\u0203': 'a', + '\u1EA1': 'a', + '\u1EAD': 'a', + '\u1EB7': 'a', + '\u1E01': 'a', + '\u0105': 'a', + '\u2C65': 'a', + '\u0250': 'a', + '\uA733': 'aa', + '\u00E6': 'ae', + '\u01FD': 'ae', + '\u01E3': 'ae', + '\uA735': 'ao', + '\uA737': 'au', + '\uA739': 'av', + '\uA73B': 'av', + '\uA73D': 'ay', + '\u24D1': 'b', + '\uFF42': 'b', + '\u1E03': 'b', + '\u1E05': 'b', + '\u1E07': 'b', + '\u0180': 'b', + '\u0183': 'b', + '\u0253': 'b', + '\u24D2': 'c', + '\uFF43': 'c', + '\u0107': 'c', + '\u0109': 'c', + '\u010B': 'c', + '\u010D': 'c', + '\u00E7': 'c', + '\u1E09': 'c', + '\u0188': 'c', + '\u023C': 'c', + '\uA73F': 'c', + '\u2184': 'c', + '\u24D3': 'd', + '\uFF44': 'd', + '\u1E0B': 'd', + '\u010F': 'd', + '\u1E0D': 'd', + '\u1E11': 'd', + '\u1E13': 'd', + '\u1E0F': 'd', + '\u0111': 'd', + '\u018C': 'd', + '\u0256': 'd', + '\u0257': 'd', + '\uA77A': 'd', + '\u01F3': 'dz', + '\u01C6': 'dz', + '\u24D4': 'e', + '\uFF45': 'e', + '\u00E8': 'e', + '\u00E9': 'e', + '\u00EA': 'e', + '\u1EC1': 'e', + '\u1EBF': 'e', + '\u1EC5': 'e', + '\u1EC3': 'e', + '\u1EBD': 'e', + '\u0113': 'e', + '\u1E15': 'e', + '\u1E17': 'e', + '\u0115': 'e', + '\u0117': 'e', + '\u00EB': 'e', + '\u1EBB': 'e', + '\u011B': 'e', + '\u0205': 'e', + '\u0207': 'e', + '\u1EB9': 'e', + '\u1EC7': 'e', + '\u0229': 'e', + '\u1E1D': 'e', + '\u0119': 'e', + '\u1E19': 'e', + '\u1E1B': 'e', + '\u0247': 'e', + '\u025B': 'e', + '\u01DD': 'e', + '\u24D5': 'f', + '\uFF46': 'f', + '\u1E1F': 'f', + '\u0192': 'f', + '\uA77C': 'f', + '\u24D6': 'g', + '\uFF47': 'g', + '\u01F5': 'g', + '\u011D': 'g', + '\u1E21': 'g', + '\u011F': 'g', + '\u0121': 'g', + '\u01E7': 'g', + '\u0123': 'g', + '\u01E5': 'g', + '\u0260': 'g', + '\uA7A1': 'g', + '\u1D79': 'g', + '\uA77F': 'g', + '\u24D7': 'h', + '\uFF48': 'h', + '\u0125': 'h', + '\u1E23': 'h', + '\u1E27': 'h', + '\u021F': 'h', + '\u1E25': 'h', + '\u1E29': 'h', + '\u1E2B': 'h', + '\u1E96': 'h', + '\u0127': 'h', + '\u2C68': 'h', + '\u2C76': 'h', + '\u0265': 'h', + '\u0195': 'hv', + '\u24D8': 'i', + '\uFF49': 'i', + '\u00EC': 'i', + '\u00ED': 'i', + '\u00EE': 'i', + '\u0129': 'i', + '\u012B': 'i', + '\u012D': 'i', + '\u00EF': 'i', + '\u1E2F': 'i', + '\u1EC9': 'i', + '\u01D0': 'i', + '\u0209': 'i', + '\u020B': 'i', + '\u1ECB': 'i', + '\u012F': 'i', + '\u1E2D': 'i', + '\u0268': 'i', + '\u0131': 'i', + '\u24D9': 'j', + '\uFF4A': 'j', + '\u0135': 'j', + '\u01F0': 'j', + '\u0249': 'j', + '\u24DA': 'k', + '\uFF4B': 'k', + '\u1E31': 'k', + '\u01E9': 'k', + '\u1E33': 'k', + '\u0137': 'k', + '\u1E35': 'k', + '\u0199': 'k', + '\u2C6A': 'k', + '\uA741': 'k', + '\uA743': 'k', + '\uA745': 'k', + '\uA7A3': 'k', + '\u24DB': 'l', + '\uFF4C': 'l', + '\u0140': 'l', + '\u013A': 'l', + '\u013E': 'l', + '\u1E37': 'l', + '\u1E39': 'l', + '\u013C': 'l', + '\u1E3D': 'l', + '\u1E3B': 'l', + '\u017F': 'l', + '\u0142': 'l', + '\u019A': 'l', + '\u026B': 'l', + '\u2C61': 'l', + '\uA749': 'l', + '\uA781': 'l', + '\uA747': 'l', + '\u01C9': 'lj', + '\u24DC': 'm', + '\uFF4D': 'm', + '\u1E3F': 'm', + '\u1E41': 'm', + '\u1E43': 'm', + '\u0271': 'm', + '\u026F': 'm', + '\u24DD': 'n', + '\uFF4E': 'n', + '\u01F9': 'n', + '\u0144': 'n', + '\u00F1': 'n', + '\u1E45': 'n', + '\u0148': 'n', + '\u1E47': 'n', + '\u0146': 'n', + '\u1E4B': 'n', + '\u1E49': 'n', + '\u019E': 'n', + '\u0272': 'n', + '\u0149': 'n', + '\uA791': 'n', + '\uA7A5': 'n', + '\u01CC': 'nj', + '\u24DE': 'o', + '\uFF4F': 'o', + '\u00F2': 'o', + '\u00F3': 'o', + '\u00F4': 'o', + '\u1ED3': 'o', + '\u1ED1': 'o', + '\u1ED7': 'o', + '\u1ED5': 'o', + '\u00F5': 'o', + '\u1E4D': 'o', + '\u022D': 'o', + '\u1E4F': 'o', + '\u014D': 'o', + '\u1E51': 'o', + '\u1E53': 'o', + '\u014F': 'o', + '\u022F': 'o', + '\u0231': 'o', + '\u00F6': 'o', + '\u022B': 'o', + '\u1ECF': 'o', + '\u0151': 'o', + '\u01D2': 'o', + '\u020D': 'o', + '\u020F': 'o', + '\u01A1': 'o', + '\u1EDD': 'o', + '\u1EDB': 'o', + '\u1EE1': 'o', + '\u1EDF': 'o', + '\u1EE3': 'o', + '\u1ECD': 'o', + '\u1ED9': 'o', + '\u01EB': 'o', + '\u01ED': 'o', + '\u00F8': 'o', + '\u01FF': 'o', + '\u0254': 'o', + '\uA74B': 'o', + '\uA74D': 'o', + '\u0275': 'o', + '\u0153': 'oe', + '\u01A3': 'oi', + '\u0223': 'ou', + '\uA74F': 'oo', + '\u24DF': 'p', + '\uFF50': 'p', + '\u1E55': 'p', + '\u1E57': 'p', + '\u01A5': 'p', + '\u1D7D': 'p', + '\uA751': 'p', + '\uA753': 'p', + '\uA755': 'p', + '\u24E0': 'q', + '\uFF51': 'q', + '\u024B': 'q', + '\uA757': 'q', + '\uA759': 'q', + '\u24E1': 'r', + '\uFF52': 'r', + '\u0155': 'r', + '\u1E59': 'r', + '\u0159': 'r', + '\u0211': 'r', + '\u0213': 'r', + '\u1E5B': 'r', + '\u1E5D': 'r', + '\u0157': 'r', + '\u1E5F': 'r', + '\u024D': 'r', + '\u027D': 'r', + '\uA75B': 'r', + '\uA7A7': 'r', + '\uA783': 'r', + '\u24E2': 's', + '\uFF53': 's', + '\u00DF': 's', + '\u015B': 's', + '\u1E65': 's', + '\u015D': 's', + '\u1E61': 's', + '\u0161': 's', + '\u1E67': 's', + '\u1E63': 's', + '\u1E69': 's', + '\u0219': 's', + '\u015F': 's', + '\u023F': 's', + '\uA7A9': 's', + '\uA785': 's', + '\u1E9B': 's', + '\u24E3': 't', + '\uFF54': 't', + '\u1E6B': 't', + '\u1E97': 't', + '\u0165': 't', + '\u1E6D': 't', + '\u021B': 't', + '\u0163': 't', + '\u1E71': 't', + '\u1E6F': 't', + '\u0167': 't', + '\u01AD': 't', + '\u0288': 't', + '\u2C66': 't', + '\uA787': 't', + '\uA729': 'tz', + '\u24E4': 'u', + '\uFF55': 'u', + '\u00F9': 'u', + '\u00FA': 'u', + '\u00FB': 'u', + '\u0169': 'u', + '\u1E79': 'u', + '\u016B': 'u', + '\u1E7B': 'u', + '\u016D': 'u', + '\u00FC': 'u', + '\u01DC': 'u', + '\u01D8': 'u', + '\u01D6': 'u', + '\u01DA': 'u', + '\u1EE7': 'u', + '\u016F': 'u', + '\u0171': 'u', + '\u01D4': 'u', + '\u0215': 'u', + '\u0217': 'u', + '\u01B0': 'u', + '\u1EEB': 'u', + '\u1EE9': 'u', + '\u1EEF': 'u', + '\u1EED': 'u', + '\u1EF1': 'u', + '\u1EE5': 'u', + '\u1E73': 'u', + '\u0173': 'u', + '\u1E77': 'u', + '\u1E75': 'u', + '\u0289': 'u', + '\u24E5': 'v', + '\uFF56': 'v', + '\u1E7D': 'v', + '\u1E7F': 'v', + '\u028B': 'v', + '\uA75F': 'v', + '\u028C': 'v', + '\uA761': 'vy', + '\u24E6': 'w', + '\uFF57': 'w', + '\u1E81': 'w', + '\u1E83': 'w', + '\u0175': 'w', + '\u1E87': 'w', + '\u1E85': 'w', + '\u1E98': 'w', + '\u1E89': 'w', + '\u2C73': 'w', + '\u24E7': 'x', + '\uFF58': 'x', + '\u1E8B': 'x', + '\u1E8D': 'x', + '\u24E8': 'y', + '\uFF59': 'y', + '\u1EF3': 'y', + '\u00FD': 'y', + '\u0177': 'y', + '\u1EF9': 'y', + '\u0233': 'y', + '\u1E8F': 'y', + '\u00FF': 'y', + '\u1EF7': 'y', + '\u1E99': 'y', + '\u1EF5': 'y', + '\u01B4': 'y', + '\u024F': 'y', + '\u1EFF': 'y', + '\u24E9': 'z', + '\uFF5A': 'z', + '\u017A': 'z', + '\u1E91': 'z', + '\u017C': 'z', + '\u017E': 'z', + '\u1E93': 'z', + '\u1E95': 'z', + '\u01B6': 'z', + '\u0225': 'z', + '\u0240': 'z', + '\u2C6C': 'z', + '\uA763': 'z', + '\u0386': '\u0391', + '\u0388': '\u0395', + '\u0389': '\u0397', + '\u038A': '\u0399', + '\u03AA': '\u0399', + '\u038C': '\u039F', + '\u038E': '\u03A5', + '\u03AB': '\u03A5', + '\u038F': '\u03A9', + '\u03AC': '\u03B1', + '\u03AD': '\u03B5', + '\u03AE': '\u03B7', + '\u03AF': '\u03B9', + '\u03CA': '\u03B9', + '\u0390': '\u03B9', + '\u03CC': '\u03BF', + '\u03CD': '\u03C5', + '\u03CB': '\u03C5', + '\u03B0': '\u03C5', + '\u03CE': '\u03C9', + '\u03C2': '\u03C3', + '\u2019': '\'' + }; + + return diacritics; +}); + +S2.define('select2/data/base',[ + '../utils' +], function (Utils) { + function BaseAdapter ($element, options) { + BaseAdapter.__super__.constructor.call(this); + } + + Utils.Extend(BaseAdapter, Utils.Observable); + + BaseAdapter.prototype.current = function (callback) { + throw new Error('The `current` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.query = function (params, callback) { + throw new Error('The `query` method must be defined in child classes.'); + }; + + BaseAdapter.prototype.bind = function (container, $container) { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.destroy = function () { + // Can be implemented in subclasses + }; + + BaseAdapter.prototype.generateResultId = function (container, data) { + var id = container.id + '-result-'; + + id += Utils.generateChars(4); + + if (data.id != null) { + id += '-' + data.id.toString(); + } else { + id += '-' + Utils.generateChars(4); + } + return id; + }; + + return BaseAdapter; +}); + +S2.define('select2/data/select',[ + './base', + '../utils', + 'jquery' +], function (BaseAdapter, Utils, $) { + function SelectAdapter ($element, options) { + this.$element = $element; + this.options = options; + + SelectAdapter.__super__.constructor.call(this); + } + + Utils.Extend(SelectAdapter, BaseAdapter); + + SelectAdapter.prototype.current = function (callback) { + var self = this; + + var data = Array.prototype.map.call( + this.$element[0].querySelectorAll(':checked'), + function (selectedElement) { + return self.item($(selectedElement)); + } + ); + + callback(data); + }; + + SelectAdapter.prototype.select = function (data) { + var self = this; + + data.selected = true; + + // If data.element is a DOM node, use it instead + if ( + data.element != null && data.element.tagName.toLowerCase() === 'option' + ) { + data.element.selected = true; + + this.$element.trigger('input').trigger('change'); + + return; + } + + if (this.$element.prop('multiple')) { + this.current(function (currentData) { + var val = []; + + data = [data]; + data.push.apply(data, currentData); + + for (var d = 0; d < data.length; d++) { + var id = data[d].id; + + if (val.indexOf(id) === -1) { + val.push(id); + } + } + + self.$element.val(val); + self.$element.trigger('input').trigger('change'); + }); + } else { + var val = data.id; + + this.$element.val(val); + this.$element.trigger('input').trigger('change'); + } + }; + + SelectAdapter.prototype.unselect = function (data) { + var self = this; + + if (!this.$element.prop('multiple')) { + return; + } + + data.selected = false; + + if ( + data.element != null && + data.element.tagName.toLowerCase() === 'option' + ) { + data.element.selected = false; + + this.$element.trigger('input').trigger('change'); + + return; + } + + this.current(function (currentData) { + var val = []; + + for (var d = 0; d < currentData.length; d++) { + var id = currentData[d].id; + + if (id !== data.id && val.indexOf(id) === -1) { + val.push(id); + } + } + + self.$element.val(val); + + self.$element.trigger('input').trigger('change'); + }); + }; + + SelectAdapter.prototype.bind = function (container, $container) { + var self = this; + + this.container = container; + + container.on('select', function (params) { + self.select(params.data); + }); + + container.on('unselect', function (params) { + self.unselect(params.data); + }); + }; + + SelectAdapter.prototype.destroy = function () { + // Remove anything added to child elements + this.$element.find('*').each(function () { + // Remove any custom data set by Select2 + Utils.RemoveData(this); + }); + }; + + SelectAdapter.prototype.query = function (params, callback) { + var data = []; + var self = this; + + var $options = this.$element.children(); + + $options.each(function () { + if ( + this.tagName.toLowerCase() !== 'option' && + this.tagName.toLowerCase() !== 'optgroup' + ) { + return; + } + + var $option = $(this); + + var option = self.item($option); + + var matches = self.matches(params, option); + + if (matches !== null) { + data.push(matches); + } + }); + + callback({ + results: data + }); + }; + + SelectAdapter.prototype.addOptions = function ($options) { + this.$element.append($options); + }; + + SelectAdapter.prototype.option = function (data) { + var option; + + if (data.children) { + option = document.createElement('optgroup'); + option.label = data.text; + } else { + option = document.createElement('option'); + + if (option.textContent !== undefined) { + option.textContent = data.text; + } else { + option.innerText = data.text; + } + } + + if (data.id !== undefined) { + option.value = data.id; + } + + if (data.disabled) { + option.disabled = true; + } + + if (data.selected) { + option.selected = true; + } + + if (data.title) { + option.title = data.title; + } + + var normalizedData = this._normalizeItem(data); + normalizedData.element = option; + + // Override the option's data with the combined data + Utils.StoreData(option, 'data', normalizedData); + + return $(option); + }; + + SelectAdapter.prototype.item = function ($option) { + var data = {}; + + data = Utils.GetData($option[0], 'data'); + + if (data != null) { + return data; + } + + var option = $option[0]; + + if (option.tagName.toLowerCase() === 'option') { + data = { + id: $option.val(), + text: $option.text(), + disabled: $option.prop('disabled'), + selected: $option.prop('selected'), + title: $option.prop('title') + }; + } else if (option.tagName.toLowerCase() === 'optgroup') { + data = { + text: $option.prop('label'), + children: [], + title: $option.prop('title') + }; + + var $children = $option.children('option'); + var children = []; + + for (var c = 0; c < $children.length; c++) { + var $child = $($children[c]); + + var child = this.item($child); + + children.push(child); + } + + data.children = children; + } + + data = this._normalizeItem(data); + data.element = $option[0]; + + Utils.StoreData($option[0], 'data', data); + + return data; + }; + + SelectAdapter.prototype._normalizeItem = function (item) { + if (item !== Object(item)) { + item = { + id: item, + text: item + }; + } + + item = $.extend({}, { + text: '' + }, item); + + var defaults = { + selected: false, + disabled: false + }; + + if (item.id != null) { + item.id = item.id.toString(); + } + + if (item.text != null) { + item.text = item.text.toString(); + } + + if (item._resultId == null && item.id && this.container != null) { + item._resultId = this.generateResultId(this.container, item); + } + + return $.extend({}, defaults, item); + }; + + SelectAdapter.prototype.matches = function (params, data) { + var matcher = this.options.get('matcher'); + + return matcher(params, data); + }; + + return SelectAdapter; +}); + +S2.define('select2/data/array',[ + './select', + '../utils', + 'jquery' +], function (SelectAdapter, Utils, $) { + function ArrayAdapter ($element, options) { + this._dataToConvert = options.get('data') || []; + + ArrayAdapter.__super__.constructor.call(this, $element, options); + } + + Utils.Extend(ArrayAdapter, SelectAdapter); + + ArrayAdapter.prototype.bind = function (container, $container) { + ArrayAdapter.__super__.bind.call(this, container, $container); + + this.addOptions(this.convertToOptions(this._dataToConvert)); + }; + + ArrayAdapter.prototype.select = function (data) { + var $option = this.$element.find('option').filter(function (i, elm) { + return elm.value == data.id.toString(); + }); + + if ($option.length === 0) { + $option = this.option(data); + + this.addOptions($option); + } + + ArrayAdapter.__super__.select.call(this, data); + }; + + ArrayAdapter.prototype.convertToOptions = function (data) { + var self = this; + + var $existing = this.$element.find('option'); + var existingIds = $existing.map(function () { + return self.item($(this)).id; + }).get(); + + var $options = []; + + // Filter out all items except for the one passed in the argument + function onlyItem (item) { + return function () { + return $(this).val() == item.id; + }; + } + + for (var d = 0; d < data.length; d++) { + var item = this._normalizeItem(data[d]); + + // Skip items which were pre-loaded, only merge the data + if (existingIds.indexOf(item.id) >= 0) { + var $existingOption = $existing.filter(onlyItem(item)); + + var existingData = this.item($existingOption); + var newData = $.extend(true, {}, item, existingData); + + var $newOption = this.option(newData); + + $existingOption.replaceWith($newOption); + + continue; + } + + var $option = this.option(item); + + if (item.children) { + var $children = this.convertToOptions(item.children); + + $option.append($children); + } + + $options.push($option); + } + + return $options; + }; + + return ArrayAdapter; +}); + +S2.define('select2/data/ajax',[ + './array', + '../utils', + 'jquery' +], function (ArrayAdapter, Utils, $) { + function AjaxAdapter ($element, options) { + this.ajaxOptions = this._applyDefaults(options.get('ajax')); + + if (this.ajaxOptions.processResults != null) { + this.processResults = this.ajaxOptions.processResults; + } + + AjaxAdapter.__super__.constructor.call(this, $element, options); + } + + Utils.Extend(AjaxAdapter, ArrayAdapter); + + AjaxAdapter.prototype._applyDefaults = function (options) { + var defaults = { + data: function (params) { + return $.extend({}, params, { + q: params.term + }); + }, + transport: function (params, success, failure) { + var $request = $.ajax(params); + + $request.then(success); + $request.fail(failure); + + return $request; + } + }; + + return $.extend({}, defaults, options, true); + }; + + AjaxAdapter.prototype.processResults = function (results) { + return results; + }; + + AjaxAdapter.prototype.query = function (params, callback) { + var matches = []; + var self = this; + + if (this._request != null) { + // JSONP requests cannot always be aborted + if (typeof this._request.abort === 'function') { + this._request.abort(); + } + + this._request = null; + } + + var options = $.extend({ + type: 'GET' + }, this.ajaxOptions); + + if (typeof options.url === 'function') { + options.url = options.url.call(this.$element, params); + } + + if (typeof options.data === 'function') { + options.data = options.data.call(this.$element, params); + } + + function request () { + var $request = options.transport(options, function (data) { + var results = self.processResults(data, params); + + if (self.options.get('debug') && window.console && console.error) { + // Check to make sure that the response included a `results` key. + if (!results || !results.results || !Array.isArray(results.results)) { + console.error( + 'Select2: The AJAX results did not return an array in the ' + + '`results` key of the response.' + ); + } + } + + callback(results); + }, function () { + // Attempt to detect if a request was aborted + // Only works if the transport exposes a status property + if ('status' in $request && + ($request.status === 0 || $request.status === '0')) { + return; + } + + self.trigger('results:message', { + message: 'errorLoading' + }); + }); + + self._request = $request; + } + + if (this.ajaxOptions.delay && params.term != null) { + if (this._queryTimeout) { + window.clearTimeout(this._queryTimeout); + } + + this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay); + } else { + request(); + } + }; + + return AjaxAdapter; +}); + +S2.define('select2/data/tags',[ + 'jquery' +], function ($) { + function Tags (decorated, $element, options) { + var tags = options.get('tags'); + + var createTag = options.get('createTag'); + + if (createTag !== undefined) { + this.createTag = createTag; + } + + var insertTag = options.get('insertTag'); + + if (insertTag !== undefined) { + this.insertTag = insertTag; + } + + decorated.call(this, $element, options); + + if (Array.isArray(tags)) { + for (var t = 0; t < tags.length; t++) { + var tag = tags[t]; + var item = this._normalizeItem(tag); + + var $option = this.option(item); + + this.$element.append($option); + } + } + } + + Tags.prototype.query = function (decorated, params, callback) { + var self = this; + + this._removeOldTags(); + + if (params.term == null || params.page != null) { + decorated.call(this, params, callback); + return; + } + + function wrapper (obj, child) { + var data = obj.results; + + for (var i = 0; i < data.length; i++) { + var option = data[i]; + + var checkChildren = ( + option.children != null && + !wrapper({ + results: option.children + }, true) + ); + + var optionText = (option.text || '').toUpperCase(); + var paramsTerm = (params.term || '').toUpperCase(); + + var checkText = optionText === paramsTerm; + + if (checkText || checkChildren) { + if (child) { + return false; + } + + obj.data = data; + callback(obj); + + return; + } + } + + if (child) { + return true; + } + + var tag = self.createTag(params); + + if (tag != null) { + var $option = self.option(tag); + $option.attr('data-select2-tag', 'true'); + + self.addOptions([$option]); + + self.insertTag(data, tag); + } + + obj.results = data; + + callback(obj); + } + + decorated.call(this, params, wrapper); + }; + + Tags.prototype.createTag = function (decorated, params) { + if (params.term == null) { + return null; + } + + var term = params.term.trim(); + + if (term === '') { + return null; + } + + return { + id: term, + text: term + }; + }; + + Tags.prototype.insertTag = function (_, data, tag) { + data.unshift(tag); + }; + + Tags.prototype._removeOldTags = function (_) { + var $options = this.$element.find('option[data-select2-tag]'); + + $options.each(function () { + if (this.selected) { + return; + } + + $(this).remove(); + }); + }; + + return Tags; +}); + +S2.define('select2/data/tokenizer',[ + 'jquery' +], function ($) { + function Tokenizer (decorated, $element, options) { + var tokenizer = options.get('tokenizer'); + + if (tokenizer !== undefined) { + this.tokenizer = tokenizer; + } + + decorated.call(this, $element, options); + } + + Tokenizer.prototype.bind = function (decorated, container, $container) { + decorated.call(this, container, $container); + + this.$search = container.dropdown.$search || container.selection.$search || + $container.find('.select2-search__field'); + }; + + Tokenizer.prototype.query = function (decorated, params, callback) { + var self = this; + + function createAndSelect (data) { + // Normalize the data object so we can use it for checks + var item = self._normalizeItem(data); + + // Check if the data object already exists as a tag + // Select it if it doesn't + var $existingOptions = self.$element.find('option').filter(function () { + return $(this).val() === item.id; + }); + + // If an existing option wasn't found for it, create the option + if (!$existingOptions.length) { + var $option = self.option(item); + $option.attr('data-select2-tag', true); + + self._removeOldTags(); + self.addOptions([$option]); + } + + // Select the item, now that we know there is an option for it + select(item); + } + + function select (data) { + self.trigger('select', { + data: data + }); + } + + params.term = params.term || ''; + + var tokenData = this.tokenizer(params, this.options, createAndSelect); + + if (tokenData.term !== params.term) { + // Replace the search term if we have the search box + if (this.$search.length) { + this.$search.val(tokenData.term); + this.$search.trigger('focus'); + } + + params.term = tokenData.term; + } + + decorated.call(this, params, callback); + }; + + Tokenizer.prototype.tokenizer = function (_, params, options, callback) { + var separators = options.get('tokenSeparators') || []; + var term = params.term; + var i = 0; + + var createTag = this.createTag || function (params) { + return { + id: params.term, + text: params.term + }; + }; + + while (i < term.length) { + var termChar = term[i]; + + if (separators.indexOf(termChar) === -1) { + i++; + + continue; + } + + var part = term.substr(0, i); + var partParams = $.extend({}, params, { + term: part + }); + + var data = createTag(partParams); + + if (data == null) { + i++; + continue; + } + + callback(data); + + // Reset the term to not include the tokenized portion + term = term.substr(i + 1) || ''; + i = 0; + } + + return { + term: term + }; + }; + + return Tokenizer; +}); + +S2.define('select2/data/minimumInputLength',[ + +], function () { + function MinimumInputLength (decorated, $e, options) { + this.minimumInputLength = options.get('minimumInputLength'); + + decorated.call(this, $e, options); + } + + MinimumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (params.term.length < this.minimumInputLength) { + this.trigger('results:message', { + message: 'inputTooShort', + args: { + minimum: this.minimumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MinimumInputLength; +}); + +S2.define('select2/data/maximumInputLength',[ + +], function () { + function MaximumInputLength (decorated, $e, options) { + this.maximumInputLength = options.get('maximumInputLength'); + + decorated.call(this, $e, options); + } + + MaximumInputLength.prototype.query = function (decorated, params, callback) { + params.term = params.term || ''; + + if (this.maximumInputLength > 0 && + params.term.length > this.maximumInputLength) { + this.trigger('results:message', { + message: 'inputTooLong', + args: { + maximum: this.maximumInputLength, + input: params.term, + params: params + } + }); + + return; + } + + decorated.call(this, params, callback); + }; + + return MaximumInputLength; +}); + +S2.define('select2/data/maximumSelectionLength',[ + +], function (){ + function MaximumSelectionLength (decorated, $e, options) { + this.maximumSelectionLength = options.get('maximumSelectionLength'); + + decorated.call(this, $e, options); + } + + MaximumSelectionLength.prototype.bind = + function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('select', function () { + self._checkIfMaximumSelected(); + }); + }; + + MaximumSelectionLength.prototype.query = + function (decorated, params, callback) { + var self = this; + + this._checkIfMaximumSelected(function () { + decorated.call(self, params, callback); + }); + }; + + MaximumSelectionLength.prototype._checkIfMaximumSelected = + function (_, successCallback) { + var self = this; + + this.current(function (currentData) { + var count = currentData != null ? currentData.length : 0; + if (self.maximumSelectionLength > 0 && + count >= self.maximumSelectionLength) { + self.trigger('results:message', { + message: 'maximumSelected', + args: { + maximum: self.maximumSelectionLength + } + }); + return; + } + + if (successCallback) { + successCallback(); + } + }); + }; + + return MaximumSelectionLength; +}); + +S2.define('select2/dropdown',[ + 'jquery', + './utils' +], function ($, Utils) { + function Dropdown ($element, options) { + this.$element = $element; + this.options = options; + + Dropdown.__super__.constructor.call(this); + } + + Utils.Extend(Dropdown, Utils.Observable); + + Dropdown.prototype.render = function () { + var $dropdown = $( + '' + + '' + + '' + ); + + $dropdown.attr('dir', this.options.get('dir')); + + this.$dropdown = $dropdown; + + return $dropdown; + }; + + Dropdown.prototype.bind = function () { + // Should be implemented in subclasses + }; + + Dropdown.prototype.position = function ($dropdown, $container) { + // Should be implemented in subclasses + }; + + Dropdown.prototype.destroy = function () { + // Remove the dropdown from the DOM + this.$dropdown.remove(); + }; + + return Dropdown; +}); + +S2.define('select2/dropdown/search',[ + 'jquery' +], function ($) { + function Search () { } + + Search.prototype.render = function (decorated) { + var $rendered = decorated.call(this); + var searchLabel = this.options.get('translations').get('search'); + + var $search = $( + '' + + '' + + '' + ); + + this.$searchContainer = $search; + this.$search = $search.find('input'); + + this.$search.prop('autocomplete', this.options.get('autocomplete')); + this.$search.attr('aria-label', searchLabel()); + + $rendered.prepend($search); + + return $rendered; + }; + + Search.prototype.bind = function (decorated, container, $container) { + var self = this; + + var resultsId = container.id + '-results'; + + decorated.call(this, container, $container); + + this.$search.on('keydown', function (evt) { + self.trigger('keypress', evt); + + self._keyUpPrevented = evt.isDefaultPrevented(); + }); + + // Workaround for browsers which do not support the `input` event + // This will prevent double-triggering of events for browsers which support + // both the `keyup` and `input` events. + this.$search.on('input', function (evt) { + // Unbind the duplicated `keyup` event + $(this).off('keyup'); + }); + + this.$search.on('keyup input', function (evt) { + self.handleSearch(evt); + }); + + container.on('open', function () { + self.$search.attr('tabindex', 0); + self.$search.attr('aria-controls', resultsId); + + self.$search.trigger('focus'); + + window.setTimeout(function () { + self.$search.trigger('focus'); + }, 0); + }); + + container.on('close', function () { + self.$search.attr('tabindex', -1); + self.$search.removeAttr('aria-controls'); + self.$search.removeAttr('aria-activedescendant'); + + self.$search.val(''); + self.$search.trigger('blur'); + }); + + container.on('focus', function () { + if (!container.isOpen()) { + self.$search.trigger('focus'); + } + }); + + container.on('results:all', function (params) { + if (params.query.term == null || params.query.term === '') { + var showSearch = self.showSearch(params); + + if (showSearch) { + self.$searchContainer[0].classList.remove('select2-search--hide'); + } else { + self.$searchContainer[0].classList.add('select2-search--hide'); + } + } + }); + + container.on('results:focus', function (params) { + if (params.data._resultId) { + self.$search.attr('aria-activedescendant', params.data._resultId); + } else { + self.$search.removeAttr('aria-activedescendant'); + } + }); + }; + + Search.prototype.handleSearch = function (evt) { + if (!this._keyUpPrevented) { + var input = this.$search.val(); + + this.trigger('query', { + term: input + }); + } + + this._keyUpPrevented = false; + }; + + Search.prototype.showSearch = function (_, params) { + return true; + }; + + return Search; +}); + +S2.define('select2/dropdown/hidePlaceholder',[ + +], function () { + function HidePlaceholder (decorated, $element, options, dataAdapter) { + this.placeholder = this.normalizePlaceholder(options.get('placeholder')); + + decorated.call(this, $element, options, dataAdapter); + } + + HidePlaceholder.prototype.append = function (decorated, data) { + data.results = this.removePlaceholder(data.results); + + decorated.call(this, data); + }; + + HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) { + if (typeof placeholder === 'string') { + placeholder = { + id: '', + text: placeholder + }; + } + + return placeholder; + }; + + HidePlaceholder.prototype.removePlaceholder = function (_, data) { + var modifiedData = data.slice(0); + + for (var d = data.length - 1; d >= 0; d--) { + var item = data[d]; + + if (this.placeholder.id === item.id) { + modifiedData.splice(d, 1); + } + } + + return modifiedData; + }; + + return HidePlaceholder; +}); + +S2.define('select2/dropdown/infiniteScroll',[ + 'jquery' +], function ($) { + function InfiniteScroll (decorated, $element, options, dataAdapter) { + this.lastParams = {}; + + decorated.call(this, $element, options, dataAdapter); + + this.$loadingMore = this.createLoadingMore(); + this.loading = false; + } + + InfiniteScroll.prototype.append = function (decorated, data) { + this.$loadingMore.remove(); + this.loading = false; + + decorated.call(this, data); + + if (this.showLoadingMore(data)) { + this.$results.append(this.$loadingMore); + this.loadMoreIfNeeded(); + } + }; + + InfiniteScroll.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('query', function (params) { + self.lastParams = params; + self.loading = true; + }); + + container.on('query:append', function (params) { + self.lastParams = params; + self.loading = true; + }); + + this.$results.on('scroll', this.loadMoreIfNeeded.bind(this)); + }; + + InfiniteScroll.prototype.loadMoreIfNeeded = function () { + var isLoadMoreVisible = $.contains( + document.documentElement, + this.$loadingMore[0] + ); + + if (this.loading || !isLoadMoreVisible) { + return; + } + + var currentOffset = this.$results.offset().top + + this.$results.outerHeight(false); + var loadingMoreOffset = this.$loadingMore.offset().top + + this.$loadingMore.outerHeight(false); + + if (currentOffset + 50 >= loadingMoreOffset) { + this.loadMore(); + } + }; + + InfiniteScroll.prototype.loadMore = function () { + this.loading = true; + + var params = $.extend({}, {page: 1}, this.lastParams); + + params.page++; + + this.trigger('query:append', params); + }; + + InfiniteScroll.prototype.showLoadingMore = function (_, data) { + return data.pagination && data.pagination.more; + }; + + InfiniteScroll.prototype.createLoadingMore = function () { + var $option = $( + '
        • ' + ); + + var message = this.options.get('translations').get('loadingMore'); + + $option.html(message(this.lastParams)); + + return $option; + }; + + return InfiniteScroll; +}); + +S2.define('select2/dropdown/attachBody',[ + 'jquery', + '../utils' +], function ($, Utils) { + function AttachBody (decorated, $element, options) { + this.$dropdownParent = $(options.get('dropdownParent') || document.body); + + decorated.call(this, $element, options); + } + + AttachBody.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('open', function () { + self._showDropdown(); + self._attachPositioningHandler(container); + + // Must bind after the results handlers to ensure correct sizing + self._bindContainerResultHandlers(container); + }); + + container.on('close', function () { + self._hideDropdown(); + self._detachPositioningHandler(container); + }); + + this.$dropdownContainer.on('mousedown', function (evt) { + evt.stopPropagation(); + }); + }; + + AttachBody.prototype.destroy = function (decorated) { + decorated.call(this); + + this.$dropdownContainer.remove(); + }; + + AttachBody.prototype.position = function (decorated, $dropdown, $container) { + // Clone all of the container classes + $dropdown.attr('class', $container.attr('class')); + + $dropdown[0].classList.remove('select2'); + $dropdown[0].classList.add('select2-container--open'); + + $dropdown.css({ + position: 'absolute', + top: -999999 + }); + + this.$container = $container; + }; + + AttachBody.prototype.render = function (decorated) { + var $container = $(''); + + var $dropdown = decorated.call(this); + $container.append($dropdown); + + this.$dropdownContainer = $container; + + return $container; + }; + + AttachBody.prototype._hideDropdown = function (decorated) { + this.$dropdownContainer.detach(); + }; + + AttachBody.prototype._bindContainerResultHandlers = + function (decorated, container) { + + // These should only be bound once + if (this._containerResultsHandlersBound) { + return; + } + + var self = this; + + container.on('results:all', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('results:append', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('results:message', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('select', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + container.on('unselect', function () { + self._positionDropdown(); + self._resizeDropdown(); + }); + + this._containerResultsHandlersBound = true; + }; + + AttachBody.prototype._attachPositioningHandler = + function (decorated, container) { + var self = this; + + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.each(function () { + Utils.StoreData(this, 'select2-scroll-position', { + x: $(this).scrollLeft(), + y: $(this).scrollTop() + }); + }); + + $watchers.on(scrollEvent, function (ev) { + var position = Utils.GetData(this, 'select2-scroll-position'); + $(this).scrollTop(position.y); + }); + + $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent, + function (e) { + self._positionDropdown(); + self._resizeDropdown(); + }); + }; + + AttachBody.prototype._detachPositioningHandler = + function (decorated, container) { + var scrollEvent = 'scroll.select2.' + container.id; + var resizeEvent = 'resize.select2.' + container.id; + var orientationEvent = 'orientationchange.select2.' + container.id; + + var $watchers = this.$container.parents().filter(Utils.hasScroll); + $watchers.off(scrollEvent); + + $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent); + }; + + AttachBody.prototype._positionDropdown = function () { + var $window = $(window); + + var isCurrentlyAbove = this.$dropdown[0].classList + .contains('select2-dropdown--above'); + var isCurrentlyBelow = this.$dropdown[0].classList + .contains('select2-dropdown--below'); + + var newDirection = null; + + var offset = this.$container.offset(); + + offset.bottom = offset.top + this.$container.outerHeight(false); + + var container = { + height: this.$container.outerHeight(false) + }; + + container.top = offset.top; + container.bottom = offset.top + container.height; + + var dropdown = { + height: this.$dropdown.outerHeight(false) + }; + + var viewport = { + top: $window.scrollTop(), + bottom: $window.scrollTop() + $window.height() + }; + + var enoughRoomAbove = viewport.top < (offset.top - dropdown.height); + var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height); + + var css = { + left: offset.left, + top: container.bottom + }; + + // Determine what the parent element is to use for calculating the offset + var $offsetParent = this.$dropdownParent; + + // For statically positioned elements, we need to get the element + // that is determining the offset + if ($offsetParent.css('position') === 'static') { + $offsetParent = $offsetParent.offsetParent(); + } + + var parentOffset = { + top: 0, + left: 0 + }; + + if ( + $.contains(document.body, $offsetParent[0]) || + $offsetParent[0].isConnected + ) { + parentOffset = $offsetParent.offset(); + } + + css.top -= parentOffset.top; + css.left -= parentOffset.left; + + if (!isCurrentlyAbove && !isCurrentlyBelow) { + newDirection = 'below'; + } + + if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) { + newDirection = 'above'; + } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) { + newDirection = 'below'; + } + + if (newDirection == 'above' || + (isCurrentlyAbove && newDirection !== 'below')) { + css.top = container.top - parentOffset.top - dropdown.height; + } + + if (newDirection != null) { + this.$dropdown[0].classList.remove('select2-dropdown--below'); + this.$dropdown[0].classList.remove('select2-dropdown--above'); + this.$dropdown[0].classList.add('select2-dropdown--' + newDirection); + + this.$container[0].classList.remove('select2-container--below'); + this.$container[0].classList.remove('select2-container--above'); + this.$container[0].classList.add('select2-container--' + newDirection); + } + + this.$dropdownContainer.css(css); + }; + + AttachBody.prototype._resizeDropdown = function () { + var css = { + width: this.$container.outerWidth(false) + 'px' + }; + + if (this.options.get('dropdownAutoWidth')) { + css.minWidth = css.width; + css.position = 'relative'; + css.width = 'auto'; + } + + this.$dropdown.css(css); + }; + + AttachBody.prototype._showDropdown = function (decorated) { + this.$dropdownContainer.appendTo(this.$dropdownParent); + + this._positionDropdown(); + this._resizeDropdown(); + }; + + return AttachBody; +}); + +S2.define('select2/dropdown/minimumResultsForSearch',[ + +], function () { + function countResults (data) { + var count = 0; + + for (var d = 0; d < data.length; d++) { + var item = data[d]; + + if (item.children) { + count += countResults(item.children); + } else { + count++; + } + } + + return count; + } + + function MinimumResultsForSearch (decorated, $element, options, dataAdapter) { + this.minimumResultsForSearch = options.get('minimumResultsForSearch'); + + if (this.minimumResultsForSearch < 0) { + this.minimumResultsForSearch = Infinity; + } + + decorated.call(this, $element, options, dataAdapter); + } + + MinimumResultsForSearch.prototype.showSearch = function (decorated, params) { + if (countResults(params.data.results) < this.minimumResultsForSearch) { + return false; + } + + return decorated.call(this, params); + }; + + return MinimumResultsForSearch; +}); + +S2.define('select2/dropdown/selectOnClose',[ + '../utils' +], function (Utils) { + function SelectOnClose () { } + + SelectOnClose.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('close', function (params) { + self._handleSelectOnClose(params); + }); + }; + + SelectOnClose.prototype._handleSelectOnClose = function (_, params) { + if (params && params.originalSelect2Event != null) { + var event = params.originalSelect2Event; + + // Don't select an item if the close event was triggered from a select or + // unselect event + if (event._type === 'select' || event._type === 'unselect') { + return; + } + } + + var $highlightedResults = this.getHighlightedResults(); + + // Only select highlighted results + if ($highlightedResults.length < 1) { + return; + } + + var data = Utils.GetData($highlightedResults[0], 'data'); + + // Don't re-select already selected resulte + if ( + (data.element != null && data.element.selected) || + (data.element == null && data.selected) + ) { + return; + } + + this.trigger('select', { + data: data + }); + }; + + return SelectOnClose; +}); + +S2.define('select2/dropdown/closeOnSelect',[ + +], function () { + function CloseOnSelect () { } + + CloseOnSelect.prototype.bind = function (decorated, container, $container) { + var self = this; + + decorated.call(this, container, $container); + + container.on('select', function (evt) { + self._selectTriggered(evt); + }); + + container.on('unselect', function (evt) { + self._selectTriggered(evt); + }); + }; + + CloseOnSelect.prototype._selectTriggered = function (_, evt) { + var originalEvent = evt.originalEvent; + + // Don't close if the control key is being held + if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) { + return; + } + + this.trigger('close', { + originalEvent: originalEvent, + originalSelect2Event: evt + }); + }; + + return CloseOnSelect; +}); + +S2.define('select2/dropdown/dropdownCss',[ + '../utils' +], function (Utils) { + function DropdownCSS () { } + + DropdownCSS.prototype.render = function (decorated) { + var $dropdown = decorated.call(this); + + var dropdownCssClass = this.options.get('dropdownCssClass') || ''; + + if (dropdownCssClass.indexOf(':all:') !== -1) { + dropdownCssClass = dropdownCssClass.replace(':all:', ''); + + Utils.copyNonInternalCssClasses($dropdown[0], this.$element[0]); + } + + $dropdown.addClass(dropdownCssClass); + + return $dropdown; + }; + + return DropdownCSS; +}); + +S2.define('select2/dropdown/tagsSearchHighlight',[ + '../utils' +], function (Utils) { + function TagsSearchHighlight () { } + + TagsSearchHighlight.prototype.highlightFirstItem = function (decorated) { + var $options = this.$results + .find( + '.select2-results__option--selectable' + + ':not(.select2-results__option--selected)' + ); + + if ($options.length > 0) { + var $firstOption = $options.first(); + var data = Utils.GetData($firstOption[0], 'data'); + var firstElement = data.element; + + if (firstElement && firstElement.getAttribute) { + if (firstElement.getAttribute('data-select2-tag') === 'true') { + $firstOption.trigger('mouseenter'); + + return; + } + } + } + + decorated.call(this); + }; + + return TagsSearchHighlight; +}); + +S2.define('select2/i18n/en',[],function () { + // English + return { + errorLoading: function () { + return 'The results could not be loaded.'; + }, + inputTooLong: function (args) { + var overChars = args.input.length - args.maximum; + + var message = 'Please delete ' + overChars + ' character'; + + if (overChars != 1) { + message += 's'; + } + + return message; + }, + inputTooShort: function (args) { + var remainingChars = args.minimum - args.input.length; + + var message = 'Please enter ' + remainingChars + ' or more characters'; + + return message; + }, + loadingMore: function () { + return 'Loading more results…'; + }, + maximumSelected: function (args) { + var message = 'You can only select ' + args.maximum + ' item'; + + if (args.maximum != 1) { + message += 's'; + } + + return message; + }, + noResults: function () { + return 'No results found'; + }, + searching: function () { + return 'Searching…'; + }, + removeAllItems: function () { + return 'Remove all items'; + }, + removeItem: function () { + return 'Remove item'; + }, + search: function() { + return 'Search'; + } + }; +}); + +S2.define('select2/defaults',[ + 'jquery', + + './results', + + './selection/single', + './selection/multiple', + './selection/placeholder', + './selection/allowClear', + './selection/search', + './selection/selectionCss', + './selection/eventRelay', + + './utils', + './translation', + './diacritics', + + './data/select', + './data/array', + './data/ajax', + './data/tags', + './data/tokenizer', + './data/minimumInputLength', + './data/maximumInputLength', + './data/maximumSelectionLength', + + './dropdown', + './dropdown/search', + './dropdown/hidePlaceholder', + './dropdown/infiniteScroll', + './dropdown/attachBody', + './dropdown/minimumResultsForSearch', + './dropdown/selectOnClose', + './dropdown/closeOnSelect', + './dropdown/dropdownCss', + './dropdown/tagsSearchHighlight', + + './i18n/en' +], function ($, + + ResultsList, + + SingleSelection, MultipleSelection, Placeholder, AllowClear, + SelectionSearch, SelectionCSS, EventRelay, + + Utils, Translation, DIACRITICS, + + SelectData, ArrayData, AjaxData, Tags, Tokenizer, + MinimumInputLength, MaximumInputLength, MaximumSelectionLength, + + Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll, + AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect, + DropdownCSS, TagsSearchHighlight, + + EnglishTranslation) { + function Defaults () { + this.reset(); + } + + Defaults.prototype.apply = function (options) { + options = $.extend(true, {}, this.defaults, options); + + if (options.dataAdapter == null) { + if (options.ajax != null) { + options.dataAdapter = AjaxData; + } else if (options.data != null) { + options.dataAdapter = ArrayData; + } else { + options.dataAdapter = SelectData; + } + + if (options.minimumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MinimumInputLength + ); + } + + if (options.maximumInputLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumInputLength + ); + } + + if (options.maximumSelectionLength > 0) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + MaximumSelectionLength + ); + } + + if (options.tags) { + options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags); + } + + if (options.tokenSeparators != null || options.tokenizer != null) { + options.dataAdapter = Utils.Decorate( + options.dataAdapter, + Tokenizer + ); + } + } + + if (options.resultsAdapter == null) { + options.resultsAdapter = ResultsList; + + if (options.ajax != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + InfiniteScroll + ); + } + + if (options.placeholder != null) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + HidePlaceholder + ); + } + + if (options.selectOnClose) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + SelectOnClose + ); + } + + if (options.tags) { + options.resultsAdapter = Utils.Decorate( + options.resultsAdapter, + TagsSearchHighlight + ); + } + } + + if (options.dropdownAdapter == null) { + if (options.multiple) { + options.dropdownAdapter = Dropdown; + } else { + var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch); + + options.dropdownAdapter = SearchableDropdown; + } + + if (options.minimumResultsForSearch !== 0) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + MinimumResultsForSearch + ); + } + + if (options.closeOnSelect) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + CloseOnSelect + ); + } + + if (options.dropdownCssClass != null) { + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + DropdownCSS + ); + } + + options.dropdownAdapter = Utils.Decorate( + options.dropdownAdapter, + AttachBody + ); + } + + if (options.selectionAdapter == null) { + if (options.multiple) { + options.selectionAdapter = MultipleSelection; + } else { + options.selectionAdapter = SingleSelection; + } + + // Add the placeholder mixin if a placeholder was specified + if (options.placeholder != null) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + Placeholder + ); + } + + if (options.allowClear) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + AllowClear + ); + } + + if (options.multiple) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + SelectionSearch + ); + } + + if (options.selectionCssClass != null) { + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + SelectionCSS + ); + } + + options.selectionAdapter = Utils.Decorate( + options.selectionAdapter, + EventRelay + ); + } + + // If the defaults were not previously applied from an element, it is + // possible for the language option to have not been resolved + options.language = this._resolveLanguage(options.language); + + // Always fall back to English since it will always be complete + options.language.push('en'); + + var uniqueLanguages = []; + + for (var l = 0; l < options.language.length; l++) { + var language = options.language[l]; + + if (uniqueLanguages.indexOf(language) === -1) { + uniqueLanguages.push(language); + } + } + + options.language = uniqueLanguages; + + options.translations = this._processTranslations( + options.language, + options.debug + ); + + return options; + }; + + Defaults.prototype.reset = function () { + function stripDiacritics (text) { + // Used 'uni range + named function' from http://jsperf.com/diacritics/18 + function match(a) { + return DIACRITICS[a] || a; + } + + return text.replace(/[^\u0000-\u007E]/g, match); + } + + function matcher (params, data) { + // Always return the object if there is nothing to compare + if (params.term == null || params.term.trim() === '') { + return data; + } + + // Do a recursive check for options with children + if (data.children && data.children.length > 0) { + // Clone the data object if there are children + // This is required as we modify the object to remove any non-matches + var match = $.extend(true, {}, data); + + // Check each child of the option + for (var c = data.children.length - 1; c >= 0; c--) { + var child = data.children[c]; + + var matches = matcher(params, child); + + // If there wasn't a match, remove the object in the array + if (matches == null) { + match.children.splice(c, 1); + } + } + + // If any children matched, return the new object + if (match.children.length > 0) { + return match; + } + + // If there were no matching children, check just the plain object + return matcher(params, match); + } + + var original = stripDiacritics(data.text).toUpperCase(); + var term = stripDiacritics(params.term).toUpperCase(); + + // Check if the text contains the term + if (original.indexOf(term) > -1) { + return data; + } + + // If it doesn't contain the term, don't return anything + return null; + } + + this.defaults = { + amdLanguageBase: './i18n/', + autocomplete: 'off', + closeOnSelect: true, + debug: false, + dropdownAutoWidth: false, + escapeMarkup: Utils.escapeMarkup, + language: {}, + matcher: matcher, + minimumInputLength: 0, + maximumInputLength: 0, + maximumSelectionLength: 0, + minimumResultsForSearch: 0, + selectOnClose: false, + scrollAfterSelect: false, + sorter: function (data) { + return data; + }, + templateResult: function (result) { + return result.text; + }, + templateSelection: function (selection) { + return selection.text; + }, + theme: 'default', + width: 'resolve' + }; + }; + + Defaults.prototype.applyFromElement = function (options, $element) { + var optionLanguage = options.language; + var defaultLanguage = this.defaults.language; + var elementLanguage = $element.prop('lang'); + var parentLanguage = $element.closest('[lang]').prop('lang'); + + var languages = Array.prototype.concat.call( + this._resolveLanguage(elementLanguage), + this._resolveLanguage(optionLanguage), + this._resolveLanguage(defaultLanguage), + this._resolveLanguage(parentLanguage) + ); + + options.language = languages; + + return options; + }; + + Defaults.prototype._resolveLanguage = function (language) { + if (!language) { + return []; + } + + if ($.isEmptyObject(language)) { + return []; + } + + if ($.isPlainObject(language)) { + return [language]; + } + + var languages; + + if (!Array.isArray(language)) { + languages = [language]; + } else { + languages = language; + } + + var resolvedLanguages = []; + + for (var l = 0; l < languages.length; l++) { + resolvedLanguages.push(languages[l]); + + if (typeof languages[l] === 'string' && languages[l].indexOf('-') > 0) { + // Extract the region information if it is included + var languageParts = languages[l].split('-'); + var baseLanguage = languageParts[0]; + + resolvedLanguages.push(baseLanguage); + } + } + + return resolvedLanguages; + }; + + Defaults.prototype._processTranslations = function (languages, debug) { + var translations = new Translation(); + + for (var l = 0; l < languages.length; l++) { + var languageData = new Translation(); + + var language = languages[l]; + + if (typeof language === 'string') { + try { + // Try to load it with the original name + languageData = Translation.loadPath(language); + } catch (e) { + try { + // If we couldn't load it, check if it wasn't the full path + language = this.defaults.amdLanguageBase + language; + languageData = Translation.loadPath(language); + } catch (ex) { + // The translation could not be loaded at all. Sometimes this is + // because of a configuration problem, other times this can be + // because of how Select2 helps load all possible translation files + if (debug && window.console && console.warn) { + console.warn( + 'Select2: The language file for "' + language + '" could ' + + 'not be automatically loaded. A fallback will be used instead.' + ); + } + } + } + } else if ($.isPlainObject(language)) { + languageData = new Translation(language); + } else { + languageData = language; + } + + translations.extend(languageData); + } + + return translations; + }; + + Defaults.prototype.set = function (key, value) { + var camelKey = $.camelCase(key); + + var data = {}; + data[camelKey] = value; + + var convertedData = Utils._convertData(data); + + $.extend(true, this.defaults, convertedData); + }; + + var defaults = new Defaults(); + + return defaults; +}); + +S2.define('select2/options',[ + 'jquery', + './defaults', + './utils' +], function ($, Defaults, Utils) { + function Options (options, $element) { + this.options = options; + + if ($element != null) { + this.fromElement($element); + } + + if ($element != null) { + this.options = Defaults.applyFromElement(this.options, $element); + } + + this.options = Defaults.apply(this.options); + } + + Options.prototype.fromElement = function ($e) { + var excludedData = ['select2']; + + if (this.options.multiple == null) { + this.options.multiple = $e.prop('multiple'); + } + + if (this.options.disabled == null) { + this.options.disabled = $e.prop('disabled'); + } + + if (this.options.autocomplete == null && $e.prop('autocomplete')) { + this.options.autocomplete = $e.prop('autocomplete'); + } + + if (this.options.dir == null) { + if ($e.prop('dir')) { + this.options.dir = $e.prop('dir'); + } else if ($e.closest('[dir]').prop('dir')) { + this.options.dir = $e.closest('[dir]').prop('dir'); + } else { + this.options.dir = 'ltr'; + } + } + + $e.prop('disabled', this.options.disabled); + $e.prop('multiple', this.options.multiple); + + if (Utils.GetData($e[0], 'select2Tags')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-select2-tags` attribute has been changed to ' + + 'use the `data-data` and `data-tags="true"` attributes and will be ' + + 'removed in future versions of Select2.' + ); + } + + Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags')); + Utils.StoreData($e[0], 'tags', true); + } + + if (Utils.GetData($e[0], 'ajaxUrl')) { + if (this.options.debug && window.console && console.warn) { + console.warn( + 'Select2: The `data-ajax-url` attribute has been changed to ' + + '`data-ajax--url` and support for the old attribute will be removed' + + ' in future versions of Select2.' + ); + } + + $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); + Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl')); + } + + var dataset = {}; + + function upperCaseLetter(_, letter) { + return letter.toUpperCase(); + } + + // Pre-load all of the attributes which are prefixed with `data-` + for (var attr = 0; attr < $e[0].attributes.length; attr++) { + var attributeName = $e[0].attributes[attr].name; + var prefix = 'data-'; + + if (attributeName.substr(0, prefix.length) == prefix) { + // Get the contents of the attribute after `data-` + var dataName = attributeName.substring(prefix.length); + + // Get the data contents from the consistent source + // This is more than likely the jQuery data helper + var dataValue = Utils.GetData($e[0], dataName); + + // camelCase the attribute name to match the spec + var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter); + + // Store the data attribute contents into the dataset since + dataset[camelDataName] = dataValue; + } + } + + // Prefer the element's `dataset` attribute if it exists + // jQuery 1.x does not correctly handle data attributes with multiple dashes + if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) { + dataset = $.extend(true, {}, $e[0].dataset, dataset); + } + + // Prefer our internal data cache if it exists + var data = $.extend(true, {}, Utils.GetData($e[0]), dataset); + + data = Utils._convertData(data); + + for (var key in data) { + if (excludedData.indexOf(key) > -1) { + continue; + } + + if ($.isPlainObject(this.options[key])) { + $.extend(this.options[key], data[key]); + } else { + this.options[key] = data[key]; + } + } + + return this; + }; + + Options.prototype.get = function (key) { + return this.options[key]; + }; + + Options.prototype.set = function (key, val) { + this.options[key] = val; + }; + + return Options; +}); + +S2.define('select2/core',[ + 'jquery', + './options', + './utils', + './keys' +], function ($, Options, Utils, KEYS) { + var Select2 = function ($element, options) { + if (Utils.GetData($element[0], 'select2') != null) { + Utils.GetData($element[0], 'select2').destroy(); + } + + this.$element = $element; + + this.id = this._generateId($element); + + options = options || {}; + + this.options = new Options(options, $element); + + Select2.__super__.constructor.call(this); + + // Set up the tabindex + + var tabindex = $element.attr('tabindex') || 0; + Utils.StoreData($element[0], 'old-tabindex', tabindex); + $element.attr('tabindex', '-1'); + + // Set up containers and adapters + + var DataAdapter = this.options.get('dataAdapter'); + this.dataAdapter = new DataAdapter($element, this.options); + + var $container = this.render(); + + this._placeContainer($container); + + var SelectionAdapter = this.options.get('selectionAdapter'); + this.selection = new SelectionAdapter($element, this.options); + this.$selection = this.selection.render(); + + this.selection.position(this.$selection, $container); + + var DropdownAdapter = this.options.get('dropdownAdapter'); + this.dropdown = new DropdownAdapter($element, this.options); + this.$dropdown = this.dropdown.render(); + + this.dropdown.position(this.$dropdown, $container); + + var ResultsAdapter = this.options.get('resultsAdapter'); + this.results = new ResultsAdapter($element, this.options, this.dataAdapter); + this.$results = this.results.render(); + + this.results.position(this.$results, this.$dropdown); + + // Bind events + + var self = this; + + // Bind the container to all of the adapters + this._bindAdapters(); + + // Register any DOM event handlers + this._registerDomEvents(); + + // Register any internal event handlers + this._registerDataEvents(); + this._registerSelectionEvents(); + this._registerDropdownEvents(); + this._registerResultsEvents(); + this._registerEvents(); + + // Set the initial state + this.dataAdapter.current(function (initialData) { + self.trigger('selection:update', { + data: initialData + }); + }); + + // Hide the original select + $element[0].classList.add('select2-hidden-accessible'); + $element.attr('aria-hidden', 'true'); + + // Synchronize any monitored attributes + this._syncAttributes(); + + Utils.StoreData($element[0], 'select2', this); + + // Ensure backwards compatibility with $element.data('select2'). + $element.data('select2', this); + }; + + Utils.Extend(Select2, Utils.Observable); + + Select2.prototype._generateId = function ($element) { + var id = ''; + + if ($element.attr('id') != null) { + id = $element.attr('id'); + } else if ($element.attr('name') != null) { + id = $element.attr('name') + '-' + Utils.generateChars(2); + } else { + id = Utils.generateChars(4); + } + + id = id.replace(/(:|\.|\[|\]|,)/g, ''); + id = 'select2-' + id; + + return id; + }; + + Select2.prototype._placeContainer = function ($container) { + $container.insertAfter(this.$element); + + var width = this._resolveWidth(this.$element, this.options.get('width')); + + if (width != null) { + $container.css('width', width); + } + }; + + Select2.prototype._resolveWidth = function ($element, method) { + var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i; + + if (method == 'resolve') { + var styleWidth = this._resolveWidth($element, 'style'); + + if (styleWidth != null) { + return styleWidth; + } + + return this._resolveWidth($element, 'element'); + } + + if (method == 'element') { + var elementWidth = $element.outerWidth(false); + + if (elementWidth <= 0) { + return 'auto'; + } + + return elementWidth + 'px'; + } + + if (method == 'style') { + var style = $element.attr('style'); + + if (typeof(style) !== 'string') { + return null; + } + + var attrs = style.split(';'); + + for (var i = 0, l = attrs.length; i < l; i = i + 1) { + var attr = attrs[i].replace(/\s/g, ''); + var matches = attr.match(WIDTH); + + if (matches !== null && matches.length >= 1) { + return matches[1]; + } + } + + return null; + } + + if (method == 'computedstyle') { + var computedStyle = window.getComputedStyle($element[0]); + + return computedStyle.width; + } + + return method; + }; + + Select2.prototype._bindAdapters = function () { + this.dataAdapter.bind(this, this.$container); + this.selection.bind(this, this.$container); + + this.dropdown.bind(this, this.$container); + this.results.bind(this, this.$container); + }; + + Select2.prototype._registerDomEvents = function () { + var self = this; + + this.$element.on('change.select2', function () { + self.dataAdapter.current(function (data) { + self.trigger('selection:update', { + data: data + }); + }); + }); + + this.$element.on('focus.select2', function (evt) { + self.trigger('focus', evt); + }); + + this._syncA = Utils.bind(this._syncAttributes, this); + this._syncS = Utils.bind(this._syncSubtree, this); + + this._observer = new window.MutationObserver(function (mutations) { + self._syncA(); + self._syncS(mutations); + }); + this._observer.observe(this.$element[0], { + attributes: true, + childList: true, + subtree: false + }); + }; + + Select2.prototype._registerDataEvents = function () { + var self = this; + + this.dataAdapter.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerSelectionEvents = function () { + var self = this; + var nonRelayEvents = ['toggle', 'focus']; + + this.selection.on('toggle', function () { + self.toggleDropdown(); + }); + + this.selection.on('focus', function (params) { + self.focus(params); + }); + + this.selection.on('*', function (name, params) { + if (nonRelayEvents.indexOf(name) !== -1) { + return; + } + + self.trigger(name, params); + }); + }; + + Select2.prototype._registerDropdownEvents = function () { + var self = this; + + this.dropdown.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerResultsEvents = function () { + var self = this; + + this.results.on('*', function (name, params) { + self.trigger(name, params); + }); + }; + + Select2.prototype._registerEvents = function () { + var self = this; + + this.on('open', function () { + self.$container[0].classList.add('select2-container--open'); + }); + + this.on('close', function () { + self.$container[0].classList.remove('select2-container--open'); + }); + + this.on('enable', function () { + self.$container[0].classList.remove('select2-container--disabled'); + }); + + this.on('disable', function () { + self.$container[0].classList.add('select2-container--disabled'); + }); + + this.on('blur', function () { + self.$container[0].classList.remove('select2-container--focus'); + }); + + this.on('query', function (params) { + if (!self.isOpen()) { + self.trigger('open', {}); + } + + this.dataAdapter.query(params, function (data) { + self.trigger('results:all', { + data: data, + query: params + }); + }); + }); + + this.on('query:append', function (params) { + this.dataAdapter.query(params, function (data) { + self.trigger('results:append', { + data: data, + query: params + }); + }); + }); + + this.on('keypress', function (evt) { + var key = evt.which; + + if (self.isOpen()) { + if (key === KEYS.ESC || (key === KEYS.UP && evt.altKey)) { + self.close(evt); + + evt.preventDefault(); + } else if (key === KEYS.ENTER || key === KEYS.TAB) { + self.trigger('results:select', {}); + + evt.preventDefault(); + } else if ((key === KEYS.SPACE && evt.ctrlKey)) { + self.trigger('results:toggle', {}); + + evt.preventDefault(); + } else if (key === KEYS.UP) { + self.trigger('results:previous', {}); + + evt.preventDefault(); + } else if (key === KEYS.DOWN) { + self.trigger('results:next', {}); + + evt.preventDefault(); + } + } else { + if (key === KEYS.ENTER || key === KEYS.SPACE || + (key === KEYS.DOWN && evt.altKey)) { + self.open(); + + evt.preventDefault(); + } + } + }); + }; + + Select2.prototype._syncAttributes = function () { + this.options.set('disabled', this.$element.prop('disabled')); + + if (this.isDisabled()) { + if (this.isOpen()) { + this.close(); + } + + this.trigger('disable', {}); + } else { + this.trigger('enable', {}); + } + }; + + Select2.prototype._isChangeMutation = function (mutations) { + var self = this; + + if (mutations.addedNodes && mutations.addedNodes.length > 0) { + for (var n = 0; n < mutations.addedNodes.length; n++) { + var node = mutations.addedNodes[n]; + + if (node.selected) { + return true; + } + } + } else if (mutations.removedNodes && mutations.removedNodes.length > 0) { + return true; + } else if (Array.isArray(mutations)) { + return mutations.some(function (mutation) { + return self._isChangeMutation(mutation); + }); + } + + return false; + }; + + Select2.prototype._syncSubtree = function (mutations) { + var changed = this._isChangeMutation(mutations); + var self = this; + + // Only re-pull the data if we think there is a change + if (changed) { + this.dataAdapter.current(function (currentData) { + self.trigger('selection:update', { + data: currentData + }); + }); + } + }; + + /** + * Override the trigger method to automatically trigger pre-events when + * there are events that can be prevented. + */ + Select2.prototype.trigger = function (name, args) { + var actualTrigger = Select2.__super__.trigger; + var preTriggerMap = { + 'open': 'opening', + 'close': 'closing', + 'select': 'selecting', + 'unselect': 'unselecting', + 'clear': 'clearing' + }; + + if (args === undefined) { + args = {}; + } + + if (name in preTriggerMap) { + var preTriggerName = preTriggerMap[name]; + var preTriggerArgs = { + prevented: false, + name: name, + args: args + }; + + actualTrigger.call(this, preTriggerName, preTriggerArgs); + + if (preTriggerArgs.prevented) { + args.prevented = true; + + return; + } + } + + actualTrigger.call(this, name, args); + }; + + Select2.prototype.toggleDropdown = function () { + if (this.isDisabled()) { + return; + } + + if (this.isOpen()) { + this.close(); + } else { + this.open(); + } + }; + + Select2.prototype.open = function () { + if (this.isOpen()) { + return; + } + + if (this.isDisabled()) { + return; + } + + this.trigger('query', {}); + }; + + Select2.prototype.close = function (evt) { + if (!this.isOpen()) { + return; + } + + this.trigger('close', { originalEvent : evt }); + }; + + /** + * Helper method to abstract the "enabled" (not "disabled") state of this + * object. + * + * @return {true} if the instance is not disabled. + * @return {false} if the instance is disabled. + */ + Select2.prototype.isEnabled = function () { + return !this.isDisabled(); + }; + + /** + * Helper method to abstract the "disabled" state of this object. + * + * @return {true} if the disabled option is true. + * @return {false} if the disabled option is false. + */ + Select2.prototype.isDisabled = function () { + return this.options.get('disabled'); + }; + + Select2.prototype.isOpen = function () { + return this.$container[0].classList.contains('select2-container--open'); + }; + + Select2.prototype.hasFocus = function () { + return this.$container[0].classList.contains('select2-container--focus'); + }; + + Select2.prototype.focus = function (data) { + // No need to re-trigger focus events if we are already focused + if (this.hasFocus()) { + return; + } + + this.$container[0].classList.add('select2-container--focus'); + this.trigger('focus', {}); + }; + + Select2.prototype.enable = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("enable")` method has been deprecated and will' + + ' be removed in later Select2 versions. Use $element.prop("disabled")' + + ' instead.' + ); + } + + if (args == null || args.length === 0) { + args = [true]; + } + + var disabled = !args[0]; + + this.$element.prop('disabled', disabled); + }; + + Select2.prototype.data = function () { + if (this.options.get('debug') && + arguments.length > 0 && window.console && console.warn) { + console.warn( + 'Select2: Data can no longer be set using `select2("data")`. You ' + + 'should consider setting the value instead using `$element.val()`.' + ); + } + + var data = []; + + this.dataAdapter.current(function (currentData) { + data = currentData; + }); + + return data; + }; + + Select2.prototype.val = function (args) { + if (this.options.get('debug') && window.console && console.warn) { + console.warn( + 'Select2: The `select2("val")` method has been deprecated and will be' + + ' removed in later Select2 versions. Use $element.val() instead.' + ); + } + + if (args == null || args.length === 0) { + return this.$element.val(); + } + + var newVal = args[0]; + + if (Array.isArray(newVal)) { + newVal = newVal.map(function (obj) { + return obj.toString(); + }); + } + + this.$element.val(newVal).trigger('input').trigger('change'); + }; + + Select2.prototype.destroy = function () { + Utils.RemoveData(this.$container[0]); + this.$container.remove(); + + this._observer.disconnect(); + this._observer = null; + + this._syncA = null; + this._syncS = null; + + this.$element.off('.select2'); + this.$element.attr('tabindex', + Utils.GetData(this.$element[0], 'old-tabindex')); + + this.$element[0].classList.remove('select2-hidden-accessible'); + this.$element.attr('aria-hidden', 'false'); + Utils.RemoveData(this.$element[0]); + this.$element.removeData('select2'); + + this.dataAdapter.destroy(); + this.selection.destroy(); + this.dropdown.destroy(); + this.results.destroy(); + + this.dataAdapter = null; + this.selection = null; + this.dropdown = null; + this.results = null; + }; + + Select2.prototype.render = function () { + var $container = $( + '' + + '' + + '' + + '' + ); + + $container.attr('dir', this.options.get('dir')); + + this.$container = $container; + + this.$container[0].classList + .add('select2-container--' + this.options.get('theme')); + + Utils.StoreData($container[0], 'element', this.$element); + + return $container; + }; + + return Select2; +}); + +S2.define('jquery-mousewheel',[ + 'jquery' +], function ($) { + // Used to shim jQuery.mousewheel for non-full builds. + return $; +}); + +S2.define('jquery.select2',[ + 'jquery', + 'jquery-mousewheel', + + './select2/core', + './select2/defaults', + './select2/utils' +], function ($, _, Select2, Defaults, Utils) { + if ($.fn.select2 == null) { + // All methods that should return the element + var thisMethods = ['open', 'close', 'destroy']; + + $.fn.select2 = function (options) { + options = options || {}; + + if (typeof options === 'object') { + this.each(function () { + var instanceOptions = $.extend(true, {}, options); + + var instance = new Select2($(this), instanceOptions); + }); + + return this; + } else if (typeof options === 'string') { + var ret; + var args = Array.prototype.slice.call(arguments, 1); + + this.each(function () { + var instance = Utils.GetData(this, 'select2'); + + if (instance == null && window.console && console.error) { + console.error( + 'The select2(\'' + options + '\') method was called on an ' + + 'element that is not using Select2.' + ); + } + + ret = instance[options].apply(instance, args); + }); + + // Check if we should be returning `this` + if (thisMethods.indexOf(options) > -1) { + return this; + } + + return ret; + } else { + throw new Error('Invalid arguments for Select2: ' + options); + } + }; + } + + if ($.fn.select2.defaults == null) { + $.fn.select2.defaults = Defaults; + } + + return Select2; +}); + + // Return the AMD loader configuration so it can be used outside of this file + return { + define: S2.define, + require: S2.require + }; +}()); + + // Autoload the jQuery bindings + // We know that all of the modules exist above this, so we're safe + var select2 = S2.require('jquery.select2'); + + // Hold the AMD module references on the jQuery function that was just loaded + // This allows Select2 to use the internal loader outside of this file, such + // as in the language files. + jQuery.fn.select2.amd = S2; + + // Return the Select2 instance for anyone who is importing it. + return select2; +})); diff --git a/src/scripts/cores/plugin.js b/src/scripts/cores/plugin.js new file mode 100644 index 0000000..00cb4c8 --- /dev/null +++ b/src/scripts/cores/plugin.js @@ -0,0 +1,97 @@ +const states = { + loading: 0, + interactive: 1, + complete: 2 +}; + +const READY_STATE = 'DOMContentLoaded'; + +function getElementData (el) { + const elDataset = el.dataset; + + return Object.keys(elDataset).reduce((obj, key) => { + let data = {}; + + if (elDataset[key]) { + data[key] = ''; + + try { + data[key] = JSON.parse(elDataset[key]); + } catch (err) { + data[key] = elDataset[key]; + } + } + + return { ...obj, ...data }; + }, {}); +} + +function setupClass (Class, element, options, pluginName) { + const _this = new Class(); + + _this.__pluginName = pluginName; + _this.$element = $(element); + _this.options = $.extend( + {}, + $.fn[pluginName].defaults, + getElementData(element), + options + ); + _this.props = {}; + + typeof _this.init === 'function' && _this.init(); + + return _this; +} + +export default function Plugin (param) { + function createPlugin (Class) { + const baseName = Class.name; + const name = baseName.toKebabCase(); + const options = (param && param.options) || {}; + const loadEvent = (param && param.when) || READY_STATE; + const selector = param && param.selector; + + function init () { + $(`[data-${name}]`)[name](); + + if (typeof selector === 'string') { + $(selector)[name](); + } + } + + $.fn[name] = function (opts, params) { + const instanceName = `${name}-instance`; + + return this.each(function () { + const instance = $.data(this, instanceName); + + if (!(instance instanceof Class)) { + $.data(this, instanceName, setupClass(Class, this, opts, name)); + + return; + } + + if (typeof instance[opts] !== 'function') { + console.error(`This element has been initialized with plugin ${baseName}, please provide a correct method`); + + return; + } + + instance[opts](params); + }); + }; + + $.fn[name].defaults = options; + + if (loadEvent === READY_STATE && states[document.readyState] > 0) { + init(); + } else { + window.addEventListener(loadEvent, init); + } + + return Class; + } + + return typeof param === 'function' ? createPlugin(param) : createPlugin; +} diff --git a/src/scripts/cores/prototype.js b/src/scripts/cores/prototype.js new file mode 100644 index 0000000..8d36500 --- /dev/null +++ b/src/scripts/cores/prototype.js @@ -0,0 +1,17 @@ +const PI = Math.PI; + +String.prototype.toCamelCase = function () { + return this.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); +}; + +String.prototype.toKebabCase = function () { + return this.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +}; + +Number.prototype.toRad = function () { + return this * PI / 180; +}; + +Number.prototype.toDeg = function () { + return this * 180 / PI; +}; diff --git a/src/scripts/initializations/browser-detect.js b/src/scripts/initializations/browser-detect.js new file mode 100644 index 0000000..6b5b080 --- /dev/null +++ b/src/scripts/initializations/browser-detect.js @@ -0,0 +1,59 @@ +const { documentElement: html } = document; + +const isTouch = 'ontouchstart' in window + || navigator.maxTouchPoints > 0 + || navigator.msMaxTouchPoints > 0; + +html.classList.add(isTouch ? 'touch' : 'no-touch'); + +if (typeof InstallTrigger !== 'undefined') { + html.classList.add('firefox'); +} + +const isIOSPlatform = [ + 'iPad Simulator', + 'iPhone Simulator', + 'iPod Simulator', + 'iPad', + 'iPhone', + 'iPod', +].includes(navigator.platform); + +if (isIOSPlatform || (navigator.userAgent.includes('Mac') && isTouch)) { + html.classList.add('ios'); +} + +const isDeviceMobile = [ + 'Android', + 'webOS', + 'iPhone', + 'iPod', + 'BlackBerry', + 'IEMobile', + 'Opera Mini', +].includes(navigator.userAgent); +if (isDeviceMobile) { + html.classList.add('is-device'); +} + +const isIE = document.documentMode; + +if (isIE) { + html.classList.add('ie'); +} + +if (!isIE && window.StyleMedia) { + html.classList.add('edge'); +} + +const isChrome = !!window.chrome; + +if (isChrome) { + html.classList.add('chrome'); +} + +if (isChrome && navigator.userAgent.indexOf('Edg') > -1) { + html.classList.add('edge-chromium'); +} + +export default null; diff --git a/src/scripts/initializations/import-jquery-plugins.js b/src/scripts/initializations/import-jquery-plugins.js new file mode 100644 index 0000000..7befcf3 --- /dev/null +++ b/src/scripts/initializations/import-jquery-plugins.js @@ -0,0 +1,9 @@ +// Initializations +import '../cores/prototype'; +import 'moment'; +import 'parsleyjs'; +import 'jquery'; + +// import '../_libs/jquery.fullPage.min.js'; + +window.devj = $; diff --git a/src/scripts/initializations/improve-window-events.js b/src/scripts/initializations/improve-window-events.js new file mode 100644 index 0000000..fc699c9 --- /dev/null +++ b/src/scripts/initializations/improve-window-events.js @@ -0,0 +1,82 @@ +import { throttle } from '../utils'; +import { $win } from '../utils/doms'; +import layout from '../utils/layout'; + +let passiveIfSupported = false; +let lastWinScroll = layout.scroll; +let resizeTimeout; +let lastWinWidth = layout.width; +let lastWinHeight = layout.height; +let lastBreakpointIsDesktop = layout.isDesktop; + +const RESIZE_TIME = 180; + +try { + const passive = Object.defineProperty({}, 'passive', { + get() { + passiveIfSupported = { passive: true }; + + return true; + }, + }); + + window.addEventListener('test', null, passive); +} catch (err) { /**/ } + +window.addEventListener('scroll', throttle(() => { + const currentWinScroll = layout.scroll; + + if (currentWinScroll === lastWinScroll) { + return; + } + + const name = currentWinScroll < lastWinScroll ? 'up' : 'down'; + + $win.trigger('scrolling', currentWinScroll); + $win.trigger(`scroll:${name}`, currentWinScroll); + + lastWinScroll = currentWinScroll; +}), passiveIfSupported); + +window.addEventListener('resize', () => { + clearTimeout(resizeTimeout); + setTimeout(() => { + const currentWinWidth = layout.width; + const currentWinHeight = layout.height; + const isWidthChanged = lastWinWidth !== currentWinWidth; + const isHeightChanged = lastWinHeight !== currentWinHeight; + + $win.trigger('resized', [currentWinWidth, currentWinHeight]); + + if (isWidthChanged) { + $win.trigger('width-change', currentWinWidth); + + const currentBreakpointIsDesktop = layout.isDesktop; + + if (lastBreakpointIsDesktop !== currentBreakpointIsDesktop) { + // Prevent conflict event name with slick + $win.trigger('breakpoint:change', currentWinWidth); + + const breakpointEvtName = currentBreakpointIsDesktop + ? 'desktop' + : 'mobile'; + + $win.trigger(`breakpoint:${breakpointEvtName}`, currentWinWidth); + + lastBreakpointIsDesktop = currentBreakpointIsDesktop; + } + + lastWinWidth = currentWinWidth; + } + + if (isHeightChanged) { + $win.trigger('height-change', currentWinHeight); + + lastWinHeight = currentWinHeight; + } + + if (isWidthChanged && isHeightChanged) { + $win.trigger('size-change', currentWinWidth, currentWinHeight); + } + }, RESIZE_TIME); +}, passiveIfSupported); diff --git a/src/scripts/initializations/update-js-assets-path.js b/src/scripts/initializations/update-js-assets-path.js new file mode 100644 index 0000000..2c6b38f --- /dev/null +++ b/src/scripts/initializations/update-js-assets-path.js @@ -0,0 +1,7 @@ +const { staticJsAssetsPath } = window; + +if (staticJsAssetsPath) { + __webpack_require__.p = staticJsAssetsPath; +} + +export default null; diff --git a/src/scripts/main.js b/src/scripts/main.js new file mode 100644 index 0000000..4617aca --- /dev/null +++ b/src/scripts/main.js @@ -0,0 +1,8 @@ +import '@/initializations/update-js-assets-path'; +import '@/initializations/browser-detect'; +import '@/initializations/import-jquery-plugins'; +import '@/initializations/improve-window-events'; +import 'slick-carousel'; + +// Plugins +import '@/plugins/back-to-top'; diff --git a/src/scripts/plugins/back-to-top.js b/src/scripts/plugins/back-to-top.js new file mode 100644 index 0000000..0251b65 --- /dev/null +++ b/src/scripts/plugins/back-to-top.js @@ -0,0 +1,22 @@ +import { $win } from '@/utils/doms'; + +@Plugin +export default class BackToTop { + init() { + const $ele = this.$element; + + $ele.off(`click`).on(`click`, () => { + $('html, body').animate({ + scrollTop: 0, + }); + }); + + $win.on('scroll', () => { + if ($win.scrollTop() > 20) { + $ele.addClass('active'); + } else { + $ele.removeClass('active'); + } + }); + } +} diff --git a/src/scripts/repositories/api.js b/src/scripts/repositories/api.js new file mode 100644 index 0000000..38106c7 --- /dev/null +++ b/src/scripts/repositories/api.js @@ -0,0 +1,98 @@ +import { transformObjectToParams } from '@/utils'; + +function getScript(src) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + + script.async = true; + script.src = src; + script.onload = resolve; + script.onerror = reject; + + document.head.appendChild(script); + }); +} + +export class ApiRepository { + baseUrl = ''; + + scriptCacher = {}; + + getScript = getScript; + + constructor(baseUrl = '') { + this.baseUrl = baseUrl; + } + + getUrl(path, params) { + let url = this.baseUrl + path; + + if (typeof params === 'object') { + url += `?${transformObjectToParams(params)}`; + } + + return url; + } + + loadScript(url) { + if (!this.scriptCacher[url]) { + this.scriptCacher[url] = getScript(url); + } + + return this.scriptCacher[url]; + } + + get(path, data) { + return new Promise((resolve, reject) => { + const url = this.getUrl(path); + + $.ajax({ + url, + data, + method: 'get', + }) + .done(resolve) + .catch(reject); + }); + } + + post(path, data = {}) { + return new Promise((resolve, reject) => { + const url = this.getUrl(path); + + $.ajax({ + url, + data, + method: 'post', + }) + .done(resolve) + .catch(reject); + }); + } + + put(path, data = {}) { + return new Promise((resolve, reject) => { + const url = this.getUrl(path); + + $.ajax({ + url, + data, + method: 'put', + }) + .done(resolve) + .catch(reject); + }); + } + + async resolveJSON(data) { + if (typeof data === 'object') { + return data; + } + + const response = await this.get(data); + + return response; + } +} + +export default new ApiRepository(); diff --git a/src/scripts/utils/cookies.js b/src/scripts/utils/cookies.js new file mode 100644 index 0000000..47cbf52 --- /dev/null +++ b/src/scripts/utils/cookies.js @@ -0,0 +1,40 @@ +export default { + set(key, value = '', days = 7) { + if (!key) { + console.error('no key to set cookie'); + return; + } + + let expires = ''; + + if (days) { + const date = new Date(); + const expiresDate = days * 24 * 60 * 60 * 1000; + + date.setTime(date.getTime() + expiresDate); + expires = `; expires=${date.toUTCString()}`; + } + + document.cookie = `${key}=${value.toString() + expires}`; + }, + + get(key) { + const cookieArr = document.cookie.split(';'); + + for (let i = 0, l = cookieArr.length; i < l; i += 1) { + const perCookie = cookieArr[i].trim(); + const indexOfSplitter = perCookie.trim().indexOf('='); + const perCookieKey = perCookie.slice(0, indexOfSplitter); + + if (perCookieKey === key) { + return perCookie.slice(indexOfSplitter + 1); + } + } + + return null; + }, + + remove(key) { + document.cookie = `${key}=; Max-Age=-99999999;`; + }, +}; diff --git a/src/scripts/utils/date.js b/src/scripts/utils/date.js new file mode 100644 index 0000000..6828b67 --- /dev/null +++ b/src/scripts/utils/date.js @@ -0,0 +1,11 @@ +import dayjs from 'dayjs'; + +export const defaultDateFormat = 'YYYY-MM-DD'; + +export function transformNumberToDate(number, format = defaultDateFormat) { + return dayjs(number).format(format); +} + +export function transformDateToNumber(date) { + return +dayjs(date, defaultDateFormat); +} diff --git a/src/scripts/utils/doms.js b/src/scripts/utils/doms.js new file mode 100644 index 0000000..43e6688 --- /dev/null +++ b/src/scripts/utils/doms.js @@ -0,0 +1,9 @@ +export const $win = $(window); +export const $doc = $(document); +export const $html = $('html'); +export const $body = $('body'); +export const $mapKey = $('meta[name="map-key"]'); +export const $htmlAndbody = $('html, body'); +export const $canFixed = $([ + '.header', +].join(',')); diff --git a/src/scripts/utils/http.js b/src/scripts/utils/http.js new file mode 100644 index 0000000..6bf70b8 --- /dev/null +++ b/src/scripts/utils/http.js @@ -0,0 +1,75 @@ +import { GMAP_URL, DEFAULT_AJAX_OPTS } from './variables'; +import { wait } from './index'; +import { lang } from './layout'; + +const SCRIPT_CACHED = {}; + +function getScript (src) { + return new Promise((resolve, reject) => { + let script = document.createElement('script'); + + script.async = true; + script.src = src; + script.onload = resolve; + script.onerror = reject; + + document.head.appendChild(script); + }); +} + +export function loadScript (url) { + if (!SCRIPT_CACHED[url]) { + SCRIPT_CACHED[url] = getScript(url); + } + + return SCRIPT_CACHED[url]; +} + +export function callApi (opts) { + return new Promise((resolve, reject) => { + const isString = typeof opts === 'string'; + + let options = { + ...DEFAULT_AJAX_OPTS, + data: {}, + url: isString ? opts : '' + }; + + if (!isString) { + options = { ...options, ...opts }; + } + + if (!options.data.lang) { + options.data.lang = lang; + } + + $.ajax(options).done(resolve).fail(reject); + }); +} + +export async function loadMapApi () { + await loadScript(GMAP_URL); + + return window.google.maps; +} + +export async function download (url, fileName = '') { + if (!url) { + return; + } + + const $link = $('', { + href: url, + download: fileName, + style: 'display:none' + }); + + $link + .on('click', e => e.stopImmediatePropagation()) + .appendTo('body')[0] + .click(); + + await wait(); + + $link.remove(); +} diff --git a/src/scripts/utils/index.js b/src/scripts/utils/index.js new file mode 100644 index 0000000..c0abd06 --- /dev/null +++ b/src/scripts/utils/index.js @@ -0,0 +1,53 @@ +export function throttle(fn, thisArg = window) { + let scheduledAnimationFrame; + + return () => { + if (scheduledAnimationFrame) { + return; + } + + scheduledAnimationFrame = true; + + requestAnimationFrame(() => { + fn.call(thisArg); + scheduledAnimationFrame = false; + }); + }; +} + +export function transformToCamelCase(str) { + return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); +} + +export function transformToKebabCase(str) { + return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +} + +export function cloneJSON(json) { + return JSON.parse(JSON.stringify(json)); +} + +export function transformObjectToParams(obj) { + return Object + .keys(obj) + .reduce((arr, key) => ( + arr.concat(`${key}=${encodeURIComponent(obj[key])}`) + ), []) + .join('&'); +} + +export function wait (ms = 10) { + let timeout; + + const _promise = new Promise((resolve) => { + timeout = setTimeout(() => { + resolve(timeout); + }, ms); + }); + + _promise.cancel = () => clearTimeout(timeout); + + return _promise; +} + +export const waitTmp = { cancel () { /* empty fn */ } }; diff --git a/src/scripts/utils/layout.js b/src/scripts/utils/layout.js new file mode 100644 index 0000000..57f94d4 --- /dev/null +++ b/src/scripts/utils/layout.js @@ -0,0 +1,225 @@ +import { $canFixed } from '@/utils/doms'; +import { RESPONSIVE_BREAKPOINTS } from './variables'; + +const { TABLET, DESKTOP } = RESPONSIVE_BREAKPOINTS; + +const { body, documentElement: html } = document; + +const freezeClass = '--freeze'; +const addBorderClass = '--add-fixed-border'; + +let lockTimeout; +let lastScroll; +let $header = $('.header'); + +function calculateScrollWidth() { + const div = document.createElement('div'); + + div.style.position = 'absolute'; + div.style.top = '0px'; + div.style.left = '0px'; + div.style.width = '100%'; + div.style.height = '50px'; + + body.appendChild(div); + + const fullWidth = div.offsetWidth; + + div.style.overflowY = 'scroll'; + + const limitWidth = div.clientWidth; + + body.removeChild(div); + + const scrollWidth = fullWidth - limitWidth; + + html.classList.add(`--scroll-${scrollWidth}`); + + return scrollWidth; +} + +export const removeAccents = (alias) => { + let str = alias; + str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a') + .replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, 'e') + .replace(/ì|í|ị|ỉ|ĩ/g, 'i') + .replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o') + .replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u') + .replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y') + .replace(/đ/g, 'd') + .replace(/À|Á|Ạ|Ả|Ã|Â|Ầ|Ấ|Ậ|Ẩ|Ẫ|Ă|Ằ|Ắ|Ặ|Ẳ|Ẵ/g, 'A') + .replace(/È|É|Ẹ|Ẻ|Ẽ|Ê|Ề|Ế|Ệ|Ể|Ễ/g, 'E') + .replace(/Ì|Í|Ị|Ỉ|Ĩ/g, 'I') + .replace(/Ò|Ó|Ọ|Ỏ|Õ|Ô|Ồ|Ố|Ộ|Ổ|Ỗ|Ơ|Ờ|Ớ|Ợ|Ở|Ỡ/g, 'O') + .replace(/Ù|Ú|Ụ|Ủ|Ũ|Ư|Ừ|Ứ|Ự|Ử|Ữ/g, 'U') + .replace(/Ỳ|Ý|Ỵ|Ỷ|Ỹ/g, 'Y') + .replace(/Đ/g, 'D') + .replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, '') + .replace(/\u02C6|\u0306|\u031B/g, '') + .replace(/[\u0300-\u036f]/g, ''); + return str; +}; + +export const removeEscape = (value) => { + const str = value.trim().replace(/(<([^>]+)>)|&|<|>|"|'/ig, ''); + + return removeAccents(str); +}; + +export const regexEmail = /^([A-Za-z0-9][\._\+\-]{0,1})+[A-Za-z0-9]+@[A-Za-z0-9]+(\.{0,1}[A-Za-z0-9]){2,3}\.[a-z]{2,3}$/; //eslint-disable-line + +export const scrollWidth = calculateScrollWidth(); +export const lang = html.getAttribute('lang') || ''; +export const isRTL = html.getAttribute('dir') === 'rtl'; +export const isIOS = html.classList.contains('ios'); +export const isIE = html.classList.contains('ie') + || html.classList.contains('edge'); + +export default { + scrollWidth, + lang, + isRTL, + isIOS, + isIE, + + get screenWidth() { + return window.innerWidth; + }, + + get width() { + return body.clientWidth; + }, + + get height() { + return window.innerHeight; + }, + + get bodyHeight() { + return body.offsetHeight; + }, + + get isFrozen() { + return body.classList.contains(freezeClass); + }, + + get isDesktop() { + return this.screenWidth >= DESKTOP; + }, + + get isSmallScreen() { + return this.screenWidth >= TABLET; + }, + + get isMobile() { + return !this.isDesktop; + }, + + get scroll() { + return document.documentElement.scrollTop + || document.body.scrollTop + || window.pageYOffset; + }, + + set scroll(top) { + window.scrollTo({ + top, + left: 0, + behavior: 'smooth', + }); + }, + + scrollImmediate(top) { + window.scrollTo(0, top); + }, + + scrollToElement($element, immediately) { + if (!$element[0]) { + return; + } + + if (!$header[0]) { + $header = $('.header'); + } + + const scrollTo = $element.offset().top - ($header.outerHeight() || 0); + + if (immediately) { + this.scrollImmediate(scrollTo); + } else { + this.scroll = $element.offset().top - ($header.outerHeight() || 0); + } + }, + + freeze(callback) { + clearTimeout(lockTimeout); + setTimeout(() => { + window.isFreezing = true; + + const willBeFrozen = !this.isFrozen; + + if (!willBeFrozen) { + if (typeof callback === 'function') { + callback(); + } + + return; + } + + if (this.isIOS) { + lastScroll = this.scroll; + body.style.top = `${-lastScroll}px`; + } + + body.classList.add(freezeClass); + + if (this.bodyHeight > this.height) { + body.classList.add(addBorderClass); + $canFixed.each((_, element) => { + if (window.getComputedStyle(element).position === 'fixed') { + element.classList.add(addBorderClass); + } + }); + } + + if (this.isIOS) { + this.scrollImmediate(0); + } + + if (typeof callback === 'function') { + callback(); + } + }); + }, + + unfreeze(callback) { + clearTimeout(lockTimeout); + lockTimeout = setTimeout(() => { + window.isFreezing = false; + + if (!this.isFrozen) { + if (typeof callback === 'function') { + callback(); + } + + return; + } + + body.classList.remove(freezeClass); + body.classList.remove(addBorderClass); + $canFixed.removeClass(addBorderClass); + + if (this.isIOS) { + body.style.top = ''; + this.scrollImmediate(lastScroll); + + if (typeof callback === 'function') { + setTimeout(() => { + callback(); + }, 50); + } + } else if (typeof callback === 'function') { + callback(); + } + }); + }, +}; diff --git a/src/scripts/utils/variables.js b/src/scripts/utils/variables.js new file mode 100644 index 0000000..b846adb --- /dev/null +++ b/src/scripts/utils/variables.js @@ -0,0 +1,17 @@ +import { $mapKey } from '../utils/doms'; + +const GMAP_KEY = $mapKey.attr('content') || ''; + + +export const GMAP_URL = GMAP_KEY && `https://maps.googleapis.com/maps/api/js?v=3&key=${GMAP_KEY}`; + +export const RESIZE_TIME = 180; + +export const RESPONSIVE_BREAKPOINTS = { + TABLET: 768, + DESKTOP: 992 +}; + +export const DEFAULT_AJAX_OPTS = { + cache: false +}; diff --git a/src/styles/$components/footer.scss b/src/styles/$components/footer.scss new file mode 100644 index 0000000..1c7476f --- /dev/null +++ b/src/styles/$components/footer.scss @@ -0,0 +1,15 @@ +.footer { + width: 100%; + height: rem(80); + align-items: center; + display: flex; + justify-content: space-between; + color: $color-white; + background-color: $color-black; + padding: 0 rem(20); + font-size: 16px; + + img { + max-width: rem(60); + } +} diff --git a/src/styles/$components/header.scss b/src/styles/$components/header.scss new file mode 100644 index 0000000..5cef4f0 --- /dev/null +++ b/src/styles/$components/header.scss @@ -0,0 +1,31 @@ +.header { + width: 100%; + padding: rem(10) rem(20); + color: $color-black; + background-color: $color-black; + display: flex; + align-items: center; + justify-content: space-between; + + img { + max-width: 60px; + } + + ul { + display: flex; + color: $color-white; + list-style: none; + padding-left: 0; + margin: 0; + + li { + padding: 0 rem(10); + + a { + color: $color-white; + font-size: rem(16); + text-decoration: none; + } + } + } +} diff --git a/src/styles/$libs.scss b/src/styles/$libs.scss new file mode 100644 index 0000000..8a43d3c --- /dev/null +++ b/src/styles/$libs.scss @@ -0,0 +1,8 @@ +@import "_utils/index"; +@import "_cores/layout"; + +@import "../../node_modules/bootstrap/scss/bootstrap"; +@import "../../node_modules/bootstrap-select/dist/css/bootstrap-select"; +@import "../../node_modules/slick-carousel/slick/slick"; +@import "../../node_modules/slick-carousel/slick/slick-theme"; +// @import "_libs/jquery.fullPage"; diff --git a/src/styles/_cores/layout.scss b/src/styles/_cores/layout.scss new file mode 100644 index 0000000..0fdd8ba --- /dev/null +++ b/src/styles/_cores/layout.scss @@ -0,0 +1,55 @@ +html { + font-family: $font-family-default; + font-size: $font-size-rem; + scroll-behavior: smooth; + // height: 100%; + height: -webkit-fill-available; + + // Fix modernizr bug + &.safari { + background: none; + } +} + +body { + position: relative; + width: 100%; + margin: 0; + font-size: $font-size-default; + font-family: $font-family-default; + --bs-body-font-family: $font-family-default; + + .main { + min-height: calc(100vh - 145px); + } + + &.freeze { + height: 100%; + overflow: hidden !important; /* stylelint-disable-line */ + + html.ios & { + position: fixed; + width: 100%; + } + + &[class*="--scroll-"] { + border-right-style: solid; + border-right-color: $color-white; + } + + @for $i from 13 through 18 { + &.\--scroll-#{$i} { + border-right-width: $i * 1px; + } + } + } +} + +img { + max-width: 100%; + + &:not([src]), + &[src=""] { + display: none; + } +} diff --git a/src/styles/_libs/.gitkeep b/src/styles/_libs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/styles/_libs/jquery.fullPage.css b/src/styles/_libs/jquery.fullPage.css new file mode 100644 index 0000000..054b7ba --- /dev/null +++ b/src/styles/_libs/jquery.fullPage.css @@ -0,0 +1,203 @@ +/** + * fullPage 2.6.6 + * https://github.com/alvarotrigo/fullPage.js + * MIT licensed + * + * Copyright (C) 2013 alvarotrigo.com - A project by Alvaro Trigo + */ +html.fp-enabled, +.fp-enabled body { + margin: 0; + padding: 0; + overflow:hidden; + + /*Avoid flicker on slides transitions for mobile phones #336 */ + -webkit-tap-highlight-color: rgba(0,0,0,0); +} +#superContainer { + height: 100%; + position: relative; + + /* Touch detection for Windows 8 */ + -ms-touch-action: none; + + /* IE 11 on Windows Phone 8.1*/ + touch-action: none; +} +.fp-section { + position: relative; + -webkit-box-sizing: border-box; /* Safari<=5 Android<=3 */ + -moz-box-sizing: border-box; /* <=28 */ + box-sizing: border-box; +} +.fp-slide { + float: left; +} +.fp-slide, .fp-slidesContainer { + height: 100%; + display: block; +} +.fp-slides { + z-index:1; + height: 100%; + overflow: hidden; + position: relative; + -webkit-transition: all 0.3s ease-out; /* Safari<=6 Android<=4.3 */ + transition: all 0.3s ease-out; +} +.fp-section.fp-table, .fp-slide.fp-table { + display: table; + table-layout:fixed; + width: 100%; +} +.fp-tableCell { + display: table-cell; + vertical-align: middle; + width: 100%; + height: 100%; +} +.fp-slidesContainer { + float: left; + position: relative; +} +.fp-controlArrow { + position: absolute; + z-index: 4; + top: 50%; + cursor: pointer; + width: 0; + height: 0; + border-style: solid; + margin-top: -38px; + -webkit-transform: translate3d(0,0,0); + -ms-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); +} +.fp-controlArrow.fp-prev { + left: 15px; + width: 0; + border-width: 38.5px 34px 38.5px 0; + border-color: transparent #fff transparent transparent; +} +.fp-controlArrow.fp-next { + right: 15px; + border-width: 38.5px 0 38.5px 34px; + border-color: transparent transparent transparent #fff; +} +.fp-scrollable { + overflow: scroll; +} +.fp-notransition { + -webkit-transition: none !important; + transition: none !important; +} +#fp-nav { + position: fixed; + z-index: 100; + margin-top: -32px; + top: 50%; + opacity: 1; + -webkit-transform: translate3d(0,0,0); +} +#fp-nav.right { + right: 17px; +} +#fp-nav.left { + left: 17px; +} +.fp-slidesNav{ + position: absolute; + z-index: 4; + left: 50%; + opacity: 1; +} +.fp-slidesNav.bottom { + bottom: 17px; +} +.fp-slidesNav.top { + top: 17px; +} +#fp-nav ul, +.fp-slidesNav ul { + margin: 0; + padding: 0; +} +#fp-nav ul li, +.fp-slidesNav ul li { + display: block; + width: 14px; + height: 13px; + margin: 7px; + position:relative; +} +.fp-slidesNav ul li { + display: inline-block; +} +#fp-nav ul li a, +.fp-slidesNav ul li a { + display: block; + position: relative; + z-index: 1; + width: 100%; + height: 100%; + cursor: pointer; + text-decoration: none; +} +#fp-nav ul li a.active span, +.fp-slidesNav ul li a.active span, +#fp-nav ul li:hover a.active span, +.fp-slidesNav ul li:hover a.active span{ + height: 12px; + width: 12px; + margin: -6px 0 0 -6px; + border-radius: 100%; + } +#fp-nav ul li a span, +.fp-slidesNav ul li a span { + border-radius: 50%; + position: absolute; + z-index: 1; + height: 4px; + width: 4px; + border: 0; + background: #333; + left: 50%; + top: 50%; + margin: -2px 0 0 -2px; + -webkit-transition: all 0.1s ease-in-out; + -moz-transition: all 0.1s ease-in-out; + -o-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; +} +#fp-nav ul li:hover a span, +.fp-slidesNav ul li:hover a span{ + width: 10px; + height: 10px; + margin: -5px 0px 0px -5px; +} +#fp-nav ul li .fp-tooltip { + position: absolute; + top: -2px; + color: #fff; + font-size: 14px; + font-family: arial, helvetica, sans-serif; + white-space: nowrap; + max-width: 220px; + overflow: hidden; + display: block; + opacity: 0; + width: 0; +} +#fp-nav ul li:hover .fp-tooltip, +#fp-nav.fp-show-active a.active + .fp-tooltip { + -webkit-transition: opacity 0.2s ease-in; + transition: opacity 0.2s ease-in; + width: auto; + opacity: 1; +} +#fp-nav ul li .fp-tooltip.right { + right: 20px; +} +#fp-nav ul li .fp-tooltip.left { + left: 20px; +} diff --git a/src/styles/_utils/common.scss b/src/styles/_utils/common.scss new file mode 100644 index 0000000..932649c --- /dev/null +++ b/src/styles/_utils/common.scss @@ -0,0 +1,21 @@ +.back-to-top { + position: fixed; + bottom: 45px; + right: 45px; + z-index: 10; + cursor: pointer; + opacity: 0; + transition: opacity 0.3s ease-in-out; + + img { + max-width: 60px; + } + + @include mq('md', max) { + right: 25px; + } + + &.active { + opacity: 1; + } +} diff --git a/src/styles/_utils/functions.scss b/src/styles/_utils/functions.scss new file mode 100644 index 0000000..2c69b79 --- /dev/null +++ b/src/styles/_utils/functions.scss @@ -0,0 +1,43 @@ +@use 'sass:math'; + +$unit-range: ( + 'px': 1px, + 'em': 1em, + 'rem': 1rem, +); + +@function addUnit($value, $unit) { + @if type-of($unit) != 'string' { + @error 'Value for unit should be a string.'; + } + + $matched-unit: map-get($unit-range, $unit); + + @if $matched-unit { + @return $value * $matched-unit; + } @else { + @error 'Value for unit not a valid unit.'; + } +} + +@function px2unit($unit, $pixels, $context) { + @if $unit { + @if (unitless($pixels)) { + $pixels: $pixels * 1px; + } + + @if (unitless($context)) { + $context: $context * 1px; + } + + @return addUnit(math.div($pixels, $context), $unit); + } +} + +@function em($pixels, $context) { + @return px2unit('em', $pixels, $context); +} + +@function rem($pixels, $context: $font-size-rem) { + @return px2unit('rem', $pixels, $context); +} diff --git a/src/styles/_utils/index.scss b/src/styles/_utils/index.scss new file mode 100644 index 0000000..eead94d --- /dev/null +++ b/src/styles/_utils/index.scss @@ -0,0 +1,5 @@ +@import "variables"; +@import "override-grid"; +@import "functions"; +@import "mixins"; +@import "common"; diff --git a/src/styles/_utils/mixins.scss b/src/styles/_utils/mixins.scss new file mode 100644 index 0000000..4050086 --- /dev/null +++ b/src/styles/_utils/mixins.scss @@ -0,0 +1,182 @@ +@mixin font-face($name, $pathNotIncludeExt, $weight: normal, $style: normal) { + @font-face { + font-family: $name; + src: + url("#{$pathNotIncludeExt}.woff") format("woff"), + url("#{$pathNotIncludeExt}.woff2") format("woff2"); + font-weight: $weight; + font-style: $style; + font-display: swap; + } +} + +@mixin font-smooth { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +@function screen($name) { + @return map-get($grid-breakpoints, $name); +} + +@mixin mq($width, $type: min) { + @if map_has_key($grid-breakpoints, $width) { + $width: screen($width); + } + // @if unitless($width) { + // $width: $width * 1px + // } + + @if $type == max { + $width: $width - 1px; + } + + @media only screen and (#{$type}-width: $width) { + @content; + } +} + +@mixin ip-small() { + @media only screen and (max-width: 375px) and (max-height: 600px) { + .iphone & { + @content; + } + .chrome & { + @content; + } + } +} + +@mixin ip-medium() { + @media only screen and (max-width: 375px) and (max-height: 667px) { + .iphone & { + @content; + } + .chrome & { + @content; + } + } +} + +@mixin ip-chrome-large() { + @media only screen and (max-width: 414px) and (max-height: 720px) { + .iphone.chrome & { + @content; + } + } +} + +@mixin ip-large() { + @media only screen and (max-width: 450px) and (max-height: 844px) { + .iphone & { + @content; + } + .chrome & { + @content; + } + } +} + +@mixin android-large() { + @media only screen and (max-width: 414px) and (max-height: 896px) { + .chrome & { + @content; + } + } +} + +@mixin mm($minwidth, $maxwidth) { + @if map_has_key($grid-breakpoints, $minwidth) { + $minwidth: screen($minwidth); + } + @if map_has_key($grid-breakpoints, $maxwidth) { + $maxwidth: screen($maxwidth) - 1px; + } + @media only screen and (min-width: $minwidth) and (max-width: $maxwidth) { + @content; + } +} + +@mixin no-touch { + html.no-touch & { + @content; + } +} +@mixin hover($parentSelector: false) { + @if $parentSelector { + #{$parentSelector}:hover & { + @include no-touch { + @content; + } + } + } @else { + @include no-touch { + &:hover { + @content; + } + } + } +} + + +@mixin size($width, $height: $width) { + @if unitless($width) { + $width: rem($width * 1px) + } + + @if unitless($height) { + $height: rem($height * 1px) + } + + width: $width; + height: $height; +} + + +@mixin font-carnegie-sans-regular { + font-family: $font-family-default; + font-weight: normal; +} +@mixin font-carnegie-sans-bold { + font-family: $font-family-default; + font-weight: 700; +} +@mixin font-carnegie-sans-light { + font-family: $font-family-default; + font-weight: 300; +} + + +@mixin text-overflow { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + + +@mixin overflow-y { + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +@mixin overflow-x { + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: none; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } +} + +@mixin hide-text-with-line($line: 4) { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: $line; + -webkit-box-orient: vertical; + white-space: initial; +} diff --git a/src/styles/_utils/override-grid.scss b/src/styles/_utils/override-grid.scss new file mode 100644 index 0000000..83724e5 --- /dev/null +++ b/src/styles/_utils/override-grid.scss @@ -0,0 +1,26 @@ +$gutters: 20px; +$half-gutters: 10px; + +$screen-xxl: 1600; +$screen-xl: 1440; +$screen-lg: 1200; +$screen-md: 992; +$screen-sm: 768; +$screen-xs: 0; +$screen-xs-max: 767px; +$screen-md-max: 991px; + +$grid-breakpoints: ( + xs: 0, + sm: 768px, + md: 992px, + lg: 1200px, + xl: 1440px, +); + +$container-max-widths: ( + sm: 720px, + md: 960px, + lg: 1134px, + xl: 1400px, +); diff --git a/src/styles/_utils/variables.scss b/src/styles/_utils/variables.scss new file mode 100644 index 0000000..d4261f5 --- /dev/null +++ b/src/styles/_utils/variables.scss @@ -0,0 +1,5 @@ +@import "variables/others"; +@import "variables/colors"; +@import "variables/fonts"; +@import "variables/z-indexes"; +@import "variables/icomoon"; diff --git a/src/styles/_utils/variables/colors.scss b/src/styles/_utils/variables/colors.scss new file mode 100644 index 0000000..a78053f --- /dev/null +++ b/src/styles/_utils/variables/colors.scss @@ -0,0 +1,2 @@ +$color-white: #FFFFFF; +$color-black: #000000; diff --git a/src/styles/_utils/variables/fonts.scss b/src/styles/_utils/variables/fonts.scss new file mode 100644 index 0000000..626cfc5 --- /dev/null +++ b/src/styles/_utils/variables/fonts.scss @@ -0,0 +1,7 @@ +$font-family-default: 'Arial', sans-serif; + +$font-size-rem: 10px; +$font-size-mobile-default: 18px; +$font-size-desktop-default: 18px; +$line-height-default: 1.5; +$font-size-default: 14px; diff --git a/src/styles/_utils/variables/icomoon.scss b/src/styles/_utils/variables/icomoon.scss new file mode 100644 index 0000000..67c679e --- /dev/null +++ b/src/styles/_utils/variables/icomoon.scss @@ -0,0 +1,33 @@ +@font-face { + font-family: 'icomoon'; + src: url('../fonts/icomoon/icomoon.eot?dr0xri'); + src: url('../fonts/icomoon/icomoon.eot?dr0xri#iefix') format('embedded-opentype'), + url('../fonts/icomoon/icomoon.ttf?dr0xri') format('truetype'), + url('../fonts/icomoon/icomoon.woff?dr0xri') format('woff'), + url('../fonts/icomoon/icomoon.svg?dr0xri#icomoon') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'icomoon' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-arrow-down:before { + content: "\e901"; +} +.icon-infomation:before { + content: "\e900"; +} \ No newline at end of file diff --git a/src/styles/_utils/variables/others.scss b/src/styles/_utils/variables/others.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/styles/_utils/variables/z-indexes.scss b/src/styles/_utils/variables/z-indexes.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/styles/apps.scss b/src/styles/apps.scss new file mode 100644 index 0000000..e49d5ef --- /dev/null +++ b/src/styles/apps.scss @@ -0,0 +1,4 @@ +@import "_utils/index"; + +@import "$components/header"; +@import "$components/footer"; diff --git a/src/views/$modules/back-to-top.pug b/src/views/$modules/back-to-top.pug new file mode 100644 index 0000000..3c401e4 --- /dev/null +++ b/src/views/$modules/back-to-top.pug @@ -0,0 +1,2 @@ +.back-to-top(data-back-to-top) + +img('https://www.seekpng.com/png/detail/216-2162589_back-to-top-img-back-to-top.png', 'Back to top') diff --git a/src/views/$modules/footer.pug b/src/views/$modules/footer.pug new file mode 100644 index 0000000..da103fc --- /dev/null +++ b/src/views/$modules/footer.pug @@ -0,0 +1,5 @@ +footer.footer + .logo + img(src='../images/logo.png') + .copyright + span.text-copyright © 2022 Demo. All Rights Reserved. diff --git a/src/views/$modules/header.pug b/src/views/$modules/header.pug new file mode 100644 index 0000000..89df2f0 --- /dev/null +++ b/src/views/$modules/header.pug @@ -0,0 +1,12 @@ +header.header + .logo-pineapple + +link('/index.html', 'Home') + img(src='../images/logo.png') + .main-menu + ul.active-mb + li + +link('/index.html', 'Home')(class=[menuIndex === 0 && '--active' ]) + li + +link('#', 'About us')(class=[menuIndex === 2 && '--active' ]) + li + +link('#', 'Contact')(class=[menuIndex === 3 && '--active' ]) diff --git a/src/views/_blocks/favicon.pug b/src/views/_blocks/favicon.pug new file mode 100644 index 0000000..54edd85 --- /dev/null +++ b/src/views/_blocks/favicon.pug @@ -0,0 +1,12 @@ +| +| +// Start: Favicon +link(rel="apple-touch-icon" sizes="180x180" href=`${assetsPath}apple-touch-icon.png`) +link(rel="icon" type="image/png" sizes="32x32" href=`${assetsPath}favicon-32x32.png`) +link(rel="icon" type="image/png" sizes="16x16" href=`${assetsPath}favicon-16x16.png`) +link(rel="mask-icon" href="/safari-pinned-tab.svg" color="#e9662c") +meta(name="msapplication-TileColor" content="#ffc40d") +meta(name="theme-color" content="#ffffff") +// End: Favicon +| +| diff --git a/src/views/_blocks/meta.pug b/src/views/_blocks/meta.pug new file mode 100644 index 0000000..f46b050 --- /dev/null +++ b/src/views/_blocks/meta.pug @@ -0,0 +1,8 @@ +| +| +// Start: Meta +meta(name="map-key" content="") +//- meta(http-equiv="Content-Security-Policy", content="upgrade-insecure-requests") +// End: Keys +| +| diff --git a/src/views/_blocks/scripts.pug b/src/views/_blocks/scripts.pug new file mode 100644 index 0000000..03bd9c6 --- /dev/null +++ b/src/views/_blocks/scripts.pug @@ -0,0 +1,4 @@ +// build:js +script(src=`${assetsPath}js/externals.js` defer) +script(src=`${assetsPath}js/main.js` defer) +// endbuild diff --git a/src/views/_blocks/styles.pug b/src/views/_blocks/styles.pug new file mode 100644 index 0000000..1735b56 --- /dev/null +++ b/src/views/_blocks/styles.pug @@ -0,0 +1,9 @@ +| +| ++style('$libs') ++style('apps') +// endbuild +| +| + + diff --git a/src/views/_layouts/layout-component.pug b/src/views/_layouts/layout-component.pug new file mode 100644 index 0000000..7f2fddf --- /dev/null +++ b/src/views/_layouts/layout-component.pug @@ -0,0 +1,154 @@ +extends layout + +block append vars + - useHeader = false + - useFooter = false + - pageLevel = 1 + +block styles + link( + rel="stylesheet" + href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.1/styles/vs2015.min.css" + ) + + style. + .component-container { + max-width: 1600px; + padding: 0 4rem; + margin: 0 auto; + } + + @media screen and (min-width: 992px) { + .component-container { + padding: 0 4rem; + } + } + + .component-dev { + counter-reset: count; + padding: 1rem; + margin-bottom: 5rem; + } + + .component-dev > .title, + .component-dev > .block > .sub-title { + line-height: 1.1; + letter-spacing: 0; + font-weight: bold; + } + + .component-dev > .title { + font-size: 30px; + margin-bottom: 20px; + border-left: 10px solid currentColor; + padding-left: 10px; + } + + .component-dev > .block { + margin-top: 20px; + } + + .component-dev > .block > .sub-title { + font-size: 20px; + margin-bottom: 10px; + } + + .component-dev .section.\--custom-height { + height: 35vh; + } + + .component-dev > .block > .sub-title:before { + counter-increment: count; + content: counter(count) '. '; + } + + .component-dev > .block > .code-frame { + position: relative; + font-family: Consolas, monospace; + font-size: 14px; + z-index: 1; + overflow: auto; + } + + .component-dev > .block > .code-frame > textarea { + width: 100%; + height: 300px; + } + + .component-dev > .block > .code-frame > code { + padding: 20px; + } + + .component-dev > .block > .block-demo { + border: 1px dashed rgba(255, 0, 0, .3); + } + + .component.complete > .block > .block-demo { + border-color: rgba(5, 70, 22, .3); + } + + .component-dev .color-block { + width: 100%; + height: 150px; + border: 3px solid #C3C3C3; + } + + .component-dev .demo-ico em[class^="ico"], + .component-dev .demo-ico em.fa { + color: #C9BA9E; + font-size: 20px; + } + +block styles-custom + +block append scripts + script(src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.13.5/beautify-html.min.js") + script(src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.1/highlight.min.js") + script(src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.1/languages/htmlbars.min.js") + script. + document.querySelectorAll('.code-frame .textarea').forEach(function (element) { + var pre = document.createElement('pre'); + var code = document.createElement('code'); + var preCode = element.innerHTML + .trim() + .replace(/"\{/g, "'{") + .replace(/"\[/g, "'[") + .replace(/\}"/g, "}'") + .replace(/\]"/g, "]'") + .replace(/\<"/g, "<'") + .replace(/\'/g, "'") + .replace(/\"/g, "\"") + .replace(/"/g, "\"") + + var html = html_beautify(preCode, { + indent_size: 4, + indent_char: ' ', + break_chained_methods: true + }) + + code.innerText = html; + + code.classList.add('html'); + pre.appendChild(code); + element.insertAdjacentElement('afterend', pre); + element.parentElement.removeChild(element); + }); + + hljs.highlightAll(); + +block mixin + mixin component(title) + .component-container + .component-dev&attributes(attributes) + h1.h1.title!=title + + .block + h2.sub-title Demo + .block-demo + block + + .block + h2.sub-title Code Frame + .code-frame + .textarea + block diff --git a/src/views/_layouts/layout.pug b/src/views/_layouts/layout.pug new file mode 100644 index 0000000..f64cb10 --- /dev/null +++ b/src/views/_layouts/layout.pug @@ -0,0 +1,81 @@ +include ../_mixins/all + +block vars + - let useHeader = true + - let useFooter = true + - let useBackToTop = true + - let footerSpace = false + - let title = 'Layout' + - let namespace = '' + - let menuIndex = -1 + - let pageLevel = 0 + - let classMain = '' + - let titleOrg = '' + - let descOrg = '' + - let imgOrg = '' + - let urlOrg = '' + +- const rootAssetsPath = $localeName ? '../' : '' +- const _levelPath = [...Array(pageLevel)].map(() => '..').join('/') +- const _trailDash = pageLevel ? '/' : '' +- const levelPath = _levelPath + _trailDash +- const assetsPath = rootAssetsPath + levelPath +- const isRTL = $translator.isRTL +- const dir = isRTL ? 'rtl' : 'ltr' +- const rtlSuffix = isRTL ? '-rtl' : '' +- const pagePath = $path; +- + const viewports = [ + 'width=device-width', + 'initial-scale=1', + 'shrink-to-fit=no', + 'user-scalable=0' + ].join(',') + +block const + +- + const translateAssetPath = (path, useLevel) => { + const isExternalPath = /^(https?:)?\/\//.test(path) + + return isExternalPath + ? path + : (useLevel ? levelPath : assetsPath) + (path.replace ? path.replace(/^\//, '') : '') + } + +block mixin + +doctype html +html(lang=$localeName dir=dir) + head + title=title + meta(http-equiv="X-UA-Compatible" content="IE=edge") + meta(charset="utf-8") + meta(name="viewport" content!=viewports) + + include ../_blocks/meta + include ../_blocks/favicon + include ../_blocks/styles + + block styles + block styles-custom + + body(class=namespace) + if useHeader + include ../$modules/header + + - var mainClass = useHeader ? classList : '' + .main(class!=classMain) + block container + + if useFooter + include ../$modules/footer + + if useBackToTop + include ../$modules/back-to-top + + block scripts + script. + var staticJsAssetsPath = '#{assetsPath}js/'; + + include ../_blocks/scripts diff --git a/src/views/_mixins/all.pug b/src/views/_mixins/all.pug new file mode 100644 index 0000000..d6451e2 --- /dev/null +++ b/src/views/_mixins/all.pug @@ -0,0 +1,7 @@ +- imageTransparent = 'images/transparent.png' + +include common/style +include common/link +include common/img +include common/nav +include common/cta diff --git a/src/views/_mixins/common/cta.pug b/src/views/_mixins/common/cta.pug new file mode 100644 index 0000000..51f4d15 --- /dev/null +++ b/src/views/_mixins/common/cta.pug @@ -0,0 +1,24 @@ +mixin cta(href = '#', title) + +link(href, title).cta-primary&attributes(attributes) + span!=title + + +mixin cta-button(title) + button(type="button").button.cta-primary&attributes(attributes) + span!=title + +mixin cta-link(href='#', title) + +link(href, title).cta-link&attributes(attributes) + span!=title + span.icon-arrow-right-thin + +mixin cta-radio-button(name, title) + label.cta-primary.cta-radio-button + input(type="radio" name=name)&attributes(attributes) + span!=title + +mixin cta-block-link(href='#', title) + .cta-block-link + +link(href, title)&attributes(attributes) + span!=title + span.icon-arrow-down diff --git a/src/views/_mixins/common/img.pug b/src/views/_mixins/common/img.pug new file mode 100644 index 0000000..8286f5c --- /dev/null +++ b/src/views/_mixins/common/img.pug @@ -0,0 +1,13 @@ +mixin img(src = imageTransparent, alt) + - const _src = translateAssetPath(src) + - const _alt = alt || _src.slice(_src.lastIndexOf('/') + 1) + img(src=_src alt=_alt)&attributes(attributes) + +mixin picture(sources, alt) + picture&attributes(picAttrs) + each source, breakpoint in sources + - const src = typeof source === 'string' ? source : (source.src || imageTransparent) + if breakpoint === 'default' + +img(src, alt)&attributes(attributes) + else + source(media=`(max-width: ${breakpoint}px)` srcset=translateAssetPath(src) type=source.type) diff --git a/src/views/_mixins/common/link.pug b/src/views/_mixins/common/link.pug new file mode 100644 index 0000000..badd74a --- /dev/null +++ b/src/views/_mixins/common/link.pug @@ -0,0 +1,8 @@ +mixin link(href = '#', title, text = title) + - const _href = href !== '#' ? translateAssetPath(href, true) : href + - const _title = title || _href.slice(_href.lastIndexOf('/') + 1) + a(href=_href title=_title)&attributes(attributes) + if block + block + else + !=text diff --git a/src/views/_mixins/common/nav.pug b/src/views/_mixins/common/nav.pug new file mode 100644 index 0000000..4dad953 --- /dev/null +++ b/src/views/_mixins/common/nav.pug @@ -0,0 +1,23 @@ +mixin nav(items = []) + ul.navigation&attributes(attributes) + each item, index in items + li.navigation-item( + class= item.isActive ? '--active' : '' + ) + +link(item.href, item.title, item.text) + + if item.itemsMenu2 && item.itemsMenu2[0] + span.icon.icon-plus + span.icon-close + ul.navigation-level2 + each item2, index in item.itemsMenu2 + li.navigation-item + +link(item2.href, item2.title, item2.text) + + if item2.itemsMenu3 && item2.itemsMenu3[0] + span.icon.icon-plus + span.icon.icon-close + ul.navigation-level3 + each item3, index in item2.itemsMenu3 + li.navigation-item + +link(item3.href, item3.title, item3.text) diff --git a/src/views/_mixins/common/style.pug b/src/views/_mixins/common/style.pug new file mode 100644 index 0000000..a12cf54 --- /dev/null +++ b/src/views/_mixins/common/style.pug @@ -0,0 +1,2 @@ +mixin style(name) + link(rel="stylesheet" href=`${assetsPath}css/${name + rtlSuffix}.css`) diff --git a/src/views/components/footer.pug b/src/views/components/footer.pug new file mode 100644 index 0000000..b77ce25 --- /dev/null +++ b/src/views/components/footer.pug @@ -0,0 +1,8 @@ +extends ../_layouts/layout-component + +block append vars + - title = 'Footer' + +block container + +component('Footer') + include ../$modules/footer diff --git a/src/views/components/header.pug b/src/views/components/header.pug new file mode 100644 index 0000000..7ee0e12 --- /dev/null +++ b/src/views/components/header.pug @@ -0,0 +1,8 @@ +extends ../_layouts/layout-component + +block append vars + - title = 'Header' + +block container + +component('Header') + include ../$modules/header diff --git a/src/views/index.pug b/src/views/index.pug new file mode 100644 index 0000000..2535cdf --- /dev/null +++ b/src/views/index.pug @@ -0,0 +1,10 @@ +extends _layouts/layout + +block append vars + - title = 'Homepage' + - classMain = 'homepage' + +block container + section + .container + h1 Test diff --git a/src/views/sitemap.pug b/src/views/sitemap.pug new file mode 100644 index 0000000..d4b9869 --- /dev/null +++ b/src/views/sitemap.pug @@ -0,0 +1,138 @@ +extends _layouts/layout + +block append vars + - title = 'Sitemap' + - useHeader = false + - useFooter = false + +block append container + mixin blank(title, href = 'javascript:void(0)', target = "_self") + .blank + +link(href, title)(target=target).link + span.text=title + + .container-fluid + h1.text-center SITEMAP + + .block + h2 Pages + .clearfix + +blank('Homepage', '/index.html') + + + + + style. + body { + background: #eee; + overflow-x: hidden; + padding: 0 20px; + font-size: 16px; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.1; + letter-spacing: 0; + font-weight: bold; + } + + h1 { + font-size: 2.5em; + margin: 1rem 0; + } + + h2 { + font-size: 1.5em; + text-transform: uppercase; + margin-bottom: 1rem; + border-left: .5rem solid black; + padding-left: 1rem; + } + + h3 { + font-size: 1.2em; + text-transform: uppercase; + margin-bottom: 1rem; + border-left: 3px solid black; + padding-left: 1rem; + } + + h4 { + font-size: 1em; + } + + h5 { + font-size: 0.83em; + } + + h6 { + font-size: 0.67em; + } + .clearfix { + overflow: hidden; + } + + .text-center { + text-align: center; + } + + .block { + margin-bottom: 2rem; + } + + @media screen and (min-width: 992px) { + .block { + margin-bottom: 5rem; + } + } + + .block .clearfix { + counter-reset: link; + } + + .blank { + float: left; + width: 100%; + padding: 0 .5rem; + } + + @media screen and (min-width: 992px) { + .blank { + width: calc(20% - 3px); + } + } + + .link { + display: block; + position: relative; + margin-bottom: 1rem; + padding: .7rem 1rem; + text-decoration: none; + border: 1px solid rgba(0,0,0,.2); + font-size: .85em; + background: #fff; + color: inherit; + box-shadow: 0 0 2px rgba(0,0,0,0); + transform: translateY(0); + transition: all .1s ease; + } + + .link:hover, + .link:focus { + text-decoration: none; + border-color: rgba(0,0,0,.5); + color: inherit; + box-shadow: 0 10px 10px -4px rgba(0,0,0,.1); + } + + .link::before { + counter-increment: link; + content: counter(link) "."; + font-weight: bold; + margin-right: 5px; + }