Repository: Alex-D/Trumbowyg Branch: develop Commit: c4ccb3fc1aee Files: 166 Total size: 964.7 KB Directory structure: gitextract_ml_8nko7/ ├── .bowerrc ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE.md │ └── workflows/ │ ├── lint.yml │ └── website.yml ├── .gitignore ├── .jshintrc ├── .npmignore ├── BACKERS.md ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── bower.json ├── docs/ │ ├── .gitignore │ ├── .jshintrc │ ├── css/ │ │ └── main.css │ ├── demos/ │ │ ├── core/ │ │ │ ├── dark-theme.html │ │ │ ├── default.html │ │ │ └── simple.html │ │ ├── index.html │ │ ├── js/ │ │ │ ├── highlight.js │ │ │ ├── loader.js │ │ │ └── runExampleCode.js │ │ └── plugins/ │ │ ├── allowtagsfrompaste.html │ │ ├── base64.html │ │ ├── cleanpaste.html │ │ ├── colors.html │ │ ├── emoji.html │ │ ├── fontfamily.html │ │ ├── fontsize.html │ │ ├── giphy.html │ │ ├── highlight.html │ │ ├── history.html │ │ ├── indent.html │ │ ├── insertaudio.html │ │ ├── lineheight.html │ │ ├── mathml.html │ │ ├── mention.html │ │ ├── noembed.html │ │ ├── pasteembed.html │ │ ├── pasteimage.html │ │ ├── preformatted.html │ │ ├── resizimg.html │ │ ├── ruby.html │ │ ├── specialchars.html │ │ ├── speechrecognition.html │ │ ├── table.html │ │ ├── template.html │ │ ├── tenor.html │ │ └── upload.html │ ├── documentation/ │ │ ├── core/ │ │ │ └── index.html │ │ ├── index.html │ │ └── plugins/ │ │ └── index.html │ ├── gulpfile.mjs │ ├── humans.txt │ ├── index.html │ ├── js/ │ │ ├── main.js │ │ └── vendor/ │ │ └── highlight.js │ ├── package.json │ ├── robots.txt │ └── scss/ │ ├── _base.scss │ ├── _buttons.scss │ ├── _documentation.scss │ ├── _donate.scss │ ├── _font.scss │ ├── _footer.scss │ ├── _get-started.scss │ ├── _header.scss │ ├── _highlightjs-github.scss │ ├── _introduction.scss │ ├── _languages.scss │ ├── _normalize.scss │ ├── _plugins-packages.scss │ ├── _section.scss │ ├── _variables.scss │ └── main.scss ├── gulpfile.mjs ├── index.html ├── package.json ├── plugins/ │ ├── allowtagsfrompaste/ │ │ └── trumbowyg.allowtagsfrompaste.js │ ├── base64/ │ │ └── trumbowyg.base64.js │ ├── cleanpaste/ │ │ └── trumbowyg.cleanpaste.js │ ├── colors/ │ │ ├── trumbowyg.colors.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.colors.scss │ ├── emoji/ │ │ ├── trumbowyg.emoji.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.emoji.scss │ ├── fontfamily/ │ │ └── trumbowyg.fontfamily.js │ ├── fontsize/ │ │ └── trumbowyg.fontsize.js │ ├── giphy/ │ │ ├── trumbowyg.giphy.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.giphy.scss │ ├── highlight/ │ │ ├── trumbowyg.highlight.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.highlight.scss │ ├── history/ │ │ └── trumbowyg.history.js │ ├── indent/ │ │ └── trumbowyg.indent.js │ ├── insertaudio/ │ │ └── trumbowyg.insertaudio.js │ ├── lineheight/ │ │ └── trumbowyg.lineheight.js │ ├── mathml/ │ │ ├── trumbowyg.mathml.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.mathml.scss │ ├── mention/ │ │ ├── trumbowyg.mention.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.mention.scss │ ├── noembed/ │ │ └── trumbowyg.noembed.js │ ├── pasteembed/ │ │ └── trumbowyg.pasteembed.js │ ├── pasteimage/ │ │ └── trumbowyg.pasteimage.js │ ├── preformatted/ │ │ └── trumbowyg.preformatted.js │ ├── resizimg/ │ │ └── trumbowyg.resizimg.js │ ├── ruby/ │ │ └── trumbowyg.ruby.js │ ├── specialchars/ │ │ ├── trumbowyg.specialchars.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.specialchars.scss │ ├── speechrecognition/ │ │ └── trumbowyg.speechrecognition.js │ ├── table/ │ │ ├── trumbowyg.table.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.table.scss │ ├── template/ │ │ └── trumbowyg.template.js │ ├── tenor/ │ │ ├── trumbowyg.tenor.js │ │ └── ui/ │ │ └── sass/ │ │ └── trumbowyg.tenor.scss │ └── upload/ │ └── trumbowyg.upload.js └── src/ ├── langs/ │ ├── ar.js │ ├── az.js │ ├── bg.js │ ├── bn.js │ ├── by.js │ ├── ca.js │ ├── cs.js │ ├── da.js │ ├── de.js │ ├── el.js │ ├── en.js │ ├── es.js │ ├── es_ar.js │ ├── et.js │ ├── fa.js │ ├── fi.js │ ├── fr.js │ ├── he.js │ ├── hr.js │ ├── hu.js │ ├── id.js │ ├── it.js │ ├── ja.js │ ├── ko.js │ ├── lt.js │ ├── mn.js │ ├── my.js │ ├── nb.js │ ├── nl.js │ ├── ph.js │ ├── pl.js │ ├── pt.js │ ├── pt_br.js │ ├── ro.js │ ├── rs.js │ ├── rs_latin.js │ ├── ru.js │ ├── sk.js │ ├── sl.js │ ├── sq.js │ ├── sv.js │ ├── th.js │ ├── tr.js │ ├── ua.js │ ├── vi.js │ ├── zh_cn.js │ └── zh_tw.js ├── trumbowyg.js └── ui/ └── sass/ └── trumbowyg.scss ================================================ FILE CONTENTS ================================================ ================================================ FILE: .bowerrc ================================================ { "directory": "bower_components/" } ================================================ FILE: .editorconfig ================================================ # EditorConfig: https://EditorConfig.org # Notepad++ Plugin: https://github.com/editorconfig/editorconfig-notepad-plus-plus # Visual Studio Code Plugin: https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig root = true [*] charset = utf-8 indent_style = space indent_size = 4 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [{*.json,*.yml,*.yaml,*.md}] indent_size = 2 # Ignore paths [{docs/css/**,docs/scss/_normalize.scss,**/vendor/**,dist/**}] charset = unset end_of_line = unset insert_final_newline = unset trim_trailing_whitespace = unset indent_style = unset indent_size = unset ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # SCSS and JS files must always use LF for tools to work *.js eol=lf *.scss eol=lf ================================================ FILE: .github/FUNDING.yml ================================================ github: Alex-D patreon: AlexandreDemode custom: https://paypal.me/demodealexandre ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ### Description ### How to reproduce? ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint on: push: jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npm run lint editorconfig: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: editorconfig-checker/action-editorconfig-checker@main - run: editorconfig-checker ================================================ FILE: .github/workflows/website.yml ================================================ name: Deploy Website on: push: branches: - main jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npm run build working-directory: docs/ - uses: actions/upload-pages-artifact@v3 with: path: docs/ deploy: needs: build # Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: pages: write # to deploy to Pages id-token: write # to verify the deployment originates from an appropriate source # Deploy to the github-pages environment environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .gitignore ================================================ # Others ~$* /.sass-cache /uploaded-files /node_modules /bower_components /dist /src/ui/sass/_sprite* /plugins/**/ui/sass/_sprite* # We use npm: ignore yarn yarn.lock # Windows image file caches Thumbs.db # Folder config file Desktop.ini # Mac crap .DS_Store # IDEA .idea /nbproject ================================================ FILE: .jshintrc ================================================ { "bitwise": true, "camelcase": true, "esnext": true, "curly": true, "eqeqeq": true, "forin": true, "indent": 4, "latedef": false, "newcap": false, "noarg": true, "noempty": true, "plusplus": true, "quotmark": "single", "undef": true, "unused": true, "strict": true, "trailing": true, "maxparams": 5, "maxdepth": 5, "maxstatements": 40, "maxcomplexity": 20, "maxlen": 1200, "eqnull": true, "browser": true, "globals": { "jQuery": false } } ================================================ FILE: .npmignore ================================================ Gulpfile.js banner.jpg bower.json src docs bower_components .gitattributes .gitignore .jshintrc .github .idea .bowerrc sponsors BACKERS.md CONTRIBUTORS.md ================================================ FILE: BACKERS.md ================================================

Sponsors & Backers

Trumbowyg is an MIT-licensed open source project and completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing. You can support it's ongoing development by being a backer or a sponsor: - [Become a backer or sponsor on Patreon](https://www.patreon.com/alexandredemode) - [One-time donation via PayPal](https://www.paypal.me/demodealexandre/20eur)

Gold sponsors

avot®

Become a Sponsor

Silver sponsors

SocialOptic

Become a Sponsor

Backers

- Johan Rosenson - Integrious Ltd

Become a Backer

================================================ FILE: CONTRIBUTORS.md ================================================ # Contributors Trumbowyg is the result of many people who made translations or the code better. Special thanks to [Adrien Gervaix](https://dribbble.com/adriengervaix) for the Trumbowyg v2 icon set. - Alex-D - Steve Rackham - Lawrence - Ra100 - lizardK - Ulrich Mayring - Kirill Urgant - VeeeneX - Nicolas Pion - Sven Dunemann - Zane Chua - freekpost - Blufish Technologies - Civil - Danny Hiemstra - Edwin Veldhuizen - Florian - Michael Holroyd - Nicklas - Nicolás Moncada - Don Desroches - Jan Svoboda - Martin - Max Seelig - Stufingo - Alexander van Eerd - AragurDEV - Davor Budimir - Eduardo Russo - Fathi Anshory - Fyers - Hirokazu Kutsu - Justas Brazauskas - Jérôme Steunou - Manfred62 - Merianos Nikos - Nikola Trifunovic - Paul Pritchard - Richard Kiewiet - Stef Kariotidis - Vijay Jagadeesan - Vlad Radulescu - Vladimir - foo9 - g2010a - hiendv - jake johns - matopeter - ronan - sinjuice - Adam Balogh - AdamHess - Aleksandar Dimitrov - Aleksandr-ru - Alessio Dionisi - Alex Gotardi - Andreas Kohn - Andrei - Andrey Kogut - Antoine Leblanc - Anton Morozov - Artur - Benjamin Bourot - Bennett - Boylett - Burak Erdem - Burak Ozdemir - Carlos Barros - Chitoku - Christian - Christopher Kirk-Nielsen - maxom - Dariush Abbasi - Delvallée - Denis Jacquemin - Elisha Witte - Eric Radin - Ersin Guvenc - François Houlbrèque - Freek Post - Gabriel S. L - Grafikart - Henio Tierra - Hunor Karamán - Ian Mustafa - John Pozy - Jonathan Hedrén - Jonathan del Strother - JoongSeob Vito Kim - Jorrit Schippers - Karalkou - Ken Cheung - Kim Trolle Wadum - Lars Boldt - Leo Gono - Leopard Ho - MIRK0 - Marcin Wieprzkowicz - Markus Spallek - Matt Johnson - Mattias Hagberg - Mike Goodfellow - Mike Richmond - Mitja Šlenc - Moisés Márquez - Nathan Rosquist - Nenad Novkovic - Nikki Locke - OBernard2 - Oguzhan Inan - Oleg Berman - Paweł Abramowicz - Peter Dave Hello - Petr Čech - Philipp Palmtag - Ramiro Varandas Jr - Rezha Julio - Ricardo Cardeña - Roberto - Rodrigo Boratto - Sergey Golomedov - Stéphane Lavergne - TheGeekTortoise - Tiago Alves - TikiTDO - Tim - Timo Jarventausta - Todd Graham - Val - Vietworm - Vinzgore - Wisse Jelgersma - Zane J. Chua - abomokhahmed - adalenv - akai - basteyy - brainfogz - brentanalexander - chadidi - cleoo - de Oliveira Prado - dev.hibiki - dominiczaq - figroc - gonatee - joey91133 - jsejakobsen - kingdido999 - loclamor - luetge - munzur - naoh - root - slackwalker - teppokoivula - udidoron - Игорь - Михаил Гущин - Олег Ильин ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2012-2016 Alexandre Demode (Alex-D) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Downloads Downloads Licence
Version on npm CDNJS Version on bower

Supporting Trumbowyg

Trumbowyg is an MIT-licensed open source project and completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing. You can support its ongoing development by being a backer or a sponsor: - [Become a backer or sponsor on Patreon](https://www.patreon.com/alexandredemode) - [One-time donation via PayPal](https://www.paypal.me/demodealexandre/20eur)

Sponsors

Gold

Premirus Corporation

•••
Become a Sponsor

------------------------------------ ## Introduction Trumbowyg is a simple and lightweight WYSIWYG editor, weight only 30kB minified (10kB gzip) for faster page loading. Visit presentation page: http://alex-d.github.io/Trumbowyg/ ## Documentation All you need to know about Trumbowyg is here: - [Get started](https://alex-d.github.io/Trumbowyg/documentation/) - [25+ Demos](https://alex-d.github.io/Trumbowyg/demos/) - [45+ Supported languages](https://alex-d.github.io/Trumbowyg/#languages) - [All Options explained](https://alex-d.github.io/Trumbowyg/documentation/#basic-options) - [All existing Plugins](https://alex-d.github.io/Trumbowyg/documentation/plugins/) - [Create your own Plugin](https://alex-d.github.io/Trumbowyg/documentation/plugins/#create-your-own) ## Contribution You can contribute to Trumbowyg with translations in languages you know. Thanks to `node` and `gulp`, you can improve core script, style or icons easily. First, fork and clone the repository ```bash cd Trumbowyg # go into the project's root directory npm install # install development dependencies npm run dev # watch mode npm run build # to build the project ``` ## Author

@Alex-D

Alexandre Demode
• • •
Latest release and announcements
https://x.com/AlexandreDemode
## License This project is under [MIT license](LICENSE). ================================================ FILE: bower.json ================================================ { "name": "trumbowyg", "version": "2.28.0", "homepage": "https://github.com/Alex-D/Trumbowyg", "authors": [ { "name": "Alexandre Demode (Alex-D)", "email": "contact@alex-d.fr", "homepage": "https://alex-d.fr" } ], "description": "A lightweight WYSIWYG editor", "main": "dist/trumbowyg.js", "keywords": [ "wysiwyg", "editor", "rich text", "contenteditable", "trumbowyg" ], "license": "MIT", "ignore": [ "**/.*", "bower_components", "node_modules", "plugins", "!dist/plugins", "test", "tests", "src", "docs", "sponsors", "banner.jpg", "gulpfile.js", "package.json" ], "dependencies": { "jquery": ">=1.8" } } ================================================ FILE: docs/.gitignore ================================================ /node_modules/ /bower_components /dist /src /plugins uploaded-files/ ================================================ FILE: docs/.jshintrc ================================================ { "bitwise": true, "camelcase": true, "esnext": true, "curly": true, "eqeqeq": true, "forin": true, "indent": 4, "latedef": false, "newcap": false, "noarg": true, "noempty": true, "plusplus": true, "quotmark": "single", "undef": true, "unused": true, "strict": true, "trailing": true, "maxparams": 5, "maxdepth": 5, "maxstatements": 40, "maxcomplexity": 20, "maxlen": 1200, "eqnull": true, "browser": true, "node": true, "globals": { "jQuery": false, "hljs": false } } ================================================ FILE: docs/css/main.css ================================================ /*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}button,html,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ol,nav ul{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@font-face{font-family:Panton;src:url(../fonts/panton.woff2) format("woff2");font-weight:300;font-style:normal;font-display:swap}@font-face{font-family:"Open Sans";src:url(../fonts/open-sans-light.woff2) format("woff2");font-weight:300;font-style:normal;font-display:swap}@font-face{font-family:"Open Sans";src:url(../fonts/open-sans-regular.woff2) format("woff2");font-weight:400;font-style:normal;font-display:swap}@font-face{font-family:"Open Sans";src:url(../fonts/open-sans-semibold.woff2) format("woff2");font-weight:600;font-style:normal;font-display:swap}@font-face{font-family:"JetBrains Mono";src:url(../fonts/jetbrains-mono-regular.woff2) format("woff2");font-weight:400;font-style:normal;font-display:swap}/*! HTML5 Boilerplate v4.3.0 | MIT License | http://h5bp.com/ */button,html,input,select,textarea{color:#222}html{font-size:1em;line-height:1.4}body,html{background:#fff}::-moz-selection{background:#b3d4fc;text-shadow:none}::selection{background:#b3d4fc;text-shadow:none}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}audio,canvas,img,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}html{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*,:after,:before{-webkit-box-sizing:inherit;-moz-box-sizing:inherit;box-sizing:inherit}body,button,input,select,textarea{font-family:"Open Sans",sans-serif;font-size:18px;font-weight:300}.wrapper{max-width:1200px;margin:0 auto;clear:both}.wrapper p:last-child{margin-bottom:0}.wrapper h4{font-size:30px}.wrapper code.console{background:#392813;color:#fff;font-size:16px;padding:3px 7px}.wrapper .note{display:table;color:#666;background:rgba(0,0,0,.02);padding:8px 12px;border-left:3px solid #ffb864;border-radius:8px;font-size:15px;-webkit-margin-before:.5em;margin-block-start:.5em;-webkit-margin-after:.5em;margin-block-end:.5em}.wrapper .note>:first-child{margin-top:6px}.wrapper .note>:last-child{margin-bottom:6px}.wrapper .note code{margin-left:2px;margin-right:2px}.section a{text-decoration:none;color:#ff974a}.section a:focus,.section a:hover{text-decoration:underline}h1,h2,h3,h4,h5,h6{font-weight:300;margin:0;padding:0}hr.clearfix{display:block;border:none;background:0 0;margin:0;padding:0;height:0;clear:both}code,kbd,pre,samp{font-family:"JetBrains Mono",monospace;font-size:15px;line-height:1.5}:root{--tbw-cell-vertical-padding:4px;--tbw-cell-horizontal-padding:8px;--tbw-cell-line-height:1.5em}table{margin-bottom:var(--tbw-cell-line-height)}td,th{height:calc(var(--tbw-cell-vertical-padding) * 2 + var(--tbw-cell-line-height));min-width:calc(var(--tbw-cell-horizontal-padding) * 2);padding:var(--tbw-cell-vertical-padding) var(--tbw-cell-horizontal-padding);border:1px solid #e7eaec}thead{background:#fff}.button{display:inline-block;position:relative;width:250px;border:2px solid transparent;margin:0 auto;padding:23px 30px;color:#fff;font-weight:400;font-size:16px;line-height:1.2;text-decoration:none;border-radius:50px;text-align:center;-webkit-transition:color 150ms,background-color 150ms;-o-transition:color 150ms,background-color 150ms;transition:color 150ms,background-color 150ms}.button-primary{background:#ff974a}.button-secondary{background:#f48d40}.button-ghost{border-color:rgba(255,255,255,.4)}.button:not(:last-child){margin-right:20px}.button:focus,.button:hover{background:#fff;color:#ff974a;outline:0}.header-landing{position:relative;background:#ff974a -webkit-gradient(linear,right top,left bottom,from(#ff974a),to(#ffb864));background:#ff974a -webkit-linear-gradient(top right,#ff974a,#ffb864);background:#ff974a -o-linear-gradient(top right,#ff974a,#ffb864);background:#ff974a linear-gradient(to bottom left,#ff974a,#ffb864);text-align:center;color:#fff;padding-bottom:200px}.header-nav{float:right;width:100%;margin:0;padding:25px 40px 0;font-weight:400}.header-nav li{list-style:none;float:right;margin-right:30px}.header-nav li:first-child{float:left}.header-nav li:nth-child(2){margin-right:0}.header-nav li a{display:block;text-decoration:none;color:#b65207;font-size:16px;padding:10px 0;-webkit-transition:color 150ms;-o-transition:color 150ms;transition:color 150ms}.header-nav li a:hover{color:#fff}.header-nav li a.view-on-github{-webkit-transform:translateY(-11px);-ms-transform:translateY(-11px);-o-transform:translateY(-11px);transform:translateY(-11px)}.header-nav li a.view-on-github svg{width:25px;height:25px;fill:currentColor;margin-right:10px;vertical-align:baseline;-webkit-transform:translateY(5px);-ms-transform:translateY(5px);-o-transform:translateY(5px);transform:translateY(5px)}.header-nav li a.view-on-github .star{font-size:18px}.header-logo-container{margin:0 auto;padding-top:calc(80px + 4%);text-align:center}.header-logo-container .header-logo-h1{position:relative;margin:0 auto;width:1000px;max-width:100%}.header-logo-container .header-logo{margin-right:-3%;width:1000px;max-width:80%}.header-subtitle{font-size:28px;padding:0 20px}.header-description{font-size:18px;line-height:1.6;padding:0 20px;font-weight:300}.header-buttons{margin:50px 0}.header-install{font-size:18px;line-height:1.6;font-weight:300}#demonstration{padding-bottom:0;margin-bottom:80px}#demonstration .trumbowyg-editor,#demonstration .trumbowyg-textarea{padding:25px}#demonstration .trumbowyg:not(.trumbowyg-fullscreen),#trumbowyg-demo:not(.trumbowyg-textarea){margin:-150px auto 0;width:100%;height:340px;max-width:900px;background:#fff;-webkit-box-shadow:0 0 27px rgba(0,0,0,.03);box-shadow:0 0 27px rgba(0,0,0,.03);resize:none}#demonstration .trumbowyg{font-size:16px;line-height:2}#demonstration .trumbowyg p{margin:0 0 32px}#demonstration .trumbowyg button{font-weight:400}#trumbowyg-demo:not(.trumbowyg-textarea){color:transparent;overflow:hidden;border:1px solid #dbdfe0}.demo-switcher{position:absolute;bottom:-20px;left:50%;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);-o-transform:translateX(-50%);transform:translateX(-50%);z-index:10;margin:0 auto;width:250px;height:40px;border-radius:50px;border:1px solid #dbdfe0;background:#fff}.demo-switcher .button{display:block;float:left;padding:10px;margin:0;border:none;width:50%;background:0 0;color:#392813;font-weight:300;-webkit-transition:color 150ms,text-indent 150ms;-o-transition:color 150ms,text-indent 150ms;transition:color 150ms,text-indent 150ms}.demo-switcher .button:first-child{text-indent:5px}.demo-switcher .button:last-child{text-indent:-5px}.demo-switcher .button.current{text-indent:0;color:#fff}.demo-switcher::after{content:"";display:block;width:50%;height:100%;border-radius:50px;background:#ff974a;-webkit-transition:margin-left 150ms;-o-transition:margin-left 150ms;transition:margin-left 150ms}.demo-switcher.current-plugins::after{margin-left:50%}.header-logo-animation{position:absolute;overflow:hidden;top:-30px;left:91.5%;width:150px;height:150px}.header-logo-animation svg{position:absolute;top:0;left:0;fill:#fff;height:25px;width:25px}.header-logo-animation .header-logo-animation-strong{-webkit-animation:headerLogoStrong 1s linear infinite;-o-animation:headerLogoStrong 1s linear infinite;animation:headerLogoStrong 1s linear infinite;-webkit-animation-delay:-.85s;-o-animation-delay:-.85s;animation-delay:-.85s}.header-logo-animation .header-logo-animation-p{-webkit-animation:headerLogoP 1s linear infinite;-o-animation:headerLogoP 1s linear infinite;animation:headerLogoP 1s linear infinite;-webkit-animation-delay:-.2s;-o-animation-delay:-.2s;animation-delay:-.2s}.header-logo-animation .header-logo-animation-link{-webkit-animation:headerLogoLink 1s linear infinite;-o-animation:headerLogoLink 1s linear infinite;animation:headerLogoLink 1s linear infinite;-webkit-animation-delay:-.4s;-o-animation-delay:-.4s;animation-delay:-.4s}.header-logo-animation .header-logo-animation-blockquote{-webkit-animation:headerLogoBlockquote 1s linear infinite;-o-animation:headerLogoBlockquote 1s linear infinite;animation:headerLogoBlockquote 1s linear infinite;-webkit-animation-delay:-.6s;-o-animation-delay:-.6s;animation-delay:-.6s}.header-logo-animation .header-logo-animation-view-html{-webkit-animation:headerLogoViewHtml 1s linear infinite;-o-animation:headerLogoViewHtml 1s linear infinite;animation:headerLogoViewHtml 1s linear infinite;-webkit-animation-delay:-.3s;-o-animation-delay:-.3s;animation-delay:-.3s}@-webkit-keyframes headerLogoStrong{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(70px);transform:translateX(-30px) translateY(70px)}20%{-webkit-transform:translateX(15px) translateY(80px);transform:translateX(15px) translateY(80px)}30%{-webkit-transform:translateX(35px) translateY(75px);transform:translateX(35px) translateY(75px)}40%{-webkit-transform:translateX(40px) translateY(60px);transform:translateX(40px) translateY(60px)}50%{opacity:1;-webkit-transform:translateX(35px) translateY(40px);transform:translateX(35px) translateY(40px)}100%{opacity:0;-webkit-transform:translateX(10px) translateY(0) scale(.5);transform:translateX(10px) translateY(0) scale(.5)}}@-o-keyframes headerLogoStrong{0%{opacity:1;-o-transform:translateX(-30px) translateY(70px);transform:translateX(-30px) translateY(70px)}20%{-o-transform:translateX(15px) translateY(80px);transform:translateX(15px) translateY(80px)}30%{-o-transform:translateX(35px) translateY(75px);transform:translateX(35px) translateY(75px)}40%{-o-transform:translateX(40px) translateY(60px);transform:translateX(40px) translateY(60px)}50%{opacity:1;-o-transform:translateX(35px) translateY(40px);transform:translateX(35px) translateY(40px)}100%{opacity:0;-o-transform:translateX(10px) translateY(0) scale(.5);transform:translateX(10px) translateY(0) scale(.5)}}@keyframes headerLogoStrong{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(70px);-o-transform:translateX(-30px) translateY(70px);transform:translateX(-30px) translateY(70px)}20%{-webkit-transform:translateX(15px) translateY(80px);-o-transform:translateX(15px) translateY(80px);transform:translateX(15px) translateY(80px)}30%{-webkit-transform:translateX(35px) translateY(75px);-o-transform:translateX(35px) translateY(75px);transform:translateX(35px) translateY(75px)}40%{-webkit-transform:translateX(40px) translateY(60px);-o-transform:translateX(40px) translateY(60px);transform:translateX(40px) translateY(60px)}50%{opacity:1;-webkit-transform:translateX(35px) translateY(40px);-o-transform:translateX(35px) translateY(40px);transform:translateX(35px) translateY(40px)}100%{opacity:0;-webkit-transform:translateX(10px) translateY(0) scale(.5);-o-transform:translateX(10px) translateY(0) scale(.5);transform:translateX(10px) translateY(0) scale(.5)}}@-webkit-keyframes headerLogoP{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(60px);transform:translateX(-30px) translateY(60px)}20%{-webkit-transform:translateX(10px) translateY(60px);transform:translateX(10px) translateY(60px)}30%{-webkit-transform:translateX(20px) translateY(60px);transform:translateX(20px) translateY(60px)}40%{-webkit-transform:translateX(25px) translateY(55px);transform:translateX(25px) translateY(55px)}50%{opacity:1;-webkit-transform:translateX(32px) translateY(40px);transform:translateX(32px) translateY(40px)}100%,90%{opacity:0;-webkit-transform:translateX(35px) translateY(20px) scale(.5);transform:translateX(35px) translateY(20px) scale(.5)}}@-o-keyframes headerLogoP{0%{opacity:1;-o-transform:translateX(-30px) translateY(60px);transform:translateX(-30px) translateY(60px)}20%{-o-transform:translateX(10px) translateY(60px);transform:translateX(10px) translateY(60px)}30%{-o-transform:translateX(20px) translateY(60px);transform:translateX(20px) translateY(60px)}40%{-o-transform:translateX(25px) translateY(55px);transform:translateX(25px) translateY(55px)}50%{opacity:1;-o-transform:translateX(32px) translateY(40px);transform:translateX(32px) translateY(40px)}100%,90%{opacity:0;-o-transform:translateX(35px) translateY(20px) scale(.5);transform:translateX(35px) translateY(20px) scale(.5)}}@keyframes headerLogoP{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(60px);-o-transform:translateX(-30px) translateY(60px);transform:translateX(-30px) translateY(60px)}20%{-webkit-transform:translateX(10px) translateY(60px);-o-transform:translateX(10px) translateY(60px);transform:translateX(10px) translateY(60px)}30%{-webkit-transform:translateX(20px) translateY(60px);-o-transform:translateX(20px) translateY(60px);transform:translateX(20px) translateY(60px)}40%{-webkit-transform:translateX(25px) translateY(55px);-o-transform:translateX(25px) translateY(55px);transform:translateX(25px) translateY(55px)}50%{opacity:1;-webkit-transform:translateX(32px) translateY(40px);-o-transform:translateX(32px) translateY(40px);transform:translateX(32px) translateY(40px)}100%,90%{opacity:0;-webkit-transform:translateX(35px) translateY(20px) scale(.5);-o-transform:translateX(35px) translateY(20px) scale(.5);transform:translateX(35px) translateY(20px) scale(.5)}}@-webkit-keyframes headerLogoLink{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(90px);transform:translateX(-30px) translateY(90px)}20%{-webkit-transform:translateX(15px) translateY(100px);transform:translateX(15px) translateY(100px)}30%{-webkit-transform:translateX(25px) translateY(95px);transform:translateX(25px) translateY(95px)}40%{-webkit-transform:translateX(30px) translateY(80px);transform:translateX(30px) translateY(80px)}50%{opacity:1;-webkit-transform:translateX(30px) translateY(70px);transform:translateX(30px) translateY(70px)}100%,90%{opacity:0;-webkit-transform:translateX(10px) translateY(0) scale(.5);transform:translateX(10px) translateY(0) scale(.5)}}@-o-keyframes headerLogoLink{0%{opacity:1;-o-transform:translateX(-30px) translateY(90px);transform:translateX(-30px) translateY(90px)}20%{-o-transform:translateX(15px) translateY(100px);transform:translateX(15px) translateY(100px)}30%{-o-transform:translateX(25px) translateY(95px);transform:translateX(25px) translateY(95px)}40%{-o-transform:translateX(30px) translateY(80px);transform:translateX(30px) translateY(80px)}50%{opacity:1;-o-transform:translateX(30px) translateY(70px);transform:translateX(30px) translateY(70px)}100%,90%{opacity:0;-o-transform:translateX(10px) translateY(0) scale(.5);transform:translateX(10px) translateY(0) scale(.5)}}@keyframes headerLogoLink{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(90px);-o-transform:translateX(-30px) translateY(90px);transform:translateX(-30px) translateY(90px)}20%{-webkit-transform:translateX(15px) translateY(100px);-o-transform:translateX(15px) translateY(100px);transform:translateX(15px) translateY(100px)}30%{-webkit-transform:translateX(25px) translateY(95px);-o-transform:translateX(25px) translateY(95px);transform:translateX(25px) translateY(95px)}40%{-webkit-transform:translateX(30px) translateY(80px);-o-transform:translateX(30px) translateY(80px);transform:translateX(30px) translateY(80px)}50%{opacity:1;-webkit-transform:translateX(30px) translateY(70px);-o-transform:translateX(30px) translateY(70px);transform:translateX(30px) translateY(70px)}100%,90%{opacity:0;-webkit-transform:translateX(10px) translateY(0) scale(.5);-o-transform:translateX(10px) translateY(0) scale(.5);transform:translateX(10px) translateY(0) scale(.5)}}@-webkit-keyframes headerLogoBlockquote{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(55px);transform:translateX(-30px) translateY(55px)}30%{-webkit-transform:translateX(5px) translateY(50px);transform:translateX(5px) translateY(50px)}40%{-webkit-transform:translateX(10px) translateY(45px);transform:translateX(10px) translateY(45px)}50%{opacity:1;-webkit-transform:translateX(13px) translateY(40px);transform:translateX(13px) translateY(40px)}100%{opacity:0;-webkit-transform:translateX(10px) translateY(10px) scale(.5);transform:translateX(10px) translateY(10px) scale(.5)}}@-o-keyframes headerLogoBlockquote{0%{opacity:1;-o-transform:translateX(-30px) translateY(55px);transform:translateX(-30px) translateY(55px)}30%{-o-transform:translateX(5px) translateY(50px);transform:translateX(5px) translateY(50px)}40%{-o-transform:translateX(10px) translateY(45px);transform:translateX(10px) translateY(45px)}50%{opacity:1;-o-transform:translateX(13px) translateY(40px);transform:translateX(13px) translateY(40px)}100%{opacity:0;-o-transform:translateX(10px) translateY(10px) scale(.5);transform:translateX(10px) translateY(10px) scale(.5)}}@keyframes headerLogoBlockquote{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(55px);-o-transform:translateX(-30px) translateY(55px);transform:translateX(-30px) translateY(55px)}30%{-webkit-transform:translateX(5px) translateY(50px);-o-transform:translateX(5px) translateY(50px);transform:translateX(5px) translateY(50px)}40%{-webkit-transform:translateX(10px) translateY(45px);-o-transform:translateX(10px) translateY(45px);transform:translateX(10px) translateY(45px)}50%{opacity:1;-webkit-transform:translateX(13px) translateY(40px);-o-transform:translateX(13px) translateY(40px);transform:translateX(13px) translateY(40px)}100%{opacity:0;-webkit-transform:translateX(10px) translateY(10px) scale(.5);-o-transform:translateX(10px) translateY(10px) scale(.5);transform:translateX(10px) translateY(10px) scale(.5)}}@-webkit-keyframes headerLogoViewHtml{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(90px);transform:translateX(-30px) translateY(90px)}40%{-webkit-transform:translateX(30px) translateY(105px);transform:translateX(30px) translateY(105px)}50%{-webkit-transform:translateX(40px) translateY(100px);transform:translateX(40px) translateY(100px)}60%{opacity:1;-webkit-transform:translateX(50px) translateY(90px);transform:translateX(50px) translateY(90px)}100%{opacity:0;-webkit-transform:translateX(70px) translateY(70px) scale(.5);transform:translateX(70px) translateY(70px) scale(.5)}}@-o-keyframes headerLogoViewHtml{0%{opacity:1;-o-transform:translateX(-30px) translateY(90px);transform:translateX(-30px) translateY(90px)}40%{-o-transform:translateX(30px) translateY(105px);transform:translateX(30px) translateY(105px)}50%{-o-transform:translateX(40px) translateY(100px);transform:translateX(40px) translateY(100px)}60%{opacity:1;-o-transform:translateX(50px) translateY(90px);transform:translateX(50px) translateY(90px)}100%{opacity:0;-o-transform:translateX(70px) translateY(70px) scale(.5);transform:translateX(70px) translateY(70px) scale(.5)}}@keyframes headerLogoViewHtml{0%{opacity:1;-webkit-transform:translateX(-30px) translateY(90px);-o-transform:translateX(-30px) translateY(90px);transform:translateX(-30px) translateY(90px)}40%{-webkit-transform:translateX(30px) translateY(105px);-o-transform:translateX(30px) translateY(105px);transform:translateX(30px) translateY(105px)}50%{-webkit-transform:translateX(40px) translateY(100px);-o-transform:translateX(40px) translateY(100px);transform:translateX(40px) translateY(100px)}60%{opacity:1;-webkit-transform:translateX(50px) translateY(90px);-o-transform:translateX(50px) translateY(90px);transform:translateX(50px) translateY(90px)}100%{opacity:0;-webkit-transform:translateX(70px) translateY(70px) scale(.5);-o-transform:translateX(70px) translateY(70px) scale(.5);transform:translateX(70px) translateY(70px) scale(.5)}}.section{position:relative;padding-bottom:100px}.section-primary{color:#b65207;background:-webkit-gradient(linear,right top,left bottom,from(#ff974a),to(#ffb864));background:-webkit-linear-gradient(top right,#ff974a,#ffb864);background:-o-linear-gradient(top right,#ff974a,#ffb864);background:linear-gradient(to bottom left,#ff974a,#ffb864)}.section-primary .section-title{color:#fff}.section-primary a{color:#fff}.section-secondary{background:#f4f7fa}.section h4{font-weight:400;color:#392813;font-size:28px;padding-top:80px}.section-title{text-align:center;font-family:Panton,sans-serif;font-size:100px;color:#ff974a;padding-top:100px;padding-bottom:30px}.section-subtitle{margin-top:-22px;font-size:23px;color:#392813;font-weight:400}.section-introduction{margin-top:50px;padding-bottom:70px}.section-introduction .introduction-section{height:300px}.section-introduction .introduction-section-col{position:relative;float:left;width:30%;margin-right:5%}.section-introduction .introduction-section-col:last-child{margin-right:0}.section-introduction .introduction-section-col-title{font-size:28px;font-weight:300;margin-bottom:0;padding-top:50px}.section-introduction .introduction-section-col-title img{height:40px;margin-right:8px;-webkit-transform:translateY(-3px);-ms-transform:translateY(-3px);-o-transform:translateY(-3px);transform:translateY(-3px)}.section-introduction .introduction-section-col-title img.illu-lightweight{-webkit-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}.section-introduction .introduction-section-col-description{font-size:18px;line-height:28px;margin-top:10px}.installation-first-step{height:400px;color:#392813;text-align:center;padding-top:10px;margin:20px 0 50px;border:1px solid rgba(0,0,0,.15);border-radius:8px}.installation-first-step code{display:block}.installation-first-step .installation-download,.installation-first-step .installation-package-managers{width:48%;float:left}.installation-first-step .installation-col-title{font-size:22px;padding:30px 0;color:#392813}.installation-first-step .button{display:block;color:#392813;border-color:rgba(0,0,0,.07);background-color:transparent;-webkit-box-shadow:0 0 50px rgba(0,0,0,.03);box-shadow:0 0 50px rgba(0,0,0,.03);-webkit-transition:background-color 150ms,color 150ms,-webkit-box-shadow 150ms;transition:background-color 150ms,color 150ms,-webkit-box-shadow 150ms;-o-transition:background-color 150ms,box-shadow 150ms,color 150ms;transition:background-color 150ms,box-shadow 150ms,color 150ms;transition:background-color 150ms,box-shadow 150ms,color 150ms,-webkit-box-shadow 150ms}.installation-first-step .button:hover{text-decoration:none;color:#fff;border-color:transparent;background-color:#ff974a;-webkit-box-shadow:0 0 50px rgba(255,151,74,.4);box-shadow:0 0 50px rgba(255,151,74,.4)}.installation-first-step .installation-or{position:relative;font-weight:700;text-transform:uppercase;width:4%;float:left;font-size:18px;margin:140px 0 20px;border-radius:50px;z-index:0}.installation-first-step .installation-or::before{content:"";display:block;position:absolute;top:50%;left:50%;z-index:-1;width:100%;padding-top:100%;min-width:45px;min-height:45px;background:#fff;border-radius:100%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);-o-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.installation-first-step .installation-package-managers{font-size:18px}.installation-first-step .installation-package-managers code{padding-top:5px;line-height:1.6}.installation-first-step .installation-package-managers code+code{padding-top:0}.installation-first-step .installation-cdn{clear:both;width:100%}.languages p{text-align:center;margin:0 0 5px;font-size:18px;color:#fff}.languages p a{color:#b65207}.languages .languages-columns{display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex}.languages .col-globe{-webkit-box-flex:1.07;-webkit-flex:1.07;-moz-box-flex:1.07;-ms-flex:1.07;flex:1.07;margin-top:30px}.languages .col-globe .globe{width:90%;-webkit-transform:translateX(-40px);-ms-transform:translateX(-40px);-o-transform:translateX(-40px);transform:translateX(-40px)}.languages .col-list{-webkit-box-flex:.93;-webkit-flex:.93;-moz-box-flex:.93;-ms-flex:.93;flex:.93;padding-top:80px;line-height:1.6;font-weight:400}.languages .col-list .continent-name{color:#fff;font-size:22px;font-weight:400;background:0 0;border:none;padding:0;margin:0}.languages .col-list .continent-name:focus{outline:0}.languages .col-list .continent-name::after{content:"";display:inline-block;height:0;width:0;border:6px solid transparent;border-left-color:#fff;-webkit-transform:translateX(5px) translateY(-1px);-ms-transform:translateX(5px) translateY(-1px);-o-transform:translateX(5px) translateY(-1px);transform:translateX(5px) translateY(-1px)}.languages .col-list li[style] .continent-name::after{border-left-color:transparent;border-top-color:#fff;-webkit-transform:translateX(2px) translateY(2px);-ms-transform:translateX(2px) translateY(2px);-o-transform:translateX(2px) translateY(2px);transform:translateX(2px) translateY(2px)}.languages .col-list .lang-code{display:inline-block;min-width:30px;padding-right:8px;opacity:.6;-webkit-transition:opacity 150ms;-o-transition:opacity 150ms;transition:opacity 150ms}.languages .col-list .lang-name{-webkit-transition:padding-left 150ms;-o-transition:padding-left 150ms;transition:padding-left 150ms}.languages .col-list a{color:#b65207;-webkit-transition:color 150ms;-o-transition:color 150ms;transition:color 150ms}.languages .col-list a:hover{color:#fff;text-decoration:none}.languages .col-list a:hover .lang-code{opacity:1}.languages .col-list a:hover .lang-name{padding-left:8px}.languages .col-list li,.languages .col-list ul{margin:0;padding:0;list-style:none}.languages .col-list>ul{padding-right:50px}.languages .col-list>ul>li{overflow:hidden;-webkit-transition:height 150ms linear;-o-transition:height 150ms linear;transition:height 150ms linear}.languages .col-list>ul>li[data-height]{height:30px}.languages .col-list>ul>li ul{padding:15px 0;-webkit-columns:2;-moz-columns:2;columns:2;line-height:1.8}.plugins-packages .wrapper{display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex}.plugins-packages .col-plugins{-webkit-box-flex:1.07;-webkit-flex:1.07;-moz-box-flex:1.07;-ms-flex:1.07;flex:1.07}.plugins-packages .col-packages{-webkit-box-flex:.93;-webkit-flex:.93;-moz-box-flex:.93;-ms-flex:.93;flex:.93}.plugins-packages .section-title{text-align:left;-webkit-transform:translateX(-5px);-ms-transform:translateX(-5px);-o-transform:translateX(-5px);transform:translateX(-5px)}.plugins-packages p{padding:0 100px 0 0;line-height:1.6}.plugins-packages li,.plugins-packages ul{margin:0;padding:0;list-style:none}.plugins-packages ul{-webkit-columns:2;-moz-columns:2;columns:2;margin:50px 0 0;max-width:500px}.plugins-packages li a{position:relative;display:inline-block;overflow:visible;color:#392813;-webkit-transition:color 150ms,-webkit-transform 150ms;transition:color 150ms,-webkit-transform 150ms;-o-transition:color 150ms,-o-transform 150ms;transition:color 150ms,transform 150ms;transition:color 150ms,transform 150ms,-webkit-transform 150ms,-o-transform 150ms;line-height:1.8}.plugins-packages li a:hover{color:#ff974a;text-decoration:none}.plugins-packages li a img,.plugins-packages li a svg{display:inline-block;color:#ff974a;fill:#ff974a;width:20px;height:20px;margin-right:8px;vertical-align:sub}.donate-container{position:relative;max-width:800px;margin:0 auto}.donate-container .donate-description{width:100%;padding:30px 50px 50px;background:#f4f7fa;border-top-left-radius:6px;border-top-right-radius:6px}.donate-container .donate-footer{text-align:center;padding:50px 0;background:#ff974a -webkit-gradient(linear,right top,left bottom,from(#ff974a),to(#ffb864));background:#ff974a -webkit-linear-gradient(top right,#ff974a,#ffb864);background:#ff974a -o-linear-gradient(top right,#ff974a,#ffb864);background:#ff974a linear-gradient(to bottom left,#ff974a,#ffb864);border-bottom-left-radius:6px;border-bottom-right-radius:6px}.donate-container .donate-beer{position:absolute;left:-190px;bottom:-30px;width:250px}.donate-container .button{color:#fff;text-decoration:none}.donate-container .button:focus,.donate-container .button:hover{text-decoration:none;background:#fff;color:#ff974a}.sponsors-container{margin:150px 0 0;text-align:center}.sponsors-container a{padding:0 20px}.footer{margin-top:40px;text-align:center;color:#fff;background:-webkit-gradient(linear,right top,left bottom,from(#ff974a),to(#ffb864));background:-webkit-linear-gradient(top right,#ff974a,#ffb864);background:-o-linear-gradient(top right,#ff974a,#ffb864);background:linear-gradient(to bottom left,#ff974a,#ffb864);clear:both}.footer .footer-link,.footer .footer-text{display:inline-block;padding:40px}.footer .footer-text{padding-bottom:0}.footer .footer-text .hearts{font-size:18px;color:#b65207}.footer .footer-link{text-decoration:none;color:#fff;-webkit-transition:color .15s;-o-transition:color .15s;transition:color .15s}.footer .footer-link:focus,.footer .footer-link:hover{color:#b65207}/*! Theme: GitHub Description: Light theme as seen on github.com Author: github.com Maintainer: @Hirse Updated: 2021-05-15 Outdated base version: https://github.com/primer/github-syntax-light Current colors taken from GitHub's CSS */pre code.hljs{display:block;overflow-x:auto;padding:0 1.5em;border-radius:8px;-webkit-box-shadow:0 4px 16px rgba(0,0,0,.02);box-shadow:0 4px 16px rgba(0,0,0,.02)}code.hljs{padding:3px 5px}.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-comment{font-style:italic}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}.documentation-body{background:#f4f7fa;display:-webkit-box;display:-webkit-flex;display:-moz-box;display:-ms-flexbox;display:flex}.documentation-body .main{padding:0 60px;height:100vh;width:78%;overflow:auto}.documentation-body .main-demos{padding:0;overflow:hidden}.documentation-body .main-demos iframe{width:100%;height:100vh;background:#f4f7fa}.documentation-body .main-demo-inner{width:100%}.documentation-body .section-title{padding:50px 0 0}.documentation-body h3{display:inline-block;font-weight:700;font-size:26px}.documentation-body h4{padding:20px 0 0}.documentation-body dd :not(pre)>code,.documentation-body dd>code,.documentation-body p :not(pre)>code,.documentation-body p>code,.documentation-body ul :not(pre)>code,.documentation-body ul>code{color:#616870;background:#dfe5eb;padding:0 5px;border-radius:4px}.documentation-body dd a code,.documentation-body p a code,.documentation-body ul a code{color:inherit}.documentation-body code.type{padding:0 3px;color:#b65207}.documentation-body code.type::before{content:"<"}.documentation-body code.type::after{content:">"}.documentation-body .button.button-demo{border-color:rgba(244,141,64,.6);color:#f48d40;width:auto;padding:10px 30px}.documentation-body .button.button-demo:focus,.documentation-body .button.button-demo:hover{text-decoration:none;border-color:#f48d40;background:#f48d40;color:#fff}.documentation-body ::-webkit-scrollbar{width:17px}.documentation-body ::-webkit-scrollbar-track{background:#f4f7fa}.documentation-body ::-webkit-scrollbar-thumb{background:#cfd7de;border:2px solid #f4f7fa}.documentation-body ::-webkit-scrollbar-thumb:hover{background:#616870}.header-documentation{margin-top:50px}.header-documentation .documentation-logo-link{display:block;margin:0 auto;width:80%;max-width:350px}.header-documentation .documentation-logo-link .documentation-logo{width:100%}.header-documentation .documentation-title{text-align:center;font-family:Panton,sans-serif;font-weight:100;line-height:1}@media (max-width:1290px){.header-documentation .documentation-title{font-size:2.5vw}}.header-documentation .documentation-menu{text-align:center;margin-top:40px;background:#f48d40}.header-documentation .documentation-menu a{display:inline-block;padding:20px 5px}.header-documentation .documentation-menu a:focus,.header-documentation .documentation-menu a:hover{text-decoration:underline}@media (max-width:1550px){.header-documentation .documentation-menu{padding:5px 7%}.header-documentation .documentation-menu a{width:48%;padding:10px 0}.header-documentation .documentation-menu .documentation-menu-dot{display:none}}.sidebar{position:relative;top:0;left:0;height:100vh;color:#fff;background:#ff974a -webkit-gradient(linear,right top,left bottom,from(#ff974a),to(#ffb864));background:#ff974a -webkit-linear-gradient(top right,#ff974a,#ffb864);background:#ff974a -o-linear-gradient(top right,#ff974a,#ffb864);background:#ff974a linear-gradient(to bottom left,#ff974a,#ffb864);width:22%}.sidebar ::-webkit-scrollbar-track{background:0 0}.sidebar ::-webkit-scrollbar-thumb{background:#ffb864;border:1px solid #ffb864;border-right:none}.sidebar ::-webkit-scrollbar-thumb:hover{background:#fff}.sidebar::after{content:"";display:block;position:absolute;left:0;bottom:70px;width:calc(100% - 17px);height:100px;background:-webkit-gradient(linear,left top,left bottom,from(rgba(255,184,100,0)),to(#ffb864));background:-webkit-linear-gradient(top,rgba(255,184,100,0),#ffb864);background:-o-linear-gradient(top,rgba(255,184,100,0),#ffb864);background:linear-gradient(to bottom,rgba(255,184,100,0),#ffb864);pointer-events:none}.sidebar .sidebar-inner{overflow:auto;overflow-y:scroll;overflow-x:hidden;height:calc(100vh - 70px)}.sidebar li,.sidebar ul{padding:0;margin:0;list-style:none}.sidebar a{text-decoration:none;color:#fff;-webkit-transition:color 150ms,text-indent 150ms;-o-transition:color 150ms,text-indent 150ms;transition:color 150ms,text-indent 150ms}.sidebar .documentation-summary{position:relative;font-size:18px;margin-bottom:100px}.sidebar .documentation-summary>ul{max-width:340px;margin:0 auto;padding:0 30px}.sidebar .documentation-summary>ul .documentation-summary-title,.sidebar .documentation-summary>ul a{display:block;height:30px;line-height:30px;text-overflow:ellipsis;width:100%;white-space:nowrap;overflow:hidden}.sidebar .documentation-summary>ul>li{margin-top:30px}.sidebar .documentation-summary>ul>li:first-child{margin-top:50px}.sidebar .documentation-summary>ul>li .documentation-summary-title,.sidebar .documentation-summary>ul>li>a{font-weight:600;text-transform:uppercase;margin-bottom:5px}.sidebar .documentation-summary>ul>li>a:focus,.sidebar .documentation-summary>ul>li>a:hover{color:#b65207}.sidebar .documentation-summary>ul>li ul li a:focus,.sidebar .documentation-summary>ul>li ul li a:hover{color:#b65207;text-indent:10px}.sidebar .documentation-sidebar-beer{position:fixed;width:22%;bottom:0;left:0;background:#fff;height:70px}.sidebar .documentation-sidebar-beer a{position:relative;display:block;height:70px;width:100%;text-align:left;padding:10px 17px 0 0;background:0 0;border:none;border-top:1px solid #e9eef3;color:#9ca4ac}.sidebar .documentation-sidebar-beer a:focus{outline:0}.sidebar .documentation-sidebar-beer a .beer-icon{display:none}@media (min-width:1700px){.sidebar .documentation-sidebar-beer a .beer-icon{position:absolute;display:block;width:calc((100% - 236px)/ 2);height:50px;margin:0 auto}}.sidebar .documentation-sidebar-beer a .beer-label{position:relative;display:block;max-width:236px;margin:0 auto}@media (max-width:1290px){.sidebar .documentation-sidebar-beer a .beer-label{font-size:14px;margin:0;padding:7px 0 0 15px;width:200px}}.added-feature,.deprecated-feature{display:inline-block;padding:3px 13px;margin:0;color:#fff;font-size:14px;border-radius:50px;-webkit-transform:translateX(10px) translateY(-4px);-ms-transform:translateX(10px) translateY(-4px);-o-transform:translateX(10px) translateY(-4px);transform:translateX(10px) translateY(-4px)}.added-feature{background:#5ecb0e}.deprecated-feature{background:#ff9a4d}.deprecated-info{padding:7px 15px;border-radius:4px;background:#f2dfc1;border:1px solid #ff9a4d}.deprecated-info a{color:#ff9a4d}.version-tag{display:inline-block;padding:2px 6px;background:#5ecb0e;color:#fff;font-size:12px;font-style:normal;border-radius:20px}.note .version-tag{-webkit-transform:translateY(-1px);-ms-transform:translateY(-1px);-o-transform:translateY(-1px);transform:translateY(-1px)}.feature{position:relative;padding-bottom:60px}.feature::after{content:" ";display:block;position:absolute;bottom:0;left:50%;width:40%;height:0;border-bottom:1px solid #ffb864;-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);-o-transform:translateX(-50%);transform:translateX(-50%)}.feature h3,.feature h4{display:inline-block;position:relative}.feature h3{padding-top:50px;margin-left:-15px;padding-left:15px}.feature h4{font-size:20px;font-weight:700}.feature h3+a+h4,.feature h3+h4{display:block}.feature .title-link{display:block;position:absolute;left:-45px;height:30px;width:30px;opacity:0;text-align:center;text-decoration:none;background:#ff974a;border-radius:50%;-webkit-transform:translateY(-36px);-ms-transform:translateY(-36px);-o-transform:translateY(-36px);transform:translateY(-36px);-webkit-transition:opacity 150ms,background 150ms,color 150ms;-o-transition:opacity 150ms,background 150ms,color 150ms;transition:opacity 150ms,background 150ms,color 150ms}.feature .title-link:focus,.feature .title-link:hover{background:#fff;text-decoration:none}.feature .title-link:focus svg,.feature .title-link:hover svg{fill:#ff974a}.feature .title-link svg{fill:#fff;width:70%;height:100%}.feature h4+.title-link{-webkit-transform:translateY(-30px);-ms-transform:translateY(-30px);-o-transform:translateY(-30px);transform:translateY(-30px)}.feature:hover .title-link{opacity:1}.feature .trumbowyg-box,.feature .trumbowyg-editor{margin:24px auto}.sample-data{background:#fff;padding:25px}.sample-data h4{padding:0}.sample-data input{width:100%;border:none;padding:5px 7px;color:#616870;background:#dfe5eb}.sample-data input:not(:last-child){margin-bottom:10px}dl dt{display:inline-block;color:#1b2126;background:#dfe5eb;padding:0 4px 0 6px;border-radius:4px}dl dd{padding:5px 0 15px;margin-left:15px}dl dd ul{margin:.5em 0}.tree{list-style:none;background:#fff;padding:1em 1.5em;border-radius:8px;-webkit-box-shadow:0 4px 16px rgba(0,0,0,.02);box-shadow:0 4px 16px rgba(0,0,0,.02)}.tree ul{position:relative;list-style:none;margin:0;padding-left:8px;overflow:hidden}.tree li{padding-top:8px}.tree>li{padding-top:0}.tree ul li{position:relative;padding-left:16px}.tree ul li::after,.tree ul li::before{content:"";display:block;position:absolute;background:#ff974a}.tree ul li::before{top:20px;left:0;width:12px;height:1px}.tree ul li::after{left:0;bottom:calc(100% - 20px);width:1px;height:500px} ================================================ FILE: docs/demos/core/dark-theme.html ================================================ Trumbowyg by Alex-D

Dark theme

Basic usage

Gives you a dark button pane.

For demo purpose, this page has been forced to be in dark mode.

The code


<!-- Wrap the editor with an element with the class trumbowyg-dark -->
<div class="trumbowyg-dark">
    <textarea id="editor"></textarea>
</div>
            

$('#editor').trumbowyg();
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/core/default.html ================================================ Trumbowyg by Alex-D

Default

Basic usage

No plugin, no options. Just naked Trumbowyg.

This editor is the default build of Trumbowyg.

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor').trumbowyg();
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/core/simple.html ================================================ Trumbowyg by Alex-D

Simple

Basic usage

Only strong (bold), emphasis (italic), some align, image and link.

Colllect — See more at https://colllect.io

Colllect

The code


$('#editor').trumbowyg({
    btns: [
        ['strong', 'em'],
        ['justifyLeft', 'justifyCenter'],
        ['insertImage', 'link']
    ]
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/index.html ================================================ Demos | Trumbowyg: A lightweight WYSIWYG editor | Alex-D / Alexandre Demode
================================================ FILE: docs/demos/js/highlight.js ================================================ !function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){var n=(e.className+" "+(e.parentNode?e.parentNode.className:"")).split(/\s+/);return n=n.map(function(e){return e.replace(/^lang(uage)?-/,"")}),n.filter(function(e){return N(e)||/no(-?)highlight/.test(e)})[0]}function o(e,n){var t={};for(var r in e)t[r]=e[r];if(n)for(var r in n)t[r]=n[r];return t}function i(e){var n=[];return function r(e,a){for(var o=e.firstChild;o;o=o.nextSibling)3==o.nodeType?a+=o.nodeValue.length:1==o.nodeType&&(n.push({event:"start",offset:a,node:o}),a=r(o,a),t(o).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:o}));return a}(e,0),n}function c(e,r,a){function o(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function c(e){l+=""}function u(e){("start"==e.event?i:c)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=o();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(c);do u(g.splice(0,1)[0]),g=o();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(i)}else"start"==g[0].event?f.push(g[0].node):f.pop(),u(g.splice(0,1)[0])}return l+n(a.substr(s))}function u(e){function n(e){return e&&e.source||e}function t(t,r){return RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var c={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");c[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):Object.keys(a.k).forEach(function(e){u(e,a.k[e])}),a.k=c}a.lR=t(a.l||/\b[A-Za-z0-9_]+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function s(e,t,a,o){function i(e,n){for(var t=0;t";return o+=e+'">',o+n+i}function d(){if(!w.k)return n(y);var e="",t=0;w.lR.lastIndex=0;for(var r=w.lR.exec(y);r;){e+=n(y.substr(t,r.index-t));var a=g(w,r);a?(B+=a[1],e+=p(a[0],n(r[0]))):e+=n(r[0]),t=w.lR.lastIndex,r=w.lR.exec(y)}return e+n(y.substr(t))}function h(){if(w.sL&&!R[w.sL])return n(y);var e=w.sL?s(w.sL,y,!0,L[w.sL]):l(y);return w.r>0&&(B+=e.r),"continuous"==w.subLanguageMode&&(L[w.sL]=e.top),p(e.language,e.value,!1,!0)}function v(){return void 0!==w.sL?h():d()}function b(e,t){var r=e.cN?p(e.cN,"",!0):"";e.rB?(M+=r,y=""):e.eB?(M+=n(t)+r,y=""):(M+=r,y=t),w=Object.create(e,{parent:{value:w}})}function m(e,t){if(y+=e,void 0===t)return M+=v(),0;var r=i(t,w);if(r)return M+=v(),b(r,t),r.rB?0:t.length;var a=c(w,t);if(a){var o=w;o.rE||o.eE||(y+=t),M+=v();do w.cN&&(M+=""),B+=w.r,w=w.parent;while(w!=a.parent);return o.eE&&(M+=n(t)),y="",a.starts&&b(a.starts,""),o.rE?0:t.length}if(f(t,w))throw new Error('Illegal lexeme "'+t+'" for mode "'+(w.cN||"")+'"');return y+=t,t.length||1}var x=N(e);if(!x)throw new Error('Unknown language: "'+e+'"');u(x);for(var w=o||x,L={},M="",k=w;k!=x;k=k.parent)k.cN&&(M=p(k.cN,"",!0)+M);var y="",B=0;try{for(var C,j,I=0;;){if(w.t.lastIndex=I,C=w.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}m(t.substr(I));for(var k=w;k.parent;k=k.parent)k.cN&&(M+="");return{r:B,value:M,language:e,top:w}}catch(A){if(-1!=A.message.indexOf("Illegal"))return{r:0,value:n(t)};throw A}}function l(e,t){t=t||E.languages||Object.keys(R);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(N(n)){var t=s(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function f(e){return E.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,E.tabReplace)})),E.useBR&&(e=e.replace(/\n/g,"
")),e}function g(e,n,t){var r=n?x[n]:t,a=[e.trim()];return e.match(/(\s|^)hljs(\s|$)/)||a.push("hljs"),r&&a.push(r),a.join(" ").trim()}function p(e){var n=a(e);if(!/no(-?)highlight/.test(n)){var t;E.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?s(n,r,!0):l(r),u=i(t);if(u.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(u,i(p),r)}o.value=f(o.value),e.innerHTML=o.value,e.className=g(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){E=o(E,e)}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",h,!1),addEventListener("load",h,!1)}function b(n,t){var r=R[n]=t(e);r.aliases&&r.aliases.forEach(function(e){x[e]=n})}function m(){return Object.keys(R)}function N(e){return R[e]||R[x[e]]}var E={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},R={},x={};return e.highlight=s,e.highlightAuto=l,e.fixMarkup=f,e.highlightBlock=p,e.configure=d,e.initHighlighting=h,e.initHighlightingOnLoad=v,e.registerLanguage=b,e.listLanguages=m,e.getLanguage=N,e.inherit=o,e.IR="[a-zA-Z][a-zA-Z0-9_]*",e.UIR="[a-zA-Z_][a-zA-Z0-9_]*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.CLCM={cN:"comment",b:"//",e:"$",c:[e.PWM]},e.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[e.PWM]},e.HCM={cN:"comment",b:"#",e:"$",c:[e.PWM]},e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("xml",function(){var t="[A-Za-z0-9\\._:-]+",e={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"},c={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[c],starts:{e:"",rE:!0,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[c],starts:{e:"",rE:!0,sL:"javascript"}},e,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:/[^ \/><\n\t]+/,r:0},c]}]}});hljs.registerLanguage("javascript",function(r){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document"},c:[{cN:"pi",r:10,v:[{b:/^\s*('|")use strict('|")/},{b:/^\s*('|")use asm('|")/}]},r.ASM,r.QSM,r.CLCM,r.CBCM,r.CNM,{b:"("+r.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[r.CLCM,r.CBCM,r.RM,{b:/;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[r.inherit(r.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[r.CLCM,r.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+r.IR,r:0}]}}); hljs.initHighlightingOnLoad(); ================================================ FILE: docs/demos/js/loader.js ================================================ var isOnline = window.location.hostname.indexOf('github.io') !== -1; var baseURL = isOnline ? '//rawcdn.githack.com/Alex-D/Trumbowyg/v2.31.0/' : '../../../'; var styleLoadingContainer = document.querySelector('.loading-head'); var scriptLoadingContainer = document.querySelector('.loading-body'); function loadTag(tagToInsert, container, comment, tagForDocumentation) { 'use strict'; document.write(tagToInsert); var html = ''; if (container.innerHTML.trim().length > 0) { html = '\n' + container.innerHTML.trim() + '\n'; } if (comment !== undefined) { html += '\n<!-- ' + comment + ' -->'; } html += tagForDocumentation.replace(/', styleLoadingContainer, comment, '\n\n\n' ); } function loadScript(scriptPath, comment) { 'use strict'; if (!isOnline) { scriptPath = scriptPath.replace('.min', ''); } loadTag( '', scriptLoadingContainer, comment, '\n\n\n' ); } (function($) { 'use strict'; $('a').click(function() { window.top.location = $(this).attr('href'); return false; }); })(jQuery); ================================================ FILE: docs/demos/js/runExampleCode.js ================================================ (function ($) { 'use strict'; $('.js-code-to-eval').each(function () { eval($(this).text()); // jshint ignore:line $(this).text( $(this).text() .replace(/'Client-ID\s[a-z0-9]+'/, '\'Client-ID xxxxxxxxxxxx\'') .replace(/apiKey:\s+'.*'/, 'apiKey: \'xxxxxxxxxxxx\'') ); }); })(jQuery); ================================================ FILE: docs/demos/plugins/allowtagsfrompaste.html ================================================  Allow tags from paste plugin | Trumbowyg

Allow tags from paste plugin

Basic usage

This plugin allows you to filter tags allowed when an user paste some code into the editor.

Read allow tags from paste plugin documentation

Try to paste something!

Only `h2`, `p` `strong` and `br` tags are allowed! All other tags will be removed.

Some text to copy to test

This table will be not pasted:

h4 must be kept but not this table
Second line will be striped out too

The code


$('#editor')
.trumbowyg({
    btns: [
        'viewHTML',
        'h4'
    ],
    plugins: {
        allowTagsFromPaste: {
            allowedTags: ['h4', 'p', 'br']
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/base64.html ================================================ Base64 plugin | Trumbowyg

Base64 plugin

Basic usage

You can insert an image in base64 in src attribute of img tag.

Read base64 plugin documentation

Insert your base64 image!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: ['base64']
});
                

base64 in dropdown

Insert your base64 image through image dropdown!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor-dropdown')
.trumbowyg({
    btnsDef: {
        // Create a new dropdown
        image: {
            dropdown: ['insertImage', 'base64'],
            ico: 'insertImage'
        }
    },
    // Redefine the button pane
    btns: [
        ['viewHTML'],
        ['formatting'],
        ['strong', 'em', 'del'],
        ['superscript', 'subscript'],
        ['link'],
        ['image'], // Our fresh created dropdown
        ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
        ['unorderedList', 'orderedList'],
        ['horizontalRule'],
        ['removeformat'],
        ['fullscreen']
    ]
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/cleanpaste.html ================================================ Clean paste plugin | Trumbowyg

Clean paste plugin

Basic usage

Clean paste plugin handle paste events, clean the HTML code before insert content into the editor.

Read clean paste plugin documentation

Try to paste something from Microsoft Word or something!

Then, check the markup. Should be cleaner than original.

The code


$('#editor').trumbowyg();
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/colors.html ================================================ Colors plugin | Trumbowyg

Colors plugin

Basic usage

This plugin allow you to add a foreground and/or background color picker.

Read colors plugin documentation

You can color me!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: [
        ['foreColor', 'backColor']
    ],
});
            

Custom color list

You can customize the color list used by both dropdowns.

You can color me with custom color list!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!


$('#editor-custom-color-list').trumbowyg({
    btns: [
        ['foreColor', 'backColor']
    ],
    plugins: {
        colors: {
            colorList: [
                '000', '111', '222', 'ffea00'
            ]
        }
    }
});
            

Custom color list for each button

You can customize the color list used by each dropdown independently.

You can color me with custom color list!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!


$('#editor-custom-color-list-for-each').trumbowyg({
    btns: [
        ['foreColor', 'backColor']
    ],
    plugins: {
        colors: {
            foreColorList: [
                'ff0000', '00ff00', '0000ff'
            ],
            backColorList: [
                '000', '333', '555'
            ]
        }
    }
});
            

Display colors as a list like other dropdowns

The displayAsList option allows you to change the layout of the color dropdown from squares to list.

Check the dropdown is not like other examples!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!


// Human labels for each hex color
var colorLabels = {
    '#000': 'Black',
    '#555': 'Dark grey',
    '#ff0000': 'Red',
    '#00ff00': 'Green',
    '#0000ff': 'Blue',
    '#ff1493': 'Pink',
};

$.each(colorLabels, function(colorHexCode, colorLabel) {
    $.trumbowyg.langs.en[colorHexCode] = colorLabel;
})

$('#editor-display-as-list').trumbowyg({
    btns: [
        ['foreColor', 'backColor']
    ],
    plugins: {
        colors: {
            foreColorList: [
                'ff0000', '00ff00', '0000ff', 'ff1493'
            ],
            backColorList: [
                '000', '555', 'ff0000'
            ],
            displayAsList: true
        }
    }
});
            

Loading

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/emoji.html ================================================ Emoji plugin | Trumbowyg

Emoji plugin

Basic usage

This plugin allow you to add an emoji picker in Trumbowyg.

Read emoji plugin documentation

Select an emoji !

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor')
.trumbowyg({
    btns: ['emoji']
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/fontfamily.html ================================================ Font family plugin | Trumbowyg

Font family plugin

Basic usage

User can pick some fonts in Trumbowyg.

Read font family plugin documentation

Change the font!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor')
.trumbowyg({
    btns: [
        ['fontfamily']
    ]
});
            

Custom font list

Change the font!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor-custom-font-list')
.trumbowyg({
    btns: [
        ['fontfamily']
    ],
    plugins: {
        fontfamily: {
            fontList: [
                {name: 'Arial', family: 'Arial, Helvetica, sans-serif'},
                {name: 'Comic Sans', family: '\'Comic Sans MS\', Textile, cursive, sans-serif'},
                {name: 'Open Sans', family: '\'Open Sans\', sans-serif'}
            ]
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/fontsize.html ================================================ Font size plugin | Trumbowyg

Font size plugin

Basic usage

User can change font size in Trumbowyg.

Read font size plugin documentation

Change the size!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor')
.trumbowyg({
    btns: [
        ['fontsize']
    ]
});
            

Custom font sizes

Change the font!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor-custom-font-sizes')
.trumbowyg({
    btns: [
        ['fontsize']
    ],
    plugins: {
        fontsize: {
            sizeList: [
                '14px',
                '18px',
                '22px'
            ],
            allowCustomSize: false
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/giphy.html ================================================ Giphy plugin | Trumbowyg

Giphy plugin

Basic usage

User can insert some GIFs from Giphy in Trumbowyg.

Read Giphy plugin documentation

Insert some GIFs

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor')
.trumbowyg({
    btns: [
        ['giphy']
    ],
    plugins: {
        giphy: {
            apiKey: 'dNhCbN6hrhpBMxXhIswM34wIR2UBpCns'
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/highlight.html ================================================ Highlight plugin | Trumbowyg

Highlight plugin

Basic usage

This plugin allow you to add an code highlighter in Trumbowyg.

Read highlight plugin documentation

Just press plugin button and paste code there!


The code


$('#editor')
.trumbowyg({
    btns: [
        ['highlight']
    ]
});
            

Setup

In head tag


<!-- Import prismjs stylesheet -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.13.0/themes/prism.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/plugins/line-highlight/prism-line-highlight.min.css">
            

At the end of body


<!-- Import jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
<!-- Import prismjs -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script>
<!-- Import prismjs line highlight plugin -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/plugins/line-highlight/prism-line-highlight.min.js"></script>
            
================================================ FILE: docs/demos/plugins/history.html ================================================ History plugin | Trumbowyg by Alex-D

History plugin

Basic usage

This plugin gives you an improved undo and redo functionality based on editor changes.

Read history plugin documentation

Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: [['historyUndo','historyRedo']]
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/indent.html ================================================ Indent plugin | Trumbowyg

Indent plugin

Basic usage

This plugin allow you to indent or outdent into your page creating vibrante documents.

Read insert indent plugin documentation

You can make me come alive!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: ['indent', 'outdent']
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/insertaudio.html ================================================  Insert audio plugin | Trumbowyg

Insert audio plugin

Basic usage

This plugin allow you to insert audio files into your page creating vibrante documents.

Read insert audio plugin documentation

You can make me come alive! Insert Audio

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: ['insertAudio']
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/lineheight.html ================================================ Line height plugin | Trumbowyg

Line height plugin

Basic usage

User can change the line-height in Trumbowyg.

Read font size plugin documentation

Change the size!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor')
.trumbowyg({
    btns: [
        ['lineheight']
    ]
});
            

Custom font sizes

Change the font!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor-custom-font-sizes')
.trumbowyg({
    btns: [
        ['lineheight']
    ],
    plugins: {
        lineheight: {
            sizeList: [
                '14px',
                '18px',
                '22px'
            ]
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/mathml.html ================================================ MathML plugin | Trumbowyg

MathML plugin

Basic usage

This plugin allows user to use MathML.

Read MathML plugin documentation

You can try to insert some formulas

Some formulas to copy/paste to try it

The code


// MathJax inline configuration
MathJax.Hub.Config({
    tex2jax: {
        inlineMath: [
            ['$', '$'],
            ['\\(', '\\)']
        ]
    }
});

// Trumbowyg initialization with MathML button
$('#editor')
.trumbowyg({
    btns: [
        'viewHTML',
        'mathml'
    ]
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>

<!-- Import MathJax -->
<script src="//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"></script>
            
================================================ FILE: docs/demos/plugins/mention.html ================================================ Mention plugin | Trumbowyg

Mention plugin

Basic usage

This plugin allows to mention an user from a source list.

Read Mention plugin documentation

Check this out, there are lot of guys in the mention dropdown!

The code


$('#editor')
.trumbowyg({
    btns: [
        ['mention']
    ],
    plugins: {
        mention: {
            source: [
                {login: 'jdoe', name: 'John Doe (The Jean-Claude Van Damme\'s intern)'},
                {login: 'lgaga', name: 'Lady Gaga'},
                {login: 'jcvd', name: 'Jean-Claude Van Damme'},
                {login: 'nminaj', name: 'Nicki Minaj'},
                {login: 'mshinoda', name: 'Mike Shinoda'},
                {login: 'epiaf', name: 'Edith Piaf'},
                {login: 'kwest', name: 'Kanye West'},
                {login: 'jbalasko', name: 'Josiane Balasko'},
                {login: 'jcesar', name: 'Julius Cesarius'},
                {login: 'mlisa', name: 'Mona Lisa'},
                {login: 'mjackson', name: 'Mickael Jackson'},
                {login: 'fflament', name: 'Flavie Flament'},
            ],
            formatDropdownItem: function (item) {
                return item.name + ' (@' + item.login + ')';
            }
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/noembed.html ================================================ Noembed plugin | Trumbowyg

Noembed plugin

Basic usage

You can insert embedded content via an embed proxy. Plugin uses noembed by default.

Read noembed plugin documentation

Embed Content!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: ['noembed']
});
            

Put in dropdown

Embed content through image dropdown!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor-dropdown')
.trumbowyg({
    btnsDef: {
        // Create a new dropdown
        image: {
            dropdown: ['insertImage', 'noembed'],
            ico: 'insertImage'
        }
    },
    // Redefine the button pane
    btns: [
        ['viewHTML'],
        ['formatting'],
        ['strong', 'em', 'del'],
        ['superscript', 'subscript'],
        ['link'],
        ['image'], // Our fresh created dropdown
        ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
        ['unorderedList', 'orderedList'],
        ['horizontalRule'],
        ['removeformat'],
        ['fullscreen']
    ]
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/pasteembed.html ================================================  Paste embed plugin | Trumbowyg

Paste embed plugin

Basic usage

This plugin allow you to insert iframes into your editor just by pasting an URL.
It uses noembed.com API to support X, YouTube, SoundCloud and more. Find all supported websites at noembed.com.

Read paste embed plugin documentation

Try to paste some urls!

Try pasting this one: https://www.youtube.com/watch?v=dQw4w9WgXcQ

The code


$('#editor').trumbowyg();
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/pasteimage.html ================================================  Paste image plugin | Trumbowyg

Paste image plugin

Basic usage

Paste image plugin handle paste events, check if you have image files in your clipboard, then paste them into the editor as base64. It do nothing on text or HTML paste.

Read paste image plugin documentation

Try to paste some image!

For example, you can right-click > Copy Image on the Trumbowyg logo, then paste it here.

The code


$('#editor').trumbowyg();
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/preformatted.html ================================================ Preformatted plugin | Trumbowyg

Preformatted plugin

Basic usage

Wraps/unwraps your code with <pre><code> tags.

Read preformatted plugin documentation

Some code to wrap

$('#editor') .trumbowyg({ btns: [ 'viewHTML', 'preformatted' ] });

Then check the HTML, code is now wrapped into pre + code tags!

The code


$('#editor')
.trumbowyg({
    btns: [
        'viewHTML',
        'preformatted'
    ]
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/resizimg.html ================================================ Resizimg plugin | Trumbowyg

Resizimg plugin

Basic usage

Images can be resized by click over the image and dragging their bottom-right corner (the white ones).

Read resizimg plugin documentation

Resize that image!

The code

Nothing to do unless you want to change the default values of options, in which case you could:


$('#editor').trumbowyg({
    plugins: {
        resizimg: {
            minSize: 64,
            step: 16,
        }
    }
});
            

Setup

In head tag


            

At the end of body

Note the additional requirement: the jquery-resizable plugin must be loaded for Resizimg to work.


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.4.0.min.js"><\/script>')</script>

<!-- Import dependency for Resizimg (tested with version 0.35). For a production setup, follow install instructions here: https://github.com/RickStrahl/jquery-resizable -->
<script src="//rawcdn.githack.com/RickStrahl/jquery-resizable/0.35/dist/jquery-resizable.min.js"></script>
            
================================================ FILE: docs/demos/plugins/ruby.html ================================================ Ruby plugin | Trumbowyg

Ruby plugin

Basic usage

You can insert Ruby markup.

Read ruby plugin documentation

Insert your ruby markup!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: ['ruby']
});
                

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/specialchars.html ================================================ Special chars plugin | Trumbowyg

Special chars plugin

Basic usage

This plugin allow you to add a picker for special character in Trumbowyg.

Read special chars plugin documentation

Select a character!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor')
.trumbowyg({
    btns: [
        ['specialChars']
    ]
});
            

Custom char list

Select a character!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor-custom-list')
.trumbowyg({
    btns: [
        ['specialChars']
    ],
    plugins: {
        specialchars: {
            symbolList: [
                '00A2', '00A5', '00A4', '2030',  null,
                '00A9', '00AE',  '2122',  null,
                '2023', '25B6', '2B29', '25C6', null,
                '2211', '2243', '2264', '2265'
            ]
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/speechrecognition.html ================================================ Speech recognition plugin | Trumbowyg

Speech recognition plugin

Basic usage

This plugins allow you to enter text via speech recognition in Trumbowyg. Suddenly it does not work with Firefox which has not implemented the Web Speech API yet.

Read speech recognition plugin documentation

The code


$('#editor')
.trumbowyg({
    btns: [
        ['speechrecognition']
    ]
});
            

Language setting

The code


$('#editor-settings-lang')
.trumbowyg({
    btns: [
        ['speechrecognition']
    ],
    plugins: {
        speechrecognition: {
            lang: 'de-DE'
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/table.html ================================================ Table plugin | Trumbowyg

Table plugin

Basic usage

This plugin allows you to add and manage a table.

Read table plugin documentation

Add a table to the editor

Below is an example of a simple table with a header row.

Project & Pitch Status Technologies Stars Downloads URL
Trumbowyg
Lightweight WYSIWYG Editor
Active JavaScript ~4 000 ★ 70 000 / month Landing
GitHub
Monitoror
Unified monitoring wallboard
Maintenance Go
Vue
~4 000 ★ 100 000 + Landing
GitHub
Cookies EU Banner
1kb vanilla JS for cookie banner
Maintenance JavaScript ~420 ★ 4 000 / month Landing
GitHub
Colllect
Bookmark Manager for Creatives
Paused Symfony
Vue
~340 ★ Landing
GitHub
Check Disk Space
Multi-platform disk space checker for Node.js
Maintenance TypeScript ~78 ★ 2 000 000 / month GitHub
Column View
Multi-columns page viewer
Maintenance TypeScript
Snabbdom
~7 ★ Landing
GitHub

Tables with merged cells

Example of a pricing table.

Feature Free Premium Business Enterprise
Price Free 5,99€ / user 12,99€ / user 22,99€ / user
Users 1 Unlimited 20 users minimum
Items 100 Unlimited
Bandwidth 5 Mbps On your charge

Another tab with different merged cells:

Those plugins allow you to merge cells And also unmerge Two cells merged
You can see some examples on this page Two horizontally merged too Simple cell
Upload image Pick, upload, and inserts an image Next is empty

The code


$('#editor, #editor2')
.trumbowyg({
    btns: [['table'], ['tableCellBackgroundColor', 'tableBorderColor']]
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/template.html ================================================ Template plugin | Trumbowyg by Alex-D

Template plugin

Basic usage

This plugin allows you to add a template-dropdown.

Read template plugin documentation

Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: ['template'],
    plugins: {
        templates: [
            {
                name: 'Template 1',
                html: '<p>I am a template!</p>'
            },
            {
                name: 'Template 2',
                html: '<p>I am a different template!</p>'
            }
        ]
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/tenor.html ================================================ Tenor plugin | Trumbowyg

Tenor plugin

Basic usage

User can insert some GIFs from Tenor in Trumbowyg.

Read Tenor plugin documentation

Insert some GIFs

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The code


$('#editor')
.trumbowyg({
    btns: [
        ['tenor']
    ],
    plugins: {
        tenor: {
            apiKey: 'AIzaSyDSkXpGdEiI0OrQO9xZKbvTAvekZhmXW3c'
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/demos/plugins/upload.html ================================================ Upload plugin | Trumbowyg by Alex-D

Upload plugin

Basic usage

You can insert an image by upload.

Read upload plugin documentation

Insert your uploaded image!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor')
.trumbowyg({
    btns: ['upload'],
    plugins: {
        // Add imgur parameters to upload plugin for demo purposes
        upload: {
            serverPath: 'https://api.imgur.com/3/image',
            fileFieldName: 'image',
            headers: {
                'Authorization': 'Client-ID 9e57cb1c4791cea'
            },
            urlPropertyName: 'data.link'
        }
    }
});
            

Upload in dropdown

Insert your uploaded image through image dropdown!

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

The code


$('#editor-dropdown')
.trumbowyg({
    btnsDef: {
        // Create a new dropdown
        image: {
            dropdown: ['insertImage', 'upload'],
            ico: 'insertImage'
        }
    },
    // Redefine the button pane
    btns: [
        ['viewHTML'],
        ['formatting'],
        ['strong', 'em', 'del'],
        ['superscript', 'subscript'],
        ['link'],
        ['image'], // Our fresh created dropdown
        ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
        ['unorderedList', 'orderedList'],
        ['horizontalRule'],
        ['removeformat'],
        ['fullscreen']
    ],
    plugins: {
        // Add imgur parameters to upload plugin for demo purposes
        upload: {
            serverPath: 'https://api.imgur.com/3/image',
            fileFieldName: 'image',
            headers: {
                'Authorization': 'Client-ID 9e57cb1c4791cea'
            },
            urlPropertyName: 'data.link'
        }
    }
});
            

Setup

In head tag


            

At the end of body


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            
================================================ FILE: docs/documentation/core/index.html ================================================ Core | Trumbowyg: A lightweight WYSIWYG editor | Alex-D / Alexandre Demode

Core

When you want create your custom extension for Trumbowyg, you can open and close a modal box with custom inner HTML code, listen events and more.

Open and close

For that use the right method: openModal or closeModal like that:


// Open a modal box
var $modal = trumbowyg.openModal({
    title: 'A title for modal box',
    content: '<p>Content in HTML which you want include in created modal box</p>'
});

// Close current modal box
trumbowyg.closeModal();
            

An openModal call returns a jQuery object which contains the modal box. You need this object if you want to use listen events (see below).

Only one modal box can open at any given moment. So, openModal return false if a modal is currently opened.

Events on modal box

Modal boxes in Trumbowy come with two buttons: "Confirm" and "Cancel". An event is associated to each one:

  • tbwsubmit: triggered when form is submit
  • tbwreset: triggered when user cancels operation

// Open a modal box
var $modal = trumbowyg.openModal({
    title: 'A title for modal box',
    content: '<p>Content in HTML which you want include in created modal box</p>'
});

// Listen clicks on modal box buttons
$modal.on('tbwconfirm', function(e){
    // Do what you want
    trumbowyg.closeModal();
});
$modal.on('tbwcancel', function(e){
    trumbowyg.closeModal();
});
            

Only build inputs in modal

If you want to only add inputs in the modal box, this function is more simple. Indeed, you do not manage confirm and close buttons, and get all input value on confirm.


var img = $('img#an-img');
$modal = trumbowyg.openModalInsert({
    title: 'A title for modal box',
    fields: {
        url: {
            value: img.attr('src')
        },
        alt: {
            label: 'Alt',
            name: 'alt',
            value: img.attr('alt'),
            type: 'text',
            attributes: {}
        },

        // Build your own input by setting type as a function and returning the html
        referrerpolicy: {
            label: 'Referrer Policy',
            name: 'referrerpolicy',
            type: function(field, fieldId, prefix, lg) {
                var html += '<div class="' + prefix + 'input-row">' +
                    '<div class="' + prefix + 'input-infos">' +
                        '<label for="' + fieldId + '">' +
                            '<span>' + (lg[field.label] ? lg[field.label] : field.label) + '</span>' +
                        '</label>' +
                    '</div>' +
                    '<div class="' + prefix + 'input-html">' +
                        '<select id="' + fieldId + '" name="' + field.name + '">' +
                            '<option' + (field.value === 'no-referrer' ? ' selected="selected"' : '') + '>no-referrer</option>' +
                            '<option' + (field.value === 'no-referrer-when-downgrade' ? ' selected="selected"' : '') + '>no-referrer-when-downgrade</option>' +
                            '<option' + (field.value === 'origin' ? ' selected="selected"' : '') + '>origin</option>' +
                            '<option' + (field.value === 'origin-when-cross-origin' ? ' selected="selected"' : '') + '>origin-when-cross-origin</option>' +
                            '<option' + (field.value === 'unsafe-url' ? ' selected="selected"' : '') + '>unsafe-url</option>' +
                        '</select>' +
                    '</div>' +
                '</div>';

                return html;
            }
        },
        example: {
            // Missing label is replaced by the key of this object (here 'example')
            // Missing name is the key
            // When value is missing, value = ''
            // When type is missing, 'text' is assumed. You can use all the input field types,
            //   plus checkbox and radio (select and textarea are not supported)
            // When attributes is missing, {} is used. Attributes are added as attributes to
            //   the input element.
            // For radio and checkbox fields, you will need to use attributes if you want it
            //   to be checked by default.
        }
    },
    // Callback is called when user confirms
    callback: function(values){
        img.attr('src', values['url']);
        img.attr('alt', values['alt']);

        return true; // Return true if you have finished with this modal box
        // If you do not return anything, you must manage the closing of the modal box yourself
    }
});

// You can also listen for modal confirm/cancel events to do some custom things
// Note: the openModalInsert callback is called on tbwconfirm
$modal.on('tbwconfirm', function(e){
    // Do what you want
});
$modal.on('tbwcancel', function(e){
    trumbowyg.closeModal();
});
            

Range

Managing correctly text range, is not so trivial. Trumbowyg has a system to save and restore selection range which does not involve typical getter/setter.

Save and get current range


// Save current range
trumbowyg.saveRange();

// Restore last saved range
trumbowyg.restoreRange();
            

Get selection range


// range contains a JavaScript range
var range = trumbowyg.getRange();
            

Get last saved range text (aka selected text)


var text = trumbowyg.getRangeText();
            

Manage content

You can set and get current HTML content of the editor with a getter/setter:


// Get HTML content
trumbowyg.html();

// Set HTML content
trumbowyg.html('<p>Your content here</p>');
            

Empty

You can empty the content of the editor.


trumbowyg.empty();
            

Enable/disable edition

Added in 2.1.0

As you can disable editor by using disabled option, you can also switch between enabled and disabled states by using API.


trumbowyg.setDisabled(true);
trumbowyg.setDisabled(false);
            

Toggle between HTML & WYSIWYG modes

You can switch between HTML view and WYSIWYG view via toggle method.


trumbowyg.toggle();
            
================================================ FILE: docs/documentation/index.html ================================================ Documentation | Trumbowyg: A lightweight WYSIWYG editor | Alex-D / Alexandre Demode

Get started

Installation

Via a package manager
npm install trumbowyg bower install trumbowyg
or
Download the package
Download

If you don't already do it, load jQuery at bottom of <body> like so:


<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>
            

Trumbowyg requires jQuery >= 1.8

After these lines, you have to load Trumbowyg.


<script src="trumbowyg/dist/trumbowyg.min.js"></script>
            

Don't forget to load Trumbowyg CSS in the <head>, or load your own style for the editor.


<link rel="stylesheet" href="trumbowyg/dist/ui/trumbowyg.min.css">
            

Basics

This the minimal code to transform a simple div into the amazing WYSIWYG editor which is Trumbowyg.


$('#trumbowyg-demo').trumbowyg();
            

If you want to set options to Trumbowyg, add an object which contains your options as parameters.


$('#trumbowyg-demo').trumbowyg({
    btns: [['strong', 'em',], ['insertImage']],
    autogrow: true
});
            

Common issues

SVG icons does not load

  • check if you are in HTTP(S) protocol;
  • check if you do not have cross domain error (XHR request is used).

If your problem is not solved by these tips, check the SVG icons section.

Use plugins

Add a plugin to your page

Basically you need to add the plugin JS to your page, at the bottom of your <body>, after jQuery and Trumbowyg imports (in this order), but before your custom JS which initialize Trumbowyg:


<!-- Import jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-3.3.1.min.js"><\/script>')</script>

<!-- Import Trumbowyg -->
<script src="trumbowyg/dist/trumbowyg.min.js"></script>

<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/upload/trumbowyg.cleanpaste.min.js"></script>
<script src="trumbowyg/dist/plugins/upload/trumbowyg.pasteimage.min.js"></script>

<!-- Init Trumbowyg -->
<script>
    // Doing this in a loaded JS file is better, I put this here for simplicity
    $('#my-editor').trumbowyg();
</script>
            

In the previous case, plugins are "auto registering" themself into Trumbowyg and be activated in all future Trumbowyg instances.

But most popular plugins are button-based, like upload or colors ones. If you want to use these plugins, load them like explained before, then add the plugin button to your editor by checking the button pane doc section.

For more information on plugins, check the plugins documentation page.

General

Prefix

You can change prefix of all class added on elements of Trumbowyg using this option:


$('textarea').trumbowyg({
    prefix: 'custom-prefix'
});
            

For example, the bold button class is now custom-prefix-bold-button

Default value is trumbowyg

Localization

Your users don't speak english? No problem, Trumbowyg has a language parameter. You have to load the appropriate lang file. Search in /dist/langs folder to see if a language file already exists, if not create it and share it :). See add a localization for more details on language file.

Don't forget include the lang file in your pages:


<script type="text/javascript" src="js/dist/langs/fr.min.js"></script>
            

Warning, include lang file after Trumbowyg and before instantiating the editor!

Usage of language parameter:


$('textarea').trumbowyg({
    lang: 'fr'
});
            

If the lang was not found, english values are used by default.

Custom skin

Trumbowyg is flexible and simple. You want a different look?

Replace existing CSS file

Copy /src/ui/ folder somewhere and customize style. Finally, link your new CSS file and remove link to Trumbowyg default style.

It is not recommended to directly edit the CSS file in the trumbowyg folder. When you update the plugin, your modifications will be erased.


Multiple skins for multiple Trumbowyg

This is useful when you do not want same design for all instances of Trumbowyg.

Use the prefix option:


$('.editor-modern-ui').trumbowyg({
    prefix: 'modern-ui'
});
            

In your CSS, you can now stylize .modern-ui-link-button for example, whitout conflicts with the default skin. It's necessary to redefine style for all elements. If you want to start from the default skin, copy /src/ui/ folder, replace all trumbowyg- by your prefix and work from here.

SVG icons

Added in 2.1.0

A pack of SVG icons is available and enabled by default. This file is loaded via XHR request in JavaScript so it is possible the path is not matching with your assets file paths. You can change the path of the SVG or disable this feature.

Change SVG path globally

To change SVG path, you need to set the path in global Trumbowyg configuration object:


$.trumbowyg.svgPath = '/assets/my-custom-path/icons.svg';
            

This global way needs to be done before initialize any Trumbowyg instance.

Disable SVG icons globally

If you do not want SVG icons, you can set this option to false. Then, you can add your custom icons by CSS or what you want.


$.trumbowyg.svgPath = false;
            

Check hide button texts option if you did not want button content at all and customize it by yourself.

This global way needs to be done before initialize any Trumbowyg instance.

Change SVG path or disable SVG icons locally

You can also apply svgPath option for an isolated editor only.


$('.editor').trumbowyg({
    svgPath: false // or a path like '/assets/my-custom-path/icons.svg'
});
            

Use absolute path to SVG sprite

Added in 2.22.0

If you want to be IE-complient you should use this option.

This will load icons directly from the sprite file. Instead of loading the sprite inline into the page, you can directly set this global option to true.


$.trumbowyg.svgAbsoluteUsePath = true;
            

This option is useful to avoid issues with baseURL.

This global way needs to be done before initialize any Trumbowyg instance.

Default value for svgAbsoluteUsePath is false.

Use SVG icons without XHR or via an another protocol than HTTP(S)

If you want to be IE9-complient you should use this option.

If you do not want use HTTP(S) protocol or use XHR to load icons, you can load icons in your HTML on your own. You just need to include the icons.svg file inline in your <body>:


<div id="trumbowyg-icons">
    <svg xmlns="http://www.w3.org/2000/svg">
        <symbol id="trumbowyg-blockquote" viewBox="0 0 72 72"><path d="..."></path></symbol>
        <symbol id="trumbowyg-bold" viewBox="0 0 72 72"><path d="..."></path></symbol>
        <symbol id="trumbowyg-close" viewBox="0 0 72 72"><path d="..."></path></symbol>
        <!-- ... all other icons here -->
    </svg>
</div>
            

You need to use this id: <prefix>-icons.

Trumbowyg checks if a #<prefix>-icons exists. If not, it loads SVG icons file via XHR and puts it in a div with this id. Next editor on the page does not load icons again. By adding this div in the page, you "hack" this mechanism to load it on your own.

Placeholder

Like the HTML5 attribute on input and textarea, you can add a placeholder with Trumbowyg.


<div class="my-editor" placeholder="Your text as placeholder"></div>
            

Placeholder is visible only if the element is empty (no HTML/text).

Placeholder is both managed in JS and CSS with the default style. If you create your own style, you need to apply the following style (replace trumbowyg by your prefix):


.trumbowyg-editor[contenteditable=true]:empty::before{
    content: attr(placeholder);
    color: #999;
}
                

Disabled

Added in 2.1.0

Trumbowyg supports disabled attribute on a textarea and an option.


<textarea class="my-editor" disabled></textarea>
            

$('.a-disabled-editor').trumbowyg({
    disabled: true
});
            

You can switch between disabled and enabled states by using API

Basic Options

Button pane

It's probably the most interesting option, it allows you to choose the buttons that appears in the button pane. This option is an array containing string values representing the buttons or vertical separators (using the pipe character). To create your own custom button pane, define an array and pass it to the btns option.


$('.simple-editor').trumbowyg({
    btns: [['bold', 'italic'], ['link']]
});
            

By default, btns option value is:


$('.simple-editor').trumbowyg({
    btns: [
        ['viewHTML'],
        ['undo', 'redo'], // Only supported in Blink browsers
        ['formatting'],
        ['strong', 'em', 'del'],
        ['superscript', 'subscript'],
        ['link'],
        ['insertImage'],
        ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
        ['unorderedList', 'orderedList'],
        ['horizontalRule'],
        ['removeformat'],
        ['fullscreen']
    ]
});
                

Active dropdown icon

Added in 2.18.0

Dropdown icon can change to the active sub-button icon with this option enabled.


$('.simple-editor').trumbowyg({
    changeActiveDropdownIcon: true
});
            

By default, changeActiveDropdownIcon option value is: false

Hide button texts

Added in 2.5.0

You can hide button texts showed when you put svgPath to false.


// Globally
$.trumbowyg.hideButtonTexts = true

// Or locally
$('.trumbowyg').trumbowyg({
    hideButtonTexts: true
});
            

If you disable SVG icons and button texts, you should design them by yourself.

Semantic

Generates a better, more semantic oriented HTML (i.e. <em> instead of <i>, <strong> instead of <b>, etc.). It's just a boolean:


$('.trumbowyg').trumbowyg({
    semantic: false
});
            

Enabling semantic mode deactivates the underline button by default because they do not convey any real semantic. If you want to reactivate them, you have to do it explicitly, see Button pane

Since 2.12.0 you can also put an object to customize the semantic tag mapping for each one of these tags: <b>, <i>, <s>, <strike>, <div>.


$('.trumbowyg').trumbowyg({
    semantic: {
        'div': 'div' // Editor does nothing on div tags now
    }
});
            

This option is set to true by default which is equivalent to:


$('.trumbowyg').trumbowyg({
    semantic: {
        'b': 'strong',
        'i': 'em',
        's': 'del',
        'strike': 'del',
        'div': 'p'
    }
});
            

Reset CSS

If you don't want the page style to impact on the look of the text in the editor, you will need to apply a reset-css on the editor. You can activate this with the resetCss option:


$('.trumbowyg').trumbowyg({
    resetCss: true
});
            

This option is set to false by default. The reset only applies to the editor, not to the generated HTML code.

Remove format pasted

If you don't want styles pasted from clipboard (from Word or other webpage for example), pass the removeformatPasted option to true


$('.trumbowyg').trumbowyg({
    removeformatPasted: true
});
            

In order to use this option, you need to define a font size in your CSS or use a reset like normalize.

Remove format pasted is not active by default (set to false).

Tags to remove

Allow to sanitize the code by removing all tags you want. The tagsToRemove option is an array.


$('.trumbowyg').trumbowyg({
    tagsToRemove: ['script', 'link']
});
            

You must do the sanitize server-side too to avoid some security issues like XSS.

Tags to remove is an empty array by default (set to []).

Tags to keep

Added in 2.12.0

Sometimes you want to keep some empty i tags for Font Awesome or anything else. You can define this list via the tagsToKeep option.


$('.trumbowyg').trumbowyg({
    tagsToKeep: ['i', 'script[src]']
});
            

Default tags to keep are ['hr', 'img', 'embed', 'iframe', 'input'].

Auto grow

The text editing zone can extend itself when writing a long text. To activate this feature, use the autogrow option:


$('.trumbowyg').trumbowyg({
    autogrow: true
});
            

Autogrow is not active by default (set to false).

Auto grow on enter

Added in 2.7.0

The text editing zone can extend itself when editor get focus and reduce on blur. To activate this feature, use the following option:


$('.trumbowyg').trumbowyg({
    autogrowOnEnter: true
});
            

Autogrow on enter is not active by default (set to false).

Image width modal edit

Added in 2.9.0

Add a field in image insert/edit modal which allow users to set the image width. To activate this feature, use the following option:


$('.trumbowyg').trumbowyg({
    imageWidthModalEdit: true
});
            

Image width modal edit is not active by default (set to false).

URL protocol

Added in 2.9.4

An option to auto-prefix URLs with a protocol.

When this option is set to true, URLs missing a protocol will be prefixed with https://. Alternatively, a string can be provided for a custom prefix.

For example, a value of true would convert example.com to https://example.com, while a value of ftp converts to ftp://example.com.


$('.trumbowyg').trumbowyg({
    urlProtocol: true
});
            

Anchors, email addresses and relative links are left unchanged.

URL protocol is not active by default (set to false).

Added in 2.9.4

Reduce the link overlay to use only url and text fields, omitting title and target.


$('.trumbowyg').trumbowyg({
    minimalLinks: true
});
            

The minimal links option is not active by default (set to false).

Added in 2.26.0

Allow to set link target attribute value to what you want, even if the minimalLinks option is set to true.

First value is the default value (similar to previous defaultLinkTarget option).


$('.trumbowyg').trumbowyg({
    linkTargets: ['_blank', '_self']
});
            

Link targets only list most used options by default (set to ['_self', '_blank']).

Upgrade from defaultLinkTarget


$('.trumbowyg').trumbowyg({
-    defaultLinkTarget: '_blank',
+    linkTargets: ['_blank', '_self', '_parent', '_top'],
});
                

Tag classes

Added in 2.23.0

Add classes to any tag.


$('.trumbowyg').trumbowyg({
    tagClasses: {
        h1: 'h1', // Bootstrap example
        blockquote: 'bg-grey-100 rounded-xl', // Tailwind CSS example
    }
});
            

No classes are applied by default.

Advanced Options

Add a localization

The structure of a language file is simple, it is a JavaScript Object which associate a translation to any key. For example:


jQuery.trumbowyg.langs.fr = {
    _dir: "ltr", // Changes the editor dir

    bold: "Gras",
    close: "Fermer"
};
            

Some localizations are added each day, you can find them in the langs folder on GitHub

You can submit a new localization file by creating a new pull request on the Github repository.

English is the default localization, you don't need to include any file to get Trumbowyg in English.

Custom buttons

If Trumbowyg does not fit your needs, maybe you can create your very own custom button to solve this. The following code (which does not works as is) show you all available button parameters.


$('.trumbowyg').trumbowyg({
    btnsDef: {
        buttonName: {
            fn: 'functionName',
            tag: 'tagName',
            title: 'Button tooltip',
            text: 'Displayed button name',
            isSupported: function () { return true; },
            key: 'K',
            param: '' ,
            forceCSS: false,
            class: '',
            hasIcon: true
        }
    }
});
            

The key (here buttonName) is the button name used in btns and in dropdown. It is used as many default in options below.

Parameters

fn string|function
The name of a Trumbowyg internal method OR a callable function OR browser native execCommand name. In any case, the function is called when the user click on the button or use the associated shortcut.
Default: button name
tag string
The HTML tag name which highlight the button if cursor is in a tag which this tag name.
Default: button name
title string
The text displayed in a tooltip on mouse over a button.
Default: text parameter OR translated button name OR raw button name
text string
The text of the button used when svgPath is set to false. This text can be hidden via hideButtonTexts option.
Default: title parameter OR translated button name OR raw button name
isSupported function
A function which returns a Boolean to know if the custom button is supported (browser compatibility check, context check, ...)
Default: null (no check)
key string
Shortcut key which triggers the button function (fn)
Default: null (no shortcut defined)
param string
In case of using browser native execCommand, give this as parameter.
Default: button name
forceCSS string
In case of using browser native execCommand, force usage of inline CSS insteed of a tag.
Default: false
class string
A string which is added to the button class attribute.
Default: Trumbowyg adds a prefix + '-' + buttonName class
hasIcon boolean
Allow us to force the display of the text for a specific button.
Default: true

Working example:


$('.trumbowyg').trumbowyg({
    btnsDef: {
        alert: {
            fn: function() {
                alert('some text')
            },
            ico: 'blockquote'
        }
    },
    btns: [
        ['alert']
    ]
});
            

Custom dropdowns

You can also create your own dropdown, like buttons. The following code (which does not works as is) show use all options which can be used to make our own customized dropdown.


$('.trumbowyg').trumbowyg({
    btnsDef: {
        dropdownButtonName: {
            dropdown: ['btnA', 'btnB'],
            title: 'Displayed dropdown button name',
            ico: 'iconName',
            hasIcon: true
        }
    }
});
            

Parameters

dropdown array<string>
A list of button names to list in the dropdown.
Default: []
title string
The text displayed in a tooltip on mouse over the dropdown button.
Default: translated dropdown button name OR raw dropdown button name
ico string
The icon name for the dropdown. You can reuse some existing icons from Trumbowyg.
Default: icon with button name in snake case
hasIcon boolean
Allow us to force the display of the text for a specific button.
Default: true

Working example which groups all justify buttons in one dropdown:


$('.trumbowyg').trumbowyg({
    btnsDef: {
        align: {
            dropdown: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
            ico: 'justifyLeft'
        }
    },
    btns: [
        ['align']
    ]
});
            

API

When you want create your custom extension for Trumbowyg, you can open and close a modal box with custom inner HTML code, listen events and more.

Open and close

For that use the .trumbowyg() method and give parameters 'openModal' or 'closeModal' like that:


// Open a modal box
var $modal = $('#editor').trumbowyg('openModal', {
    title: 'A title for modal box',
    content: '<p>Content in HTML which you want include in created modal box</p>'
});

// Close current modal box
$('#editor').trumbowyg('closeModal');
            

An openModal call returns a jQuery object which contains the modal box. You need this object if you want to use listen events (see below).

Only one modal box can open at any given moment. So, openModal return false if a modal is currently opened.

Events on modal box

Modal boxes in Trumbowy come with two buttons: "Confirm" and "Cancel". An event is associated to each one:

  • tbwsubmit: triggered when form is submit
  • tbwreset: triggered when user cancel operation

// Open a modal box
var $modal = $('#editor').trumbowyg('openModal', {
    title: 'A title for modal box',
    content: '<p>Content in HTML which you want include in created modal box</p>'
});

// Listen clicks on modal box buttons
$modal.on('tbwconfirm', function(e){
    // Save data
    $("#editor").trumbowyg('closeModal');
});
$modal.on('tbwcancel', function(e){
    $('#editor').trumbowyg('closeModal');
});
            

Only build inputs in modal

If you want only add inputs in the modal box, this function is more simple. Indeed, you do not manage confirm and close buttons, and get all input value on confirm.


var img = $('img#an-img');
$("#editor").trumbowyg('openModalInsert', {
    title: 'A title for modal box',
    fields: {
        url: {
            value: img.attr('src')
        },
        alt: {
            label: 'Alt',
            name: 'alt',
            value: img.attr('alt'),
            type: 'text',
            attributes: {}
        },
        example: {
            // Missing label is replaced by the key of this object (here 'example')
            // Missing name is the key
            // When value is missing, value = ''
            // When type is missing, 'text' is assumed. You can use all the input field types,
            //   plus checkbox and radio (select and textarea are not supported)
            // When attributes is missing, {} is used. Attributes are added as attributes to
            //   the input element.
            // For radio and checkbox fields, you will need to use attributes if you want it
            //   to be checked by default.
        }
    },
    // Callback is called when user confirms
    callback: function(values){
        img.attr('src', values['url']);
        img.attr('alt', values['alt']);

        return true; // Return true if you have finished with this modal box
        // If you do not return anything, you must manage the closing of the modal box yourself
    }
});

// You can also listen for modal confirm/cancel events to do some custom things
// Note: the openModalInsert callback is called on tbwconfirm
$modal.on('tbwconfirm', function(e){
    // Do what you want
});
$modal.on('tbwcancel', function(e){
    trumbowyg.closeModal();
});
            

Range

Managing correctly text range, is not so trivial. Trumbowyg has a system to save and restore selection range which does not involves typical getter/setter.

Save and get current range


// Save current range
$('#editor').trumbowyg('saveRange');

// Restore last saved range
$('#editor').trumbowyg('restoreRange');
            

Get selection range


// range contains a JavaScript range
var range = $('#editor').trumbowyg('getRange');
            

Get last saved range text


var text = $('#editor').trumbowyg('getRangeText');
            

Manage content

You can set and get current HTML content of the editor with a getter/setter:


// Get HTML content
$('#editor').trumbowyg('html');

// Set HTML content
$('#editor').trumbowyg('html', "<p>Your content here</p>");
            

Empty

You can empty the content of the editor.


$('#editor').trumbowyg('empty');
            

Enable/disable edition

Added in 2.1.0

As you can disable editor by using disabled option, you can also switch between enabled and disabled states by using API.


$('#editor').trumbowyg('disable');
$('#editor').trumbowyg('enable');
            

Toggle between HTML & WYSIWYG modes

Added in 2.9.0

You can switch between HTML view and WYSIWYG view via toggle method.


$('#editor').trumbowyg('toggle');
            

Destroy editor

When you wish, you can restore the previous state of the element was used to create the editor.


$('#editor').trumbowyg('destroy');
            

Events

Some events are fired on the jQuery element which is used to build the editor.

  • Focus on editor: tbwfocus
  • Blur on editor: tbwblur
  • Editor is initialized: tbwinit 2.0.0
  • Change in editor: tbwchange 2.0.0
  • Resize the editor on autogrow: tbwresize 2.0.0
  • Paste something in the editor: tbwpaste 2.0.0
  • Switch to fullscreen mode: tbwopenfullscreen 2.0.0
  • Leave editor's fullscreen mode: tbwclosefullscreen 2.0.0
  • Close the editor: tbwclose 2.0.0
  • Modal open: tbwmodalopen 2.20.0
  • Modal close: tbwmodalclose 2.20.0

$('#editor')
.trumbowyg() // Build Trumbowyg on the #editor element
.on('tbwfocus', function(){ console.log('Focus!'); }); // Listen for `tbwfocus` event
.on('tbwblur', function(){ console.log('Blur!'); }); // Listen for `tbwblur` event
            
================================================ FILE: docs/documentation/plugins/index.html ================================================ Plugins | Trumbowyg: A lightweight WYSIWYG editor | Alex-D / Alexandre Demode

Existing plugins

If you want to add a plugin, be sure to read the use plugins section of documentation before continue.

Allow tags from paste

Allow tags from paste plugin allows you to filter tags allowed when an user paste some code into the editor.

Try allow tags from paste live demo! View allow tags from paste plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/allowtagsfrompaste/trumbowyg.allowtagsfrompaste.min.js"></script>
            

Then you can configure your custom tag whitelist. Any tag pasted which is not in allowedTags list will be unwrap, only the text will be kept.


$('#my-editor').trumbowyg({
    plugins: {
        allowTagsFromPaste: {
            allowedTags: ['h4', 'p', 'br']
        }
    }
});
            

Enabling this plugin will force disable the removeformatPasted core option (set it to false).

Base 64

Base 64 plugin allows you to insert images inline as base64.

Try base64 live demo! View base64 plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/base64/trumbowyg.base64.min.js"></script>
            

Then you can use the new button definition base64


$('#my-editor').trumbowyg({
    btns: [
        ['base64']
    ]
});
            

Clean paste

Clean paste plugin handle paste events, clean the HTML code before insert content into the editor.

Try cleanpaste live demo! View cleanpaste plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/cleanpaste/trumbowyg.cleanpaste.min.js"></script>
            

Clean paste works now on every new Trumbowyg instance.

Colors

Colors plugin allows you to change foreground and background color of your text.

Try colors live demo! View colors plugin code on GitHub

How to use it?


<!-- Import Trumbowyg colors style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/colors/ui/trumbowyg.colors.min.css">
            

<!-- Import Trumbowyg colors JS at the end of <body>... -->
<script src="trumbowyg/dist/plugins/colors/trumbowyg.colors.min.js"></script>
            

Then you can use the new button definitions foreColor and backColor


$('#my-editor').trumbowyg({
    btns: [
        ['foreColor', 'backColor']
    ]
});
            

Options

colorList array<string>
List of colors available for both foreColor and backColor dropdowns.
Default: check the code or the demo
foreColorList array<string>
List of colors available used in the foreColor dropdown instead of colorList's one.
Default: fallbacks on colorList
allowCustomForeColor boolean
Allow user to pick a color outside the list.
Default: true
backColorList array<string>
List of colors available used in the backColor dropdown instead of colorList's one.
Default: fallbacks on colorList
allowCustomBackColor boolean
Allow user to pick a color outside the list.
Default: true
displayAsList boolean
Switch from square to labelled list view. Default view is the square's one.
Default: false

Example with colorList parameter:


$('#my-editor').trumbowyg({
    btns: [
        ['foreColor', 'backColor']
    ],
    plugins: {
        colors: {
            colorList: [
                '000', '111', '222', 'ffea00'
            ]
        }
    }
});
            

Example with both foreColorList and backColorList, displayed as list:


$('#my-editor').trumbowyg({
    btns: [
        ['foreColor', 'backColor']
    ],
    plugins: {
        colors: {
            foreColorList: [
                'ff0000', '00ff00', '0000ff'
            ],
            backColorList: [
                '000', '333', '555'
            ],
            displayAsList: true
        }
    }
});
            

If both foreColorList and backColorList are set, colorList will be not used at all.

Emoji

Emoji plugin allows you to insert some emojis in your editor.

Try emoji live demo! View emoji plugin code on GitHub

How to use it?


<!-- Import Trumbowyg emoji style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/emoji/ui/trumbowyg.emoji.min.css">
            

<!-- Import Trumbowyg emoji at the end of <body>... -->
<script src="trumbowyg/dist/plugins/emoji/trumbowyg.emoji.min.js"></script>
            

Then you can use the new button definition emoji


$('#my-editor').trumbowyg({
    btns: [
        ['emoji']
    ]
});
            

Font family

This plugin allows user to custom font family.

Try font family live demo! View font family plugin code on GitHub

How to use it?


<!-- Import Trumbowyg fontfamily JS at the end of <body>... -->
<script src="trumbowyg/dist/plugins/fontfamily/trumbowyg.fontfamily.min.js"></script>
            

Then you can use the new button definition fontfamily


$('#my-editor').trumbowyg({
    btns: [
        ['fontfamily']
    ]
});
            

You can also choose custom font list:


$('#my-editor').trumbowyg({
    btns: [
        ['fontfamily']
    ],
    plugins: {
        fontfamily: {
            fontList: [
                {name: 'Arial', family: 'Arial, Helvetica, sans-serif'},
                {name: 'Open Sans', family: '\'Open Sans\', sans-serif'}
            ]
        }
    }
});
            

Font size

This plugin allows user to custom font size.

Try font size live demo! View font size plugin code on GitHub

How to use it?


<!-- Import Trumbowyg font size JS at the end of <body>... -->
<script src="trumbowyg/dist/plugins/fontsize/trumbowyg.fontsize.min.js"></script>
            

Then you can use the new button definition fontsize


$('#my-editor').trumbowyg({
    btns: [
        ['fontsize']
    ]
});
            

You can also choose custom size list:


$('#my-editor').trumbowyg({
    btns: [
        ['fontsize']
    ],
    plugins: {
        fontsize: {
            sizeList: [
                '12px',
                '14px',
                '16px'
            ]
        }
    }
});
            

If you want to disable the custom font size option, use the allowCustomSize option:


$('#my-editor').trumbowyg({
    btns: [
        ['fontsize']
    ],
    plugins: {
        fontsize: {
            allowCustomSize: false
        }
    }
});
            

Default allowCustomSize value is true.

Giphy

User can search and pick gif to insert from Giphy

Try Giphy plugin live demo! View Giphy plugin code on GitHub

How to use it?


<!-- Import Trumbowyg Giphy style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/giphy/ui/trumbowyg.giphy.min.css">
            

<!-- Import Trumbowyg Giphy JS at the end of <body>... -->
<script src="trumbowyg/dist/plugins/giphy/trumbowyg.giphy.min.js"></script>
            

Then, you need to create a Giphy App to get an API Key.

Use the new button definition giphy and your newly created Giphy API Key:


$('#my-editor').trumbowyg({
    btns: [
        ['giphy']
    ],
    plugins: {
        giphy: {
            apiKey: 'yourVeryOwnApiKey'
        }
    }
});
            

Options

apiKey string (required)
Giphy API Key of your application. Create a Giphy App to get an API Key.
rating string
Giphy image rating filter. See Giphy documentation for allowed rating value details.
Default: g
throttleDelay number (in milliseconds)
It's the delay between each Giphy API requests while user is typing his query.
Default: 300
limit number
Number of images shown in result list.
Default: 50
noResultGifUrl string
The image URL shown when there is no result.
Default: https://media.giphy.com/media/2Faz9FbRzmwxY0pZS/giphy.gif

Example with options:


$('#my-editor').trumbowyg({
    btns: [
        ['giphy']
    ],
    plugins: {
        giphy: {
            apiKey: 'yourVeryOwnApiKey',
            rating: 'pg',
            throttleDelay: 500,
            noResultGifUrl: 'http://example.com/yourimage.gif'
        }
    }
});
            

Code Highlight

This plugin allows user to add code highlight parts.

Try highlight plugin live demo! View highlight plugin code on GitHub

How to use it?

This plugin require Prism which can be installed with: npm install prismjs


<!-- Import prismjs style in <head>... -->
<link rel="stylesheet" href="node_modules/prismjs/themes/prism.css">
<!-- Import highlight plugin style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/highlight/ui/trumbowyg.highlight.min.css">
<!-- Import prismjs and Trumbowyg highlight at the end of <body>... -->
<script src="node_modules/prismjs/prism.js"></script>
<script src="trumbowyg/dist/plugins/highlight/trumbowyg.highlight.min.js"></script>
            

You also can add another prism language syntaxes for example:


<!-- Import prismjs and Trumbowyg highlight at the end of <body>... -->
<script src="node_modules/prismjs/prism.js"></script>
<script src="node_modules/prismjs/components/prism-csharp.js"></script>
<script src="node_modules/prismjs/components/prism-go.js"></script>
<script src="node_modules/prismjs/components/prism-markdown.js"></script>
<script src="trumbowyg/dist/plugins/highlight/trumbowyg.highlight.min.js"></script>
            

Also on your website you must connect prismjs styles to lighting code worked. For example connect prism.css in head:


<!-- Import prismjs style in <head>... on your site -->
<link rel="stylesheet" href="node_modules/prismjs/themes/prism.css">
            

Options

enableLineHighlight boolean
Add a field to highlight some lines if enabled.
You need to import the Prism Line Highlight plugin JS + CSS.
Default: true
languageNames Object<string, string>
Pairs with language code as key and language name as value.
Default: List of all languages available in Prism

History

History plugin is an enhanced implementation of the undo and redo functionality. It tracks changes of the editor and will add each change to a history stack. If a word is typed in or text was pasted, it counts as a single stack entry.

Try history plugin live demo! View history plugin code on GitHub

How to use it?


<!-- Import Trumbowyg history JS at the end of <body>... -->
<script src="trumbowyg/dist/plugins/history/trumbowyg.history.min.js"></script>
            

Then you can use the new button definitions historyUndo and historyRedo


$('#my-editor').trumbowyg({
    btns: [
        ['historyUndo', 'historyRedo']
    ]
});
            

Indent

This plugin allows you to indent or outdent your text.

Try indent live demo! View indent plugin code on GitHub

How to use it?


<!-- Import Trumbowyg indent at the end of <body>... -->
<script src="trumbowyg/dist/plugins/indent/trumbowyg.indent.min.js"></script>
            

Then you can use the news buttons definition indent or outdent


$('#my-editor').trumbowyg({
    btns: [
        ['indent', 'outdent']
    ]
});
            

Insert audio

Do the same as insert image, but for audio.

Try insert audio live demo! View insertAudio plugin code on GitHub

How to use it?


<!-- Import Trumbowyg insertAudio at the end of <body>... -->
<script src="trumbowyg/dist/plugins/insertaudio/trumbowyg.insertaudio.min.js"></script>
            

Then you can use the new button definition insertAudio


$('#my-editor').trumbowyg({
    btns: [
        ['insertAudio']
    ]
});
            

Line height

This plugin allows user to custom line height.

Try line height live demo! View line height plugin code on GitHub

How to use it?


<!-- Import Trumbowyg lineheight at the end of <body>... -->
<script src="trumbowyg/dist/plugins/lineheight/trumbowyg.lineheight.min.js"></script>
            

Then you can use the new button definition lineheight


$('#my-editor').trumbowyg({
    btns: [
        ['lineheight']
    ]
});
            

You can also choose custom size list:


$('#my-editor').trumbowyg({
    btns: [
        ['lineheight']
    ],
    plugins: {
        fontsize: {
            sizeList: [
                '12px',
                '14px',
                '16px'
            ]
        }
    }
});
            

MathML

This plugin allows user to use MathML.

Try MathML live demo! View MathML plugin code on GitHub

How to use it?


<!-- Import Trumbowyg MathML style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/mathml/ui/trumbowyg.mathml.min.css">
            

<!-- Import Trumbowyg MathJax at the end of <body>... -->
<script src="//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"></script>
<!-- Import Trumbowyg MathML plugin at the end of <body>... -->
<script src="trumbowyg/dist/plugins/mathml/trumbowyg.mathml.min.js"></script>
            

Then you can use the new button definition mathml


// MathJax inline configuration
MathJax.Hub.Config({
    tex2jax: {
        inlineMath: [
            ['$', '$'],
            ['\\(', '\\)']
        ]
    }
});

// Trumbowyg initialization with MathML button
$('#editor')
.trumbowyg({
    btns: [
        'mathml'
    ]
});
            

Mention

This plugin allows to mention an user from a source list.

Try mention live demo! View mention plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/mention/trumbowyg.mention.min.js"></script>
            

Then you can use the new button definition mention


$('#my-editor').trumbowyg({
    btns: [
        ['mention']
    ],
    plugins: {
        mention: {
            source: [
                {login: 'lucy'},
                {login: 'jdoe'},
                {login: 'mlisa'},
                {login: 'jcesar'},
            ]
        }
    }
});
            

Options

source array<object>
List of mentionable users. Must contains login key if you do not override two other options.
Default: []
formatDropdownItem function
Custom format items in mention dropdown: function (item) {}. Must return a string.
Default output: item.login
formatResult function
The string which will be inserted in editor: function (item) {}. Must return a string.
Default output: '@' + item.login + ' '

Example with options:


$('#my-editor').trumbowyg({
    btns: [
        ['mention']
    ],
    plugins: {
        mention: {
            source: [
                {login: 'lucy', name: 'Lucy'},
                {login: 'jdoe', name: 'John Doe'},
                {login: 'mlisa', name: 'Mona Lisa'},
                {login: 'jcesar', name: 'Julius Cesarius'},
            ],
            formatDropdownItem: function (item) {
                return item.name + ' (' + item.login + ')';
            },
            formatResult: function (item) {
                return '@"' + item.name + '" ';
            }
        }
    }
});
            

Noembed

Allows you to embed any content from a link using noembed.com API.

Try noembed live demo! View noembed plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/noembed/trumbowyg.noembed.min.js"></script>
            

Then you can use the new button definition noembed


$('#my-editor').trumbowyg({
    btns: [
        ['noembed']
    ]
});
            

Paste embed

Paste embed plugin handles paste events. It looks for paste event, checks if it's a URL and uses noembed.com API to retrieve an iframe from that URL. If it can't retrieve an iframe, it will put an anchor tag around the URL. It doesn't do anything on text or HTML paste.

Try paste embed live demo! View pasteembed plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/pasteembed/trumbowyg.pasteembed.min.js"></script>
            

Paste embed now works on every new Trumbowyg instance.

Paste image

Paste image plugin handle paste events, check if you have image files in your clipboard, then paste them into the editor as base64. It do nothing on text or HTML paste.

Try paste image live demo! View paste image plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/pasteimage/trumbowyg.pasteimage.min.js"></script>
            

Paste image works now on every new Trumbowyg instance.

Preformatted

Wraps your code with <pre> tags.

Try preformatted live demo! View preformatted plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/preformatted/trumbowyg.preformatted.min.js"></script>
            

Then you can use the new button definition preformatted


$('#my-editor').trumbowyg({
    btns: [
        ['preformatted']
    ]
});
            

Resizimg

Allows you to resize images (preserving aspect ratio) by dragging their bottom-right corner.

Try resizimg live demo! View resizimg plugin code on GitHub

Dependencies

This plugin depends on the jquery-resizable plugin, which must be loaded beforehand.

If in your project use the JQuery UI library with resizable interaction you need to include this script to avoid issue before load jquery-resizable.

How to use it?


<!-- Import dependency for Resizimg (tested with version 0.35). For a production setup, follow install instructions here: https://github.com/RickStrahl/jquery-resizable -->
<script src="//rawcdn.githack.com/RickStrahl/jquery-resizable/0.35/dist/jquery-resizable.min.js"></script>
<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/resizimg/trumbowyg.resizimg.min.js"></script>
            

Options

minSize number
Minimal size of image and also of draggable right-bottom corner.
Default: 32
step number
Increment when increasing / decreasing image height.
Default: 4

Example with options:


$('#editor').trumbowyg({
    plugins: {
        resizimg: {
            minSize: 64,
            step: 16,
        }
    }
});
            

The resulting image size is currently absolute: CSS height in pixels (style attribute).

Ruby

Allows you to support ruby markup.

Try ruby live demo! View ruby plugin code on GitHub

How to use it?


<!-- Import Trumbowyg ruby style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/ruby/ui/trumbowyg.ruby.min.css">
            

<!-- Import Trumbowyg ruby at the end of <body>... -->
<script src="trumbowyg/dist/plugins/ruby/trumbowyg.ruby.min.js"></script>
            

Then you can use the new button definition ruby


$('#my-editor').trumbowyg({
    btns: [
        ['specialChars']
    ]
});
            

Special Chars

Special Chars plugin allows you to insert some special characters in your editor.

Try special chars live demo! View specialChars plugin code on GitHub

How to use it?


<!-- Import Trumbowyg specialchars style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/specialchars/ui/trumbowyg.specialchars.min.css">
            

<!-- Import Trumbowyg specialchars at the end of <body>... -->
<script src="trumbowyg/dist/plugins/specialchars/trumbowyg.specialchars.min.js"></script>
            

Then you can use the new button definition specialchars


$('#my-editor').trumbowyg({
    btns: [
        ['specialChars']
    ]
});
            

Options

symbolList array<string>
List of special chars displayed in the dropdown.
To force next char to be on a new line in dropdown, add a null entry.
Default: check in the code.

Example with options:


$('#editor-custom-list')
.trumbowyg({
    btns: [
        ['specialChars']
    ],
    plugins: {
        specialchars: {
            symbolList: [
                '00A2', '00A5', '00A4', '2030',  null,
                '00A9', '00AE',  '2122',  null,
                '2023', '25B6', '2B29', '25C6', null,
                '2211', '2243', '2264', '2265'
            ]
        }
    }
});
            

Speech recogintion

Speech recognition plugin allows you to add text via speech.

Try speech recognition live demo! View speech recognition plugin code on GitHub

How to use it?


<!-- Import Trumbowyg speech recognition at the end of <body>... -->
<script src="trumbowyg/dist/plugins/speechrecognition/trumbowyg.speechrecognition.min.js"></script>
            

Then you can use the new button definition speechrecognition.


$('#my-editor').trumbowyg({
    btns: [
        ['speechrecognition']
    ]
});
            

Options

lang string
Language code which language should be detected.
Default: en_GB

Example with options:


$('#my-editor)
.trumbowyg({
    btns: [
        ['speechrecognition']
    ],
    plugins: {
        speechrecognition: {
            lang: 'de-DE'
        }
    }
});
            

Table

Table plugin allows users to create and manage tables.

Try table live demo! View table plugin code on GitHub

How to use it?


<!-- Import Trumbowyg Table style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/table/ui/trumbowyg.table.min.css">
            

<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/table/trumbowyg.table.min.js"></script>
            

Then you can use the new button definition table


$('#my-editor').trumbowyg({
    btns: [
        ['table', 'tableCellBackgroundColor', 'tableBorderColor']
    ],
    plugins: {
        table: {
            // Some table plugin options, see details below
        }
    }
});
            

To get a good UX, you need to have some CSS defined on your tables. A good start can be something to guarantee that cell width and height will not collapse, and allow to click inside. Also, ensure you have some borders to distinguish cells. Here is a minimal example using CSS custom properties:


:root {
    --tbw-cell-vertical-padding: 4px;
    --tbw-cell-horizontal-padding: 8px;
    --tbw-cell-line-height: 1.5em;
}

table {
    margin-bottom: var(--tbw-cell-line-height);
}

th,
td {
    height: calc(var(--tbw-cell-vertical-padding) * 2 + var(--tbw-cell-line-height));
    min-width: calc(var(--tbw-cell-horizontal-padding) * 2);
    padding: var(--tbw-cell-vertical-padding) var(--tbw-cell-horizontal-padding);
    border: 1px solid #e7eaec;
}
            

Options

rows number
The count of rows which should be used for table building in the table grid dropdown.
Default: 8
columns number
The count of columns which should be used for table building in the table grid dropdown.
Default: 8
allowHorizontalResize boolean
Enable handles to adjust columns width.
Default: true
colorList array<string>
List of colors available for both tableCellBackgroundColor and tableBorderColor dropdowns.
Default: check the code or the demo
backgroundColorList array<string>
List of colors available used in the tableCellBackgroundColor dropdown instead of colorList's one.
Default: fallbacks on colorList
allowCustomBackgroundColor boolean
Allow user to pick a color outside the list for tableCellBackgroundColor.
Default: true
displayBackgroundColorsAsList boolean
Switch from square to labelled list view for tableCellBackgroundColor. Default view is the square's one.
Default: false
borderColorList array<string>
List of colors available used in the tableBorderColor dropdown instead of colorList's one.
Default: fallbacks on colorList
allowCustomBorderColor boolean
Allow user to pick a color outside the list for tableBorderColor.
Default: true
displayBorderColorsAsList boolean
Switch from square to labelled list view for tableBorderColor. Default view is the square's one.
Default: false
dropdown object
Define the 'table' dropdown buttons when selection is inside a table.
  • title: the dropdown section title translation key.
  • buttons: button list
You can rearrange them and remove those you do not want.
Default:

[
    {
        title: 'tableRows',
        buttons: [
            'tableAddHeaderRow',
            'tableAddRowAbove',
            'tableAddRow',
            'tableDeleteRow',
        ],
    },
    {
        title: 'tableColumns',
        buttons: [
            'tableAddColumnLeft',
            'tableAddColumn',
            'tableDeleteColumn',
        ],
    },
    {
        title: 'tableVerticalAlign',
        buttons: [
            'tableVerticalAlignTop',
            'tableVerticalAlignMiddle',
            'tableVerticalAlignBottom',
        ],
    },
    {
        title: 'tableOthers',
        buttons: [
            // Cell merge/split
            'tableMergeCells',
            'tableUnmergeCells',
            'tableDestroy',
        ]
    }
]
                    
styler string Removed in 2.27.0
The class which should be assigned to each table by default
Default: 'table'

From version 2.27, use core's tagClasses feature:


$('.trumbowyg').trumbowyg({
-    plugins: {
-        table: {
-            styler: 'table',
-        },
-    },
+    tagClasses: {
+        table: 'table',
+    },
});
                    

Buttons

Table plugin comes with a lot of buttons.
You can use most of them anywhere: in the table dropdown, in the button pane or in your custom dropdown.
Exceptions are: table, tableCellBackgroundColor and tableBorderColor which can only be used directly in the button pane.

table
The table button comes with two different dropdowns depending on current selection:
  • If the current cursor is inside a table, the dropdown shows table actions;
  • Otherwise, renders a table grid where you can build the table.
See the dropdown option details above to see all default dropdown buttons.
tableAddHeaderRow
Add a <thead> to the table if there is no one already.
Can be deleted using tableRemoveRow.
tableAddRowAbove
Add a row above the selected row.
tableAddRow
Add a row under the selected row.
tableDeleteRow
Remove selected rows.
tableAddColumnLeft
Add a column to the left of the selected cell.
tableAddColumn
Add a column to the right of the selected cell.
tableDeleteColumn
Remove the selected cells columns.
tableVerticalAlignTop
Vertically align cell text to top.
tableVerticalAlignMiddle
Vertically align cell text to middle.
tableVerticalAlignBottom
Vertically align cell text to bottom.
tableMergeCells
Merge selected cells, if possible.
tableUnmergeCells
Unmerge selected cells.
tableDestroy
Delete the selected table.
tableCellBackgroundColor
Allow to choose selected cells background color.
tableBorderColor
Allow to choose table border color.

Template

Manage a set of HTML templates to insert fast.

Try template live demo! View template plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/template/trumbowyg.template.min.js"></script>
            

Then you can use the new button definition template and can add your template code.


$('#my-editor').trumbowyg({
    btns: [
        ['template']
    ],
    plugins: {
        templates: [
            {
                name: 'Template 1',
                html: '<p>I am a template!</p>'
            },
            {
                name: 'Template 2',
                html: '<p>I am a different template!</p>'
            }
        ]
    }
});
            

Tenor

User can search and pick gif to insert from Tenor

Try Tenor plugin live demo! View Tenor plugin code on GitHub

How to use it?


<!-- Import Trumbowyg Tenor style in <head>... -->
<link rel="stylesheet" href="trumbowyg/dist/plugins/giphy/ui/trumbowyg.tenor.min.css">
            

<!-- Import Trumbowyg Giphy JS at the end of <body>... -->
<script src="trumbowyg/dist/plugins/giphy/trumbowyg.tenor.min.js"></script>
            

Then, you need to create a Tenor App to get an API Key.

Use the new button definition tenor and your newly created Tenor API Key:


$('#my-editor').trumbowyg({
    btns: [
        ['tenor']
    ],
    plugins: {
        tenor: {
            apiKey: 'yourVeryOwnApiKey'
        }
    }
});
            

Options

apiKey string (required)
Tenor API Key of your application. Create a Tenor App to get an API Key.
contentFilter string
Specify the content safety filter level.
The accepted values are off, low, medium, and high.
Default: all
locale string
Specify the default language to interpret the search string (xx_YY).
xx is the language's ISO 639-1 language code, while the optional _YY value is the two-letter 3166-1 country code.
Default: en_US
throttleDelay number (in milliseconds)
It's the delay between each Tenor API requests while user is typing his query.
Default: 300
noResultGifUrl string
The image URL shown when there is no result.
Default: https://media1.tenor.com/m/KOZLvzU0o4kAAAAC/no-results.gif

Example with options:


$('#my-editor').trumbowyg({
    btns: [
        ['tenor']
    ],
    plugins: {
        giphy: {
            apiKey: 'yourVeryOwnApiKey',
            locale: 'en_US',
            contentFilter: "off",
            throttleDelay: 500,
            noResultGifUrl: 'https://media1.tenor.com/m/KOZLvzU0o4kAAAAC/no-results.gif'
        }
    }
});
            

Upload

Add an upload front-end allowing users to select an image, upload it with progress bar and then insert the uploaded image in the editor.

Try upload live demo! View upload plugin code on GitHub

How to use it?


<!-- Import Trumbowyg plugins... -->
<script src="trumbowyg/dist/plugins/upload/trumbowyg.upload.min.js"></script>
            

Then you can use the new button definition upload


$('#my-editor').trumbowyg({
    btns: [
        ['upload']
    ],
    plugins: {
        upload: {
            // Some upload plugin options, see details below
        }
    }
});
            

Options

serverPath string
The URL to the server which catch the upload request
Default: ''
fileFieldName string
The POST property key associated to the upload file
Default: 'fileToUpload'
data array<Object>
Additional data for ajax. jQuery.ajax data option
Example: [{name: 'myKey1', value: 'myValue1'}, {name: 'username', value: 'myUsername'}]
Default: []
headers Object
Additional headers. Check jQuery.ajax headers option
Example: {headerKey: 'headerValue', Authorization: 'Client-ID xxxxxxxxx'}
Default: {}
xhrFields Object
Additional xhrFields. Check jQuery.ajax xhrFields option
Default: {}
urlPropertyName string
How to get image URL in the JSON response.
Example: 'url' for {url: 'https://example.com/myimage.jpg', success: true}
Default: 'file'
statusPropertyName string
How to get status from the json response.
Example: 'success' for {success: true, url: 'https://example.com/myimage.jpg'}
Default: 'success'
success function
Success callback: function (data, trumbowyg, $modal, values) {}
Default: null
error function
Error callback: function () {}
Default: null
imageWidthModalEdit boolean
Add a field allowing user to set image width
Default: false

Server side

Server side receives a POST request with the image in the fileFieldName field and alt which contains image description:


alt: 'Image description'
fileToUpload: // The image file
            

It must save this image, then return a JSON response like that:


{
    success: true, // Must be false if upload fails
    file: 'https://example.com/myimage.jpg'
}
            

Create your own

Introduction

A plugin can be a button, a paste handler or whatever you want. If you want to add a plugin myplugin to the official Trumbowyg list, you should follow this tree:

  • plugins
    • myplugin
      • ui
        • icons
          • myplugin.svg
        • sass
          • trumbowyg.myplugin.scss
      • trumbowyg.myplugin.js

As plugin can be something other than a button, icons and scss are optional depending on your feature.

In a plugin, you get access to all Trumbowyg core and you can extends all things as you wish.

Skeleton

To create a new plugin, you should start your trumbowyg.myplugin.js with this code:


(function ($) {
    'use strict';

    // Plugin default options
    var defaultOptions = {
    };

    // If the plugin is a button
    function buildButtonDef (trumbowyg) {
        return {
            fn: function () {
                // Plugin button logic
            }
        }
    }

    // If the plugin is a button
    function buildButtonIcon() {
        if ($("#trumbowyg-myplugin").length > 0) {
            return;
        }

        /*
        When your button is created, an SVG will be inserted with an xref to a
        symbol living at the fragment "#trumbowyg-myplugin". For Trumbowyg's
        own plugins, this will come from a sprite sheet which is injected into
        the document, built from the icon SVG in "ui/icons" in your plugin's
        source tree. This is how you should organise things if you are
        proposing your plugin to be included in the Trumbowyg main
        distribution, or if you are rolling your own custom build of Trumbowyg
        for your site.

        But, nothing says it *has* to come from Trumbowyg's injected sprite
        sheet; it only requires that this symbol exists in your document. To
        allow stand-alone distribution of your plugin, we're going to insert a
        custom SVG symbol into the document with the correct ID.
        */
        const iconWrap = $(document.createElementNS("http://www.w3.org/2000/svg", "svg"));
        iconWrap.addClass("trumbowyg-icons");

        // For demonstration purposes, we've taken the "File" icon from
        // Remix Icon - https://remixicon.com/
        iconWrap.html(`
            <symbol id="trumbowyg-myplugin" viewBox="0 0 24 24">
                <path d="M21 8v12.993A1 1 0 0 1 20.007 22H3.993A.993.993 0 0 1 3 21.008V2.992C3 2.455 3.449 2 4.002 2h10.995L21 8zm-2 1h-5V4H5v16h14V9zM8 7h3v2H8V7zm0 4h8v2H8v-2zm0 4h8v2H8v-2z"/>
            </symbol>
        `).appendTo(document.body);
    }


    $.extend(true, $.trumbowyg, {
        // Add some translations
        langs: {
            en: {
                myplugin: 'My plugin'
            }
        },
        // Register plugin in Trumbowyg
        plugins: {
            myplugin: {
                // Code called by Trumbowyg core to register the plugin
                init: function (trumbowyg) {
                    // Fill current Trumbowyg instance with the plugin default options
                    trumbowyg.o.plugins.myplugin = $.extend(true, {},
                        defaultOptions,
                        trumbowyg.o.plugins.myplugin || {}
                    );

                    // If the plugin is a paste handler, register it
                    trumbowyg.pasteHandlers.push(function(pasteEvent) {
                        // My plugin paste logic
                    });

                    // If the plugin is a button
                    buildButtonIcon();
                    trumbowyg.addBtnDef('myplugin', buildButtonDef(trumbowyg));
                },
                // Return a list of button names which are active on current element
                tagHandler: function (element, trumbowyg) {
                    return [];
                },
                destroy: function (trumbowyg) {
                }
            }
        }
    })
})(jQuery);
            

Interact with content

To interact with editor and its content, you should use Trumbowyg core API. You can also check existing plugins source code to see how it works!

Go to the core API documentation

================================================ FILE: docs/gulpfile.mjs ================================================ // jshint node:true 'use strict'; import gulp from 'gulp'; import gulpAutoprefixer from 'gulp-autoprefixer'; import gulpLivereload from 'gulp-livereload'; import gulpCleanCss from 'gulp-clean-css'; import gulpSassPlugin from 'gulp-sass'; import * as sass from 'sass'; import {deleteAsync} from 'del'; const gulpSass = () => gulpSassPlugin(sass)(); const mainStyle = 'scss/main.scss'; const clean = function () { return deleteAsync(['dist/*']); }; const styles = function () { return gulp.src(mainStyle) .pipe(gulpSass({ sass: 'sass', includePaths: ['sass'] })) .pipe(gulpAutoprefixer(['last 1 version', '> 1%', 'ff >= 20', 'ie >= 8', 'opera >= 12', 'Android >= 2.2'], {cascade: true})) .pipe(gulpCleanCss()) .pipe(gulp.dest('css/')); }; const watch = function () { gulp.watch(['scss/*.scss'], styles); gulp.watch(['css/**', 'img/*', 'js/*'], function (file) { gulpLivereload.changed(file); }); gulpLivereload.listen(); }; const build = gulp.series(styles); const buildAndWatch = gulp.series(build, watch); export default buildAndWatch; export { clean, build, }; ================================================ FILE: docs/humans.txt ================================================ # humanstxt.org/ # The humans responsible & technology colophon # TEAM Alex-D -- Main developer -- @AlexandreDemode # TECHNOLOGY COLOPHON HTML5, CSS3 JavaScript, jQuery ================================================ FILE: docs/index.html ================================================ Trumbowyg - A lightweight WYSIWYG editor by Alex-D / Alexandre Demode

A lightweight WYSIWYG editor

Light, translatable and customisable jQuery plugin
Beautiful design, generates semantic code, comes with a powerful API

HTML5 & semantic

Editor and generated code are optimized for HTML5 support. Compatible with all recents browsers like IE9+, Chrome, Opera and Firefox.

Fast & lightweight

All existing WYSIWYG editors are larger than 45kB. Trumbowyg is only 20kB which means faster page loading. No useless features, just the necessary ones to generate clean, semantic code.

Customizable

Options and design are entirely configurable to suit your needs. However, the default design is compatible with Retina display and optimized for a great and simple user experience.

Packages

Ready for use Web frameworks and CMS packages

You can create your own package for Trumbowyg or use a ready for use. This is the full list of known packages for Trumbowyg.

Sponsored by

Premirus Corporation
================================================ FILE: docs/js/main.js ================================================ 'use strict'; if (window.location.href.indexOf('index.html') > 0) { window.location = window.location.href.replace('index.html', ''); } hljs.configure({ ignoreUnescapedHTML: true, }); // Highlight.js mergeHtmlPlugin hljs.addPlugin((function () { 'use strict'; var originalStream; /** * @param {string} value * @returns {string} */ function escapeHTML(value) { return value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /* plugin itself */ /** @type {HLJSPlugin} */ const mergeHTMLPlugin = { // preserve the original HTML token stream "before:highlightElement": ({ el }) => { originalStream = nodeStream(el); }, // merge it afterwards with the highlighted token stream "after:highlightElement": ({ el, result, text }) => { if (!originalStream.length) return; const resultNode = document.createElement('div'); resultNode.innerHTML = result.value; result.value = mergeStreams(originalStream, nodeStream(resultNode), text); el.innerHTML = result.value; } }; /* Stream merging support functions */ /** * @typedef Event * @property {'start'|'stop'} event * @property {number} offset * @property {Node} node */ /** * @param {Node} node */ function tag(node) { return node.nodeName.toLowerCase(); } /** * @param {Node} node */ function nodeStream(node) { /** @type Event[] */ const result = []; (function _nodeStream(node, offset) { for (let child = node.firstChild; child; child = child.nextSibling) { if (child.nodeType === 3) { offset += child.nodeValue.length; } else if (child.nodeType === 1) { result.push({ event: 'start', offset: offset, node: child }); offset = _nodeStream(child, offset); // Prevent void elements from having an end tag that would actually // double them in the output. There are more void elements in HTML // but we list only those realistically expected in code display. if (!tag(child).match(/br|hr|img|input/)) { result.push({ event: 'stop', offset: offset, node: child }); } } } return offset; })(node, 0); return result; } /** * @param {any} original - the original stream * @param {any} highlighted - stream of the highlighted source * @param {string} value - the original source itself */ function mergeStreams(original, highlighted, value) { let processed = 0; let result = ''; const nodeStack = []; function selectStream() { if (!original.length || !highlighted.length) { return original.length ? original : highlighted; } if (original[0].offset !== highlighted[0].offset) { return (original[0].offset < highlighted[0].offset) ? original : highlighted; } return highlighted[0].event === 'start' ? original : highlighted; } /** * @param {Node} node */ function open(node) { function attributeString(attr) { return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"'; } result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>'; } /** * @param {Node} node */ function close(node) { result += ''; } /** * @param {Event} event */ function render(event) { (event.event === 'start' ? open : close)(event.node); } while (original.length || highlighted.length) { let stream = selectStream(); result += escapeHTML(value.substring(processed, stream[0].offset)); processed = stream[0].offset; if (stream === original) { /* On any opening or closing tag of the original markup we first close the entire highlighted node stack, then render the original tag along with all the following original tags at the same offset and then reopen all the tags on the highlighted stack. */ nodeStack.reverse().forEach(close); do { render(stream.splice(0, 1)[0]); stream = selectStream(); } while (stream === original && stream.length && stream[0].offset === processed); nodeStack.reverse().forEach(open); } else { if (stream[0].event === 'start') { nodeStack.push(stream[0].node); } else { nodeStack.pop(); } render(stream.splice(0, 1)[0]); } } return result + escapeHTML(value.substr(processed)); } return mergeHTMLPlugin; }())); Array.from(document.querySelectorAll('pre code')).forEach(function (codeElement) { const hasDiff = codeElement.classList.contains('diff'); if (hasDiff) { hljs.highlightElement(codeElement); codeElement.classList.remove('diff', 'language-diff'); } hljs.highlightElement(codeElement); if (hasDiff) { codeElement.classList.add('language-diff'); } }); (function ($) { if ($.trumbowyg) { var configurations = { core: {}, plugins: { btnsDef: { // Customizable dropdowns align: { dropdown: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], ico: 'justifyLeft' } }, btns: [ ['viewHTML'], ['undo', 'redo'], ['formatting'], ['strong', 'em', 'del', 'underline'], ['foreColor', 'backColor'], ['link'], ['insertImage', 'upload', 'base64', 'noembed', 'giphy'], ['align'], ['preformatted'], ['horizontalRule'], ['fullscreen'] ], plugins: { giphy: { apiKey: 'dNhCbN6hrhpBMxXhIswM34wIR2UBpCns' }, // Add imgur parameters to upload plugin upload: { serverPath: 'https://api.imgur.com/3/image', fileFieldName: 'image', headers: { 'Authorization': 'Client-ID 9e57cb1c4791cea' }, urlPropertyName: 'data.link' } } } }; // Demo switch var $demoTextarea = $('#trumbowyg-demo'); $demoTextarea.trumbowyg(configurations.core); $('.demo-switcher .button').on('click', function () { var $current = $('.demo-switcher .current'); $(this).parent().removeClass('current-' + $current.data('config')); $current.removeClass('current'); $(this).addClass('current'); $(this).parent().addClass('current-' + $(this).data('config')); $demoTextarea.trumbowyg('destroy'); $demoTextarea.trumbowyg(configurations[$(this).data('config')]); }); // Lang accordion $('#lang-list-view-full').on('click', function () { $('#lang-list-light').slideUp(100); $('#lang-list-full').slideDown(350); }); } // Languages continent switch var $continentNames = $('.continent-name'); $continentNames.each(function () { $(this).parent().attr('data-height', $(this).parent().height()); }); $continentNames.click(function () { var $oldOpen = $('#languages').find('.col-list ul li[style]'); $oldOpen.removeAttr('style'); $(this).parent().css({ height: $(this).parent().attr('data-height') + 'px' }); }); $continentNames.last().parent().css({ height: $continentNames.last().parent().attr('data-height') + 'px' }); // Add anchors $('.feature h3[id], .feature h4[id]').each(function () { $(this).after($('', { html: '', 'class': 'title-link', href: '#' + $(this).attr('id'), title: 'Permalink to ' + $(this).text() })); }); // Show star count function setStarsCount(stars) { $('.star-count').text(stars); } var date = new Date(); var starsKey = 'stars_' + date.getMonth() + '_' + date.getYear(); var stars = localStorage.getItem(starsKey); if (!stars) { $.ajax('https://api.github.com/repos/Alex-D/Trumbowyg', { success: function (data) { var stars = data.stargazers_count; // jshint ignore:line localStorage.clear(); localStorage.setItem(starsKey, stars); setStarsCount(stars); } }); } else { setStarsCount(stars); } // Switch iframe src for demos if ($('.main-demos').length > 0) { $('.documentation-summary a').each(function() { var demoHash = $(this).attr('href').replace('.html', '').replace(/[\/.]/g, '-').replace(/^-*/g, ''); $(this).attr('data-hash', demoHash); $(this).click(function() { $('.main-demos iframe').attr('src', $(this).attr('href')); window.location.hash = demoHash; return false; }); }); if (window.location.hash.length > 1) { var demoHref = $('[data-hash="' + window.location.hash.replace('#', '') + '"]').attr('href'); $('.main-demos iframe').attr('src', demoHref); } } })(jQuery); /* Google Analytics */ // jshint ignore:start (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('set', 'anonymizeIp', true); ga('create', 'UA-35470243-1', 'auto'); ga('send', 'pageview'); ================================================ FILE: docs/js/vendor/highlight.js ================================================ /*! Highlight.js v11.7.0 (git: 82688fad18) (c) 2006-2022 undefined and other contributors License: BSD-3-Clause */ var hljs=function(){"use strict";var e={exports:{}};function t(e){ return e instanceof Map?e.clear=e.delete=e.set=()=>{ throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{ throw Error("set is read-only") }),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n] ;"object"!=typeof i||Object.isFrozen(i)||t(i)})),e} e.exports=t,e.exports.default=t;class n{constructor(e){ void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} ignoreMatch(){this.isMatchIgnored=!0}}function i(e){ return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") }function r(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] ;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n} const s=e=>!!e.scope||e.sublanguage&&e.language;class o{constructor(e,t){ this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ this.buffer+=i(e)}openNode(e){if(!s(e))return;let t="" ;t=e.sublanguage?"language-"+e.language:((e,{prefix:t})=>{if(e.includes(".")){ const n=e.split(".") ;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") }return`${t}${e}`})(e.scope,{prefix:this.classPrefix}),this.span(t)} closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ this.buffer+=``}}const a=(e={})=>{const t={children:[]} ;return Object.assign(t,e),t};class c{constructor(){ this.rootNode=a(),this.stack=[this.rootNode]}get top(){ return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ this.top.children.push(e)}openNode(e){const t=a({scope:e}) ;this.add(t),this.stack.push(t)}closeNode(){ if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ "string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ c._collapse(e)})))}}class l extends c{constructor(e){super(),this.options=e} addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root ;n.sublanguage=!0,n.language=t,this.add(n)}toHTML(){ return new o(this,this.options).value()}finalize(){return!0}}function g(e){ return e?"string"==typeof e?e:e.source:null}function d(e){return p("(?=",e,")")} function u(e){return p("(?:",e,")*")}function h(e){return p("(?:",e,")?")} function p(...e){return e.map((e=>g(e))).join("")}function f(...e){const t=(e=>{ const t=e[e.length-1] ;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} })(e);return"("+(t.capture?"":"?:")+e.map((e=>g(e))).join("|")+")"} function b(e){return RegExp(e.toString()+"|").exec("").length-1} const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ ;function E(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n ;let i=g(e),r="";for(;i.length>0;){const e=m.exec(i);if(!e){r+=i;break} r+=i.substring(0,e.index), i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+t):(r+=e[0], "("===e[0]&&n++)}return r})).map((e=>`(${e})`)).join(t)} const x="[a-zA-Z]\\w*",w="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",_="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",O="\\b(0b[01]+)",v={ begin:"\\\\[\\s\\S]",relevance:0},N={scope:"string",begin:"'",end:"'", illegal:"\\n",contains:[v]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", contains:[v]},M=(e,t,n={})=>{const i=r({scope:"comment",begin:e,end:t, contains:[]},n);i.contains.push({scope:"doctag", begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) ;const s=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) ;return i.contains.push({begin:p(/[ ]+/,"(",s,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i },S=M("//","$"),R=M("/\\*","\\*/"),j=M("#","$");var A=Object.freeze({ __proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:x,UNDERSCORE_IDENT_RE:w, NUMBER_RE:y,C_NUMBER_RE:_,BINARY_NUMBER_RE:O, RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", SHEBANG:(e={})=>{const t=/^#![ ]*\// ;return e.binary&&(e.begin=p(t,/.*\b/,e.binary,/\b.*/)),r({scope:"meta",begin:t, end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, BACKSLASH_ESCAPE:v,APOS_STRING_MODE:N,QUOTE_STRING_MODE:k,PHRASAL_WORDS_MODE:{ begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ },COMMENT:M,C_LINE_COMMENT_MODE:S,C_BLOCK_COMMENT_MODE:R,HASH_COMMENT_MODE:j, NUMBER_MODE:{scope:"number",begin:y,relevance:0},C_NUMBER_MODE:{scope:"number", begin:_,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:O,relevance:0}, REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//, end:/\/[gimuy]*/,illegal:/\n/,contains:[v,{begin:/\[/,end:/\]/,relevance:0, contains:[v]}]}]},TITLE_MODE:{scope:"title",begin:x,relevance:0}, UNDERSCORE_TITLE_MODE:{scope:"title",begin:w,relevance:0},METHOD_GUARD:{ begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{ "on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{ t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function I(e,t){ "."===e.input[e.index-1]&&t.ignoreMatch()}function T(e,t){ void 0!==e.className&&(e.scope=e.className,delete e.className)}function L(e,t){ t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", e.__beforeBegin=I,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, void 0===e.relevance&&(e.relevance=0))}function B(e,t){ Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function D(e,t){ if(e.match){ if(e.begin||e.end)throw Error("begin & end are not supported with match") ;e.begin=e.match,delete e.match}}function H(e,t){ void 0===e.relevance&&(e.relevance=1)}const P=(e,t)=>{if(!e.beforeMatch)return ;if(e.starts)throw Error("beforeMatch cannot be used with starts") ;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] })),e.keywords=n.keywords,e.begin=p(n.beforeMatch,d(n.begin)),e.starts={ relevance:0,contains:[Object.assign(n,{endsParent:!0})] },e.relevance=0,delete n.beforeMatch },C=["of","and","for","in","not","or","if","then","parent","list","value"] ;function $(e,t,n="keyword"){const i=Object.create(null) ;return"string"==typeof e?r(n,e.split(" ")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{ Object.assign(i,$(e[n],t,n))})),i;function r(e,n){ t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") ;i[n[0]]=[e,U(n[0],n[1])]}))}}function U(e,t){ return t?Number(t):(e=>C.includes(e.toLowerCase()))(e)?0:1}const z={},K=e=>{ console.error(e)},W=(e,...t)=>{console.log("WARN: "+e,...t)},X=(e,t)=>{ z[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),z[`${e}/${t}`]=!0) },G=Error();function Z(e,t,{key:n}){let i=0;const r=e[n],s={},o={} ;for(let e=1;e<=t.length;e++)o[e+i]=r[e],s[e+i]=!0,i+=b(t[e-1]) ;e[n]=o,e[n]._emit=s,e[n]._multi=!0}function F(e){(e=>{ e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ _wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope }),(e=>{if(Array.isArray(e.begin)){ if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), G ;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), G;Z(e,e.begin,{key:"beginScope"}),e.begin=E(e.begin,{joinWith:""})}})(e),(e=>{ if(Array.isArray(e.end)){ if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), G ;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), G;Z(e,e.end,{key:"endScope"}),e.end=E(e.end,{joinWith:""})}})(e)}function V(e){ function t(t,n){ return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) }class n{constructor(){ this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} addRule(e,t){ t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), this.matchAt+=b(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) ;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(E(e,{joinWith:"|" }),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex ;const t=this.matcherRe.exec(e);if(!t)return null ;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] ;return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){ this.rules=[],this.multiRegexes=[], this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n ;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex ;let n=t.exec(e) ;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} return n&&(this.regexIndex+=n.position+1, this.regexIndex===this.count&&this.considerAll()),n}} if(e.compilerExtensions||(e.compilerExtensions=[]), e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") ;return e.classNameAliases=r(e.classNameAliases||{}),function n(s,o){const a=s ;if(s.isCompiled)return a ;[T,D,F,P].forEach((e=>e(s,o))),e.compilerExtensions.forEach((e=>e(s,o))), s.__beforeBegin=null,[L,B,H].forEach((e=>e(s,o))),s.isCompiled=!0;let c=null ;return"object"==typeof s.keywords&&s.keywords.$pattern&&(s.keywords=Object.assign({},s.keywords), c=s.keywords.$pattern, delete s.keywords.$pattern),c=c||/\w+/,s.keywords&&(s.keywords=$(s.keywords,e.case_insensitive)), a.keywordPatternRe=t(c,!0), o&&(s.begin||(s.begin=/\B|\b/),a.beginRe=t(a.begin),s.end||s.endsWithParent||(s.end=/\B|\b/), s.end&&(a.endRe=t(a.end)), a.terminatorEnd=g(a.end)||"",s.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(s.end?"|":"")+o.terminatorEnd)), s.illegal&&(a.illegalRe=t(s.illegal)), s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>r(e,{ variants:null},t)))),e.cachedVariants?e.cachedVariants:q(e)?r(e,{ starts:e.starts?r(e.starts):null }):Object.isFrozen(e)?r(e):e))("self"===e?s:e)))),s.contains.forEach((e=>{n(e,a) })),s.starts&&n(s.starts,o),a.matcher=(e=>{const t=new i ;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" }))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" }),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function q(e){ return!!e&&(e.endsWithParent||q(e.starts))}class J extends Error{ constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} const Y=i,Q=r,ee=Symbol("nomatch");var te=(t=>{ const i=Object.create(null),r=Object.create(null),s=[];let o=!0 ;const a="Could not find the language '{}', did you forget to load/include a language module?",c={ disableAutodetect:!0,name:"Plain text",contains:[]};let g={ ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", cssSelector:"pre code",languages:null,__emitter:l};function b(e){ return g.noHighlightRe.test(e)}function m(e,t,n){let i="",r="" ;"object"==typeof t?(i=e, n=t.ignoreIllegals,r=t.language):(X("10.7.0","highlight(lang, code, ...args) has been deprecated."), X("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), r=e,i=t),void 0===n&&(n=!0);const s={code:i,language:r};k("before:highlight",s) ;const o=s.result?s.result:E(s.language,s.code,n) ;return o.code=s.code,k("after:highlight",o),o}function E(e,t,r,s){ const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(S) ;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(S),n="" ;for(;t;){n+=S.substring(e,t.index) ;const r=y.case_insensitive?t[0].toLowerCase():t[0],s=(i=r,N.keywords[i]);if(s){ const[e,i]=s ;if(M.addText(n),n="",c[r]=(c[r]||0)+1,c[r]<=7&&(R+=i),e.startsWith("_"))n+=t[0];else{ const n=y.classNameAliases[e]||e;M.addKeyword(t[0],n)}}else n+=t[0] ;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(S)}var i ;n+=S.substring(e),M.addText(n)}function d(){null!=N.subLanguage?(()=>{ if(""===S)return;let e=null;if("string"==typeof N.subLanguage){ if(!i[N.subLanguage])return void M.addText(S) ;e=E(N.subLanguage,S,!0,k[N.subLanguage]),k[N.subLanguage]=e._top }else e=x(S,N.subLanguage.length?N.subLanguage:null) ;N.relevance>0&&(R+=e.relevance),M.addSublanguage(e._emitter,e.language) })():l(),S=""}function u(e,t){let n=1;const i=t.length-1;for(;n<=i;){ if(!e._emit[n]){n++;continue}const i=y.classNameAliases[e[n]]||e[n],r=t[n] ;i?M.addKeyword(r,i):(S=r,l(),S=""),n++}}function h(e,t){ return e.scope&&"string"==typeof e.scope&&M.openNode(y.classNameAliases[e.scope]||e.scope), e.beginScope&&(e.beginScope._wrap?(M.addKeyword(S,y.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), S=""):e.beginScope._multi&&(u(e.beginScope,t),S="")),N=Object.create(e,{parent:{ value:N}}),N}function p(e,t,i){let r=((e,t)=>{const n=e&&e.exec(t) ;return n&&0===n.index})(e.endRe,i);if(r){if(e["on:end"]){const i=new n(e) ;e["on:end"](t,i),i.isMatchIgnored&&(r=!1)}if(r){ for(;e.endsParent&&e.parent;)e=e.parent;return e}} if(e.endsWithParent)return p(e.parent,t,i)}function f(e){ return 0===N.matcher.regexIndex?(S+=e[0],1):(I=!0,0)}function b(e){ const n=e[0],i=t.substring(e.index),r=p(N,e,i);if(!r)return ee;const s=N ;N.endScope&&N.endScope._wrap?(d(), M.addKeyword(n,N.endScope._wrap)):N.endScope&&N.endScope._multi?(d(), u(N.endScope,e)):s.skip?S+=n:(s.returnEnd||s.excludeEnd||(S+=n), d(),s.excludeEnd&&(S=n));do{ N.scope&&M.closeNode(),N.skip||N.subLanguage||(R+=N.relevance),N=N.parent }while(N!==r.parent);return r.starts&&h(r.starts,e),s.returnEnd?0:n.length} let m={};function w(i,s){const a=s&&s[0];if(S+=i,null==a)return d(),0 ;if("begin"===m.type&&"end"===s.type&&m.index===s.index&&""===a){ if(S+=t.slice(s.index,s.index+1),!o){const t=Error(`0 width match regex (${e})`) ;throw t.languageName=e,t.badRule=m.rule,t}return 1} if(m=s,"begin"===s.type)return(e=>{ const t=e[0],i=e.rule,r=new n(i),s=[i.__beforeBegin,i["on:begin"]] ;for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return f(t) ;return i.skip?S+=t:(i.excludeBegin&&(S+=t), d(),i.returnBegin||i.excludeBegin||(S=t)),h(i,e),i.returnBegin?0:t.length})(s) ;if("illegal"===s.type&&!r){ const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"') ;throw e.mode=N,e}if("end"===s.type){const e=b(s);if(e!==ee)return e} if("illegal"===s.type&&""===a)return 1 ;if(A>1e5&&A>3*s.index)throw Error("potential infinite loop, way more iterations than matches") ;return S+=a,a.length}const y=O(e) ;if(!y)throw K(a.replace("{}",e)),Error('Unknown language: "'+e+'"') ;const _=V(y);let v="",N=s||_;const k={},M=new g.__emitter(g);(()=>{const e=[] ;for(let t=N;t!==y;t=t.parent)t.scope&&e.unshift(t.scope) ;e.forEach((e=>M.openNode(e)))})();let S="",R=0,j=0,A=0,I=!1;try{ for(N.matcher.considerAll();;){ A++,I?I=!1:N.matcher.considerAll(),N.matcher.lastIndex=j ;const e=N.matcher.exec(t);if(!e)break;const n=w(t.substring(j,e.index),e) ;j=e.index+n} return w(t.substring(j)),M.closeAllNodes(),M.finalize(),v=M.toHTML(),{ language:e,value:v,relevance:R,illegal:!1,_emitter:M,_top:N}}catch(n){ if(n.message&&n.message.includes("Illegal"))return{language:e,value:Y(t), illegal:!0,relevance:0,_illegalBy:{message:n.message,index:j, context:t.slice(j-100,j+100),mode:n.mode,resultSoFar:v},_emitter:M};if(o)return{ language:e,value:Y(t),illegal:!1,relevance:0,errorRaised:n,_emitter:M,_top:N} ;throw n}}function x(e,t){t=t||g.languages||Object.keys(i);const n=(e=>{ const t={value:Y(e),illegal:!1,relevance:0,_top:c,_emitter:new g.__emitter(g)} ;return t._emitter.addText(e),t})(e),r=t.filter(O).filter(N).map((t=>E(t,e,!1))) ;r.unshift(n);const s=r.sort(((e,t)=>{ if(e.relevance!==t.relevance)return t.relevance-e.relevance ;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1 ;if(O(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=s,l=o ;return l.secondBest=a,l}function w(e){let t=null;const n=(e=>{ let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" ;const n=g.languageDetectRe.exec(t);if(n){const t=O(n[1]) ;return t||(W(a.replace("{}",n[1])), W("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return ;if(k("before:highlightElement",{el:e,language:n }),e.children.length>0&&(g.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), console.warn("The element with unescaped HTML:"), console.warn(e)),g.throwUnescapedHTML))throw new J("One of your code blocks includes unescaped HTML.",e.innerHTML) ;t=e;const i=t.textContent,s=n?m(i,{language:n,ignoreIllegals:!0}):x(i) ;e.innerHTML=s.value,((e,t,n)=>{const i=t&&r[t]||n ;e.classList.add("hljs"),e.classList.add("language-"+i) })(e,n,s.language),e.result={language:s.language,re:s.relevance, relevance:s.relevance},s.secondBest&&(e.secondBest={ language:s.secondBest.language,relevance:s.secondBest.relevance }),k("after:highlightElement",{el:e,result:s,text:i})}let y=!1;function _(){ "loading"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(w):y=!0 }function O(e){return e=(e||"").toLowerCase(),i[e]||i[r[e]]} function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ r[e.toLowerCase()]=t}))}function N(e){const t=O(e) ;return t&&!t.disableAutodetect}function k(e,t){const n=e;s.forEach((e=>{ e[n]&&e[n](t)}))} "undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ y&&_()}),!1),Object.assign(t,{highlight:m,highlightAuto:x,highlightAll:_, highlightElement:w, highlightBlock:e=>(X("10.7.0","highlightBlock will be removed entirely in v12.0"), X("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{g=Q(g,e)}, initHighlighting:()=>{ _(),X("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, initHighlightingOnLoad:()=>{ _(),X("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") },registerLanguage:(e,n)=>{let r=null;try{r=n(t)}catch(t){ if(K("Language definition for '{}' could not be registered.".replace("{}",e)), !o)throw t;K(t),r=c} r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&v(r.aliases,{ languageName:e})},unregisterLanguage:e=>{delete i[e] ;for(const t of Object.keys(r))r[t]===e&&delete r[t]}, listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v, autoDetection:N,inherit:Q,addPlugin:e=>{(e=>{ e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ e["before:highlightBlock"](Object.assign({block:t.el},t)) }),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)} }),t.debugMode=()=>{o=!1},t.safeMode=()=>{o=!0 },t.versionString="11.7.0",t.regex={concat:p,lookahead:d,either:f,optional:h, anyNumberOfTimes:u};for(const t in A)"object"==typeof A[t]&&e.exports(A[t]) ;return Object.assign(t,A),t})({});return te}() ;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);/*! `scss` grammar compiled for Highlight.js 11.7.0 */ (()=>{var e=(()=>{"use strict" ;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],r=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],i=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse() ;return n=>{const a=(e=>({IMPORTANT:{scope:"meta",begin:"!important"}, BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number", begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{ className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{ scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ scope:"number", begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/} }))(n),l=t,s=i,d="@[a-z-]+",c={className:"variable", begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b",relevance:0};return{name:"SCSS", case_insensitive:!0,illegal:"[=/|']", contains:[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE,a.CSS_NUMBER_MODE,{ className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{ className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0 },a.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag", begin:"\\b("+e.join("|")+")\\b",relevance:0},{className:"selector-pseudo", begin:":("+s.join("|")+")"},{className:"selector-pseudo", begin:":(:)?("+l.join("|")+")"},c,{begin:/\(/,end:/\)/, contains:[a.CSS_NUMBER_MODE]},a.CSS_VARIABLE,{className:"attribute", begin:"\\b("+o.join("|")+")\\b"},{ begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" },{begin:/:/,end:/[;}{]/,relevance:0, contains:[a.BLOCK_COMMENT,c,a.HEXCOLOR,a.CSS_NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,a.IMPORTANT,a.FUNCTION_DISPATCH] },{begin:"@(page|font-face)",keywords:{$pattern:d,keyword:"@page @font-face"}},{ begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/, keyword:"and or not only",attribute:r.join(" ")},contains:[{begin:d, className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute" },c,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,a.HEXCOLOR,a.CSS_NUMBER_MODE] },a.FUNCTION_DISPATCH]}}})();hljs.registerLanguage("scss",e)})();/*! `javascript` grammar compiled for Highlight.js 11.7.0 */ (()=>{var e=(()=>{"use strict" ;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],r=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],c=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(r,t,s) ;return o=>{const l=o.regex,b=e,d={begin:/<[A-Za-z0-9\\._:-]+/, end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ const a=e[0].length+e.index,t=e.input[a] ;if("<"===t||","===t)return void n.ignoreMatch();let s ;">"===t&&(((e,{after:n})=>{const a="",M={ match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(C)], keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]} ;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ PARAMS_CONTAINS:p,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/, contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ label:"use_strict",className:"meta",relevance:10, begin:/^\s*['"]use (strict|asm)['"]/ },o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,y,N,_,h,{match:/\$\d+/},E,R,{ className:"attr",begin:b+l.lookahead(":"),relevance:0},M,{ begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", keywords:"return throw case",relevance:0,contains:[h,o.REGEXP_MODE,{ className:"function",begin:C,returnBegin:!0,end:"\\s*=>",contains:[{ className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, excludeEnd:!0,keywords:g,contains:p}]}]},{begin:/,/,relevance:0},{match:/\s+/, relevance:0},{variants:[{begin:"<>",end:""},{ match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:d.begin, "on:begin":d.isTrulyOpeningTag,end:d.end}],subLanguage:"xml",contains:[{ begin:d.begin,end:d.end,skip:!0,contains:["self"]}]}]},O,{ beginKeywords:"while if switch catch for"},{ begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:b, className:"title.function"})]},{match:/\.\.\./,relevance:0},x,{match:"\\$"+b, relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, className:"variable.constant"},w,T,{match:/\$[(.]/}]}}})() ;hljs.registerLanguage("javascript",e)})();/*! `typescript` grammar compiled for Highlight.js 11.7.0 */ (()=>{var e=(()=>{"use strict" ;const e="[A-Za-z$_][0-9A-Za-z$_]*",n=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],a=["true","false","null","undefined","NaN","Infinity"],t=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],s=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],c=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],r=["arguments","this","super","console","window","document","localStorage","module","global"],i=[].concat(c,t,s) ;function o(o){const l=o.regex,d=e,b={begin:/<[A-Za-z0-9\\._:-]+/, end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ const a=e[0].length+e.index,t=e.input[a] ;if("<"===t||","===t)return void n.ignoreMatch();let s ;">"===t&&(((e,{after:n})=>{const a="",M={ match:[/const|var|let/,/\s+/,d,/\s*/,/=\s*/,/(async\s*)?/,l.lookahead(T)], keywords:"async",className:{1:"keyword",3:"title.function"},contains:[S]} ;return{name:"Javascript",aliases:["js","jsx","mjs","cjs"],keywords:g,exports:{ PARAMS_CONTAINS:v,CLASS_REFERENCE:R},illegal:/#(?![$_A-z])/, contains:[o.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ label:"use_strict",className:"meta",relevance:10, begin:/^\s*['"]use (strict|asm)['"]/ },o.APOS_STRING_MODE,o.QUOTE_STRING_MODE,A,p,_,N,{match:/\$\d+/},E,R,{ className:"attr",begin:d+l.lookahead(":"),relevance:0},M,{ begin:"("+o.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", keywords:"return throw case",relevance:0,contains:[N,o.REGEXP_MODE,{ className:"function",begin:T,returnBegin:!0,end:"\\s*=>",contains:[{ className:"params",variants:[{begin:o.UNDERSCORE_IDENT_RE,relevance:0},{ className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, excludeEnd:!0,keywords:g,contains:v}]}]},{begin:/,/,relevance:0},{match:/\s+/, relevance:0},{variants:[{begin:"<>",end:""},{ match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:b.begin, "on:begin":b.isTrulyOpeningTag,end:b.end}],subLanguage:"xml",contains:[{ begin:b.begin,end:b.end,skip:!0,contains:["self"]}]}]},x,{ beginKeywords:"while if switch catch for"},{ begin:"\\b(?!function)"+o.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", returnBegin:!0,label:"func.def",contains:[S,o.inherit(o.TITLE_MODE,{begin:d, className:"title.function"})]},{match:/\.\.\./,relevance:0},I,{match:"\\$"+d, relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, contains:[S]},k,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, className:"variable.constant"},w,C,{match:/\$[(.]/}]}}return t=>{ const s=o(t),c=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],l={ beginKeywords:"namespace",end:/\{/,excludeEnd:!0, contains:[s.exports.CLASS_REFERENCE]},d={beginKeywords:"interface",end:/\{/, excludeEnd:!0,keywords:{keyword:"interface extends",built_in:c}, contains:[s.exports.CLASS_REFERENCE]},b={$pattern:e, keyword:n.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]), literal:a,built_in:i.concat(c),"variable.language":r},g={className:"meta", begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},u=(e,n,a)=>{ const t=e.contains.findIndex((e=>e.label===n)) ;if(-1===t)throw Error("can not find mode to replace");e.contains.splice(t,1,a)} ;return Object.assign(s.keywords,b), s.exports.PARAMS_CONTAINS.push(g),s.contains=s.contains.concat([g,l,d]), u(s,"shebang",t.SHEBANG()),u(s,"use_strict",{className:"meta",relevance:10, begin:/^\s*['"]use strict['"]/ }),s.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(s,{ name:"TypeScript",aliases:["ts","tsx"]}),s}})() ;hljs.registerLanguage("typescript",e)})();/*! `css` grammar compiled for Highlight.js 11.7.0 */ (()=>{var e=(()=>{"use strict" ;const e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],i=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],r=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],t=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse() ;return n=>{const a=n.regex,l=(e=>({IMPORTANT:{scope:"meta",begin:"!important"}, BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number", begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{ className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{ scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ scope:"number", begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z][A-Za-z0-9_-]*/} }))(n),s=[n.APOS_STRING_MODE,n.QUOTE_STRING_MODE];return{name:"CSS", case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"}, classNameAliases:{keyframePosition:"selector-tag"},contains:[l.BLOCK_COMMENT,{ begin:/-(webkit|moz|ms|o)-(?=[a-z])/},l.CSS_NUMBER_MODE,{ className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{ className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0 },l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{ begin:":("+r.join("|")+")"},{begin:":(:)?("+t.join("|")+")"}]},l.CSS_VARIABLE,{ className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:/:/,end:/[;}{]/, contains:[l.BLOCK_COMMENT,l.HEXCOLOR,l.IMPORTANT,l.CSS_NUMBER_MODE,...s,{ begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri" },contains:[...s,{className:"string",begin:/[^)]/,endsWithParent:!0, excludeEnd:!0}]},l.FUNCTION_DISPATCH]},{begin:a.lookahead(/@/),end:"[{;]", relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/ },{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{ $pattern:/[a-z-]+/,keyword:"and or not only",attribute:i.join(" ")},contains:[{ begin:/[a-z-]+(?=:)/,className:"attribute"},...s,l.CSS_NUMBER_MODE]}]},{ className:"selector-tag",begin:"\\b("+e.join("|")+")\\b"}]}}})() ;hljs.registerLanguage("css",e)})();/*! `json` grammar compiled for Highlight.js 11.7.0 */ (()=>{var e=(()=>{"use strict";return e=>{const a=["true","false","null"],n={ scope:"literal",beginKeywords:a.join(" ")};return{name:"JSON",keywords:{ literal:a},contains:[{className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/, relevance:1.01},{match:/[{}[\],:]/,className:"punctuation",relevance:0 },e.QUOTE_STRING_MODE,n,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE], illegal:"\\S"}}})();hljs.registerLanguage("json",e)})();/*! `diff` grammar compiled for Highlight.js 11.7.0 */ (()=>{var e=(()=>{"use strict";return e=>{const a=e.regex;return{name:"Diff", aliases:["patch"],contains:[{className:"meta",relevance:10, match:a.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/) },{className:"comment",variants:[{ begin:a.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/), end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{ className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/, end:/$/}]}}})();hljs.registerLanguage("diff",e)})();/*! `xml` grammar compiled for Highlight.js 11.7.0 */ (()=>{var e=(()=>{"use strict";return e=>{ const a=e.regex,n=a.concat(/[\p{L}_]/u,a.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),s={ className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},t={begin:/\s/, contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] },i=e.inherit(t,{begin:/\(/,end:/\)/}),c=e.inherit(e.APOS_STRING_MODE,{ className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),r={ endsWithParent:!0,illegal:/`]+/}]}]}]};return{ name:"HTML, XML", aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[t,l,c,i,{begin:/\[/,end:/\]/,contains:[{ className:"meta",begin://,contains:[t,i,l,c]}]}] },e.COMMENT(//,{relevance:10}),{begin://, relevance:10},s,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/, relevance:10,contains:[l]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag", begin:/)/,end:/>/,keywords:{name:"style"},contains:[r],starts:{ end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", begin:/)/,end:/>/,keywords:{name:"script"},contains:[r],starts:{ end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ className:"tag",begin:/<>|<\/>/},{className:"tag", begin:a.concat(//,/>/,/\s/)))), end:/\/?>/,contains:[{className:"name",begin:n,relevance:0,starts:r}]},{ className:"tag",begin:a.concat(/<\//,a.lookahead(a.concat(n,/>/))),contains:[{ className:"name",begin:n,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}} })();hljs.registerLanguage("xml",e)})(); ================================================ FILE: docs/package.json ================================================ { "name": "trumbowyg-github-page", "private": true, "author": { "name": "Alexandre Demode (Alex-D)", "email": "contact@alex-d.fr", "url": "https://alex-d.fr" }, "license": "MIT", "scripts": { "dev": "gulp", "clean": "gulp clean", "build": "gulp build" }, "volta": { "node": "22.13.1" } } ================================================ FILE: docs/robots.txt ================================================ # robotstxt.org/ User-agent: * ================================================ FILE: docs/scss/_base.scss ================================================ @use "variables"; /*! HTML5 Boilerplate v4.3.0 | MIT License | http://h5bp.com/ */ html, button, input, select, textarea { color: #222; } html { font-size: 1em; line-height: 1.4; } html, body { background: #fff; } ::-moz-selection { background: #b3d4fc; text-shadow: none; } ::selection { background: #b3d4fc; text-shadow: none; } hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } audio, canvas, img, video { vertical-align: middle; } fieldset { border: 0; margin: 0; padding: 0; } textarea { resize: vertical; } html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } body, button, input, select, textarea { font-family: variables.$font; font-size: 18px; font-weight: 300; } .wrapper { max-width: 1200px; margin: 0 auto; clear: both; p:last-child { margin-bottom: 0; } h4 { font-size: 30px; } code.console { background: variables.$text-color; color: #fff; font-size: 16px; padding: 3px 7px; } .note { display: table; color: #666; background: rgba(0, 0, 0, 0.02); padding: 8px 12px; border-left: 3px solid variables.$primary-color-light; border-radius: 8px; font-size: 15px; margin-block-start: 0.5em; margin-block-end: 0.5em; > :first-child { margin-top: 6px; } > :last-child { margin-bottom: 6px; } code { margin-left: 2px; margin-right: 2px; } } } .section { a { text-decoration: none; color: variables.$primary-color; &:hover, &:focus { text-decoration: underline; } } } h1, h2, h3, h4, h5, h6 { font-weight: 300; margin: 0; padding: 0; } hr.clearfix { display: block; border: none; background: none; margin: 0; padding: 0; height: 0; clear: both; } code, kbd, pre, samp { font-family: "JetBrains Mono", monospace; font-size: 15px; line-height: 1.5; } :root { --tbw-cell-vertical-padding: 4px; --tbw-cell-horizontal-padding: 8px; --tbw-cell-line-height: 1.5em; } table { margin-bottom: var(--tbw-cell-line-height); } th, td { height: calc(var(--tbw-cell-vertical-padding) * 2 + var(--tbw-cell-line-height)); min-width: calc(var(--tbw-cell-horizontal-padding) * 2); padding: var(--tbw-cell-vertical-padding) var(--tbw-cell-horizontal-padding); border: 1px solid #e7eaec; } thead { background: #FFFFFF; } ================================================ FILE: docs/scss/_buttons.scss ================================================ @use "variables"; .button { display: inline-block; position: relative; width: 250px; border: 2px solid transparent; margin: 0 auto; padding: 23px 30px; color: #fff; font-weight: 400; font-size: 16px; line-height: 1.2; text-decoration: none; border-radius: 50px; text-align: center; transition: color variables.$transition-duration, background-color variables.$transition-duration; &-primary { background: variables.$primary-color; } &-secondary { background: variables.$secondary-color; } &-ghost { border-color: rgba(255, 255, 255, 0.4); } &:not(:last-child) { margin-right: 20px; } } .button:hover, .button:focus { background: #fff; color: variables.$primary-color; outline: none; } ================================================ FILE: docs/scss/_documentation.scss ================================================ @use "variables"; $added-color: #5ecb0e; $deprecated-color: #ff9a4d; $sidebar-width: 22%; $beer-height: 70px; .documentation-body { background: variables.$grey; display: flex; .main { padding: 0 60px; height: 100vh; width: 100% - $sidebar-width; overflow: auto; } .main-demos { padding: 0; overflow: hidden; iframe { width: 100%; height: 100vh; background: variables.$grey; } } .main-demo-inner { width: 100%; } .section-title { padding: 50px 0 0; } h3 { display: inline-block; font-weight: bold; font-size: 26px; } h4 { padding: 20px 0 0; } p, dd, ul { & > code, *:not(pre) > code { color: #616870; background: #dfe5eb; padding: 0 5px; border-radius: 4px; } a code { color: inherit; } } code.type { padding: 0 3px; color: variables.$primary-color-dark; &::before { content: "<"; } &::after { content: ">"; } } .button.button-demo { border-color: rgba(variables.$secondary-color, 0.6); color: variables.$secondary-color; width: auto; padding: 10px 30px; &:hover, &:focus { text-decoration: none; border-color: variables.$secondary-color; background: variables.$secondary-color; color: variables.$white; } } ::-webkit-scrollbar { width: 17px; } ::-webkit-scrollbar-track { background: variables.$grey; } ::-webkit-scrollbar-thumb { background: #cfd7de; border: 2px solid variables.$grey; &:hover { background: #616870; } } } .header-documentation { margin-top: 50px; .documentation-logo-link { display: block; margin: 0 auto; width: 80%; max-width: 350px; .documentation-logo { width: 100%; } } .documentation-title { text-align: center; font-family: variables.$panton; font-weight: 100; line-height: 1; @media (max-width: 1290px) { font-size: 2.5vw; } } .documentation-menu { text-align: center; margin-top: 40px; background: variables.$secondary-color; a { display: inline-block; padding: 20px 5px; &:hover, &:focus { text-decoration: underline; } } @media (max-width: 1550px) { padding: 5px 7%; a { width: 48%; padding: 10px 0; } .documentation-menu-dot { display: none; } } } } .sidebar { position: relative; top: 0; left: 0; height: 100vh; color: variables.$white; background: variables.$primary-color linear-gradient(to bottom left, variables.$primary-color, variables.$primary-color-light); width: $sidebar-width; ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: variables.$primary-color-light; border: 1px solid variables.$primary-color-light; border-right: none; &:hover { background: variables.$white; } } &::after { content: ""; display: block; position: absolute; left: 0; bottom: $beer-height; width: calc(100% - 17px); height: 100px; background: linear-gradient(to bottom, rgba(variables.$primary-color-light, 0), variables.$primary-color-light); pointer-events: none; } .sidebar-inner { overflow: auto; overflow-y: scroll; overflow-x: hidden; height: calc(100vh - #{$beer-height}); } ul, li { padding: 0; margin: 0; list-style: none; } a { text-decoration: none; color: variables.$white; transition: color variables.$transition-duration, text-indent variables.$transition-duration; } .documentation-summary { position: relative; font-size: 18px; margin-bottom: 100px; > ul { max-width: 340px; margin: 0 auto; padding: 0 30px; a, .documentation-summary-title { display: block; height: 30px; line-height: 30px; text-overflow: ellipsis; width: 100%; white-space: nowrap; overflow: hidden; } > li { margin-top: 30px; &:first-child { margin-top: 50px; } > a, .documentation-summary-title { font-weight: 600; text-transform: uppercase; margin-bottom: 5px; } > a { &:hover, &:focus { color: variables.$primary-color-dark; } } ul li a { &:hover, &:focus { color: variables.$primary-color-dark; text-indent: 10px; } } } } } .documentation-sidebar-beer { position: fixed; width: $sidebar-width; bottom: 0; left: 0; background: variables.$white; height: $beer-height; a { position: relative; display: block; height: $beer-height; width: 100%; text-align: left; padding: 10px 17px 0 0; background: none; border: none; border-top: 1px solid #e9eef3; color: #9ca4ac; &:focus { outline: none; } .beer-icon { display: none; } @media (min-width: 1700px) { .beer-icon { position: absolute; display: block; width: calc((100% - 236px) / 2); height: 50px; margin: 0 auto; } } .beer-label { position: relative; display: block; max-width: 236px; margin: 0 auto; } @media (max-width: 1290px) { .beer-label { font-size: 14px; margin: 0; padding: 7px 0 0 15px; width: 200px; } } } } } .added-feature, .deprecated-feature { display: inline-block; padding: 3px 13px; margin: 0; color: variables.$white; font-size: 14px; border-radius: 50px; transform: translateX(10px) translateY(-4px); } .added-feature { background: $added-color; } .deprecated-feature { background: $deprecated-color; } .deprecated-info { padding: 7px 15px; border-radius: 4px; background: #f2dfc1; border: 1px solid $deprecated-color; a { color: $deprecated-color; } } .version-tag { display: inline-block; padding: 2px 6px; background: $added-color; color: #FFF; font-size: 12px; font-style: normal; border-radius: 20px; } .note .version-tag { transform: translateY(-1px); } .feature { position: relative; padding-bottom: 60px; &::after { content: " "; display: block; position: absolute; bottom: 0; left: 50%; width: 40%; height: 0; border-bottom: 1px solid variables.$primary-color-light; transform: translateX(-50%); } h3, h4 { display: inline-block; position: relative; } h3 { padding-top: 50px; margin-left: -15px; padding-left: 15px; } h4 { font-size: 20px; font-weight: bold; } h3 + h4, h3 + a + h4 { display: block; } .title-link { display: block; position: absolute; left: -45px; height: 30px; width: 30px; opacity: 0; text-align: center; text-decoration: none; background: variables.$primary-color; border-radius: 50%; transform: translateY(-36px); transition: opacity variables.$transition-duration, background variables.$transition-duration, color variables.$transition-duration; &:hover, &:focus { background: variables.$white; text-decoration: none; svg { fill: variables.$primary-color; } } svg { fill: variables.$white; width: 70%; height: 100%; } } h4 + .title-link { transform: translateY(-30px); } &:hover { .title-link { opacity: 1; } } .trumbowyg-box, .trumbowyg-editor { margin: 24px auto; } } .sample-data { background: variables.$white; padding: 25px; h4 { padding: 0; } input { width: 100%; border: none; padding: 5px 7px; color: #616870; background: #dfe5eb; &:not(:last-child) { margin-bottom: 10px; } } } dl { dt { display: inline-block; color: #1b2126; background: #dfe5eb; padding: 0 4px 0 6px; border-radius: 4px; } dd { padding: 5px 0 15px; margin-left: 15px; ul { margin: 0.5em 0; } } } .tree { list-style: none; background: white; padding: 1em 1.5em; border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.02); ul { position: relative; list-style: none; margin: 0; padding-left: 8px; overflow: hidden; } li { padding-top: 8px; } & > li { padding-top: 0; } ul li { position: relative; padding-left: 16px; &::before, &::after { content: ''; display: block; position: absolute; background: variables.$primary-color; } &::before { top: 20px; left: 0; width: 12px; height: 1px; } &::after { left: 0; bottom: calc(100% - 20px); width: 1px; height: 500px; } } } ================================================ FILE: docs/scss/_donate.scss ================================================ @use "variables"; .donate-container { position: relative; max-width: 800px; margin: 0 auto; .donate-description { width: 100%; padding: 30px 50px 50px; background: variables.$grey; border-top-left-radius: variables.$radius; border-top-right-radius: variables.$radius; } .donate-footer { text-align: center; padding: 50px 0; background: variables.$primary-color linear-gradient(to bottom left, variables.$primary-color, variables.$primary-color-light); border-bottom-left-radius: variables.$radius; border-bottom-right-radius: variables.$radius; } .donate-beer { position: absolute; left: -190px; bottom: -30px; width: 250px; } .button { color: variables.$white; text-decoration: none; &:hover, &:focus { text-decoration: none; background: variables.$white; color: variables.$primary-color; } } } .sponsors-container { margin: 150px 0 0; text-align: center; a { padding: 0 20px; } } ================================================ FILE: docs/scss/_font.scss ================================================ // Panton Light @font-face { font-family: 'Panton'; src: url('../fonts/panton.woff2') format('woff2'); font-weight: 300; font-style: normal; font-display: swap; } // Open Sans @font-face { font-family: 'Open Sans'; src: url('../fonts/open-sans-light.woff2') format('woff2'); font-weight: 300; font-style: normal; font-display: swap; } @font-face { font-family: 'Open Sans'; src: url('../fonts/open-sans-regular.woff2') format('woff2'); font-weight: normal; font-style: normal; font-display: swap; } @font-face { font-family: 'Open Sans'; src: url('../fonts/open-sans-semibold.woff2') format('woff2'); font-weight: 600; font-style: normal; font-display: swap; } // JetBrains Mono @font-face { font-family: 'JetBrains Mono'; src: url('../fonts/jetbrains-mono-regular.woff2') format('woff2'); font-weight: normal; font-style: normal; font-display: swap; } ================================================ FILE: docs/scss/_footer.scss ================================================ @use "variables"; .footer { margin-top: 40px; text-align: center; color: variables.$white; background: linear-gradient(to bottom left, variables.$primary-color, variables.$primary-color-light); clear: both; .footer-text, .footer-link { display: inline-block; padding: 40px; } .footer-text { padding-bottom: 0; .hearts { font-size: 18px; color: variables.$primary-color-dark; } } .footer-link { text-decoration: none; color: variables.$white; transition: color 0.15s; &:hover, &:focus { color: variables.$primary-color-dark; } } } ================================================ FILE: docs/scss/_get-started.scss ================================================ @use "variables"; .installation-first-step { height: 400px; color: variables.$text-color; text-align: center; padding-top: 10px; margin: 20px 0 50px; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 8px; code { display: block; } .installation-download, .installation-package-managers { width: 48%; float: left; } .installation-col-title { font-size: 22px; padding: 30px 0; color: variables.$text-color; } .button { display: block; color: variables.$text-color; border-color: rgba(0, 0, 0, 0.07); background-color: transparent; box-shadow: 0 0 50px rgba(0, 0, 0, 0.03); transition: background-color 150ms, box-shadow 150ms, color 150ms; &:hover { text-decoration: none; color: variables.$white; border-color: transparent; background-color: variables.$primary-color; box-shadow: 0 0 50px rgba(variables.$primary-color, 0.4); } } .installation-or { position: relative; font-weight: bold; text-transform: uppercase; width: 4%; float: left; font-size: 18px; margin: 140px 0 20px; border-radius: 50px; z-index: 0; } .installation-or::before { content: ""; display: block; position: absolute; top: 50%; left: 50%; z-index: -1; width: 100%; padding-top: 100%; min-width: 45px; min-height: 45px; background: variables.$white; border-radius: 100%; transform: translate(-50%, -50%); } .installation-package-managers { font-size: 18px; code { padding-top: 5px; line-height: 1.6; & + code { padding-top: 0; } } } .installation-cdn { clear: both; width: 100%; code { } } } ================================================ FILE: docs/scss/_header.scss ================================================ @use "variables"; .header-landing { position: relative; background: variables.$primary-color linear-gradient(to bottom left, variables.$primary-color, variables.$primary-color-light); text-align: center; color: variables.$white; padding-bottom: 200px; } .header-nav { float: right; width: 100%; margin: 0; padding: 25px 40px 0; font-weight: 400; li { list-style: none; float: right; margin-right: 30px; &:first-child { float: left; } &:nth-child(2) { margin-right: 0; } a { display: block; text-decoration: none; color: variables.$primary-color-dark; font-size: 16px; padding: 10px 0; transition: color variables.$transition-duration; &:hover { color: variables.$white; } &.view-on-github { transform: translateY(-11px); svg { width: 25px; height: 25px; fill: currentColor; margin-right: 10px; vertical-align: baseline; transform: translateY(5px); } .star { font-size: 18px; } } } } } .header-logo-container { margin: 0 auto; padding-top: calc(80px + 4%); text-align: center; .header-logo-h1 { position: relative; margin: 0 auto; width: 1000px; max-width: 100%; } .header-logo { margin-right: -3%; width: 1000px; max-width: 80%; } } .header-subtitle { font-size: 28px; padding: 0 20px; } .header-description { font-size: 18px; line-height: 1.6; padding: 0 20px; font-weight: 300; } .header-buttons { margin: 50px 0; } .header-install { font-size: 18px; line-height: 1.6; font-weight: 300; } #demonstration { padding-bottom: 0; margin-bottom: 80px; .trumbowyg-editor { //height: 300px !important; } .trumbowyg-editor, .trumbowyg-textarea { padding: 25px; } } #demonstration .trumbowyg:not(.trumbowyg-fullscreen), #trumbowyg-demo:not(.trumbowyg-textarea) { margin: -150px auto 0; width: 100%; height: 340px; max-width: 900px; background: variables.$white; box-shadow: 0 0 27px rgba(0, 0, 0, 0.03); resize: none; } #demonstration .trumbowyg { font-size: 16px; line-height: 2; p { margin: 0 0 32px; } button { font-weight: 400; } } #trumbowyg-demo:not(.trumbowyg-textarea) { color: transparent; overflow: hidden; border: 1px solid #dbdfe0; } .demo-switcher { position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%); z-index: 10; margin: 0 auto; width: 250px; height: 40px; border-radius: 50px; border: 1px solid #dbdfe0; background: variables.$white; .button { display: block; float: left; padding: 10px; margin: 0; border: none; width: 50%; background: none; color: variables.$text-color; font-weight: 300; transition: color variables.$transition-duration, text-indent variables.$transition-duration; &:first-child { text-indent: 5px; } &:last-child { text-indent: -5px; } &.current { text-indent: 0; color: variables.$white; } } &::after { content: ""; display: block; width: 50%; height: 100%; border-radius: 50px; background: variables.$primary-color; transition: margin-left variables.$transition-duration; } &.current-plugins { &::after { margin-left: 50%; } } } .header-logo-animation { position: absolute; overflow: hidden; top: -30px; left: 91.5%; width: 150px; height: 150px; svg { position: absolute; top: 0; left: 0; fill: variables.$white; height: 25px; width: 25px; } .header-logo-animation-strong { animation: headerLogoStrong 1s linear infinite; animation-delay: -0.85s; } .header-logo-animation-p { animation: headerLogoP 1s linear infinite; animation-delay: -0.2s; } .header-logo-animation-link { animation: headerLogoLink 1s linear infinite; animation-delay: -0.4s; } .header-logo-animation-blockquote { animation: headerLogoBlockquote 1s linear infinite; animation-delay: -0.6s; } .header-logo-animation-view-html { animation: headerLogoViewHtml 1s linear infinite; animation-delay: -0.3s; } } @keyframes headerLogoStrong { 0% { opacity: 1; transform: translateX(-30px) translateY(70px); } 20% { transform: translateX(15px) translateY(80px); } 30% { transform: translateX(35px) translateY(75px); } 40% { transform: translateX(40px) translateY(60px); } 50% { opacity: 1; transform: translateX(35px) translateY(40px); } 100% { opacity: 0; transform: translateX(10px) translateY(0) scale(0.5); } } @keyframes headerLogoP { 0% { opacity: 1; transform: translateX(-30px) translateY(60px); } 20% { transform: translateX(10px) translateY(60px); } 30% { transform: translateX(20px) translateY(60px); } 40% { transform: translateX(25px) translateY(55px); } 50% { opacity: 1; transform: translateX(32px) translateY(40px); } 90%, 100% { opacity: 0; transform: translateX(35px) translateY(20px) scale(0.5); } } @keyframes headerLogoLink { 0% { opacity: 1; transform: translateX(-30px) translateY(90px); } 20% { transform: translateX(15px) translateY(100px); } 30% { transform: translateX(25px) translateY(95px); } 40% { transform: translateX(30px) translateY(80px); } 50% { opacity: 1; transform: translateX(30px) translateY(70px); } 90%, 100% { opacity: 0; transform: translateX(10px) translateY(0) scale(0.5); } } @keyframes headerLogoBlockquote { 0% { opacity: 1; transform: translateX(-30px) translateY(55px); } 30% { transform: translateX(5px) translateY(50px); } 40% { transform: translateX(10px) translateY(45px); } 50% { opacity: 1; transform: translateX(13px) translateY(40px); } 100% { opacity: 0; transform: translateX(10px) translateY(10px) scale(0.5); } } @keyframes headerLogoViewHtml { 0% { opacity: 1; transform: translateX(-30px) translateY(90px); } 40% { transform: translateX(30px) translateY(105px); } 50% { transform: translateX(40px) translateY(100px); } 60% { opacity: 1; transform: translateX(50px) translateY(90px); } 100% { opacity: 0; transform: translateX(70px) translateY(70px) scale(0.5); } } ================================================ FILE: docs/scss/_highlightjs-github.scss ================================================ /*! Theme: GitHub Description: Light theme as seen on github.com Author: github.com Maintainer: @Hirse Updated: 2021-05-15 Outdated base version: https://github.com/primer/github-syntax-light Current colors taken from GitHub's CSS */ pre code.hljs { display: block; overflow-x: auto; padding: 0 1.5em; border-radius: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.02); } code.hljs { padding: 3px 5px; } .hljs { color: #24292e; background: #fff } .hljs-doctag, .hljs-keyword, .hljs-meta .hljs-keyword, .hljs-template-tag, .hljs-template-variable, .hljs-type, .hljs-variable.language_ { color: #d73a49 } .hljs-title, .hljs-title.class_, .hljs-title.class_.inherited__, .hljs-title.function_ { color: #6f42c1 } .hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number, .hljs-operator, .hljs-selector-attr, .hljs-selector-class, .hljs-selector-id, .hljs-variable { color: #005cc5 } .hljs-meta .hljs-string, .hljs-regexp, .hljs-string { color: #032f62 } .hljs-built_in, .hljs-symbol { color: #e36209 } .hljs-code, .hljs-comment, .hljs-formula { color: #6a737d } .hljs-comment { font-style: italic; } .hljs-name, .hljs-quote, .hljs-selector-pseudo, .hljs-selector-tag { color: #22863a } .hljs-subst { color: #24292e } .hljs-section { color: #005cc5; font-weight: 700 } .hljs-bullet { color: #735c0f } .hljs-emphasis { color: #24292e; font-style: italic } .hljs-strong { color: #24292e; font-weight: 700 } .hljs-addition { color: #22863a; background-color: #f0fff4 } .hljs-deletion { color: #b31d28; background-color: #ffeef0 } ================================================ FILE: docs/scss/_introduction.scss ================================================ @use "variables"; .section-introduction { margin-top: 50px; padding-bottom: 70px; .introduction-section { height: 300px; } .introduction-section-col { position: relative; float: left; width: 30%; margin-right: 5%; &:last-child { margin-right: 0; } } .introduction-section-col-title { font-size: 28px; font-weight: 300; margin-bottom: 0; padding-top: 50px; img { height: 40px; margin-right: 8px; transform: translateY(-3px); &.illu-lightweight { transform: translateY(0px); } } } .introduction-section-col-description { font-size: 18px; line-height: 28px; margin-top: 10px; } } ================================================ FILE: docs/scss/_languages.scss ================================================ @use "variables"; .languages { p { text-align: center; margin: 0 0 5px; font-size: 18px; color: variables.$white; a { color: variables.$primary-color-dark; } } .languages-columns { display: flex; } .col-globe { flex: 1.07; margin-top: 30px; .globe { width: 90%; transform: translateX(-40px); } } .col-list { flex: 0.93; padding-top: 80px; line-height: 1.6; font-weight: 400; .continent-name { color: variables.$white; font-size: 22px; font-weight: 400; background: none; border: none; padding: 0; margin: 0; &:focus { outline: none; } &::after { content: ""; display: inline-block; height: 0; width: 0; border: 6px solid transparent; border-left-color: variables.$white; transform: translateX(5px) translateY(-1px); } } li[style] { .continent-name { &::after { border-left-color: transparent; border-top-color: variables.$white; transform: translateX(2px) translateY(2px); } } } .lang-code { display: inline-block; min-width: 30px; padding-right: 8px; opacity: 0.6; transition: opacity variables.$transition-duration; } .lang-name { transition: padding-left variables.$transition-duration; } a { color: variables.$primary-color-dark; transition: color variables.$transition-duration; &:hover { color: variables.$white; text-decoration: none; .lang-code { opacity: 1; } .lang-name { padding-left: 8px; } } } ul, li { margin: 0; padding: 0; list-style: none; } > ul { padding-right: 50px; > li { overflow: hidden; transition: height variables.$transition-duration linear; &[data-height] { height: 30px; } ul { padding: 15px 0; columns: 2; line-height: 1.8; } } } } } ================================================ FILE: docs/scss/_normalize.scss ================================================ /*! normalize.css v1.1.3 | MIT License | git.io/normalize */ /* ========================================================================== HTML5 display definitions ========================================================================== */ /** * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3. */ article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } /** * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. */ audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } /** * Prevent modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /** * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. * Known issue: no IE 6 support. */ [hidden] { display: none; } /* ========================================================================== Base ========================================================================== */ /** * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using * `em` units. * 2. Prevent iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-size: 100%; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } /** * Address `font-family` inconsistency between `textarea` and other form * elements. */ html, button, input, select, textarea { font-family: sans-serif; } /** * Address margins handled incorrectly in IE 6/7. */ body { margin: 0; } /* ========================================================================== Links ========================================================================== */ /** * Address `outline` inconsistency between Chrome and other browsers. */ a:focus { outline: thin dotted; } /** * Improve readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* ========================================================================== Typography ========================================================================== */ /** * Address font sizes and margins set differently in IE 6/7. * Address font sizes within `section` and `article` in Firefox 4+, Safari 5, * and Chrome. */ h1 { font-size: 2em; margin: 0.67em 0; } h2 { font-size: 1.5em; margin: 0.83em 0; } h3 { font-size: 1.17em; margin: 1em 0; } h4 { font-size: 1em; margin: 1.33em 0; } h5 { font-size: 0.83em; margin: 1.67em 0; } h6 { font-size: 0.67em; margin: 2.33em 0; } /** * Address styling not present in IE 7/8/9, Safari 5, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /** * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. */ b, strong { font-weight: bold; } blockquote { margin: 1em 40px; } /** * Address styling not present in Safari 5 and Chrome. */ dfn { font-style: italic; } /** * Address differences between Firefox and other browsers. * Known issue: no IE 6/7 normalization. */ hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } /** * Address styling not present in IE 6/7/8/9. */ mark { background: #ff0; color: #000; } /** * Address margins set differently in IE 6/7. */ p, pre { margin: 1em 0; } /** * Correct font family set oddly in IE 6, Safari 4/5, and Chrome. */ code, kbd, pre, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; } /** * Improve readability of pre-formatted text in all browsers. */ pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } /** * Address CSS quotes not supported in IE 6/7. */ q { quotes: none; } /** * Address `quotes` property not supported in Safari 4. */ q:before, q:after { content: ''; content: none; } /** * Address inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /** * Prevent `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* ========================================================================== Lists ========================================================================== */ /** * Address margins set differently in IE 6/7. */ dl, menu, ol, ul { margin: 1em 0; } dd { margin: 0 0 0 40px; } /** * Address paddings set differently in IE 6/7. */ menu, ol, ul { padding: 0 0 0 40px; } /** * Correct list images handled incorrectly in IE 7. */ nav ul, nav ol { list-style: none; list-style-image: none; } /* ========================================================================== Embedded content ========================================================================== */ /** * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. * 2. Improve image quality when scaled in IE 7. */ img { border: 0; /* 1 */ -ms-interpolation-mode: bicubic; /* 2 */ } /** * Correct overflow displayed oddly in IE 9. */ svg:not(:root) { overflow: hidden; } /* ========================================================================== Figures ========================================================================== */ /** * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. */ figure { margin: 0; } /* ========================================================================== Forms ========================================================================== */ /** * Correct margin displayed oddly in IE 6/7. */ form { margin: 0; } /** * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /** * 1. Correct color not being inherited in IE 6/7/8/9. * 2. Correct text not wrapping in Firefox 3. * 3. Correct alignment displayed oddly in IE 6/7. */ legend { border: 0; /* 1 */ padding: 0; white-space: normal; /* 2 */ *margin-left: -7px; /* 3 */ } /** * 1. Correct font size not being inherited in all browsers. * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, * and Chrome. * 3. Improve appearance and consistency in all browsers. */ button, input, select, textarea { font-size: 100%; /* 1 */ margin: 0; /* 2 */ vertical-align: baseline; /* 3 */ *vertical-align: middle; /* 3 */ } /** * Address Firefox 3+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ button, input { line-height: normal; } /** * Address inconsistent `text-transform` inheritance for `button` and `select`. * All other form control elements do not inherit `text-transform` values. * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. * Correct `select` style inheritance in Firefox 4+ and Opera. */ button, select { text-transform: none; } /** * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Correct inability to style clickable `input` types in iOS. * 3. Improve usability and consistency of cursor style between image-type * `input` and others. * 4. Remove inner spacing in IE 7 without affecting normal text inputs. * Known issue: inner spacing remains in IE 6. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ *overflow: visible; /* 4 */ } /** * Re-set default cursor for disabled elements. */ button[disabled], html input[disabled] { cursor: default; } /** * 1. Address box sizing set to content-box in IE 8/9. * 2. Remove excess padding in IE 8/9. * 3. Remove excess padding in IE 7. * Known issue: excess padding remains in IE 6. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ *height: 13px; /* 3 */ *width: 13px; /* 3 */ } /** * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /** * Remove inner padding and search cancel button in Safari 5 and Chrome * on OS X. */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /** * Remove inner padding and border in Firefox 3+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /** * 1. Remove default vertical scrollbar in IE 6/7/8/9. * 2. Improve readability and alignment in all browsers. */ textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ } /* ========================================================================== Tables ========================================================================== */ /** * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } ================================================ FILE: docs/scss/_plugins-packages.scss ================================================ @use "variables"; .plugins-packages { .wrapper { display: flex; } .col-plugins { flex: 1.07; } .col-packages { flex: 0.93; } .section-title { text-align: left; transform: translateX(-5px); } p { padding: 0 100px 0 0; line-height: 1.6; } ul, li { margin: 0; padding: 0; list-style: none; } ul { columns: 2; margin: 50px 0 0; max-width: 500px; } li { a { position: relative; display: inline-block; overflow: visible; color: variables.$text-color; transition: color variables.$transition-duration, transform variables.$transition-duration; line-height: 1.8; &:hover { color: variables.$primary-color; text-decoration: none; } img, svg { display: inline-block; color: variables.$primary-color; fill: variables.$primary-color; width: 20px; height: 20px; margin-right: 8px; vertical-align: sub; } } } } ================================================ FILE: docs/scss/_section.scss ================================================ @use "variables"; .section { position: relative; padding-bottom: 100px; &-primary { color: variables.$primary-color-dark; background: linear-gradient(to bottom left, variables.$primary-color, variables.$primary-color-light); .section-title { color: variables.$white; } a { color: variables.$white; } } &-secondary { background: variables.$grey; } h4 { font-weight: 400; color: variables.$text-color; font-size: 28px; padding-top: 80px; } } .section-title { text-align: center; font-family: variables.$panton; font-size: 100px; color: variables.$primary-color; padding-top: 100px; padding-bottom: 30px; } .section-subtitle { margin-top: -22px; font-size: 23px; color: variables.$text-color; font-weight: 400; } ================================================ FILE: docs/scss/_variables.scss ================================================ $primary-color: #ff974a; $primary-color-light: #ffb864; $primary-color-dark: #b65207; $secondary-color: #f48d40; $text-color: #392813; $grey: #f4f7fa; $white: #fff; $panton: "Panton", sans-serif; $font: "Open Sans", sans-serif; $radius: 6px; $transition-duration: 150ms; ================================================ FILE: docs/scss/main.scss ================================================ @use "normalize"; @use "variables"; @use "font"; @use "base"; @use "buttons"; @use "header"; @use "section"; @use "introduction"; @use "get-started"; @use "languages"; @use "plugins-packages"; @use "donate"; @use "footer"; @use "highlightjs-github"; @use "documentation"; ================================================ FILE: gulpfile.mjs ================================================ // jshint node:true 'use strict'; import fs from 'fs'; import gulp from 'gulp'; import gulpAutoprefixer from 'gulp-autoprefixer'; import gulpConcat from 'gulp-concat'; import gulpHeader from 'gulp-header'; import gulpJsHint from 'gulp-jshint'; import gulpLivereload from 'gulp-livereload'; import gulpCleanCss from 'gulp-clean-css'; import gulpNewer from 'gulp-newer'; import gulpRename from 'gulp-rename'; import gulpSassPlugin from 'gulp-sass'; import gulpSize from 'gulp-size'; import gulpSourcemaps from 'gulp-sourcemaps'; import gulpSvgMin from 'gulp-svgmin'; import gulpSvgStore from 'gulp-svgstore'; import gulpTerser from 'gulp-terser'; import {deleteAsync} from 'del'; import * as sass from 'sass'; const gulpSass = () => gulpSassPlugin(sass)(); const paths = { langs: ['src/langs/**.js', '!src/langs/en.js'], icons: ['src/ui/icons/**.svg', 'plugins/*/ui/icons/**.svg'], scripts: ['src/trumbowyg.js'], styles: ['src/ui/sass/trumbowyg.scss'], pluginsScripts: ['plugins/*/**.js'], pluginsStyles: ['plugins/*/ui/sass/**.scss'] }; const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8')); const banner = [ '/**', ' * <%= pkg.title %> v<%= pkg.version %> - <%= pkg.description %>', ' * <%= description %>', ' * ------------------------', ' * @link <%= pkg.homepage %>', ' * @license <%= pkg.license %>', ' * @author <%= pkg.author.name %>', ' * Twitter : @AlexandreDemode', ' * Website : <%= pkg.author.url.replace("https://", "") %>', ' */', '\n' ].join('\n'); const bannerLight = [ '/** <%= pkg.title %> v<%= pkg.version %> - <%= pkg.description %>', ' - <%= pkg.homepage.replace("https://", "") %>', ' - License <%= pkg.license %>', ' - Author : <%= pkg.author.name %>', ' / <%= pkg.author.url.replace("https://", "") %>', ' */', '\n' ].join(''); const clean = function () { return deleteAsync(['dist/*']); }; const lint = function () { return gulp.src([ ...paths.scripts, ...paths.pluginsScripts, ...paths.langs, ]) .pipe(gulpJsHint()) .pipe(gulpJsHint.reporter('jshint-stylish', {})) .pipe(gulpJsHint.reporter('fail', {})); }; const scripts = function scripts() { return gulp.src(paths.scripts) .pipe(gulpHeader(banner, {pkg: pkg, description: 'Trumbowyg core file'})) .pipe(gulpNewer('dist/trumbowyg.js')) .pipe(gulpConcat('trumbowyg.js', {newLine: '\r\n\r\n'})) .pipe(gulp.dest('dist/')) .pipe(gulpSize({title: 'trumbowyg.js'})) .pipe(gulpRename({suffix: '.min'})) .pipe(gulpTerser({ format: { comments: false } })) .pipe(gulpHeader(bannerLight, {pkg: pkg})) .pipe(gulp.dest('dist/')) .pipe(gulpSize({title: 'trumbowyg.min.js'})); }; const pluginsScripts = function pluginsScripts() { return gulp.src(paths.pluginsScripts) .pipe(gulp.dest('dist/plugins/')) .pipe(gulpRename({suffix: '.min'})) .pipe(gulpTerser({ format: { comments: /trumbowyg\./ } })) .pipe(gulp.dest('dist/plugins/')); }; const langs = function langs() { return gulp.src(paths.langs) .pipe(gulp.dest('dist/langs/')) .pipe(gulpRename({suffix: '.min'})) .pipe(gulpTerser({ format: { comments: 'all' } })) .pipe(gulp.dest('dist/langs/')); }; const icons = function () { return gulp.src(paths.icons) .pipe(gulpRename({prefix: 'trumbowyg-'})) .pipe(gulpSvgMin()) .pipe(gulpSvgStore({inlineSvg: true})) .pipe(gulp.dest('dist/ui/')); }; const styles = function () { let stylesPipe = gulp.src(paths.styles) .pipe(gulpSass()); if (process.env.ENV !== 'production') { stylesPipe = stylesPipe.pipe(gulpSourcemaps.init()); } stylesPipe = stylesPipe .pipe(gulpAutoprefixer(['last 2 version', '> 1%'], {cascade: true})) .pipe(gulpHeader(banner, {pkg: pkg, description: 'Default stylesheet for Trumbowyg editor'})) .pipe(gulp.dest('dist/ui/')) .pipe(gulpSize({title: 'trumbowyg.css'})) .pipe(gulpRename({suffix: '.min'})) .pipe(gulpCleanCss()) .pipe(gulpHeader(bannerLight, {pkg: pkg})); if (process.env.ENV !== 'production') { stylesPipe = stylesPipe.pipe(gulpSourcemaps.write('.')); } stylesPipe = stylesPipe .pipe(gulp.dest('dist/ui/')) .pipe(gulpSize({title: 'trumbowyg.min.css'})); return stylesPipe; }; const sassDist = gulp.series(styles, function sassDist() { return gulp.src(paths.styles) .pipe(gulpHeader(banner, {pkg: pkg, description: 'Default stylesheet for Trumbowyg editor'})) .pipe(gulp.dest('dist/ui/sass')); }); const pluginsStyles = function () { return gulp.src(paths.pluginsStyles) .pipe(gulpSass()) .pipe(gulpAutoprefixer(['last 2 version', '> 1%'], {cascade: true})) .pipe(gulpHeader(banner, {pkg: pkg, description: 'Trumbowyg plugin stylesheet'})) .pipe(gulpRename(function (path) { path.dirname += '/..'; })) .pipe(gulp.dest('dist/plugins/')) .pipe(gulpRename({suffix: '.min'})) .pipe(gulpCleanCss()) .pipe(gulpHeader(bannerLight, {pkg: pkg})) .pipe(gulp.dest('dist/plugins/')) .pipe(gulpSize({title: 'Plugins styles'})); }; const pluginsSassDist = gulp.series(pluginsStyles, function pluginsSassDist() { return gulp.src(paths.pluginsStyles) .pipe(gulpHeader(banner, {pkg: pkg, description: 'Trumbowyg plugin stylesheet'})) .pipe(gulp.dest('dist/plugins')); }); const watch = function () { gulp.watch([ ...paths.scripts, ...paths.pluginsScripts, ...paths.langs, ], lint); gulp.watch(paths.icons, icons); gulp.watch(paths.scripts, scripts); gulp.watch(paths.langs, langs); gulp.watch(paths.pluginsScripts, pluginsScripts); gulp.watch(paths.pluginsStyles, pluginsStyles); gulp.watch(paths.styles, styles); gulp.watch(['dist/**', 'dist/*/**'], function (file) { gulpLivereload.changed(file); }); gulpLivereload.listen(); }; const build = gulp.series(clean, gulp.parallel( scripts, pluginsScripts, langs, icons, sassDist, pluginsSassDist )); const buildAndWatch = gulp.series(build, watch); export default buildAndWatch; export { clean, build, lint, watch }; ================================================ FILE: index.html ================================================ Trumbowyg by Alex-D

Trumbowyg examples

Close an editor on clic on "Close" in top right corner.
To reopen an editor, double-click on his text.

This editor is the default build of Trumbowyg.

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

$('#default-editor').trumbowyg();
                

This is a minimalist demo of Trumbowyg.

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

$('#simple-editor').trumbowyg({
    btns: ['strong', 'em', 'del']
});
                

This is a minimalist demo of Trumbowyg with upload image plugin.

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

$('#simple-editor').trumbowyg({
    btns: ['strong', 'em', 'del', 'upload'],
    btnsDef: {
        upload: {
            ico: 'insertImage'
        }
    }
});
                

All buttons of Trumbowyg are here.

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus, aliquam, minima fugiat placeat provident optio nam reiciendis eius beatae quibusdam!

This is a demo of Trumbowyg with a customized button pane & tagClasses

The text is derived from Cicero's De Finibus Bonorum et Malorum (On the Ends of Goods and Evils, or alternatively [About] The Purposes of Good and Evil ). The original passage began: Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit (Translation: "Neither is there anyone who loves grief itself since it is grief and thus wants to obtain it").

Trumbowyg logo

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dignissimos, minima, asperiores libero architecto sequi fugit dolore sunt in officiis facere ut quaerat ullam laudantium delectus aliquam tenetur alias! Ea, nisi, est earum temporibus dolores quas qui repellendus aliquid voluptatibus tempore facilis eligendi omnis reiciendis nihil ullam quo dolorem nam deleniti. Fugit dignissimos dolorum dolore voluptate repudiandae recusandae debitis. Neque, adipisci, maiores magni aliquam molestiae ex natus minus quod tempora nemo debitis eum laboriosam voluptatum ut architecto animi nobis vero quis dolore eaque! Corporis, dolore, illum, autem in eum ea doloribus ut consequuntur modi et ullam adipisci blanditiis corrupti ab voluptate.

It is not known exactly when the text acquired its current standard form; it may have been as late as the 1960s. The passage was discovered by Richard McClintock, a Latin scholar who is the publications director at Hampden-Sydney College in Virginia, by searching for citings of the rarely used word 'consectetur' in classical literature.

/* Add new words for customs btnsDef just below */
$.extend(true, $.trumbowyg.langs, {
fr: {
    align: 'Alignement',
    image: 'Image'
}
});
$('#customized-buttonpane').trumbowyg({
lang: 'fr',
fixedBtnPane: true,
semantic: false,
btnsDef: {
    // Customizables dropdowns
    align: {
        dropdown: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
        ico: 'justifyLeft'
    },
    image: {
        dropdown: ['insertImage', 'upload', 'base64'],
        ico: 'insertImage'
    }
},
btns: ['viewHTML',
    ['formatting'],
    ['align'],
    ['image'],
    ['preformatted'],
    'fullscreen', 'close']
});
                

The dark side of Trumbowyg

================================================ FILE: package.json ================================================ { "name": "trumbowyg", "title": "Trumbowyg", "description": "A lightweight WYSIWYG editor", "version": "2.31.0", "main": "dist/trumbowyg.js", "license": "MIT", "scripts": { "dev": "gulp", "build": "gulp build", "clean": "gulp clean", "lint": "gulp lint", "lint:editorconfig": "editorconfig-checker" }, "devDependencies": { "del": "^8.0.0", "editorconfig-checker": "^6.0.1", "gulp": "^5.0.0", "gulp-autoprefixer": "^9.0.0", "gulp-clean-css": "^4.3.0", "gulp-concat": "^2.6.1", "gulp-header": "^2.0.9", "gulp-jshint": "^2.1.0", "gulp-livereload": "^4.0.2", "gulp-newer": "^1.4.0", "gulp-rename": "^2.0.0", "gulp-sass": "^6.0.0", "gulp-size": "^5.0.0", "gulp-sourcemaps": "^3.0.0", "gulp-svgmin": "^4.1.0", "gulp-svgstore": "^9.0.0", "gulp-terser": "^2.1.0", "jquery": "^4.0.0-beta.2", "jshint": "^2.13.6", "jshint-stylish": "^2.2.1", "sass": "^1.83.4" }, "peerDependencies": { "jquery": ">=1.8" }, "homepage": "https://alex-d.github.io/Trumbowyg/", "author": { "name": "Alexandre Demode (Alex-D)", "email": "contact@alex-d.fr", "url": "https://alex-d.fr" }, "repository": { "type": "git", "url": "https://github.com/Alex-D/Trumbowyg.git" }, "bugs": { "url": "https://github.com/Alex-D/Trumbowyg/issues" }, "keywords": [ "wysiwyg", "editor", "rich text", "contenteditable", "trumbowyg" ], "volta": { "node": "22.13.1" } } ================================================ FILE: plugins/allowtagsfrompaste/trumbowyg.allowtagsfrompaste.js ================================================ /* =========================================================== * trumbowyg.allowTagsFromPaste.js v1.0.2 * It cleans tags from pasted text, whilst allowing several specified tags * http://alex-d.github.com/Trumbowyg * =========================================================== * Author : Fathi Anshory (0x00000F5C) * Twitter : @fscchannl * Notes: * - removeformatPasted must be set to FALSE since it was applied prior to pasteHandlers, or else it will be useless * - It is most advisable to use along with the cleanpaste plugin, or else you'd end up with dirty markup */ (function ($) { 'use strict'; var defaultOptions = { // When empty, all tags are allowed making this plugin useless // If you want to remove all tags, use removeformatPasted core option instead allowedTags: [], // List of tags which can be allowed removableTags: [ 'a', 'abbr', 'address', 'b', 'bdi', 'bdo', 'blockquote', 'br', 'cite', 'code', 'del', 'dfn', 'details', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'mark', 'meter', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'time', 'u', 'var', 'wbr', 'img', 'map', 'area', 'canvas', 'figcaption', 'figure', 'picture', 'audio', 'source', 'track', 'video', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'table', 'caption', 'th', 'tr', 'td', 'thead', 'tbody', 'tfoot', 'col', 'colgroup', 'style', 'div', 'p', 'form', 'input', 'textarea', 'button', 'select', 'optgroup', 'option', 'label', 'fieldset', 'legend', 'datalist', 'keygen', 'output', 'iframe', 'link', 'nav', 'header', 'hgroup', 'footer', 'main', 'section', 'article', 'aside', 'dialog', 'script', 'noscript', 'embed', 'object', 'param' ] }; $.extend(true, $.trumbowyg, { plugins: { allowTagsFromPaste: { init: function (trumbowyg) { if (!trumbowyg.o.plugins.allowTagsFromPaste) { return; } // Force disable remove format pasted trumbowyg.o.removeformatPasted = false; var allowedTags = trumbowyg.o.plugins.allowTagsFromPaste.allowedTags || defaultOptions.allowedTags; var removableTags = trumbowyg.o.plugins.allowTagsFromPaste.removableTags || defaultOptions.removableTags; if (allowedTags.length === 0) { return; } // Get list of tags to remove var tagsToRemove = $(removableTags).not(allowedTags).get(); trumbowyg.pasteHandlers.push(function () { setTimeout(function () { var processNodes = trumbowyg.$ed.html(); $.each(tagsToRemove, function (iterator, tagName) { processNodes = processNodes.replace(new RegExp('<\\/?' + tagName + '(\\s[^>]*)?>', 'gi'), ''); }); trumbowyg.$ed.html(processNodes); }, 0); }); } } } }); })(jQuery); ================================================ FILE: plugins/base64/trumbowyg.base64.js ================================================ /* =========================================================== * trumbowyg.base64.js v1.0 * Base64 plugin for Trumbowyg * http://alex-d.github.com/Trumbowyg * =========================================================== * Author : Cyril Biencourt (lizardK) */ (function ($) { 'use strict'; var isSupported = function () { return typeof FileReader !== 'undefined'; }; var isValidImage = function (type) { return /^data:image\/[a-z]?/i.test(type); }; $.extend(true, $.trumbowyg, { langs: { // jshint camelcase:false en: { base64: 'Image as base64', file: 'File', errFileReaderNotSupported: 'FileReader is not supported by your browser.', errInvalidImage: 'Invalid image file.' }, az: { base64: 'base64 olaraq şəkil', file: 'Fayl', errFileReaderNotSupported: 'FileReader brauzeriniz tərəfindən dəstəklənmir.', errInvalidImage: 'Yanlış şəkil faylı.' }, by: { base64: 'Выява (фармат base64)', file: 'Файл', errFileReaderNotSupported: 'FileReader не падтрымліваецца вашым браўзэрам.', errInvalidImage: 'Несапраўдны файл выявы.' }, cs: { base64: 'Vložit obrázek', file: 'Soubor' }, da: { base64: 'Billede som base64', file: 'Fil', errFileReaderNotSupported: 'FileReader er ikke understøttet af din browser.', errInvalidImage: 'Ugyldig billedfil.' }, de: { base64: 'Bild als base64', file: 'Datei', errFileReaderNotSupported: 'FileReader ist nicht in deinem Browser unterstützt.', errInvalidImage: 'Ungültige Bilddatei.' }, et: { base64: 'Pilt base64 formaadis', file: 'Fail', errFileReaderNotSupported: 'Teie veebilehitseja ei toeta FileReader funktsiooni.', errInvalidImage: 'Vigane pildifail.' }, fr: { base64: 'Image en base64', file: 'Fichier', errFileReaderNotSupported: 'FileReader n\'est pas supporté par votre navigateur.', errInvalidImage: 'Fichier image invalide.' }, hu: { base64: 'Kép beszúrás inline', file: 'Fájl', errFileReaderNotSupported: 'Ez a böngésző nem támogatja a FileReader funkciót.', errInvalidImage: 'Érvénytelen képfájl.' }, ja: { base64: '画像 (Base64形式)', file: 'ファイル', errFileReaderNotSupported: 'あなたのブラウザーはFileReaderをサポートしていません', errInvalidImage: '画像形式が正しくありません' }, ko: { base64: '그림 넣기(base64)', file: '파일', errFileReaderNotSupported: 'FileReader가 현재 브라우저를 지원하지 않습니다.', errInvalidImage: '유효하지 않은 파일' }, nl: { base64: 'Afbeelding inline', file: 'Bestand', errFileReaderNotSupported: 'Uw browser ondersteunt deze functionaliteit niet.', errInvalidImage: 'De gekozen afbeelding is ongeldig.' }, pt_br: { base64: 'Imagem em base64', file: 'Arquivo', errFileReaderNotSupported: 'FileReader não é suportado pelo seu navegador.', errInvalidImage: 'Arquivo de imagem inválido.' }, ru: { base64: 'Изображение как код в base64', file: 'Файл', errFileReaderNotSupported: 'FileReader не поддерживается вашим браузером.', errInvalidImage: 'Недопустимый файл изображения.' }, sl: { base64: 'Slika kot base64', file: 'Datoteka', errFileReaderNotSupported: 'FileReader ni podprt v tem brskalniku.', errInvalidImage: 'Neveljavna datoteka s sliko.' }, tr: { base64: 'Base64 olarak resim', file: 'Dosya', errFileReaderNotSupported: 'FileReader tarayıcınız tarafından desteklenmiyor.', errInvalidImage: 'Geçersiz resim dosyası.' }, zh_cn: { base64: '图片(Base64编码)', file: '文件' }, zh_tw: { base64: '圖片(base64編碼)', file: '檔案', errFileReaderNotSupported: '你的瀏覽器不支援FileReader', errInvalidImage: '不正確的檔案格式' }, }, // jshint camelcase:true plugins: { base64: { shouldInit: isSupported, init: function (trumbowyg) { var btnDef = { isSupported: isSupported, fn: function () { trumbowyg.saveRange(); var file; var $modal = trumbowyg.openModalInsert( // Title trumbowyg.lang.base64, // Fields { file: { type: 'file', required: true, attributes: { accept: 'image/*' } }, alt: { label: 'description', value: trumbowyg.getRangeText() } }, // Callback function (values) { var fReader = new FileReader(); fReader.onloadend = function (e) { if (isValidImage(e.target.result)) { trumbowyg.execCmd('insertImage', fReader.result, false, true); $(['img[src="', fReader.result, '"]:not([alt])'].join(''), trumbowyg.$box).attr('alt', values.alt); trumbowyg.syncCode(); trumbowyg.closeModal(); } else { trumbowyg.addErrorOnModalField( $('input[type=file]', $modal), trumbowyg.lang.errInvalidImage ); } }; fReader.readAsDataURL(file); } ); $('input[type=file]').on('change', function (e) { file = e.target.files[0]; }); } }; trumbowyg.addBtnDef('base64', btnDef); } } } }); })(jQuery); ================================================ FILE: plugins/cleanpaste/trumbowyg.cleanpaste.js ================================================ /* =========================================================== * trumbowyg.cleanpaste.js v1.0 * Font Clean paste plugin for Trumbowyg * http://alex-d.github.com/Trumbowyg * =========================================================== * Authors : Eric Radin * Todd Graham (slackwalker) * * This plugin will perform a "cleaning" on any paste, in particular * it will clean pasted content of microsoft word document tags and classes. */ (function ($) { 'use strict'; function checkValidTags(snippet) { var theString = snippet; // Replace uppercase element names with lowercase theString = theString.replace(/<[^> ]*/g, function (match) { return match.toLowerCase(); }); // Replace uppercase attribute names with lowercase theString = theString.replace(/<[^>]*>/g, function (match) { match = match.replace(/ [^=]+=/g, function (match2) { return match2.toLowerCase(); }); return match; }); // Put quotes around unquoted attributes theString = theString.replace(/<[^>]*>/g, function (match) { match = match.replace(/( [^=]+=)([^"][^ >]*)/g, '$1\"$2\"'); return match; }); return theString; } function cleanIt(html) { // first make sure all tags and attributes are made valid html = checkValidTags(html); // Replace opening bold tags with strong html = html.replace(/)/g, ')/g, ')/g, ')/g, '\s*/g, ''); // strip out   -cgCraft html = html.replace(/ /gi, ' '); // strip out extra spaces -cgCraft html = html.replace(/ <\//gi, ']*>/g, function (match) { match = match.replace(/ ([^=]+)="[^"]*"/g, function (match2, attributeName) { if (['alt', 'href', 'src', 'title'].indexOf(attributeName) !== -1) { return match2; } return ''; }); return match; }); // Final clean out for MS Word crud html = html.replace(/<\?xml[^>]*>/g, ''); html = html.replace(/<[^ >]+:[^>]*>/g, ''); html = html.replace(/<\/[^ >]+:[^>]*>/g, ''); // remove unwanted tags html = html.replace(/<(div|span|style|meta|link).*?>/gi, ''); return html; } // clean editor // this will clean the inserted contents // it does a compare, before and after paste to determine the // pasted contents $.extend(true, $.trumbowyg, { plugins: { cleanPaste: { init: function (trumbowyg) { trumbowyg.pasteHandlers.push(function (pasteEvent) { setTimeout(function () { try { trumbowyg.saveRange(); var clipboardData = (pasteEvent.originalEvent || pasteEvent).clipboardData, pastedData = clipboardData.getData('Text'), node = trumbowyg.doc.getSelection().focusNode, range = trumbowyg.doc.createRange(), cleanedPaste = cleanIt(pastedData.trim()), newNode = $(cleanedPaste)[0] || trumbowyg.doc.createTextNode(cleanedPaste); if (trumbowyg.$ed.html() === '') { // simply append if there is no content in editor trumbowyg.$ed[0].appendChild(newNode); } else { // insert pasted content behind last focused node range.setStartAfter(node); range.setEndAfter(node); trumbowyg.doc.getSelection().removeAllRanges(); trumbowyg.doc.getSelection().addRange(range); trumbowyg.range.insertNode(newNode); } // now set cursor right after pasted content range = trumbowyg.doc.createRange(); range.setStartAfter(newNode); range.setEndAfter(newNode); trumbowyg.doc.getSelection().removeAllRanges(); trumbowyg.doc.getSelection().addRange(range); // prevent defaults pasteEvent.stopPropagation(); pasteEvent.preventDefault(); // save new node as focused node trumbowyg.saveRange(); trumbowyg.syncCode(); trumbowyg.$c.trigger('tbwchange'); } catch (c) { } }, 0); }); } } } }); })(jQuery); ================================================ FILE: plugins/colors/trumbowyg.colors.js ================================================ /* =========================================================== * trumbowyg.colors.js v1.2 * Colors picker plugin for Trumbowyg * http://alex-d.github.com/Trumbowyg * =========================================================== * Author : Alexandre Demode (Alex-D) * Twitter : @AlexandreDemode * Website : alex-d.fr */ (function ($) { 'use strict'; $.extend(true, $.trumbowyg, { langs: { // jshint camelcase:false en: { foreColor: 'Text color', backColor: 'Background color', foreColorRemove: 'Remove text color', backColorRemove: 'Remove background color' }, az: { foreColor: 'Yazı rəngi', backColor: 'Arxa plan rəngi', foreColorRemove: 'Yazı rəngini sil', backColorRemove: 'Arxa plan rəngini sil' }, by: { foreColor: 'Колер тэксту', backColor: 'Колер фону тэксту', foreColorRemove: 'Выдаліць колер тэксту', backColorRemove: 'Выдаліць колер фону тэксту' }, ca: { foreColor: 'Color del text', backColor: 'Color del fons', foreColorRemove: 'Eliminar color del text', backColorRemove: 'Eliminar color del fons' }, cs: { foreColor: 'Barva textu', backColor: 'Barva pozadí' }, da: { foreColor: 'Tekstfarve', backColor: 'Baggrundsfarve' }, de: { foreColor: 'Textfarbe', backColor: 'Hintergrundfarbe', foreColorRemove: 'Textfarbe entfernen', backColorRemove: 'Hintergundfarbe entfernen' }, es: { foreColor: 'Color del texto', backColor: 'Color del fondo', foreColorRemove: 'Eliminar color del texto', backColorRemove: 'Eliminar color del fondo' }, et: { foreColor: 'Teksti värv', backColor: 'Taustavärv', foreColorRemove: 'Eemalda teksti värv', backColorRemove: 'Eemalda taustavärv' }, fr: { foreColor: 'Couleur du texte', backColor: 'Couleur de fond', foreColorRemove: 'Supprimer la couleur du texte', backColorRemove: 'Supprimer la couleur de fond' }, hu: { foreColor: 'Betű szín', backColor: 'Háttér szín', foreColorRemove: 'Betű szín eltávolítása', backColorRemove: 'Háttér szín eltávolítása' }, ja: { foreColor: '文字色', backColor: '背景色' }, ko: { foreColor: '글자색', backColor: '배경색', foreColorRemove: '글자색 지우기', backColorRemove: '배경색 지우기' }, nl: { foreColor: 'Tekstkleur', backColor: 'Achtergrondkleur' }, pt_br: { foreColor: 'Cor de fonte', backColor: 'Cor de fundo' }, ru: { foreColor: 'Цвет текста', backColor: 'Цвет выделения текста', foreColorRemove: 'Очистить цвет текста', backColorRemove: 'Очистить цвет выделения текста' }, sl: { foreColor: 'Barva teksta', backColor: 'Barva ozadja', foreColorRemove: 'Ponastavi barvo teksta', backColorRemove: 'Ponastavi barvo ozadja', }, sk: { foreColor: 'Farba textu', backColor: 'Farba pozadia' }, tr: { foreColor: 'Yazı rengi', backColor: 'Arka plan rengi', foreColorRemove: 'Yazı rengini kaldır', backColorRemove: 'Arka plan rengini kaldır' }, zh_cn: { foreColor: '文字颜色', backColor: '背景颜色' }, zh_tw: { foreColor: '文字顏色', backColor: '背景顏色' }, } }); // jshint camelcase:true function hex(x) { return ('0' + parseInt(x).toString(16)).slice(-2); } function colorToHex(rgb) { if (rgb.search('rgb') === -1) { return rgb.replace('#', ''); } else if (rgb === 'rgba(0, 0, 0, 0)') { return 'transparent'; } else { rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d?(.\d+)))?\)$/); if (rgb == null) { return 'transparent'; // No match, return transparent as unkown color } return hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); } } function colorTagHandler(element, trumbowyg) { var tags = []; if (!element.style) { return tags; } // background color if (element.style.backgroundColor !== '') { var backColor = colorToHex(element.style.backgroundColor); if (trumbowyg.o.plugins.colors.colorList.indexOf(backColor) >= 0) { tags.push('backColor' + backColor); } else { tags.push('backColorFree'); } } // text color var foreColor; if (element.style.color !== '') { foreColor = colorToHex(element.style.color); } else if (element.hasAttribute('color')) { foreColor = colorToHex(element.getAttribute('color')); } if (foreColor) { if (trumbowyg.o.plugins.colors.colorList.indexOf(foreColor) >= 0) { tags.push('foreColor' + foreColor); } else { tags.push('foreColorFree'); } } return tags; } var defaultOptions = { colorList: [ 'ffffff', '000000', 'eeece1', '1f497d', '4f81bd', 'c0504d', '9bbb59', '8064a2', '4bacc6', 'f79646', 'ffff00', 'f2f2f2', '7f7f7f', 'ddd9c3', 'c6d9f0', 'dbe5f1', 'f2dcdb', 'ebf1dd', 'e5e0ec', 'dbeef3', 'fdeada', 'fff2ca', 'd8d8d8', '595959', 'c4bd97', '8db3e2', 'b8cce4', 'e5b9b7', 'd7e3bc', 'ccc1d9', 'b7dde8', 'fbd5b5', 'ffe694', 'bfbfbf', '3f3f3f', '938953', '548dd4', '95b3d7', 'd99694', 'c3d69b', 'b2a2c7', 'b7dde8', 'fac08f', 'f2c314', 'a5a5a5', '262626', '494429', '17365d', '366092', '953734', '76923c', '5f497a', '92cddc', 'e36c09', 'c09100', '7f7f7f', '0c0c0c', '1d1b10', '0f243e', '244061', '632423', '4f6128', '3f3151', '31859b', '974806', '7f6000' ], foreColorList: null, // fallbacks on colorList backColorList: null, // fallbacks on colorList allowCustomForeColor: true, allowCustomBackColor: true, displayAsList: false, }; // Add all colors in two dropdowns $.extend(true, $.trumbowyg, { plugins: { color: { init: function (trumbowyg) { trumbowyg.o.plugins.colors = trumbowyg.o.plugins.colors || defaultOptions; var dropdownClass = trumbowyg.o.plugins.colors.displayAsList ? trumbowyg.o.prefix + 'dropdown--color-list' : ''; var foreColorBtnDef = { dropdown: buildDropdown('foreColor', trumbowyg), dropdownClass: dropdownClass, }, backColorBtnDef = { dropdown: buildDropdown('backColor', trumbowyg), dropdownClass: dropdownClass, }; trumbowyg.addBtnDef('foreColor', foreColorBtnDef); trumbowyg.addBtnDef('backColor', backColorBtnDef); }, tagHandler: colorTagHandler } } }); function buildDropdown(fn, trumbowyg) { var dropdown = [], trumbowygColorOptions = trumbowyg.o.plugins.colors, colorList = trumbowygColorOptions[fn + 'List'] || trumbowygColorOptions.colorList; $.each(colorList, function (i, color) { var btn = fn + color, btnDef = { fn: fn, forceCss: true, hasIcon: false, text: trumbowyg.lang['#' + color] || ('#' + color), param: '#' + color, style: 'background-color: #' + color + ';' }; if (trumbowygColorOptions.displayAsList && fn === 'foreColor') { btnDef.style = 'color: #' + color + ' !important;'; } trumbowyg.addBtnDef(btn, btnDef); dropdown.push(btn); }); // Remove color var removeColorButtonName = fn + 'Remove', removeColorBtnDef = { fn: 'removeFormat', hasIcon: false, param: fn, style: 'background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAG0lEQVQIW2NkQAAfEJMRmwBYhoGBYQtMBYoAADziAp0jtJTgAAAAAElFTkSuQmCC);' }; if (trumbowygColorOptions.displayAsList) { removeColorBtnDef.style = ''; } trumbowyg.addBtnDef(removeColorButtonName, removeColorBtnDef); dropdown.push(removeColorButtonName); // Custom color if (trumbowygColorOptions['allowCustom' + fn.charAt(0).toUpperCase() + fn.substr(1)]) { // add free color btn var freeColorButtonName = fn + 'Free', freeColorBtnDef = { fn: function () { trumbowyg.openModalInsert(trumbowyg.lang[fn], { color: { label: fn, forceCss: true, type: 'color', value: '#FFFFFF' } }, // callback function (values) { trumbowyg.execCmd(fn, values.color); return true; } ); }, hasIcon: false, text: '#', // style adjust for displaying the text style: 'text-indent: 0; line-height: 20px; padding: 0 5px;' }; trumbowyg.addBtnDef(freeColorButtonName, freeColorBtnDef); dropdown.push(freeColorButtonName); } return dropdown; } })(jQuery); ================================================ FILE: plugins/colors/ui/sass/trumbowyg.colors.scss ================================================ .trumbowyg-dropdown-foreColor:not(.trumbowyg-dropdown--color-list), .trumbowyg-dropdown-backColor:not(.trumbowyg-dropdown--color-list) { max-width: 276px; padding: 7px 5px; overflow: initial; button { display: block; position: relative; float: left; text-indent: -9999px; height: 20px; width: 20px; border: 1px solid #333; padding: 0; margin: 2px; &:hover, &:focus { &::after { content: " "; display: block; position: absolute; top: -5px; left: -5px; width: 27px; height: 27px; background: inherit; border: 1px solid #fff; box-shadow: #000 0 0 2px; z-index: 10; } } } } .trumbowyg-dropdown-backColor.trumbowyg-dropdown--color-list { button:not(.trumbowyg-backColorRemove-dropdown-button) { position: relative; color: #fff !important; &:hover, &:focus { &::after { content: " "; display: block; position: absolute; top: 13px; left: 0; width: 0; height: 0; border: 5px solid transparent; border-left-color: #fff; } } } } ================================================ FILE: plugins/emoji/trumbowyg.emoji.js ================================================ /* =========================================================== * trumbowyg.emoji.js v0.1 * Emoji picker plugin for Trumbowyg * http://alex-d.github.com/Trumbowyg * =========================================================== * Author : Nicolas Pion * Twitter : @nicolas_pion */ (function ($) { 'use strict'; var defaultOptions = { emojiList: [ '⁉', '™', 'ℹ', '↔', '↕', '↖', '↗', '↘', '↙', '⌨', '☀', '☁', '☂', '☃', '☄', '☑', '☔', '☕', '☘', '☠', '☢', '☣', '☦', '☸', '☹', '♀', '♂', '♈', '♉', '♐', '♑', '♒', '♓', '♠', '♣', '♥', '♦', '♨', '⚒', '⚓', '⚔', '⚕', '⚖', '⚗', '⚙', '✂', '✅', '✈', '✉', '✒', '✔', '✖', '✡', '✨', '✳', '✴', '❄', '❇', '❓', '❔', '❕', '❗', '❣', '❤', '➕', '➖', '➗', '⤴', '⤵', '〰', '㊗', '㊙', '😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '☺', '😊', '😇', '🙂', '🙃', '😉', '😌', '🥲', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳', '😏', '😒', '😞', '😔', '😟', '😕', '🙁', '😣', '😖', '😫', '😩', '🥺', '😢', '😭', '😤', '😮', '😠', '😡', '🤬', '🤯', '😳', '😶', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🤗', '🤔', '🤭', '🥱', '🤫', '🤥', '😐', '😑', '😬', '🙄', '😯', '😦', '😧', '😲', '😴', '🤤', '😪', '😵', '🤐', '🥴', '🤢', '🤮', '🤧', '😷', '🤒', '🤕', '🤑', '🤠', '🥸', '😈', '👿', '👹', '👺', '🤡', '💩', '👻', '💀', '👽', '👾', '🤖', '🎃', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾', '🤲', '👐', '🙌', '👏', '🤝', '👍', '👎', '👊', '✊', '🤛', '🤜', '🤞', '✌', '🤟', '🤘', '👌', '🤏', '🤌', '👈', '👉', '👆', '👇', '☝', '✋', '🤚', '🖐', '🖖', '👋', '🤙', '💪', '🦾', '🖕', '✍', '🙏', '🦶', '🦵', '🦿', '💄', '💋', '👄', '🦷', '👅', '👂', '🦻', '👃', '👣', '👁', '👀', '🧠', '🫀', '🫁', '🦴', '🗣', '👤', '👥', '🫂', '👶', '👧', '🧒', '👦', '👩', '🧑', '👨', '👱', '🧔', '👵', '🧓', '👴', '👲', '👳', '🧕', '👮', '👷', '💂', '🕵', '👰', '🤵', '👸', '🤴', '🦸', '🦹', '🥷', '🤶', '🎅', '🧙', '🧝', '🧛', '🧟', '🧞', '🧜', '🧚', '👼', '🤰', '🤱', '🙇', '💁', '🙅', '🙆', '🙋', '🧏', '🤦', '🤷', '🙎', '🙍', '💇', '💆', '🧖', '💅', '🤳', '💃', '🕺', '👯', '🕴', '🚶', '🧎', '🏃', '🧍', '👫', '👭', '👬', '💑', '💏', '👪', '🧶', '🧵', '🧥', '🥼', '🦺', '👚', '👕', '👖', '🩲', '🩳', '👔', '👗', '👙', '🩱', '👘', '🥻', '🥿', '👠', '👡', '👢', '👞', '👟', '🥾', '🩴', '🧦', '🧤', '🧣', '🎩', '🧢', '👒', '🎓', '⛑', '🪖', '👑', '💍', '👝', '👛', '👜', '💼', '🎒', '🧳', '👓', '🕶', '🥽', '🌂', '🦱', '🦰', '🦳', '🦲', '🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐨', '🐯', '🦁', '🐮', '🐷', '🐽', '🐸', '🐵', '🙈', '🙉', '🙊', '🐒', '🐔', '🐧', '🐦', '🐤', '🐣', '🐥', '🦆', '🦤', '🦅', '🦉', '🦇', '🐺', '🐗', '🐴', '🦄', '🐝', '🐛', '🦋', '🐌', '🪱', '🐞', '🐜', '🪰', '🦟', '🪳', '🪲', '🦗', '🕷', '🕸', '🦂', '🐢', '🐍', '🦎', '🦖', '🦕', '🐙', '🦑', '🦐', '🦞', '🦀', '🐡', '🐠', '🐟', '🦭', '🐬', '🐳', '🐋', '🦈', '🐊', '🐅', '🐆', '🦓', '🦍', '🦧', '🐘', '🦣', '🦬', '🦛', '🦏', '🐪', '🐫', '🦒', '🦘', '🐃', '🐂', '🐄', '🐎', '🐖', '🐏', '🐑', '🦙', '🐐', '🦌', '🐕', '🐩', '🦮', '🐈', '🐓', '🦃', '🦚', '🦜', '🦢', '🦩', '🕊', '🐇', '🦝', '🦨', '🦡', '🦫', '🦦', '🦥', '🐁', '🐀', '🐿', '🦔', '🐾', '🐉', '🐲', '🌵', '🎄', '🌲', '🌳', '🌴', '🌱', '🌿', '🍀', '🎍', '🎋', '🍃', '🍂', '🍁', '🪶', '🍄', '🐚', '🪨', '🪵', '🌾', '🪴', '💐', '🌷', '🌹', '🥀', '🌺', '🌸', '🌼', '🌻', '🌞', '🌝', '🌛', '🌜', '🌚', '🌕', '🌖', '🌗', '🌘', '🌑', '🌒', '🌓', '🌔', '🌙', '🌎', '🌍', '🌏', '🪐', '💫', '⭐', '🌟', '⚡', '💥', '🔥', '🌪', '🌈', '🌤', '⛅', '🌥', '🌦', '🌧', '⛈', '🌩', '🌨', '⛄', '🌬', '💨', '💧', '💦', '🌊', '🌫', '🍏', '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🫐', '🍓', '🍈', '🍒', '🍑', '🥭', '🍍', '🥥', '🥝', '🍅', '🍆', '🥑', '🫒', '🥦', '🥬', '🫑', '🥒', '🌶', '🌽', '🥕', '🧄', '🧅', '🥔', '🍠', '🥐', '🥯', '🍞', '🥖', '🫓', '🥨', '🧀', '🥚', '🍳', '🧈', '🥞', '🧇', '🥓', '🥩', '🍗', '🍖', '🌭', '🍔', '🍟', '🍕', '🥪', '🥙', '🧆', '🌮', '🌯', '🫔', '🥗', '🥘', '🫕', '🥫', '🍝', '🍜', '🍲', '🍛', '🍣', '🍱', '🥟', '🦪', '🍤', '🍙', '🍚', '🍘', '🍥', '🥠', '🥮', '🍢', '🍡', '🍧', '🍨', '🍦', '🥧', '🧁', '🍰', '🎂', '🍮', '🍭', '🍬', '🍫', '🍿', '🍩', '🍪', '🌰', '🥜', '🍯', '🥛', '🍼', '🍵', '🫖', '🧉', '🧋', '🧃', '🥤', '🍶', '🍺', '🍻', '🥂', '🍷', '🥃', '🍸', '🍹', '🍾', '🧊', '🥄', '🍴', '🍽', '🥣', '🥡', '🥢', '🧂', '⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🪃', '🎱', '🪀', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', '🥅', '⛳', '🪁', '🏹', '🎣', '🤿', '🥊', '🥋', '🎽', '🛹', '🛼', '🛷', '⛸', '🥌', '🎿', '⛷', '🏂', '🪂', '🏋', '🤼', '🤸', '⛹', '🤺', '🤾', '🏌', '🏇', '🧘', '🏄', '🏊', '🤽', '🚣', '🧗', '🚵', '🚴', '🏆', '🥇', '🥈', '🥉', '🏅', '🎖', '🏵', '🎗', '🎫', '🎟', '🎪', '🤹', '🎭', '🩰', '🎨', '🎬', '🎤', '🎧', '🎼', '🎹', '🥁', '🪘', '🎷', '🎺', '🎸', '🪕', '🎻', '🪗', '🎲', '♟', '🎯', '🎳', '🎮', '🎰', '🧩', '🚗', '🚕', '🚙', '🛻', '🚌', '🚎', '🏎', '🚓', '🚑', '🚒', '🚐', '🚚', '🚛', '🚜', '🦯', '🦽', '🦼', '🛴', '🚲', '🛵', '🏍', '🛺', '🚨', '🚔', '🚍', '🚘', '🚖', '🚡', '🚠', '🚟', '🚃', '🚋', '🚞', '🚝', '🚄', '🚅', '🚈', '🚂', '🚆', '🚇', '🚊', '🚉', '🛫', '🛬', '🛩', '💺', '🛰', '🚀', '🛸', '🚁', '🛶', '⛵', '🚤', '🛥', '🛳', '⛴', '🚢', '⛽', '🚧', '🚦', '🚥', '🚏', '🗺', '🗿', '🗽', '🗼', '🏰', '🏯', '🏟', '🎡', '🎢', '🎠', '⛲', '⛱', '🏖', '🏝', '🏜', '🌋', '⛰', '🏔', '🗻', '🏕', '⛺', '🏠', '🏡', '🏘', '🏚', '🛖', '🏗', '🏭', '🏢', '🏬', '🏣', '🏤', '🏥', '🏦', '🏨', '🏪', '🏫', '🏩', '💒', '🏛', '⛪', '🕌', '🕍', '🛕', '🕋', '⛩', '🛤', '🛣', '🗾', '🎑', '🏞', '🌅', '🌄', '🌠', '🎇', '🎆', '🌇', '🌆', '🏙', '🌃', '🌌', '🌉', '🌁', '⌚', '📱', '📲', '💻', '🖥', '🖨', '🖱', '🖲', '🕹', '🗜', '💽', '💾', '💿', '📀', '📼', '📷', '📸', '📹', '🎥', '📽', '🎞', '📞', '☎', '📟', '📠', '📺', '📻', '🎙', '🎚', '🎛', '🧭', '⏱', '⏲', '⏰', '🕰', '⌛', '⏳', '📡', '🔋', '🔌', '💡', '🔦', '🕯', '🪔', '🧯', '🛢', '💸', '💵', '💴', '💶', '💷', '🪙', '💰', '💳', '💎', '🪜', '🧰', '🪛', '🔧', '🔨', '🛠', '⛏', '🔩', '🧱', '⛓', '🪝', '🪢', '🧲', '🔫', '💣', '🧨', '🪓', '🪚', '🔪', '🗡', '🛡', '🚬', '⚰', '🪦', '⚱', '🏺', '🪄', '🔮', '📿', '🧿', '💈', '🔭', '🔬', '🕳', '🪟', '🩹', '🩺', '💊', '💉', '🩸', '🧬', '🦠', '🧫', '🧪', '🌡', '🪤', '🧹', '🧺', '🪡', '🧻', '🚽', '🪠', '🪣', '🚰', '🚿', '🛁', '🛀', '🪥', '🧼', '🪒', '🧽', '🧴', '🛎', '🔑', '🗝', '🚪', '🪑', '🪞', '🛋', '🛏', '🛌', '🧸', '🖼', '🛍', '🛒', '🎁', '🎈', '🎏', '🎀', '🎊', '🎉', '🪅', '🪆', '🎎', '🏮', '🎐', '🧧', '📩', '📨', '📧', '💌', '📥', '📤', '📦', '🏷', '📪', '📫', '📬', '📭', '📮', '📯', '🪧', '📜', '📃', '📄', '📑', '🧾', '📊', '📈', '📉', '🗒', '🗓', '📆', '📅', '🗑', '📇', '🗃', '🗳', '🗄', '📋', '📁', '📂', '🗂', '🗞', '📰', '📓', '📔', '📒', '📕', '📗', '📘', '📙', '📚', '📖', '🔖', '🧷', '🔗', '📎', '🖇', '📐', '📏', '🧮', '📌', '📍', '🖊', '🖋', '🖌', '🖍', '📝', '✏', '🔍', '🔎', '🔏', '🔐', '🔒', '🔓', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤎', '🤍', '💔', '💕', '💞', '💓', '💗', '💖', '💘', '💝', '💟', '☮', '✝', '☪', '🕉', '🔯', '🕎', '☯', '🛐', '⛎', '♊', '♋', '♌', '♍', '♎', '♏', '🆔', '⚛', '🉑', '📴', '📳', '🈶', '🈚', '🈸', '🈺', '🈷', '🆚', '💮', '🉐', '🈴', '🈵', '🈹', '🈲', '🅰', '🅱', '🆎', '🆑', '🅾', '🆘', '❌', '⭕', '🛑', '⛔', '📛', '🚫', '💯', '💢', '🚷', '🚯', '🚳', '🚱', '🔞', '📵', '🚭', '‼', '🔅', '🔆', '〽', '⚠', '🚸', '🔱', '⚜', '🔰', '♻', '🈯', '💹', '❎', '🌐', '💠', 'Ⓜ', '🌀', '💤', '🏧', '🚾', '♿', '🅿', '🈳', '🈂', '🛂', '🛃', '🛄', '🛅', '🛗', '🚹', '🚺', '🚼', '🚻', '🚮', '🎦', '📶', '🈁', '🔣', '🔤', '🔡', '🔠', '🆖', '🆗', '🆙', '🆒', '🆕', '🆓', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '🔟', '🔢', '#', '*', '⏏', '▶', '⏸', '⏯', '⏹', '⏺', '⏭', '⏮', '⏩', '⏪', '⏫', '⏬', '◀', '🔼', '🔽', '➡', '⬅', '⬆', '⬇', '↪', '↩', '🔀', '🔁', '🔂', '🔄', '🔃', '🎵', '🎶', '♾', '💲', '💱', '©', '®', '➰', '➿', '🔚', '🔙', '🔛', '🔝', '🔜', '🔘', '⚪', '⚫', '🔴', '🔵', '🟤', '🟣', '🟢', '🟡', '🟠', '🔺', '🔻', '🔸', '🔹', '🔶', '🔷', '🔳', '🔲', '▪', '▫', '◾', '◽', '◼', '◻', '⬛', '⬜', '🟧', '🟦', '🟥', '🟫', '🟪', '🟩', '🟨', '🔈', '🔇', '🔉', '🔊', '🔔', '🔕', '📣', '📢', '🗨', '💬', '💭', '🗯', '🃏', '🎴', '🀄', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', '🕛', '🕜', '🕝', '🕞', '🕟', '🕠', '🕡', '🕢', '🕣', '🕤', '🕥', '🕦', '🕧', '⚧', '🏳', '🏴', '🏁', '🚩', '🇦', '🇩', '🇧', '🇮', '🇻', '🇰', '🇨', '🇹', '🇭', '🇪', '🇸', '🇬', '🇫', '🇵', '🇯', '🎌', '🇽', '🇱', '🇲', '🇾', '🇳', '🇴', '🇶', '🇷', '🇼', '🇿', '🇺', '🏻', '🏼', '🏽', '🏾', '🏿' ] }; // Add all emoji in a dropdown $.extend(true, $.trumbowyg, { langs: { // jshint camelcase:false en: { emoji: 'Add an emoji' }, az: { emoji: 'Emoji yerləşdir' }, ca: { emoji: 'Afegir una emoticona' }, da: { emoji: 'Tilføj et humørikon' }, de: { emoji: 'Emoticon einfügen' }, es: { emoji: 'Añadir un emoticono' }, et: { emoji: 'Lisa emotikon' }, fr: { emoji: 'Ajouter un emoji' }, hu: { emoji: 'Emoji beszúrás' }, ja: { emoji: '絵文字の挿入' }, ko: { emoji: '이모지 넣기' }, ru: { emoji: 'Вставить emoji' }, sl: { emoji: 'Vstavi emotikon' }, tr: { emoji: 'Emoji ekle' }, zh_cn: { emoji: '添加表情' }, }, // jshint camelcase:true plugins: { emoji: { init: function (trumbowyg) { trumbowyg.o.plugins.emoji = trumbowyg.o.plugins.emoji || defaultOptions; var emojiBtnDef = { dropdown: buildDropdown(trumbowyg) }; trumbowyg.addBtnDef('emoji', emojiBtnDef); } } } }); function buildDropdown(trumbowyg) { var dropdown = []; $.each(trumbowyg.o.plugins.emoji.emojiList, function (i, emoji) { if (Array.isArray(emoji)) { // Custom emoji behaviour var emojiCode = emoji[0], emojiUrl = emoji[1], emojiHtml = '' + emojiCode + '', customEmojiBtnName = 'emoji-' + emojiCode.replace(/:/g, ''), customEmojiBtnDef = { hasIcon: false, text: emojiHtml, fn: function () { trumbowyg.execCmd('insertImage', emojiUrl, false, true); return true; } }; trumbowyg.addBtnDef(customEmojiBtnName, customEmojiBtnDef); dropdown.push(customEmojiBtnName); } else { // Default behaviour var btn = emoji.replace(/:/g, ''), defaultEmojiBtnName = 'emoji-' + btn, defaultEmojiBtnDef = { text: emoji, fn: function () { var encodedEmoji = String.fromCodePoint(emoji.replace('&#', '0')); trumbowyg.execCmd('insertText', encodedEmoji); return true; } }; trumbowyg.addBtnDef(defaultEmojiBtnName, defaultEmojiBtnDef); dropdown.push(defaultEmojiBtnName); } }); return dropdown; } })(jQuery); ================================================ FILE: plugins/emoji/ui/sass/trumbowyg.emoji.scss ================================================ .trumbowyg-dropdown-emoji { width: 265px; padding: 7px 0 7px 5px; } .trumbowyg-dropdown-emoji svg { display: none !important; } .trumbowyg-dropdown-emoji button { display: block; position: relative; float: left; height: 26px; width: 26px; padding: 0; margin: 2px; line-height: 24px; text-align: center; &:hover, &:focus { &::after { display: block; position: absolute; top: -5px; left: -5px; height: 27px; width: 27px; background: inherit; box-shadow: #000 0 0 2px; z-index: 10; background-color: transparent; } } } .trumbowyg .emoji { width: 22px; height: 22px; display: inline-block; } ================================================ FILE: plugins/fontfamily/trumbowyg.fontfamily.js ================================================ (function ($) { 'use strict'; $.extend(true, $.trumbowyg, { langs: { // jshint camelcase:false en: { fontFamily: 'Font' }, az: { fontFamily: 'Şrift' }, by: { fontFamily: 'Шрыфт' }, ca: { fontFamily: 'Font' }, da: { fontFamily: 'Skrifttype' }, de: { fontFamily: 'Schriftart' }, es: { fontFamily: 'Fuente' }, et: { fontFamily: 'Font' }, fr: { fontFamily: 'Police' }, hu: { fontFamily: 'Betűtípus' }, ko: { fontFamily: '글꼴' }, nl: { fontFamily: 'Lettertype' }, pt_br: { fontFamily: 'Fonte' }, ru: { fontFamily: 'Шрифт' }, sl: { fontFamily: 'Pisava' }, tr: { fontFamily: 'Yazı tipi' }, zh_tw: { fontFamily: '字體' } } }); // jshint camelcase:true var defaultOptions = { fontList: [ {name: 'Arial', family: 'Arial, Helvetica, sans-serif'}, {name: 'Arial Black', family: 'Arial Black, Gadget, sans-serif'}, {name: 'Comic Sans', family: 'Comic Sans MS, Textile, cursive, sans-serif'}, {name: 'Courier New', family: 'Courier New, Courier, monospace'}, {name: 'Georgia', family: 'Georgia, serif'}, {name: 'Impact', family: 'Impact, Charcoal, sans-serif'}, {name: 'Lucida Console', family: 'Lucida Console, Monaco, monospace'}, {name: 'Lucida Sans', family: 'Lucida Sans Uncide, Lucida Grande, sans-serif'}, {name: 'Palatino', family: 'Palatino Linotype, Book Antiqua, Palatino, serif'}, {name: 'Tahoma', family: 'Tahoma, Geneva, sans-serif'}, {name: 'Times New Roman', family: 'Times New Roman, Times, serif'}, {name: 'Trebuchet', family: 'Trebuchet MS, Helvetica, sans-serif'}, {name: 'Verdana', family: 'Verdana, Geneva, sans-serif'} ] }; // Add dropdown with web safe fonts $.extend(true, $.trumbowyg, { plugins: { fontfamily: { init: function (trumbowyg) { trumbowyg.o.plugins.fontfamily = $.extend({}, defaultOptions, trumbowyg.o.plugins.fontfamily || {} ); trumbowyg.addBtnDef('fontfamily', { dropdown: buildDropdown(trumbowyg), hasIcon: false, text: trumbowyg.lang.fontFamily }); } } } }); function buildDropdown(trumbowyg) { var dropdown = []; $.each(trumbowyg.o.plugins.fontfamily.fontList, function (index, font) { trumbowyg.addBtnDef('fontfamily_' + index, { title: '' + font.name + '', hasIcon: false, fn: function () { trumbowyg.execCmd('fontName', font.family, true); } }); dropdown.push('fontfamily_' + index); }); return dropdown; } })(jQuery); ================================================ FILE: plugins/fontsize/trumbowyg.fontsize.js ================================================ (function ($) { 'use strict'; $.extend(true, $.trumbowyg, { langs: { // jshint camelcase:false en: { fontsize: 'Font size', fontsizes: { 'x-small': 'Extra small', 'small': 'Small', 'medium': 'Regular', 'large': 'Large', 'x-large': 'Extra large', 'custom': 'Custom' }, fontCustomSize: { title: 'Custom Font Size', label: 'Font Size', value: '48px' } }, az: { fontsize: 'Şrift həcmi', fontsizes: { 'x-small': 'Daha kiçik', 'small': 'Kiçik', 'medium': 'Normal', 'large': 'Böyük', 'x-large': 'Daha böyük', 'custom': 'Fərdi həcm' }, fontCustomSize: { title: 'Fərdi şrift həcmi', label: 'Şrift həcmi', value: '48px' } }, by: { fontsize: 'Памер шрыфта', fontsizes: { 'x-small': 'Вельмі маленькі', 'small': 'Маленькі', 'medium': 'Звычайны', 'large': 'Вялікі', 'x-large': 'Вельмі вялікі', 'custom': 'Карыстальніцкі' }, fontCustomSize: { title: 'Карыстальніцкі Памер Шрыфта', label: 'Памер Шрыфта', value: '48px' } }, ca: { fontsize: 'Mida de la lletra', fontsizes: { 'x-small': 'Molt petita', 'small': 'Petita', 'medium': 'Normal', 'large': 'Gran', 'x-large': 'Molt Gran', 'custom': 'Personalitzada' }, fontCustomSize: { title: 'Mida de lletra personalitzada', label: 'Mida de lletra', value: '48px' } }, da: { fontsize: 'Skriftstørrelse', fontsizes: { 'x-small': 'Ekstra lille', 'small': 'Lille', 'medium': 'Normal', 'large': 'Stor', 'x-large': 'Ekstra stor', 'custom': 'Brugerdefineret' } }, de: { fontsize: 'Schriftgröße', fontsizes: { 'x-small': 'Sehr klein', 'small': 'Klein', 'medium': 'Normal', 'large': 'Groß', 'x-large': 'Sehr groß', 'custom': 'Benutzerdefiniert' }, fontCustomSize: { title: 'Benutzerdefinierte Schriftgröße', label: 'Schriftgröße', value: '48px' } }, es: { fontsize: 'Tamaño de Fuente', fontsizes: { 'x-small': 'Extra pequeña', 'small': 'Pegueña', 'medium': 'Regular', 'large': 'Grande', 'x-large': 'Extra Grande', 'custom': 'Customizada' }, fontCustomSize: { title: 'Tamaño de Fuente Customizada', label: 'Tamaño de Fuente', value: '48px' } }, et: { fontsize: 'Teksti suurus', fontsizes: { 'x-small': 'Väga väike', 'small': 'Väike', 'medium': 'Tavaline', 'large': 'Suur', 'x-large': 'Väga suur', 'custom': 'Määra ise' }, fontCustomSize: { title: 'Kohandatud teksti suurus', label: 'Teksti suurus', value: '48px' } }, fr: { fontsize: 'Taille de la police', fontsizes: { 'x-small': 'Très petit', 'small': 'Petit', 'medium': 'Normal', 'large': 'Grand', 'x-large': 'Très grand', 'custom': 'Taille personnalisée' }, fontCustomSize: { title: 'Taille de police personnalisée', label: 'Taille de la police', value: '48px' } }, hu: { fontsize: 'Betű méret', fontsizes: { 'x-small': 'Extra kicsi', 'small': 'Kicsi', 'medium': 'Normális', 'large': 'Nagy', 'x-large': 'Extra nagy', 'custom': 'Egyedi' }, fontCustomSize: { title: 'Egyedi betű méret', label: 'Betű méret', value: '48px' } }, it: { fontsize: 'Dimensioni del testo', fontsizes: { 'x-small': 'Molto piccolo', 'small': 'piccolo', 'regular': 'normale', 'large': 'grande', 'x-large': 'Molto grande', 'custom': 'Personalizzato' }, fontCustomSize: { title: 'Dimensioni del testo personalizzato', label: 'Dimensioni del testo', value: '48px' } }, ko: { fontsize: '글꼴 크기', fontsizes: { 'x-small': '아주 작게', 'small': '작게', 'medium': '보통', 'large': '크게', 'x-large': '아주 크게', 'custom': '사용자 지정' }, fontCustomSize: { title: '사용자 지정 글꼴 크기', label: '글꼴 크기', value: '48px' } }, nl: { fontsize: 'Lettergrootte', fontsizes: { 'x-small': 'Extra klein', 'small': 'Klein', 'medium': 'Normaal', 'large': 'Groot', 'x-large': 'Extra groot', 'custom': 'Handmatig' }, fontCustomSize: { title: 'Handmatige lettergrootte', label: 'Lettergrootte', value: '48px' } }, pt_br: { fontsize: 'Tamanho da fonte', fontsizes: { 'x-small': 'Extra pequeno', 'small': 'Pequeno', 'regular': 'Médio', 'large': 'Grande', 'x-large': 'Extra grande', 'custom': 'Personalizado' }, fontCustomSize: { title: 'Tamanho de Fonte Personalizado', label: 'Tamanho de Fonte', value: '48px' } }, ru: { fontsize: 'Размер шрифта', fontsizes: { 'x-small': 'Очень маленький', 'small': 'Маленький', 'medium': 'Обычный', 'large': 'Большой', 'x-large': 'Очень большой', 'custom': 'Пользовательский' }, fontCustomSize: { title: 'Пользовательский Размер Шрифта', label: 'Размер Шрифта', value: '48px' } }, sl: { fontsize: 'Velikost pisave', fontsizes: { 'x-small': 'Ekstra majhna', 'small': 'Majhna', 'medium': 'Navadno', 'large': 'Velika', 'x-large': 'Ekstra velika', 'custom': 'Poljubna' }, fontCustomSize: { title: 'Poljubna velikost pisave', label: 'Velikost pisave', value: '48px' } }, tr: { fontsize: 'Yazı boyutu', fontsizes: { 'x-small': 'Çok küçük', 'small': 'Küçük', 'medium': 'Normal', 'large': 'Büyük', 'x-large': 'Çok büyük', 'custom': 'Özel' }, fontCustomSize: { title: 'Özel Yazı Boyutu', label: 'Yazı Boyutu', value: '48px' } }, zh_tw: { fontsize: '字體大小', fontsizes: { 'x-small': '最小', 'small': '小', 'medium': '中', 'large': '大', 'x-large': '最大', 'custom': '自訂大小' }, fontCustomSize: { title: '自訂義字體大小', label: '字體大小', value: '48px' } } } }); // jshint camelcase:true var defaultOptions = { sizeList: [ 'x-small', 'small', 'medium', 'large', 'x-large' ], allowCustomSize: true }; // Add dropdown with font sizes $.extend(true, $.trumbowyg, { plugins: { fontsize: { init: function (trumbowyg) { trumbowyg.o.plugins.fontsize = $.extend({}, defaultOptions, trumbowyg.o.plugins.fontsize || {} ); trumbowyg.addBtnDef('fontsize', { dropdown: buildDropdown(trumbowyg) }); } } } }); function setFontSize(trumbowyg, size) { trumbowyg.$ed.focus(); trumbowyg.saveRange(); // Temporary size trumbowyg.execCmd('fontSize', '1'); var fontElements = trumbowyg.$ed.find('font[size="1"]'); // Remove previous font-size span tags. Needed to prevent Firefox from // nesting multiple spans on font-size changes. // (see https://github.com/Alex-D/Trumbowyg/issues/1252) fontElements.find('span[style*="font-size"]').contents().unwrap(); // Find elements that were added and change to with chosen size fontElements.replaceWith(function () { return $('', { css: {'font-size': size}, html: this.innerHTML }); }); // Remove and leftover elements $(trumbowyg.range.startContainer.parentElement).find('span[style=""]').contents().unwrap(); trumbowyg.restoreRange(); trumbowyg.syncCode(); trumbowyg.$c.trigger('tbwchange'); } function buildDropdown(trumbowyg) { var dropdown = []; $.each(trumbowyg.o.plugins.fontsize.sizeList, function (index, size) { trumbowyg.addBtnDef('fontsize_' + size, { text: '' + (trumbowyg.lang.fontsizes[size] || size) + '', hasIcon: false, fn: function () { setFontSize(trumbowyg, size); } }); dropdown.push('fontsize_' + size); }); if (trumbowyg.o.plugins.fontsize.allowCustomSize) { var customSizeButtonName = 'fontsize_custom'; var customSizeBtnDef = { fn: function () { trumbowyg.openModalInsert(trumbowyg.lang.fontCustomSize.title, { size: { label: trumbowyg.lang.fontCustomSize.label, value: trumbowyg.lang.fontCustomSize.value } }, function (form) { setFontSize(trumbowyg, form.size); return true; } ); }, text: '' + trumbowyg.lang.fontsizes.custom + '', hasIcon: false }; trumbowyg.addBtnDef(customSizeButtonName, customSizeBtnDef); dropdown.push(customSizeButtonName); } return dropdown; } })(jQuery); ================================================ FILE: plugins/giphy/trumbowyg.giphy.js ================================================ /* global AbortController: true */ (function ($) { 'use strict'; $.extend(true, $.trumbowyg, { langs: { // jshint camelcase:false en: { giphy: 'Insert GIF' }, az: { giphy: 'GIF yerləşdir' }, by: { giphy: 'Уставіць GIF' }, de: { giphy: 'GIF einfügen' }, et: { giphy: 'Sisesta GIF' }, fr: { giphy: 'Insérer un GIF' }, hu: { giphy: 'GIF beszúrás' }, ru: { giphy: 'Вставить GIF' }, sl: { giphy: 'Vstavi GIF' }, tr: { giphy: 'GIF ekle' } // jshint camelcase:true } }); var giphyLogo = ''; // jshint ignore:line var CANCEL_EVENT = 'tbwcancel'; // Throttle helper function trumbowygThrottle(callback, delay) { var last; var timer; return function () { var context = this; var now = new Date().getTime(); var args = arguments; if (last && now < last + delay) { clearTimeout(timer); timer = setTimeout(function () { last = now; callback.apply(context, args); }, delay); } else { last = now; callback.apply(context, args); } }; } // Fills modal with response gifs function renderGifs(response, $giphyModal, trumbowyg, mustEmpty) { var width = ($giphyModal.width() - 20) / 3; var html = response.data .filter(function (gifData) { var image = gifData.images[GIPHY_OPTIONS.pickerRendition]; return !!image[GIPHY_OPTIONS.pickerRenditionUrlAttribute]; }) .map(function (gifData) { var image = gifData.images[GIPHY_OPTIONS.pickerRendition]; var selectionImage = gifData.images[GIPHY_OPTIONS.selectionRendition]; var imageRatio = image.height / image.width, altText = gifData.title; var url = image[GIPHY_OPTIONS.pickerRenditionUrlAttribute]; var selectionUrl = selectionImage[GIPHY_OPTIONS.selectionRenditionUrlAttribute]; var imgHtml = '' + altText + ''; return '
' + imgHtml + '
'; }) .join('') ; if (mustEmpty === true) { if (html.length === 0) { if ($('.' + trumbowyg.o.prefix + 'giphy-no-result', $giphyModal).length > 0) { return; } html = ''; } $giphyModal.empty(); } $giphyModal.append(html); // Remove gray overlay on image load // moved here from inline callback definition due to CSP issue // Note: this is being done post-factum because load event doesn't bubble up and so can't be delegated var addLoadedClass = function (img) { img.classList.add('tbw-loaded'); }; $('img', $giphyModal).each(function () { var img = this; if (img.complete) { // images load instantly when cached and esp. when loaded in previous modal open addLoadedClass(img); } else { img.addEventListener('load', function () { addLoadedClass(this); }); } }); $('img', $giphyModal).on('click', function () { var src = $(this).data('selection-url'), alt = $(this).attr('alt'); trumbowyg.restoreRange(); trumbowyg.execCmd('insertImage', src, false, true); // relay alt tag into inserted image if (alt) { var $img = $('img[src="' + src + '"]:not([alt])', trumbowyg.$box); $img.attr('alt', alt); // Note: This seems to fire relatively early and could be wrapped in a setTimeout if needed trumbowyg.syncCode(); } $('img', $giphyModal).off(); trumbowyg.closeModal(); }); } // see: https://developers.giphy.com/explorer/ var defaultOptions = { rating: 'g', apiKey: null, throttleDelay: 300, limit: 50, noResultGifUrl: 'https://media.giphy.com/media/2Faz9FbRzmwxY0pZS/giphy.gif' }; const GIPHY_OPTIONS = { bundle: 'low_bandwidth', pickerRendition: 'fixed_width_small', // see: https://developers.giphy.com/docs/optional-settings/#renditions-on-demand pickerRenditionUrlAttribute: 'webp', // can be 'url' or 'mp4' or 'webp' selectionRendition: 'original', selectionRenditionUrlAttribute: 'url' }; // Add dropdown with font sizes $.extend(true, $.trumbowyg, { plugins: { giphy: { init: function (trumbowyg) { trumbowyg.o.plugins.giphy = $.extend({}, defaultOptions, trumbowyg.o.plugins.giphy || {} ); trumbowyg.addBtnDef('giphy', { fn: function () { if (trumbowyg.o.plugins.giphy.apiKey === null) { throw new Error('You must set a Giphy API Key'); } var BASE_URL = [ 'https://api.giphy.com/v1/gifs/search?api_key=', trumbowyg.o.plugins.giphy.apiKey, '&rating=', trumbowyg.o.plugins.giphy.rating, '&limit=', trumbowyg.o.plugins.giphy.limit, '&bundle=', GIPHY_OPTIONS.bundle, ].join(''); const DEFAULT_URL = BASE_URL.replace('/search', '/trending'); var prefix = trumbowyg.o.prefix; var abortController = new AbortController(); // Create and open the modal var searchInput = '', closeButton = '', poweredByGiphy = '
Powered by' + giphyLogo + '
', giphyModalHtml = searchInput + closeButton + poweredByGiphy + '
'; trumbowyg .openModal(null, giphyModalHtml, false) .one(CANCEL_EVENT, function () { try { abortController.abort(); abortController = new AbortController(); } catch (e) { } trumbowyg.closeModal(); }); var $giphyInput = $('.' + prefix + 'giphy-search'), $giphyClose = $('.' + prefix + 'giphy-close'), $giphyModal = $('.' + prefix + 'giphy-modal'); var onError = function () { if (navigator.onLine || $('.' + prefix + 'giphy-offline', $giphyModal).length) { return; } $giphyModal.empty(); $giphyModal.append('

You are offline

'); }; // Load trending gifs as default fetch(DEFAULT_URL, { method: 'GET', cache: 'no-cache', signal: abortController.signal }).then((response) => { response.json().then((responseJson) => { renderGifs(responseJson, $giphyModal, trumbowyg, true); }); }).catch(() => { onError(); }); var searchGifsOnInput = function () { var query = $giphyInput.val(); if (query.length === 0) { return; } try { abortController.abort(); abortController = new AbortController(); } catch (e) { } fetch(BASE_URL + '&q=' + encodeURIComponent(query), { method: 'GET', cache: 'no-cache', signal: abortController.signal }).then((response) => { response.json().then((responseJson) => { renderGifs(responseJson, $giphyModal, trumbowyg, true); }); }).catch(() => { onError(); }); }; var throttledInputRequest = trumbowygThrottle(searchGifsOnInput, trumbowyg.o.plugins.giphy.throttleDelay); $giphyInput.on('input', throttledInputRequest); $giphyInput.focus(); $giphyClose.one('click', function () { $giphyModal.trigger(CANCEL_EVENT); }); } }); } } } }); })(jQuery); ================================================ FILE: plugins/giphy/ui/sass/trumbowyg.giphy.scss ================================================ .trumbowyg-giphy-button svg { transform: scale(1.22); } .trumbowyg-giphy-search { display: block; width: 80%; margin: 5%; padding-left: 10px; padding-right: 150px; } .trumbowyg-giphy-close { position: absolute; top: calc(5% + 8px); right: calc(5% - 2px); width: 30px; height: 30px; background: none; border: 1px solid transparent; &:hover, &:focus { outline: none; background: #ecf0f1; } &:focus { border-color: rgba(0, 0, 0, 0.3); } } .trumbowyg-powered-by-giphy { position: absolute; top: calc(5% + 12px); right: calc(15% + 10px); pointer-events: none; user-select: none; span { text-transform: uppercase; font-weight: bold; font-size: 10px; opacity: 0.6; } svg { width: 66px; height: 15px; vertical-align: middle; margin-left: 6px; opacity: 0.45; } } .trumbowyg-giphy-modal-scroll { overflow: auto; overflow-x: hidden; height: 240px; } .trumbowyg-giphy-modal { padding: 0 5%; columns: 3; column-gap: 10px; .trumbowyg-giphy-no-result { width: 250%; margin: 13% 0 0 29%; } .trumbowyg-giphy-offline { font-size: 18px; width: 305%; height: 600px; margin-top: 95px; text-align: center; } } .trumbowyg-giphy-modal .img-container { width: 100%; margin-bottom: 10px; background-color: #ecf0f1; img { width: 100%; cursor: pointer; opacity: 0; transition: opacity 150ms; &:hover, &:focus { border: #2ecc71 solid 3px; } } img.tbw-loaded { opacity: 1; } } ================================================ FILE: plugins/highlight/trumbowyg.highlight.js ================================================ /* globals Prism */ (function ($, Prism) { 'use strict'; // My plugin default options var defaultOptions = { enableLineHighlight: true, languageNames: { // For updated list of languages // see https://github.com/PrismJS/prism/blob/master/plugins/show-language/prism-show-language.js 'html': 'HTML', 'xml': 'XML', 'svg': 'SVG', 'mathml': 'MathML', 'ssml': 'SSML', 'css': 'CSS', 'clike': 'C-like', 'js': 'JavaScript', 'abap': 'ABAP', 'abnf': 'Augmented Backus–Naur form', 'al': 'AL', 'antlr4': 'ANTLR4', 'g4': 'ANTLR4', 'apacheconf': 'Apache Configuration', 'apl': 'APL', 'aql': 'AQL', 'arff': 'ARFF', 'asciidoc': 'AsciiDoc', 'adoc': 'AsciiDoc', 'asm6502': '6502 Assembly', 'aspnet': 'ASP.NET (C#)', 'autohotkey': 'AutoHotkey', 'autoit': 'AutoIt', 'basic': 'BASIC', 'bbcode': 'BBcode', 'bnf': 'Backus–Naur form', 'rbnf': 'Routing Backus–Naur form', 'conc': 'Concurnas', 'csharp': 'C#', 'cs': 'C#', 'dotnet': 'C#', 'cpp': 'C++', 'cil': 'CIL', 'coffee': 'CoffeeScript', 'cmake': 'CMake', 'csp': 'Content-Security-Policy', 'css-extras': 'CSS Extras', 'dax': 'DAX', 'django': 'Django/Jinja2', 'jinja2': 'Django/Jinja2', 'dns-zone-file': 'DNS zone file', 'dns-zone': 'DNS zone file', 'dockerfile': 'Docker', 'ebnf': 'Extended Backus–Naur form', 'ejs': 'EJS', 'etlua': 'Embedded Lua templating', 'erb': 'ERB', 'excel-formula': 'Excel Formula', 'xlsx': 'Excel Formula', 'xls': 'Excel Formula', 'fsharp': 'F#', 'firestore-security-rules': 'Firestore security rules', 'ftl': 'FreeMarker Template Language', 'gcode': 'G-code', 'gdscript': 'GDScript', 'gedcom': 'GEDCOM', 'glsl': 'GLSL', 'gml': 'GameMaker Language', 'gamemakerlanguage': 'GameMaker Language', 'graphql': 'GraphQL', 'hs': 'Haskell', 'hcl': 'HCL', 'hlsl': 'HLSL', 'http': 'HTTP', 'hpkp': 'HTTP Public-Key-Pins', 'hsts': 'HTTP Strict-Transport-Security', 'ichigojam': 'IchigoJam', 'iecst': 'Structured Text (IEC 61131-3)', 'inform7': 'Inform 7', 'javadoc': 'JavaDoc', 'javadoclike': 'JavaDoc-like', 'javastacktrace': 'Java stack trace', 'jq': 'JQ', 'jsdoc': 'JSDoc', 'js-extras': 'JS Extras', 'js-templates': 'JS Templates', 'json': 'JSON', 'jsonp': 'JSONP', 'json5': 'JSON5', 'latex': 'LaTeX', 'tex': 'TeX', 'context': 'ConTeXt', 'lilypond': 'LilyPond', 'ly': 'LilyPond', 'emacs': 'Lisp', 'elisp': 'Lisp', 'emacs-lisp': 'Lisp', 'llvm': 'LLVM IR', 'lolcode': 'LOLCODE', 'md': 'Markdown', 'markup-templating': 'Markup templating', 'matlab': 'MATLAB', 'mel': 'MEL', 'moon': 'MoonScript', 'n1ql': 'N1QL', 'n4js': 'N4JS', 'n4jsd': 'N4JS', 'nand2tetris-hdl': 'Nand To Tetris HDL', 'nasm': 'NASM', 'neon': 'NEON', 'nginx': 'nginx', 'nsis': 'NSIS', 'objectivec': 'Objective-C', 'objc': 'Objective-C', 'ocaml': 'OCaml', 'opencl': 'OpenCL', 'parigp': 'PARI/GP', 'objectpascal': 'Object Pascal', 'pcaxis': 'PC-Axis', 'px': 'PC-Axis', 'peoplecode': 'PeopleCode', 'pcode': 'PeopleCode', 'php': 'PHP', 'phpdoc': 'PHPDoc', 'php-extras': 'PHP Extras', 'plsql': 'PL/SQL', 'powerquery': 'PowerQuery', 'pq': 'PowerQuery', 'mscript': 'PowerQuery', 'powershell': 'PowerShell', 'properties': '.properties', 'protobuf': 'Protocol Buffers', 'py': 'Python', 'q': 'Q (kdb+ database)', 'qml': 'QML', 'rkt': 'Racket', 'jsx': 'React JSX', 'tsx': 'React TSX', 'renpy': 'Ren\'py', 'rest': 'reST (reStructuredText)', 'robotframework': 'Robot Framework', 'robot': 'Robot Framework', 'rb': 'Ruby', 'sas': 'SAS', 'sass': 'Sass (Sass)', 'scss': 'Sass (Scss)', 'shell-session': 'Shell session', 'solidity': 'Solidity (Ethereum)', 'solution-file': 'Solution file', 'sln': 'Solution file', 'soy': 'Soy (Closure Template)', 'sparql': 'SPARQL', 'rq': 'SPARQL', 'splunk-spl': 'Splunk SPL', 'sqf': 'SQF: Status Quo Function (Arma 3)', 'sql': 'SQL', 'tap': 'TAP', 'toml': 'TOML', 'tt2': 'Template Toolkit 2', 'trig': 'TriG', 'ts': 'TypeScript', 't4-cs': 'T4 Text Templates (C#)', 't4': 'T4 Text Templates (C#)', 't4-vb': 'T4 Text Templates (VB)', 't4-templating': 'T4 templating', 'uscript': 'UnrealScript', 'uc': 'UnrealScript', 'vbnet': 'VB.Net', 'vhdl': 'VHDL', 'vim': 'vim', 'visual-basic': 'Visual Basic', 'vb': 'Visual Basic', 'wasm': 'WebAssembly', 'wiki': 'Wiki markup', 'xeoracube': 'XeoraCube', 'xojo': 'Xojo (REALbasic)', 'xquery': 'XQuery', 'yaml': 'YAML', 'yml': 'YAML' } }; function highlightIt(text, language, lineHighlight) { return [ '
',
            '' + Prism.highlight(text, Prism.languages[language]) + '',
            '
' ].join(''); } function escapeHtml(html) { return $('
').text(html).html(); } function buildHighlightOptions(trumbowyg) { var languageNames = trumbowyg.o.plugins.highlight.languageNames; var languageNameKeys = Object.keys(languageNames); var prismLanguageKeys = Object.keys(Prism.languages); var options = prismLanguageKeys.filter(function (languageKey) { return languageNameKeys.indexOf(languageKey) >= 0; }).map(function (languageKey) { return { id: languageKey, name: languageNames[languageKey] }; }).sort(function (a, b) { // Sort languages by name return a.name.localeCompare(b.name); }).map(function (language) { // Generate a list of options return ''; }).join(''); return options; } function buildLineHighlightFieldIfEnabled(trumbowyg) { if (trumbowyg.o.plugins.highlight.enableLineHighlight === false) { return ''; } return '
' + ' ' + '
'; } // If my plugin is a button function buildButtonDef(trumbowyg) { return { fn: function () { var $modal = trumbowyg.openModal('Code', [ '
', ' ', '
', '
', ' ', '
', buildLineHighlightFieldIfEnabled(trumbowyg), ].join('\n')), $language = $modal.find('.language'), $code = $modal.find('.code'), $lineHighlight = $modal.find('.trumbowyg-line-highlight'); // Listen clicks on modal box buttons $modal.on('tbwconfirm', function () { trumbowyg.restoreRange(); trumbowyg.execCmd('insertHTML', highlightIt($code.val(), $language.val(), $lineHighlight.val())); trumbowyg.execCmd('insertHTML', '


'); trumbowyg.closeModal(); }); $modal.on('tbwcancel', function () { trumbowyg.closeModal(); }); } }; } $.extend(true, $.trumbowyg, { // Add some translations langs: { // jshint camelcase:false en: { highlight: 'Code syntax highlight', highlightLine: 'Highlight lines, e.g.: 1,3-5' }, az: { highlight: 'Kod birləşməsini vurğulamaq', highlightLine: 'Sətirləri vurğulamaq, məsələn: 1,3-5' }, by: { highlight: 'Падсветка сінтаксісу кода', highlightLine: 'Падсвятліць радкі, напр.: 1,3-5' }, es: { highlight: 'Resaltado de sintaxis de código', highlightLine: 'Resaltar lineas, ej: 1,3-5' }, et: { highlight: 'Koodi esiletoomine', highlightLine: 'Koodiread, näiteks: 1,3-5' }, hu: { highlight: 'Kód kiemelés' }, ko: { highlight: '코드 문법 하이라이트' }, pt_br: { highlight: 'Realçar sintaxe de código' }, ru: { highlight: 'Подсветка синтаксиса кода', highlightLine: 'Подсветить строки, напр.: 1,3-5' }, sl: { highlight: 'Označi sintakso kode', highlightLine: 'Označi številko vrstice, npr.: 1,3-5' }, tr: { highlight: 'Kod sözdizimini vurgula', highlightLine: 'Vurgu çizgileri, örneğin: 1,3-5' } // jshint camelcase:true }, // Add our plugin to Trumbowyg registered plugins plugins: { highlight: { init: function (trumbowyg) { // Fill current Trumbowyg instance with my plugin default options trumbowyg.o.plugins.highlight = $.extend(true, {}, defaultOptions, trumbowyg.o.plugins.highlight || {} ); // If my plugin is a button trumbowyg.addBtnDef('highlight', buildButtonDef(trumbowyg)); } } } }); })(jQuery, Prism); ================================================ FILE: plugins/highlight/ui/sass/trumbowyg.highlight.scss ================================================ .trumbowyg-highlight-form-group { margin: 15px 10px; .trumbowyg-highlight-form-control { width: 100%; border: 1px solid #DEDEDE; font-size: 14px; padding: 7px; &.code { height: 200px; } } } ================================================ FILE: plugins/history/trumbowyg.history.js ================================================ /*/* =========================================================== * trumbowyg.history.js v1.0 * history plugin for Trumbowyg * http://alex-d.github.com/Trumbowyg * =========================================================== * Author : Sven Dunemann [dunemann@forelabs.eu] */ (function ($) { 'use strict'; $.extend(true, $.trumbowyg, { plugins: { history: { destroy: function (t) { t.$c.off('tbwinit.history tbwchange.history'); }, init: function (t) { t.o.plugins.history = $.extend(true, { _stack: [], _index: -1, _focusEl: undefined }, t.o.plugins.history || {}); var btnBuildDefRedo = { title: t.lang.redo, ico: 'redo', key: 'Y', fn: function () { if (t.o.plugins.history._index < t.o.plugins.history._stack.length - 1) { t.o.plugins.history._index += 1; var index = t.o.plugins.history._index; var newState = t.o.plugins.history._stack[index]; t.execCmd('html', newState); // because of some semantic optimisations we have to save the state back // to history t.o.plugins.history._stack[index] = t.$ed.html(); carretToEnd(); toggleButtonStates(); } } }; var btnBuildDefUndo = { title: t.lang.undo, ico: 'undo', key: 'Z', fn: function () { if (t.o.plugins.history._index > 0) { t.o.plugins.history._index -= 1; var index = t.o.plugins.history._index, newState = t.o.plugins.history._stack[index]; t.execCmd('html', newState); // because of some semantic optimisations we have to save the state back // to history t.o.plugins.history._stack[index] = t.$ed.html(); carretToEnd(); toggleButtonStates(); } } }; var pushToHistory = function () { var index = t.o.plugins.history._index, stack = t.o.plugins.history._stack, latestState = stack.slice(-1)[0] || '

', prevState = stack[index], newState = t.$ed.html(), focusEl = t.doc.getSelection().focusNode, focusElText = '', latestStateTagsList, newStateTagsList, prevFocusEl = t.o.plugins.history._focusEl; latestStateTagsList = $('
' + latestState + '
').find('*').map(function () { return this.localName; }); newStateTagsList = $('
' + newState + '
').find('*').map(function () { return this.localName; }); if (focusEl) { t.o.plugins.history._focusEl = focusEl; focusElText = focusEl.outerHTML || focusEl.textContent; } if (newState !== prevState) { // a new stack entry is defined when current insert ends on a whitespace character // or count of node elements has been changed // or focused element differs from previous one if (focusElText.slice(-1).match(/\s/) || !arraysAreIdentical(latestStateTagsList, newStateTagsList) || t.o.plugins.history._index <= 0 || focusEl !== prevFocusEl) { t.o.plugins.history._index += 1; // remove newer entries in history when something new was added // because timeline was changes with interaction t.o.plugins.history._stack = stack.slice( 0, t.o.plugins.history._index ); // now add new state to modified history t.o.plugins.history._stack.push(newState); } else { // modify last stack entry t.o.plugins.history._stack[index] = newState; } toggleButtonStates(); } }; var toggleButtonStates = function () { var index = t.o.plugins.history._index, stackSize = t.o.plugins.history._stack.length, undoState = (index > 0), redoState = (stackSize !== 0 && index !== stackSize - 1); toggleButtonState('historyUndo', undoState); toggleButtonState('historyRedo', redoState); }; var toggleButtonState = function (btn, enable) { var button = t.$box.find('.trumbowyg-' + btn + '-button'); if (enable) { button.removeClass('trumbowyg-disable'); } else if (!button.hasClass('trumbowyg-disable')) { button.addClass('trumbowyg-disable'); } }; var arraysAreIdentical = function (a, b) { if (a === b) { return true; } if (a == null || b == null) { return false; } if (a.length !== b.length) { return false; } for (var i = 0; i < a.length; i += 1) { if (a[i] !== b[i]) { return false; } } return true; }; var carretToEnd = function () { var node = t.doc.getSelection().focusNode, range = t.doc.createRange(); if (node.childNodes.length > 0) { range.setStartAfter(node.childNodes[node.childNodes.length - 1]); range.setEndAfter(node.childNodes[node.childNodes.length - 1]); t.doc.getSelection().removeAllRanges(); t.doc.getSelection().addRange(range); } }; t.$c.on('tbwinit.history tbwchange.history', pushToHistory); t.addBtnDef('historyRedo', btnBuildDefRedo); t.addBtnDef('historyUndo', btnBuildDefUndo); } } } }); })(jQuery); ================================================ FILE: plugins/indent/trumbowyg.indent.js ================================================ /* =========================================================== * trumbowyg.indent.js v1.0 * Indent or Outdent plugin for Trumbowyg * http://alex-d.github.com/Trumbowyg * =========================================================== * Author : Fabacks * Website : https://github.com/Fabacks */ (function ($) { 'use strict'; $.extend(true, $.trumbowyg, { langs: { // jshint camelcase:false en: { indent: 'Indent', outdent: 'Outdent' }, az: { indent: 'Girinti', outdent: 'Çıxıntı' }, by: { indent: 'Водступ', outdent: 'Выступ' }, de: { indent: 'Einzug vergrößern', outdent: 'Einzug verkleinern' }, et: { indent: 'Taande suurendamine', outdent: 'Taande vähendamine' }, fr: { indent: 'Augmenter le retrait', outdent: 'Diminuer le retrait' }, pt_br: { indent: 'Aumentar Recuo', outdent: 'Diminuir Recuo' }, ru: { indent: 'Отступ', outdent: 'Выступ' }, sl: { indent: 'Povečaj zamik', outdent: 'Zmanjšaj zamik' }, tr: { indent: 'Girinti', outdent: 'Çıkıntı' } // jshint camelcase:true } }); // Adds the extra button definition $.extend(true, $.trumbowyg, { plugins: { paragraph: { init: function (trumbowyg) { var indentBtnDef = { fn: 'indent', title: trumbowyg.lang.indent, isSupported: function () { return !!document.queryCommandSupported && !!document.queryCommandSupported('indent'); }, ico: 'indent' }; var outdentBtnDef = { fn: 'outdent', title: trumbowyg.lang.outdent, isSupported: function () { return !!document.queryCommandSupported && !!document.queryCommandSupported('outdent'); }, ico: 'outdent' }; trumbowyg.addBtnDef('indent', indentBtnDef); trumbowyg.addBtnDef('outdent', outdentBtnDef); } } } }); })(jQuery); ================================================ FILE: plugins/insertaudio/trumbowyg.insertaudio.js ================================================ /*/* =========================================================== * trumbowyg.insertaudio.js v1.0 * InsertAudio plugin for Trumbowyg * http://alex-d.github.com/Trumbowyg * =========================================================== * Author : Adam Hess (AdamHess) */ (function ($) { 'use strict'; var insertAudioOptions = { src: { label: 'URL', required: true }, autoplay: { label: 'AutoPlay', required: false, type: 'checkbox' }, muted: { label: 'Muted', required: false, type: 'checkbox' }, preload: { label: 'preload options', required: false } }; $.extend(true, $.trumbowyg, { langs: { // jshint camelcase:false en: { insertAudio: 'Insert Audio' }, az: { insertAudio: 'Səs yerləşdir' }, by: { insertAudio: 'Уставіць аўдыё' }, ca: { insertAudio: 'Inserir Audio' }, da: { insertAudio: 'Indsæt lyd' }, de: { insertAudio: 'Audio einfügen' }, es: { insertAudio: 'Insertar Audio' }, et: { insertAudio: 'Lisa helifail' }, fr: { insertAudio: 'Insérer un son' }, hu: { insertAudio: 'Audio beszúrás' }, ja: { insertAudio: '音声の挿入' }, ko: { insertAudio: '소리 넣기' }, pt_br: { insertAudio: 'Inserir áudio' }, ru: { insertAudio: 'Вставить аудио' }, sl: { insertAudio: 'Vstavi zvočno datoteko' }, tr: { insertAudio: 'Ses Ekle' }, // jshint camelcase:true }, plugins: { insertAudio: { init: function (trumbowyg) { var btnDef = { fn: function () { var insertAudioCallback = function (v) { // controls should always be show otherwise the audio will // be invisible defeating the point of a wysiwyg var html = '