├── .gitignore ├── .pa11yci ├── info.json ├── yii2 ├── assets │ ├── FontAweSomeAsset.php │ └── RichTextAsset.php └── widgets │ └── RichTextWidget.php ├── cypress.config.js ├── src ├── richtext-colors.scss ├── richtext.min.css.map ├── richtext.min.css ├── richtext.scss └── jquery.richtext.min.js ├── .github ├── workflows │ ├── test.yml │ ├── npm_publish.yml │ ├── accessibility.yml │ └── build.yml ├── dependabot.yml ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── composer.json ├── cypress ├── support │ ├── e2e.js │ └── commands.js └── e2e │ └── basics.cy.js ├── examples ├── css │ └── site.css ├── fontawesome5.html ├── index.html └── multipleEditors.html ├── package.json ├── CONTRIBUTING.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | node_modules/ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.pa11yci: -------------------------------------------------------------------------------- 1 | { 2 | "defaults": { 3 | "chromeLaunchConfig": { 4 | "args": ["--no-sandbox"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "RichText", 3 | "version" : "1.1.3", 4 | "source" : "https://github.com/webfashionist/RichText/", 5 | "info" : "https://github.com/webfashionist/RichText/" 6 | } 7 | -------------------------------------------------------------------------------- /yii2/assets/FontAweSomeAsset.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Overview 10 | 11 | This pull request [introduces/changes/removes] [functionality/feature]. 12 | 13 | (Please write a summary of your pull request here. This paragraph should go into detail about what is changing, the motivation behind this change, and the approach you took.) 14 | 15 | 16 | 17 | **I understand that:** 18 | 19 | - [ ] I'm submitting this PR for reference only. It shows an example of what I'd like to see changed but 20 | I understand that it will not be merged and I will not be listed as a contributor on this project. -------------------------------------------------------------------------------- /examples/fontawesome5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RichText example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 |
24 | 25 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -------------------------------------------------------------------------------- /.github/workflows/accessibility.yml: -------------------------------------------------------------------------------- 1 | name: Accessibility 2 | on: push 3 | 4 | jobs: 5 | accessibility: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout code 9 | uses: actions/checkout@v3 10 | 11 | - name: Setup PHP 12 | uses: shivammathur/setup-php@v2 13 | with: 14 | php-version: '8.3' 15 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis, memcached, openssl 16 | tools: composer:2.5.8 17 | coverage: none 18 | 19 | - name : Install Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: '21' 23 | 24 | - name: Install npm dependencies 25 | run: npm install 26 | 27 | - name: Serve Files 28 | run: nohup php -S localhost:8080 -t examples/ & 29 | 30 | - name: Run pa11y 31 | run: | 32 | ./node_modules/pa11y-ci/bin/pa11y-ci.js http://localhost:8080/index.html 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | pull_request : 5 | permissions: 6 | pull-requests: write # grant write permission on the pull-requests endpoint 7 | jobs: 8 | Build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v3 13 | - name: Get branch name 14 | id: branch-name 15 | uses: tj-actions/branch-names@v9.0.0 16 | - name: Install dependencies 17 | run: npm install 18 | - name: Build development environment 19 | run: npm run build 20 | - name: Auto commit changes 21 | uses: stefanzweifel/git-auto-commit-action@v4 22 | with: 23 | branch: ${{ steps.branch-name.outputs.current_branch }} 24 | commit_message: "build(Minify): Minify CSS/JavaScript [skip actions]" 25 | commit_user_name: webfashionist 26 | commit_user_email: info@webfashion.in 27 | commit_author: webfashionist 28 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RichText example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 |
25 | 26 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | ### Describe the problem 2 | 3 | 4 | 5 | ### What did you expect? 6 | 7 | 8 | 9 | ### What version and browser are you using? 10 | 11 | Version: 12 | Browser and version: 13 | 14 | 15 | ### Reproducible test case 16 | 17 | 18 | 19 | 20 | 21 | ### Bug report checklist 22 | 23 | - [ ] I have filled out as much of the above information as I can 24 | - [ ] I have included a test case because my odds go _way_ up that the issue can be fixed when I do 25 | - [ ] I have [searched for existing issues](https://github.com/webfashionist/RichText/issues) and to the best of my knowledge this is not a duplicate 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | ### Describe the problem you'd like to see solved or additional feature for RichText 2 | 3 | 4 | 5 | 6 | ### Is there an existing part of RichText this feature is related to or is it something new? 7 | 8 | 9 | 10 | 11 | 12 | ### Why would other RichText users care about this? 13 | 14 | 15 | 16 | 17 | ### On a scale of 1 (sometime in the future) to 10 (absolutely right now), how soon would you recommend this feature to be done? 18 | 19 | 20 | 21 | 22 | ### Feature request checklist 23 | 24 | - [ ] This is a single feature (i.e. not a re-write of all of RichText) 25 | - [ ] The title starts with "Feature request: " and is followed by a clear feature name (e.g.: `Feature request: moar cowbell`) 26 | - [ ] I have [searched for existing issues](https://github.com/webfashionist/RichText/issues) and to the best of my knowledge this is not a duplicate 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@webfashionist/richtext", 3 | "version": "1.1.3", 4 | "description": "WYSIWYG editor developed as jQuery plugin", 5 | "directories": { 6 | "example": "examples" 7 | }, 8 | "scripts": { 9 | "start": "http-server -p 8080", 10 | "installuglify": "npm i uglify-js -g", 11 | "uglify": "npx uglifyjs src/jquery.richtext.js > src/jquery.richtext.min.js --comments --warn", 12 | "test": "cypress run -b chrome", 13 | "minifycss": "npx sass --style=compressed src/richtext.scss:src/richtext.min.css", 14 | "build": "npm run uglify && npm run minifycss" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/webfashionist/RichText.git" 19 | }, 20 | "author": "webfashionist", 21 | "license": "AGPL-3.0", 22 | "bugs": { 23 | "url": "https://github.com/webfashionist/RichText/issues" 24 | }, 25 | "homepage": "https://github.com/webfashionist/RichText#readme", 26 | "devDependencies": { 27 | "cypress": "^15.0.0", 28 | "http-server": "^14.1.1", 29 | "pa11y-ci": "^4.0.0", 30 | "sass": "^1.83.0", 31 | "uglify-js": "^3.19.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /yii2/widgets/RichTextWidget.php: -------------------------------------------------------------------------------- 1 | view); 21 | if ($this->autoInit) { 22 | $id = Html::getInputId($this->model, $this->attribute); 23 | $this->view->registerJs('$("#' . $id . '").richText('. json_encode($this->clientParams) .');'); 24 | } 25 | if (empty($this->inputParams['class'])) { 26 | $this->inputParams['class'] = []; 27 | } 28 | if (is_string($this->inputParams['class'])) { 29 | $this->inputParams['class'] = [$this->inputParams['class']]; 30 | } 31 | $this->inputParams['class'][] = 'rich-text-widget'; 32 | return Html::activeTextarea($this->model, $this->attribute, $this->inputParams); 33 | } 34 | } -------------------------------------------------------------------------------- /examples/multipleEditors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RichText example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/richtext.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["richtext.scss","richtext-colors.scss"],"names":[],"mappings":"AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,EAwBA,UACI,UACA,kBACA,iBC1BQ,QD2BR,yBACA,MCnBS,QDoBT,WAEA,yBACI,iDACA,+BACI,cACA,kBAGJ,gKACI,kBACA,sBACA,gBACA,WAGJ,gCACI,eAGJ,gCACI,cACA,kBACA,iBCrDA,QDsDA,YACA,MCtDA,QDuDA,eACA,wBACA,qBACA,gBACA,0BACA,uBACA,kBAIR,4BACI,gBACA,MACA,gBACA,yBACA,gCACA,UAEA,6CACI,yCACA,eACA,sBACA,iBACA,oDACI,MC3ER,KD6EI,qDACI,MC5EP,OD8EG,kDACI,MC9EV,IDgFM,iEACI,iBAIR,gDACI,yCACA,eACA,sBACA,iBAGJ,+BACI,eACA,gBACA,aACA,gBAEA,kCACI,WACA,cACA,gBAEA,oCACI,cACA,kBACA,+BACA,eACA,wCACA,qCACA,gCAEA,kKACI,oBAGJ,6DACI,aACA,kBACA,MACA,OACA,QACA,SACA,gCACA,eAEA,sFACI,kBACA,SACA,uBACA,WCpIpB,KDqIoB,yBACA,kBACA,MCnIf,QDoIe,eACA,eACA,kBACA,WACA,mDACA,gDACA,2CACA,wLACI,MCtJpB,QD0JY,gFACI,kBACA,cACA,sBACA,iBC7JhB,QD8JgB,yBACA,kBACA,gBACA,YACA,cACA,mDACA,gDACA,2CACA,iBACA,gBACA,kBAEA,yGACI,kBACA,MACA,YACA,WCzKxB,KD0KwB,MCtKnB,QDuKmB,eACA,eACA,kBACA,WAKR,kFACI,gBAEA,qFACI,cACA,WACA,iDAEA,uFACI,cACA,kBACA,gCAGJ,6FACI,iBClM5B,KDsMoB,4FACI,gBACA,WAEA,8FACI,cACA,UACA,SACA,YACA,0BACA,uBACA,kBACA,mCACA,gCACA,2BAEA,mGACI,cACA,YACA,WACA,0BACA,uBACA,kBAMhB,mFACI,kBAQZ,0CACI,iBC5OZ,KDgPI,qDACI,WACA,uDACI,eAIR,gGACI,cAIR,qCACI,cACA,WACA,WAIR,uCACI,eACA,WAGJ,kCACI,cACA,WACA,WAGJ,2BACI,aACA,iBChRA,KDiRA,2BACA,iDACA,aACA,aACA,kBACA,gBAGI,+CACI,0BACA,MCxRN,KD4RF,4DACI,iBAGJ,iCACI,cACA,iBACA,WAEA,wEACI,aACA,yBAMZ,iCACI,8BAGJ,4BACI,mBACA,aACA,iBCnTK,QDoTL,YACA,MCpTI,KDqTJ,2DACA,eACA,eACA,WACA,iBACA,aAGJ,yBACI,YACA,cACA,kBACA,eAGJ,kDAEI,WACA,cACA,kBACA,+BACA,eAEJ,0EAEI,WAIA,iCACI,MClVG,QDmVH,0BAGJ,kCACI,0BACA,YACA,6BAIR,yCACI,kBACA,iBC1WI,QD2WJ,+BACA,gCAEA,4CACI,gBACA,eACA","file":"richtext.min.css"} -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RichText 2 | 3 | Thank you for your interest in contributing to RichText! 4 | 5 | I've put together the following guidelines to help you figure out where you can best be helpful to the project. 6 | 7 | ## Quick start 8 | 9 | - [Request a new feature](https://github.com/webfashionist/RichText/issues/new??title=Feature%20request:feature-name&template=feature-request.md) 10 | - [Submit a bug report](https://github.com/webfashionist/RichText/issues/new?template=bug-report.md) 11 | 12 | ## Table of Contents 13 | 14 | 1. [Types of contributions](#types-of-contributions) 15 | 2. [Ground rules & expectations](#ground-rules--expectations) 16 | 3. [How to contribute](#how-to-contribute) 17 | 18 | ## Types of contributions 19 | 20 | Currently, there's no specific documentation or style guide for the code. 21 | Therefore, it would probably be easier to create [issues](https://github.com/webfashionist/RichText/issues) rather than [pull requests](https://github.com/webfashionist/RichText/pulls). 22 | 23 | However, there are still many ways you can contribute to RichText: 24 | 25 | 1. Suggest new ideas on how to extend the editor: [Request a new feature](https://github.com/webfashionist/RichText/issues/new??title=Feature%20request:feature-name&template=feature-request.md) 26 | 2. Suggest any update that would simplify the usage of the editor: [Request a new feature](https://github.com/webfashionist/RichText/issues/new??title=Feature%20request:feature-name&template=feature-request.md) 27 | 3. Fix or suggest a fix for issues/bugs that occur: [Submit a bug report](https://github.com/webfashionist/RichText/issues/new?template=bug-report.md) 28 | 29 | 30 | ## Ground rules & expectations 31 | 32 | Before we get started, here are a few expectations (and that you should expect from others): 33 | 34 | - Be kind and thoughtful in your conversations around this project. 35 | - Before opening a pull request, please ensure that you are working on the latest version of RichText. 36 | - If you open a pull request, please ensure that your contribution works in all current browsers. 37 | - The minified versions of the updated files should as well be kept up-to-date. 38 | 39 | 40 | ## How to contribute 41 | 42 | If you'd like to contribute, start by searching through the [issues](https://github.com/webfashionist/RichText/issues) and [pull requests](https://github.com/webfashionist/RichText/pulls) to see whether someone else has raised a similar idea or question. 43 | 44 | If you don't see your idea listed, and you think it fits into the goals of this guide, do one of the following: 45 | 46 | - If your contribution is **minor**, such as a typo fix, or self-contained, such as writing a translation, open a pull request. 47 | - If your contribution is **major**, such as a new guide, start by opening an issue first. That way, other people can weigh in on the discussion before you do any work. 48 | 49 | Have a look at [Quick start](#quick-start) to directly open a new issue. 50 | 51 | -------------------------------------------------------------------------------- /src/richtext.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | RichText: WYSIWYG editor developed as jQuery plugin 3 | 4 | @name RichText 5 | @author https://github.com/webfashionist - Bob Schockweiler - richtext@webfashion.eu 6 | 7 | Copyright (C) 2020 Bob Schockweiler ( richtext@webfashion.eu ) 8 | 9 | This program is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published 11 | by the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with this program. If not, see . 21 | */.richText{z-index:1;position:relative;background-color:#fafafa;border:#efefef solid 1px;color:#282828;width:100%}.richText .richText-form{font-family:Calibri,Verdana,Helvetica,sans-serif}.richText .richText-form label{display:block;padding:10px 15px}.richText .richText-form input[type=text],.richText .richText-form input[type=file],.richText .richText-form input[type=number],.richText .richText-form select{padding:10px 15px;border:#999 solid 1px;min-width:200px;width:100%}.richText .richText-form select{cursor:pointer}.richText .richText-form button{margin:10px 0;padding:10px 15px;background-color:#3498db;border:none;color:#fafafa;cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.richText .richText-toolbar{position:sticky;top:0;min-height:20px;background-color:inherit;border-bottom:#efefef solid 1px;z-index:1}.richText .richText-toolbar .richText-length{font-family:Verdana,Helvetica,sans-serif;font-size:13px;vertical-align:middle;line-height:34px}.richText .richText-toolbar .richText-length .black{color:#000}.richText .richText-toolbar .richText-length .orange{color:orange}.richText .richText-toolbar .richText-length .red{color:red}.richText .richText-toolbar .richText-length+.richText-wordcount{margin-left:10px}.richText .richText-toolbar .richText-wordcount{font-family:Verdana,Helvetica,sans-serif;font-size:13px;vertical-align:middle;line-height:34px}.richText .richText-toolbar ul{padding-left:0;padding-right:0;margin-top:0;margin-bottom:0}.richText .richText-toolbar ul li{float:left;display:block;list-style:none}.richText .richText-toolbar ul li a{display:block;padding:10px 13px;border-right:#efefef solid 1px;cursor:pointer;-webkit-transition:background-color .4s;-moz-transition:background-color .4s;transition:background-color .4s}.richText .richText-toolbar ul li a .fa,.richText .richText-toolbar ul li a .fas,.richText .richText-toolbar ul li a .far,.richText .richText-toolbar ul li a svg{pointer-events:none}.richText .richText-toolbar ul li a .richText-dropdown-outer{display:none;position:absolute;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.3);cursor:default}.richText .richText-toolbar ul li a .richText-dropdown-outer>.richText-dropdown-close{position:absolute;top:28px;left:calc(50% + 147px);background:#fff;border:#efefef solid 1px;border-radius:4px;color:#282828;cursor:pointer;font-size:20px;text-align:center;width:32px;-webkit-box-shadow:rgba(149,157,165,.2) 0 8px 24px;-moz-box-shadow:rgba(149,157,165,.2) 0 8px 24px;box-shadow:rgba(149,157,165,.2) 0 8px 24px}.richText .richText-toolbar ul li a .richText-dropdown-outer>.richText-dropdown-close:focus,.richText .richText-toolbar ul li a .richText-dropdown-outer>.richText-dropdown-close:hover{color:#3498db}.richText .richText-toolbar ul li a .richText-dropdown-outer .richText-dropdown{position:relative;display:block;margin:3% auto 0 auto;background-color:#fafafa;border:#efefef solid 1px;border-radius:4px;min-width:100px;width:300px;max-width:90%;-webkit-box-shadow:rgba(149,157,165,.2) 0 8px 24px;-moz-box-shadow:rgba(149,157,165,.2) 0 8px 24px;box-shadow:rgba(149,157,165,.2) 0 8px 24px;max-height:300px;overflow-y:auto;overflow-x:hidden}.richText .richText-toolbar ul li a .richText-dropdown-outer .richText-dropdown .richText-dropdown-close{position:absolute;top:0;right:-23px;background:#fff;color:#282828;cursor:pointer;font-size:20px;text-align:center;width:20px}.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown{list-style:none}.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li{display:block;float:none;font-family:Calibri,Verdana,Helvetica,sans-serif}.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li a{display:block;padding:10px 15px;border-bottom:#efefef solid 1px}.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li a:hover{background-color:#fff}.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li.inline{margin:10px 6px;float:left}.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li.inline a{display:block;padding:0;margin:0;border:none;-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%;-webkit-box-shadow:0 0 10px 0 #999;-moz-box-shadow:0 0 10px 0 #999;box-shadow:0 0 10px 0 #999}.richText .richText-toolbar ul li a .richText-dropdown-outer ul.richText-dropdown li.inline a span{display:block;height:30px;width:30px;-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%}.richText .richText-toolbar ul li a .richText-dropdown-outer div.richText-dropdown{padding:10px 15px}.richText .richText-toolbar ul li a:hover{background-color:#fff}.richText .richText-toolbar ul li[data-disable=true]{opacity:.1}.richText .richText-toolbar ul li[data-disable=true] a{cursor:default}.richText .richText-toolbar ul li:not([data-disable=true]).is-selected .richText-dropdown-outer{display:block}.richText .richText-toolbar ul:after{display:block;content:"";clear:both}.richText .richText-toolbar:last-child{font-size:12px;z-index:-1}.richText .richText-toolbar:after{display:block;clear:both;content:""}.richText .richText-editor{padding:20px;background-color:#fff;border-left:#fff solid 2px;font-family:Calibri,Verdana,Helvetica,sans-serif;height:300px;outline:none;overflow-y:scroll;overflow-x:auto}.richText .richText-editor[placeholder]:before{content:attr(placeholder);color:#555}.richText .richText-editor ul,.richText .richText-editor ol{margin:10px 25px}.richText .richText-editor table{margin:10px 0;border-spacing:0;width:100%}.richText .richText-editor table td,.richText .richText-editor table th{padding:10px;border:#efefef solid 1px}.richText .richText-editor:focus{border-left:#3498db solid 2px}.richText .richText-initial{margin-bottom:-4px;padding:10px;background-color:#282828;border:none;color:#3f3;font-family:Monospace,Calibri,Verdana,Helvetica,sans-serif;max-width:100%;min-width:100%;width:100%;min-height:400px;height:400px}.richText .richText-help{float:right;display:block;padding:10px 15px;cursor:pointer}.richText .richText-undo,.richText .richText-redo{float:left;display:block;padding:10px 15px;border-right:#efefef solid 1px;cursor:pointer}.richText .richText-undo.is-disabled,.richText .richText-redo.is-disabled{opacity:.4}.richText .richText-help-popup a{color:#3498db;text-decoration:underline}.richText .richText-help-popup hr{margin:10px auto 5px auto;border:none;border-top:#efefef solid 1px}.richText .richText-list.list-rightclick{position:absolute;background-color:#fafafa;border-right:#efefef solid 1px;border-bottom:#efefef solid 1px}.richText .richText-list.list-rightclick li{padding:5px 7px;cursor:pointer;list-style:none}/*# sourceMappingURL=richtext.min.css.map */ 22 | -------------------------------------------------------------------------------- /cypress/e2e/basics.cy.js: -------------------------------------------------------------------------------- 1 | describe('basic formatting', () => { 2 | const editorSelector = '.richText-editor'; 3 | const commandSelector = (command, options) => { 4 | const selector = `a[data-command="${command}"]`; 5 | if (options) { 6 | return `${selector}[data-option="${options}"]`; 7 | } 8 | return selector; 9 | }; 10 | 11 | const typeWithCommand = (command, text, options) => { 12 | cy.get(commandSelector(command, options)).click(); 13 | cy.get(editorSelector).type(text); 14 | }; 15 | 16 | beforeEach(() => { 17 | cy.visit('/examples/index.html'); 18 | cy.get(editorSelector).focus(); 19 | }); 20 | 21 | it('has correct init state', () => { 22 | cy.get(editorSelector).should('have.html', '

'); 23 | }); 24 | 25 | it('writes text unstyled by default', () => { 26 | cy.get(editorSelector).type('hello world'); 27 | cy.get(editorSelector).should('have.html', '
hello world
'); 28 | }); 29 | 30 | it('can write text in bold', () => { 31 | typeWithCommand('bold', 'hello world'); 32 | cy.get(editorSelector).should('have.html', '
hello world
'); 33 | }); 34 | 35 | it('can write text in italic', () => { 36 | typeWithCommand('italic', 'hello world'); 37 | cy.get(editorSelector).should('have.html', '
hello world
'); 38 | }); 39 | 40 | it('can write text in underline', () => { 41 | typeWithCommand('underline', 'hello world'); 42 | cy.get(editorSelector).should('have.html', '
hello world
'); 43 | }); 44 | 45 | it('can write text left-aligned', () => { 46 | cy.get(editorSelector).type('hello world'); 47 | cy.get(commandSelector('justifyRight')).click(); 48 | cy.get(commandSelector('justifyLeft')).click(); 49 | cy.get(editorSelector).should('have.html', '
hello world
'); 50 | }); 51 | 52 | it('can write text center-aligned', () => { 53 | cy.get(editorSelector).type('hello world'); 54 | cy.get(commandSelector('justifyCenter')).click(); 55 | cy.get(editorSelector).should('have.html', '
hello world
'); 56 | }); 57 | 58 | it('can write text right-aligned', () => { 59 | cy.get(editorSelector).type('hello world'); 60 | cy.get(commandSelector('justifyRight')).click(); 61 | cy.get(editorSelector).should('have.html', '
hello world
'); 62 | }); 63 | 64 | it('can write text fully-aligned', () => { 65 | cy.get(editorSelector).type('hello world'); 66 | cy.get(commandSelector('justifyFull')).click(); 67 | cy.get(editorSelector).should('have.html', '
hello world
'); 68 | }); 69 | 70 | it('can write ordered lists', () => { 71 | typeWithCommand('insertOrderedList', 'hello world'); 72 | cy 73 | .get(editorSelector) 74 | .should('have.html', '
  1. hello world
') 75 | .type('{enter}') 76 | .type('hello world2') 77 | .get(editorSelector) 78 | .should('have.html', '
  1. hello world
  2. hello world2
'); 79 | }); 80 | 81 | it('can write unorderd lists', () => { 82 | typeWithCommand('insertUnorderedList', 'hello world'); 83 | cy 84 | .get(editorSelector) 85 | .should('have.html', '
  • hello world
') 86 | .type('{enter}') 87 | .type('hello world2') 88 | .get(editorSelector) 89 | .should('have.html', '
  • hello world
  • hello world2
'); 90 | }); 91 | 92 | it('can remove formatting', () => { 93 | // check bold 94 | typeWithCommand('bold', 'hello world'); 95 | cy.get(editorSelector).should('have.html', '
hello world
'); 96 | cy.get(editorSelector).type('{selectall}'); 97 | cy.get(commandSelector('removeFormat')).click(); 98 | cy.get(editorSelector).should('have.html', '
hello world
'); 99 | // check italic 100 | cy.get(editorSelector).type('{selectall}'); 101 | typeWithCommand('italic', 'hello world'); 102 | cy.get(editorSelector).should('have.html', '
hello world
'); 103 | cy.get(editorSelector).type('{selectall}'); 104 | cy.get(commandSelector('removeFormat')).click(); 105 | cy.get(editorSelector).should('have.html', '
hello world
'); 106 | }); 107 | 108 | it('can write text with font-size', () => { 109 | for (let n = 24; n >= 12; n -= 2) { 110 | cy.get(editorSelector).type('{selectall}'); 111 | cy.get(commandSelector('removeFormat')).click(); 112 | cy.get(editorSelector).type(`hello world ${n}`); 113 | cy.get(editorSelector).type('{selectall}'); 114 | const buttonSelector = cy.get(`.fa-text-height`).parent(`a`); 115 | buttonSelector.click(); // open dropdown 116 | // check if dropdown is open 117 | buttonSelector.children(`.richText-dropdown-outer`).should('be.visible'); 118 | 119 | cy.get(`a[data-command="fontSize"][data-option="${n}"]`).click(); 120 | cy 121 | .get(editorSelector) 122 | .should('include.html', `
hello world ${n}
`) 123 | .type('{enter}'); 124 | } 125 | }); 126 | 127 | it('can write text with font-family', () => { 128 | const fontNames = [ 129 | 'Arial', 130 | 'Arial Black', 131 | 'Comic Sans MS', 132 | 'Courier New', 133 | 'Geneva', 134 | 'Georgia', 135 | 'Helvetica', 136 | 'Impact', 137 | 'Lucida Console', 138 | 'Tahoma', 139 | 'Times New Roman', 140 | 'Verdana' 141 | ]; 142 | 143 | for (const fontName of fontNames) { 144 | cy.get(editorSelector).type('{selectall}'); 145 | cy.get(commandSelector('removeFormat')).click(); 146 | cy.get(editorSelector).type(`hello world ${fontName}`); 147 | cy.get(editorSelector).type('{selectall}'); 148 | const buttonSelector = cy.get(`.fa-font`).parent(`a`); 149 | buttonSelector.click(); // open dropdown 150 | // check if dropdown is open 151 | buttonSelector.children(`.richText-dropdown-outer`).should('be.visible'); 152 | 153 | cy.get(`a[data-command="fontName"][data-option="${fontName}"]`).click(); 154 | cy 155 | .get(editorSelector) 156 | .should('include.html', `
hello world ${fontName}
`) 157 | .type('{enter}'); 158 | } 159 | }); 160 | 161 | it('can write text with font color', () => { 162 | const fontColors = [ 163 | '#FFFFFF', 164 | '#000000', 165 | '#7F6000', 166 | '#938953', 167 | '#1F497D', 168 | '#4F81BD', 169 | '#953734', 170 | '#4F6128', 171 | '#3F3151', 172 | '#31859B', 173 | '#4BACC6', 174 | '#E36C09', 175 | '#F79646', 176 | '#FFFF00' 177 | ]; 178 | 179 | for (const fontColor of fontColors) { 180 | cy.get(editorSelector).type('{selectall}'); 181 | cy.get(commandSelector('removeFormat')).click(); 182 | cy.get(editorSelector).type(`hello world ${fontColor}`); 183 | cy.get(editorSelector).type('{selectall}'); 184 | const buttonSelector = cy.get(`.fa-pencil`).parent(`a`); 185 | buttonSelector.click(); // open dropdown 186 | // check if dropdown is open 187 | buttonSelector.children(`.richText-dropdown-outer`).should('be.visible'); 188 | 189 | cy.get(`a[data-command="forecolor"][data-option="${fontColor}"]`).click(); 190 | cy 191 | .get(editorSelector) 192 | .should('include.html', `
hello world ${fontColor}
`) 193 | .type('{enter}'); 194 | } 195 | }); 196 | 197 | it('can write text with background color', () => { 198 | const backgroundColors = [ 199 | '#FFFFFF', 200 | '#000000', 201 | '#7F6000', 202 | '#938953', 203 | '#1F497D', 204 | 'blue', 205 | '#4F81BD', 206 | '#953734', 207 | 'red', 208 | '#4F6128', 209 | 'green', 210 | '#3F3151', 211 | '#31859B', 212 | '#4BACC6', 213 | '#E36C09', 214 | '#F79646', 215 | '#FFFF00' 216 | ]; 217 | 218 | function hexToRgb(hex) { 219 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 220 | return result ? { 221 | r: parseInt(result[1], 16), 222 | g: parseInt(result[2], 16), 223 | b: parseInt(result[3], 16) 224 | } : null; 225 | } 226 | 227 | for (const backgroundColor of backgroundColors) { 228 | cy.get(editorSelector).type('{selectall}'); 229 | cy.get(commandSelector('removeFormat')).click(); 230 | cy.get(editorSelector).type(`hello world ${backgroundColor}`); 231 | cy.get(editorSelector).type('{selectall}'); 232 | const buttonSelector = cy.get(`.fa-paint-brush`).parent(`a`); 233 | buttonSelector.click(); // open dropdown 234 | // check if dropdown is open 235 | buttonSelector.children(`.richText-dropdown-outer`).should('be.visible'); 236 | 237 | const matchingBackgroundColor = backgroundColor.startsWith('#') ? `rgb(${hexToRgb(backgroundColor).r}, ${hexToRgb(backgroundColor).g}, ${hexToRgb(backgroundColor).b})` : backgroundColor; 238 | 239 | cy.get(`a[data-command="hiliteColor"][data-option="${backgroundColor}"]`).click(); 240 | cy 241 | .get(editorSelector) 242 | .should('include.html', `
hello world ${backgroundColor}
`) 243 | .type('{enter}'); 244 | } 245 | }); 246 | 247 | /** 248 | * TODO: add more tests for: 249 | * - links 250 | * - images 251 | * - headings 252 | * - code 253 | * - files 254 | * - videos 255 | * - tables 256 | */ 257 | }); 258 | -------------------------------------------------------------------------------- /src/richtext.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | RichText: WYSIWYG editor developed as jQuery plugin 3 | 4 | @name RichText 5 | @author https://github.com/webfashionist - Bob Schockweiler - richtext@webfashion.eu 6 | 7 | Copyright (C) 2020 Bob Schockweiler ( richtext@webfashion.eu ) 8 | 9 | This program is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU Affero General Public License as published 11 | by the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU Affero General Public License for more details. 18 | 19 | You should have received a copy of the GNU Affero General Public License 20 | along with this program. If not, see . 21 | */ 22 | 23 | @use "richtext-colors.scss" as colors; 24 | 25 | .richText { 26 | z-index: 1; 27 | position: relative; 28 | background-color: colors.$alabaster; 29 | border: colors.$gallery solid 1px; 30 | color: colors.$mine-shaft; 31 | width: 100%; 32 | 33 | .richText-form { 34 | font-family: Calibri,Verdana,Helvetica,sans-serif; 35 | label { 36 | display: block; 37 | padding: 10px 15px; 38 | } 39 | 40 | input[type="text"], input[type="file"], input[type="number"], select { 41 | padding: 10px 15px; 42 | border: colors.$dusty-gray solid 1px; 43 | min-width: 200px; 44 | width: 100%; 45 | } 46 | 47 | select { 48 | cursor: pointer; 49 | } 50 | 51 | button { 52 | margin: 10px 0; 53 | padding: 10px 15px; 54 | background-color: colors.$highlight; 55 | border: none; 56 | color: colors.$alabaster; 57 | cursor: pointer; 58 | -webkit-appearance: none; 59 | -moz-appearance: none; 60 | appearance: none; 61 | -webkit-border-radius: 4px; 62 | -moz-border-radius: 4px; 63 | border-radius: 4px; 64 | } 65 | } 66 | 67 | .richText-toolbar { 68 | position: sticky; 69 | top: 0; 70 | min-height: 20px; 71 | background-color: inherit; 72 | border-bottom: colors.$gallery solid 1px; 73 | z-index: 1; 74 | 75 | .richText-length { 76 | font-family: Verdana, Helvetica, sans-serif; 77 | font-size: 13px; 78 | vertical-align: middle; 79 | line-height: 34px; 80 | .black { 81 | color: colors.$black; 82 | } 83 | .orange { 84 | color: colors.$orange; 85 | } 86 | .red { 87 | color: colors.$red; 88 | } 89 | + .richText-wordcount { 90 | margin-left: 10px; 91 | } 92 | } 93 | 94 | .richText-wordcount { 95 | font-family: Verdana, Helvetica, sans-serif; 96 | font-size: 13px; 97 | vertical-align: middle; 98 | line-height: 34px; 99 | } 100 | 101 | ul { 102 | padding-left: 0; 103 | padding-right: 0; 104 | margin-top: 0; 105 | margin-bottom: 0; 106 | 107 | li { 108 | float: left; 109 | display: block; 110 | list-style: none; 111 | 112 | a { 113 | display: block; 114 | padding: 10px 13px; 115 | border-right: colors.$gallery solid 1px; 116 | cursor: pointer; 117 | -webkit-transition: background-color 0.4s; 118 | -moz-transition: background-color 0.4s; 119 | transition: background-color 0.4s; 120 | 121 | .fa, .fas, .far, svg { 122 | pointer-events: none; 123 | } 124 | 125 | .richText-dropdown-outer { 126 | display: none; 127 | position: absolute; 128 | top: 0; 129 | left: 0; 130 | right: 0; 131 | bottom: 0; 132 | background-color: rgba(0,0,0,0.3); 133 | cursor: default; 134 | 135 | > .richText-dropdown-close { 136 | position: absolute; 137 | top: 28px; 138 | left: calc(50% + 147px); 139 | background: colors.$white; 140 | border: colors.$gallery solid 1px; 141 | border-radius: 4px; 142 | color: colors.$mine-shaft; 143 | cursor: pointer; 144 | font-size: 20px; 145 | text-align: center; 146 | width: 32px; 147 | -webkit-box-shadow: rgba(149, 157, 165, 0.2) 0 8px 24px; 148 | -moz-box-shadow: rgba(149, 157, 165, 0.2) 0 8px 24px; 149 | box-shadow: rgba(149, 157, 165, 0.2) 0 8px 24px; 150 | &:focus, &:hover { 151 | color: colors.$highlight; 152 | } 153 | } 154 | 155 | .richText-dropdown { 156 | position:relative; 157 | display: block; 158 | margin: 3% auto 0 auto; 159 | background-color: colors.$alabaster; 160 | border: colors.$gallery solid 1px; 161 | border-radius: 4px; 162 | min-width: 100px; 163 | width: 300px; 164 | max-width: 90%; 165 | -webkit-box-shadow: rgba(149, 157, 165, 0.2) 0 8px 24px; 166 | -moz-box-shadow: rgba(149, 157, 165, 0.2) 0 8px 24px; 167 | box-shadow: rgba(149, 157, 165, 0.2) 0 8px 24px; 168 | max-height: 300px; 169 | overflow-y: auto; 170 | overflow-x: hidden; 171 | 172 | .richText-dropdown-close { 173 | position: absolute; 174 | top: 0; 175 | right: -23px; 176 | background: colors.$white; 177 | color: colors.$mine-shaft; 178 | cursor: pointer; 179 | font-size: 20px; 180 | text-align: center; 181 | width: 20px; 182 | } 183 | } 184 | 185 | 186 | ul.richText-dropdown { 187 | list-style: none; 188 | 189 | li { 190 | display: block; 191 | float: none; 192 | font-family: Calibri,Verdana,Helvetica,sans-serif; 193 | 194 | a { 195 | display: block; 196 | padding: 10px 15px; 197 | border-bottom: colors.$gallery solid 1px; 198 | } 199 | 200 | a:hover { 201 | background-color: colors.$white; 202 | } 203 | } 204 | 205 | li.inline{ 206 | margin: 10px 6px; 207 | float: left; 208 | 209 | a { 210 | display: block; 211 | padding: 0; 212 | margin: 0; 213 | border: none; 214 | -webkit-border-radius: 50%; 215 | -moz-border-radius: 50%; 216 | border-radius: 50%; 217 | -webkit-box-shadow: 0 0 10px 0 colors.$dusty-gray; 218 | -moz-box-shadow: 0 0 10px 0 colors.$dusty-gray; 219 | box-shadow: 0 0 10px 0 colors.$dusty-gray; 220 | 221 | span { 222 | display: block; 223 | height: 30px; 224 | width: 30px; 225 | -webkit-border-radius: 50%; 226 | -moz-border-radius: 50%; 227 | border-radius: 50%; 228 | } 229 | } 230 | } 231 | } 232 | 233 | div.richText-dropdown { 234 | padding: 10px 15px; 235 | 236 | } 237 | } 238 | 239 | 240 | } 241 | 242 | a:hover { 243 | background-color: colors.$white; 244 | } 245 | } 246 | 247 | li[data-disable="true"] { 248 | opacity: 0.1; 249 | a { 250 | cursor: default; 251 | } 252 | } 253 | 254 | li:not([data-disable="true"]).is-selected .richText-dropdown-outer { 255 | display: block; 256 | } 257 | } 258 | 259 | ul:after { 260 | display: block; 261 | content: ""; 262 | clear: both; 263 | } 264 | } 265 | 266 | .richText-toolbar:last-child { 267 | font-size: 12px; 268 | z-index: -1; 269 | } 270 | 271 | .richText-toolbar:after{ 272 | display: block; 273 | clear: both; 274 | content:""; 275 | } 276 | 277 | .richText-editor { 278 | padding: 20px; 279 | background-color: colors.$white; 280 | border-left: colors.$white solid 2px; 281 | font-family: Calibri,Verdana,Helvetica,sans-serif; 282 | height: 300px; 283 | outline: none; 284 | overflow-y: scroll; 285 | overflow-x: auto; 286 | 287 | &[placeholder] { 288 | &:before { 289 | content: attr(placeholder); 290 | color: colors.$emperor; 291 | } 292 | } 293 | 294 | ul, ol { 295 | margin: 10px 25px; 296 | } 297 | 298 | table { 299 | margin:10px 0; 300 | border-spacing:0; 301 | width:100%; 302 | 303 | td, th { 304 | padding: 10px; 305 | border: colors.$gallery solid 1px; 306 | } 307 | } 308 | } 309 | 310 | 311 | .richText-editor:focus { 312 | border-left: colors.$highlight solid 2px; 313 | } 314 | 315 | .richText-initial { 316 | margin-bottom: -4px; 317 | padding: 10px; 318 | background-color: colors.$mine-shaft; 319 | border: none; 320 | color: colors.$harlequin; 321 | font-family: Monospace,Calibri,Verdana,Helvetica,sans-serif; 322 | max-width: 100%; 323 | min-width: 100%; 324 | width: 100%; 325 | min-height: 400px; 326 | height: 400px; 327 | } 328 | 329 | .richText-help { 330 | float: right; 331 | display: block; 332 | padding: 10px 15px; 333 | cursor: pointer; 334 | } 335 | 336 | .richText-undo, 337 | .richText-redo { 338 | float: left; 339 | display: block; 340 | padding: 10px 15px; 341 | border-right: colors.$gallery solid 1px; 342 | cursor: pointer; 343 | } 344 | .richText-undo.is-disabled, 345 | .richText-redo.is-disabled { 346 | opacity: 0.4; 347 | } 348 | 349 | .richText-help-popup { 350 | a { 351 | color: colors.$curious-blue; 352 | text-decoration:underline; 353 | } 354 | 355 | hr { 356 | margin: 10px auto 5px auto; 357 | border:none; 358 | border-top: colors.$gallery solid 1px; 359 | } 360 | } 361 | 362 | .richText-list.list-rightclick { 363 | position: absolute; 364 | background-color: colors.$alabaster; 365 | border-right: colors.$gallery solid 1px; 366 | border-bottom: colors.$gallery solid 1px; 367 | 368 | li { 369 | padding: 5px 7px; 370 | cursor: pointer; 371 | list-style: none; 372 | } 373 | } 374 | 375 | } 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RichText 2 | 3 | [![Cypress Tests](https://github.com/webfashionist/RichText/actions/workflows/test.yml/badge.svg)](https://github.com/webfashionist/RichText/actions/workflows/test.yml) 4 | [![Accessibility](https://github.com/webfashionist/RichText/actions/workflows/accessibility.yml/badge.svg)](https://github.com/webfashionist/RichText/actions/workflows/accessibility.yml) 5 | [![GitHub release](https://img.shields.io/github/v/release/webfashionist/RichText)](https://github.com/webfashionist/RichText/releases) 6 | 7 | 8 | WYSIWYG editor developed as jQuery plugin. 9 | 10 | ## Requirements 11 | 12 | The following libraries are required to use the RichText editor: 13 | 14 | - [jQuery](https://jquery.com/) (v.3+, v.3.2+ recommended) 15 | - FontAwesome ([v.4.7.0](https://fontawesome.com/v4.7.0/) / [v.5+](https://fontawesome.com/)) 16 | 17 | ## Install RichText 18 | 19 | ### NPM 20 | 21 | ```bash 22 | npm install @webfashionist/richtext 23 | ``` 24 | 25 | ### Yarn 26 | 27 | ```bash 28 | yarn add @webfashionist/richtext 29 | ``` 30 | 31 | 32 | ### Manually 33 | 34 | Download the latest release from the [releases page](https://github.com/webfashionist/RichText/releases) and include the files in your project. 35 | 36 | 37 | ## Initialize editor 38 | 39 | In order to use the RichText editor, you need to include the following files in your project: 40 | 41 | ```html 42 | 43 | 44 | ``` 45 | 46 | Simply call `.richText()` on your `jQuery('textarea')` or `jQuery('input')` field (other HTML tags are allowed as well, but not recommended). 47 | 48 | 49 | ## Options 50 | 51 | `.richText()` allows several options to be set, the default option object is: 52 | 53 | ```javascript 54 | $(element).richText({ 55 | 56 | // text formatting 57 | bold: true, 58 | italic: true, 59 | underline: true, 60 | 61 | // text alignment 62 | leftAlign: true, 63 | centerAlign: true, 64 | rightAlign: true, 65 | justify: true, 66 | 67 | // lists 68 | ol: true, 69 | ul: true, 70 | 71 | // title 72 | heading: true, 73 | 74 | // fonts 75 | fonts: true, 76 | fontList: [ 77 | "Arial", 78 | "Arial Black", 79 | "Comic Sans MS", 80 | "Courier New", 81 | "Geneva", 82 | "Georgia", 83 | "Helvetica", 84 | "Impact", 85 | "Lucida Console", 86 | "Tahoma", 87 | "Times New Roman", 88 | "Verdana" 89 | ], 90 | fontColor: true, 91 | backgroundColor: true, 92 | fontSize: true, 93 | fontSizes: undefined, 94 | 95 | // uploads 96 | imageUpload: true, 97 | fileUpload: true, 98 | 99 | // media 100 | videoEmbed: true, 101 | 102 | // link 103 | urls: true, 104 | 105 | // tables 106 | table: true, 107 | 108 | // code 109 | removeStyles: true, 110 | code: true, 111 | 112 | // colors 113 | colors: [], 114 | 115 | // dropdowns 116 | fileHTML: '', 117 | imageHTML: '', 118 | 119 | // translations 120 | translations: { 121 | 'title': 'Title', 122 | 'white': 'White', 123 | 'black': 'Black', 124 | 'brown': 'Brown', 125 | 'beige': 'Beige', 126 | 'darkBlue': 'Dark Blue', 127 | 'blue': 'Blue', 128 | 'lightBlue': 'Light Blue', 129 | 'darkRed': 'Dark Red', 130 | 'red': 'Red', 131 | 'darkGreen': 'Dark Green', 132 | 'green': 'Green', 133 | 'purple': 'Purple', 134 | 'darkTurquois': 'Dark Turquois', 135 | 'turquois': 'Turquois', 136 | 'darkOrange': 'Dark Orange', 137 | 'orange': 'Orange', 138 | 'yellow': 'Yellow', 139 | 'imageURL': 'Image URL', 140 | 'fileURL': 'File URL', 141 | 'linkText': 'Link text', 142 | 'url': 'URL', 143 | 'size': 'Size', 144 | 'responsive': 'Responsive', 145 | 'text': 'Text', 146 | 'openIn': 'Open in', 147 | 'sameTab': 'Same tab', 148 | 'newTab': 'New tab', 149 | 'align': 'Align', 150 | 'left': 'Left', 151 | 'justify': 'Justify', 152 | 'center': 'Center', 153 | 'right': 'Right', 154 | 'rows': 'Rows', 155 | 'columns': 'Columns', 156 | 'add': 'Add', 157 | 'pleaseEnterURL': 'Please enter an URL', 158 | 'videoURLnotSupported': 'Video URL not supported', 159 | 'pleaseSelectImage': 'Please select an image', 160 | 'pleaseSelectFile': 'Please select a file', 161 | 'bold': 'Bold', 162 | 'italic': 'Italic', 163 | 'underline': 'Underline', 164 | 'alignLeft': 'Align left', 165 | 'alignCenter': 'Align centered', 166 | 'alignRight': 'Align right', 167 | 'addOrderedList': 'Ordered list', 168 | 'addUnorderedList': 'Unordered list', 169 | 'addHeading': 'Heading/title', 170 | 'addFont': 'Font', 171 | 'addFontColor': 'Font color', 172 | 'addBackgroundColor': 'Background color', 173 | 'addFontSize': 'Font size', 174 | 'addImage': 'Add image', 175 | 'addVideo': 'Add video', 176 | 'addFile': 'Add file', 177 | 'addURL': 'Add URL', 178 | 'addTable': 'Add table', 179 | 'removeStyles': 'Remove styles', 180 | 'code': 'Show HTML code', 181 | 'undo': 'Undo', 182 | 'redo': 'Redo', 183 | 'close': 'Close', 184 | 'save': 'Save', 185 | 'words': 'word(s)' 186 | }, 187 | 188 | // privacy 189 | youtubeCookies: false, 190 | 191 | // preview 192 | preview: false, 193 | 194 | // placeholder 195 | placeholder: '', 196 | 197 | // developer settings 198 | useSingleQuotes: false, 199 | height: 0, 200 | heightPercentage: 0, 201 | adaptiveHeight: false, 202 | id: "", 203 | class: "", 204 | useParagraph: false, 205 | maxlength: 0, 206 | maxlengthIncludeHTML: false, 207 | wordCount: false, 208 | callback: undefined, 209 | useTabForNext: false, 210 | save: false, 211 | saveCallback: undefined, 212 | saveOnBlur: 0, 213 | undoRedo: true, 214 | disableScripts: false, 215 | DOMPurify: false 216 | }); 217 | ``` 218 | 219 | **Text formatting** 220 | - `bold` (default: `(boolean) true`) :: Defines if the bold button should be displayed in the editor toolbar 221 | - `italic` (default: `(boolean) true`) :: Defines if the italic button should be displayed 222 | - `underline` (default: `(boolean) true`) :: Displays the underline button 223 | 224 | **Fonts** 225 | - `fonts` (default: `(boolean) true`) :: Enables font formatting 226 | - `fontList` :: Array of allowed fonts. The fonts set by default are fonts which should work on Windows, Mac and Linux by default. Setting fonts manually will overwrite the array. 227 | - `fontSize` (default: `(boolean) true`) :: Allows to use different font sizes 228 | - `fontSizes` (default: `undefined`) :: Array of allowed font sizes. If the default value is being used, the editor will provide a default set of font sizes. Setting font sizes manually will overwrite the default array. Example: `fontSizes: [10, 12, 14, 16]` 229 | 230 | **Text alignment** 231 | - `leftAlign` (default: `(boolean) true`) 232 | - `centerAlign` (default: `(boolean) true`) 233 | - `rightAlign` (default: `(boolean) true`) 234 | - `justify` (default: `(boolean) true`) 235 | 236 | **Lists** 237 | - `ol` (default: `(boolean) true`) :: Ordered list 238 | - `ul` (default: `(boolean) true`) :: Unordered list 239 | 240 | **Titles** 241 | - `heading` (default: `(boolean) true`) 242 | 243 | **Colors** 244 | - `fontColor` (default: `(boolean) true`) 245 | - `backgroundColor` (default: `(boolean) true`) 246 | - `colors` :: Set own colors for the editor. They will replace the default colors. Example: 247 | 248 | ```javascript 249 | var colors; 250 | colors["#FFFFFF"] = 'White'; 251 | colors["#000000"] = 'Black'; 252 | ``` 253 | 254 | **Uploads/Files** 255 | - `imageUpload` (default: `(boolean) true`) 256 | - `fileUpload` (default: `(boolean) true`) 257 | 258 | **Media/Videos** 259 | - `videoEmbed` (default: `(boolean) true`) :: Simplify embedding videos from YouTube, Facebook, Vimeo and Dailymotion 260 | 261 | **Links** 262 | - `urls` (default: `(boolean) true`) 263 | 264 | **Tables** 265 | - `table` (default: `(boolean) true`) 266 | 267 | **Code** 268 | - `removeStyles` (default: `(boolean) true`) :: Allows to remove the CSS styles from the selection 269 | - `code` (default: `(boolean) true`) :: Allows to display the raw HTML code 270 | 271 | **Custom dropdowns** 272 | 273 | Custom dropdowns allow to customize in a restricted way the dropdowns in the editor. 274 | 275 | - `fileHTML` :: HTML string of the file dropdown. MUST include an input field (`select`, `input` or `textarea`) with the `id` equal to `fileURL`. 276 | - `imageHTML` :: HTML string of the image dropdown. MUST include an input field (`select`, `input` or `textarea`) with the `id` equal to `imageURL`. 277 | 278 | **Translations** 279 | 280 | - `translations` :: An object of key-value entries allowing to set other texts/translations for the editor. The keys must stay the same as in the default translation object. 281 | 282 | **Privacy settings** 283 | 284 | - `youtubeCookies` (default: `(boolean) false`) :: If set to true, YouTube might set tracking cookies. By default (if the value is set to `false`), `youtube-nocookie.com` will be used to display YouTube videos. 285 | 286 | **Preview** 287 | 288 | - `preview` (default: `(boolean) false`) :: If set to true, the `contenteditable` property is set to `false` and the toolbar buttons are not loaded. 289 | 290 | **Placeholder** 291 | 292 | - `placeholder` (default: `(string) ''`) :: If a non-empty string is set, a placeholder will be shown if no text has been written in the `contenteditable` part of the editor. HTML code without text is considered empty and displays the placeholder. 293 | 294 | **Tabbing** 295 | 296 | - `useTabForNext` (default: `(boolean) false`) :: If set to true, you can tab to the next input element or RichText editor within the `contenteditable` part of the editor. 297 | 298 | **Developer settings** 299 | 300 | - `useSingleQuotes` (default: `(boolean) false`) :: Replaces all double quotes from HTML attributes to single quotes, if set to `(boolean) true`. 301 | - `height` (default: `(int) 0`) :: Sets a custom height for the editor frame and code view. The default value `0` uses the initial height set with CSS. To overwrite the height without using this setting (and without using inline CSS), use the CSS selectors `.richText .richText-editor` and `.richText .richText-initial` to change the height. 302 | - `heightPercentage` (default: `(int) 0`) :: Sets a custom percentage height based on the editor's parent element. This won't work if the `height` option is used as well. 303 | - `adaptiveHeight` (default: `(boolean) false`) :: If `true`, the height of the editor will be adapted based on the height of the content. 304 | - `id` (default: `(string) ""`) :: Sets a custom ID for the editor 305 | - `class` (default: `(string) ""`) :: Sets additional custom classes for the editor 306 | - `useParagraph` (default: `(boolean) false`) :: Uses paragraph tags instead of div containers (browser default) when pressing ENTER, if set to `true`. 307 | - `maxlength` (default: `(int) 0`) :: Defines a max length for the text (HTML length not considered!). The default value `0` doesn't define any limit 308 | - `maxlengthIncludeHTML` (default: `(boolean) false`) :: If `true`, the length of the HTML code will be used instead of only the written text 309 | - `wordCount` (default: `(boolean) false`) :: If set to `true`, a word count will be displayed below the editor 310 | - `callback` (default: `undefined`) :: Sets a callback if the editor has been loaded. The first and only parameter of the callback contains the jQuery element of the editor 311 | - `save`: (default: `(boolean) false`) :: If set to `true`, an additional icon to save the content manually will be added 312 | - `saveOnBlur`: (default: `(int) 0`) :: If set to a value greater than 0, the editor will be saved after the given amount of milliseconds after the last change. The default value `0` disables this feature. 313 | - `saveCallback`: (default: `undefined`) :: If a function is set, it will be called after blur (see `saveOnBlur`) or when the save action is being triggered (see `save`). See [Saving the content](#saving-the-content) for more information. 314 | - `undoRedo`: (default `(boolean) true`) :: If set to `false`, the undo/redo buttons will be hidden 315 | - `disableScripts`: (default `(boolean) false`) :: If set to `true`, all ` 382 | ``` 383 | 384 | With this basic example code, the content of the editor will be set to the POST parameter `example`. 385 | 386 | PHP: 387 | you can access the content with `$_POST['example']` (careful to properly sanitize any content!). 388 | 389 | Node.js: 390 | you can access the content with `req.body.example` (careful to properly sanitize any content!). 391 | 392 | 393 | ## Events 394 | 395 | The `.richText-editor` element listens to specific events in order to handle or modify the content of the editor. 396 | 397 | ### Clear content 398 | 399 | If you want to clear the content of the editor, you can trigger the `clear` event on the `.richText-editor` element. 400 | 401 | *As noted in [#12](https://github.com/webfashionist/RichText/issues/12) the editor is considered empty if it contains `

` as content. 402 | If you need to check if the editor is empty in the backend or frontend, you should strip `
`, `

` and `
` tags and check for the length of the content.* 403 | 404 | **Example:** 405 | 406 | ```javascript 407 | $('.richText-editor').trigger('clear'); 408 | ``` 409 | 410 | ### Add content at caret position 411 | 412 | If you want to add content at the caret position, you can trigger the `pasteAtCaret` event on the `.richText-editor` element. 413 | 414 | **Example:** 415 | 416 | ```javascript 417 | $('.richText-editor').trigger('pasteAtCaret', '

Some content
'); 418 | ``` 419 | 420 | Note: If the editor is currently not focused/the caret not active, the editor will try to restore the last caret position. 421 | If the caret position is not available, the content will be added at the start of the available editor content. 422 | 423 | ### Set content 424 | 425 | If you want to set the content of the editor, you can trigger the `setContent` event on the `.richText-editor` element. 426 | 427 | **Example:** 428 | 429 | ```javascript 430 | $('.richText-editor').trigger('setContent', '
Some content
'); 431 | ``` 432 | 433 | ### Get content 434 | 435 | If you want to get the content of the editor, you can trigger the `getContent` event on the `.richText-editor` element. 436 | This event requires a callback function as second parameter, which will be called with the content as second parameter. 437 | 438 | This event is similar to the `save` event, but it doesn't trigger the `saveCallback` function (see [Save callback](#save-callback)) and is therefore more variable to use for each event trigger. 439 | 440 | **Example:** 441 | 442 | ```javascript 443 | $('.richText-editor').trigger('getContent', function (event, content) { 444 | console.log(content); 445 | }); 446 | ``` 447 | 448 | 449 | ### Save 450 | 451 | If you want to save the content of the editor, you can trigger the `save` event on the `.richText-editor` element. 452 | The `saveCallback` function (see [Save callback](#save-callback)) will be called and the `change` event will be triggered on the editor element with the class `.richText-editor`. 453 | 454 | **Example:** 455 | 456 | ```javascript 457 | $('.richText-editor').trigger('save'); 458 | ``` 459 | 460 | ### Destroy / Undo RichText 461 | 462 | If you want to destroy the editor, you can trigger the `destroy` event on the `.richText-editor` element. 463 | The editor will be removed and the textarea element will be restored to its initial state keeping the current value set. 464 | 465 | **Example:** 466 | 467 | ```javascript 468 | $('.richText-editor').trigger('destroy'); 469 | ``` 470 | 471 | You can also set a delay value in milliseconds to delay the destruction of the editor. 472 | 473 | **Example:** 474 | 475 | ```javascript 476 | $('.richText-editor').trigger('destroy', {delay: 2000}); 477 | ``` 478 | 479 | Additionally the `callback` option is available as well and will provide the jQuery element of the remaining `"));$inputElement=$('[data-richtext="init"]');$inputElement.removeAttr("data-richtext")}else if($inputElement.html()){value=$inputElement.html();attributes=$inputElement.prop("attributes");$.each(attributes,function(){if(this.name){attributes_html+=" "+this.name+'="'+this.value+'"'}});$inputElement.replaceWith($("'+value+""));$inputElement=$('[data-richtext="init"]');$inputElement.removeAttr("data-richtext")}else{attributes=$inputElement.prop("attributes");$.each(attributes,function(){if(this.name){attributes_html+=" "+this.name+'="'+this.value+'"'}});$inputElement.replaceWith($("'));$inputElement=$('[data-richtext="init"]');$inputElement.removeAttr("data-richtext")}$editor=$("
",{class:"richText"});var $toolbar=$("
",{class:"richText-toolbar"});var $editorView=$("
",{class:"richText-editor",id:editorID,contenteditable:!settings.preview});$editorView.on("clear",()=>{var $editor=$("#"+editorID);$editor.siblings(".richText-initial").val("

");$editor.html($editor.siblings(".richText-initial").val())});$editorView.on("setContent",(event,content)=>{var $editor=$("#"+editorID);$editor.siblings(".richText-initial").val(content);$editor.html($editor.siblings(".richText-initial").val())});$editorView.on("getContent",(event,callback)=>{if(typeof callback!=="function"){return}callback($editorView.siblings(".richText-initial").val())});$editorView.on("save",(event,callback)=>{$editorView.trigger("change");if(typeof settings.saveCallback==="function"){settings.saveCallback($editor,"save",getEditorContent(editorID))}});$editorView.on("pasteAtCaret",(event,content)=>{var $editor=$("#"+editorID);if(!content||!content.length){return}restoreSelection($editor.attr("id"));pasteHTMLAtCaret(content);if(settings.disableScripts){removeScripts($editor.attr("id"))}if(settings.DOMPurify){sanitizeContent($editor.attr("id"))}updateTextarea()});$editorView.on("destroy",(event,options)=>{const destroy=()=>{let $main=$editorView.parents(".richText");$main.find(".richText-toolbar").remove();$main.find(".richText-editor").remove();const $textarea=$main.find(".richText-initial");$textarea.unwrap(".richText").data("editor","richText").removeClass("richText-initial").show();if(options&&typeof options.callback==="function"){options.callback($textarea)}};if(options&&options.delay){setTimeout(()=>{destroy()},options.delay);return}destroy()});var tabindex=$inputElement.prop("tabindex");if(tabindex>=0&&settings.useTabForNext===true){$editorView.attr("tabindex",tabindex)}if(!settings.preview){$toolbar.append($toolbarList)}if(settings.placeholder){if(!$editorView.text().length){$editorView.attr("placeholder",settings.placeholder);$editorView.on("focus",function(){$editorView.removeAttr("placeholder")});$editorView.on("focusout blur",function(){if(this.hasAttribute("placeholder")){return}if($(this).text().length){return}$(this).attr("placeholder",settings.placeholder)})}}settings.$editor=$editor;settings.blurTriggered=false;settings.$editor.on("click",()=>{settings.blurTriggered=false});if(settings.bold===true){$toolbarList.append($toolbarElement.clone().append($btnBold))}if(settings.italic===true){$toolbarList.append($toolbarElement.clone().append($btnItalic))}if(settings.underline===true){$toolbarList.append($toolbarElement.clone().append($btnUnderline))}if(settings.leftAlign===true){$toolbarList.append($toolbarElement.clone().append($btnLeftAlign))}if(settings.centerAlign===true){$toolbarList.append($toolbarElement.clone().append($btnCenterAlign))}if(settings.rightAlign===true){$toolbarList.append($toolbarElement.clone().append($btnRightAlign))}if(settings.justify===true){$toolbarList.append($toolbarElement.clone().append($btnJustify))}if(settings.ol===true){$toolbarList.append($toolbarElement.clone().append($btnOL))}if(settings.ul===true){$toolbarList.append($toolbarElement.clone().append($btnUL))}if(settings.fonts===true&&settings.fontList.length>0){$toolbarList.append($toolbarElement.clone().append($btnFont))}if(settings.fontSize===true){$toolbarList.append($toolbarElement.clone().append($btnFontSize))}if(settings.heading===true){$toolbarList.append($toolbarElement.clone().append($btnHeading))}if(settings.fontColor===true){$toolbarList.append($toolbarElement.clone().append($btnFontColor))}if(settings.backgroundColor===true){$toolbarList.append($toolbarElement.clone().append($btnBackgroundColor))}if(settings.imageUpload===true){$toolbarList.append($toolbarElement.clone().append($btnImageUpload))}if(settings.fileUpload===true){$toolbarList.append($toolbarElement.clone().append($btnFileUpload))}if(settings.videoEmbed===true){$toolbarList.append($toolbarElement.clone().append($btnVideoEmbed))}if(settings.urls===true){$toolbarList.append($toolbarElement.clone().append($btnURLs))}if(settings.table===true){$toolbarList.append($toolbarElement.clone().append($btnTable))}if(settings.removeStyles===true){$toolbarList.append($toolbarElement.clone().append($btnRemoveStyles))}if(settings.code===true){$toolbarList.append($toolbarElement.clone().append($btnCode))}if(settings.save===true){$toolbarList.append($toolbarElement.clone().append($btnSave))}$editorView.html($inputElement.val());$editorView.data("content-val",$inputElement.val());$editor.append($toolbar);$editor.append($editorView);$editor.append($inputElement.clone().hide());$inputElement.replaceWith($editor);$bottomToolbar=$("
",{class:"richText-toolbar"});if(!settings.preview&&settings.undoRedo){$bottomToolbar.append($("",{class:"richText-undo is-disabled",html:'',title:settings.translations.undo,name:"undo","aria-label":settings.translations.undo,"aria-role":"button"}));$bottomToolbar.append($("",{class:"richText-redo is-disabled",html:'',title:settings.translations.redo,name:"redo","aria-label":settings.translations.redo,"aria-role":"button"}))}$bottomToolbar.append($("",{class:"richText-help","aria-label":settings.translations.help,"aria-role":"button",title:settings.translations.help,name:"help",html:''}));$editor.append($bottomToolbar);var maxlength=settings.maxlength;if(!maxlength&&$inputElement.attr("maxlength")){maxlength=$inputElement.attr("maxlength")}if(maxlength>0){$editor.data("maxlength",maxlength);$editor.children(".richText-toolbar").children(".richText-help").before($("",{class:"richText-length",text:"0/"+maxlength}));updateMaxLength($editor.find(".richText-editor").attr("id"))}if(settings.wordCount){$editor.children(".richText-toolbar").children(".richText-help").before($("",{class:"richText-wordcount",text:"0 "+settings.translations.words}));updateWordCount($editor.find(".richText-editor").attr("id"))}if(settings.height&&settings.height>0){$editor.children(".richText-editor, .richText-initial").css({"min-height":settings.height+"px",height:settings.adaptiveHeight?"auto":settings.height+"px"})}else if(settings.heightPercentage&&settings.heightPercentage>0){var parentHeight=$editor.parent().innerHeight();var height=settings.heightPercentage/100*parentHeight;height-=$toolbar.outerHeight()*2;height-=parseInt($editor.css("margin-top"));height-=parseInt($editor.css("margin-bottom"));height-=parseInt($editor.find(".richText-editor").css("padding-top"));height-=parseInt($editor.find(".richText-editor").css("padding-bottom"));$editor.children(".richText-editor, .richText-initial").css({"min-height":height+"px",height:settings.adaptiveHeight?"auto":height+"px"})}else if(settings.adaptiveHeight){$editor.children(".richText-editor, .richText-initial").css({height:"auto"})}if(settings.class){$editor.addClass(settings.class)}if(settings.id){$editor.attr("id",settings.id)}fixFirstLine();history[editorID].push($editor.find("textarea").val());if(settings.callback&&typeof settings.callback==="function"){settings.callback($editor)}}init();settings.$editor.find(".richText-help").on("click",function(){var $editor=$(this).parents(".richText");if($editor){var $outer=$("
",{class:"richText-help-popup",style:"position:absolute;top:0;right:0;bottom:0;left:0;background-color: rgba(0,0,0,0.3);"});var $inner=$("
",{style:"position:relative;margin:60px auto;padding:20px;background-color:#FAFAFA;width:70%;font-family:Calibri,Verdana,Helvetica,sans-serif;font-size:small;"});var $content=$("
",{html:''});$content.append('

RichText

');$content.append('

Powered by
webfashionist/RichText (Github)
License: AGPL-3.0');$outer.append($inner.append($content));$editor.append($outer);$outer.on("click","#closeHelp",function(){$(this).parents(".richText-help-popup").remove()})}});settings.$editor.find(".richText-undo, .richText-redo").on("click",function(){var $this=$(this);if($this.hasClass("richText-undo")&&!$this.hasClass("is-disabled")){undo(settings.$editor)}else if($this.hasClass("richText-redo")&&!$this.hasClass("is-disabled")){redo(settings.$editor)}});settings.$editor.find(".richText-editor").on("input change blur keydown keyup",function(e){if((e.keyCode===9||e.keyCode==="9")&&e.type==="keydown"){if(settings.useTabForNext===true){focusNextElement();return false}e.preventDefault();tabifyEditableTable(window,e);return false}fixFirstLine();if(settings.disableScripts){removeScripts($(this).attr("id"))}if(settings.DOMPurify){sanitizeContent($(this).attr("id"))}updateTextarea(e);doSave($(this).attr("id"));updateMaxLength($(this).attr("id"));updateWordCount($(this).attr("id"))});function sanitizeContent(editorID){if(!window.DOMPurify||typeof window.DOMPurify.sanitize!=="function"){console.error("RichText: DOMPurify is not loaded. Please include it in your project.");return}const $editor=$("#"+editorID);const html=$editor.html();const clean=window.DOMPurify.sanitize(html,{USE_PROFILES:{html:true}});$editor.html(clean);const $editorInitial=$editor.parent().find(".richText-initial");$editorInitial.val(window.DOMPurify.sanitize($editorInitial.val(),{USE_PROFILES:{html:true}}))}function removeScripts(editorID){const regex=/)<[^<]*)*<\/script\s*>/gi;const regex2=/\s+[a-z]+=['"]javascript:.*['"]/gi;const $editor=$("#"+editorID);const $editorInitial=$editor.parent().find(".richText-initial");$editorInitial.val($editorInitial.val().replace(regex,""));$editorInitial.val($editorInitial.val().replace(regex2,""));const html=$editor.html();const $div=$("
").html(html);$div.html($div.html().replace(regex,""));$div.html($div.html().replace(regex2,""));$editor.html($div.html())}settings.$editor.find(".richText-editor").on("contextmenu",function(e){var $list=$("
    ",{class:"list-rightclick richText-list"});var $li=$("
  • ");$(".richText-editor").find(".richText-editNode").removeClass("richText-editNode");var $target=$(e.target);var $richText=$target.parents(".richText");var $toolbar=$richText.find(".richText-toolbar");var positionX=e.pageX-$richText.offset().left;var positionY=e.pageY-$richText.offset().top;$list.css({top:positionY,left:positionX});if($target.prop("tagName")==="A"){e.preventDefault();$list.append($li.clone().html(''));$target.parents(".richText").append($list);$list.find(".fa-link").on("click",function(){$(".list-rightclick.richText-list").remove();$target.addClass("richText-editNode");var $popup=$toolbar.find("#richText-URL");$popup.find("input#url").val($target.attr("href"));$popup.find("input#urlText").val($target.text());$popup.find("select#openIn").val($target.attr("target"));$toolbar.find(".richText-btn").children(".fa-link").parents("li").addClass("is-selected")});return false}else if($target.prop("tagName")==="IMG"){e.preventDefault();$list.append($li.clone().html(''));$target.parents(".richText").append($list);$list.find(".fa-image").on("click",function(){var align;if($target.parent("div").length>0&&$target.parent("div").attr("style")==="text-align:center;"){align="center"}else{align=$target.attr("align")}$(".list-rightclick.richText-list").remove();$target.addClass("richText-editNode");var $popup=$toolbar.find("#richText-Image");$popup.find("input#imageURL").val($target.attr("src"));$popup.find("input#altText").val($target.attr("alt"));$popup.find("select#align").val(align);$toolbar.find(".richText-btn").children(".fa-image").parents("li").addClass("is-selected")});return false}});settings.$editor.find(".richText-initial").on("input change blur",function(){if(settings.useSingleQuotes===true){$(this).val(changeAttributeQuotes($(this).val()))}var editorID=$(this).siblings(".richText-editor").attr("id");updateEditor(editorID);doSave(editorID);updateMaxLength(editorID);updateWordCount(editorID)});settings.$editor.find(".richText-editor").on("dblclick mouseup",function(){doSave($(this).attr("id"))});settings.$editor.find("#richText-Video button.btn").on("click",function(event){event.preventDefault();var $button=$(this);var $form=$button.parent(".richText-form-item").parent(".richText-form");if($form.attr("data-editor")===editorID){var url=$form.find("input#videoURL").val();var size=$form.find("select#size").val();if(!url){$form.prepend($("
    ",{style:"color:red;display:none;",class:"form-item is-error",text:settings.translations.pleaseEnterURL}));$form.children(".form-item.is-error").slideDown();setTimeout(function(){$form.children(".form-item.is-error").slideUp(function(){$(this).remove()})},5e3)}else{var html="";html=getVideoCode(url,size);if(!html){$form.prepend($("
    ",{style:"color:red;display:none;",class:"form-item is-error",text:settings.translations.videoURLnotSupported}));$form.children(".form-item.is-error").slideDown();setTimeout(function(){$form.children(".form-item.is-error").slideUp(function(){$(this).remove()})},5e3)}else{if(settings.useSingleQuotes===true){}else{}restoreSelection(editorID,true);pasteHTMLAtCaret(html);updateTextarea();$form.find("input#videoURL").val("");$(".richText-toolbar li.is-selected").removeClass("is-selected")}}}});$(document).on("mousedown",function(e){var $target=$(e.target);if(!$target.hasClass("richText-list")&&$target.parents(".richText-list").length===0){$(".richText-list.list-rightclick").remove();if(!$target.hasClass("richText-form")&&$target.parents(".richText-form").length===0){$(".richText-editNode").each(function(){var $this=$(this);$this.removeClass("richText-editNode");if($this.attr("class")===""){$this.removeAttr("class")}})}}if($target.prop("tagName")==="IMG"&&$target.parents("#"+editorID).length){startX=e.pageX;startY=e.pageY;startW=$target.innerWidth();startH=$target.innerHeight();var left=$target.offset().left;var right=$target.offset().left+$target.innerWidth();var bottom=$target.offset().top+$target.innerHeight();var top=$target.offset().top;var resize=false;$target.css({cursor:"default"});if(startY<=bottom&&startY>=bottom-20&&startX>=right-20&&startX<=right){$resizeImage=$target;$resizeImage.css({cursor:"nwse-resize"});resize=true}if((resize===true||$resizeImage)&&!$resizeImage.data("width")){$resizeImage.data("width",$target.parents("#"+editorID).innerWidth());$resizeImage.data("height",$target.parents("#"+editorID).innerHeight()*3);e.preventDefault()}else if(resize===true||$resizeImage){e.preventDefault()}else{$resizeImage=null}}});$(document).mouseup(function(){if($resizeImage){$resizeImage.css({cursor:"default"})}$resizeImage=null}).mousemove(function(e){if($resizeImage!==null){var maxWidth=$resizeImage.data("width");var currentWidth=$resizeImage.width();var maxHeight=$resizeImage.data("height");var currentHeight=$resizeImage.height();if(startW+e.pageX-startX<=maxWidth&&startH+e.pageY-startY<=maxHeight){$resizeImage.innerWidth(startW+e.pageX-startX);updateTextarea()}else if(startW+e.pageX-startX<=currentWidth&&startH+e.pageY-startY<=currentHeight){$resizeImage.innerWidth(startW+e.pageX-startX);updateTextarea()}}});settings.$editor.find("#richText-URL button.btn").on("click",function(event){event.preventDefault();var $button=$(this);var $form=$button.parent(".richText-form-item").parent(".richText-form");if($form.attr("data-editor")===editorID){var url=$form.find("input#url").val();var text=$form.find("input#urlText").val();var target=$form.find("#openIn").val();if(!target){target="_self"}if(!text){text=url}if(!url){$form.prepend($("
    ",{style:"color:red;display:none;",class:"form-item is-error",text:settings.translations.pleaseEnterURL}));$form.children(".form-item.is-error").slideDown();setTimeout(function(){$form.children(".form-item.is-error").slideUp(function(){$(this).remove()})},5e3)}else{var html="";if(settings.useSingleQuotes===true){html=""+text+""}else{html=''+text+""}restoreSelection(editorID,false,true);var $editNode=$(".richText-editNode");if($editNode.length>0&&$editNode.prop("tagName")==="A"){$editNode.attr("href",url);$editNode.attr("target",target);$editNode.text(text);$editNode.removeClass("richText-editNode");if($editNode.attr("class")===""){$editNode.removeAttr("class")}}else{pasteHTMLAtCaret(html)}$form.find("input#url").val("");$form.find("input#urlText").val("");$(".richText-toolbar li.is-selected").removeClass("is-selected")}}});settings.$editor.find("#richText-Image button.btn").on("click",function(event){event.preventDefault();var $button=$(this);var $form=$button.parent(".richText-form-item").parent(".richText-form");if($form.attr("data-editor")===editorID){var url=$form.find("#imageURL").val();var align=$form.find("select#align").val();var alt=$form.find("#altText").val();if(!align){align="center"}if(!url){$form.prepend($("
    ",{style:"color:red;display:none;",class:"form-item is-error",text:settings.translations.pleaseSelectImage}));$form.children(".form-item.is-error").slideDown();setTimeout(function(){$form.children(".form-item.is-error").slideUp(function(){$(this).remove()})},5e3)}else{var html="";if(settings.useSingleQuotes===true){if(align==="center"){html="
    "+alt+"
    "}else{html=""+alt+""}}else{if(align==="center"){html='
    '+alt+'
    '}else{html=''+alt+''}}restoreSelection(editorID,true);var $editNode=$(".richText-editNode");if($editNode.length>0&&$editNode.prop("tagName")==="IMG"){$editNode.attr("src",url);if($editNode.parent("div").length>0&&$editNode.parent("div").attr("style")==="text-align:center;"&&align!=="center"){$editNode.unwrap("div");$editNode.attr("align",align)}else if(($editNode.parent("div").length===0||$editNode.parent("div").attr("style")!=="text-align:center;")&&align==="center"){$editNode.wrap('
    ');$editNode.removeAttr("align")}else{$editNode.attr("align",align)}$editNode.removeClass("richText-editNode");if($editNode.attr("class")===""){$editNode.removeAttr("class")}}else{pasteHTMLAtCaret(html)}$form.find("input#imageURL").val("");$(".richText-toolbar li.is-selected").removeClass("is-selected")}}});settings.$editor.find("#richText-File button.btn").on("click",function(event){event.preventDefault();var $button=$(this);var $form=$button.parent(".richText-form-item").parent(".richText-form");if($form.attr("data-editor")===editorID){var url=$form.find("#fileURL").val();var text=$form.find("#fileText").val();if(!text){text=url}if(!url){$form.prepend($("
    ",{style:"color:red;display:none;",class:"form-item is-error",text:settings.translations.pleaseSelectFile}));$form.children(".form-item.is-error").slideDown();setTimeout(function(){$form.children(".form-item.is-error").slideUp(function(){$(this).remove()})},5e3)}else{var html="";if(settings.useSingleQuotes===true){html=""+text+""}else{html=''+text+""}restoreSelection(editorID,true);pasteHTMLAtCaret(html);$form.find("input#fileURL").val("");$form.find("input#fileText").val("");$(".richText-toolbar li.is-selected").removeClass("is-selected")}}});settings.$editor.find("#richText-Table button.btn").on("click",function(event){event.preventDefault();var $button=$(this);var $form=$button.parent(".richText-form-item").parent(".richText-form");if($form.attr("data-editor")===editorID){var rows=$form.find("input#tableRows").val();var columns=$form.find("input#tableColumns").val();if(!rows||rows<=0){rows=2}if(!columns||columns<=0){columns=2}var html="";if(settings.useSingleQuotes===true){html=""}else{html='
    '}for(var i=1;i<=rows;i++){html+="";for(var n=1;n<=columns;n++){html+=""}html+=""}html+="
    ";restoreSelection(editorID,true);pasteHTMLAtCaret(html);$form.find("input#tableColumns").val("");$form.find("input#tableRows").val("");$(".richText-toolbar li.is-selected").removeClass("is-selected")}});$(document).on("click",function(event){var $clickedElement=$(event.target);if($clickedElement.parents(".richText-toolbar").length===0){}else if($clickedElement.hasClass("richText-dropdown-outer")){$clickedElement.parent("a").parent("li").removeClass("is-selected")}else if($clickedElement.find(".richText").length>0){$(".richText-toolbar li").removeClass("is-selected")}else if($clickedElement.parent().hasClass("richText-dropdown-close")){$(".richText-toolbar li").removeClass("is-selected")}else if($clickedElement.hasClass("richText-btn")&&$(event.target).children(".richText-dropdown-outer").length>0){$clickedElement.parent("li").addClass("is-selected");if($clickedElement.children(".fa,svg").hasClass("fa-link")){restoreSelection(editorID,false,true);var selectedText=getSelectedText();$clickedElement.find("input#urlText").val("");$clickedElement.find("input#url").val("");if(selectedText){$clickedElement.find("input#urlText").val(selectedText)}}else if($clickedElement.hasClass("fa-image")){}}});settings.$editor.find(".richText-toolbar a[data-command]").on("click",function(event){var $button=$(this);var $toolbar=$button.closest(".richText-toolbar");var $editor=$toolbar.siblings(".richText-editor");var id=$editor.attr("id");if($editor.length>0&&id===editorID&&(!$button.parent("li").attr("data-disable")||$button.parent("li").attr("data-disable")==="false")){event.preventDefault();var command=$(this).data("command");if(command==="toggleSave"){$editor.trigger("change");if(typeof settings.saveCallback==="function"){settings.saveCallback($editor,"save",getEditorContent(editorID))}}else if(command==="toggleCode"){toggleCode($editor.attr("id"))}else{var option=null;if($(this).data("option")){option=$(this).data("option").toString();if(option.match(/^h[1-6]$/)){command="heading"}}formatText(command,option,id);if(command==="removeFormat"){$editor.find("*").each(function(){var keepAttributes=["id","class","name","action","method","src","align","alt","title","style","webkitallowfullscreen","mozallowfullscreen","allowfullscreen","width","height","frameborder"];var element=$(this);var attributes=$.map(this.attributes,function(item){return item.name});$.each(attributes,function(i,item){if(keepAttributes.indexOf(item)<0&&item.substr(0,5)!=="data-"){element.removeAttr(item)}});if(element.prop("tagName")==="A"){element.replaceWith(function(){return $("",{html:$(this).html()})})}});formatText("formatBlock","div",id)}$editor.find("div:empty,p:empty,li:empty,h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty").remove();$editor.find("h1,h2,h3,h4,h5,h6").unwrap("h1,h2,h3,h4,h5,h6")}}$button.parents("li.is-selected").removeClass("is-selected")});function focusNextElement(){var focussableElements='a:not([disabled]):not(.richText-btn,.richText-undo,.richText-redo,.richText-help), button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])';if(document.activeElement){var focussable=Array.prototype.filter.call(document.querySelectorAll(focussableElements),function(element){return element.offsetWidth>0||element.offsetHeight>0||element===document.activeElement});var index=focussable.indexOf(document.activeElement);if(index>-1){var nextElement=focussable[index+1]||focussable[0];nextElement.focus()}}}function formatText(command,option,editorID){if(typeof option==="undefined"){option=null}doRestore(editorID);if(command==="heading"&&getSelectedText()){wrapTextNode(option,"<"+option+">"+getSelectedText()+"")}else if(command==="fontSize"&&parseInt(option)>0){var selection=getSelectedText();selection=(selection+"").replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g,"$1"+"
    "+"$2");var html=settings.useSingleQuotes?""+selection+"":''+selection+"";wrapTextNode('span style="font-size:'+option+'px;"',html)}else{document.execCommand(command,false,option)}}function getEditorContent(editorId){var $editor=$("#"+editorID);var content=$editor.html();if(settings.useSingleQuotes===true){content=changeAttributeQuotes(content)}return content}function updateTextarea(event){var $editor=$("#"+editorID);content=getEditorContent(editorID);if(content!==$editor.siblings(".richText-initial").val()){$editor.siblings(".richText-initial").val(content)}if(settings.saveOnBlur&&event&&event.type==="blur"){settings.blurTriggered=true;setTimeout(()=>{if(!settings.blurTriggered){return}var content=getEditorContent(editorID);if($editor.data("content-val")!==content){$editor.data("content-val",content);$editor.trigger("change");if(typeof settings.saveCallback==="function"){settings.saveCallback($editor,"blur",content)}}},settings.saveOnBlur)}}function updateEditor(editorID){const $editor=$("#"+editorID);const content=$editor.siblings(".richText-initial").val();if(settings.disableScripts){removeScripts(editorID)}if(settings.DOMPurify){sanitizeContent(editorID)}$editor.html(content)}function saveSelection(editorID){var containerEl=document.getElementById(editorID);var range,start,end,type;if(window.getSelection&&document.createRange){var sel=window.getSelection&&window.getSelection();if(sel&&sel.rangeCount>0&&$(sel.anchorNode).parents("#"+editorID).length>0){range=window.getSelection().getRangeAt(0);var preSelectionRange=range.cloneRange();preSelectionRange.selectNodeContents(containerEl);preSelectionRange.setEnd(range.startContainer,range.startOffset);start=preSelectionRange.toString().length;end=start+range.toString().length;type=start===end?"caret":"selection";anchor=sel.anchorNode;start=type==="caret"&&anchor!==false?start:preSelectionRange.toString().length;end=type==="caret"&&anchor!==false?end:start+range.toString().length;return{start:start,end:end,type:type,anchor:anchor,editorID:editorID}}}return savedSelection?savedSelection:{start:0,end:0}}function restoreSelection(editorID,media,url){var containerEl=document.getElementById(editorID);var savedSel=savedSelection;if(!savedSel){savedSel={start:0,end:0,type:"caret",editorID:editorID,anchor:$("#"+editorID).children("div")[0]}}else if(!savedSel.editorID&&editorID){savedSel.editorID=editorID}if(savedSel.editorID!==editorID){return false}else if(media===true){containerEl=savedSel.anchor?savedSel.anchor:containerEl}else if(url===true){if(savedSel.start===0&&savedSel.end===0){containerEl=savedSel.anchor?savedSel.anchor:containerEl}}if(window.getSelection&&document.createRange){var charIndex=0,range=document.createRange();if(!range||!containerEl){window.getSelection().removeAllRanges();return true}range.setStart(containerEl,0);range.collapse(true);var nodeStack=[containerEl],node,foundStart=false,stop=false;while(!stop&&(node=nodeStack.pop())){if(node.nodeType===3){var nextCharIndex=charIndex+node.length;if(!foundStart&&savedSel.start>=charIndex&&savedSel.start<=nextCharIndex){range.setStart(node,savedSel.start-charIndex);foundStart=true}if(foundStart&&savedSel.end>=charIndex&&savedSel.end<=nextCharIndex){range.setEnd(node,savedSel.end-charIndex);stop=true}charIndex=nextCharIndex}else{var i=node.childNodes.length;while(i--){nodeStack.push(node.childNodes[i])}}}var sel=window.getSelection();sel.removeAllRanges();sel.addRange(range)}}function tabifyEditableTable(win,e){if(e.keyCode!==9){return false}var sel;if(win.getSelection){sel=win.getSelection();if(sel.rangeCount>0){var textNode=null,direction=null;if(!e.shiftKey){direction="next";textNode=sel.focusNode.nodeName==="TD"?sel.focusNode.nextSibling!=null?sel.focusNode.nextSibling:sel.focusNode.parentNode.nextSibling!=null?sel.focusNode.parentNode.nextSibling.childNodes[0]:null:sel.focusNode.parentNode.nextSibling!=null?sel.focusNode.parentNode.nextSibling:sel.focusNode.parentNode.parentNode.nextSibling!=null?sel.focusNode.parentNode.parentNode.nextSibling.childNodes[0]:null}else{direction="previous";textNode=sel.focusNode.nodeName==="TD"?sel.focusNode.previousSibling!=null?sel.focusNode.previousSibling:sel.focusNode.parentNode.previousSibling!=null?sel.focusNode.parentNode.previousSibling.childNodes[sel.focusNode.parentNode.previousSibling.childNodes.length-1]:null:sel.focusNode.parentNode.previousSibling!=null?sel.focusNode.parentNode.previousSibling:sel.focusNode.parentNode.parentNode.previousSibling!=null?sel.focusNode.parentNode.parentNode.previousSibling.childNodes[sel.focusNode.parentNode.parentNode.previousSibling.childNodes.length-1]:null}if(textNode!=null){sel.collapse(textNode,Math.min(textNode.length,sel.focusOffset+1));if(textNode.textContent!=null){sel.selectAllChildren(textNode)}e.preventDefault();return true}else if(textNode===null&&direction==="next"&&sel.focusNode.nodeName==="TD"){var $table=$(sel.focusNode).parents("table");var cellsPerLine=$table.find("tr").first().children("td").length;var $tr=$("");var $td=$("");for(var i=1;i<=cellsPerLine;i++){$tr.append($td.clone())}$table.append($tr);tabifyEditableTable(window,{keyCode:9,shiftKey:false,preventDefault:function(){}})}}}return false}function getSelectedText(){var range;if(window.getSelection){range=window.getSelection();if(range.isCollapsed){return false}return range.toString()?range.toString():range.focusNode.nodeValue}else if(document.selection.createRange){range=document.selection.createRange();return range.text}return false}function doSave(editorID){var $textarea=$(".richText-editor#"+editorID).siblings(".richText-initial");addHistory($textarea.val(),editorID);savedSelection=saveSelection(editorID)}function updateWordCount(editorID){const $editorInner=$(".richText-editor#"+editorID);const $editor=$editorInner.parents(".richText");const $wordCount=$editor.find(".richText-wordcount");if(!$wordCount||$wordCount.length===0){return}const content=$editor.find(".richText-editor").text().trim();const words=content.replace(/\s+/g," ").split(" ").filter(function(entry){return entry.trim()!==""}).length;$wordCount.html(""+words+" "+settings.translations.words)}function updateMaxLength(editorID){var $editorInner=$(".richText-editor#"+editorID);var $editor=$editorInner.parents(".richText");if(!$editor.data("maxlength")){return true}var color;var maxLength=parseInt($editor.data("maxlength"));var content=settings.maxlengthIncludeHTML?$editorInner.html():$editorInner.text();var percentage=content.length/maxLength*100;if(percentage>99){color="red"}else if(percentage>=90){color="orange"}else{color="black"}$editor.find(".richText-length").html(''+content.length+"/"+maxLength);if(content.length>maxLength){$editor.find(".richText-editor").html(truncateHTMLText(content,maxLength));updateTextarea();return false}return true}function truncateHTMLText(content,approxNumChars){const taggish=/<[^>]+>/g;content=content.slice(0,approxNumChars-3);content=content.replace(/<[^>]*$/,"");let tags=content.match(taggish);const openTagsSeen=[];for(let tag_i in tags){var tag=tags[tag_i];if(tag.match(/<[^>]+>/)!==null){openTagsSeen.push(tag)}else{openTagsSeen.pop()}}openTagsSeen.reverse();for(let tag_i in openTagsSeen){content+="<\\"+openTagsSeen[tag_i].match(/\w+/)[0]+">"}return content+"..."}function addHistory(val,id){if(!history[id]){return false}if(history[id].length-1>historyPosition[id]){history[id].length=historyPosition[id]+1}if(history[id][history[id].length-1]!==val){history[id].push(val)}historyPosition[id]=history[id].length-1;setHistoryButtons(id)}function setHistoryButtons(id){if(historyPosition[id]<=0){$editor.find(".richText-undo").addClass("is-disabled")}else{$editor.find(".richText-undo").removeClass("is-disabled")}if(historyPosition[id]>=history[id].length-1||history[id].length===0){$editor.find(".richText-redo").addClass("is-disabled")}else{$editor.find(".richText-redo").removeClass("is-disabled")}}function undo($editor){var id=$editor.children(".richText-editor").attr("id");historyPosition[id]--;if(!historyPosition[id]&&historyPosition[id]!==0){return false}var value=history[id][historyPosition[id]];$editor.find("textarea").val(value);$editor.find(".richText-editor").html(value);setHistoryButtons(id)}function redo($editor){var id=$editor.children(".richText-editor").attr("id");historyPosition[id]++;if(!historyPosition[id]&&historyPosition[id]!==0){return false}var value=history[id][historyPosition[id]];$editor.find("textarea").val(value);$editor.find(".richText-editor").html(value);setHistoryButtons(id)}function doRestore(id){if(savedSelection){restoreSelection(id?id:savedSelection.editorID)}}function wrapTextNode(tag,html){if(window.getSelection){sel=window.getSelection();console.log(sel,1);if(sel.focusNode.nodeType===3){$(sel.focusNode).wrap("<"+tag+" />")}return}pasteHTMLAtCaret(html)}function pasteHTMLAtCaret(html){var sel,range;if(window.getSelection){sel=window.getSelection();if(sel.getRangeAt&&sel.rangeCount){range=sel.getRangeAt(0);range.deleteContents();var el=document.createElement("div");el.innerHTML=html;var frag=document.createDocumentFragment(),node,lastNode;while(node=el.firstChild){lastNode=frag.appendChild(node)}range.insertNode(frag);if(lastNode){range=range.cloneRange();range.setStartAfter(lastNode);range.collapse(true);sel.removeAllRanges();sel.addRange(range)}}}else if(document.selection&&document.selection.type!=="Control"){document.selection.createRange().pasteHTML(html)}}function changeAttributeQuotes(string){if(!string){return""}var regex;var rstring;if(settings.useSingleQuotes===true){regex=/\s+(\w+\s*=\s*(["][^"]*["])|(['][^']*[']))+/g;rstring=string.replace(regex,function($0,$1,$2){if(!$2){return $0}return $0.replace($2,$2.replace(/\"/g,"'"))})}else{regex=/\s+(\w+\s*=\s*(['][^']*['])|(["][^"]*["]))+/g;rstring=string.replace(regex,function($0,$1,$2){if(!$2){return $0}return $0.replace($2,$2.replace(/'/g,'"'))})}return rstring}function loadColors(command){var colors={};var result="";colors["#FFFFFF"]=settings.translations.white;colors["#000000"]=settings.translations.black;colors["#7F6000"]=settings.translations.brown;colors["#938953"]=settings.translations.beige;colors["#1F497D"]=settings.translations.darkBlue;colors["blue"]=settings.translations.blue;colors["#4F81BD"]=settings.translations.lightBlue;colors["#953734"]=settings.translations.darkRed;colors["red"]=settings.translations.red;colors["#4F6128"]=settings.translations.darkGreen;colors["green"]=settings.translations.green;colors["#3F3151"]=settings.translations.purple;colors["#31859B"]=settings.translations.darkTurquois;colors["#4BACC6"]=settings.translations.turquois;colors["#E36C09"]=settings.translations.darkOrange;colors["#F79646"]=settings.translations.orange;colors["#FFFF00"]=settings.translations.yellow;if(settings.colors&&Object.keys(settings.colors).length){colors=settings.colors}for(var i in colors){result+='
  • '}return result}function toggleCode(editorID){doRestore(editorID);if($editor.find(".richText-editor").is(":visible")){$editor.find(".richText-initial").show();$editor.find(".richText-editor").hide();$(".richText-toolbar").find(".richText-btn").each(function(){if($(this).children(".fa-code").length===0){$(this).parent("li").attr("data-disable","true")}});convertCaretPosition(editorID,savedSelection)}else{$editor.find(".richText-initial").hide();$editor.find(".richText-editor").show();convertCaretPosition(editorID,savedSelection,true);$(".richText-toolbar").find("li").removeAttr("data-disable")}}function convertCaretPosition(editorID,selection,reverse){var $editor=$("#"+editorID);var $textarea=$editor.siblings(".richText-initial");var code=$textarea.val();if(!selection||!code){return{start:0,end:0}}if(reverse===true){savedSelection={start:$editor.text().length,end:$editor.text().length,editorID:editorID};restoreSelection(editorID);return true}selection.node=$textarea[0];var states={start:false,end:false,tag:false,isTag:false,tagsCount:0,isHighlight:selection.start!==selection.end};for(var i=0;i"){states.tagsCount++}else if(states.isTag===true&&code[i]===">"){states.isTag=false;states.tag=true;states.tagsCount++}else if(states.tag===true){states.tag=false}if(!reverse){if(selection.start+states.tagsCount<=i&&states.isHighlight&&!states.isTag&&!states.tag&&!states.start){selection.start=i;states.start=true}else if(selection.start+states.tagsCount<=i+1&&!states.isHighlight&&!states.isTag&&!states.tag&&!states.start){selection.start=i+1;states.start=true}if(selection.end+states.tagsCount<=i+1&&!states.isTag&&!states.tag&&!states.end){selection.end=i+1;states.end=true}}}createSelection(selection.node,selection.start,selection.end);return selection}function createSelection(field,start,end){if(field.createTextRange){var selRange=field.createTextRange();selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();field.focus()}else if(field.setSelectionRange){field.focus();field.setSelectionRange(start,end)}else if(typeof field.selectionStart!="undefined"){field.selectionStart=start;field.selectionEnd=end;field.focus()}}function getVideoCode(url,size){var video=getVideoID(url);var responsive=false,success=false;if(!video){return false}if(!size){size="640x360";size=size.split("x")}else if(size!=="responsive"){size=size.split("x")}else{responsive=true;size="640x360";size=size.split("x")}var html="

    ";if(responsive===true){html+='
    '}var allowfullscreen="webkitallowfullscreen mozallowfullscreen allowfullscreen";if(video.platform==="YouTube"){var youtubeDomain=settings.youtubeCookies?"www.youtube.com":"www.youtube-nocookie.com";html+='";success=true}else if(video.platform==="Vimeo"){html+='";success=true}else if(video.platform==="Facebook"){html+='";success=true}else if(video.platform==="Dailymotion"){html+='";success=true}if(responsive===true){html+="
    "}html+="

    ";if(success){return html}return false}function getVideoID(url){var vimeoRegExp=/(?:http?s?:\/\/)?(?:www\.)?(?:vimeo\.com)\/?(.+)/;var youtubeRegExp=/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;var facebookRegExp=/(?:http?s?:\/\/)?(?:www\.)?(?:facebook\.com)\/.*\/videos\/[0-9]+/;var dailymotionRegExp=/(?:http?s?:\/\/)?(?:www\.)?(?:dailymotion\.com)\/video\/([a-zA-Z0-9]+)/;var youtubeMatch=url.match(youtubeRegExp);var vimeoMatch=url.match(vimeoRegExp);var facebookMatch=url.match(facebookRegExp);var dailymotionMatch=url.match(dailymotionRegExp);if(youtubeMatch&&youtubeMatch[2].length===11){return{platform:"YouTube",id:youtubeMatch[2]}}else if(vimeoMatch&&vimeoMatch[1]){return{platform:"Vimeo",id:vimeoMatch[1]}}else if(facebookMatch&&facebookMatch[0]){return{platform:"Facebook",id:facebookMatch[0]}}else if(dailymotionMatch&&dailymotionMatch[1]){return{platform:"Dailymotion",id:dailymotionMatch[1]}}return false}function fixFirstLine(){if($editor&&!$editor.find(".richText-editor").html()){if(settings.useParagraph!==false){$editor.find(".richText-editor").html("


    ")}else{$editor.find(".richText-editor").html("

    ")}}else{if(settings.useParagraph!==false){$editor.find(".richText-editor").find("div:not(.videoEmbed)").replaceWith(function(){return $("

    ",{html:$(this).html()})})}else{$editor.find(".richText-editor").find("p").replaceWith(function(){return $("

    ",{html:$(this).html()})})}}const content=$editor.find(".richText-editor").html();if(content.trim().substring(0,1)!=="<"){$editor.find(".richText-editor").html("
    "+content+"
    ")}updateTextarea()}return $(this)}})(jQuery); 26 | --------------------------------------------------------------------------------