├── .eslintrc ├── .gitignore ├── README.md ├── bower.json ├── index.html ├── package.json ├── preview.jpg ├── src └── plugin.js └── style.css /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "rules": { 7 | "eqeqeq": 0, 8 | "curly": 0, 9 | "no-underscore-dangle": 0, 10 | "quotes": 0, 11 | "strict": 0, 12 | "space-unary-ops": 0, 13 | "indent": [ 2, 4 ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | npm-debug.log 4 | .DS_Store 5 | plugin.min.js 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyMCE variable [![CircleCI token](https://img.shields.io/circleci/project/github/ambassify/tinymce-variable/master.svg)](https://circleci.com/gh/ambassify/manage) [![maintainer](https://img.shields.io/badge/maintainer-Sitebase-brightgreen.svg)](https://github.com/Sitebase) 2 | 3 | TinyMCE variable is a plugin that makes it easier to work with variables in text. 4 | A lot of web applications today allow users to write content with variables. Server side these variables can then be replaced with actual data. 5 | There are many large companies that use this kind of functionality but a lot of these implementations are not very user friendly. 6 | 7 | With this project we provide a user friendly implementation of such a feature nicely packaged as a TinyMCE plugin. 8 | 9 | ![TinyMCE variables plugin example](preview.jpg) 10 | 11 | ## Demo 12 | 13 | [Demo example of this plugin](https://ambassify.github.io/tinymce-variable/) 14 | 15 | ## Features 16 | 17 | * Replace variables like `{{example}}` with something more readable 18 | * Variables are not editable 19 | * Delete variables with one hit on the backspace button 20 | * Custom class for variable elements 21 | * Auto replace when typing a variable 22 | * Custom variable prefix and suffix 23 | 24 | ## Example 25 | 26 | ``` 27 | tinymce.init({ 28 | selector: "textarea", // change this value according to your HTML 29 | plugins: "variable" 30 | }); 31 | ``` 32 | 33 | ## Options 34 | These settings affect the execution of the `variables` plugin. The settings described here will affect the visual appearance and the working of the `variables` plugin in the current editor instance. 35 | 36 | ### `variable_mapper` 37 | This option makes it possible to provide a human readable variant of specific variables. If the variables plugin detects such a mapper it will use that value to display the variable in the editor. An example use case for this could be to localize variable names. 38 | 39 | ``` 40 | tinymce.init({ 41 | selector: "textarea", 42 | plugins: "variable", 43 | variable_mapper: { 44 | account_id: "Account ID", 45 | email: "E-mail address" 46 | } 47 | }); 48 | ``` 49 | 50 | ### `variable_valid` 51 | This option makes it possible to provide a specific list of allowed variables, if the variable is not in the list then the plugin will not visualize it as such. 52 | 53 | ``` 54 | tinymce.init({ 55 | selector: "textarea", 56 | plugins: "variable", 57 | variable_valid: ["username", "sender", "phone", "community_name", "email"] 58 | }); 59 | ``` 60 | 61 | ### `variable_class` 62 | By default each variable instance in the editor will have a class name `variable`. If you want to use a custom class name you can use this option to overwrite it. 63 | 64 | ``` 65 | tinymce.init({ 66 | selector: "textarea", 67 | plugins: "variable", 68 | variable_class: "my-custom-variable" 69 | }); 70 | ``` 71 | 72 | ### `variable_prefix` and `variable_suffix` 73 | By default the prefix and suffix used are, the commonly used, double brackets (`{{` and `}}`). You can customize these if you prefer something else using these options. 74 | 75 | ``` 76 | tinymce.init({ 77 | selector: "textarea", 78 | plugins: "variable", 79 | variable_prefix: "{%", 80 | variable_suffix: "%}" 81 | }); 82 | ``` 83 | 84 | ## Develop 85 | To start a HTTP server to test your changes you can run following command and open the reported URL in your browser. 86 | 87 | ``` 88 | npm run serve 89 | ``` 90 | 91 | Make sure to run the tests before pushing code or submitting any pull request using: 92 | 93 | ``` 94 | npm run test 95 | ``` 96 | 97 | ## Products using TinyMCE Variables 98 | * [BuboBox](https://www.bubobox.com/?utm_source=github&utm_medium=readme&utm_campaign=tinymce-variable) 99 | * [Ambassify](https://www.ambassify.com/?utm_source=github&utm_medium=readme&utm_campaign=tinymce-variable) 100 | 101 | ## Contributing 102 | 103 | 1. Fork it! 104 | 2. Create your feature branch: `git checkout -b my-new-feature` 105 | 3. Commit your changes: `git commit -m 'Add some feature'` 106 | 4. Push to the branch: `git push origin my-new-feature` 107 | 5. Submit a pull request :D 108 | 109 | ## History 110 | 111 | For detailed changelog, check [Releases](https://github.com/bubobox/tinymce-variable/releases). 112 | 113 | ## License 114 | 115 | [MIT License](http://opensource.org/licenses/MIT) 116 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinymce-variable", 3 | "version": "0.1.0", 4 | "description": "Make it possible to use variables in your TinyMCE editor.", 5 | "main": "src/main.js", 6 | "keywords": [ 7 | "tinyMCE", 8 | "variables", 9 | "customize" 10 | ], 11 | "authors": [ 12 | "Sitebase (Wim Mostmans)" 13 | ], 14 | "license": "MIT", 15 | "homepage": "http://sitebase.github.io/tinymce-variable", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test", 21 | "tests", 22 | "package.json" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Preview TinyMCE variable plugin 7 | 8 | 9 | 10 | 11 | 21 | 22 | 23 | 64 | 65 | 66 |
67 | 71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tinymce-variable", 3 | "version": "0.9.1", 4 | "scripts": { 5 | "test": "eslint -c .eslintrc src/plugin.js", 6 | "serve": "python -m SimpleHTTPServer", 7 | "postinstall": "./node_modules/.bin/uglifyjs src/plugin.js -m -o src/plugin.min.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/bubobox/tinymce-variable.git" 12 | }, 13 | "description": "Make it possible to use variables in your TinyMCE editor.", 14 | "main": "src/plugin.js", 15 | "dependencies": { 16 | "eslint": "^1.0.0-rc-1", 17 | "uglify-js": "^3.1.0" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/bubobox/tinymce-variable/issues" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ambassify/tinymce-variable/b84bcf54db126d6c2061b3e06f32518879387057/preview.jpg -------------------------------------------------------------------------------- /src/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * plugin.js 3 | * 4 | * Copyright, BuboBox 5 | * Released under MIT License. 6 | * 7 | * License: https://www.bubobox.com 8 | * Contributing: https://www.bubobox.com/contributing 9 | */ 10 | 11 | /*global tinymce:true */ 12 | 13 | tinymce.PluginManager.add('variable', function(editor) { 14 | 15 | var VK = tinymce.util.VK; 16 | 17 | /** 18 | * Object that is used to replace the variable string to be used 19 | * in the HTML view 20 | * @type {object} 21 | */ 22 | var mapper = editor.getParam("variable_mapper", {}); 23 | 24 | /** 25 | * define a list of variables that are allowed 26 | * if the variable is not in the list it will not be automatically converterd 27 | * by default no validation is done 28 | * @todo make it possible to pass in a function to be used a callback for validation 29 | * @type {array} 30 | */ 31 | var valid = editor.getParam("variable_valid", null); 32 | 33 | /** 34 | * Get custom variable class name 35 | * @type {string} 36 | */ 37 | var className = editor.getParam("variable_class", "variable"); 38 | 39 | /** 40 | * Prefix and suffix to use to mark a variable 41 | * @type {string} 42 | */ 43 | var prefix = editor.getParam("variable_prefix", "{{"); 44 | var suffix = editor.getParam("variable_suffix", "}}"); 45 | var stringVariableRegex = new RegExp(prefix + '([a-z. _]*)?' + suffix, 'g'); 46 | 47 | /** 48 | * check if a certain variable is valid 49 | * @param {string} name 50 | * @return {bool} 51 | */ 52 | function isValid( name ) 53 | { 54 | 55 | if( ! valid || valid.length === 0 ) 56 | return true; 57 | 58 | var validString = '|' + valid.join('|') + '|'; 59 | 60 | return validString.indexOf( '|' + name + '|' ) > -1 ? true : false; 61 | } 62 | 63 | function getMappedValue( cleanValue ) { 64 | if(typeof mapper === 'function') 65 | return mapper(cleanValue); 66 | 67 | return mapper.hasOwnProperty(cleanValue) ? mapper[cleanValue] : cleanValue; 68 | } 69 | 70 | /** 71 | * Strip variable to keep the plain variable string 72 | * @example "{test}" => "test" 73 | * @param {string} value 74 | * @return {string} 75 | */ 76 | function cleanVariable(value) { 77 | return value.replace(/[^a-zA-Z0-9._]/g, ""); 78 | } 79 | 80 | /** 81 | * convert a text variable "x" to a span with the needed 82 | * attributes to style it with CSS 83 | * @param {string} value 84 | * @return {string} 85 | */ 86 | function createHTMLVariable( value ) { 87 | 88 | var cleanValue = cleanVariable(value); 89 | 90 | // check if variable is valid 91 | if( ! isValid(cleanValue) ) 92 | return value; 93 | 94 | var cleanMappedValue = getMappedValue(cleanValue); 95 | 96 | editor.fire('variableToHTML', { 97 | value: value, 98 | cleanValue: cleanValue 99 | }); 100 | 101 | var variable = prefix + cleanValue + suffix; 102 | return '' + cleanMappedValue + ''; 103 | } 104 | 105 | /** 106 | * convert variable strings into html elements 107 | * @return {void} 108 | */ 109 | function stringToHTML() 110 | { 111 | var nodeList = [], 112 | nodeValue, 113 | node, 114 | div; 115 | 116 | // find nodes that contain a string variable 117 | tinymce.walk(editor.getBody(), function(n) { 118 | if (n.nodeType == 3 && n.nodeValue && stringVariableRegex.test(n.nodeValue)) { 119 | nodeList.push(n); 120 | } 121 | }, 'childNodes'); 122 | 123 | // loop over all nodes that contain a string variable 124 | for (var i = 0; i < nodeList.length; i++) { 125 | nodeValue = nodeList[i].nodeValue.replace(stringVariableRegex, createHTMLVariable); 126 | div = editor.dom.create('div', null, nodeValue); 127 | while ((node = div.lastChild)) { 128 | editor.dom.insertAfter(node, nodeList[i]); 129 | 130 | if(isVariable(node)) { 131 | var next = node.nextSibling; 132 | editor.selection.setCursorLocation(next); 133 | } 134 | } 135 | 136 | editor.dom.remove(nodeList[i]); 137 | } 138 | } 139 | 140 | /** 141 | * convert HTML variables back into their original string format 142 | * for example when a user opens source view 143 | * @return {void} 144 | */ 145 | function htmlToString() 146 | { 147 | var nodeList = [], 148 | nodeValue, 149 | node, 150 | div; 151 | 152 | // find nodes that contain a HTML variable 153 | tinymce.walk( editor.getBody(), function(n) { 154 | if (n.nodeType == 1) { 155 | var original = n.getAttribute('data-original-variable'); 156 | if (original !== null) { 157 | nodeList.push(n); 158 | } 159 | } 160 | }, 'childNodes'); 161 | 162 | // loop over all nodes that contain a HTML variable 163 | for (var i = 0; i < nodeList.length; i++) { 164 | nodeValue = nodeList[i].getAttribute('data-original-variable'); 165 | div = editor.dom.create('div', null, nodeValue); 166 | while ((node = div.lastChild)) { 167 | editor.dom.insertAfter(node, nodeList[i]); 168 | } 169 | 170 | // remove HTML variable node 171 | // because we now have an text representation of the variable 172 | editor.dom.remove(nodeList[i]); 173 | } 174 | 175 | } 176 | 177 | function setCursor(selector) { 178 | var ell = editor.dom.select(selector)[0]; 179 | if(ell) { 180 | var next = ell.nextSibling; 181 | editor.selection.setCursorLocation(next); 182 | } 183 | } 184 | 185 | /** 186 | * handle formatting the content of the editor based on 187 | * the current format. For example if a user switches to source view and back 188 | * @param {object} e 189 | * @return {void} 190 | */ 191 | function handleContentRerender(e) { 192 | // store cursor location 193 | return e.format === 'raw' ? stringToHTML() : htmlToString(); 194 | // restore cursor location 195 | } 196 | 197 | /** 198 | * insert a variable into the editor at the current cursor location 199 | * @param {string} value 200 | * @return {void} 201 | */ 202 | function addVariable(value) { 203 | var htmlVariable = createHTMLVariable(value); 204 | editor.execCommand('mceInsertContent', false, htmlVariable); 205 | } 206 | 207 | function isVariable(element) { 208 | if(typeof element.getAttribute === 'function' && element.hasAttribute('data-original-variable')) 209 | return true; 210 | 211 | return false; 212 | } 213 | 214 | /** 215 | * Trigger special event when user clicks on a variable 216 | * @return {void} 217 | */ 218 | function handleClick(e) { 219 | var target = e.target; 220 | 221 | if(!isVariable(target)) 222 | return null; 223 | 224 | var value = target.getAttribute('data-original-variable'); 225 | editor.fire('variableClick', { 226 | value: cleanVariable(value), 227 | target: target 228 | }); 229 | } 230 | 231 | editor.on('nodechange', stringToHTML ); 232 | editor.on('keyup', stringToHTML ); 233 | editor.on('beforegetcontent', handleContentRerender); 234 | editor.on('click', handleClick); 235 | 236 | this.addVariable = addVariable; 237 | 238 | }); 239 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 14px; 3 | line-height: 1.4em; 4 | } 5 | .variable { 6 | cursor: default; 7 | background-color: #65b9dd; 8 | color: #FFF; 9 | padding: 2px 8px; 10 | border-radius: 3px; 11 | font-weight: bold; 12 | font-style: normal; 13 | font-size: 10px; 14 | display: inline-block; 15 | line-height: 12px; 16 | } 17 | --------------------------------------------------------------------------------