├── .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 [](https://circleci.com/gh/ambassify/manage) [](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 | 
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 |
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 |
--------------------------------------------------------------------------------