├── .editorconfig ├── .eslintrc.js ├── .github └── workflows │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── rollup.config.js └── src ├── index.js └── style.scss /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | 8 | [*.js] 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_size = 2 12 | 13 | [{*.css,*.scss}] 14 | trim_trailing_whitespace = true 15 | indent_size = 2 16 | 17 | [*.md] 18 | max_line_length = off 19 | trim_trailing_whitespace = false 20 | indent_size = 2 21 | 22 | [{*.txt,*.json,*.xml}] 23 | insert_final_newline = false 24 | indent_size = 2 25 | 26 | [*.yml] 27 | insert_final_newline = true 28 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser" : true, 4 | "commonjs": true, 5 | "es6" : true, 6 | "node" : true 7 | }, 8 | "extends": "eslint:recommended", 9 | "parserOptions": { 10 | "ecmaVersion": 6, 11 | "sourceType" : "module" 12 | }, 13 | "plugins": [ 14 | ], 15 | "rules": { 16 | "array-bracket-spacing" : ["error", "never"], 17 | "array-callback-return" : ["error"], 18 | "block-scoped-var" : ["error"], 19 | "block-spacing" : ["error", "always"], 20 | "curly" : ["error"], 21 | "dot-notation" : ["error"], 22 | "eqeqeq" : ["error"], 23 | "indent" : ["error", 2], 24 | "linebreak-style" : ["error", "unix"], 25 | "no-console" : ["warn"], 26 | "no-floating-decimal" : ["error"], 27 | "no-implicit-coercion" : ["error"], 28 | "no-implicit-globals" : ["error"], 29 | "no-loop-func" : ["error"], 30 | "no-return-assign" : ["error"], 31 | "no-template-curly-in-string": ["error"], 32 | "no-unneeded-ternary" : ["error"], 33 | "no-unused-vars" : ["error", { "args": "none" }], 34 | "no-useless-computed-key" : ["error"], 35 | "no-useless-return" : ["error"], 36 | "no-var" : ["error"], 37 | "prefer-const" : ["error"], 38 | "quotes" : ["error", "single"], 39 | "semi" : ["error", "always"] 40 | } 41 | }; -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build plugin 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Setup Node 13 | uses: actions/setup-node@v2 14 | with: 15 | node-version: '16.x' 16 | registry-url: 'https://registry.npmjs.org' 17 | - name: Install dependencies and build 🔧 18 | run: npm ci 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish plugin 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Setup Node 12 | uses: actions/setup-node@v2 13 | with: 14 | node-version: '16.x' 15 | registry-url: 'https://registry.npmjs.org' 16 | - name: Install dependencies and build 🔧 17 | run: npm ci 18 | - name: Publish on NPM 📦 19 | run: npm publish 20 | env: 21 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !book/** 3 | !index.js 4 | !package.json 5 | !LICENSE -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Fabian Zankl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docsify plugin: Flexible Alerts 2 | 3 | [![Build Status](https://github.com/fzankl/docsify-plugin-flexible-alerts/actions/workflows/main.yml/badge.svg)](https://github.com/fzankl/docsify-plugin-flexible-alerts) 4 | [![npm Version](https://img.shields.io/npm/v/docsify-plugin-flexible-alerts/latest.svg)](https://www.npmjs.com/package/docsify-plugin-flexible-alerts) 5 | [![npm Downloads](https://img.shields.io/npm/dt/docsify-plugin-flexible-alerts.svg)](https://www.npmjs.com/package/docsify-plugin-flexible-alerts) 6 | 7 | This docsify plugin converts blockquotes into beautiful alerts. Look and feel can be configured on a global as well as on a alert specific level so output does fit your needs (some examples are shown below). In addition, you can provide own alert types. 8 | 9 | ![Sample alerts created with plugin 'flexible-alerts'](https://user-images.githubusercontent.com/44210522/93708131-10fb5780-fb34-11ea-85ae-e18b3e239f83.jpg) 10 | 11 | ## Installation and Usage 12 | 13 | ### Step #1 - Update `index.html` file 14 | 15 | Assuming you have a working [docsify](https://docsify.js.org) app set up, it is easy to use this plugin. 16 | 17 | 1. Add the following script tag to your `index.html` 18 | 19 | ```html 20 | 21 | 22 | ``` 23 | 24 | 2. In docsify setup configure the plugin so it does fit your needs. A custom setup is not mandatory. By default styles `flat` and `callout` (Default: `callout`) and types `NOTE`, `TIP`, `WARNING` and `ATTENTION` are supported. 25 | 26 | You can change it using plugin configuration via `index.html` or for a single alert in your markdown files. (please see section `Customizations` for further details) 27 | 28 | **Sample `index.html` file using style `flat` instead of `callout`** 29 | 30 | ```javascript 31 | 38 | ``` 39 | 40 | **Sample `index.html` using custom headings** 41 | 42 | ```javascript 43 | 61 | ``` 62 | 63 | **Sample `index.html` using multilingual headings** 64 | 65 | ```javascript 66 | 96 | ``` 97 | 98 | ### Step #2 - Prepare documentation 99 | 100 | Modify or add a new blockquote so it matches required syntax like shown in following examples: 101 | 102 | * Sample alert using type `NOTE` 103 | 104 | ```markdown 105 | > [!NOTE] 106 | > An alert of type 'note' using global style 'callout'. 107 | ``` 108 | 109 | * Sample alert using type `TIP` 110 | 111 | ```markdown 112 | > [!TIP] 113 | > An alert of type 'tip' using global style 'callout'. 114 | ``` 115 | 116 | * Sample alert using type `WARNING` 117 | 118 | ```markdown 119 | > [!WARNING] 120 | > An alert of type 'warning' using global style 'callout'. 121 | ``` 122 | 123 | * Sample alert using type `ATTENTION` 124 | 125 | ```markdown 126 | > [!ATTENTION] 127 | > An alert of type 'attention' using global style 'callout'. 128 | ``` 129 | 130 | ### Step #3 - docsify commands 131 | 132 | Serve your documentation (`docsify serve`) as usual. 133 | 134 | ## Customizations 135 | 136 | To use the plugin just modify an existing blockquote and prepend a line matching pattern `[!type]`. By default types `NOTE`, `TIP`, `WARNING` and `ATTENTION` are supported. You can extend the available types by providing a valid configuration (see below for an example). 137 | 138 | ```markdown 139 | > [!NOTE] 140 | > An alert of type 'note' using global style 'callout'. 141 | ``` 142 | 143 | ```markdown 144 | > [!NOTE|style:flat] 145 | > An alert of type 'note' using alert specific style 'flat' which overrides global style 'callout'. 146 | ``` 147 | 148 | As you can see in the second snippet, output can be configured on alert level also. Supported options are listed in following table: 149 | 150 | | Key | Allowed value | 151 | | --------------- | ---- | 152 | | style | One of follwowing values: callout, flat | 153 | | label | Any text | 154 | | icon | A valid Font Awesome icon, e.g. 'fas fa-comment' | 155 | | className | A name of a CSS class which specifies the look and feel | 156 | | labelVisibility | One of follwowing values: visible (default), hidden | 157 | | iconVisibility | One of follwowing values: visible (default), hidden | 158 | 159 | Multiple options can be used for single alerts as shown below: 160 | 161 | ```markdown 162 | > [!TIP|style:flat|label:My own heading|iconVisibility:hidden] 163 | > An alert of type 'tip' using alert specific style 'flat' which overrides global style 'callout'. 164 | > In addition, this alert uses an own heading and hides specific icon. 165 | ``` 166 | 167 | ![Custom alert](https://user-images.githubusercontent.com/44210522/50689970-04676080-102c-11e9-9cbc-8af129cb988c.png) 168 | 169 | As mentioned above you can provide your own alert types. Therefore, you have to provide the type configuration via `index.html`. Following example shows an additional type `COMMENT`. 170 | 171 | ```javascript 172 | 191 | ``` 192 | 193 | **Since we are using FontAwesome in previous example we have to include the library via `index.html`, e.g. using a CDN.** 194 | 195 | In Markdown just use the alert according to the types provided by default. 196 | 197 | ```markdown 198 | > [!COMMENT] 199 | > An alert of type 'comment' using style 'callout' with default settings. 200 | ``` 201 | 202 | ![Custom alert type 'comment'](https://user-images.githubusercontent.com/44210522/50722960-6f21a600-10d7-11e9-87e7-d40d87045afe.png) 203 | 204 | Instead of configuring this plugin using key `flexible-alerts` you can use camel case notation `flexibleAlerts` as well. 205 | 206 | ```javascript 207 | 214 | ``` 215 | 216 | ## Troubleshooting 217 | 218 | If alerts do no look as expected, check if your `index.html` as well as alerts in Markdown are valid according to this documentation. 219 | 220 | ## Changelog 221 | 222 | 12/22/2022 223 | * Fixed rendering of alerts when the content does not contain white spaces which can occur with some character encodings. 224 | 225 | 08/20/2022 226 | * Updated development dependencies and moved to GitHub Actions. 227 | 228 | 09/20/2020 229 | * Removed dependency to FontAwesome and embedded icons as SVG directly. 230 | * Support dark mode for callout alert style. 231 | * Moved alert type naming from 'danger' to 'attention'. Introduced type mappings to support mapping further alert type keys to existing definitions, e.g. map legacy alert type 'danger' to new type definition 'attention'. 232 | 233 | 09/23/2019 234 | * Fixed issue concerning custom Font Awesome icons when using on alert based level. 235 | 236 | 04/14/2019 237 | * Added camel case support for plugin configuration key. 238 | 239 | 03/03/2019 240 | * Fixed issue concerning languages using characters others than [a-z,A-Z,0-9] like Chinese or Russian. 241 | 242 | 01/19/2019 243 | * Fixed issue when using plugin along with themeable plugin. 244 | 245 | 01/06/2019 246 | * Initial release. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docsify-plugin-flexible-alerts", 3 | "version": "1.1.1", 4 | "description": "docsify plugin to convert blockquotes into beautiful and configurable alerts using preconfigured or own styles and alert types.", 5 | "keywords": [ 6 | "docsify", 7 | "plugin", 8 | "alert", 9 | "blockquotes", 10 | "quote", 11 | "hint", 12 | "callout" 13 | ], 14 | "author": "Fabian Zankl", 15 | "license": "MIT", 16 | "files": [ 17 | "dist" 18 | ], 19 | "main": "dist/docsify-plugin-flexible-alerts.js", 20 | "unpkg": "dist/docsify-plugin-flexible-alerts.min.js", 21 | "scripts": { 22 | "build": "rollup -c", 23 | "clean": "rimraf dist/*", 24 | "prepare": "npm run clean && npm run build", 25 | "start": "npm run build -- -w" 26 | }, 27 | "homepage": "https://github.com/fzankl/docsify-plugin-flexible-alerts#readme", 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/fzankl/docsify-plugin-flexible-alerts.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/fzankl/docsify-plugin-flexible-alerts/issues" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.20.5", 37 | "@babel/preset-env": "^7.20.2", 38 | "@rollup/plugin-babel": "^5.3.1", 39 | "@rollup/plugin-commonjs": "^22.0.2", 40 | "@rollup/plugin-json": "^4.1.0", 41 | "@rollup/plugin-node-resolve": "^13.3.0", 42 | "@rollup/plugin-url": "^7.0.0", 43 | "autoprefixer": "^10.4.13", 44 | "eslint": "^8.29.0", 45 | "lodash.merge": "^4.6.2", 46 | "rimraf": "^3.0.2", 47 | "rollup": "^2.79.1", 48 | "rollup-plugin-eslint": "^7.0.0", 49 | "rollup-plugin-postcss": "^4.0.2", 50 | "rollup-plugin-scss": "^3.0.0", 51 | "rollup-plugin-terser": "^7.0.2", 52 | "sass": "^1.56.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | import autoprefixer from 'autoprefixer'; 4 | import babel from '@rollup/plugin-babel'; 5 | import commonjs from '@rollup/plugin-commonjs'; 6 | import json from '@rollup/plugin-json'; 7 | import merge from 'lodash.merge'; 8 | import pkg from './package.json'; 9 | import postcss from 'rollup-plugin-postcss' 10 | import resolve from '@rollup/plugin-node-resolve'; 11 | import url from '@rollup/plugin-url'; 12 | 13 | import { terser } from 'rollup-plugin-terser'; 14 | import { eslint } from 'rollup-plugin-eslint'; 15 | 16 | const entryFile = path.resolve(__dirname, 'src', 'index.js'); 17 | const outputFile = path.resolve(__dirname, 'dist', `${pkg.name}.js`); 18 | 19 | // Banner 20 | const bannerData = [ 21 | `${pkg.name}`, 22 | `v${pkg.version}`, 23 | `${pkg.homepage}`, 24 | `(c) ${(new Date()).getFullYear()} ${pkg.author}`, 25 | `${pkg.license} license` 26 | ]; 27 | 28 | // Plugins 29 | const pluginSettings = { 30 | eslint: { 31 | exclude: ['node_modules/**', './package.json', '**/*.scss'], 32 | throwOnWarning: false, 33 | throwOnError: true 34 | }, 35 | babel: { 36 | exclude: ['node_modules/**'], 37 | babelHelpers: 'bundled', 38 | presets: [ 39 | ['@babel/preset-env', { 40 | modules: false, 41 | targets: { 42 | browsers: ['ie >= 9'] 43 | } 44 | }] 45 | ] 46 | }, 47 | postcss: { 48 | minimize: true, 49 | plugins: [ 50 | autoprefixer() 51 | ] 52 | }, 53 | url: { 54 | limit: 10 * 1024, // inline files < 10k, copy files > 10k 55 | include: ["**/*.svg"], // defaults to .svg, .png, .jpg and .gif files 56 | emitFiles: true // defaults to true 57 | }, 58 | terser: { 59 | beautify: { 60 | compress: false, 61 | mangle: false, 62 | output: { 63 | beautify: true, 64 | comments: /(?:^!|@(?:license|preserve))/ 65 | } 66 | }, 67 | minify: { 68 | compress: true, 69 | mangle: true, 70 | output: { 71 | comments: new RegExp(pkg.name) 72 | } 73 | } 74 | } 75 | }; 76 | 77 | // Config Base 78 | const config = { 79 | input: entryFile, 80 | output: { 81 | file: outputFile, 82 | banner: `/*!\n * ${bannerData.join('\n * ')}\n */`, 83 | sourcemap: true 84 | }, 85 | plugins: [ 86 | url(pluginSettings.url), 87 | postcss(pluginSettings.postcss), 88 | resolve(), 89 | commonjs(), 90 | json(), 91 | eslint(pluginSettings.eslint), 92 | babel(pluginSettings.babel) 93 | ], 94 | watch: { 95 | clearScreen: false 96 | } 97 | }; 98 | 99 | // Format: IIFE 100 | const iife = merge({}, config, { 101 | output: { 102 | format: 'iife' 103 | }, 104 | plugins: [ 105 | terser(pluginSettings.terser.beautify) 106 | ] 107 | }); 108 | 109 | // Format: IIFE (Minified) 110 | const iifeMinified = merge({}, config, { 111 | output: { 112 | file: iife.output.file.replace(/\.js$/, '.min.js'), 113 | format: iife.output.format 114 | }, 115 | plugins: [ 116 | terser(pluginSettings.terser.minify) 117 | ] 118 | }); 119 | 120 | 121 | export default [ 122 | iife, 123 | iifeMinified 124 | ]; 125 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | import styles from './style.scss'; 3 | 4 | (function () { 5 | const CONFIG = { 6 | style: 'callout', 7 | note: { 8 | label: 'Note', 9 | icon: 'icon-note', 10 | className: 'note' 11 | }, 12 | tip: { 13 | label: 'Tip', 14 | icon: 'icon-tip', 15 | className: 'tip' 16 | }, 17 | warning: { 18 | label: 'Warning', 19 | icon: 'icon-warning', 20 | className: 'warning' 21 | }, 22 | attention: { 23 | label: 'Attention', 24 | icon: 'icon-attention', 25 | className: 'attention' 26 | }, 27 | // To support further keys in plugin we do an automated mapping between alert types. 28 | typeMappings: { 29 | info: 'note', 30 | danger: 'attention' 31 | } 32 | }; 33 | 34 | function mergeObjects(obj1, obj2, level = 0) { 35 | for (const property in obj2) { 36 | try { 37 | // Property in destination object set; update its value. 38 | if (obj2[property].constructor === Object && level < 1) { 39 | obj1[property] = mergeObjects(obj1[property], obj2[property], level + 1); 40 | } else { 41 | obj1[property] = obj2[property]; 42 | } 43 | } catch(e) { 44 | // Property in destination object not set; create it and set its value. 45 | obj1[property] = obj2[property]; 46 | } 47 | } 48 | 49 | return obj1; 50 | } 51 | 52 | const install = function (hook, vm) { 53 | const options = mergeObjects(CONFIG, vm.config['flexible-alerts'] || vm.config.flexibleAlerts); 54 | 55 | const findSetting = function findAlertSetting(input, key, fallback, callback) { 56 | const match = (input || '').match(new RegExp(`${key}:(([\\s\\w\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF-]*))`)); 57 | 58 | if (!match) { 59 | return callback ? callback(fallback) : fallback; 60 | } 61 | 62 | return callback ? callback(match[1]) : match[1]; 63 | }; 64 | 65 | hook.afterEach(function (html, next) { 66 | const modifiedHtml = html.replace(/<\s*blockquote[^>]*>[\s]+?(?:

)?\[!(\w*)((?:\|[\w*:[\s\w\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]*)*?)\]([\s\S]*?)(?:<\/p>)?<\s*\/\s*blockquote>/g, function (match, key, settings, value) { 67 | 68 | if (!options[key.toLowerCase()] && options.typeMappings[key.toLowerCase()]) { 69 | key = options.typeMappings[key.toLowerCase()]; 70 | } 71 | 72 | const config = options[key.toLowerCase()]; 73 | 74 | if (!config) { 75 | return match; 76 | } 77 | 78 | const style = findSetting(settings, 'style', options.style); 79 | let isIconVisible = findSetting(settings, 'iconVisibility', 'visible', (value) => value !== 'hidden'); 80 | let isLabelVisible = findSetting(settings, 'labelVisibility', 'visible', (value) => value !== 'hidden'); 81 | let label = findSetting(settings, 'label', config.label); 82 | const icon = findSetting(settings, 'icon', config.icon); 83 | const className = findSetting(settings, 'className', config.className); 84 | 85 | // Label can be language specific and could be specified via user configuration 86 | if (typeof label === 'object') { 87 | const foundLabel = Object.keys(label).filter(function (key) { 88 | return vm.route.path.indexOf(key) > -1; 89 | }); 90 | 91 | if (foundLabel && foundLabel.length > 0) { 92 | label = label[foundLabel[0]]; 93 | } else { 94 | isLabelVisible = false; 95 | isIconVisible = false; 96 | } 97 | } 98 | 99 | const iconTag = ``; 100 | const titleTag = `

${isIconVisible ? iconTag : ''}${isLabelVisible ? label : ''}

`; 101 | 102 | return ( 103 | `
104 | ${isIconVisible || isLabelVisible ? titleTag : '' } 105 |

${value}

106 |
` 107 | ); 108 | }); 109 | 110 | next(modifiedHtml); 111 | }); 112 | }; 113 | 114 | window.$docsify = window.$docsify || {}; 115 | window.$docsify.plugins = [].concat(install, window.$docsify.plugins); 116 | }()); 117 | -------------------------------------------------------------------------------- /src/style.scss: -------------------------------------------------------------------------------- 1 | .alert { 2 | display: block; 3 | position: relative; 4 | word-wrap: break-word; 5 | word-break: break-word; 6 | padding: 0.75rem 1.25rem !important; 7 | margin-bottom: 1rem !important; 8 | 9 | > * { 10 | max-width: 100%; 11 | } 12 | 13 | > :first-child { 14 | margin-top: 0; 15 | } 16 | 17 | > :last-child { 18 | margin-bottom: 0; 19 | } 20 | 21 | &:before { 22 | content: unset !important; 23 | } 24 | } 25 | 26 | .alert + .alert { 27 | margin-top: -0.25rem !important; 28 | } 29 | 30 | .alert p { 31 | margin-top: 0.5rem; 32 | margin-bottom: 0.5rem; 33 | } 34 | 35 | .alert .title { 36 | display: flex; 37 | align-items: center; 38 | flex-wrap: wrap; 39 | font-weight: 600; 40 | margin: 0; 41 | } 42 | 43 | .icon { 44 | display: inline-block; 45 | width: 16px; 46 | height: 16px; 47 | background-repeat: no-repeat; 48 | margin-right: 0.5rem; 49 | } 50 | 51 | @function get-note-icon($color) { 52 | @return 'data:image/svg+xml,%3Csvg width="1em" height="1em" viewBox="0 0 16 16" fill="#{$color}" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath fill-rule="evenodd" d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412l-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM8 5.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/%3E%3C/svg%3E'; 53 | } 54 | 55 | @function get-tip-icon($color) { 56 | @return 'data:image/svg+xml,%3Csvg width="1em" height="1em" viewBox="0 0 352 512" fill="#{$color}" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0 0 26.64 14.28h61.71a31.99 31.99 0 0 0 26.64-14.28l17.09-25.69a31.989 31.989 0 0 0 5.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.52 18.85 42.36 58.23 52.21 91.45.04.26.07.52.11.78h160.24c.04-.26.07-.51.11-.78 9.85-33.22 35.69-72.6 52.21-91.45C335.55 260.85 352 220.37 352 176 352 78.61 272.91-.3 175.45 0 73.44.31 0 82.97 0 176zm176-80c-44.11 0-80 35.89-80 80 0 8.84-7.16 16-16 16s-16-7.16-16-16c0-61.76 50.24-112 112-112 8.84 0 16 7.16 16 16s-7.16 16-16 16z"%3E%3C/path%3E%3C/svg%3E'; 57 | } 58 | 59 | @function get-warning-icon($color) { 60 | @return 'data:image/svg+xml,%3Csvg width="1em" height="1em" viewBox="0 0 17 16" fill="#{$color}" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath fill-rule="evenodd" d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 5zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/%3E%3C/svg%3E'; 61 | } 62 | 63 | @function get-attention-icon($color) { 64 | @return 'data:image/svg+xml,%3Csvg width="1em" height="1em" viewBox="0 0 16 16" fill="#{$color}" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/%3E%3Cpath fill-rule="evenodd" d="M11.354 4.646a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708l6-6a.5.5 0 0 1 .708 0z"/%3E%3C/svg%3E'; 65 | } 66 | 67 | // #################################################### 68 | // ################## Style: Callout ################## 69 | // #################################################### 70 | 71 | @mixin callout-item($name, $color) { 72 | &.#{$name} { 73 | border-left-color: $color !important; 74 | 75 | .title { 76 | color: $color; 77 | } 78 | 79 | .icon-#{$name} { 80 | background-image: url("#{call(get-function(get-#{$name}-icon), $color)}"); 81 | } 82 | } 83 | } 84 | 85 | .alert.callout { 86 | border: 1px solid #eee; 87 | border-left-width: 0.25rem; 88 | border-radius: 0.25rem; 89 | background: var(--background); 90 | 91 | @include callout-item('note', #17a2b8); 92 | @include callout-item('tip', #28a745); 93 | @include callout-item('warning', #f0ad4e); 94 | @include callout-item('attention', #dc3545); 95 | } 96 | 97 | // #################################################### 98 | // #################### Style: Flat ################### 99 | // #################################################### 100 | 101 | @mixin flat-item($name, $background-color, $heading-color, $text-color) { 102 | &.#{$name} { 103 | color: $text-color; 104 | background-color: $background-color; 105 | border-color: darken($background-color, 5%); 106 | 107 | .title { 108 | color: $heading-color; 109 | } 110 | 111 | .icon-#{$name} { 112 | background-image: url("#{call(get-function(get-#{$name}-icon), $heading-color)}"); 113 | } 114 | } 115 | } 116 | 117 | .alert.flat { 118 | border: 1px solid transparent; 119 | border-radius: 0.125rem; 120 | color: #383d41; 121 | background-color: #e2e3e5; 122 | border-color: #d6d8db; 123 | 124 | @include flat-item('note', #cdeefd, #01354d, #02587f); 125 | @include flat-item('tip', #dbefdc, #18381a, #285b2a); 126 | @include flat-item('warning', #ffddd3, #581e0c, #852d12); 127 | @include flat-item('attention', #fdd9d7, #551713, #7f231c); 128 | } --------------------------------------------------------------------------------