├── .gitignore ├── .travis.yml ├── README.md ├── build.js ├── lib ├── Api.js ├── Bootstrap.js ├── GitTags.js ├── Json.js ├── LessCompiler.js ├── Logger │ ├── Logger.js │ ├── LoggerLevel.js │ ├── LoggerLevels.js │ ├── LoggerLine.js │ ├── LoggerLines.js │ ├── LoggerResult.js │ └── LoggerTask.js ├── Paths.js ├── SassCompiler.js ├── Sri.js ├── Versions.js ├── argv.js ├── fs.js ├── logger.js └── semver.js ├── package-lock.json ├── package.json └── src └── 3.x.x ├── 7.x-3.x ├── less │ ├── README.md │ ├── bootstrap.less │ ├── drupal-bootstrap.less │ ├── style.less │ └── variable-overrides.less └── scss │ ├── README.md │ ├── _default-variables.scss │ ├── _drupal-bootstrap.scss │ └── style.scss ├── 8.x-3.x ├── less │ ├── README.md │ ├── bootstrap.less │ ├── drupal-bootstrap.less │ ├── drupal-bootstrap │ │ ├── component │ │ │ ├── ajax.less │ │ │ ├── alert.less │ │ │ ├── field.less │ │ │ ├── file.less │ │ │ ├── filter.less │ │ │ ├── form.less │ │ │ ├── icon.less │ │ │ ├── navbar.less │ │ │ ├── node.less │ │ │ ├── panel.less │ │ │ ├── progress-bar.less │ │ │ ├── table-drag.less │ │ │ ├── tabs.less │ │ │ └── toolbar.less │ │ └── jquery-ui │ │ │ └── autocomplete.less │ ├── style.less │ └── variable-overrides.less └── scss │ ├── README.md │ ├── _default-variables.scss │ ├── _drupal-bootstrap.scss │ ├── drupal-bootstrap │ ├── component │ │ ├── _ajax.scss │ │ ├── _alert.scss │ │ ├── _field.scss │ │ ├── _file.scss │ │ ├── _filter.scss │ │ ├── _form.scss │ │ ├── _icon.scss │ │ ├── _navbar.scss │ │ ├── _node.scss │ │ ├── _panel.scss │ │ ├── _progress-bar.scss │ │ ├── _table-drag.scss │ │ ├── _tabs.scss │ │ └── _toolbar.scss │ └── jquery-ui │ │ └── _autocomplete.scss │ └── style.scss └── missing-variables.less /.gitignore: -------------------------------------------------------------------------------- 1 | assets 2 | dist 3 | .DS_Store 4 | 5 | # Created by .ignore support plugin (hsz.mobi) 6 | ### Node template 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | 66 | # parcel-bundler cache (https://parceljs.org/) 67 | .cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless 80 | 81 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - lts/* 5 | - 8 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drupal Bootstrap Styles 2 | 3 | > Styles used to bridge the gap between Drupal and Bootstrap. 4 | 5 | The primary purpose of this project is to provide additional default styling for 6 | [Drupal Bootstrap] based sub-themes. 7 | 8 | ## Source Files 9 | 10 | #### 8.x-3.x ([documentation](https://drupal-bootstrap.org/api/bootstrap/docs!Sub-Theming.md/group/sub_theming/8.x-3.x)) 11 | - [LESS](src/3.x.x/8.x-3.x/less/) 12 | - [SASS](src/3.x.x/8.x-3.x/scss/) 13 | 14 | #### 7.x-3.x ([documentation](https://drupal-bootstrap.org/api/bootstrap/docs!Sub-Theming.md/group/sub_theming/7.x-3.x)) 15 | - [LESS](src/3.x.x/7.x-3.x/less/) 16 | - [SASS](src/3.x.x/7.x-3.x/scss/) 17 | 18 | --- 19 | 20 | ## CDN (Distributed) 21 | 22 | For CDN based sub-themes, this project compiles the above source files for all 23 | branches, versions and themes and publishes them as a package available on 24 | [NPM]. This is primarily due to the shear number of permutations created by 25 | these variables; which number in the hundreds, of which the majority of them 26 | are simply duplicates of previously compiled output. 27 | 28 | ## Development 29 | 30 | When developing locally, you must first install [Node.js] and [Yarn]. Once 31 | installed, you may run the following commands from the root directory of this 32 | project. 33 | 34 | #### Project Installation 35 | 36 | ```bash 37 | yarn install 38 | ``` 39 | 40 | #### Compiling 41 | 42 | ```bash 43 | yarn build 44 | ``` 45 | 46 | #### API 47 | 48 | After the project has been compiled, there is a `./dist/api.json` file that is 49 | generated alongside the compiled source files. This file contains a record 50 | of all files generated. If a file has a `symlink` entry, it means the file is 51 | a duplicate of a previously generated file and you should use it instead. This 52 | file is necessary since [NPM] does not actually publish symlinks. 53 | 54 | ## History 55 | 56 | This project was created in an effort to consolidate and simplify the tedious 57 | task of compiling multiple permutations of the generated styles within the 58 | project. It was also done in an effort to reduce the overall packaged size of 59 | the base theme as not everyone will need these styles and, if so, would only 60 | need a single compiled versions based on the version and theme chosen. For more 61 | information, see the following original Drupal.org issue: 62 | 63 | [Move "overrides" source files and generated CSS to separate project](https://www.drupal.org/project/bootstrap/issues/2852156) 64 | 65 | 66 | [Bootstrap Framework]: https://getbootstrap.com/docs/3.4/ 67 | [Bootstrap Framework Source Files]: https://github.com/twbs/bootstrap-sass 68 | [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap 69 | [Drupal Bootstrap Styles]: https://github.com/unicorn-fail/drupal-bootstrap-styles 70 | [Sass]: http://sass-lang.com 71 | [NPM]: https://www.npmjs.com/package/@unicorn-fail/drupal-bootstrap-styles 72 | [Node.js]: https://nodejs.org 73 | [Yarn]: https://yarnpkg.com 74 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | global.Promise = require('bluebird'); 3 | global.Promise.config({}) 4 | 5 | const path = require('path'); 6 | 7 | const argv = require('./lib/argv.js'); 8 | const fs = require('./lib/fs.js').default; 9 | const logger = require('./lib/logger.js').default; 10 | const api = require('./lib/Api.js').default; 11 | const Bootstrap = require('./lib/Bootstrap.js').default; 12 | const { absolute } = require('./lib/Paths').default; 13 | const versions = require('./lib/Versions.js').default; 14 | 15 | (async () => { 16 | return Promise.resolve() 17 | .then(() => argv.removeDist && fs.remove(absolute.dist())) 18 | .then(() => fs.ensureDir(absolute.dist())) 19 | .then(() => fs.list(absolute.src(), { files: false })) 20 | .then(({ value: ranges }) => [...ranges].map(d => d.split(path.sep).pop())) 21 | .mapSeries(range => versions.matching('bootstrap', range) 22 | .then(({ value: versions }) => versions) 23 | .mapSeries(version => { 24 | logger.header(['bootstrap@%s', version]); 25 | const asset = logger.createMultipleTasks(new Bootstrap(version)); 26 | return asset.variablesExist() 27 | .then(() => fs.list(absolute.src(range), { files: false })) 28 | .then(({ value: branches }) => [...branches].map(d => d.split(path.sep).pop())) 29 | .mapSeries(branch => asset.compile(branch)) 30 | }) 31 | ) 32 | .then(() => { 33 | logger.header('API JSON'); 34 | return api.update(); 35 | }); 36 | 37 | })(); 38 | -------------------------------------------------------------------------------- /lib/Api.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const argv = require('./argv.js'); 4 | const fs = require('./fs.js').default; 5 | const logger = require('./logger.js').default; 6 | const { absolute, relative } = require('./Paths').default; 7 | const sri = require('./Sri.js').global; 8 | 9 | class Api { 10 | 11 | constructor() { 12 | this.file = relative.dist('api.json'); 13 | this.changed = false; 14 | this.json = { time: null, files: [] }; 15 | } 16 | 17 | async getFiles() { 18 | return fs.list(relative.dist(), { directories: false, recurse: true }); 19 | } 20 | 21 | async parse(files) { 22 | const exists = (await fs.pathExists(this.file)).value; 23 | if (exists) { 24 | try { 25 | this.json = fs.readJsonSync(this.file) 26 | } 27 | catch (e) { 28 | // Intentionally left empty. 29 | } 30 | } 31 | this.json.time = new Date().toISOString(); 32 | return Promise.mapSeries(files, async file => { 33 | const index = this.json.files.findIndex(f => f.name === `/${file}`); 34 | let data = index !== -1 ? this.json.files[index] : {}; 35 | 36 | // Only process the contents of the file if forced, doesn't exist and 37 | // not the api.json file itself. 38 | if ((argv.force || index === -1) && file !== this.file) { 39 | const stat = fs.statSync(file); 40 | const lstat = fs.lstatSync(file); 41 | const symlink = lstat.isSymbolicLink() ? path.relative(absolute.root, fs.realpathSync(file)) : false; 42 | const symlinkIndex = symlink ? this.json.files.findIndex(f => f.name === `/${symlink}`) : -1; 43 | if (symlinkIndex !== -1) { 44 | data = {...this.json.files[symlinkIndex]}; 45 | data.name = `/${file}`; 46 | data.symlink = `/${symlink}`; 47 | } 48 | else { 49 | const content = (await fs.readFile(file)).value.toString('utf-8'); 50 | data = { 51 | name: `/${file}`, 52 | size: stat.size, 53 | sha512: (await sri.generateHash(content)).value, 54 | time: stat.mtime.toISOString(), 55 | }; 56 | } 57 | if (index === -1) { 58 | this.json.files.push(data); 59 | } 60 | else { 61 | this.json.files.splice(index, 1, data); 62 | } 63 | this.changed = true; 64 | } 65 | }); 66 | } 67 | 68 | async update() { 69 | return this.getFiles() 70 | .then(({ value: files }) => this.parse(files)) 71 | .then(() => this.changed && this.write()); 72 | } 73 | 74 | async write() { 75 | return fs.outputJson(this.file, this.json, { spaces: 2 }); 76 | } 77 | 78 | } 79 | 80 | module.exports = logger.createMultipleTasks(new Api()); 81 | module.exports.default = module.exports; 82 | -------------------------------------------------------------------------------- /lib/Bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { PluginManager } = require('live-plugin-manager'); 4 | const CleanCSS = require('clean-css'); 5 | 6 | const argv = require('./argv.js'); 7 | const fs = require('./fs.js'); 8 | const logger = require('./logger.js').default; 9 | const { absolute, relative } = require('./Paths.js').default; 10 | const LessCompiler = require('./LessCompiler.js'); 11 | const SassCompiler = require('./SassCompiler.js'); 12 | const sri = require('./Sri.js').global; 13 | 14 | class Bootstrap { 15 | 16 | constructor(version) { 17 | this.cleanCss = logger.createMultipleTasks(new CleanCSS({ 18 | compatibility: 'ie8', 19 | level: 2, 20 | returnPromise: true, 21 | }), { level: 'info' }); 22 | this.compilers = { 23 | less: logger.createMultipleTasks(new LessCompiler(), { level: 'info' }), 24 | sass: logger.createMultipleTasks(new SassCompiler(), { level: 'info' }), 25 | }; 26 | this.version = version; 27 | this.paths = { 28 | bootstrap: absolute.assets(this.version, 'bootstrap'), 29 | bootswatch: absolute.assets(this.version, 'bootswatch'), 30 | }; 31 | } 32 | 33 | [logger.symbols.taskOptions]() { 34 | return { 35 | bootswatchThemes: { quiet: true }, 36 | iterate: { quiet: true }, 37 | minify: { level: 'info' }, 38 | save: { level: 'info' }, 39 | variablesExist: { level: 'info' }, 40 | mappedVersion: { level: 'insane' } 41 | }; 42 | } 43 | 44 | async compile(branch) { 45 | return this.iterate(async function compile(compiler, name, version, theme) { 46 | const dest = absolute.cssOutput(version, branch, theme); 47 | const minDest = absolute.cssOutput(version, branch, theme, true); 48 | 49 | // Skip if files already exist and not forced. 50 | if (!argv.force && fs.pathExistsSync(dest) && fs.pathExistsSync(minDest)) { 51 | return true; 52 | } 53 | 54 | const imports = new Set(); 55 | 56 | // Import SASS default variables. 57 | if (compiler === 'sass') { 58 | imports.add(relative.srcDefaultVariables(compiler, version, name)); 59 | } 60 | 61 | // Import Bootstrap variables. 62 | imports.add(relative.assetVariables(compiler, version, 'bootstrap')); 63 | 64 | // Import theme variables. 65 | imports.add(relative.assetVariables(compiler, version, name, theme)); 66 | 67 | // Import LESS default variables. 68 | if (compiler === 'less') { 69 | imports.add(relative.srcDefaultVariables(compiler, version, branch)); 70 | } 71 | 72 | // Import Bootstrap mixins. 73 | imports.add(relative.assetMixins(compiler, version, 'bootstrap')); 74 | 75 | // Import missing variables. 76 | imports.add(relative.srcMissingVariables(compiler, version)); 77 | 78 | // Import base theme styles. 79 | imports.add(relative.srcDrupalBootstrap(compiler, version, branch)); 80 | 81 | logger.insane(['Imports: %j', imports]); 82 | 83 | const content = [...imports].map(i => `@import "${i}"`).join(';') + ';'; 84 | return this.compilers[compiler].render(content) 85 | .then(({ value: output }) => this.save(dest, output) 86 | .then(() => this.minify(output)) 87 | .then(({ value: minified }) => this.save(minDest, minified)) 88 | ); 89 | }); 90 | } 91 | 92 | async minify(contents) { 93 | return this.cleanCss.minify(contents) 94 | .then(({ value }) => value.styles); 95 | } 96 | 97 | async save(dest, output) { 98 | const dir = path.dirname(dest); 99 | return fs.ensureDir(dir) 100 | .then(() => sri.getTarget(output, dest)) 101 | .then(({ value: target }) => target && typeof target === 'string' 102 | ? fs.ensureSymlink(path.relative(dir, target), dest) 103 | : fs.outputFile(dest, output)) 104 | } 105 | 106 | getPath(...paths) { 107 | if (!this.paths[name]) { 108 | throw new Error(`Invalid package: ${name}`); 109 | } 110 | return this.paths[name] 111 | } 112 | 113 | /** 114 | * Maps a specific version. 115 | * 116 | * While the Bootswatch project attempts to maintain version parity with 117 | * Bootstrap, it doesn't always happen. This causes issues when the system 118 | * expects a 1:1 version match between Bootstrap and Bootswatch. 119 | * 120 | * @return {String} 121 | * 122 | * @see https://github.com/thomaspark/bootswatch/issues/892#ref-issue-410070082 123 | */ 124 | mappedVersion(name) { 125 | if (name === 'bootswatch') { 126 | switch (this.version) { 127 | // This version is "broken" because of jsDelivr's API limit. 128 | case '3.4.1': 129 | return '3.4.0'; 130 | 131 | // This version doesn't exist. 132 | case '3.1.1': 133 | return '3.2.0'; 134 | } 135 | } 136 | return this.version; 137 | } 138 | 139 | async install(name) { 140 | return fs.remove(this.getPath(name)) 141 | .then(() => this.mappedVersion(name)) 142 | .then(({ value: version }) => { 143 | // Don't use this.paths here since the plugin manager just needs the 144 | // version as it installs the asset inside a matching name directory. 145 | const manager = new PluginManager({ 146 | pluginsPath: absolute.assets(this.version), 147 | }); 148 | return manager.install(name, version).catch(e => Promise.reject(logger.result(e))); 149 | }); 150 | } 151 | 152 | async bootswatchThemes(version = this.version) { 153 | // @todo Read this from the API files? 154 | const themes = { 155 | 3: [ 156 | 'cerulean', 157 | 'cosmo', 158 | 'cyborg', 159 | 'darkly', 160 | 'flatly', 161 | 'journal', 162 | 'lumen', 163 | 'paper', 164 | 'readable', 165 | 'sandstone', 166 | 'simplex', 167 | 'slate', 168 | 'spacelab', 169 | 'superhero', 170 | 'united', 171 | 'yeti', 172 | ], 173 | 4: [ 174 | 'cerulean', 175 | 'cosmo', 176 | 'cyborg', 177 | 'darkly', 178 | 'flatly', 179 | 'journal', 180 | 'litera', 181 | 'lumen', 182 | 'lux', 183 | 'materia', 184 | 'minty', 185 | 'pulse', 186 | 'sandstone', 187 | 'simplex', 188 | 'sketchy', 189 | 'slate', 190 | 'solar', 191 | 'spacelab', 192 | 'superhero', 193 | 'united', 194 | 'yeti', 195 | ], 196 | }; 197 | return [...(version && themes[version[0]] || [])]; 198 | } 199 | 200 | async iterate(fn) { 201 | const compiler = `${this.version[0]}` === '3' ? 'less' : 'sass'; 202 | return this.bootswatchThemes().then(({value:bootswatchThemes}) => { 203 | return Promise.mapSeries(['bootstrap', 'bootswatch'], async (name) => { 204 | const themes = name === 'bootstrap' ? ['bootstrap'] : bootswatchThemes; 205 | return Promise.mapSeries(themes, (theme) => { 206 | return logger.createTask(fn, { 207 | appendArguments: true, 208 | context: this, 209 | level: 'verbose', 210 | levelLabel: `${this.constructor.name}.${fn.name || 'iterate'}` 211 | })(compiler, name, this.version, theme); 212 | }); 213 | }) 214 | }); 215 | } 216 | 217 | async variablesExist() { 218 | const compiler = `${this.version[0]}` === '3' ? 'less' : 'sass'; 219 | return Promise.mapSeries(['bootstrap', 'bootswatch'], name => { 220 | return fs.pathExists(absolute.assetVariables(compiler, this.version, name)) 221 | .then(({ value: exists }) => !exists && this.install(name)) 222 | }); 223 | } 224 | 225 | } 226 | 227 | module.exports = Bootstrap; 228 | module.exports.default = module.exports; 229 | -------------------------------------------------------------------------------- /lib/GitTags.js: -------------------------------------------------------------------------------- 1 | const gitTag = require('git-tag'); 2 | 3 | const logger = require('./logger.js'); 4 | 5 | class GitTags { 6 | 7 | constructor() { 8 | this.gitTag = gitTag({ 9 | localOnly: true, 10 | }); 11 | } 12 | 13 | async all() { 14 | return new Promise(resolve => this.gitTag.all(resolve)); 15 | } 16 | 17 | async latest() { 18 | return new Promise(resolve => { 19 | this.gitTag.latest(latest => { 20 | if (!latest) { 21 | latest = '3.0.0'; 22 | } 23 | logger.info(['Latest tagged release: %s', latest]); 24 | resolve(latest); 25 | }) 26 | }); 27 | } 28 | 29 | } 30 | 31 | module.exports = logger.createMultipleTasks(new GitTags()); 32 | module.exports.default = module.exports; 33 | -------------------------------------------------------------------------------- /lib/Json.js: -------------------------------------------------------------------------------- 1 | const simpleJsonRequest = require('simple-json-request'); 2 | 3 | const logger = require('./logger.js'); 4 | 5 | const apiUrl = 'https://data.jsdelivr.com/v1/package/npm'; 6 | 7 | class Json { 8 | 9 | get(url, options = {}) { 10 | options = { 11 | ...options, 12 | url: url, 13 | }; 14 | return simpleJsonRequest.get(options).catch(function (err) { 15 | logger.error(`Unable to request JSON from: ${url}`); 16 | logger.error(err); 17 | return []; 18 | }); 19 | } 20 | 21 | async getVersions(name) { 22 | return this.get(`${apiUrl}/${name}`).then(({value}) => value.versions); 23 | } 24 | 25 | } 26 | 27 | module.exports = logger.createMultipleTasks(new Json(), { level: 'verbose' }); 28 | module.exports.default = module.exports; 29 | -------------------------------------------------------------------------------- /lib/LessCompiler.js: -------------------------------------------------------------------------------- 1 | const eol = require('os').EOL; 2 | 3 | const { create:S } = require('@unicorn-fail/string-extra').default; 4 | const less = require('less'); 5 | const LessPluginAutoPrefix = require('less-plugin-autoprefix'); 6 | const LessPluginCleanCSS = require('less-plugin-clean-css'); 7 | 8 | 9 | const logger = require('./logger.js').default; 10 | 11 | class LessCompiler { 12 | 13 | constructor(...options) { 14 | this.less = less; 15 | this.options = { 16 | paths: [ 17 | ...(options.paths || []), 18 | ], 19 | plugins: [ 20 | new LessPluginCleanCSS({ 21 | advanced: true 22 | }), 23 | new LessPluginAutoPrefix({ 24 | browsers: [ 25 | "Android 2.3", 26 | "Android >= 4", 27 | "Chrome >= 20", 28 | "Firefox >= 24", 29 | "Explorer >= 8", 30 | "iOS >= 6", 31 | "Opera >= 12", 32 | "Safari >= 6" 33 | ], 34 | map: true 35 | }), 36 | ...(options.plugins || []), 37 | ], 38 | ...options 39 | }; 40 | } 41 | 42 | async render(content = '', options = {}) { 43 | const stacktrace = new Error().stack.split(eol).slice(1).join(eol); 44 | return this.less.render(content, options) 45 | .then (result => { 46 | return result.css; 47 | }) 48 | .catch(e => { 49 | let code = ''; 50 | if (e.extract && e.extract.length) { 51 | e.extract.splice(2, 0, ' '.repeat(e.column) + '⋀'); 52 | code = eol + eol + e.extract.filter(Boolean).map((string, index) => { 53 | const lineNumber = S(`${index === 2 ? '' : e.line - (index > 2 ? 2 : 1) + index}`).padLeft(6, ' '); 54 | const line = S(`${string}`); 55 | if (index !== 1 && index !== 2) { 56 | lineNumber.yellow.dim; 57 | line.yellow.dim; 58 | } 59 | else if (index === 1) { 60 | lineNumber.redBright.bold; 61 | line.redBright.bold; 62 | } 63 | return line.prefix(lineNumber, ' ').toString(); 64 | }).join(eol) + eol + eol; 65 | } 66 | if (!e.stack) { 67 | e.stack = `${e.type}Error ${e.message}${code} at (${e.filename}:${e.line}:${e.column})\n${stacktrace}`; 68 | } 69 | return logger.fatal(e); 70 | }); 71 | } 72 | 73 | } 74 | 75 | module.exports = LessCompiler; 76 | module.exports.default = LessCompiler; 77 | -------------------------------------------------------------------------------- /lib/Logger/Logger.js: -------------------------------------------------------------------------------- 1 | // @todo Move to an upstream project like @string.js/logger. 2 | const os = require('os'); 3 | const eol = os.EOL; 4 | 5 | const { create: S, StringExtra } = require('@unicorn-fail/string-extra'); 6 | const graceful = require('node-graceful').default; 7 | const prettyTime = require('pretty-time'); 8 | const pQueue = require('p-queue').default; 9 | 10 | const LoggerLevels = require('./LoggerLevels.js'); 11 | const LoggerLines = require('./LoggerLines.js').default; 12 | const LoggerLine = require('./LoggerLine.js').default; 13 | const LoggerResult = require('./LoggerResult.js').default; 14 | const LoggerTask = require('./LoggerTask.js').default; 15 | 16 | const appStart = process.hrtime(); 17 | 18 | // Retrieve the OS signals. 19 | const signals = new Map(Object.entries(os.constants.signals)); 20 | 21 | // Remove the following signals due to Node.js restrictions. 22 | // @see https://nodejs.org/api/process.html#process_signal_events 23 | signals.delete('SIGUSR1'); 24 | signals.delete('SIGKILL'); 25 | signals.delete('SIGPROF'); 26 | signals.delete('SIGSTOP'); 27 | 28 | // Add a custom "TIMEOUT" signal to be used internally (not actually shown). 29 | // @see https://unix.stackexchange.com/a/205080 30 | signals.set('TIMEOUT', -4); // 128 + -4 === 124; 31 | 32 | class Logger { 33 | 34 | constructor(options = {}) { 35 | options = Object.assign({}, this.constructor.defaultOptions, options); 36 | 37 | if (!(options.levels instanceof LoggerLevels)) { 38 | options.levels = LoggerLevels.standard(options.argv); 39 | } 40 | 41 | /** 42 | * @type {LoggerLevels} 43 | */ 44 | this.levels = options.levels; 45 | 46 | /** 47 | * @type {NodeJS.WriteStream|Stream} 48 | */ 49 | this.stream = options.stream; 50 | 51 | if (options.hookStream) { 52 | StringExtra.hookStream(this.stream); 53 | } 54 | 55 | this.showExitInfo = options.showTotalTime; 56 | 57 | this.queue = new pQueue(); 58 | 59 | this.lines = new LoggerLines({ stream: this.stream }); 60 | 61 | 62 | // Handle events. 63 | graceful.exitOnDouble = false; 64 | graceful.DEADLY_SIGNALS = []; 65 | if (options.catchUncaughtException) { 66 | process.on('uncaughtException', e => this.fatal(e instanceof LoggerResult ? e.value : e, { exitCode: 2 })); 67 | process.on('uncaughtExceptionHandler', e => this.fatal(e instanceof LoggerResult ? e.value : e, { exitCode: 2 })); 68 | } 69 | if (options.catchUnhandledRejection) { 70 | process.on('unhandledRejection', e => this.fatal(e instanceof LoggerResult ? e.value : e, { exitCode: 3 })); 71 | process.on('unhandledRejectionHandler', e => this.fatal(e instanceof LoggerResult ? e.value : e, { exitCode: 3 })); 72 | } 73 | if (options.catchSignals) { 74 | for (let signal of signals.keys()) { 75 | graceful.on(signal, this.onSignal.bind(this)); 76 | } 77 | } 78 | 79 | graceful.on('exit', this.onExit.bind(this), true); 80 | 81 | this._options = options; 82 | } 83 | 84 | onExit(done, event, signal) { 85 | this.cleanup(); 86 | done(); 87 | } 88 | 89 | async onSignal(done, event, signal) { 90 | if (signal !== 'SIGINT') { 91 | return this.fatal(S(['Received %s signal, quiting...', signal]).dim, { signal }); 92 | } 93 | else if (!this.isInteractive) { 94 | return this.fatal(S(['Received %s signal, but non-interactive terminal detected, quiting...', signal]).dim, { signal }); 95 | } 96 | 97 | const timer = setTimeout(() => { 98 | return this.fatal(S('Timed out, quiting...').dim, { signal: 'TIMEOUT' }); 99 | }, 30000); 100 | 101 | this.queue.pause(); 102 | this.lines.pause(); 103 | 104 | const rawMode = typeof process.stdin.setRawMode === 'function' ? process.stdin.setRawMode.bind(process.stdin) : (() => {}); 105 | rawMode(true); 106 | 107 | const line = this.block(S(['\nReceived %s. Press %s again to quit, press any other key to %s...', signal, 'Ctrl+C', 'continue']).dim, { footer: true }); 108 | 109 | process.stdin.once('data', async data => { 110 | const byteArray = [...data]; 111 | if (byteArray.length > 0 && byteArray[0] === 3) { 112 | this.lines.footer.delete(line); 113 | clearTimeout(timer); 114 | return graceful.exit(128 + os.constants.signals.SIGINT); 115 | } 116 | this.lines.footer.delete(line); 117 | clearTimeout(timer); 118 | rawMode(false); 119 | this.queue.start(); 120 | this.lines.resume(); 121 | }); 122 | } 123 | 124 | get isInteractive() { 125 | return this.stream.isTTY && typeof process.stdin.setRawMode === 'function'; 126 | } 127 | 128 | cleanup() { 129 | if (this.cleanedUp) { 130 | return; 131 | } 132 | 133 | this.cleanedUp = true; 134 | 135 | this.lines.clear(); 136 | 137 | if (this.showExitInfo) { 138 | const total = prettyTime(process.hrtime(appStart)); 139 | this.stream.write(eol); 140 | this.stream.write(S(`Total time:\t${total}${eol}`).dim.b); 141 | this.stream.write(eol); 142 | } 143 | 144 | if (this._options.hookStream) { 145 | StringExtra.unhookStream(this.stream); 146 | } 147 | } 148 | 149 | log(level, value, options = {}) { 150 | let stacktrace; 151 | 152 | options = { 153 | block: false, 154 | footer: false, 155 | header: false, 156 | printImmediately: false, 157 | showStacktrace: false, 158 | signal: false, 159 | ...options, 160 | }; 161 | 162 | if (options.signal) { 163 | options.block = true; 164 | } 165 | 166 | // Format errors. 167 | if (value instanceof Error) { 168 | if (options.showStacktrace && value.stack) { 169 | const lines = value.stack.split(eol); 170 | const lvl = this.levels.get(level); 171 | value = lines[0]; 172 | stacktrace = S(eol + lines.slice(1).join(eol)); 173 | if (lvl) { 174 | stacktrace.style(lvl.styles.message); 175 | } 176 | } 177 | else { 178 | value = value.message; 179 | } 180 | } 181 | 182 | const line = new LoggerLine(value instanceof LoggerResult ? value.message : value, { 183 | ...options, 184 | level: value instanceof LoggerResult ? undefined : this.levels.get(level), 185 | stream: this.stream 186 | }); 187 | 188 | if (options.header) { 189 | line.header; 190 | } 191 | 192 | if (value instanceof LoggerResult) { 193 | line.style(value.style); 194 | } 195 | 196 | if (stacktrace) { 197 | line.suffix(stacktrace.dim); 198 | } 199 | 200 | if (options.block) { 201 | line.prefix(eol).suffix(eol); 202 | } 203 | 204 | if (options.printImmediately) { 205 | this.stream.write(`${line}\n`); 206 | } 207 | else { 208 | this.lines.add(line, !!options.footer); 209 | } 210 | 211 | return line; 212 | } 213 | 214 | block(value, options = {}) { 215 | return this.line(value, { 216 | ...options, 217 | block: true, 218 | }); 219 | } 220 | 221 | line(message, options = {}) { 222 | return this.log('', message, options); 223 | } 224 | 225 | header(message, options = {}) { 226 | return this.block(message, { 227 | ...options, 228 | header: true, 229 | }); 230 | } 231 | 232 | insane(message, options = {}) { 233 | return this.log('insane', message, options); 234 | } 235 | 236 | debug(message, options = {}) { 237 | return this.log('debug', message, options); 238 | } 239 | 240 | verbose(message, options = {}) { 241 | return this.log('verbose', message, options); 242 | } 243 | 244 | info(message, options = {}) { 245 | return this.log('info', message, options); 246 | } 247 | 248 | warn(message, options = {}) { 249 | return this.log('warn', message, options); 250 | } 251 | 252 | success(message, options = {}) { 253 | return this.log('success', message, options); 254 | } 255 | 256 | error(message, options = {}) { 257 | options = { 258 | showStackTrace: true, 259 | ...options, 260 | }; 261 | return this.log('error', message, options); 262 | } 263 | 264 | fatal(message, options = {}) { 265 | let level = 'fatal'; 266 | 267 | options = { 268 | exitCode: 1, 269 | printImmediately: true, 270 | showStacktrace: true, 271 | signal: null, 272 | ...options, 273 | }; 274 | 275 | // Change exit code to proper fatal signal value. 276 | // @see https://www.tldp.org/LDP/abs/html/exitcodes.html 277 | // @see https://www.bogotobogo.com/Linux/linux_process_and_signals.php#signals 278 | if (options.signal && signals.has(options.signal)) { 279 | options.exitCode = 128 + signals.get(options.signal); 280 | level = ''; 281 | } 282 | 283 | // Convert message to an Error, only if not a signal. 284 | if (!options.signal && !(message instanceof Error) && !(message instanceof LoggerResult)) { 285 | message = new Error(S(message)); 286 | } 287 | 288 | // Log the message. 289 | this.log(level, message, options); 290 | 291 | // Now gracefully exit. 292 | graceful.exit(options.exitCode); 293 | } 294 | 295 | createTask(fn, options = {}) { 296 | return (...args) => this.queue.add(() => LoggerTask.create(this, fn, { stream: this.stream, ...options }).run(...args)); 297 | } 298 | 299 | createMultipleTasks(object, options = {}) { 300 | const getMethods = (object) => { 301 | let methods = new Set(); 302 | let obj = object; 303 | const ignoreMethods = ['constructor', 'default']; 304 | while ((obj = Reflect.getPrototypeOf(obj))) { 305 | let keys = Reflect.ownKeys(object); 306 | keys.forEach((k) => { 307 | if ( 308 | typeof k !== 'string' || 309 | ignoreMethods.indexOf(k) !== -1 || 310 | Object.getOwnPropertyDescriptors(object) && typeof Object.getOwnPropertyDescriptors(object)[k].get === 'function' || 311 | typeof object[k] !== 'function' || 312 | k[0] === '_' || 313 | !k[0].match(/[a-z]/) || 314 | k.endsWith('Sync') 315 | ) { 316 | return; 317 | } 318 | methods.add(k) 319 | }); 320 | } 321 | return methods; 322 | }; 323 | 324 | let methods = getMethods(object); 325 | 326 | // Prototype first, means that it's an instance. 327 | if (!methods.size) { 328 | methods = getMethods(object.constructor.prototype); 329 | } 330 | if (!methods.size) { 331 | methods = getMethods(object.constructor); 332 | } 333 | 334 | const quiet = [].concat(Array.isArray(options.quiet) ? options.quiet : []).filter(Boolean); 335 | const objectOptions = { ...(object[this.constructor.symbols.taskOptions] ? object[this.constructor.symbols.taskOptions]() : {}) }; 336 | methods.forEach(method => { 337 | let levelLabel = method; 338 | if (options.name) { 339 | levelLabel = `${options.name}.${method}`; 340 | } 341 | else if (object.constructor && object.constructor.name && object.constructor.name !== 'Object') { 342 | levelLabel = `${object.constructor.name}.${method}`; 343 | } 344 | else if (object.name) { 345 | levelLabel = `${object.name}.${method}`; 346 | } 347 | if (!object[`${method}Async`]) { 348 | object[`${method}Async`] = object[method]; 349 | } 350 | 351 | const taskOptions = { 352 | appendArguments: true, 353 | context: object, 354 | quiet: false, 355 | ...options, 356 | ...(objectOptions[method] || {}), 357 | levelLabel 358 | }; 359 | 360 | if (taskOptions.quiet === true || quiet.indexOf(method) !== -1) { 361 | taskOptions.enabled = false; 362 | } 363 | 364 | object[method] = this.createTask(object[method], taskOptions); 365 | }); 366 | return object; 367 | } 368 | 369 | result(value, message) { 370 | if (value instanceof LoggerResult) { 371 | if (message) { 372 | value.message = message; 373 | } 374 | return value; 375 | } 376 | return new LoggerResult(value, message); 377 | } 378 | 379 | setLevel(level) { 380 | this.levels.setLevel(level); 381 | return this; 382 | } 383 | } 384 | 385 | Logger.defaultOptions = { 386 | argv: {}, 387 | catchUncaughtException: true, 388 | catchUnhandledRejection: true, 389 | catchSignals: true, 390 | hookStream: true, 391 | levels: null, 392 | parent: null, 393 | showTotalTime: true, 394 | stream: process.stderr, 395 | }; 396 | 397 | Logger.symbols = { 398 | taskOptions: Symbol.for('string.js.logger.task.options') 399 | }; 400 | 401 | module.exports = Logger; 402 | module.exports.default = module.exports; 403 | -------------------------------------------------------------------------------- /lib/Logger/LoggerLevel.js: -------------------------------------------------------------------------------- 1 | const { create:S } = require('@unicorn-fail/string-extra'); 2 | const chalk = require('chalk').default; 3 | 4 | class LoggerLevel { 5 | 6 | constructor(levels, name, options = {}) { 7 | options = { 8 | showLabel: true, 9 | styles: { 10 | message: chalk, 11 | label: null, 12 | ...(options.styles || {}) 13 | }, 14 | writable: null, 15 | ...options 16 | }; 17 | 18 | this.styles = options.styles; 19 | 20 | if (!this.styles.label) { 21 | this.styles.label = typeof options.styles.label === 'function' ? options.styles.label : options.styles.message && options.styles.message.bgBlackBright; 22 | } 23 | 24 | this.levels = levels; 25 | this.name = name; 26 | this.writable = options.writable; 27 | this.showLabel = options.showLabel; 28 | } 29 | 30 | getLabel(label = this.name, applyStyle = true) { 31 | if (!label || !this.showLabel) { 32 | return ''; 33 | } 34 | label = S(label); 35 | if (applyStyle && this.styles.label) { 36 | label.style(this.styles.label); 37 | } 38 | return label; 39 | } 40 | 41 | get alwaysWritable() { 42 | return this.writable !== null && this.writable !== undefined && !!this.writable; 43 | } 44 | 45 | get isWritable() { 46 | if (this.alwaysWritable) { 47 | return true; 48 | } 49 | return this.levels.index(this.name) <= this.levels.currentLevel; 50 | } 51 | 52 | } 53 | 54 | module.exports = LoggerLevel; 55 | -------------------------------------------------------------------------------- /lib/Logger/LoggerLevels.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk').default; 2 | 3 | const LoggerLevel = require('./LoggerLevel.js'); 4 | 5 | /** 6 | * @type Map 7 | */ 8 | class LoggerLevels extends Map { 9 | 10 | constructor() { 11 | super(); 12 | this.currentLevel = 0; 13 | 14 | // Always add an empty level for printing normal lines. 15 | this.add('', null, null, { writable: true }); 16 | } 17 | 18 | add(name, messageStyle = null, labelStyle = null, options = {}) { 19 | const level = new LoggerLevel(this, name, { 20 | styles: { 21 | message: messageStyle, 22 | label: labelStyle 23 | }, 24 | ...options, 25 | }); 26 | this.set(name, level); 27 | return this; 28 | } 29 | 30 | get(key = '') { 31 | if (this.has(key)) { 32 | return super.get(key); 33 | } 34 | return new LoggerLevel(this, key, { writable: true }); 35 | } 36 | 37 | index(level) { 38 | const index = Array.from(this.keys()).indexOf(level); 39 | return index !== -1 ? index : this.size + 1; 40 | } 41 | 42 | setLevel(level) { 43 | this.currentLevel = this.index(level); 44 | return this; 45 | } 46 | 47 | } 48 | 49 | LoggerLevels.standard = function (argv = {}) { 50 | const levels = new LoggerLevels() 51 | .add('fatal', chalk.red, chalk.whiteBright.bgRed) 52 | .add('error', chalk.red, null, { showLabel: false }) 53 | .add('success', chalk.green, null, { showLabel: false }) 54 | .add('warn', chalk.yellow) 55 | .add('verbose', chalk.cyan) 56 | .add('info', chalk.green) 57 | .add('debug', chalk.magenta) 58 | .add('insane', chalk.blue); 59 | 60 | // Automatically set the level based on passed arguments (i.e. yargs). 61 | if (argv.verbose >= 4 || (argv.verbose && argv.debug)) { 62 | levels.setLevel('insane'); 63 | } 64 | else if (argv.verbose >= 3 || argv.debug) { 65 | levels.setLevel('debug'); 66 | } 67 | else if (argv.verbose >= 2) { 68 | levels.setLevel('info'); 69 | } 70 | else if (argv.verbose >= 1 || argv.verbose) { 71 | levels.setLevel('verbose'); 72 | } 73 | else { 74 | levels.setLevel('warn'); 75 | } 76 | 77 | return levels; 78 | }; 79 | 80 | module.exports = LoggerLevels; 81 | -------------------------------------------------------------------------------- /lib/Logger/LoggerLine.js: -------------------------------------------------------------------------------- 1 | const { create:S, StringJs, StringExtra } = require('@unicorn-fail/string-extra').default; 2 | const cliSpinners = require('cli-spinners').default; 3 | const prettyTime = require('pretty-time'); 4 | 5 | const LoggerResult = require('./LoggerResult.js').default; 6 | 7 | class LoggerLine extends StringExtra { 8 | 9 | constructor(value, options = {}) { 10 | if (typeof options.spinner !== 'object') { 11 | if (process.platform === 'win32') { 12 | options.spinner = cliSpinners.line; 13 | } 14 | else if (options.spinner === undefined) { 15 | // Set default spinner 16 | options.spinner = cliSpinners.dots; 17 | } 18 | else if (cliSpinners[options.spinner]) { 19 | options.spinner = cliSpinners[options.spinner]; 20 | } 21 | else { 22 | throw new Error(`There is no built-in spinner named '${options.spinner}'. See https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json for a full list.`); 23 | } 24 | } 25 | 26 | if (!Array.isArray(options.spinner.frames) || !options.spinner.frames.every(i => typeof i === 'string' || i instanceof StringExtra )) { 27 | throw new Error('The given spinner must have a `frames` property that is an array of strings.'); 28 | } 29 | 30 | super(value, { 31 | hideCursor: true, 32 | level: null, 33 | paused: false, 34 | pausedStyle: 'yellow', 35 | spinnerStyle: 'cyan', 36 | stream: process.stderr, 37 | task: null, 38 | ...options 39 | }); 40 | 41 | this.frameIndex = 0; 42 | this.printed = false; 43 | } 44 | 45 | get enabled() { 46 | const level = this.getOption('level'); 47 | 48 | // Manual override. 49 | let enabled = this.getOption('enabled'); 50 | 51 | if (enabled === undefined) { 52 | enabled = level ? level.isWritable : true; 53 | 54 | // If line is a task, that means the stream needs to be interactive so 55 | // it can dynamically update the line at certain intervals. 56 | if (enabled && this.isRunning) { 57 | const stream = this.getOption('stream'); 58 | enabled = (stream && stream.isTTY) && !process.env.CI; 59 | } 60 | } 61 | 62 | return enabled; 63 | } 64 | 65 | set enabled(value) { 66 | this.setOption('enabled', !!value); 67 | } 68 | 69 | get hasStarted() { 70 | return !!this.getOption('started'); 71 | } 72 | 73 | get hasFinished() { 74 | return !!this.getOption('stopped'); 75 | } 76 | 77 | get interval() { 78 | let interval = this.getOption('interval'); 79 | if (interval === undefined) { 80 | const spinner = this.getOption('spinner'); 81 | interval = spinner.interval || 100; 82 | this.setOption('interval', interval); 83 | } 84 | return parseInt(interval, 10); 85 | } 86 | 87 | get isPaused() { 88 | return this.isTask && !!this.getOption('paused'); 89 | } 90 | 91 | pause() { 92 | if (!this.isRunning || this.isPaused) { 93 | return this; 94 | } 95 | return this.setOption('paused', process.hrtime(this.getOption('started'))); 96 | } 97 | 98 | resume() { 99 | if (!this.isRunning || !this.isPaused) { 100 | return this; 101 | } 102 | return this.setOption('paused', false); 103 | } 104 | 105 | get isTask() { 106 | return !!this.getOption('task'); 107 | } 108 | 109 | get isRunning() { 110 | return this.isTask && !this.hasFinished; 111 | } 112 | 113 | get stream() { 114 | return this.getOption('stream'); 115 | } 116 | 117 | elapsed() { 118 | const paused = this.getOption('paused'); 119 | const time = paused ? paused : this.getOption('stopped'); 120 | const elapsed = prettyTime(time ? time : process.hrtime(this.getOption('started'))); 121 | return S(`(${elapsed})${paused ? ' [paused]' : ''}`).dim; 122 | } 123 | 124 | format() { 125 | // Immediately return an empty string if not enabled. 126 | if (!this.enabled) { 127 | return ''; 128 | } 129 | 130 | /** @type {LoggerLevel} */ 131 | const level = this.getOption('level'); 132 | 133 | let prefix = ''; 134 | let suffix = ''; 135 | 136 | if (this.isTask) { 137 | suffix = this.elapsed(); 138 | if (this.isPaused) { 139 | prefix = S('≡').style(this.getOption('pausedStyle')); 140 | this.style(this.getOption('pausedStyle')); 141 | } 142 | else if (this.isRunning) { 143 | prefix = this.spinner(); 144 | this.style(this.getOption('spinnerStyle')); 145 | } 146 | else if (this.hasFinished) { 147 | const result = this.result || new LoggerResult(); 148 | prefix = S(result.icon).style(result.style); 149 | } 150 | } 151 | else if (level && level.name === 'success') { 152 | const result = new LoggerResult(true); 153 | prefix = S(result.icon).style(result.style); 154 | } 155 | else if (!level || level.name !== '') { 156 | prefix = S(' '); 157 | } 158 | 159 | if (this.result) { 160 | this.style(this.result.style); 161 | } 162 | 163 | if (level) { 164 | if (!this.getOption('style')) { 165 | this.style(level.styles.message); 166 | } 167 | const levelLabel = this.getOption('levelLabel'); 168 | const label = level.getLabel(levelLabel, !levelLabel); 169 | if (label) { 170 | if (!label.getOption('style')) { 171 | if (this.result) { 172 | label.style(this.result.style); 173 | } 174 | else if (this.isPaused) { 175 | label.style(this.getOption('pausedStyle')); 176 | } 177 | else if (this.isRunning) { 178 | label.style(this.getOption('spinnerStyle')); 179 | } 180 | } 181 | prefix = prefix ? prefix.suffix(label) : label; 182 | } 183 | } 184 | 185 | const formatted = super.format(); 186 | 187 | return S(formatted).prefix(prefix).suffix(suffix).toString(); 188 | } 189 | 190 | spinner() { 191 | const {frames} = this.getOption('spinner'); 192 | const frame = S(frames[this.frameIndex]).style(this.getOption('spinnerStyle')); 193 | this.frameIndex = ++this.frameIndex % frames.length; 194 | this.resetFormatted(); 195 | return frame; 196 | } 197 | 198 | start() { 199 | // Immediately return if already running. 200 | if (!this.isTask || this.hasStarted) { 201 | return this; 202 | } 203 | 204 | // Render the spinner frame every half a second, only if enabled. 205 | if (this.enabled) { 206 | this.poll = setInterval(this.spinner.bind(this), this.interval); 207 | } 208 | 209 | return this.setOption('started', process.hrtime()); 210 | } 211 | 212 | stop(result) { 213 | // Stop the polling. 214 | if (this.poll) { 215 | this.poll = clearInterval(this.poll); 216 | } 217 | 218 | // Immediately return if not already running. 219 | if (!this.isTask || this.hasFinished) { 220 | return this; 221 | } 222 | 223 | this.result = result instanceof LoggerResult ? result : new LoggerResult(result); 224 | 225 | this.setOption('stopped', process.hrtime(this.getOption('started'))); 226 | } 227 | 228 | } 229 | 230 | module.exports = LoggerLine; 231 | module.exports.default = module.exports; 232 | -------------------------------------------------------------------------------- /lib/Logger/LoggerLines.js: -------------------------------------------------------------------------------- 1 | const eol = require('os').EOL; 2 | 3 | const ansiEscapes = require('ansi-escapes').default; 4 | const cliCursor = require('cli-cursor'); 5 | 6 | class LoggerLines { 7 | 8 | constructor(options = {}) { 9 | options = { 10 | stream: process.stderr, 11 | ...options, 12 | }; 13 | 14 | /** @type {Set} */ 15 | this.lines = new Set(); 16 | 17 | /** @type {Set} */ 18 | this.footer = new Set(); 19 | 20 | this.stream = options.stream; 21 | 22 | this.paused = false; 23 | 24 | this.poll = null; 25 | } 26 | 27 | add(line, footer = false) { 28 | if (footer) { 29 | if (!this.footer.has(line)) { 30 | this.footer.add(line); 31 | } 32 | } 33 | else { 34 | if (!this.lines.has(line)) { 35 | this.lines.add(line); 36 | } 37 | } 38 | this.update(); 39 | } 40 | 41 | delete(line, footer = false) { 42 | if (footer) { 43 | if (!this.footer.has(line)) { 44 | return this; 45 | } 46 | this.footer.delete(line); 47 | } 48 | else { 49 | if (!this.lines.has(line)) { 50 | return this; 51 | } 52 | this.lines.delete(line); 53 | } 54 | this.update(); 55 | } 56 | 57 | clear() { 58 | for (let line of this.lines) { 59 | line.stop(false); 60 | } 61 | for (let line of this.footer) { 62 | line.stop(false); 63 | } 64 | this.lines.clear(); 65 | this.footer.clear(); 66 | this.update(); 67 | } 68 | 69 | has(line, footer = false) { 70 | if (footer) { 71 | return this.footer.has(line); 72 | } 73 | return this.lines.has(line); 74 | } 75 | 76 | forEach(...args) { 77 | this.lines.forEach(...args); 78 | this.update(); 79 | } 80 | 81 | pause() { 82 | this.paused = true; 83 | for (let line of this.lines) { 84 | line.pause(); 85 | } 86 | this.update(); 87 | } 88 | 89 | resume() { 90 | this.paused = false; 91 | for (let line of this.lines) { 92 | line.resume(); 93 | } 94 | this.update(); 95 | } 96 | 97 | write(lines) { 98 | const array = [...lines]; 99 | const output = []; 100 | for (let i = 0, l = array.length; i < l; i++) { 101 | const line = array[i]; 102 | const string = line.toString(); 103 | if (string.length) { 104 | output.push(`${string}${ansiEscapes.eraseEndLine}`); 105 | line.printed = true; 106 | } 107 | } 108 | if (output.length) { 109 | output.push(''); 110 | this.stream.write(output.join(eol)); 111 | } 112 | return output; 113 | } 114 | 115 | update() { 116 | // Ignore disabled lines (nothing can be done with them here). 117 | const lines = [...this.lines, ...this.footer].filter(l => l.enabled); 118 | 119 | const stopUpdate = (lines = []) => { 120 | if (!lines.length && this.poll) { 121 | this.poll = clearInterval(this.poll); 122 | cliCursor.show(this.stream); 123 | } 124 | else if (lines.length && !this.poll) { 125 | cliCursor.hide(this.stream); 126 | this.poll = setInterval(this.update.bind(this), 100); 127 | } 128 | 129 | this.updating = false; 130 | }; 131 | 132 | // Immediately return if nothing to print. 133 | if (!lines.length) { 134 | return stopUpdate(lines); 135 | } 136 | 137 | // Determine the number of currently printed lines and the move cursor up. 138 | const printed = lines.filter(l => l.printed); 139 | const currentNewLines = printed.map(line => line.toString()).concat('').join(eol).split(eol).length; 140 | if (currentNewLines) { 141 | this.stream.write(ansiEscapes.cursorLeft + ansiEscapes.cursorUp(currentNewLines - 1)); 142 | } 143 | 144 | const firstRunningIndex = lines.findIndex(l => l.isRunning); 145 | 146 | // Print out finished lines. 147 | const finished = lines.slice(0, firstRunningIndex !== -1 ? firstRunningIndex : undefined); 148 | this.write(finished); 149 | 150 | // Remove finished lines. 151 | finished.forEach(l => { 152 | if (this.lines.has(l)) { 153 | this.lines.delete(l); 154 | } 155 | else if (this.footer.has(l)) { 156 | this.footer.delete(l); 157 | } 158 | }); 159 | 160 | // Return if there are no lines running. 161 | if (firstRunningIndex === -1) { 162 | return stopUpdate(lines); 163 | } 164 | 165 | // Move cursor up to the first running line so the lines can be reprinted. 166 | const remaining = lines.slice(firstRunningIndex); 167 | 168 | // Reset printed status on running lines. 169 | remaining.filter(l => l.isRunning).forEach(l => l.printed = false); 170 | 171 | const output = this.write(remaining); 172 | 173 | stopUpdate(output); 174 | } 175 | 176 | } 177 | 178 | module.exports = LoggerLines; 179 | module.exports.default = module.exports; 180 | -------------------------------------------------------------------------------- /lib/Logger/LoggerResult.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk').default; 2 | const ansiRegex = require('ansi-regex'); 3 | const logSymbols = require('log-symbols'); 4 | 5 | const success = Symbol.for('string.logger.result.success'); 6 | const error = Symbol.for('string.logger.result.error'); 7 | const warning = Symbol.for('string.logger.result.warning'); 8 | const info = Symbol.for('string.logger.result.info'); 9 | 10 | class LoggerResult { 11 | 12 | constructor(value, message) { 13 | this.message = message; 14 | this.rethrow = true; 15 | if (typeof value === 'symbol') { 16 | this.symbol = value; 17 | switch (value) { 18 | case success: 19 | this.value = true; 20 | break; 21 | 22 | case error: 23 | this.value = false; 24 | break; 25 | 26 | case warning: 27 | this.value = null; 28 | break; 29 | 30 | default: 31 | this.value = undefined; 32 | break; 33 | } 34 | } 35 | else { 36 | this.value = value; 37 | if (this.value === true) { 38 | this.symbol = success; 39 | } 40 | else if (this.value === false || this.value instanceof Error) { 41 | this.symbol = error; 42 | } 43 | else if (this.value === null || this.value === undefined) { 44 | this.symbol = warning; 45 | } 46 | else { 47 | this.symbol = value ? success : info; 48 | } 49 | } 50 | } 51 | 52 | get icon() { 53 | switch (this.symbol) { 54 | case success: 55 | return logSymbols.success.replace(ansiRegex(), ''); 56 | 57 | case error: 58 | return logSymbols.error.replace(ansiRegex(), ''); 59 | 60 | case warning: 61 | return logSymbols.warning.replace(ansiRegex(), ''); 62 | 63 | case info: 64 | return logSymbols.info.replace(ansiRegex(), ''); 65 | 66 | default: 67 | return ''; 68 | } 69 | } 70 | 71 | get style() { 72 | switch (this.symbol) { 73 | case success: 74 | return chalk.green; 75 | 76 | case error: 77 | return chalk.red; 78 | 79 | case warning: 80 | return chalk.yellow; 81 | 82 | case info: 83 | return chalk.blue; 84 | 85 | default: 86 | return ''; 87 | } 88 | } 89 | 90 | get error() { 91 | this.symbol = error; 92 | return this; 93 | } 94 | 95 | get info() { 96 | this.symbol = info; 97 | return this; 98 | } 99 | 100 | get success() { 101 | this.symbol = success; 102 | return this; 103 | } 104 | 105 | get warning() { 106 | this.symbol = warning; 107 | return this; 108 | } 109 | 110 | } 111 | 112 | module.exports = LoggerResult; 113 | module.exports.default = module.exports; 114 | -------------------------------------------------------------------------------- /lib/Logger/LoggerTask.js: -------------------------------------------------------------------------------- 1 | const LoggerLine = require('./LoggerLine.js').default; 2 | const LoggerResult = require('./LoggerResult.js').default; 3 | 4 | class LoggerTask { 5 | 6 | constructor(logger, fn, options = {}) { 7 | /** @type {Logger} */ 8 | this.logger = logger; 9 | 10 | /** @type {Function} */ 11 | this.fn = fn; 12 | 13 | /** @type {LoggerResult} */ 14 | this.result = null; 15 | 16 | this.options = { 17 | appendArguments: false, 18 | context: null, 19 | errorsAsFalse: false, 20 | message: '', 21 | rethrow: true, 22 | stream: process.stderr, 23 | ...options, 24 | level: this.logger.levels.get(options.level), 25 | task: this, 26 | }; 27 | } 28 | 29 | static create(logger, fn, options = {}) { 30 | return new LoggerTask(logger, fn, options); 31 | } 32 | 33 | async run(...args) { 34 | const options = { ...this.options }; 35 | let taskArgs = [...args].filter(a => a && typeof a === 'string' && a.length <= (process.env.COLUMNS || 200)); 36 | let message = options.message; 37 | if (options.appendArguments && taskArgs.length) { 38 | if (options.message) { 39 | message = [`${options.message} ${taskArgs.map(() => '%s').join(' ')}`, ...taskArgs]; 40 | } 41 | else { 42 | message = [taskArgs.map(() => '%s').join(' '), ...taskArgs]; 43 | } 44 | } 45 | 46 | const line = LoggerLine.create(message, { 47 | ...options, 48 | args: taskArgs 49 | }).start(); 50 | 51 | this.logger.lines.add(line); 52 | return Promise.resolve(this.fn.apply(options.context, args)) 53 | .then(result => { 54 | // Callbacks are intended to be promises, but just in case... wrap 55 | // with Promise.resolve(). If an error was thrown, assume "false" as 56 | // the resolved value. If no error was thrown and there wasn't a 57 | // return value, assume "true". If there was a value returned, 58 | // resolve with the returned value. 59 | if (result === null || result === undefined) { 60 | result = true; 61 | } 62 | 63 | this.result = result instanceof LoggerResult ? result : new LoggerResult(result); 64 | 65 | line.stop(this.result); 66 | 67 | // If a message was explicitly set, show it. 68 | if (this.result.message) { 69 | this.logger.log('', this.result); 70 | } 71 | 72 | return Promise.resolve(this.result); 73 | }) 74 | .catch(result => { 75 | if (options.errorsAsFalse && result instanceof Error) { 76 | result = new LoggerResult(false); 77 | result.rethrow = false; 78 | } 79 | 80 | this.result = result instanceof LoggerResult ? result : new LoggerResult(result); 81 | 82 | line.stop(this.result); 83 | 84 | if (!this.result.rethrow) { 85 | return Promise.resolve(this.result); 86 | } 87 | // If a message was explicitly set, show it. 88 | if (this.result.message) { 89 | this.logger.log('', this.result); 90 | } 91 | 92 | return Promise.reject(this.result); 93 | }); 94 | } 95 | 96 | } 97 | 98 | module.exports = LoggerTask; 99 | module.exports.default = module.exports; 100 | -------------------------------------------------------------------------------- /lib/Paths.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const fs = require('./fs'); 4 | const logger = require('./logger.js').default; 5 | 6 | class Paths { 7 | 8 | constructor(options = {}) { 9 | options = { 10 | root: path.resolve(__dirname, '..'), 11 | relative: false, 12 | ...options 13 | }; 14 | this.root = options.root; 15 | this.relative = options.relative; 16 | } 17 | 18 | join(...paths) { 19 | const file = path.join(this.root, ...paths); 20 | return this.relative ? path.relative(this.root, file) : file; 21 | } 22 | 23 | assets(...paths) { 24 | return this.join('assets', ...paths); 25 | } 26 | 27 | assetMixins(precompiler, version, name) { 28 | const dir = precompiler === 'sass' ? 'scss' : 'less'; 29 | const partial = precompiler === 'sass' ? '_' : ''; 30 | return this.assets(version, name, dir, `${partial}mixins.${dir}`); 31 | } 32 | 33 | assetVariables(precompiler, version, name, theme) { 34 | const dir = precompiler === 'sass' ? 'scss' : 'less'; 35 | const extension = precompiler === 'sass' ? 'scss' : 'less'; 36 | const partial = precompiler === 'sass' ? '_' : ''; 37 | if (theme === 'bootstrap') { 38 | theme = false; 39 | } 40 | else if (!theme && name === 'bootswatch') { 41 | theme = 'cerulean'; 42 | } 43 | return this.assets(version, name, theme || dir, `${partial}variables.${extension}`); 44 | } 45 | 46 | dist(...paths) { 47 | return this.join('dist', ...paths); 48 | } 49 | 50 | src(...paths) { 51 | return this.join('src', ...paths); 52 | } 53 | 54 | cssOutput(version, branch, theme, minified = false) { 55 | const min = minified ? '.min' : ''; 56 | return this.dist(version, branch, theme && theme !== 'bootstrap' ? `drupal-bootstrap-${theme}${min}.css` : `drupal-bootstrap${min}.css`); 57 | } 58 | 59 | srcDrupalBootstrap(precompiler, version, branch) { 60 | return this.srcFile(precompiler, version, branch, 'drupal-bootstrap'); 61 | } 62 | 63 | srcFile(precompiler, version, branch, ...paths) { 64 | const extension = precompiler === 'sass' ? 'scss' : 'less'; 65 | const partial = precompiler === 'sass' ? '_' : ''; 66 | const file = paths.pop(); 67 | return this.src(`${version[0]}.x.x`, branch, precompiler, ...paths, `${partial}${file}.${extension}`); 68 | } 69 | 70 | srcDefaultVariables(precompiler, version, branch) { 71 | const file = precompiler === 'sass' ? 'default-variables' : 'variable-overrides'; 72 | return this.srcFile(precompiler, version, branch, file); 73 | } 74 | 75 | srcMissingVariables(precompiler, version) { 76 | const extension = precompiler === 'sass' ? 'scss' : 'less'; 77 | const partial = precompiler === 'sass' ? '_' : ''; 78 | const file = 'missing-variables'; 79 | return this.src(`${version[0]}.x.x`, `${partial}${file}.${extension}`); 80 | } 81 | 82 | // Helper function for falling back to a Bootstrap variables file. 83 | resolveVariables(precompiler, version, name, theme, backup) { 84 | if (backup === true) { 85 | logger.insane('Checking for backup variables file...'); 86 | } 87 | if (!fs.exists(path.join(librariesPath, file))) { 88 | logger.verbose(['Missing %s', file]); 89 | file = false; 90 | if (backup && backup !== true) { 91 | file = this.resolveVariables(backup, true); 92 | if (file) { 93 | logger.insane("Using: " + file); 94 | } 95 | } 96 | return file; 97 | } 98 | else if (backup === true) { 99 | grunt.verbose.ok(); 100 | } 101 | return file; 102 | }; 103 | 104 | } 105 | 106 | module.exports = Paths; 107 | module.exports.absolute = new Paths(); 108 | module.exports.relative = new Paths({ relative: true }); 109 | module.exports.default = module.exports; 110 | -------------------------------------------------------------------------------- /lib/SassCompiler.js: -------------------------------------------------------------------------------- 1 | 2 | class SassCompiler { 3 | 4 | async render(contents, options = {}) { 5 | return Promise.reject(new Error('@todo install node-sass')); 6 | } 7 | } 8 | 9 | module.exports = SassCompiler; 10 | module.exports.default = SassCompiler; 11 | -------------------------------------------------------------------------------- /lib/Sri.js: -------------------------------------------------------------------------------- 1 | const getSRI = require('get-sri'); 2 | 3 | const logger = require('./logger.js').default; 4 | 5 | class Sri { 6 | 7 | constructor() { 8 | this.hashes = new Map(); 9 | this.duplicates = new Map(); 10 | this.seen = new Map(); 11 | } 12 | 13 | async generateHash(data) { 14 | if (this.hashes.has(data)) { 15 | return this.hashes.get(data); 16 | } 17 | const hash = getSRI(data, 'sha512'); 18 | this.hashes.set(data, hash); 19 | return hash; 20 | } 21 | 22 | async getTarget(data, target) { 23 | return this.generateHash(data) 24 | .then(({value:hash}) => this.hashExists(hash, target)); 25 | } 26 | 27 | async hashExists(hash, target) { 28 | if (!hash) { 29 | return null; 30 | } 31 | 32 | if (this.seen.has(hash)) { 33 | if (!this.duplicates.has(hash)) { 34 | this.duplicates.set(hash, new Set()); 35 | } 36 | this.duplicates.get(hash).add(target); 37 | return this.seen.get(hash); 38 | } 39 | 40 | this.seen.set(hash, target); 41 | return null; 42 | } 43 | 44 | get seenCount() { 45 | return this.seen.size; 46 | } 47 | 48 | get duplicateCount() { 49 | return [...this.duplicates.values()].map(v => [...v]).flat().length; 50 | } 51 | 52 | } 53 | 54 | module.exports = Sri; 55 | module.exports.global = logger.createMultipleTasks(new Sri(), { level: 'info' }); 56 | global.sri = module.exports.global; 57 | module.exports.default = module.exports; 58 | -------------------------------------------------------------------------------- /lib/Versions.js: -------------------------------------------------------------------------------- 1 | const json = require('../lib/json.js').default; 2 | const logger = require('../lib/logger.js').default; 3 | const semver = require('./semver.js').default; 4 | 5 | const debugArray = array => array.reduce((result, element, index) => result.push(index % 10 === 9 ? `\n${element}` : element) && result, []).join(', '); 6 | 7 | class Versions { 8 | 9 | constructor() { 10 | this.cache = {}; 11 | } 12 | 13 | async all(name) { 14 | if (this.cache[name]) { 15 | return this.cache[name]; 16 | } 17 | return json.getVersions(name).then(({value}) => { 18 | logger.verbose(['Found %d available versions for %s.', value.length, name]); 19 | logger.debug(debugArray(value)); 20 | return this.cache[name] = value; 21 | }); 22 | } 23 | 24 | async matching(name, range = '>=3.0.0') { 25 | return this.all(name).then(({value}) => { 26 | const matched = semver.filter(value, range).reverse(); 27 | logger.verbose(['Matched %d out of %d versions for %s.', matched.length, value.length, name]); 28 | logger.debug(debugArray(matched)); 29 | return matched; 30 | }); 31 | } 32 | 33 | async missing(name = 'bootstrap', existing = [], range = '>=3.0.0') { 34 | return this.matching(name, range).then(({value}) => { 35 | const missing = value.filter(version => existing.indexOf(version) === -1); 36 | logger.verbose(['Found %d missing versions for %s.', missing.length, name]); 37 | logger.debug(debugArray(missing)); 38 | return missing; 39 | }); 40 | } 41 | 42 | } 43 | 44 | module.exports = logger.createMultipleTasks(new Versions()); 45 | module.exports.default = module.exports; 46 | -------------------------------------------------------------------------------- /lib/argv.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk').default; 2 | 3 | const argv = require('yargs') 4 | .alias('c', 'color') 5 | .alias('d', 'debug') 6 | .alias('f', 'force') 7 | .alias('r', 'remove-dist') 8 | .alias('v', 'verbose') 9 | .count('verbose') 10 | .argv; 11 | 12 | if (argv.color === undefined) { 13 | argv.color = chalk.supportsColor; 14 | } 15 | chalk.level = argv.color ? 1 : 0; 16 | 17 | module.exports = argv; 18 | -------------------------------------------------------------------------------- /lib/fs.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // Promisify all filesystem methods. 4 | // Log to the "insane" level though as it can be quite frequent. 5 | const fs = require('fs-extra'); 6 | 7 | const logger = require('./logger.js'); 8 | 9 | fs.list = async function (dir, options = {}) { 10 | options = { 11 | absolute: true, 12 | create: false, 13 | directories: true, 14 | files: true, 15 | symbolicLink: true, 16 | permissions: null, 17 | recurse: false, 18 | reject: false, 19 | ...options 20 | }; 21 | 22 | // Determine needed permissions automatically. 23 | if (options.permissions === null) { 24 | options.permissions = options.create ? this.R_OK | this.W_OK : this.R_OK; 25 | } 26 | 27 | // Check access. 28 | return this 29 | .access(dir, options.permissions) 30 | .then(({ value: access }) => !access && options.create && this.mkdir(dir, options).then(() => this.access(dir, options).then(({ value: access }) => !access && Promise[options.reject ? 'reject' : 'resolve']([])))) 31 | .then(() => this.readdir(dir)) 32 | .then(({ value: files }) => Promise.reduce(files, async (results, file) => { 33 | const absolutePath = path.join(dir, file); 34 | return this.stat(absolutePath) 35 | .then(({value:stat}) => { 36 | if ((options.directories && stat.isDirectory()) 37 | || (options.files && stat.isFile()) 38 | || (options.symbolicLink && stat.isSymbolicLink()) 39 | ) { 40 | results = [...results, options.absolute ? absolutePath : file]; 41 | } 42 | if (options.recurse && stat.isDirectory()) { 43 | return this.list(absolutePath, options).then(({value:sub}) => results = [...results, ...sub]); 44 | } 45 | return results; 46 | }); 47 | }, [])); 48 | }; 49 | 50 | module.exports = logger.createMultipleTasks(fs, { 51 | errorsAsFalse: true, 52 | quiet: ['stat'], 53 | level: 'insane', 54 | name: 'fs', 55 | }); 56 | module.exports.default = module.exports; 57 | 58 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | const argv = require('./argv.js'); 2 | const Logger = require('./Logger/Logger.js'); 3 | 4 | // Construct a new logger instance and pass argv to set the current level. 5 | const logger = new Logger({ argv }); 6 | 7 | // // Tests. 8 | // logger.line(['Testing %s placeholder', 'sprintf']); 9 | // logger.insane(['Testing %s placeholder', 'sprintf']); 10 | // logger.debug(['Testing %s placeholder', 'sprintf']); 11 | // logger.info(['Testing %s placeholder', 'sprintf']); 12 | // logger.verbose(['Testing %s placeholder', 'sprintf']); 13 | // logger.warn(['Testing %s placeholder', 'sprintf']); 14 | // logger.error(['Testing %s placeholder', 'sprintf']); 15 | // logger.fatal(['Testing %s placeholder', 'sprintf']); 16 | 17 | module.exports = logger; 18 | module.exports.symbols = Logger.symbols; 19 | module.exports.default = module.exports; 20 | -------------------------------------------------------------------------------- /lib/semver.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver'); 2 | const defaultOptions = { 3 | includePrerelease: false, 4 | }; 5 | 6 | semver.filter = (versions, range, options = {}) => { 7 | options = { 8 | ...defaultOptions, 9 | maxOperator: '<=', 10 | minOperator: '>=', 11 | ...options, 12 | }; 13 | // Range is a min/max based on the array versions supplied. 14 | if (Array.isArray(range)) { 15 | const min = semver.minSatisfying(range, '*'); 16 | const max = semver.maxSatisfying(range, '*'); 17 | range = `${options.minOperator}${min} ${options.maxOperator}${max}`; 18 | } 19 | return versions.filter(version => semver.valid(version, options) && semver.satisfies(version, range, options)); 20 | }; 21 | 22 | module.exports = semver; 23 | module.exports.default = module.exports; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@unicorn-fail/drupal-bootstrap-styles", 3 | "version": "0.0.2", 4 | "description": "Styles used to bridge the gap between Drupal and Bootstrap.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/unicorn-fail/drupal-bootstrap-styles.git" 8 | }, 9 | "author": "Mark Carver", 10 | "license": "GPL-2.0", 11 | "files": [ 12 | "dist" 13 | ], 14 | "scripts": { 15 | "build": "node ./build.js", 16 | "test": "node ./build.js --no-interactive", 17 | "prepare": "node ./build.js --no-interactive" 18 | }, 19 | "devDependencies": { 20 | "@unicorn-fail/string-extra": "^1.0.2", 21 | "ansi-escapes": "^4.0.1", 22 | "ansi-regex": "^4.1.0", 23 | "bluebird": "^3.5.3", 24 | "chalk": "^2.4.2", 25 | "clean-css": "^4.2.1", 26 | "cli-cursor": "^2.1.0", 27 | "cli-spinners": "^2.0.0", 28 | "fs-extra": "^7.0.1", 29 | "get-sri": "^1.0.2", 30 | "less": "^3.9.0", 31 | "less-plugin-autoprefix": "^2.0.0", 32 | "less-plugin-clean-css": "^1.5.1", 33 | "live-plugin-manager": "^0.12.0", 34 | "log-symbols": "^2.2.0", 35 | "node-graceful": "^1.0.1", 36 | "p-queue": "^4.0.0", 37 | "pretty-time": "^1.1.0", 38 | "semver": "^5.6.0", 39 | "simple-json-request": "^0.5.2", 40 | "string": "^3.3.3", 41 | "yargs": "^13.2.1" 42 | }, 43 | "dependencies": {}, 44 | "bugs": { 45 | "url": "https://github.com/unicorn-fail/drupal-bootstrap-styles/issues" 46 | }, 47 | "homepage": "https://github.com/unicorn-fail/drupal-bootstrap-styles#readme", 48 | "directories": { 49 | "lib": "lib" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/less/README.md: -------------------------------------------------------------------------------- 1 | # [Drupal Bootstrap Styles] (LESS) 2 | 3 | These files are intended to be used within a [Drupal Bootstrap] based sub-theme. 4 | 5 | ## Default Variables 6 | 7 | The `variable-overrides.scss` file is generally where you will spend the 8 | majority of your time providing any default variables that should be 9 | used by the [Bootstrap Framework] instead of its own. 10 | 11 | > **WARNING:** It is highly recommended that you only put the default variables 12 | you intend to override in this file. Copying and pasting the entire 13 | `variables.less` file provided by the [Bootstrap Framework] is redundant and 14 | only makes it more difficult to determine which variable has been overridden 15 | when the need to update or upgrade to newer versions in the future arises. 16 | 17 | ## Drupal Bootstrap 18 | 19 | The `drupal-bootstrap.less` file contains various styles to properly integrate 20 | [Drupal Bootstrap] with the [Bootstrap Framework]. 21 | 22 | ## Compiling 23 | 24 | The `style.less` file is the main compiling entry point. It is the glue that 25 | combines: `variable-overrides.less`, [Bootstrap Framework Source Files] and the 26 | `drupal-bootstrap.less` files together. Generally, you will not need to modify 27 | this file unless you need to add or remove files to be imported. 28 | 29 | Your [Less] compiler should use this file as it's "source" and its compiled 30 | output "destination" should be `../css/style.css` (overwrites the file provided 31 | by the base-theme starterkit). 32 | 33 | [Bootstrap Framework]: https://getbootstrap.com/docs/3.4/ 34 | [Bootstrap Framework Source Files]: https://github.com/twbs/bootstrap-sass 35 | [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap 36 | [Drupal Bootstrap Styles]: https://github.com/unicorn-fail/drupal-bootstrap-styles 37 | [Less]: http://lesscss.org 38 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/less/bootstrap.less: -------------------------------------------------------------------------------- 1 | // This is almost an exact duplication of the bootstrap.less file provided 2 | // by the Bootstrap Framework itself. The only difference from that file and 3 | // this one are the first two @import statements for the original framework's 4 | // variables and this theme's overrides of those variables. This is necessary 5 | // due to the fact that the Less pre-compiler language does not have the 6 | // concept of "default" variables. 7 | // 8 | // @see https://www.drupal.org/node/2775481 9 | @import "../bootstrap/less/variables.less"; 10 | @import "variable-overrides.less"; 11 | 12 | // Mixins. 13 | @import "../bootstrap/less/mixins.less"; 14 | 15 | // Reset and dependencies 16 | @import "../bootstrap/less/normalize.less"; 17 | @import "../bootstrap/less/print.less"; 18 | @import "../bootstrap/less/glyphicons.less"; 19 | 20 | // Core CSS 21 | @import "../bootstrap/less/scaffolding.less"; 22 | @import "../bootstrap/less/type.less"; 23 | @import "../bootstrap/less/code.less"; 24 | @import "../bootstrap/less/grid.less"; 25 | 26 | @import "../bootstrap/less/tables.less"; 27 | @import "../bootstrap/less/forms.less"; 28 | @import "../bootstrap/less/buttons.less"; 29 | 30 | // Components 31 | @import "../bootstrap/less/component-animations.less"; 32 | @import "../bootstrap/less/dropdowns.less"; 33 | @import "../bootstrap/less/button-groups.less"; 34 | @import "../bootstrap/less/input-groups.less"; 35 | @import "../bootstrap/less/navs.less"; 36 | @import "../bootstrap/less/navbar.less"; 37 | @import "../bootstrap/less/breadcrumbs.less"; 38 | @import "../bootstrap/less/pagination.less"; 39 | @import "../bootstrap/less/pager.less"; 40 | @import "../bootstrap/less/labels.less"; 41 | @import "../bootstrap/less/badges.less"; 42 | @import "../bootstrap/less/jumbotron.less"; 43 | @import "../bootstrap/less/thumbnails.less"; 44 | @import "../bootstrap/less/alerts.less"; 45 | @import "../bootstrap/less/progress-bars.less"; 46 | @import "../bootstrap/less/media.less"; 47 | @import "../bootstrap/less/list-group.less"; 48 | @import "../bootstrap/less/panels.less"; 49 | @import "../bootstrap/less/responsive-embed.less"; 50 | @import "../bootstrap/less/wells.less"; 51 | @import "../bootstrap/less/close.less"; 52 | 53 | // Components w/ JavaScript 54 | @import "../bootstrap/less/modals.less"; 55 | @import "../bootstrap/less/tooltip.less"; 56 | @import "../bootstrap/less/popovers.less"; 57 | @import "../bootstrap/less/carousel.less"; 58 | 59 | // Utility classes 60 | @import "../bootstrap/less/utilities.less"; 61 | @import "../bootstrap/less/responsive-utilities.less"; 62 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/less/drupal-bootstrap.less: -------------------------------------------------------------------------------- 1 | // Media query variables. 2 | @tablet: ~"screen and (min-width: @{screen-sm-min})"; 3 | @normal: ~"screen and (min-width: @{screen-md-min})"; 4 | @wide: ~"screen and (min-width: @{screen-lg-min})"; 5 | @grid-breakpoint: ~"screen and (min-width: @{grid-float-breakpoint})"; 6 | 7 | // Drop shadows. 8 | .box-shadow(@shadow) { 9 | -webkit-box-shadow: @shadow; 10 | -moz-box-shadow: @shadow; 11 | box-shadow: @shadow; 12 | } 13 | 14 | html { 15 | &.overlay-open .navbar-fixed-top { 16 | z-index: 400; 17 | } 18 | &.js { 19 | // Collapsible fieldsets. 20 | fieldset.collapsed { 21 | border-left-width: 1px; 22 | border-right-width: 1px; 23 | height: auto; 24 | } 25 | // Throbbers. 26 | input.form-autocomplete { 27 | background-image: none; 28 | } 29 | // Autocomplete. 30 | input.form-autocomplete { 31 | background-image: none; 32 | } 33 | // Autocomplete (fallback throbber, no icon). 34 | .autocomplete-throbber { 35 | background-position: 100% 2px; 36 | background-repeat: no-repeat; 37 | display: inline-block; 38 | height: 15px; 39 | margin: 2px 0 0 2px; 40 | width: 15px; 41 | } 42 | .autocomplete-throbber.throbbing { 43 | background-position: 100% -18px; 44 | } 45 | } 46 | } 47 | body { 48 | // Fix horizontal scrolling on iOS devices. 49 | // https://www.drupal.org/node/1870076 50 | position: relative; 51 | &.admin-expanded.admin-vertical.admin-nw .navbar, 52 | &.admin-expanded.admin-vertical.admin-sw .navbar { 53 | margin-left: 260px; 54 | } 55 | // Add padding to body if various toolbar or navbars are fixed on top. 56 | &.navbar-is-fixed-top { 57 | padding-top: 64px; 58 | } 59 | &.navbar-is-fixed-bottom { 60 | padding-bottom: 64px !important; 61 | } 62 | &.toolbar { 63 | padding-top: 30px !important; 64 | .navbar-fixed-top { 65 | top: 30px; 66 | } 67 | &.navbar-is-fixed-top { 68 | padding-top: 94px !important; 69 | } 70 | } 71 | &.toolbar-drawer { 72 | padding-top: 64px !important; 73 | .navbar-fixed-top { 74 | top: 64px; 75 | } 76 | &.navbar-is-fixed-top { 77 | padding-top: 128px !important; 78 | } 79 | } 80 | // Admin_menu toolbar. 81 | &.admin-menu { 82 | .navbar-fixed-top { 83 | top: 29px; 84 | } 85 | &.navbar-is-fixed-top { 86 | padding-top: 93px; 87 | } 88 | } 89 | div#admin-toolbar { 90 | z-index: 1600; 91 | } 92 | // Override box-shadow styles on all "admin" menus. 93 | #toolbar, #admin-menu, #admin-toolbar { 94 | .box-shadow(none); 95 | } 96 | // Override #admin-menu style. 97 | #admin-menu { 98 | margin: 0; 99 | padding: 0; 100 | position: fixed; 101 | z-index: 1600; 102 | .dropdown li { 103 | line-height: normal; 104 | } 105 | } 106 | } 107 | 108 | // Default navbar. 109 | .navbar { 110 | &.container { 111 | @media @tablet { 112 | max-width: ((@container-sm - @grid-gutter-width)); 113 | } 114 | @media @normal { 115 | max-width: ((@container-md - @grid-gutter-width)); 116 | } 117 | @media @wide { 118 | max-width: ((@container-lg - @grid-gutter-width)); 119 | } 120 | } 121 | &.container, 122 | &.container-fluid { 123 | margin-top: 20px; 124 | } 125 | &.container > .container, 126 | &.container-fluid > .container-fluid { 127 | margin: 0; 128 | padding: 0; 129 | width: auto; 130 | } 131 | } 132 | 133 | // Adjust z-index of core components. 134 | #overlay-container, 135 | .overlay-modal-background, 136 | .overlay-element { 137 | z-index: 1500; 138 | } 139 | #toolbar { 140 | z-index: 1600; 141 | } 142 | // Adjust z-index of bootstrap modals 143 | .modal { 144 | z-index: 1620; 145 | } 146 | .modal-dialog { 147 | z-index: 1630; 148 | } 149 | .ctools-modal-dialog .modal-body { 150 | width: 100% !important; 151 | overflow: auto; 152 | } 153 | .modal-backdrop { 154 | z-index: 1610; 155 | } 156 | .footer { 157 | margin-top: 45px; 158 | padding-top: 35px; 159 | padding-bottom: 36px; 160 | border-top: 1px solid #E5E5E5; 161 | } 162 | 163 | // Element invisible fix 164 | .element-invisible { 165 | margin: 0; 166 | padding: 0; 167 | width: 1px; 168 | } 169 | 170 | // Branding. 171 | .navbar .logo { 172 | margin-right: -15px; 173 | padding-left: 15px; 174 | padding-right: 15px; 175 | @media @grid-breakpoint { 176 | margin-right: 0; 177 | padding-left: 0; 178 | } 179 | } 180 | 181 | // Navigation. 182 | ul.secondary { 183 | float: left; 184 | @media @tablet { 185 | float: right; 186 | } 187 | } 188 | 189 | // Page header. 190 | .page-header { 191 | margin-top: 0; 192 | } 193 | 194 | // Sidebar blocks. 195 | .region-sidebar-first, 196 | .region-sidebar-second { 197 | .block:first-child h2.block-title { 198 | margin-top: 0; 199 | } 200 | } 201 | 202 | // Paragraphs. 203 | p:last-child { 204 | margin-bottom: 0; 205 | } 206 | 207 | // Help region. 208 | .region-help { 209 | > .glyphicon { 210 | font-size: @font-size-large; 211 | float: left; 212 | margin: -0.05em 0.5em 0 0; 213 | } 214 | .block { 215 | overflow: hidden; 216 | } 217 | } 218 | 219 | // Search form. 220 | form#search-block-form { 221 | margin: 0; 222 | } 223 | .navbar #block-search-form { 224 | float: none; 225 | margin: 5px 0 5px 5px; 226 | @media @normal { 227 | float: right; 228 | } 229 | 230 | .input-group-btn { 231 | width: auto; 232 | } 233 | } 234 | 235 | // Action Links 236 | ul.action-links { 237 | margin: @padding-base-horizontal 0; 238 | padding: 0; 239 | li { 240 | display: inline; 241 | margin: 0; 242 | padding: 0 @padding-base-vertical 0 0; 243 | } 244 | .glyphicon { 245 | padding-right: 0.5em; 246 | } 247 | } 248 | 249 | // Form elements. 250 | input, textarea, select, .uneditable-input { 251 | max-width: 100%; 252 | width: auto; 253 | } 254 | input.error { 255 | color: @state-danger-text; 256 | border-color: @state-danger-border; 257 | } 258 | fieldset legend.panel-heading { 259 | float: left; 260 | line-height: 1em; 261 | margin: 0; 262 | } 263 | fieldset .panel-body { 264 | clear: both; 265 | } 266 | fieldset .panel-heading a.panel-title { 267 | color: inherit; 268 | display: block; 269 | margin: -10px -15px; 270 | padding: 10px 15px; 271 | &:hover { 272 | text-decoration: none; 273 | } 274 | } 275 | .form-group:last-child, 276 | .panel:last-child { 277 | margin-bottom: 0; 278 | } 279 | .form-horizontal .form-group { 280 | margin-left: 0; 281 | margin-right: 0; 282 | } 283 | .form-actions{ 284 | clear: both; 285 | } 286 | .managed-files.table { 287 | td:first-child { 288 | width: 100%; 289 | } 290 | } 291 | div.image-widget-data { 292 | float: none; 293 | overflow: hidden; 294 | } 295 | table.sticky-header { 296 | z-index: 1; 297 | } 298 | .resizable-textarea textarea { 299 | border-radius: @border-radius-base @border-radius-base 0 0; 300 | } 301 | .text-format-wrapper { 302 | // Use same value as .form-group. 303 | margin-bottom: 15px; 304 | > .form-type-textarea, 305 | .filter-wrapper { 306 | margin-bottom: 0; 307 | } 308 | } 309 | .filter-wrapper { 310 | border-radius: 0 0 @border-radius-base @border-radius-base; 311 | .panel-body { 312 | padding: 7px; 313 | } 314 | .form-type-select { 315 | min-width: 30%; 316 | .filter-list { 317 | width: 100%; 318 | } 319 | } 320 | } 321 | .filter-help { 322 | margin-top: 5px; 323 | text-align: center; 324 | @media @tablet { 325 | float: right; 326 | } 327 | .glyphicon { 328 | margin: 0 5px 0 0; 329 | vertical-align: text-top; 330 | } 331 | } 332 | .radio, .checkbox { 333 | &:first-child { 334 | margin-top: 0; 335 | } 336 | &:last-child { 337 | margin-bottom: 0; 338 | } 339 | } 340 | .help-block, .control-group .help-inline { 341 | color: @gray-light; 342 | font-size: 12px; 343 | margin: 5px 0 10px; 344 | padding: 0; 345 | } 346 | .panel-heading { 347 | display: block; 348 | } 349 | a.tabledrag-handle .handle { 350 | height: auto; 351 | width: auto; 352 | } 353 | 354 | // Error containers. 355 | .error { 356 | color: @state-danger-text; 357 | } 358 | div.error, 359 | table tr.error { 360 | background-color: @state-danger-bg; 361 | color: @state-danger-text; 362 | } 363 | .form-group.error, 364 | .form-group.has-error { 365 | background: none; 366 | label, .control-label { 367 | color: @state-danger-text; 368 | font-weight: 600; 369 | } 370 | input, textarea, select, .uneditable-input { 371 | color: @input-color; 372 | } 373 | .help-block, .help-inline { 374 | color: @text-muted; 375 | } 376 | } 377 | 378 | // Lists 379 | .nav-tabs { 380 | margin-bottom: 10px; 381 | } 382 | ul li.collapsed, 383 | ul li.expanded, 384 | ul li.leaf { 385 | list-style: none; 386 | list-style-image: none; 387 | } 388 | .tabs--secondary { 389 | margin: 0 0 10px; 390 | } 391 | 392 | // Submitted 393 | .submitted { 394 | margin-bottom: 1em; 395 | font-style: italic; 396 | font-weight: normal; 397 | color: #777; 398 | } 399 | 400 | // Password strength/match. 401 | .form-type-password-confirm { 402 | position: relative; 403 | label { 404 | display: block; 405 | .label { 406 | float: right; 407 | } 408 | } 409 | .password-help { 410 | padding-left: 2em; 411 | @media (min-width: @screen-sm-min) { 412 | border-left: 1px solid @table-border-color; 413 | left: percentage((6/12)); 414 | margin-left: (@grid-gutter-width / 2); 415 | position: absolute; 416 | } 417 | @media (min-width: @screen-md-min) { 418 | left: percentage((4/12)); 419 | } 420 | } 421 | .progress { 422 | background: transparent; 423 | -moz-border-radius: 0 0 5px 5px; 424 | -webkit-border-radius: 0 0 5px 5px; 425 | border-radius: 0 0 5px 5px; 426 | .box-shadow(none); 427 | height: 4px; 428 | margin: -5px 0px 0; 429 | } 430 | .form-type-password { 431 | clear: left; 432 | } 433 | .form-control-feedback { 434 | right: 15px; 435 | } 436 | .help-block { 437 | clear: both; 438 | } 439 | } 440 | 441 | // Views AJAX pager. 442 | ul.pagination li > a { 443 | &.progress-disabled { 444 | float: left; 445 | } 446 | } 447 | 448 | // Autocomplete and throbber 449 | .form-autocomplete { 450 | .glyphicon { 451 | color: @gray-light; 452 | font-size: 120%; 453 | &.glyphicon-spin { 454 | color: @brand-primary; 455 | } 456 | } 457 | .input-group-addon { 458 | background-color: rgb(255, 255, 255); 459 | } 460 | .dropdown a { 461 | white-space: normal; 462 | } 463 | } 464 | 465 | // AJAX "Progress bar". 466 | .ajax-progress-bar { 467 | border-radius: 0 0 @border-radius-base @border-radius-base; 468 | border: 1px solid @input-group-addon-border-color; 469 | margin: -1px 0 0; 470 | padding: @padding-base-vertical @padding-base-horizontal; 471 | width: 100%; 472 | .progress { 473 | height: 8px; 474 | margin: 0; 475 | } 476 | .percentage, 477 | .message { 478 | color: @text-muted; 479 | font-size: @font-size-small; 480 | line-height: 1em; 481 | margin: 5px 0 0; 482 | padding: 0; 483 | } 484 | } 485 | 486 | .glyphicon-spin { 487 | display: inline-block; 488 | -moz-animation: spin 1s infinite linear; 489 | -o-animation: spin 1s infinite linear; 490 | -webkit-animation: spin 1s infinite linear; 491 | animation: spin 1s infinite linear; 492 | } 493 | a .glyphicon-spin { 494 | display: inline-block; 495 | text-decoration: none; 496 | } 497 | @-moz-keyframes spin { 498 | 0% { 499 | -moz-transform: rotate(0deg); 500 | } 501 | 100% { 502 | -moz-transform: rotate(359deg); 503 | } 504 | } 505 | @-webkit-keyframes spin { 506 | 0% { 507 | -webkit-transform: rotate(0deg); 508 | } 509 | 100% { 510 | -webkit-transform: rotate(359deg); 511 | } 512 | } 513 | @-o-keyframes spin { 514 | 0% { 515 | -o-transform: rotate(0deg); 516 | } 517 | 100% { 518 | -o-transform: rotate(359deg); 519 | } 520 | } 521 | @-ms-keyframes spin { 522 | 0% { 523 | -ms-transform: rotate(0deg); 524 | } 525 | 100% { 526 | -ms-transform: rotate(359deg); 527 | } 528 | } 529 | @keyframes spin { 530 | 0% { 531 | transform: rotate(0deg); 532 | } 533 | 100% { 534 | transform: rotate(359deg); 535 | } 536 | } 537 | 538 | /** 539 | * Missing Bootstrap 2 tab styling. 540 | * @see http://stackoverflow.com/questions/18432577/stacked-tabs-in-bootstrap-3 541 | * @see http://bootply.com/74926 542 | */ 543 | .tabbable { 544 | margin-bottom: 20px; 545 | } 546 | .tabs-below, .tabs-left, .tabs-right { 547 | > .nav-tabs { 548 | border-bottom: 0; 549 | .summary { 550 | color: @nav-disabled-link-color; 551 | font-size: @font-size-small; 552 | } 553 | } 554 | } 555 | .tab-pane > .panel-heading { 556 | display: none; 557 | } 558 | .tab-content > .active { 559 | display: block; 560 | } 561 | 562 | // Below. 563 | .tabs-below { 564 | > .nav-tabs { 565 | border-top: 1px solid @nav-tabs-border-color; 566 | > li { 567 | margin-top: -1px; 568 | margin-bottom: 0; 569 | > a { 570 | border-radius: 0 0 @border-radius-base @border-radius-base; 571 | &:hover, 572 | &:focus { 573 | border-top-color: @nav-tabs-border-color; 574 | border-bottom-color: transparent; 575 | } 576 | } 577 | } 578 | > .active { 579 | > a, 580 | > a:hover, 581 | > a:focus { 582 | border-color: transparent @nav-tabs-border-color @nav-tabs-border-color @nav-tabs-border-color; 583 | } 584 | } 585 | } 586 | } 587 | 588 | // Left and right tabs. 589 | .tabs-left, 590 | .tabs-right { 591 | > .nav-tabs { 592 | padding-bottom: 20px; 593 | width: 220px; 594 | > li { 595 | float: none; 596 | &:focus { 597 | outline: 0; 598 | } 599 | > a { 600 | margin-right: 0; 601 | margin-bottom: 3px; 602 | &:focus { 603 | outline: 0; 604 | } 605 | } 606 | } 607 | } 608 | > .tab-content { 609 | border-radius: 0 @border-radius-base @border-radius-base @border-radius-base; 610 | .box-shadow(0 1px 1px rgba(0,0,0,.05)); 611 | border: 1px solid @nav-tabs-border-color; 612 | overflow: hidden; 613 | padding: 10px 15px; 614 | } 615 | } 616 | 617 | // Left tabs. 618 | .tabs-left { 619 | > .nav-tabs { 620 | float: left; 621 | margin-right: -1px; 622 | > li > a { 623 | border-radius: @border-radius-base 0 0 @border-radius-base; 624 | &:hover, 625 | &:focus { 626 | border-color: @nav-tabs-link-hover-border-color @nav-tabs-border-color @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color; 627 | } 628 | } 629 | > .active > a, 630 | > .active > a:hover, 631 | > .active > a:focus { 632 | border-color: @nav-tabs-border-color transparent @nav-tabs-border-color @nav-tabs-border-color; 633 | .box-shadow(-1px 1px 1px rgba(0,0,0,.05)); 634 | } 635 | } 636 | } 637 | 638 | // Right tabs. 639 | .tabs-right { 640 | > .nav-tabs { 641 | float: right; 642 | margin-left: -1px; 643 | > li > a { 644 | border-radius: 0 @border-radius-base @border-radius-base 0; 645 | &:hover, 646 | &:focus { 647 | border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color; 648 | .box-shadow(1px 1px 1px rgba(0,0,0,.05)); 649 | } 650 | } 651 | > .active > a, 652 | > .active > a:hover, 653 | > .active > a:focus { 654 | border-color: @nav-tabs-border-color @nav-tabs-border-color @nav-tabs-border-color transparent; 655 | } 656 | } 657 | } 658 | 659 | // Checkbox cell fix. 660 | th.checkbox, 661 | td.checkbox, 662 | th.radio, 663 | td.radio { 664 | display: table-cell; 665 | } 666 | 667 | // Views UI fixes. 668 | .views-display-settings { 669 | .label { 670 | font-size: 100%; 671 | color:#666666; 672 | } 673 | .footer { 674 | padding:0; 675 | margin:4px 0 0 0; 676 | } 677 | } 678 | .views-exposed-form .views-exposed-widget .btn { 679 | margin-top: 1.8em; 680 | } 681 | 682 | // Radio and checkbox in table fixes 683 | table { 684 | .radio input[type="radio"], 685 | .checkbox input[type="checkbox"] { 686 | max-width: inherit; 687 | } 688 | } 689 | 690 | // Exposed filters 691 | .form-horizontal .form-group label { 692 | position: relative; 693 | min-height: 1px; 694 | margin-top: 0; 695 | margin-bottom: 0; 696 | padding-top: 7px; 697 | padding-left: (@grid-gutter-width / 2); 698 | padding-right: (@grid-gutter-width / 2); 699 | text-align: right; 700 | 701 | @media (min-width: @screen-sm-min) { 702 | float: left; 703 | width: percentage((2 / @grid-columns)); 704 | } 705 | } 706 | 707 | // Treat all links inside alert as .alert-link 708 | .alert a { 709 | font-weight: @alert-link-font-weight; 710 | } 711 | .alert-success { 712 | a, a:hover, a:focus { 713 | color: darken(@alert-success-text, 10%); 714 | } 715 | } 716 | .alert-info { 717 | a, a:hover, a:focus { 718 | color: darken(@alert-info-text, 10%); 719 | } 720 | } 721 | .alert-warning { 722 | a, a:hover, a:focus { 723 | color: darken(@alert-warning-text, 10%); 724 | } 725 | } 726 | .alert-danger { 727 | a, a:hover, a:focus { 728 | color: darken(@alert-danger-text, 10%); 729 | } 730 | } 731 | 732 | // Override image module. 733 | div.image-style-new, 734 | div.image-style-new div { 735 | display: block; 736 | } 737 | div.image-style-new div.input-group { 738 | display: table; 739 | } 740 | 741 | td.module, 742 | .table-striped>tbody>tr:nth-child(odd)>td.module, 743 | .table>tbody>tr>td.module { 744 | background: @table-border-color; 745 | font-weight: 700; 746 | } 747 | 748 | // Book module. 749 | .book-toc > .dropdown-menu { 750 | overflow: hidden; 751 | > .dropdown-header { 752 | white-space: nowrap; 753 | } 754 | > li:nth-child(1) > a { 755 | font-weight: bold; 756 | } 757 | .dropdown-menu { 758 | .box-shadow(none); 759 | border: 0; 760 | display: block; 761 | font-size: @font-size-small; 762 | margin: 0; 763 | padding: 0; 764 | position: static; 765 | width: 100%; 766 | > li { 767 | padding-left: 20px; 768 | > a { 769 | margin-left: -40px; 770 | padding-left: 60px; 771 | } 772 | } 773 | } 774 | } 775 | 776 | // Features module. 777 | #features-filter .form-item.form-type-checkbox { 778 | padding-left: 20px; 779 | } 780 | fieldset.features-export-component { 781 | font-size: @font-size-small; 782 | html.js #features-export-form &, & { 783 | margin: 0 0 (@line-height-computed / 2); 784 | } 785 | .panel-heading { 786 | padding: 5px 10px; 787 | a.panel-title { 788 | font-size: @font-size-small; 789 | font-weight: 500; 790 | margin: -5px -10px; 791 | padding: 5px 10px; 792 | } 793 | } 794 | .panel-body { 795 | padding: 0 10px; 796 | } 797 | } 798 | div.features-export-list { 799 | margin: -11px 0 10px; 800 | padding: 0 10px; 801 | } 802 | 803 | fieldset.features-export-component .component-select , 804 | div.features-export-list { 805 | .form-type-checkbox { 806 | line-height: 1em; 807 | margin: 5px 5px 5px 0 !important; 808 | min-height: 0; 809 | padding: 3px 3px 3px 25px!important; 810 | input[type=checkbox] { 811 | margin-top: 0; 812 | } 813 | } 814 | } 815 | 816 | // Navbar module. 817 | body.navbar-is-fixed-top.navbar-administration.navbar-horizontal.navbar-tray-open .navbar-fixed-top { 818 | top: 79px; 819 | } 820 | body.navbar-is-fixed-top.navbar-administration .navbar-fixed-top { 821 | top: 39px; 822 | } 823 | /* Small devices (tablets, 768px and up) */ 824 | @media (min-width: @screen-sm-min) { 825 | body.navbar-is-fixed-top.navbar-administration.navbar-vertical.navbar-tray-open .navbar-fixed-top { 826 | left: 240px; 827 | left: 24rem; 828 | } 829 | } 830 | .navbar-administration #navbar-administration.navbar-oriented .navbar-bar{ 831 | z-index: 1032; 832 | } 833 | .navbar-administration #navbar-administration .navbar-tray { 834 | z-index: 1031; 835 | } 836 | body.navbar-is-fixed-top.navbar-administration { 837 | padding-top: 103px!important; 838 | } 839 | body.navbar-is-fixed-top.navbar-administration.navbar-horizontal.navbar-tray-open { 840 | padding-top: 143px!important; 841 | } 842 | body.navbar-tray-open.navbar-vertical.navbar-fixed { 843 | margin-left: 240px; 844 | margin-left: 24rem; 845 | } 846 | #navbar-administration.navbar-oriented .navbar-tray-vertical { 847 | width: 24rem; 848 | } 849 | 850 | /** 851 | * Icon styles. 852 | */ 853 | a { 854 | &.icon-before .glyphicon, 855 | & .glyphicon.icon-before { 856 | margin-right: .25em; 857 | } 858 | &.icon-after .glyphicon, 859 | & .glyphicon.icon-after { 860 | margin-left: .25em; 861 | } 862 | } 863 | 864 | .btn { 865 | &.icon-before .glyphicon, 866 | & .glyphicon.icon-before { 867 | margin-left: -.25em; 868 | margin-right: .25em; 869 | } 870 | &.icon-after .glyphicon, 871 | & .glyphicon.icon-after { 872 | margin-left: .25em; 873 | margin-right: -.25em; 874 | } 875 | } 876 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/less/style.less: -------------------------------------------------------------------------------- 1 | // Bootstrap library. 2 | @import 'bootstrap'; 3 | 4 | // Base-theme overrides. 5 | @import 'drupal-bootstrap'; 6 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/less/variable-overrides.less: -------------------------------------------------------------------------------- 1 | // Variable Overrides. 2 | // 3 | // Modify this file to override the Bootstrap Framework variables. You can copy 4 | // these variables directly from ../bootstrap/less/variables.less to this file. 5 | 6 | // Set the proper directory for the Bootstrap Glyphicon font. 7 | @icon-font-path: '../bootstrap/fonts/'; 8 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/scss/README.md: -------------------------------------------------------------------------------- 1 | # [Drupal Bootstrap Styles] (SASS) 2 | 3 | These files are intended to be used within a [Drupal Bootstrap] based sub-theme. 4 | 5 | ## Default Variables 6 | 7 | The `_default-variables.scss` file is generally where you will spend the 8 | majority of your time providing any default variables that should be 9 | used by the [Bootstrap Framework] instead of its own. 10 | 11 | > **WARNING:** It is highly recommended that you only put the default variables 12 | you intend to override in this file. Copying and pasting the entire 13 | `_variables.scss` file provided by the [Bootstrap Framework] is redundant and 14 | only makes it more difficult to determine which variable has been overridden 15 | when the need to update or upgrade to newer versions in the future arises. 16 | 17 | ## Drupal Bootstrap 18 | 19 | The `_drupal-bootstrap.scss` file contains various styles to properly integrate 20 | [Drupal Bootstrap] with the [Bootstrap Framework]. 21 | 22 | ## Compiling 23 | 24 | The `style.scss` file is the main compiling entry point. It is the glue that 25 | combines: `_default-variables.scss`, [Bootstrap Framework Source Files] and the 26 | `_drupal-bootstrap.scss` files together. Generally, you will not need to modify 27 | this file unless you need to add or remove files to be imported. 28 | 29 | Your [Sass] compiler should use this file as it's "source" and its compiled 30 | output "destination" should be `../css/style.css` (overwrites the file provided 31 | by the base-theme starterkit). 32 | 33 | [Bootstrap Framework]: https://getbootstrap.com/docs/3.4/ 34 | [Bootstrap Framework Source Files]: https://github.com/twbs/bootstrap-sass 35 | [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap 36 | [Drupal Bootstrap Styles]: https://github.com/unicorn-fail/drupal-bootstrap-styles 37 | [Sass]: http://sass-lang.com 38 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/scss/_default-variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Default Variables. 3 | * 4 | * Modify this file to provide default Bootstrap Framework variables. The 5 | * Bootstrap Framework will not override any variables defined here because it 6 | * uses the `!default` flag which will only set their default if not already 7 | * defined here. 8 | * 9 | * You can copy existing variables directly from the following file: 10 | * ./THEMENAME/bootstrap/assets/stylesheets/bootstrap/_variables.scss 11 | */ 12 | 13 | // Set the proper directory for the Bootstrap Glyphicon font. 14 | $icon-font-path: '../bootstrap/assets/fonts/bootstrap/'; 15 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/scss/_drupal-bootstrap.scss: -------------------------------------------------------------------------------- 1 | // Media query variables. 2 | $tablet: "screen and (min-width: #{$screen-sm-min})"; 3 | $normal: "screen and (min-width: #{$screen-md-min})"; 4 | $wide: "screen and (min-width: #{$screen-lg-min})"; 5 | $grid-breakpoint: "screen and (min-width: #{$grid-float-breakpoint})"; 6 | 7 | // Drop shadows. 8 | @mixin box-shadow($shadow) { 9 | -webkit-box-shadow: $shadow; 10 | -moz-box-shadow: $shadow; 11 | box-shadow: $shadow; 12 | } 13 | 14 | html { 15 | &.overlay-open .navbar-fixed-top { 16 | z-index: 400; 17 | } 18 | &.js { 19 | // Collapsible fieldsets. 20 | fieldset.collapsed { 21 | border-left-width: 1px; 22 | border-right-width: 1px; 23 | height: auto; 24 | } 25 | // Throbbers. 26 | input.form-autocomplete { 27 | background-image: none; 28 | } 29 | // Autocomplete. 30 | input.form-autocomplete { 31 | background-image: none; 32 | } 33 | // Autocomplete (fallback throbber, no icon). 34 | .autocomplete-throbber { 35 | background-position: 100% 2px; 36 | background-repeat: no-repeat; 37 | display: inline-block; 38 | height: 15px; 39 | margin: 2px 0 0 2px; 40 | width: 15px; 41 | } 42 | .autocomplete-throbber.throbbing { 43 | background-position: 100% -18px; 44 | } 45 | } 46 | } 47 | body { 48 | // Fix horizontal scrolling on iOS devices. 49 | // https://www.drupal.org/node/1870076 50 | position: relative; 51 | &.admin-expanded.admin-vertical.admin-nw .navbar, 52 | &.admin-expanded.admin-vertical.admin-sw .navbar { 53 | margin-left: 260px; 54 | } 55 | // Add padding to body if various toolbar or navbars are fixed on top. 56 | &.navbar-is-fixed-top { 57 | padding-top: 64px; 58 | } 59 | &.navbar-is-fixed-bottom { 60 | padding-bottom: 64px !important; 61 | } 62 | &.toolbar { 63 | padding-top: 30px !important; 64 | .navbar-fixed-top { 65 | top: 30px; 66 | } 67 | &.navbar-is-fixed-top { 68 | padding-top: 94px !important; 69 | } 70 | } 71 | &.toolbar-drawer { 72 | padding-top: 64px !important; 73 | .navbar-fixed-top { 74 | top: 64px; 75 | } 76 | &.navbar-is-fixed-top { 77 | padding-top: 128px !important; 78 | } 79 | } 80 | // Admin_menu toolbar. 81 | &.admin-menu { 82 | .navbar-fixed-top { 83 | top: 29px; 84 | } 85 | &.navbar-is-fixed-top { 86 | padding-top: 93px !important; 87 | } 88 | } 89 | div#admin-toolbar { 90 | z-index: 1600; 91 | } 92 | // Override box-shadow styles on all "admin" menus. 93 | #toolbar, #admin-menu, #admin-toolbar { 94 | @include box-shadow(none); 95 | } 96 | // Override #admin-menu style. 97 | #admin-menu { 98 | margin: 0; 99 | padding: 0; 100 | position: fixed; 101 | z-index: 1600; 102 | .dropdown li { 103 | line-height: normal; 104 | } 105 | } 106 | } 107 | 108 | // Default navbar. 109 | .navbar { 110 | &.container { 111 | @media #{$tablet} { 112 | max-width: (($container-sm - $grid-gutter-width)); 113 | } 114 | @media #{$normal} { 115 | max-width: (($container-md - $grid-gutter-width)); 116 | } 117 | @media #{$wide} { 118 | max-width: (($container-lg - $grid-gutter-width)); 119 | } 120 | } 121 | &.container, 122 | &.container-fluid { 123 | margin-top: 20px; 124 | } 125 | &.container > .container, 126 | &.container-fluid > .container-fluid { 127 | margin: 0; 128 | padding: 0; 129 | width: auto; 130 | } 131 | } 132 | 133 | // Adjust z-index of core components. 134 | #overlay-container, 135 | .overlay-modal-background, 136 | .overlay-element { 137 | z-index: 1500; 138 | } 139 | #toolbar { 140 | z-index: 1600; 141 | } 142 | // Adjust z-index of bootstrap modals 143 | .modal { 144 | z-index: 1620; 145 | } 146 | .modal-dialog { 147 | z-index: 1630; 148 | } 149 | .ctools-modal-dialog .modal-body { 150 | width: 100% !important; 151 | overflow: auto; 152 | } 153 | .modal-backdrop { 154 | z-index: 1610; 155 | } 156 | .footer { 157 | margin-top: 45px; 158 | padding-top: 35px; 159 | padding-bottom: 36px; 160 | border-top: 1px solid #E5E5E5; 161 | } 162 | 163 | // Element invisible fix 164 | .element-invisible { 165 | margin: 0; 166 | padding: 0; 167 | width: 1px; 168 | } 169 | 170 | // Branding. 171 | .navbar .logo { 172 | margin-right: -15px; 173 | padding-left: 15px; 174 | padding-right: 15px; 175 | @media #{$grid-breakpoint} { 176 | margin-right: 0; 177 | padding-left: 0; 178 | } 179 | } 180 | 181 | // Navigation. 182 | ul.secondary { 183 | float: left; 184 | @media #{$tablet} { 185 | float: right; 186 | } 187 | } 188 | 189 | // Page header. 190 | .page-header { 191 | margin-top: 0; 192 | } 193 | 194 | // Sidebar blocks. 195 | .region-sidebar-first, 196 | .region-sidebar-second { 197 | .block:first-child h2.block-title { 198 | margin-top: 0; 199 | } 200 | } 201 | 202 | // Paragraphs. 203 | p:last-child { 204 | margin-bottom: 0; 205 | } 206 | 207 | // Help region. 208 | .region-help { 209 | > .glyphicon { 210 | font-size: $font-size-large; 211 | float: left; 212 | margin: -0.05em 0.5em 0 0; 213 | } 214 | .block { 215 | overflow: hidden; 216 | } 217 | } 218 | 219 | // Search form. 220 | form#search-block-form { 221 | margin: 0; 222 | } 223 | .navbar #block-search-form { 224 | float: none; 225 | margin: 5px 0 5px 5px; 226 | @media #{$normal} { 227 | float: right; 228 | } 229 | 230 | .input-group-btn { 231 | width: auto; 232 | } 233 | } 234 | .navbar-search .control-group { 235 | margin-bottom:0px; 236 | } 237 | 238 | // Action Links 239 | ul.action-links { 240 | margin: $padding-base-horizontal 0; 241 | padding: 0; 242 | li { 243 | display: inline; 244 | margin: 0; 245 | padding: 0 $padding-base-vertical 0 0; 246 | } 247 | .glyphicon { 248 | padding-right: 0.5em; 249 | } 250 | } 251 | 252 | // Form elements. 253 | input, textarea, select, .uneditable-input { 254 | max-width: 100%; 255 | width: auto; 256 | } 257 | input.error { 258 | color: $state-danger-text; 259 | border-color: $state-danger-border; 260 | } 261 | fieldset legend.panel-heading { 262 | float: left; 263 | line-height: 1em; 264 | margin: 0; 265 | } 266 | fieldset .panel-body { 267 | clear: both; 268 | } 269 | fieldset .panel-heading a.panel-title { 270 | color: inherit; 271 | display: block; 272 | margin: -10px -15px; 273 | padding: 10px 15px; 274 | &:hover { 275 | text-decoration: none; 276 | } 277 | } 278 | .form-group:last-child, 279 | .panel:last-child { 280 | margin-bottom: 0; 281 | } 282 | .form-horizontal .form-group { 283 | margin-left: 0; 284 | margin-right: 0; 285 | } 286 | .form-actions{ 287 | clear: both; 288 | } 289 | .managed-files.table { 290 | td:first-child { 291 | width: 100%; 292 | } 293 | } 294 | div.image-widget-data { 295 | float: none; 296 | overflow: hidden; 297 | } 298 | table.sticky-header { 299 | z-index: 1; 300 | } 301 | .resizable-textarea textarea { 302 | border-radius: $border-radius-base $border-radius-base 0 0; 303 | } 304 | .text-format-wrapper { 305 | // Use same value as .form-group. 306 | margin-bottom: 15px; 307 | > .form-type-textarea, 308 | .filter-wrapper { 309 | margin-bottom: 0; 310 | } 311 | } 312 | .filter-wrapper { 313 | border-radius: 0 0 $border-radius-base $border-radius-base; 314 | .panel-body { 315 | padding: 7px; 316 | } 317 | .form-type-select { 318 | min-width: 30%; 319 | .filter-list { 320 | width: 100%; 321 | } 322 | } 323 | } 324 | .filter-help { 325 | margin-top: 5px; 326 | text-align: center; 327 | @media #{$tablet} { 328 | float: right; 329 | } 330 | .glyphicon { 331 | margin: 0 5px 0 0; 332 | vertical-align: text-top; 333 | } 334 | } 335 | .radio, .checkbox { 336 | &:first-child { 337 | margin-top: 0; 338 | } 339 | &:last-child { 340 | margin-bottom: 0; 341 | } 342 | } 343 | .help-block, .control-group .help-inline { 344 | color: $gray-light; 345 | font-size: 12px; 346 | margin: 5px 0 10px; 347 | padding: 0; 348 | } 349 | .panel-heading { 350 | display: block; 351 | } 352 | a.tabledrag-handle .handle { 353 | height: auto; 354 | width: auto; 355 | } 356 | 357 | // Error containers. 358 | .error { 359 | color: $state-danger-text; 360 | } 361 | div.error, 362 | table tr.error { 363 | background-color: $state-danger-bg; 364 | color: $state-danger-text; 365 | } 366 | .form-group.error, 367 | .form-group.has-error { 368 | background: none; 369 | label, .control-label { 370 | color: $state-danger-text; 371 | font-weight: 600; 372 | } 373 | input, textarea, select, .uneditable-input { 374 | color: $input-color; 375 | } 376 | .help-block, .help-inline { 377 | color: $text-muted; 378 | } 379 | } 380 | 381 | // Lists 382 | .nav-tabs { 383 | margin-bottom: 10px; 384 | } 385 | ul li.collapsed, 386 | ul li.expanded, 387 | ul li.leaf { 388 | list-style: none; 389 | list-style-image: none; 390 | } 391 | .tabs--secondary { 392 | margin: 0 0 10px; 393 | } 394 | 395 | // Submitted 396 | .submitted { 397 | margin-bottom: 1em; 398 | font-style: italic; 399 | font-weight: normal; 400 | color: #777; 401 | } 402 | 403 | // Password strength/match. 404 | .form-type-password-confirm { 405 | position: relative; 406 | label { 407 | display: block; 408 | .label { 409 | float: right; 410 | } 411 | } 412 | .password-help { 413 | padding-left: 2em; 414 | @media (min-width: $screen-sm-min) { 415 | border-left: 1px solid $table-border-color; 416 | left: percentage((6/12)); 417 | margin-left: ($grid-gutter-width / 2); 418 | position: absolute; 419 | } 420 | @media (min-width: $screen-md-min) { 421 | left: percentage((4/12)); 422 | } 423 | } 424 | .progress { 425 | background: transparent; 426 | -moz-border-radius: 0 0 5px 5px; 427 | -webkit-border-radius: 0 0 5px 5px; 428 | border-radius: 0 0 5px 5px; 429 | @include box-shadow(none); 430 | height: 4px; 431 | margin: -5px 0px 0; 432 | } 433 | .form-type-password { 434 | clear: left; 435 | } 436 | .form-control-feedback { 437 | right: 15px; 438 | } 439 | .help-block { 440 | clear: both; 441 | } 442 | } 443 | 444 | // Views AJAX pager. 445 | ul.pagination li > a { 446 | &.progress-disabled { 447 | float: left; 448 | } 449 | } 450 | 451 | // Autocomplete and throbber 452 | .form-autocomplete { 453 | .glyphicon { 454 | color: $gray-light; 455 | font-size: 120%; 456 | &.glyphicon-spin { 457 | color: $brand-primary; 458 | } 459 | } 460 | .input-group-addon { 461 | background-color: rgb(255, 255, 255); 462 | } 463 | } 464 | 465 | // AJAX "Progress bar". 466 | .ajax-progress-bar { 467 | border-radius: 0 0 $border-radius-base $border-radius-base; 468 | border: 1px solid $input-group-addon-border-color; 469 | margin: -1px 0 0; 470 | padding: $padding-base-vertical $padding-base-horizontal; 471 | width: 100%; 472 | .progress { 473 | height: 8px; 474 | margin: 0; 475 | } 476 | .percentage, 477 | .message { 478 | color: $text-muted; 479 | font-size: $font-size-small; 480 | line-height: 1em; 481 | margin: 5px 0 0; 482 | padding: 0; 483 | } 484 | } 485 | 486 | .glyphicon-spin { 487 | display: inline-block; 488 | -moz-animation: spin 1s infinite linear; 489 | -o-animation: spin 1s infinite linear; 490 | -webkit-animation: spin 1s infinite linear; 491 | animation: spin 1s infinite linear; 492 | } 493 | a .glyphicon-spin { 494 | display: inline-block; 495 | text-decoration: none; 496 | } 497 | @-moz-keyframes spin { 498 | 0% { 499 | -moz-transform: rotate(0deg); 500 | } 501 | 100% { 502 | -moz-transform: rotate(359deg); 503 | } 504 | } 505 | @-webkit-keyframes spin { 506 | 0% { 507 | -webkit-transform: rotate(0deg); 508 | } 509 | 100% { 510 | -webkit-transform: rotate(359deg); 511 | } 512 | } 513 | @-o-keyframes spin { 514 | 0% { 515 | -o-transform: rotate(0deg); 516 | } 517 | 100% { 518 | -o-transform: rotate(359deg); 519 | } 520 | } 521 | @-ms-keyframes spin { 522 | 0% { 523 | -ms-transform: rotate(0deg); 524 | } 525 | 100% { 526 | -ms-transform: rotate(359deg); 527 | } 528 | } 529 | @keyframes spin { 530 | 0% { 531 | transform: rotate(0deg); 532 | } 533 | 100% { 534 | transform: rotate(359deg); 535 | } 536 | } 537 | 538 | /** 539 | * Missing Bootstrap 2 tab styling. 540 | * @see http://stackoverflow.com/questions/18432577/stacked-tabs-in-bootstrap-3 541 | * @see http://bootply.com/74926 542 | */ 543 | .tabbable { 544 | margin-bottom: 20px; 545 | } 546 | .tabs-below, .tabs-left, .tabs-right { 547 | > .nav-tabs { 548 | border-bottom: 0; 549 | .summary { 550 | color: $nav-disabled-link-color; 551 | font-size: $font-size-small; 552 | } 553 | } 554 | } 555 | .tab-pane > .panel-heading { 556 | display: none; 557 | } 558 | .tab-content > .active { 559 | display: block; 560 | } 561 | 562 | // Below. 563 | .tabs-below { 564 | > .nav-tabs { 565 | border-top: 1px solid $nav-tabs-border-color; 566 | > li { 567 | margin-top: -1px; 568 | margin-bottom: 0; 569 | > a { 570 | border-radius: 0 0 $border-radius-base $border-radius-base; 571 | &:hover, 572 | &:focus { 573 | border-top-color: $nav-tabs-border-color; 574 | border-bottom-color: transparent; 575 | } 576 | } 577 | } 578 | > .active { 579 | > a, 580 | > a:hover, 581 | > a:focus { 582 | border-color: transparent $nav-tabs-border-color $nav-tabs-border-color $nav-tabs-border-color; 583 | } 584 | } 585 | } 586 | } 587 | 588 | // Left and right tabs. 589 | .tabs-left, 590 | .tabs-right { 591 | > .nav-tabs { 592 | padding-bottom: 20px; 593 | width: 220px; 594 | > li { 595 | float: none; 596 | &:focus { 597 | outline: 0; 598 | } 599 | > a { 600 | margin-right: 0; 601 | margin-bottom: 3px; 602 | &:focus { 603 | outline: 0; 604 | } 605 | } 606 | } 607 | } 608 | > .tab-content { 609 | border-radius: 0 $border-radius-base $border-radius-base $border-radius-base; 610 | @include box-shadow(0 1px 1px rgba(0,0,0,.05)); 611 | border: 1px solid $nav-tabs-border-color; 612 | overflow: hidden; 613 | padding: 10px 15px; 614 | } 615 | } 616 | 617 | // Left tabs. 618 | .tabs-left { 619 | > .nav-tabs { 620 | float: left; 621 | margin-right: -1px; 622 | > li > a { 623 | border-radius: $border-radius-base 0 0 $border-radius-base; 624 | &:hover, 625 | &:focus { 626 | border-color: $nav-tabs-link-hover-border-color $nav-tabs-border-color $nav-tabs-link-hover-border-color $nav-tabs-link-hover-border-color; 627 | } 628 | } 629 | > .active > a, 630 | > .active > a:hover, 631 | > .active > a:focus { 632 | border-color: $nav-tabs-border-color transparent $nav-tabs-border-color $nav-tabs-border-color; 633 | @include box-shadow(-1px 1px 1px rgba(0,0,0,.05)); 634 | } 635 | } 636 | } 637 | 638 | // Right tabs. 639 | .tabs-right { 640 | > .nav-tabs { 641 | float: right; 642 | margin-left: -1px; 643 | > li > a { 644 | border-radius: 0 $border-radius-base $border-radius-base 0; 645 | &:hover, 646 | &:focus { 647 | border-color: $nav-tabs-link-hover-border-color $nav-tabs-link-hover-border-color $nav-tabs-link-hover-border-color $nav-tabs-border-color; 648 | @include box-shadow(1px 1px 1px rgba(0,0,0,.05)); 649 | } 650 | } 651 | > .active > a, 652 | > .active > a:hover, 653 | > .active > a:focus { 654 | border-color: $nav-tabs-border-color $nav-tabs-border-color $nav-tabs-border-color transparent; 655 | } 656 | } 657 | } 658 | 659 | // Checkbox cell fix. 660 | th.checkbox, 661 | td.checkbox, 662 | th.radio, 663 | td.radio { 664 | display: table-cell; 665 | } 666 | 667 | // Views UI fixes. 668 | .views-display-settings { 669 | .label { 670 | font-size: 100%; 671 | color:#666666; 672 | } 673 | .footer { 674 | padding:0; 675 | margin:4px 0 0 0; 676 | } 677 | } 678 | .views-exposed-form .views-exposed-widget .btn { 679 | margin-top: 1.8em; 680 | } 681 | 682 | // Radio and checkbox in table fixes 683 | table { 684 | .radio input[type="radio"], 685 | .checkbox input[type="checkbox"] { 686 | max-width: inherit; 687 | } 688 | } 689 | 690 | // Exposed filters 691 | .form-horizontal .form-group label { 692 | position: relative; 693 | min-height: 1px; 694 | margin-top: 0; 695 | margin-bottom: 0; 696 | padding-top: 7px; 697 | padding-left: ($grid-gutter-width / 2); 698 | padding-right: ($grid-gutter-width / 2); 699 | text-align: right; 700 | 701 | @media (min-width: $screen-sm-min) { 702 | float: left; 703 | width: percentage((2 / $grid-columns)); 704 | } 705 | } 706 | 707 | // Treat all links inside alert as .alert-link 708 | .alert a { 709 | font-weight: $alert-link-font-weight; 710 | } 711 | .alert-success { 712 | a, a:hover, a:focus { 713 | color: darken($alert-success-text, 10%); 714 | } 715 | } 716 | .alert-info { 717 | a, a:hover, a:focus { 718 | color: darken($alert-info-text, 10%); 719 | } 720 | } 721 | .alert-warning { 722 | a, a:hover, a:focus { 723 | color: darken($alert-warning-text, 10%); 724 | } 725 | } 726 | .alert-danger { 727 | a, a:hover, a:focus { 728 | color: darken($alert-danger-text, 10%); 729 | } 730 | } 731 | 732 | // Override image module. 733 | div.image-style-new, 734 | div.image-style-new div { 735 | display: block; 736 | } 737 | div.image-style-new div.input-group { 738 | display: table; 739 | } 740 | 741 | td.module, 742 | .table-striped>tbody>tr:nth-child(odd)>td.module, 743 | .table>tbody>tr>td.module { 744 | background: $table-border-color; 745 | font-weight: 700; 746 | } 747 | 748 | // Book module. 749 | .book-toc > .dropdown-menu { 750 | overflow: hidden; 751 | > .dropdown-header { 752 | white-space: nowrap; 753 | } 754 | > li:nth-child(1) > a { 755 | font-weight: bold; 756 | } 757 | .dropdown-menu { 758 | @include box-shadow(none); 759 | border: 0; 760 | display: block; 761 | font-size: $font-size-small; 762 | margin: 0; 763 | padding: 0; 764 | position: static; 765 | width: 100%; 766 | > li { 767 | padding-left: 20px; 768 | > a { 769 | margin-left: -40px; 770 | padding-left: 60px; 771 | } 772 | } 773 | } 774 | } 775 | 776 | // Features module. 777 | #features-filter .form-item.form-type-checkbox { 778 | padding-left: 20px; 779 | } 780 | fieldset.features-export-component { 781 | font-size: $font-size-small; 782 | html.js #features-export-form &, & { 783 | margin: 0 0 ($line-height-computed / 2); 784 | } 785 | .panel-heading { 786 | padding: 5px 10px; 787 | a.panel-title { 788 | font-size: $font-size-small; 789 | font-weight: 500; 790 | margin: -5px -10px; 791 | padding: 5px 10px; 792 | } 793 | } 794 | .panel-body { 795 | padding: 0 10px; 796 | } 797 | } 798 | div.features-export-list { 799 | margin: -11px 0 10px; 800 | padding: 0 10px; 801 | } 802 | 803 | fieldset.features-export-component .component-select , 804 | div.features-export-list { 805 | .form-type-checkbox { 806 | line-height: 1em; 807 | margin: 5px 5px 5px 0 !important; 808 | min-height: 0; 809 | padding: 3px 3px 3px 25px!important; 810 | input[type=checkbox] { 811 | margin-top: 0; 812 | } 813 | } 814 | } 815 | 816 | // Navbar module. 817 | body.navbar-is-fixed-top.navbar-administration.navbar-horizontal.navbar-tray-open .navbar-fixed-top { 818 | top: 79px; 819 | } 820 | body.navbar-is-fixed-top.navbar-administration .navbar-fixed-top { 821 | top: 39px; 822 | } 823 | /* Small devices (tablets, 768px and up) */ 824 | @media (min-width: $screen-sm-min) { 825 | body.navbar-is-fixed-top.navbar-administration.navbar-vertical.navbar-tray-open .navbar-fixed-top { 826 | left: 240px; 827 | left: 24rem; 828 | } 829 | } 830 | .navbar-administration #navbar-administration.navbar-oriented .navbar-bar{ 831 | z-index: 1032; 832 | } 833 | .navbar-administration #navbar-administration .navbar-tray { 834 | z-index: 1031; 835 | } 836 | body.navbar-is-fixed-top.navbar-administration { 837 | padding-top: 103px!important; 838 | } 839 | body.navbar-is-fixed-top.navbar-administration.navbar-horizontal.navbar-tray-open { 840 | padding-top: 143px!important; 841 | } 842 | body.navbar-tray-open.navbar-vertical.navbar-fixed { 843 | margin-left: 240px; 844 | margin-left: 24rem; 845 | } 846 | #navbar-administration.navbar-oriented .navbar-tray-vertical { 847 | width: 24rem; 848 | } 849 | 850 | /** 851 | * Icon styles. 852 | */ 853 | a { 854 | &.icon-before .glyphicon, 855 | & .glyphicon.icon-before { 856 | margin-right: .25em; 857 | } 858 | &.icon-after .glyphicon, 859 | & .glyphicon.icon-after { 860 | margin-left: .25em; 861 | } 862 | } 863 | 864 | .btn { 865 | &.icon-before .glyphicon, 866 | & .glyphicon.icon-before { 867 | margin-left: -.25em; 868 | margin-right: .25em; 869 | } 870 | &.icon-after .glyphicon, 871 | & .glyphicon.icon-after { 872 | margin-left: .25em; 873 | margin-right: -.25em; 874 | } 875 | } 876 | -------------------------------------------------------------------------------- /src/3.x.x/7.x-3.x/scss/style.scss: -------------------------------------------------------------------------------- 1 | // Default variables. 2 | @import 'default-variables'; 3 | 4 | // Bootstrap Framework. 5 | @import '../bootstrap/assets/stylesheets/bootstrap'; 6 | 7 | // Base-theme overrides. 8 | @import 'drupal-bootstrap'; 9 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/README.md: -------------------------------------------------------------------------------- 1 | # [Drupal Bootstrap Styles] (LESS) 2 | 3 | These files are intended to be used within a [Drupal Bootstrap] based sub-theme. 4 | 5 | ## Default Variables 6 | 7 | The `variable-overrides.scss` file is generally where you will spend the 8 | majority of your time providing any default variables that should be 9 | used by the [Bootstrap Framework] instead of its own. 10 | 11 | > **WARNING:** It is highly recommended that you only put the default variables 12 | you intend to override in this file. Copying and pasting the entire 13 | `variables.less` file provided by the [Bootstrap Framework] is redundant and 14 | only makes it more difficult to determine which variable has been overridden 15 | when the need to update or upgrade to newer versions in the future arises. 16 | 17 | ## Drupal Bootstrap 18 | 19 | The `drupal-bootstrap.less` file contains various styles to properly integrate 20 | [Drupal Bootstrap] with the [Bootstrap Framework]. 21 | 22 | ## Compiling 23 | 24 | The `style.less` file is the main compiling entry point. It is the glue that 25 | combines: `variable-overrides.less`, [Bootstrap Framework Source Files] and the 26 | `drupal-bootstrap.less` files together. Generally, you will not need to modify 27 | this file unless you need to add or remove files to be imported. 28 | 29 | Your [Less] compiler should use this file as it's "source" and its compiled 30 | output "destination" should be `../css/style.css` (overwrites the file provided 31 | by the base-theme starterkit). 32 | 33 | [Bootstrap Framework]: https://getbootstrap.com/docs/3.4/ 34 | [Bootstrap Framework Source Files]: https://github.com/twbs/bootstrap-sass 35 | [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap 36 | [Drupal Bootstrap Styles]: https://github.com/unicorn-fail/drupal-bootstrap-styles 37 | [Less]: http://lesscss.org 38 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/bootstrap.less: -------------------------------------------------------------------------------- 1 | // This is almost an exact duplication of the bootstrap.less file provided 2 | // by the Bootstrap Framework itself. The only difference from that file and 3 | // this one are the first two @import statements for the original framework's 4 | // variables and this theme's overrides of those variables. This is necessary 5 | // due to the fact that the Less pre-compiler language does not have the 6 | // concept of "default" variables. 7 | // 8 | // @see https://www.drupal.org/node/2775481 9 | @import "../bootstrap/less/variables.less"; 10 | @import "variable-overrides.less"; 11 | 12 | // Mixins. 13 | @import "../bootstrap/less/mixins.less"; 14 | 15 | // Reset and dependencies 16 | @import "../bootstrap/less/normalize.less"; 17 | @import "../bootstrap/less/print.less"; 18 | @import "../bootstrap/less/glyphicons.less"; 19 | 20 | // Core CSS 21 | @import "../bootstrap/less/scaffolding.less"; 22 | @import "../bootstrap/less/type.less"; 23 | @import "../bootstrap/less/code.less"; 24 | @import "../bootstrap/less/grid.less"; 25 | 26 | @import "../bootstrap/less/tables.less"; 27 | @import "../bootstrap/less/forms.less"; 28 | @import "../bootstrap/less/buttons.less"; 29 | 30 | // Components 31 | @import "../bootstrap/less/component-animations.less"; 32 | @import "../bootstrap/less/dropdowns.less"; 33 | @import "../bootstrap/less/button-groups.less"; 34 | @import "../bootstrap/less/input-groups.less"; 35 | @import "../bootstrap/less/navs.less"; 36 | @import "../bootstrap/less/navbar.less"; 37 | @import "../bootstrap/less/breadcrumbs.less"; 38 | @import "../bootstrap/less/pagination.less"; 39 | @import "../bootstrap/less/pager.less"; 40 | @import "../bootstrap/less/labels.less"; 41 | @import "../bootstrap/less/badges.less"; 42 | @import "../bootstrap/less/jumbotron.less"; 43 | @import "../bootstrap/less/thumbnails.less"; 44 | @import "../bootstrap/less/alerts.less"; 45 | @import "../bootstrap/less/progress-bars.less"; 46 | @import "../bootstrap/less/media.less"; 47 | @import "../bootstrap/less/list-group.less"; 48 | @import "../bootstrap/less/panels.less"; 49 | @import "../bootstrap/less/responsive-embed.less"; 50 | @import "../bootstrap/less/wells.less"; 51 | @import "../bootstrap/less/close.less"; 52 | 53 | // Components w/ JavaScript 54 | @import "../bootstrap/less/modals.less"; 55 | @import "../bootstrap/less/tooltip.less"; 56 | @import "../bootstrap/less/popovers.less"; 57 | @import "../bootstrap/less/carousel.less"; 58 | 59 | // Utility classes 60 | @import "../bootstrap/less/utilities.less"; 61 | @import "../bootstrap/less/responsive-utilities.less"; 62 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap.less: -------------------------------------------------------------------------------- 1 | @import "drupal-bootstrap/component/alert"; 2 | @import "drupal-bootstrap/component/ajax"; 3 | @import "drupal-bootstrap/component/field"; 4 | @import "drupal-bootstrap/component/file"; 5 | @import "drupal-bootstrap/component/filter"; 6 | @import "drupal-bootstrap/component/form"; 7 | @import "drupal-bootstrap/component/icon"; 8 | @import "drupal-bootstrap/component/navbar"; 9 | @import "drupal-bootstrap/component/node"; 10 | @import "drupal-bootstrap/component/panel"; 11 | @import "drupal-bootstrap/component/progress-bar"; 12 | @import "drupal-bootstrap/component/table-drag"; 13 | @import "drupal-bootstrap/component/tabs"; 14 | @import "drupal-bootstrap/component/toolbar"; 15 | 16 | // jQuery UI style overrides. 17 | @import "drupal-bootstrap/jquery-ui/autocomplete"; 18 | 19 | ol, ul { 20 | padding-left: 1.5em; 21 | .popover &:last-child { 22 | margin-bottom: 0; 23 | } 24 | } 25 | 26 | // Page header. 27 | .page-header { 28 | margin-top: 0; 29 | } 30 | 31 | // Footer. 32 | .footer { 33 | margin-top: 45px; 34 | padding-top: 35px; 35 | padding-bottom: 36px; 36 | border-top: 1px solid #E5E5E5; 37 | } 38 | 39 | // Paragraphs. 40 | p:last-child, 41 | .form-group:last-child, 42 | .panel:last-child { 43 | margin-bottom: 0; 44 | } 45 | 46 | // Help region. 47 | .region-help { 48 | > .glyphicon { 49 | font-size: @font-size-large; 50 | float: left; 51 | margin: -0.05em 0.5em 0 0; 52 | } 53 | .block { 54 | overflow: hidden; 55 | } 56 | } 57 | 58 | .help-block, .control-group .help-inline { 59 | color: @gray-light; 60 | font-size: 12px; 61 | margin: 5px 0 10px; 62 | padding: 0; 63 | &:first-child { 64 | margin-top: 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/ajax.less: -------------------------------------------------------------------------------- 1 | /** 2 | * AJAX (throbber) styling. 3 | */ 4 | 5 | // @todo This should probably be it's own mixin/component? 6 | @keyframes glyphicon-spin { 7 | 0% { transform: rotate(0deg); } 8 | 100% { transform: rotate(359deg); } 9 | } 10 | .glyphicon-spin { 11 | display: inline-block; 12 | animation: glyphicon-spin 1s infinite linear; 13 | } 14 | a .glyphicon-spin { 15 | display: inline-block; 16 | text-decoration: none; 17 | } 18 | 19 | html.js { 20 | .btn .ajax-throbber { 21 | margin-left: .5em; 22 | margin-right: -.25em; 23 | } 24 | 25 | .form-item .input-group-addon { 26 | .glyphicon { 27 | color: @gray-light; 28 | opacity: .5; 29 | transition: 150ms color, 150ms opacity; 30 | &.glyphicon-spin { 31 | color: @brand-primary; 32 | opacity: 1; 33 | } 34 | } 35 | .input-group-addon { 36 | background-color: rgb(255, 255, 255); 37 | } 38 | } 39 | 40 | // Hide empty wrappers from AJAX/Field APIs. 41 | .ajax-new-content:empty { 42 | display: none !important; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/alert.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Alert styling. 3 | */ 4 | .alert-sm { 5 | padding: 5px 10px; 6 | } 7 | 8 | // Treat all links inside alert as .alert-link 9 | .alert a { 10 | font-weight: @alert-link-font-weight; 11 | } 12 | .alert-success { 13 | a, a:hover, a:focus { 14 | color: darken(@alert-success-text, 10%); 15 | } 16 | } 17 | .alert-info { 18 | a, a:hover, a:focus { 19 | color: darken(@alert-info-text, 10%); 20 | } 21 | } 22 | .alert-warning { 23 | a, a:hover, a:focus { 24 | color: darken(@alert-warning-text, 10%); 25 | } 26 | } 27 | .alert-danger { 28 | a, a:hover, a:focus { 29 | color: darken(@alert-danger-text, 10%); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/field.less: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Visual styles for fields. 4 | */ 5 | 6 | .field--label { 7 | font-weight: bold; 8 | } 9 | .field--label-inline .field--label, 10 | .field--label-inline .field--items { 11 | float: left; /*LTR*/ 12 | } 13 | .field--label-inline .field--label, 14 | .field--label-inline > .field--item, 15 | .field--label-inline .field--items { 16 | padding-right: 0.5em; 17 | } 18 | [dir="rtl"] .field--label-inline .field--label, 19 | [dir="rtl"] .field--label-inline .field--items { 20 | padding-left: 0.5em; 21 | padding-right: 0; 22 | float: right; /*RTL*/ 23 | } 24 | .field--label-inline .field--label::after { 25 | content: ':'; 26 | } 27 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/file.less: -------------------------------------------------------------------------------- 1 | /** 2 | * File (and Image Widget) styles. 3 | */ 4 | .file { 5 | display: table; 6 | font-size: 75%; 7 | font-weight: 700; 8 | margin: 5px 0; 9 | width: 100%; 10 | > span { 11 | background: #fff; 12 | color: @brand-primary; 13 | border-bottom: 1px solid @input-border; 14 | border-top: 1px solid @input-border; 15 | &:first-child { 16 | border-left: 1px solid @input-border; 17 | } 18 | &:last-child { 19 | border-right: 1px solid @input-border; 20 | } 21 | } 22 | > .tabledrag-changed { 23 | &, &:last-child { 24 | border: 1px solid darken(@alert-warning-border, 5%); 25 | } 26 | background: @alert-warning-bg; 27 | border-radius: 0; 28 | color: @alert-warning-text; 29 | display: table-cell; 30 | padding: 0 1em; 31 | top: 0; 32 | vertical-align: middle; 33 | border-left: 1px solid inherit; 34 | } 35 | } 36 | .file-icon { 37 | display: table-cell; 38 | font-size: 150%; 39 | padding: .25em .5em; 40 | text-align: center; 41 | vertical-align: middle; 42 | } 43 | .file-link { 44 | display: table-cell; 45 | vertical-align: middle; 46 | width: 100%; 47 | a, a:hover, a:focus, a:active { 48 | color: inherit; 49 | } 50 | } 51 | .file-size { 52 | display: table-cell; 53 | padding: 0 1em; 54 | text-align: right; 55 | white-space: pre; 56 | vertical-align: middle; 57 | } 58 | 59 | .image-widget.row { 60 | overflow: hidden; 61 | } 62 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/filter.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter styles. 3 | */ 4 | 5 | // Mimic .panel-default styling. 6 | .filter-wrapper { 7 | background-color: @panel-bg; 8 | border: 1px solid @panel-default-border; 9 | border-top: 0; 10 | border-radius: 0 0 @panel-border-radius @panel-border-radius; 11 | box-shadow: 0 1px 1px rgba(0,0,0,.05); 12 | margin-bottom: 0; 13 | padding: 10px; 14 | height: 51px; 15 | &:extend(.clearfix all); 16 | } 17 | .filter-help { 18 | float: right; 19 | line-height: 1; 20 | margin: .5em 0 0; 21 | } 22 | 23 | // Full list page. 24 | .nav.nav-tabs.filter-formats { 25 | margin-bottom: 15px; 26 | } 27 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/form.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Form styles. 3 | */ 4 | 5 | // Checkboxes / radio buttons. 6 | .checkbox, .radio { 7 | // Remove top/bottom margins when in table context, because this is most 8 | // likely a tableselect checkbox element. 9 | table &.form-no-label { 10 | margin-bottom: 0; 11 | margin-top: 0; 12 | } 13 | } 14 | 15 | // Browsers do not recognize pseudo :after selectors, we must create a wrapper 16 | // around the select element to style it properly. 17 | // @see http://stackoverflow.com/q/21103542 18 | .select-wrapper { 19 | display: inline-block; 20 | position: relative; 21 | width: 100%; 22 | .form-inline & { 23 | width: auto; 24 | } 25 | .input-group & { 26 | display: table-cell; 27 | // Reset rounded corners 28 | &:first-child .form-control:first-child { 29 | .border-left-radius(@border-radius-base); 30 | } 31 | &:last-child .form-control:first-child { 32 | .border-right-radius(@border-radius-base); 33 | } 34 | } 35 | select { 36 | -webkit-appearance: none; 37 | -moz-appearance: none; 38 | appearance: none; 39 | line-height: 1; 40 | padding-right: 2em; 41 | &::-ms-expand { 42 | opacity: 0; 43 | } 44 | } 45 | &:after { 46 | color: @brand-primary; 47 | content: '▼'; 48 | font-style: normal; 49 | font-weight: 400; 50 | line-height: 1; 51 | margin-top: -.5em; 52 | padding-right: .5em; 53 | pointer-events: none; 54 | position: absolute; 55 | right: 0; 56 | top: 50%; 57 | z-index: 10; 58 | 59 | // Use a more stylish icon if the theme uses glyphicons. 60 | .has-glyphicons & { 61 | -webkit-font-smoothing: antialiased; 62 | -moz-osx-font-smoothing: grayscale; 63 | content: '\e114'; // .glyphicon-chevron-down 64 | display: inline-block; 65 | font-family: 'Glyphicons Halflings'; 66 | } 67 | .has-error & { 68 | color: @state-danger-text; 69 | } 70 | .has-success & { 71 | color: @state-success-text; 72 | } 73 | .has-warning & { 74 | color: @state-warning-text; 75 | } 76 | } 77 | } 78 | 79 | // Use CSS/SVG image for required mark. 80 | // @see https://www.drupal.org/node/2152217 81 | // @see https://www.drupal.org/node/2274631 82 | .form-required:after { 83 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI0VFMDAwMCIgIGQ9Ik0wLDcuNTYybDEuMTE0LTMuNDM4YzIuNTY1LDAuOTA2LDQuNDMsMS42ODgsNS41OSwyLjM1QzYuMzk4LDMuNTUzLDYuMjM3LDEuNTQ0LDYuMjIsMC40NDdoMy41MTEgYy0wLjA1LDEuNTk3LTAuMjM0LDMuNi0wLjU1OCw2LjAwM2MxLjY2NC0wLjgzOCwzLjU2Ni0xLjYxMyw1LjcxNC0yLjMyNUwxNiw3LjU2MmMtMi4wNSwwLjY3OC00LjA2LDEuMTMxLTYuMDI4LDEuMzU2IGMwLjk4NCwwLjg1NiwyLjM3MiwyLjM4MSw0LjE2Niw0LjU3NWwtMi45MDYsMi4wNTljLTAuOTM1LTEuMjc0LTIuMDQxLTMuMDA5LTMuMzE2LTUuMjA2Yy0xLjE5NCwyLjI3NS0yLjI0NCw0LjAxMy0zLjE0Nyw1LjIwNiBsLTIuODU2LTIuMDU5YzEuODcyLTIuMzA3LDMuMjExLTMuODMyLDQuMDE3LTQuNTc1QzMuODQ5LDguNTE2LDEuODcyLDguMDYyLDAsNy41NjIiLz48L3N2Zz4K); 84 | background-size: 10px 7px; 85 | content:""; 86 | display: inline-block; 87 | vertical-align: super; 88 | line-height:1; 89 | height: 7px; 90 | width: 10px; 91 | } 92 | 93 | // Form action buttons. 94 | .form-actions { 95 | .btn, .btn-group { 96 | margin-right: 10px; 97 | } 98 | .btn-group { 99 | .btn { 100 | margin-right: 0; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/icon.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Icon styles. 3 | */ 4 | a { 5 | &.icon-before .glyphicon, 6 | & .glyphicon.icon-before { 7 | margin-right: .25em; 8 | } 9 | &.icon-after .glyphicon, 10 | & .glyphicon.icon-after { 11 | margin-left: .25em; 12 | } 13 | } 14 | 15 | .btn { 16 | &.icon-before .glyphicon, 17 | & .glyphicon.icon-before { 18 | margin-left: -.25em; 19 | margin-right: .25em; 20 | } 21 | &.icon-after .glyphicon, 22 | & .glyphicon.icon-after { 23 | margin-left: .25em; 24 | margin-right: -.25em; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/navbar.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Navbar styling. 3 | */ 4 | @mobile: ~"screen and (max-width: @{screen-xs-max})"; 5 | @tablet: ~"screen and (min-width: @{screen-sm-min})"; 6 | @normal: ~"screen and (min-width: @{screen-md-min})"; 7 | @wide: ~"screen and (min-width: @{screen-lg-min})"; 8 | @grid-breakpoint: ~"screen and (min-width: @{grid-float-breakpoint})"; 9 | 10 | body { 11 | // Fix horizontal scrolling on iOS devices. 12 | // https://www.drupal.org/node/1870076 13 | position: relative; 14 | 15 | &.navbar-is-static-top { 16 | margin-top: 0; 17 | } 18 | &.navbar-is-fixed-top { 19 | margin-top: (@navbar-height + (@grid-gutter-width / 2)); 20 | } 21 | &.navbar-is-fixed-bottom { 22 | padding-bottom: (@navbar-height + (@grid-gutter-width / 2)); 23 | } 24 | 25 | @media @tablet { 26 | margin-top: (@grid-gutter-width / 2); 27 | } 28 | @media @mobile { 29 | &.toolbar-vertical { 30 | &.navbar-is-fixed-top, 31 | &.navbar-is-fixed-bottom { 32 | .toolbar-bar { 33 | position: fixed; 34 | } 35 | header { 36 | z-index: 500; 37 | } 38 | } 39 | // Default toolbar fixed height value. 40 | // @see core/modules/toolbar/css/toolbar.icons.theme.css@261 41 | &.navbar-is-fixed-top header { 42 | top: 39px; 43 | } 44 | } 45 | } 46 | } 47 | .navbar.container { 48 | @media @tablet { 49 | max-width: ((@container-sm - @grid-gutter-width)); 50 | } 51 | @media @normal { 52 | max-width: ((@container-md - @grid-gutter-width)); 53 | } 54 | @media @wide { 55 | max-width: ((@container-lg - @grid-gutter-width)); 56 | } 57 | } 58 | 59 | // Branding. 60 | .navbar .logo { 61 | margin-right: -(@grid-gutter-width / 2); 62 | padding-left: (@grid-gutter-width / 2); 63 | padding-right: (@grid-gutter-width / 2); 64 | @media @grid-breakpoint { 65 | margin-right: 0; 66 | padding-left: 0; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/node.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Node styling. 3 | */ 4 | 5 | .is-unpublished { 6 | background-color: #fff4f4; 7 | } 8 | 9 | .node-preview-container { 10 | margin-top: -(@grid-gutter-width / 2); 11 | } 12 | 13 | .node-preview-form-select { 14 | padding: (@grid-gutter-width / 2); 15 | } 16 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/panel.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Panel styling. 3 | */ 4 | .panel-title { 5 | display: block; 6 | margin: -10px -15px; 7 | padding: 10px 15px; 8 | &, &:hover, &:focus, &:hover:focus { 9 | color: inherit; 10 | } 11 | &:focus, &:hover { 12 | text-decoration: none; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/progress-bar.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Progress bar styles. 3 | */ 4 | .progress-wrapper { 5 | margin-bottom: @form-group-margin-bottom; 6 | 7 | &:last-child .progress { 8 | margin-bottom: 5px; 9 | } 10 | 11 | .message { 12 | font-weight: 700; 13 | margin-bottom: 5px; 14 | } 15 | 16 | .percentage, 17 | .progress-label { 18 | font-size: @font-size-small; 19 | } 20 | 21 | .progress-bar { 22 | min-width: 2em; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/table-drag.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Table drag styles. 3 | */ 4 | .tabledrag-toggle-weight { 5 | float: right; 6 | margin: 1px 2px 1px 10px; 7 | } 8 | .tabledrag-changed-warning { 9 | margin: 0; 10 | overflow: hidden; 11 | } 12 | .tabledrag-handle { 13 | color: @gray-light; 14 | cursor: move; 15 | float: left; 16 | font-size: 125%; 17 | line-height: 1; 18 | margin: -10px 0 0 -10px; 19 | padding: 10px; 20 | &:hover, &:focus { 21 | color: @brand-primary; 22 | } 23 | } 24 | .indentation { 25 | float: left; /* LTR */ 26 | height: 1.7em; 27 | margin: -0.4em 0.2em -0.4em -0.4em; /* LTR */ 28 | padding: 0.42em 0 0.42em 0.6em; /* LTR */ 29 | width: 20px; 30 | [dir="rtl"] & { 31 | float: right; 32 | margin: -0.4em -0.4em -0.4em 0.2em; 33 | padding: 0.42em 0.6em 0.42em 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/tabs.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Tabs and local action styles. 3 | */ 4 | .local-actions { 5 | margin: 10px 0 10px -5px; 6 | } 7 | .tabs--secondary { 8 | margin: 10px 0 5px; 9 | } 10 | 11 | /** 12 | * Missing Bootstrap 2 tab styling. 13 | * @see http://stackoverflow.com/questions/18432577/stacked-tabs-in-bootstrap-3 14 | * @see http://bootply.com/74926 15 | */ 16 | .tabbable { 17 | margin-bottom: 20px; 18 | } 19 | .tabs-below, .tabs-left, .tabs-right { 20 | > .nav-tabs { 21 | border-bottom: 0; 22 | .summary { 23 | color: @nav-disabled-link-color; 24 | font-size: @font-size-small; 25 | } 26 | } 27 | } 28 | .tab-pane > .panel-heading { 29 | display: none; 30 | } 31 | .tab-content > .active { 32 | display: block; 33 | } 34 | 35 | // Below. 36 | .tabs-below { 37 | > .nav-tabs { 38 | border-top: 1px solid @nav-tabs-border-color; 39 | > li { 40 | margin-top: -1px; 41 | margin-bottom: 0; 42 | > a { 43 | border-radius: 0 0 @border-radius-base @border-radius-base; 44 | &:hover, 45 | &:focus { 46 | border-top-color: @nav-tabs-border-color; 47 | border-bottom-color: transparent; 48 | } 49 | } 50 | } 51 | > .active { 52 | > a, 53 | > a:hover, 54 | > a:focus { 55 | border-color: transparent @nav-tabs-border-color @nav-tabs-border-color @nav-tabs-border-color; 56 | } 57 | } 58 | } 59 | } 60 | 61 | // Left and right tabs. 62 | .tabs-left, 63 | .tabs-right { 64 | > .nav-tabs { 65 | padding-bottom: 20px; 66 | width: 220px; 67 | > li { 68 | float: none; 69 | &:focus { 70 | outline: 0; 71 | } 72 | > a { 73 | margin-right: 0; 74 | margin-bottom: 3px; 75 | &:focus { 76 | outline: 0; 77 | } 78 | } 79 | } 80 | } 81 | > .tab-content { 82 | border-radius: 0 @border-radius-base @border-radius-base @border-radius-base; 83 | border: 1px solid @nav-tabs-border-color; 84 | box-shadow: 0 1px 1px rgba(0,0,0,.05); 85 | overflow: hidden; 86 | padding: 10px 15px; 87 | } 88 | } 89 | 90 | // Left tabs. 91 | .tabs-left { 92 | > .nav-tabs { 93 | float: left; 94 | margin-right: -1px; 95 | > li > a { 96 | border-radius: @border-radius-base 0 0 @border-radius-base; 97 | &:hover, 98 | &:focus { 99 | border-color: @nav-tabs-link-hover-border-color @nav-tabs-border-color @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color; 100 | } 101 | } 102 | > .active > a, 103 | > .active > a:hover, 104 | > .active > a:focus { 105 | border-color: @nav-tabs-border-color transparent @nav-tabs-border-color @nav-tabs-border-color; 106 | box-shadow: -1px 1px 1px rgba(0,0,0,.05); 107 | } 108 | } 109 | } 110 | 111 | // Right tabs. 112 | .tabs-right { 113 | > .nav-tabs { 114 | float: right; 115 | margin-left: -1px; 116 | > li > a { 117 | border-radius: 0 @border-radius-base @border-radius-base 0; 118 | &:hover, 119 | &:focus { 120 | border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color; 121 | box-shadow: 1px 1px 1px rgba(0,0,0,.05); 122 | } 123 | } 124 | > .active > a, 125 | > .active > a:hover, 126 | > .active > a:focus { 127 | border-color: @nav-tabs-border-color @nav-tabs-border-color @nav-tabs-border-color transparent; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/component/toolbar.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Toolbar module styling. 3 | */ 4 | 5 | @toolbar-width: 240px; 6 | 7 | body.toolbar-fixed { 8 | 9 | // Fix z-index. 10 | .toolbar-oriented .toolbar-bar { 11 | z-index: (@zindex-navbar-fixed + 1); 12 | } 13 | 14 | .navbar-fixed-top { 15 | top: 39px; 16 | } 17 | 18 | // Horizontal. 19 | &.toolbar-horizontal.toolbar-tray-open .navbar-fixed-top { 20 | top: 79px; 21 | } 22 | 23 | // Vertical Open. 24 | &.toolbar-vertical.toolbar-tray-open { 25 | 26 | .navbar-fixed-top { 27 | left: @toolbar-width; 28 | } 29 | 30 | &.toolbar-fixed { 31 | margin-left: @toolbar-width; 32 | 33 | .toolbar-tray { 34 | padding-bottom: 40px; 35 | &, > .toolbar-lining:before { 36 | width: @toolbar-width; 37 | } 38 | } 39 | 40 | } 41 | 42 | } 43 | 44 | } 45 | 46 | // Remove flicking. 47 | body.toolbar-loading { 48 | 49 | margin-top: 0; 50 | 51 | &.toolbar { 52 | margin-bottom: (@grid-gutter-width / 2); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/drupal-bootstrap/jquery-ui/autocomplete.less: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery UI autocomplete widget style overrides. 3 | * 4 | * @todo Remove once jQuery UI is no longer used? 5 | */ 6 | 7 | // Mimics .dropdown-menu styles. 8 | .ui-autocomplete { 9 | background: @dropdown-bg; 10 | background-clip: padding-box; 11 | border: 1px solid @dropdown-fallback-border; 12 | border: 1px solid @dropdown-border; 13 | border-radius: @border-radius-base; 14 | box-shadow: 0 6px 12px rgba(0,0,0,.175); 15 | color: inherit; 16 | font-family: @font-family-base; 17 | font-size: @font-size-base; 18 | list-style: none; 19 | min-width: 160px; 20 | padding: 5px 0; 21 | text-align: left; 22 | z-index: @zindex-dropdown; 23 | 24 | .ui-menu-item { 25 | border: 0; 26 | border-radius: 0; 27 | clear: both; 28 | color: @dropdown-link-color; 29 | cursor: pointer; 30 | display: block; 31 | font-weight: normal; 32 | line-height: @line-height-base; 33 | margin: 0; 34 | outline: 0; 35 | padding: 3px 20px; 36 | text-decoration: none; 37 | white-space: nowrap; 38 | 39 | &.ui-state-hover { 40 | background: @dropdown-link-hover-bg; 41 | color: @dropdown-link-hover-color; 42 | } 43 | 44 | &.ui-state-active, &.ui-state-focus { 45 | background: @dropdown-link-active-bg; 46 | color: @dropdown-link-active-color; 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/style.less: -------------------------------------------------------------------------------- 1 | // Bootstrap Framework. 2 | @import 'bootstrap'; 3 | 4 | // Drupal Bootstrap Styles. 5 | @import 'drupal-bootstrap'; 6 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/less/variable-overrides.less: -------------------------------------------------------------------------------- 1 | // Variable Overrides. 2 | // 3 | // Modify this file to override the Bootstrap Framework variables. You can copy 4 | // these variables directly from ../bootstrap/less/variables.less to this file. 5 | 6 | // Set the directory for the theme root used to locate images, fonts etc. 7 | @theme-root: '..'; 8 | 9 | // Set other directories relative to theme root. 10 | @image-path: '@{theme-root}/images'; 11 | @icon-font-path: '@{theme-root}/bootstrap/fonts/'; 12 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/README.md: -------------------------------------------------------------------------------- 1 | # [Drupal Bootstrap Styles] (SASS) 2 | 3 | These files are intended to be used within a [Drupal Bootstrap] based sub-theme. 4 | 5 | ## Default Variables 6 | 7 | The `_default-variables.scss` file is generally where you will spend the 8 | majority of your time providing any default variables that should be 9 | used by the [Bootstrap Framework] instead of its own. 10 | 11 | > **WARNING:** It is highly recommended that you only put the default variables 12 | you intend to override in this file. Copying and pasting the entire 13 | `_variables.scss` file provided by the [Bootstrap Framework] is redundant and 14 | only makes it more difficult to determine which variable has been overridden 15 | when the need to update or upgrade to newer versions in the future arises. 16 | 17 | ## Drupal Bootstrap 18 | 19 | The `_drupal-bootstrap.scss` file contains various styles to properly integrate 20 | [Drupal Bootstrap] with the [Bootstrap Framework]. 21 | 22 | ## Compiling 23 | 24 | The `style.scss` file is the main compiling entry point. It is the glue that 25 | combines: `_default-variables.scss`, [Bootstrap Framework Source Files] and the 26 | `_drupal-bootstrap.scss` files together. Generally, you will not need to modify 27 | this file unless you need to add or remove files to be imported. 28 | 29 | Your [Sass] compiler should use this file as it's "source" and its compiled 30 | output "destination" should be `../css/style.css` (overwrites the file provided 31 | by the base-theme starterkit). 32 | 33 | [Bootstrap Framework]: https://getbootstrap.com/docs/3.4/ 34 | [Bootstrap Framework Source Files]: https://github.com/twbs/bootstrap-sass 35 | [Drupal Bootstrap]: https://www.drupal.org/project/bootstrap 36 | [Drupal Bootstrap Styles]: https://github.com/unicorn-fail/drupal-bootstrap-styles 37 | [Sass]: http://sass-lang.com 38 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/_default-variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Default Variables. 3 | * 4 | * Modify this file to provide default Bootstrap Framework variables. The 5 | * Bootstrap Framework will not override any variables defined here because it 6 | * uses the `!default` flag which will only set their default if not already 7 | * defined here. 8 | * 9 | * You can copy existing variables directly from the following file: 10 | * ./THEMENAME/bootstrap/assets/stylesheets/bootstrap/_variables.scss 11 | */ 12 | 13 | // Set the directory for the theme root used to locate images, fonts etc. 14 | $theme-root: '..'; 15 | 16 | // Set other directories relative to theme root. 17 | $image-path: '#{$theme-root}/images'; 18 | $icon-font-path: '#{$theme-root}/bootstrap/assets/fonts/bootstrap/'; 19 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/_drupal-bootstrap.scss: -------------------------------------------------------------------------------- 1 | @import "drupal-bootstrap/component/alert"; 2 | @import "drupal-bootstrap/component/ajax"; 3 | @import "drupal-bootstrap/component/field"; 4 | @import "drupal-bootstrap/component/file"; 5 | @import "drupal-bootstrap/component/filter"; 6 | @import "drupal-bootstrap/component/form"; 7 | @import "drupal-bootstrap/component/icon"; 8 | @import "drupal-bootstrap/component/navbar"; 9 | @import "drupal-bootstrap/component/node"; 10 | @import "drupal-bootstrap/component/panel"; 11 | @import "drupal-bootstrap/component/progress-bar"; 12 | @import "drupal-bootstrap/component/table-drag"; 13 | @import "drupal-bootstrap/component/tabs"; 14 | @import "drupal-bootstrap/component/toolbar"; 15 | 16 | // jQuery UI style overrides. 17 | @import "drupal-bootstrap/jquery-ui/autocomplete"; 18 | 19 | ol, ul { 20 | padding-left: 1.5em; 21 | .popover &:last-child { 22 | margin-bottom: 0; 23 | } 24 | } 25 | 26 | // Page header. 27 | .page-header { 28 | margin-top: 0; 29 | } 30 | 31 | // Footer. 32 | .footer { 33 | margin-top: 45px; 34 | padding-top: 35px; 35 | padding-bottom: 36px; 36 | border-top: 1px solid #E5E5E5; 37 | } 38 | 39 | // Paragraphs. 40 | p:last-child, 41 | .form-group:last-child, 42 | .panel:last-child { 43 | margin-bottom: 0; 44 | } 45 | 46 | // Help region. 47 | .region-help { 48 | > .glyphicon { 49 | font-size: $font-size-large; 50 | float: left; 51 | margin: -0.05em 0.5em 0 0; 52 | } 53 | .block { 54 | overflow: hidden; 55 | } 56 | } 57 | 58 | .help-block, .control-group .help-inline { 59 | color: $gray-light; 60 | font-size: 12px; 61 | margin: 5px 0 10px; 62 | padding: 0; 63 | &:first-child { 64 | margin-top: 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_ajax.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * AJAX (throbber) styling. 3 | */ 4 | 5 | // $todo This should probably be it's own mixin/component? 6 | @keyframes glyphicon-spin { 7 | 0% { transform: rotate(0deg); } 8 | 100% { transform: rotate(359deg); } 9 | } 10 | .glyphicon-spin { 11 | display: inline-block; 12 | animation: glyphicon-spin 1s infinite linear; 13 | } 14 | a .glyphicon-spin { 15 | display: inline-block; 16 | text-decoration: none; 17 | } 18 | 19 | html.js { 20 | .btn .ajax-throbber { 21 | margin-left: .5em; 22 | margin-right: -.25em; 23 | } 24 | 25 | .form-item .input-group-addon { 26 | .glyphicon { 27 | color: $gray-light; 28 | opacity: .5; 29 | transition: 150ms color, 150ms opacity; 30 | &.glyphicon-spin { 31 | color: $brand-primary; 32 | opacity: 1; 33 | } 34 | } 35 | .input-group-addon { 36 | background-color: rgb(255, 255, 255); 37 | } 38 | } 39 | 40 | // Hide empty wrappers from AJAX/Field APIs. 41 | .ajax-new-content:empty { 42 | display: none !important; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_alert.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Alert styling. 3 | */ 4 | .alert-sm { 5 | padding: 5px 10px; 6 | } 7 | 8 | // Treat all links inside alert as .alert-link 9 | .alert a { 10 | font-weight: $alert-link-font-weight; 11 | } 12 | .alert-success { 13 | a, a:hover, a:focus { 14 | color: darken($alert-success-text, 10%); 15 | } 16 | } 17 | .alert-info { 18 | a, a:hover, a:focus { 19 | color: darken($alert-info-text, 10%); 20 | } 21 | } 22 | .alert-warning { 23 | a, a:hover, a:focus { 24 | color: darken($alert-warning-text, 10%); 25 | } 26 | } 27 | .alert-danger { 28 | a, a:hover, a:focus { 29 | color: darken($alert-danger-text, 10%); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_field.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * $file 3 | * Visual styles for fields. 4 | */ 5 | 6 | .field--label { 7 | font-weight: bold; 8 | } 9 | .field--label-inline .field--label, 10 | .field--label-inline .field--items { 11 | float: left; /*LTR*/ 12 | } 13 | .field--label-inline .field--label, 14 | .field--label-inline > .field--item, 15 | .field--label-inline .field--items { 16 | padding-right: 0.5em; 17 | } 18 | [dir="rtl"] .field--label-inline .field--label, 19 | [dir="rtl"] .field--label-inline .field--items { 20 | padding-left: 0.5em; 21 | padding-right: 0; 22 | float: right; /*RTL*/ 23 | } 24 | .field--label-inline .field--label::after { 25 | content: ':'; 26 | } 27 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_file.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * File (and Image Widget) styles. 3 | */ 4 | .file { 5 | display: table; 6 | font-size: 75%; 7 | font-weight: 700; 8 | margin: 5px 0; 9 | width: 100%; 10 | > span { 11 | background: #fff; 12 | color: $brand-primary; 13 | border-bottom: 1px solid $input-border; 14 | border-top: 1px solid $input-border; 15 | &:first-child { 16 | border-left: 1px solid $input-border; 17 | } 18 | &:last-child { 19 | border-right: 1px solid $input-border; 20 | } 21 | } 22 | > .tabledrag-changed { 23 | &, &:last-child { 24 | border: 1px solid darken($alert-warning-border, 5%); 25 | } 26 | background: $alert-warning-bg; 27 | border-radius: 0; 28 | color: $alert-warning-text; 29 | display: table-cell; 30 | padding: 0 1em; 31 | top: 0; 32 | vertical-align: middle; 33 | border-left: 1px solid inherit; 34 | } 35 | } 36 | .file-icon { 37 | display: table-cell; 38 | font-size: 150%; 39 | padding: .25em .5em; 40 | text-align: center; 41 | vertical-align: middle; 42 | } 43 | .file-link { 44 | display: table-cell; 45 | vertical-align: middle; 46 | width: 100%; 47 | a, a:hover, a:focus, a:active { 48 | color: inherit; 49 | } 50 | } 51 | .file-size { 52 | display: table-cell; 53 | padding: 0 1em; 54 | text-align: right; 55 | white-space: pre; 56 | vertical-align: middle; 57 | } 58 | 59 | .image-widget.row { 60 | overflow: hidden; 61 | } 62 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_filter.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Filter styles. 3 | */ 4 | 5 | // Mimic .panel-default styling. 6 | .filter-wrapper { 7 | background-color: $panel-bg; 8 | border: 1px solid $panel-default-border; 9 | border-top: 0; 10 | border-radius: 0 0 $panel-border-radius $panel-border-radius; 11 | box-shadow: 0 1px 1px rgba(0,0,0,.05); 12 | margin-bottom: 0; 13 | padding: 10px; 14 | height: 51px; 15 | @extend .clearfix; 16 | } 17 | .filter-help { 18 | float: right; 19 | line-height: 1; 20 | margin: .5em 0 0; 21 | } 22 | 23 | // Full list page. 24 | .nav.nav-tabs.filter-formats { 25 | margin-bottom: 15px; 26 | } 27 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_form.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Form styles. 3 | */ 4 | 5 | // Checkboxes / radio buttons. 6 | .checkbox, .radio { 7 | // Remove top/bottom margins when in table context, because this is most 8 | // likely a tableselect checkbox element. 9 | table &.form-no-label { 10 | margin-bottom: 0; 11 | margin-top: 0; 12 | } 13 | } 14 | 15 | // Browsers do not recognize pseudo :after selectors, we must create a wrapper 16 | // around the select element to style it properly. 17 | // $see http://stackoverflow.com/q/21103542 18 | .select-wrapper { 19 | display: inline-block; 20 | position: relative; 21 | width: 100%; 22 | .form-inline & { 23 | width: auto; 24 | } 25 | .input-group & { 26 | display: table-cell; 27 | // Reset rounded corners 28 | &:first-child .form-control:first-child { 29 | @include border-left-radius($border-radius-base); 30 | } 31 | &:last-child .form-control:first-child { 32 | @include border-right-radius($border-radius-base); 33 | } 34 | } 35 | select { 36 | -webkit-appearance: none; 37 | -moz-appearance: none; 38 | appearance: none; 39 | line-height: 1; 40 | padding-right: 2em; 41 | &::-ms-expand { 42 | opacity: 0; 43 | } 44 | } 45 | &:after { 46 | color: $brand-primary; 47 | content: '▼'; 48 | font-style: normal; 49 | font-weight: 400; 50 | line-height: 1; 51 | margin-top: -.5em; 52 | padding-right: .5em; 53 | pointer-events: none; 54 | position: absolute; 55 | right: 0; 56 | top: 50%; 57 | z-index: 10; 58 | 59 | // Use a more stylish icon if the theme uses glyphicons. 60 | .has-glyphicons & { 61 | -webkit-font-smoothing: antialiased; 62 | -moz-osx-font-smoothing: grayscale; 63 | content: '\e114'; // .glyphicon-chevron-down 64 | display: inline-block; 65 | font-family: 'Glyphicons Halflings'; 66 | } 67 | .has-error & { 68 | color: $state-danger-text; 69 | } 70 | .has-success & { 71 | color: $state-success-text; 72 | } 73 | .has-warning & { 74 | color: $state-warning-text; 75 | } 76 | } 77 | } 78 | 79 | // Use CSS/SVG image for required mark. 80 | // @see https://www.drupal.org/node/2152217 81 | // @see https://www.drupal.org/node/2274631 82 | .form-required:after { 83 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI0VFMDAwMCIgIGQ9Ik0wLDcuNTYybDEuMTE0LTMuNDM4YzIuNTY1LDAuOTA2LDQuNDMsMS42ODgsNS41OSwyLjM1QzYuMzk4LDMuNTUzLDYuMjM3LDEuNTQ0LDYuMjIsMC40NDdoMy41MTEgYy0wLjA1LDEuNTk3LTAuMjM0LDMuNi0wLjU1OCw2LjAwM2MxLjY2NC0wLjgzOCwzLjU2Ni0xLjYxMyw1LjcxNC0yLjMyNUwxNiw3LjU2MmMtMi4wNSwwLjY3OC00LjA2LDEuMTMxLTYuMDI4LDEuMzU2IGMwLjk4NCwwLjg1NiwyLjM3MiwyLjM4MSw0LjE2Niw0LjU3NWwtMi45MDYsMi4wNTljLTAuOTM1LTEuMjc0LTIuMDQxLTMuMDA5LTMuMzE2LTUuMjA2Yy0xLjE5NCwyLjI3NS0yLjI0NCw0LjAxMy0zLjE0Nyw1LjIwNiBsLTIuODU2LTIuMDU5YzEuODcyLTIuMzA3LDMuMjExLTMuODMyLDQuMDE3LTQuNTc1QzMuODQ5LDguNTE2LDEuODcyLDguMDYyLDAsNy41NjIiLz48L3N2Zz4K); 84 | background-size: 10px 7px; 85 | content:""; 86 | display: inline-block; 87 | vertical-align: super; 88 | line-height:1; 89 | height: 7px; 90 | width: 10px; 91 | } 92 | 93 | // Form action buttons. 94 | .form-actions { 95 | .btn, .btn-group { 96 | margin-right: 10px; 97 | } 98 | .btn-group { 99 | .btn { 100 | margin-right: 0; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_icon.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Icon styles. 3 | */ 4 | a { 5 | &.icon-before .glyphicon, 6 | & .glyphicon.icon-before { 7 | margin-right: .25em; 8 | } 9 | &.icon-after .glyphicon, 10 | & .glyphicon.icon-after { 11 | margin-left: .25em; 12 | } 13 | } 14 | 15 | .btn { 16 | &.icon-before .glyphicon, 17 | & .glyphicon.icon-before { 18 | margin-left: -.25em; 19 | margin-right: .25em; 20 | } 21 | &.icon-after .glyphicon, 22 | & .glyphicon.icon-after { 23 | margin-left: .25em; 24 | margin-right: -.25em; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_navbar.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Navbar styling. 3 | */ 4 | $mobile: "screen and (max-width: #{$screen-xs-max})"; 5 | $tablet: "screen and (min-width: #{$screen-sm-min})"; 6 | $normal: "screen and (min-width: #{$screen-md-min})"; 7 | $wide: "screen and (min-width: #{$screen-lg-min})"; 8 | $grid-breakpoint: "screen and (min-width: #{$grid-float-breakpoint})"; 9 | 10 | body { 11 | // Fix horizontal scrolling on iOS devices. 12 | // https://www.drupal.org/node/1870076 13 | position: relative; 14 | 15 | &.navbar-is-static-top { 16 | margin-top: 0; 17 | } 18 | &.navbar-is-fixed-top { 19 | margin-top: ($navbar-height + ($grid-gutter-width / 2)); 20 | } 21 | &.navbar-is-fixed-bottom { 22 | padding-bottom: ($navbar-height + ($grid-gutter-width / 2)); 23 | } 24 | 25 | @media #{$tablet} { 26 | margin-top: ($grid-gutter-width / 2); 27 | } 28 | @media #{$mobile} { 29 | &.toolbar-vertical { 30 | &.navbar-is-fixed-top, 31 | &.navbar-is-fixed-bottom { 32 | .toolbar-bar { 33 | position: fixed; 34 | } 35 | header { 36 | z-index: 500; 37 | } 38 | } 39 | // Default toolbar fixed height value. 40 | // @see core/modules/toolbar/css/toolbar.icons.theme.css@261 41 | &.navbar-is-fixed-top header { 42 | top: 39px; 43 | } 44 | } 45 | } 46 | } 47 | .navbar.container { 48 | @media #{$tablet} { 49 | max-width: (($container-sm - $grid-gutter-width)); 50 | } 51 | @media #{$normal} { 52 | max-width: (($container-md - $grid-gutter-width)); 53 | } 54 | @media #{$wide} { 55 | max-width: (($container-lg - $grid-gutter-width)); 56 | } 57 | } 58 | 59 | // Branding. 60 | .navbar .logo { 61 | margin-right: -($grid-gutter-width / 2); 62 | padding-left: ($grid-gutter-width / 2); 63 | padding-right: ($grid-gutter-width / 2); 64 | @media #{$grid-breakpoint} { 65 | margin-right: 0; 66 | padding-left: 0; 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_node.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Node styling. 3 | */ 4 | 5 | .is-unpublished { 6 | background-color: #fff4f4; 7 | } 8 | 9 | .node-preview-container { 10 | margin-top: -($grid-gutter-width / 2); 11 | } 12 | 13 | .node-preview-form-select { 14 | padding: ($grid-gutter-width / 2); 15 | } 16 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_panel.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Panel styling. 3 | */ 4 | .panel-title { 5 | display: block; 6 | margin: -10px -15px; 7 | padding: 10px 15px; 8 | &, &:hover, &:focus, &:hover:focus { 9 | color: inherit; 10 | } 11 | &:focus, &:hover { 12 | text-decoration: none; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_progress-bar.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Progress bar styles. 3 | */ 4 | .progress-wrapper { 5 | margin-bottom: $form-group-margin-bottom; 6 | 7 | &:last-child .progress { 8 | margin-bottom: 5px; 9 | } 10 | 11 | .message { 12 | font-weight: 700; 13 | margin-bottom: 5px; 14 | } 15 | 16 | .percentage, 17 | .progress-label { 18 | font-size: $font-size-small; 19 | } 20 | 21 | .progress-bar { 22 | min-width: 2em; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_table-drag.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Table drag styles. 3 | */ 4 | .tabledrag-toggle-weight { 5 | float: right; 6 | margin: 1px 2px 1px 10px; 7 | } 8 | .tabledrag-changed-warning { 9 | margin: 0; 10 | overflow: hidden; 11 | } 12 | .tabledrag-handle { 13 | color: $gray-light; 14 | cursor: move; 15 | float: left; 16 | font-size: 125%; 17 | line-height: 1; 18 | margin: -10px 0 0 -10px; 19 | padding: 10px; 20 | &:hover, &:focus { 21 | color: $brand-primary; 22 | } 23 | } 24 | .indentation { 25 | float: left; /* LTR */ 26 | height: 1.7em; 27 | margin: -0.4em 0.2em -0.4em -0.4em; /* LTR */ 28 | padding: 0.42em 0 0.42em 0.6em; /* LTR */ 29 | width: 20px; 30 | [dir="rtl"] & { 31 | float: right; 32 | margin: -0.4em -0.4em -0.4em 0.2em; 33 | padding: 0.42em 0.6em 0.42em 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_tabs.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Tabs and local action styles. 3 | */ 4 | .local-actions { 5 | margin: 10px 0 10px -5px; 6 | } 7 | .tabs--secondary { 8 | margin: 10px 0 5px; 9 | } 10 | 11 | /** 12 | * Missing Bootstrap 2 tab styling. 13 | * $see http://stackoverflow.com/questions/18432577/stacked-tabs-in-bootstrap-3 14 | * $see http://bootply.com/74926 15 | */ 16 | .tabbable { 17 | margin-bottom: 20px; 18 | } 19 | .tabs-below, .tabs-left, .tabs-right { 20 | > .nav-tabs { 21 | border-bottom: 0; 22 | .summary { 23 | color: $nav-disabled-link-color; 24 | font-size: $font-size-small; 25 | } 26 | } 27 | } 28 | .tab-pane > .panel-heading { 29 | display: none; 30 | } 31 | .tab-content > .active { 32 | display: block; 33 | } 34 | 35 | // Below. 36 | .tabs-below { 37 | > .nav-tabs { 38 | border-top: 1px solid $nav-tabs-border-color; 39 | > li { 40 | margin-top: -1px; 41 | margin-bottom: 0; 42 | > a { 43 | border-radius: 0 0 $border-radius-base $border-radius-base; 44 | &:hover, 45 | &:focus { 46 | border-top-color: $nav-tabs-border-color; 47 | border-bottom-color: transparent; 48 | } 49 | } 50 | } 51 | > .active { 52 | > a, 53 | > a:hover, 54 | > a:focus { 55 | border-color: transparent $nav-tabs-border-color $nav-tabs-border-color $nav-tabs-border-color; 56 | } 57 | } 58 | } 59 | } 60 | 61 | // Left and right tabs. 62 | .tabs-left, 63 | .tabs-right { 64 | > .nav-tabs { 65 | padding-bottom: 20px; 66 | width: 220px; 67 | > li { 68 | float: none; 69 | &:focus { 70 | outline: 0; 71 | } 72 | > a { 73 | margin-right: 0; 74 | margin-bottom: 3px; 75 | &:focus { 76 | outline: 0; 77 | } 78 | } 79 | } 80 | } 81 | > .tab-content { 82 | border-radius: 0 $border-radius-base $border-radius-base $border-radius-base; 83 | border: 1px solid $nav-tabs-border-color; 84 | box-shadow: 0 1px 1px rgba(0,0,0,.05); 85 | overflow: hidden; 86 | padding: 10px 15px; 87 | } 88 | } 89 | 90 | // Left tabs. 91 | .tabs-left { 92 | > .nav-tabs { 93 | float: left; 94 | margin-right: -1px; 95 | > li > a { 96 | border-radius: $border-radius-base 0 0 $border-radius-base; 97 | &:hover, 98 | &:focus { 99 | border-color: $nav-tabs-link-hover-border-color $nav-tabs-border-color $nav-tabs-link-hover-border-color $nav-tabs-link-hover-border-color; 100 | } 101 | } 102 | > .active > a, 103 | > .active > a:hover, 104 | > .active > a:focus { 105 | border-color: $nav-tabs-border-color transparent $nav-tabs-border-color $nav-tabs-border-color; 106 | box-shadow: -1px 1px 1px rgba(0,0,0,.05); 107 | } 108 | } 109 | } 110 | 111 | // Right tabs. 112 | .tabs-right { 113 | > .nav-tabs { 114 | float: right; 115 | margin-left: -1px; 116 | > li > a { 117 | border-radius: 0 $border-radius-base $border-radius-base 0; 118 | &:hover, 119 | &:focus { 120 | border-color: $nav-tabs-link-hover-border-color $nav-tabs-link-hover-border-color $nav-tabs-link-hover-border-color $nav-tabs-border-color; 121 | box-shadow: 1px 1px 1px rgba(0,0,0,.05); 122 | } 123 | } 124 | > .active > a, 125 | > .active > a:hover, 126 | > .active > a:focus { 127 | border-color: $nav-tabs-border-color $nav-tabs-border-color $nav-tabs-border-color transparent; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/component/_toolbar.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Toolbar module styling. 3 | */ 4 | 5 | $toolbar-width: 240px; 6 | 7 | body.toolbar-fixed { 8 | 9 | // Fix z-index. 10 | .toolbar-oriented .toolbar-bar { 11 | z-index: ($zindex-navbar-fixed + 1); 12 | } 13 | 14 | .navbar-fixed-top { 15 | top: 39px; 16 | } 17 | 18 | // Horizontal. 19 | &.toolbar-horizontal.toolbar-tray-open .navbar-fixed-top { 20 | top: 79px; 21 | } 22 | 23 | // Vertical Open. 24 | &.toolbar-vertical.toolbar-tray-open { 25 | 26 | .navbar-fixed-top { 27 | left: $toolbar-width; 28 | } 29 | 30 | &.toolbar-fixed { 31 | margin-left: $toolbar-width; 32 | 33 | .toolbar-tray { 34 | padding-bottom: 40px; 35 | &, > .toolbar-lining:before { 36 | width: $toolbar-width; 37 | } 38 | } 39 | 40 | } 41 | 42 | } 43 | 44 | } 45 | 46 | // Remove flicking. 47 | body.toolbar-loading { 48 | 49 | margin-top: 0; 50 | 51 | &.toolbar { 52 | margin-bottom: ($grid-gutter-width / 2); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/drupal-bootstrap/jquery-ui/_autocomplete.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery UI autocomplete widget style overrides. 3 | * 4 | * $todo Remove once jQuery UI is no longer used? 5 | */ 6 | 7 | // Mimics .dropdown-menu styles. 8 | .ui-autocomplete { 9 | background: $dropdown-bg; 10 | background-clip: padding-box; 11 | border: 1px solid $dropdown-fallback-border; 12 | border: 1px solid $dropdown-border; 13 | border-radius: $border-radius-base; 14 | box-shadow: 0 6px 12px rgba(0,0,0,.175); 15 | color: inherit; 16 | font-family: $font-family-base; 17 | font-size: $font-size-base; 18 | list-style: none; 19 | min-width: 160px; 20 | padding: 5px 0; 21 | text-align: left; 22 | z-index: $zindex-dropdown; 23 | 24 | .ui-menu-item { 25 | border: 0; 26 | border-radius: 0; 27 | clear: both; 28 | color: $dropdown-link-color; 29 | cursor: pointer; 30 | display: block; 31 | font-weight: normal; 32 | line-height: $line-height-base; 33 | margin: 0; 34 | outline: 0; 35 | padding: 3px 20px; 36 | text-decoration: none; 37 | white-space: nowrap; 38 | 39 | &.ui-state-hover { 40 | background: $dropdown-link-hover-bg; 41 | color: $dropdown-link-hover-color; 42 | } 43 | 44 | &.ui-state-active, &.ui-state-focus { 45 | background: $dropdown-link-active-bg; 46 | color: $dropdown-link-active-color; 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/3.x.x/8.x-3.x/scss/style.scss: -------------------------------------------------------------------------------- 1 | // Default variables. 2 | @import "default-variables"; 3 | 4 | // Bootstrap Framework. 5 | @import '../bootstrap/assets/stylesheets/bootstrap'; 6 | 7 | // Base-theme overrides. 8 | @import 'drupal-bootstrap'; 9 | -------------------------------------------------------------------------------- /src/3.x.x/missing-variables.less: -------------------------------------------------------------------------------- 1 | // Add some default variables that may not be available. 2 | @form-group-margin-bottom: 15px; 3 | @screen-sm-min: @screen-sm; 4 | @screen-md-min: @screen-md; 5 | @screen-lg-min: @screen-lg; 6 | @container-sm: @container-tablet; 7 | @container-md: @container-desktop; 8 | @container-large-desktop: (1140px + @grid-gutter-width); 9 | @container-lg: @container-large-desktop; 10 | --------------------------------------------------------------------------------