├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib └── wysihtml │ ├── rails.rb │ └── rails │ └── version.rb ├── vendor └── assets │ ├── javascripts │ ├── wysihtml.js │ └── wysihtml │ │ ├── all_commands.js │ │ ├── extra_commands │ │ ├── alignCenterStyle.js │ │ ├── alignJustifyStyle.js │ │ ├── alignLeftStyle.js │ │ ├── alignRightStyle.js │ │ ├── bgColorStyle.js │ │ ├── bold.js │ │ ├── command_formatCode.js │ │ ├── command_insertImage.js │ │ ├── fontSize.js │ │ ├── fontSizeStyle.js │ │ ├── foreColor.js │ │ ├── foreColorStyle.js │ │ ├── insertBlockQuote.js │ │ ├── insertHorizontalRule.js │ │ ├── insertOrderedList.js │ │ ├── insertUnorderedList.js │ │ ├── italic.js │ │ ├── justifyCenter.js │ │ ├── justifyFull.js │ │ ├── justifyLeft.js │ │ ├── justifyRight.js │ │ ├── subscript.js │ │ ├── superscript.js │ │ └── underline.js │ │ ├── parser_rules │ │ ├── advanced.js │ │ ├── advanced_and_extended.js │ │ ├── advanced_unwrap.js │ │ └── simple.js │ │ ├── table_editing.js │ │ └── toolbar.js │ └── stylesheets │ └── wysihtml.css └── wysihtml-rails.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in wysihtml-rails.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Tanel Jakobsoo 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wysihtml for Rails 2 | 3 | [Voog/wysihtml](https://github.com/Voog/wysihtml) is an extended and less strict approach on [xing/wysihtml5](http://xing.github.io/wysihtml5/) open source rich text editor based on HTML5 technology. 4 | 5 | This gem adds wysihtml to Rails assets pipeline. 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem 'wysihtml-rails' 13 | ``` 14 | 15 | Or you can install from latest build: 16 | 17 | ```ruby 18 | gem 'wysihtml-rails', :git => 'https://github.com/Voog/wysihtml-rails.git' 19 | ``` 20 | 21 | And then execute: 22 | 23 | ```sh 24 | bundle 25 | ``` 26 | 27 | Or install it yourself as: 28 | 29 | ```sh 30 | $ gem install wysihtml-rails 31 | ``` 32 | 33 | ## Usage 34 | 35 | Require it in your JS manifest's file `application.js`: 36 | 37 | ```js 38 | //= require wysihtml 39 | ``` 40 | 41 | or if you also need toolbar, table editing features or all commands: 42 | 43 | ```js 44 | //= require wysihtml 45 | //= require wysihtml/toolbar 46 | //= require wysihtml/all_commands 47 | //= require wysihtml/table_editing 48 | ``` 49 | 50 | Additionally include predefined `simple`, `advanced` or `advanced_unwrap` parsing rules in your `application.js`: 51 | 52 | ```js 53 | //= require wysihtml/parser_rules/advanced_unwrap 54 | ``` 55 | 56 | Additionally include predefined `wysihtml` stiles in your `application.css.scss` file: 57 | 58 | ```scss 59 | *= require wysihtml 60 | ``` 61 | 62 | The simple initialise: 63 | 64 | ```html 65 | 73 | ``` 74 | 75 | ## Contributing 76 | 77 | 1. Fork it 78 | 2. Create your feature branch (`git checkout -b my-new-feature`) 79 | 3. Commit your changes (`git commit -am 'Add some feature'`) 80 | 4. Push to the branch (`git push origin my-new-feature`) 81 | 5. Create new Pull Request 82 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /lib/wysihtml/rails.rb: -------------------------------------------------------------------------------- 1 | require "wysihtml/rails/version" 2 | 3 | module Wysihtml 4 | module Rails 5 | class Engine < ::Rails::Engine 6 | isolate_namespace Wysihtml::Rails 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/wysihtml/rails/version.rb: -------------------------------------------------------------------------------- 1 | module Wysihtml 2 | module Rails 3 | VERSION = "0.6.0.beta2" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/all_commands.js: -------------------------------------------------------------------------------- 1 | //= require ./extra_commands/alignCenterStyle.js 2 | //= require ./extra_commands/alignJustifyStyle.js 3 | //= require ./extra_commands/alignLeftStyle.js 4 | //= require ./extra_commands/alignRightStyle.js 5 | //= require ./extra_commands/bgColorStyle.js 6 | //= require ./extra_commands/bold.js 7 | //= require ./extra_commands/command_formatCode.js 8 | //= require ./extra_commands/command_insertImage.js 9 | //= require ./extra_commands/fontSize.js 10 | //= require ./extra_commands/fontSizeStyle.js 11 | //= require ./extra_commands/foreColor.js 12 | //= require ./extra_commands/foreColorStyle.js 13 | //= require ./extra_commands/insertBlockQuote.js 14 | //= require ./extra_commands/insertHorizontalRule.js 15 | //= require ./extra_commands/insertOrderedList.js 16 | //= require ./extra_commands/insertUnorderedList.js 17 | //= require ./extra_commands/italic.js 18 | //= require ./extra_commands/justifyCenter.js 19 | //= require ./extra_commands/justifyFull.js 20 | //= require ./extra_commands/justifyLeft.js 21 | //= require ./extra_commands/justifyRight.js 22 | //= require ./extra_commands/subscript.js 23 | //= require ./extra_commands/superscript.js 24 | //= require ./extra_commands/underline.js 25 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/alignCenterStyle.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.alignCenterStyle = (function() { 2 | var nodeOptions = { 3 | styleProperty: "textAlign", 4 | styleValue: "center", 5 | toggle: true 6 | }; 7 | 8 | return { 9 | exec: function(composer, command) { 10 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 11 | }, 12 | 13 | state: function(composer, command) { 14 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 15 | } 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/alignJustifyStyle.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.alignJustifyStyle = (function() { 2 | var nodeOptions = { 3 | styleProperty: "textAlign", 4 | styleValue: "justify", 5 | toggle: true 6 | }; 7 | 8 | return { 9 | exec: function(composer, command) { 10 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 11 | }, 12 | 13 | state: function(composer, command) { 14 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 15 | } 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/alignLeftStyle.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.alignLeftStyle = (function() { 2 | var nodeOptions = { 3 | styleProperty: "textAlign", 4 | styleValue: "left", 5 | toggle: true 6 | }; 7 | 8 | return { 9 | exec: function(composer, command) { 10 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 11 | }, 12 | 13 | state: function(composer, command) { 14 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 15 | } 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/alignRightStyle.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.alignRightStyle = (function() { 2 | var nodeOptions = { 3 | styleProperty: "textAlign", 4 | styleValue: "right", 5 | toggle: true 6 | }; 7 | 8 | return { 9 | exec: function(composer, command) { 10 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 11 | }, 12 | 13 | state: function(composer, command) { 14 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 15 | } 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/bgColorStyle.js: -------------------------------------------------------------------------------- 1 | /* Sets text background color by inline styles */ 2 | wysihtml.commands.bgColorStyle = (function() { 3 | return { 4 | exec: function(composer, command, color) { 5 | var colorVals = wysihtml.quirks.styleParser.parseColor("background-color:" + (color.color || color), "background-color"), 6 | colString; 7 | 8 | if (colorVals) { 9 | colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(', ') : "rgba(" + colorVals.join(', ')) + ')'; 10 | wysihtml.commands.formatInline.exec(composer, command, {styleProperty: 'backgroundColor', styleValue: colString}); 11 | } 12 | }, 13 | 14 | state: function(composer, command, color) { 15 | var colorVals = color ? wysihtml.quirks.styleParser.parseColor("background-color:" + (color.color || color), "background-color") : null, 16 | colString; 17 | 18 | if (colorVals) { 19 | colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(', ') : "rgba(" + colorVals.join(', ')) + ')'; 20 | } 21 | 22 | return wysihtml.commands.formatInline.state(composer, command, {styleProperty: 'backgroundColor', styleValue: colString}); 23 | }, 24 | 25 | remove: function(composer, command) { 26 | return wysihtml.commands.formatInline.remove(composer, command, {styleProperty: 'backgroundColor'}); 27 | }, 28 | 29 | stateValue: function(composer, command, props) { 30 | var st = this.state(composer, command), 31 | colorStr, 32 | val = false; 33 | 34 | if (st && wysihtml.lang.object(st).isArray()) { 35 | st = st[0]; 36 | } 37 | 38 | if (st) { 39 | colorStr = st.getAttribute('style'); 40 | if (colorStr) { 41 | val = wysihtml.quirks.styleParser.parseColor(colorStr, "background-color"); 42 | return wysihtml.quirks.styleParser.unparseColor(val, props); 43 | } 44 | } 45 | return false; 46 | } 47 | }; 48 | })(); 49 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/bold.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.bold = (function() { 2 | var nodeOptions = { 3 | nodeName: "B", 4 | toggle: true 5 | }; 6 | 7 | return { 8 | exec: function(composer, command) { 9 | wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 10 | }, 11 | 12 | state: function(composer, command) { 13 | return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 14 | } 15 | }; 16 | })(); 17 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/command_formatCode.js: -------------------------------------------------------------------------------- 1 | /* Formats block for as a
block 2 | * Useful in conjuction for sytax highlight utility: highlight.js 3 | * 4 | * Usage: 5 | * 6 | * editorInstance.composer.commands.exec("formatCode", "language-html"); 7 | */ 8 | wysihtml.commands.formatCode = (function() { 9 | return { 10 | exec: function(composer, command, classname) { 11 | var pre = this.state(composer)[0], 12 | code, range, selectedNodes; 13 | 14 | if (pre) { 15 | // caret is already within a
...
16 | composer.selection.executeAndRestore(function() { 17 | code = pre.querySelector("code"); 18 | wysihtml.dom.replaceWithChildNodes(pre); 19 | if (code) { 20 | wysihtml.dom.replaceWithChildNodes(code); 21 | } 22 | }); 23 | } else { 24 | // Wrap in
...
25 | range = composer.selection.getRange(); 26 | selectedNodes = range.extractContents(); 27 | pre = composer.doc.createElement("pre"); 28 | code = composer.doc.createElement("code"); 29 | 30 | if (classname) { 31 | code.className = classname; 32 | } 33 | 34 | pre.appendChild(code); 35 | code.appendChild(selectedNodes); 36 | range.insertNode(pre); 37 | composer.selection.selectNode(pre); 38 | } 39 | }, 40 | 41 | state: function(composer) { 42 | var selectedNode = composer.selection.getSelectedNode(), node; 43 | if (selectedNode && selectedNode.nodeName && selectedNode.nodeName == "PRE"&& 44 | selectedNode.firstChild && selectedNode.firstChild.nodeName && selectedNode.firstChild.nodeName == "CODE") { 45 | return [selectedNode]; 46 | } else { 47 | node = wysihtml.dom.getParentElement(selectedNode, { query: "pre code" }); 48 | return node ? [node.parentNode] : false; 49 | } 50 | } 51 | }; 52 | })(); 53 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/command_insertImage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Inserts an 3 | * If selection is already an image link, it removes it 4 | * 5 | * @example 6 | * // either ... 7 | * wysihtml.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg"); 8 | * // ... or ... 9 | * wysihtml.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" }); 10 | */ 11 | wysihtml.commands.insertImage = (function() { 12 | var NODE_NAME = "IMG"; 13 | return { 14 | exec: function(composer, command, value) { 15 | value = typeof(value) === "object" ? value : { src: value }; 16 | 17 | var doc = composer.doc, 18 | image = this.state(composer), 19 | textNode, 20 | parent; 21 | 22 | // If image is selected and src ie empty, set the caret before it and delete the image 23 | if (image && !value.src) { 24 | composer.selection.setBefore(image); 25 | parent = image.parentNode; 26 | parent.removeChild(image); 27 | 28 | // and it's parent too if it hasn't got any other relevant child nodes 29 | wysihtml.dom.removeEmptyTextNodes(parent); 30 | if (parent.nodeName === "A" && !parent.firstChild) { 31 | composer.selection.setAfter(parent); 32 | parent.parentNode.removeChild(parent); 33 | } 34 | 35 | // firefox and ie sometimes don't remove the image handles, even though the image got removed 36 | wysihtml.quirks.redraw(composer.element); 37 | return; 38 | } 39 | 40 | // If image selected change attributes accordingly 41 | if (image) { 42 | for (var key in value) { 43 | if (value.hasOwnProperty(key)) { 44 | image.setAttribute(key === "className" ? "class" : key, value[key]); 45 | } 46 | } 47 | return; 48 | } 49 | 50 | // Otherwise lets create the image 51 | image = doc.createElement(NODE_NAME); 52 | 53 | for (var i in value) { 54 | image.setAttribute(i === "className" ? "class" : i, value[i]); 55 | } 56 | 57 | composer.selection.insertNode(image); 58 | if (wysihtml.browser.hasProblemsSettingCaretAfterImg()) { 59 | textNode = doc.createTextNode(wysihtml.INVISIBLE_SPACE); 60 | composer.selection.insertNode(textNode); 61 | composer.selection.setAfter(textNode); 62 | } else { 63 | composer.selection.setAfter(image); 64 | } 65 | }, 66 | 67 | state: function(composer) { 68 | var doc = composer.doc, 69 | selectedNode, 70 | text, 71 | imagesInSelection; 72 | 73 | if (!wysihtml.dom.hasElementWithTagName(doc, NODE_NAME)) { 74 | return false; 75 | } 76 | 77 | selectedNode = composer.selection.getSelectedNode(); 78 | if (!selectedNode) { 79 | return false; 80 | } 81 | 82 | if (selectedNode.nodeName === NODE_NAME) { 83 | // This works perfectly in IE 84 | return selectedNode; 85 | } 86 | 87 | if (selectedNode.nodeType !== wysihtml.ELEMENT_NODE) { 88 | return false; 89 | } 90 | 91 | text = composer.selection.getText(); 92 | text = wysihtml.lang.string(text).trim(); 93 | if (text) { 94 | return false; 95 | } 96 | 97 | imagesInSelection = composer.selection.getNodes(wysihtml.ELEMENT_NODE, function(node) { 98 | return node.nodeName === "IMG"; 99 | }); 100 | 101 | if (imagesInSelection.length !== 1) { 102 | return false; 103 | } 104 | 105 | return imagesInSelection[0]; 106 | } 107 | }; 108 | })(); 109 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/fontSize.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.fontSize = (function() { 2 | var REG_EXP = /wysiwyg-font-size-[0-9a-z\-]+/g; 3 | 4 | return { 5 | exec: function(composer, command, size) { 6 | wysihtml.commands.formatInline.exec(composer, command, {className: "wysiwyg-font-size-" + size, classRegExp: REG_EXP, toggle: true}); 7 | }, 8 | 9 | state: function(composer, command, size) { 10 | return wysihtml.commands.formatInline.state(composer, command, {className: "wysiwyg-font-size-" + size}); 11 | } 12 | }; 13 | })(); 14 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/fontSizeStyle.js: -------------------------------------------------------------------------------- 1 | /* Set font size by inline style */ 2 | wysihtml.commands.fontSizeStyle = (function() { 3 | return { 4 | exec: function(composer, command, size) { 5 | size = size.size || size; 6 | if (!(/^\s*$/).test(size)) { 7 | wysihtml.commands.formatInline.exec(composer, command, {styleProperty: "fontSize", styleValue: size, toggle: false}); 8 | } 9 | }, 10 | 11 | state: function(composer, command, size) { 12 | return wysihtml.commands.formatInline.state(composer, command, {styleProperty: "fontSize", styleValue: size || undefined}); 13 | }, 14 | 15 | remove: function(composer, command) { 16 | return wysihtml.commands.formatInline.remove(composer, command, {styleProperty: "fontSize"}); 17 | }, 18 | 19 | stateValue: function(composer, command) { 20 | var styleStr, 21 | st = this.state(composer, command); 22 | 23 | if (st && wysihtml.lang.object(st).isArray()) { 24 | st = st[0]; 25 | } 26 | if (st) { 27 | styleStr = st.getAttribute("style"); 28 | if (styleStr) { 29 | return wysihtml.quirks.styleParser.parseFontSize(styleStr); 30 | } 31 | } 32 | return false; 33 | } 34 | }; 35 | })(); 36 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/foreColor.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.foreColor = (function() { 2 | var REG_EXP = /wysiwyg-color-[0-9a-z]+/g; 3 | 4 | return { 5 | exec: function(composer, command, color) { 6 | wysihtml.commands.formatInline.exec(composer, command, {className: "wysiwyg-color-" + color, classRegExp: REG_EXP, toggle: true}); 7 | }, 8 | 9 | state: function(composer, command, color) { 10 | return wysihtml.commands.formatInline.state(composer, command, {className: "wysiwyg-color-" + color}); 11 | } 12 | }; 13 | })(); 14 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/foreColorStyle.js: -------------------------------------------------------------------------------- 1 | /* Sets text color by inline styles */ 2 | wysihtml.commands.foreColorStyle = (function() { 3 | return { 4 | exec: function(composer, command, color) { 5 | var colorVals, colString; 6 | 7 | if (!color) { return; } 8 | 9 | colorVals = wysihtml.quirks.styleParser.parseColor("color:" + (color.color || color), "color"); 10 | 11 | if (colorVals) { 12 | colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(", ") : "rgba(" + colorVals.join(', ')) + ')'; 13 | wysihtml.commands.formatInline.exec(composer, command, {styleProperty: "color", styleValue: colString}); 14 | } 15 | }, 16 | 17 | state: function(composer, command, color) { 18 | var colorVals = color ? wysihtml.quirks.styleParser.parseColor("color:" + (color.color || color), "color") : null, 19 | colString; 20 | 21 | 22 | if (colorVals) { 23 | colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(", ") : "rgba(" + colorVals.join(', ')) + ')'; 24 | } 25 | 26 | return wysihtml.commands.formatInline.state(composer, command, {styleProperty: "color", styleValue: colString}); 27 | }, 28 | 29 | remove: function(composer, command) { 30 | return wysihtml.commands.formatInline.remove(composer, command, {styleProperty: "color"}); 31 | }, 32 | 33 | stateValue: function(composer, command, props) { 34 | var st = this.state(composer, command), 35 | colorStr, 36 | val = false; 37 | 38 | if (st && wysihtml.lang.object(st).isArray()) { 39 | st = st[0]; 40 | } 41 | 42 | if (st) { 43 | colorStr = st.getAttribute("style"); 44 | if (colorStr) { 45 | val = wysihtml.quirks.styleParser.parseColor(colorStr, "color"); 46 | return wysihtml.quirks.styleParser.unparseColor(val, props); 47 | } 48 | } 49 | return false; 50 | } 51 | }; 52 | })(); 53 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/insertBlockQuote.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.insertBlockQuote = (function() { 2 | var nodeOptions = { 3 | nodeName: "BLOCKQUOTE", 4 | toggle: true 5 | }; 6 | 7 | return { 8 | exec: function(composer, command) { 9 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 10 | }, 11 | 12 | state: function(composer, command) { 13 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 14 | } 15 | }; 16 | })(); 17 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/insertHorizontalRule.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.insertHorizontalRule = (function() { 2 | return { 3 | exec: function(composer) { 4 | var node = composer.selection.getSelectedNode(), 5 | phrasingOnlyParent = wysihtml.dom.getParentElement(node, { query: wysihtml.PERMITTED_PHRASING_CONTENT_ONLY }, null, composer.editableArea), 6 | elem = document.createElement('hr'), 7 | range, idx; 8 | 9 | // HR is not allowed into some elements (where only phrasing content is allowed) 10 | // thus the HR insertion must break out of those https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories 11 | if (phrasingOnlyParent) { 12 | composer.selection.splitElementAtCaret(phrasingOnlyParent, elem); 13 | } else { 14 | composer.selection.insertNode(elem); 15 | } 16 | 17 | if (elem.nextSibling) { 18 | composer.selection.setBefore(elem.nextSibling); 19 | } else { 20 | composer.selection.setAfter(elem); 21 | } 22 | }, 23 | state: function() { 24 | return false; // :( 25 | } 26 | }; 27 | })(); 28 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/insertOrderedList.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.insertOrderedList = (function() { 2 | return { 3 | exec: function(composer, command) { 4 | wysihtml.commands.insertList.exec(composer, command, "OL"); 5 | }, 6 | 7 | state: function(composer, command) { 8 | return wysihtml.commands.insertList.state(composer, command, "OL"); 9 | } 10 | }; 11 | })(); 12 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/insertUnorderedList.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.insertUnorderedList = (function() { 2 | return { 3 | exec: function(composer, command) { 4 | wysihtml.commands.insertList.exec(composer, command, "UL"); 5 | }, 6 | 7 | state: function(composer, command) { 8 | return wysihtml.commands.insertList.state(composer, command, "UL"); 9 | } 10 | }; 11 | })(); 12 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/italic.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.italic = (function() { 2 | var nodeOptions = { 3 | nodeName: "I", 4 | toggle: true 5 | }; 6 | 7 | return { 8 | exec: function(composer, command) { 9 | wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 10 | }, 11 | 12 | state: function(composer, command) { 13 | return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 14 | } 15 | }; 16 | 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/justifyCenter.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.justifyCenter = (function() { 2 | var nodeOptions = { 3 | className: "wysiwyg-text-align-center", 4 | classRegExp: /wysiwyg-text-align-[0-9a-z]+/g, 5 | toggle: true 6 | }; 7 | 8 | return { 9 | exec: function(composer, command) { 10 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 11 | }, 12 | 13 | state: function(composer, command) { 14 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 15 | } 16 | }; 17 | 18 | })(); 19 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/justifyFull.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.justifyFull = (function() { 2 | var nodeOptions = { 3 | className: "wysiwyg-text-align-justify", 4 | classRegExp: /wysiwyg-text-align-[0-9a-z]+/g, 5 | toggle: true 6 | }; 7 | 8 | return { 9 | exec: function(composer, command) { 10 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 11 | }, 12 | 13 | state: function(composer, command) { 14 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 15 | } 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/justifyLeft.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.justifyLeft = (function() { 2 | var nodeOptions = { 3 | className: "wysiwyg-text-align-left", 4 | classRegExp: /wysiwyg-text-align-[0-9a-z]+/g, 5 | toggle: true 6 | }; 7 | 8 | return { 9 | exec: function(composer, command) { 10 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 11 | }, 12 | 13 | state: function(composer, command) { 14 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 15 | } 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/justifyRight.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.justifyRight = (function() { 2 | var nodeOptions = { 3 | className: "wysiwyg-text-align-right", 4 | classRegExp: /wysiwyg-text-align-[0-9a-z]+/g, 5 | toggle: true 6 | }; 7 | 8 | return { 9 | exec: function(composer, command) { 10 | return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 11 | }, 12 | 13 | state: function(composer, command) { 14 | return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 15 | } 16 | }; 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/subscript.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.subscript = (function() { 2 | var nodeOptions = { 3 | nodeName: "SUB", 4 | toggle: true 5 | }; 6 | 7 | return { 8 | exec: function(composer, command) { 9 | wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 10 | }, 11 | 12 | state: function(composer, command) { 13 | return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 14 | } 15 | }; 16 | 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/superscript.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.superscript = (function() { 2 | var nodeOptions = { 3 | nodeName: "SUP", 4 | toggle: true 5 | }; 6 | 7 | return { 8 | exec: function(composer, command) { 9 | wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 10 | }, 11 | 12 | state: function(composer, command) { 13 | return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 14 | } 15 | }; 16 | 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/extra_commands/underline.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.underline = (function() { 2 | var nodeOptions = { 3 | nodeName: "U", 4 | toggle: true 5 | }; 6 | 7 | return { 8 | exec: function(composer, command) { 9 | wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 10 | }, 11 | 12 | state: function(composer, command) { 13 | return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 14 | } 15 | }; 16 | 17 | })(); 18 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/parser_rules/advanced.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full HTML5 compatibility rule set 3 | * These rules define which tags and CSS classes are supported and which tags should be specially treated. 4 | * 5 | * Examples based on this rule set: 6 | * 7 | * foo 8 | * ... becomes ... 9 | * foo 10 | * 11 | * 12 | * ... becomes ... 13 | * 14 | * 15 | *
foo
16 | * ... becomes ... 17 | *
foo
18 | * 19 | * foo 20 | * ... becomes ... 21 | * foo 22 | * 23 | * foo
bar 24 | * ... becomes ... 25 | * foo
bar 26 | * 27 | *
hello
28 | * ... becomes ... 29 | *
hello
30 | * 31 | *
hello
32 | * ... becomes ... 33 | *
hello
34 | */ 35 | var wysihtmlParserRules = { 36 | /** 37 | * CSS Class white-list 38 | * Following CSS classes won't be removed when parsed by the wysihtml HTML parser 39 | */ 40 | "classes": { 41 | "wysiwyg-clear-both": 1, 42 | "wysiwyg-clear-left": 1, 43 | "wysiwyg-clear-right": 1, 44 | "wysiwyg-color-aqua": 1, 45 | "wysiwyg-color-black": 1, 46 | "wysiwyg-color-blue": 1, 47 | "wysiwyg-color-fuchsia": 1, 48 | "wysiwyg-color-gray": 1, 49 | "wysiwyg-color-green": 1, 50 | "wysiwyg-color-lime": 1, 51 | "wysiwyg-color-maroon": 1, 52 | "wysiwyg-color-navy": 1, 53 | "wysiwyg-color-olive": 1, 54 | "wysiwyg-color-purple": 1, 55 | "wysiwyg-color-red": 1, 56 | "wysiwyg-color-silver": 1, 57 | "wysiwyg-color-teal": 1, 58 | "wysiwyg-color-white": 1, 59 | "wysiwyg-color-yellow": 1, 60 | "wysiwyg-float-left": 1, 61 | "wysiwyg-float-right": 1, 62 | "wysiwyg-font-size-large": 1, 63 | "wysiwyg-font-size-larger": 1, 64 | "wysiwyg-font-size-medium": 1, 65 | "wysiwyg-font-size-small": 1, 66 | "wysiwyg-font-size-smaller": 1, 67 | "wysiwyg-font-size-x-large": 1, 68 | "wysiwyg-font-size-x-small": 1, 69 | "wysiwyg-font-size-xx-large": 1, 70 | "wysiwyg-font-size-xx-small": 1, 71 | "wysiwyg-text-align-center": 1, 72 | "wysiwyg-text-align-justify": 1, 73 | "wysiwyg-text-align-left": 1, 74 | "wysiwyg-text-align-right": 1 75 | }, 76 | /** 77 | * Tag list 78 | * 79 | * The following options are available: 80 | * 81 | * - add_class: converts and deletes the given HTML4 attribute (align, clear, ...) via the given method to a css class 82 | * The following methods are implemented in wysihtml.dom.parse: 83 | * - align_text: converts align attribute values (right/left/center/justify) to their corresponding css class "wysiwyg-text-align-*") 84 | *

foo

... becomes ...

foo

85 | * - clear_br: converts clear attribute values left/right/all/both to their corresponding css class "wysiwyg-clear-*" 86 | *
... becomes ...
87 | * - align_img: converts align attribute values (right/left) on to their corresponding css class "wysiwyg-float-*" 88 | * 89 | * - add_style: converts and deletes the given HTML4 attribute (align) via the given method to a css style 90 | * The following methods are implemented in wysihtml.dom.parse: 91 | * - align_text: converts align attribute values (right/left/center) to their corresponding css style) 92 | *

foo

... becomes ...

foo

93 | * 94 | * - remove: removes the element and its content 95 | * 96 | * - unwrap removes element but leaves content 97 | * 98 | * - rename_tag: renames the element to the given tag 99 | * 100 | * - set_class: adds the given class to the element (note: make sure that the class is in the "classes" white list above) 101 | * 102 | * - set_attributes: sets/overrides the given attributes 103 | * 104 | * - check_attributes: checks the given HTML attribute via the given method 105 | * - url: allows only valid urls (starting with http:// or https://) 106 | * - src: allows something like "/foobar.jpg", "http://google.com", ... 107 | * - href: allows something like "mailto:bert@foo.com", "http://google.com", "/foobar.jpg" 108 | * - alt: strips unwanted characters. if the attribute is not set, then it gets set (to ensure valid and compatible HTML) 109 | * - numbers: ensures that the attribute only contains numeric (integer) characters (no float values or units) 110 | * - dimension: for with/height attributes where floating point numbrs and percentages are allowed 111 | * - any: allows anything to pass 112 | */ 113 | "tags": { 114 | "tr": { 115 | "add_class": { 116 | "align": "align_text" 117 | } 118 | }, 119 | "strike": { 120 | "remove": 1 121 | }, 122 | "form": { 123 | "rename_tag": "div" 124 | }, 125 | "rt": { 126 | "rename_tag": "span" 127 | }, 128 | "code": {}, 129 | "acronym": { 130 | "rename_tag": "span" 131 | }, 132 | "br": { 133 | "add_class": { 134 | "clear": "clear_br" 135 | } 136 | }, 137 | "details": { 138 | "rename_tag": "div" 139 | }, 140 | "h4": { 141 | "add_class": { 142 | "align": "align_text" 143 | } 144 | }, 145 | "em": {}, 146 | "title": { 147 | "remove": 1 148 | }, 149 | "multicol": { 150 | "rename_tag": "div" 151 | }, 152 | "figure": { 153 | "rename_tag": "div" 154 | }, 155 | "xmp": { 156 | "rename_tag": "span" 157 | }, 158 | "small": { 159 | "rename_tag": "span", 160 | "set_class": "wysiwyg-font-size-smaller" 161 | }, 162 | "area": { 163 | "remove": 1 164 | }, 165 | "time": { 166 | "rename_tag": "span" 167 | }, 168 | "dir": { 169 | "rename_tag": "ul" 170 | }, 171 | "bdi": { 172 | "rename_tag": "span" 173 | }, 174 | "command": { 175 | "remove": 1 176 | }, 177 | "ul": {}, 178 | "progress": { 179 | "rename_tag": "span" 180 | }, 181 | "dfn": { 182 | "rename_tag": "span" 183 | }, 184 | "iframe": { 185 | "remove": 1 186 | }, 187 | "figcaption": { 188 | "rename_tag": "div" 189 | }, 190 | "a": { 191 | "check_attributes": { 192 | "target": "any", 193 | "href": "url" // if you compiled master manually then change this from 'url' to 'href' 194 | }, 195 | "set_attributes": { 196 | "rel": "nofollow" 197 | } 198 | }, 199 | "img": { 200 | "check_attributes": { 201 | "width": "dimension", 202 | "alt": "alt", 203 | "src": "url", // if you compiled master manually then change this from 'url' to 'src' 204 | "height": "dimension" 205 | }, 206 | "add_class": { 207 | "align": "align_img" 208 | } 209 | }, 210 | "rb": { 211 | "rename_tag": "span" 212 | }, 213 | "footer": { 214 | "rename_tag": "div" 215 | }, 216 | "noframes": { 217 | "remove": 1 218 | }, 219 | "abbr": { 220 | "rename_tag": "span" 221 | }, 222 | "u": {}, 223 | "bgsound": { 224 | "remove": 1 225 | }, 226 | "address": { 227 | "rename_tag": "div" 228 | }, 229 | "basefont": { 230 | "remove": 1 231 | }, 232 | "nav": { 233 | "rename_tag": "div" 234 | }, 235 | "h1": { 236 | "add_class": { 237 | "align": "align_text" 238 | } 239 | }, 240 | "head": { 241 | "remove": 1 242 | }, 243 | "tbody": { 244 | "add_class": { 245 | "align": "align_text" 246 | } 247 | }, 248 | "dd": { 249 | "rename_tag": "div" 250 | }, 251 | "s": { 252 | "rename_tag": "span" 253 | }, 254 | "li": {}, 255 | "td": { 256 | "check_attributes": { 257 | "rowspan": "numbers", 258 | "colspan": "numbers" 259 | }, 260 | "add_class": { 261 | "align": "align_text" 262 | } 263 | }, 264 | "object": { 265 | "remove": 1 266 | }, 267 | "div": { 268 | "add_class": { 269 | "align": "align_text" 270 | } 271 | }, 272 | "option": { 273 | "rename_tag": "span" 274 | }, 275 | "select": { 276 | "rename_tag": "span" 277 | }, 278 | "i": {}, 279 | "track": { 280 | "remove": 1 281 | }, 282 | "wbr": { 283 | "remove": 1 284 | }, 285 | "fieldset": { 286 | "rename_tag": "div" 287 | }, 288 | "big": { 289 | "rename_tag": "span", 290 | "set_class": "wysiwyg-font-size-larger" 291 | }, 292 | "button": { 293 | "rename_tag": "span" 294 | }, 295 | "noscript": { 296 | "remove": 1 297 | }, 298 | "svg": { 299 | "remove": 1 300 | }, 301 | "input": { 302 | "remove": 1 303 | }, 304 | "table": {}, 305 | "keygen": { 306 | "remove": 1 307 | }, 308 | "h5": { 309 | "add_class": { 310 | "align": "align_text" 311 | } 312 | }, 313 | "meta": { 314 | "remove": 1 315 | }, 316 | "map": { 317 | "rename_tag": "div" 318 | }, 319 | "isindex": { 320 | "remove": 1 321 | }, 322 | "mark": { 323 | "rename_tag": "span" 324 | }, 325 | "caption": { 326 | "add_class": { 327 | "align": "align_text" 328 | } 329 | }, 330 | "tfoot": { 331 | "add_class": { 332 | "align": "align_text" 333 | } 334 | }, 335 | "base": { 336 | "remove": 1 337 | }, 338 | "video": { 339 | "remove": 1 340 | }, 341 | "strong": {}, 342 | "canvas": { 343 | "remove": 1 344 | }, 345 | "output": { 346 | "rename_tag": "span" 347 | }, 348 | "marquee": { 349 | "rename_tag": "span" 350 | }, 351 | "b": {}, 352 | "q": { 353 | "check_attributes": { 354 | "cite": "url" 355 | } 356 | }, 357 | "applet": { 358 | "remove": 1 359 | }, 360 | "span": {}, 361 | "rp": { 362 | "rename_tag": "span" 363 | }, 364 | "spacer": { 365 | "remove": 1 366 | }, 367 | "source": { 368 | "remove": 1 369 | }, 370 | "aside": { 371 | "rename_tag": "div" 372 | }, 373 | "frame": { 374 | "remove": 1 375 | }, 376 | "section": { 377 | "rename_tag": "div" 378 | }, 379 | "body": { 380 | "rename_tag": "div" 381 | }, 382 | "ol": {}, 383 | "nobr": { 384 | "rename_tag": "span" 385 | }, 386 | "html": { 387 | "rename_tag": "div" 388 | }, 389 | "summary": { 390 | "rename_tag": "span" 391 | }, 392 | "var": { 393 | "rename_tag": "span" 394 | }, 395 | "del": { 396 | "remove": 1 397 | }, 398 | "blockquote": { 399 | "check_attributes": { 400 | "cite": "url" 401 | } 402 | }, 403 | "style": { 404 | "remove": 1 405 | }, 406 | "device": { 407 | "remove": 1 408 | }, 409 | "meter": { 410 | "rename_tag": "span" 411 | }, 412 | "h3": { 413 | "add_class": { 414 | "align": "align_text" 415 | } 416 | }, 417 | "textarea": { 418 | "rename_tag": "span" 419 | }, 420 | "embed": { 421 | "remove": 1 422 | }, 423 | "hgroup": { 424 | "rename_tag": "div" 425 | }, 426 | "font": { 427 | "rename_tag": "span", 428 | "add_class": { 429 | "size": "size_font" 430 | } 431 | }, 432 | "tt": { 433 | "rename_tag": "span" 434 | }, 435 | "noembed": { 436 | "remove": 1 437 | }, 438 | "thead": { 439 | "add_class": { 440 | "align": "align_text" 441 | } 442 | }, 443 | "blink": { 444 | "rename_tag": "span" 445 | }, 446 | "plaintext": { 447 | "rename_tag": "span" 448 | }, 449 | "xml": { 450 | "remove": 1 451 | }, 452 | "h6": { 453 | "add_class": { 454 | "align": "align_text" 455 | } 456 | }, 457 | "param": { 458 | "remove": 1 459 | }, 460 | "th": { 461 | "check_attributes": { 462 | "rowspan": "numbers", 463 | "colspan": "numbers" 464 | }, 465 | "add_class": { 466 | "align": "align_text" 467 | } 468 | }, 469 | "legend": { 470 | "rename_tag": "span" 471 | }, 472 | "hr": {}, 473 | "label": { 474 | "rename_tag": "span" 475 | }, 476 | "dl": { 477 | "rename_tag": "div" 478 | }, 479 | "kbd": { 480 | "rename_tag": "span" 481 | }, 482 | "listing": { 483 | "rename_tag": "div" 484 | }, 485 | "dt": { 486 | "rename_tag": "span" 487 | }, 488 | "nextid": { 489 | "remove": 1 490 | }, 491 | "pre": {}, 492 | "center": { 493 | "rename_tag": "div", 494 | "set_class": "wysiwyg-text-align-center" 495 | }, 496 | "audio": { 497 | "remove": 1 498 | }, 499 | "datalist": { 500 | "rename_tag": "span" 501 | }, 502 | "samp": { 503 | "rename_tag": "span" 504 | }, 505 | "col": { 506 | "remove": 1 507 | }, 508 | "article": { 509 | "rename_tag": "div" 510 | }, 511 | "cite": {}, 512 | "link": { 513 | "remove": 1 514 | }, 515 | "script": { 516 | "remove": 1 517 | }, 518 | "bdo": { 519 | "rename_tag": "span" 520 | }, 521 | "menu": { 522 | "rename_tag": "ul" 523 | }, 524 | "colgroup": { 525 | "remove": 1 526 | }, 527 | "ruby": { 528 | "rename_tag": "span" 529 | }, 530 | "h2": { 531 | "add_class": { 532 | "align": "align_text" 533 | } 534 | }, 535 | "ins": { 536 | "rename_tag": "span" 537 | }, 538 | "p": { 539 | "add_class": { 540 | "align": "align_text" 541 | } 542 | }, 543 | "sub": {}, 544 | "comment": { 545 | "remove": 1 546 | }, 547 | "frameset": { 548 | "remove": 1 549 | }, 550 | "optgroup": { 551 | "rename_tag": "span" 552 | }, 553 | "header": { 554 | "rename_tag": "div" 555 | }, 556 | "sup": {} 557 | } 558 | }; 559 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/parser_rules/advanced_and_extended.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full HTML5 compatibility rule set 3 | * Loosened and extended ruleset. Allows more freedom on user side 4 | * These rules define which tags and CSS classes are supported and which tags should be specially treated. 5 | */ 6 | 7 | var wysihtmlParserRulesDefaults = { 8 | "blockLevelEl": { 9 | "keep_styles": { 10 | "textAlign": /^((left)|(right)|(center)|(justify))$/i, 11 | "float": 1 12 | }, 13 | "add_style": { 14 | "align": "align_text" 15 | }, 16 | "check_attributes": { 17 | "id": "any" 18 | } 19 | }, 20 | 21 | "makeDiv": { 22 | "rename_tag": "div", 23 | "one_of_type": { 24 | "alignment_object": 1 25 | }, 26 | "remove_action": "unwrap", 27 | "keep_styles": { 28 | "textAlign": 1, 29 | "float": 1 30 | }, 31 | "add_style": { 32 | "align": "align_text" 33 | }, 34 | "check_attributes": { 35 | "id": "any" 36 | } 37 | } 38 | }; 39 | 40 | var wysihtmlParserRules = { 41 | /** 42 | * CSS Class white-list 43 | * Following CSS classes won't be removed when parsed by the wysihtml HTML parser 44 | * If all classes should pass "any" as classes value. Ex: "classes": "any" 45 | */ 46 | "classes": "any", 47 | 48 | /* blacklist of classes is only available if classes is set to any */ 49 | "classes_blacklist": { 50 | "Apple-interchange-newline": 1, 51 | "MsoNormal": 1, 52 | "MsoPlainText": 1 53 | }, 54 | 55 | "type_definitions": { 56 | 57 | "alignment_object": { 58 | "classes": { 59 | "wysiwyg-text-align-center": 1, 60 | "wysiwyg-text-align-justify": 1, 61 | "wysiwyg-text-align-left": 1, 62 | "wysiwyg-text-align-right": 1, 63 | "wysiwyg-float-left": 1, 64 | "wysiwyg-float-right": 1 65 | }, 66 | "styles": { 67 | "float": ["left", "right"], 68 | "text-align": ["left", "right", "center"] 69 | } 70 | }, 71 | 72 | "valid_image_src": { 73 | "attrs": { 74 | "src": /^[^data\:]/i 75 | } 76 | }, 77 | 78 | "text_color_object": { 79 | "styles": { 80 | "color": true, 81 | "background-color": true 82 | } 83 | }, 84 | 85 | "text_fontsize_object": { 86 | "styles": { 87 | "font-size": true 88 | } 89 | }, 90 | 91 | "text_formatting_object": { 92 | "classes": { 93 | "wysiwyg-color-aqua": 1, 94 | "wysiwyg-color-black": 1, 95 | "wysiwyg-color-blue": 1, 96 | "wysiwyg-color-fuchsia": 1, 97 | "wysiwyg-color-gray": 1, 98 | "wysiwyg-color-green": 1, 99 | "wysiwyg-color-lime": 1, 100 | "wysiwyg-color-maroon": 1, 101 | "wysiwyg-color-navy": 1, 102 | "wysiwyg-color-olive": 1, 103 | "wysiwyg-color-purple": 1, 104 | "wysiwyg-color-red": 1, 105 | "wysiwyg-color-silver": 1, 106 | "wysiwyg-color-teal": 1, 107 | "wysiwyg-color-white": 1, 108 | "wysiwyg-color-yellow": 1, 109 | "wysiwyg-font-size-large": 1, 110 | "wysiwyg-font-size-larger": 1, 111 | "wysiwyg-font-size-medium": 1, 112 | "wysiwyg-font-size-small": 1, 113 | "wysiwyg-font-size-smaller": 1, 114 | "wysiwyg-font-size-x-large": 1, 115 | "wysiwyg-font-size-x-small": 1, 116 | "wysiwyg-font-size-xx-large": 1, 117 | "wysiwyg-font-size-xx-small": 1 118 | } 119 | } 120 | }, 121 | 122 | "comments": 1, // if set allows comments to pass 123 | 124 | /** 125 | * Tag list 126 | * 127 | * The following options are available: 128 | * 129 | * - add_class: converts and deletes the given HTML4 attribute (align, clear, ...) via the given method to a css class 130 | * The following methods are implemented in wysihtml.dom.parse: 131 | * - align_text: converts align attribute values (right/left/center/justify) to their corresponding css class "wysiwyg-text-align-*") 132 | *

foo

... becomes ...

class="wysiwyg-text-align-center">foo

133 | * - clear_br: converts clear attribute values left/right/all/both to their corresponding css class "wysiwyg-clear-*" 134 | *
... becomes ...
135 | * - align_img: converts align attribute values (right/left) on to their corresponding css class "wysiwyg-float-*" 136 | * 137 | * - remove: removes the element and its content 138 | * 139 | * - unwrap removes element but leaves content 140 | * 141 | * - rename_tag: renames the element to the given tag 142 | * 143 | * - set_class: adds the given class to the element (note: make sure that the class is in the "classes" white list above) 144 | * 145 | * - set_attributes: sets/overrides the given attributes 146 | * 147 | * - check_attributes: checks the given HTML attribute via the given method 148 | * - url: allows only valid urls (starting with http:// or https://) 149 | * - src: allows something like "/foobar.jpg", "http://google.com", ... 150 | * - href: allows something like "mailto:bert@foo.com", "http://google.com", "/foobar.jpg" 151 | * - alt: strips unwanted characters. if the attribute is not set, then it gets set (to ensure valid and compatible HTML) 152 | * - numbers: ensures that the attribute only contains numeric (integer) characters (no float values or units) 153 | * - dimension: for with/height attributes where floating point numbrs and percentages are allowed 154 | * - any: allows anything to pass 155 | */ 156 | "tags": { 157 | "tr": { 158 | "add_style": { 159 | "align": "align_text" 160 | }, 161 | "check_attributes": { 162 | "id": "any" 163 | } 164 | }, 165 | "strike": { 166 | "unwrap": 1 167 | }, 168 | "form": { 169 | "unwrap": 1 170 | }, 171 | "rt": { 172 | "rename_tag": "span" 173 | }, 174 | "code": {}, 175 | "acronym": { 176 | "rename_tag": "span" 177 | }, 178 | "br": { 179 | "add_class": { 180 | "clear": "clear_br" 181 | } 182 | }, 183 | "details": { 184 | "unwrap": 1 185 | }, 186 | "h4": wysihtmlParserRulesDefaults.blockLevelEl, 187 | "em": {}, 188 | "title": { 189 | "remove": 1 190 | }, 191 | "multicol": { 192 | "unwrap": 1 193 | }, 194 | "figure": { 195 | "unwrap": 1 196 | }, 197 | "xmp": { 198 | "unwrap": 1 199 | }, 200 | "small": { 201 | "rename_tag": "span", 202 | "set_class": "wysiwyg-font-size-smaller" 203 | }, 204 | "area": { 205 | "remove": 1 206 | }, 207 | "time": { 208 | "unwrap": 1 209 | }, 210 | "dir": { 211 | "rename_tag": "ul" 212 | }, 213 | "bdi": { 214 | "unwrap": 1 215 | }, 216 | "command": { 217 | "unwrap": 1 218 | }, 219 | "ul": { 220 | "check_attributes": { 221 | "id": "any" 222 | } 223 | }, 224 | "progress": { 225 | "rename_tag": "span" 226 | }, 227 | "dfn": { 228 | "unwrap": 1 229 | }, 230 | "iframe": { 231 | "check_attributes": { 232 | "src": "any", 233 | "width": "any", 234 | "height": "any", 235 | "frameborder": "any", 236 | "style": "any", 237 | "id": "any" 238 | } 239 | }, 240 | "figcaption": { 241 | "unwrap": 1 242 | }, 243 | "a": { 244 | "check_attributes": { 245 | "href": "href", // if you compiled master manually then change this from 'url' to 'href' 246 | "rel": "any", 247 | "target": "any", 248 | "id": "any" 249 | } 250 | }, 251 | "img": { 252 | "one_of_type": { 253 | "valid_image_src": 1 254 | }, 255 | "check_attributes": { 256 | "width": "dimension", 257 | "alt": "alt", 258 | "src": "src", // if you compiled master manually then change this from 'url' to 'src' 259 | "height": "dimension", 260 | "id": "any" 261 | }, 262 | "add_class": { 263 | "align": "align_img" 264 | } 265 | }, 266 | "rb": { 267 | "unwrap": 1 268 | }, 269 | "footer": wysihtmlParserRulesDefaults.makeDiv, 270 | "noframes": { 271 | "remove": 1 272 | }, 273 | "abbr": { 274 | "unwrap": 1 275 | }, 276 | "u": {}, 277 | "bgsound": { 278 | "remove": 1 279 | }, 280 | "sup": {}, 281 | "address": { 282 | "unwrap": 1 283 | }, 284 | "basefont": { 285 | "remove": 1 286 | }, 287 | "nav": { 288 | "unwrap": 1 289 | }, 290 | "h1": wysihtmlParserRulesDefaults.blockLevelEl, 291 | "head": { 292 | "unwrap": 1 293 | }, 294 | "tbody": wysihtmlParserRulesDefaults.blockLevelEl, 295 | "dd": { 296 | "unwrap": 1 297 | }, 298 | "s": { 299 | "unwrap": 1 300 | }, 301 | "li": {}, 302 | "td": { 303 | "check_attributes": { 304 | "rowspan": "numbers", 305 | "colspan": "numbers", 306 | "valign": "any", 307 | "align": "any", 308 | "id": "any", 309 | "class": "any" 310 | }, 311 | "keep_styles": { 312 | "backgroundColor": 1, 313 | "width": 1, 314 | "height": 1 315 | }, 316 | "add_style": { 317 | "align": "align_text" 318 | } 319 | }, 320 | "object": { 321 | "remove": 1 322 | }, 323 | 324 | "div": { 325 | "one_of_type": { 326 | "alignment_object": 1 327 | }, 328 | "remove_action": "unwrap", 329 | "keep_styles": { 330 | "textAlign": 1, 331 | "float": 1 332 | }, 333 | "add_style": { 334 | "align": "align_text" 335 | }, 336 | "check_attributes": { 337 | "id": "any", 338 | "contenteditable": "any" 339 | } 340 | }, 341 | 342 | "option": { 343 | "remove":1 344 | }, 345 | "select": { 346 | "remove":1 347 | }, 348 | "i": {}, 349 | "track": { 350 | "remove": 1 351 | }, 352 | "wbr": { 353 | "remove": 1 354 | }, 355 | "fieldset": { 356 | "unwrap": 1 357 | }, 358 | "big": { 359 | "rename_tag": "span", 360 | "set_class": "wysiwyg-font-size-larger" 361 | }, 362 | "button": { 363 | "unwrap": 1 364 | }, 365 | "noscript": { 366 | "remove": 1 367 | }, 368 | "svg": { 369 | "remove": 1 370 | }, 371 | "input": { 372 | "remove": 1 373 | }, 374 | "table": { 375 | "keep_styles": { 376 | "width": 1, 377 | "textAlign": 1, 378 | "float": 1 379 | }, 380 | "check_attributes": { 381 | "id": "any" 382 | } 383 | }, 384 | "keygen": { 385 | "remove": 1 386 | }, 387 | "h5": wysihtmlParserRulesDefaults.blockLevelEl, 388 | "meta": { 389 | "remove": 1 390 | }, 391 | "map": { 392 | "remove": 1 393 | }, 394 | "isindex": { 395 | "remove": 1 396 | }, 397 | "mark": { 398 | "unwrap": 1 399 | }, 400 | "caption": wysihtmlParserRulesDefaults.blockLevelEl, 401 | "tfoot": wysihtmlParserRulesDefaults.blockLevelEl, 402 | "base": { 403 | "remove": 1 404 | }, 405 | "video": { 406 | "remove": 1 407 | }, 408 | "strong": {}, 409 | "canvas": { 410 | "remove": 1 411 | }, 412 | "output": { 413 | "unwrap": 1 414 | }, 415 | "marquee": { 416 | "unwrap": 1 417 | }, 418 | "b": {}, 419 | "q": { 420 | "check_attributes": { 421 | "cite": "url", 422 | "id": "any" 423 | } 424 | }, 425 | "applet": { 426 | "remove": 1 427 | }, 428 | "span": { 429 | "one_of_type": { 430 | "text_formatting_object": 1, 431 | "text_color_object": 1, 432 | "text_fontsize_object": 1 433 | }, 434 | "keep_styles": { 435 | "color": 1, 436 | "backgroundColor": 1, 437 | "fontSize": 1 438 | }, 439 | "remove_action": "unwrap", 440 | "check_attributes": { 441 | "id": "any" 442 | } 443 | }, 444 | "rp": { 445 | "unwrap": 1 446 | }, 447 | "spacer": { 448 | "remove": 1 449 | }, 450 | "source": { 451 | "remove": 1 452 | }, 453 | "aside": wysihtmlParserRulesDefaults.makeDiv, 454 | "frame": { 455 | "remove": 1 456 | }, 457 | "section": wysihtmlParserRulesDefaults.makeDiv, 458 | "body": { 459 | "unwrap": 1 460 | }, 461 | "ol": {}, 462 | "nobr": { 463 | "unwrap": 1 464 | }, 465 | "html": { 466 | "unwrap": 1 467 | }, 468 | "summary": { 469 | "unwrap": 1 470 | }, 471 | "var": { 472 | "unwrap": 1 473 | }, 474 | "del": { 475 | "unwrap": 1 476 | }, 477 | "blockquote": { 478 | "keep_styles": { 479 | "textAlign": 1, 480 | "float": 1 481 | }, 482 | "add_style": { 483 | "align": "align_text" 484 | }, 485 | "check_attributes": { 486 | "cite": "url", 487 | "id": "any" 488 | } 489 | }, 490 | "style": { 491 | "check_attributes": { 492 | "type": "any", 493 | "src": "any", 494 | "charset": "any" 495 | } 496 | }, 497 | "device": { 498 | "remove": 1 499 | }, 500 | "meter": { 501 | "unwrap": 1 502 | }, 503 | "h3": wysihtmlParserRulesDefaults.blockLevelEl, 504 | "textarea": { 505 | "unwrap": 1 506 | }, 507 | "embed": { 508 | "remove": 1 509 | }, 510 | "hgroup": { 511 | "unwrap": 1 512 | }, 513 | "font": { 514 | "rename_tag": "span", 515 | "add_class": { 516 | "size": "size_font" 517 | } 518 | }, 519 | "tt": { 520 | "unwrap": 1 521 | }, 522 | "noembed": { 523 | "remove": 1 524 | }, 525 | "thead": { 526 | "add_style": { 527 | "align": "align_text" 528 | }, 529 | "check_attributes": { 530 | "id": "any" 531 | } 532 | }, 533 | "blink": { 534 | "unwrap": 1 535 | }, 536 | "plaintext": { 537 | "unwrap": 1 538 | }, 539 | "xml": { 540 | "remove": 1 541 | }, 542 | "h6": wysihtmlParserRulesDefaults.blockLevelEl, 543 | "param": { 544 | "remove": 1 545 | }, 546 | "th": { 547 | "check_attributes": { 548 | "rowspan": "numbers", 549 | "colspan": "numbers", 550 | "valign": "any", 551 | "align": "any", 552 | "id": "any" 553 | }, 554 | "keep_styles": { 555 | "backgroundColor": 1, 556 | "width": 1, 557 | "height": 1 558 | }, 559 | "add_style": { 560 | "align": "align_text" 561 | } 562 | }, 563 | "legend": { 564 | "unwrap": 1 565 | }, 566 | "hr": {}, 567 | "label": { 568 | "unwrap": 1 569 | }, 570 | "dl": { 571 | "unwrap": 1 572 | }, 573 | "kbd": { 574 | "unwrap": 1 575 | }, 576 | "listing": { 577 | "unwrap": 1 578 | }, 579 | "dt": { 580 | "unwrap": 1 581 | }, 582 | "nextid": { 583 | "remove": 1 584 | }, 585 | "pre": {}, 586 | "center": wysihtmlParserRulesDefaults.makeDiv, 587 | "audio": { 588 | "remove": 1 589 | }, 590 | "datalist": { 591 | "unwrap": 1 592 | }, 593 | "samp": { 594 | "unwrap": 1 595 | }, 596 | "col": { 597 | "remove": 1 598 | }, 599 | "article": wysihtmlParserRulesDefaults.makeDiv, 600 | "cite": {}, 601 | "link": { 602 | "remove": 1 603 | }, 604 | "script": { 605 | "check_attributes": { 606 | "type": "any", 607 | "src": "any", 608 | "charset": "any" 609 | } 610 | }, 611 | "bdo": { 612 | "unwrap": 1 613 | }, 614 | "menu": { 615 | "rename_tag": "ul" 616 | }, 617 | "colgroup": { 618 | "remove": 1 619 | }, 620 | "ruby": { 621 | "unwrap": 1 622 | }, 623 | "h2": wysihtmlParserRulesDefaults.blockLevelEl, 624 | "ins": { 625 | "unwrap": 1 626 | }, 627 | "p": wysihtmlParserRulesDefaults.blockLevelEl, 628 | "sub": {}, 629 | "comment": { 630 | "remove": 1 631 | }, 632 | "frameset": { 633 | "remove": 1 634 | }, 635 | "optgroup": { 636 | "unwrap": 1 637 | }, 638 | "header": wysihtmlParserRulesDefaults.makeDiv 639 | } 640 | }; 641 | 642 | 643 | (function() { 644 | // Paste cleanup rules universal for all rules (also applied to content copied from editor) 645 | var commonRules = wysihtml.lang.object(wysihtmlParserRules).clone(true); 646 | commonRules.comments = false; 647 | commonRules.selectors = { "a u": "unwrap"}; 648 | commonRules.tags.style = { "remove": 1 }; 649 | commonRules.tags.script = { "remove": 1 }; 650 | commonRules.tags.head = { "remove": 1 }; 651 | 652 | // Paste cleanup for unindentified source 653 | var universalRules = wysihtml.lang.object(commonRules).clone(true); 654 | universalRules.tags.div.one_of_type.alignment_object = 1; 655 | universalRules.tags.div.remove_action = "unwrap"; 656 | universalRules.tags.div.check_attributes.style = false; 657 | universalRules.tags.div.keep_styles = { 658 | "textAlign": /^((left)|(right)|(center)|(justify))$/i, 659 | "float": 1 660 | }; 661 | universalRules.tags.span.keep_styles = false; 662 | 663 | // Paste cleanup for MS Office 664 | // TODO: should be extended to stricter ruleset, as current set will probably not cover all Office bizarreness 665 | var msOfficeRules = wysihtml.lang.object(universalRules).clone(true); 666 | msOfficeRules.classes = {}; 667 | 668 | window.wysihtmlParserPasteRulesets = [ 669 | { 670 | condition: //i, 674 | set: commonRules 675 | },{ 676 | set: universalRules 677 | } 678 | ]; 679 | 680 | })(); 681 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/parser_rules/advanced_unwrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Full HTML5 compatibility rule set 3 | * These rules define which tags and CSS classes are supported and which tags should be specially treated. 4 | * 5 | * Examples based on this rule set: 6 | * 7 | * foo 8 | * ... becomes ... 9 | * foo 10 | * 11 | * 12 | * ... becomes ... 13 | * 14 | * 15 | *
foo
16 | * ... becomes ... 17 | *
foo
18 | * 19 | * foo 20 | * ... becomes ... 21 | * foo 22 | * 23 | * foo
bar 24 | * ... becomes ... 25 | * foo
bar 26 | * 27 | *
hello
28 | * ... becomes ... 29 | *
hello
30 | * 31 | *
hello
32 | * ... becomes ... 33 | *
hello
34 | */ 35 | var wysihtmlParserRules = { 36 | /** 37 | * CSS Class white-list 38 | * Following CSS classes won't be removed when parsed by the wysihtml HTML parser 39 | * If all classes should pass "any" as classes value. Ex: "classes": "any" 40 | */ 41 | "classes": { 42 | "wysiwyg-clear-both": 1, 43 | "wysiwyg-clear-left": 1, 44 | "wysiwyg-clear-right": 1, 45 | "wysiwyg-color-aqua": 1, 46 | "wysiwyg-color-black": 1, 47 | "wysiwyg-color-blue": 1, 48 | "wysiwyg-color-fuchsia": 1, 49 | "wysiwyg-color-gray": 1, 50 | "wysiwyg-color-green": 1, 51 | "wysiwyg-color-lime": 1, 52 | "wysiwyg-color-maroon": 1, 53 | "wysiwyg-color-navy": 1, 54 | "wysiwyg-color-olive": 1, 55 | "wysiwyg-color-purple": 1, 56 | "wysiwyg-color-red": 1, 57 | "wysiwyg-color-silver": 1, 58 | "wysiwyg-color-teal": 1, 59 | "wysiwyg-color-white": 1, 60 | "wysiwyg-color-yellow": 1, 61 | "wysiwyg-float-left": 1, 62 | "wysiwyg-float-right": 1, 63 | "wysiwyg-font-size-large": 1, 64 | "wysiwyg-font-size-larger": 1, 65 | "wysiwyg-font-size-medium": 1, 66 | "wysiwyg-font-size-small": 1, 67 | "wysiwyg-font-size-smaller": 1, 68 | "wysiwyg-font-size-x-large": 1, 69 | "wysiwyg-font-size-x-small": 1, 70 | "wysiwyg-font-size-xx-large": 1, 71 | "wysiwyg-font-size-xx-small": 1, 72 | "wysiwyg-text-align-center": 1, 73 | "wysiwyg-text-align-justify": 1, 74 | "wysiwyg-text-align-left": 1, 75 | "wysiwyg-text-align-right": 1 76 | }, 77 | 78 | 79 | "type_definitions": { 80 | 81 | "visible_content_object": { 82 | "methods": { 83 | "has_visible_contet": 1 84 | } 85 | }, 86 | 87 | "alignment_object": { 88 | "classes": { 89 | "wysiwyg-text-align-center": 1, 90 | "wysiwyg-text-align-justify": 1, 91 | "wysiwyg-text-align-left": 1, 92 | "wysiwyg-text-align-right": 1, 93 | "wysiwyg-float-left": 1, 94 | "wysiwyg-float-right": 1 95 | }, 96 | "styles": { 97 | "float": ["left", "right"], 98 | "text-align": ["left", "right", "center"] 99 | } 100 | }, 101 | 102 | "valid_image_src": { 103 | "attrs": { 104 | "src": /^[^data\:]/i 105 | } 106 | }, 107 | 108 | "text_color_object": { 109 | "styles": { 110 | "color": true, 111 | "background-color": true 112 | } 113 | }, 114 | 115 | "text_fontsize_object": { 116 | "styles": { 117 | "font-size": true 118 | } 119 | }, 120 | 121 | "text_formatting_object": { 122 | "classes": { 123 | "wysiwyg-color-aqua": 1, 124 | "wysiwyg-color-black": 1, 125 | "wysiwyg-color-blue": 1, 126 | "wysiwyg-color-fuchsia": 1, 127 | "wysiwyg-color-gray": 1, 128 | "wysiwyg-color-green": 1, 129 | "wysiwyg-color-lime": 1, 130 | "wysiwyg-color-maroon": 1, 131 | "wysiwyg-color-navy": 1, 132 | "wysiwyg-color-olive": 1, 133 | "wysiwyg-color-purple": 1, 134 | "wysiwyg-color-red": 1, 135 | "wysiwyg-color-silver": 1, 136 | "wysiwyg-color-teal": 1, 137 | "wysiwyg-color-white": 1, 138 | "wysiwyg-color-yellow": 1, 139 | "wysiwyg-font-size-large": 1, 140 | "wysiwyg-font-size-larger": 1, 141 | "wysiwyg-font-size-medium": 1, 142 | "wysiwyg-font-size-small": 1, 143 | "wysiwyg-font-size-smaller": 1, 144 | "wysiwyg-font-size-x-large": 1, 145 | "wysiwyg-font-size-x-small": 1, 146 | "wysiwyg-font-size-xx-large": 1, 147 | "wysiwyg-font-size-xx-small": 1 148 | } 149 | } 150 | }, 151 | 152 | "comments": 1, // if set allows comments to pass 153 | 154 | /** 155 | * Tag list 156 | * 157 | * The following options are available: 158 | * 159 | * - add_class: converts and deletes the given HTML4 attribute (align, clear, ...) via the given method to a css class 160 | * The following methods are implemented in wysihtml.dom.parse: 161 | * - align_text: converts align attribute values (right/left/center/justify) to their corresponding css class "wysiwyg-text-align-*") 162 | *

foo

... becomes ...

foo

163 | * - clear_br: converts clear attribute values left/right/all/both to their corresponding css class "wysiwyg-clear-*" 164 | *
... becomes ...
165 | * - align_img: converts align attribute values (right/left) on to their corresponding css class "wysiwyg-float-*" 166 | * 167 | * - add_style: converts and deletes the given HTML4 attribute (align) via the given method to a css style 168 | * The following methods are implemented in wysihtml.dom.parse: 169 | * - align_text: converts align attribute values (right/left/center) to their corresponding css style) 170 | *

foo

... becomes ...

foo

171 | * 172 | * - remove: removes the element and its content 173 | * 174 | * - unwrap removes element but leaves content 175 | * 176 | * - rename_tag: renames the element to the given tag 177 | * 178 | * - set_class: adds the given class to the element (note: make sure that the class is in the "classes" white list above) 179 | * 180 | * - set_attributes: sets/overrides the given attributes 181 | * 182 | * - check_attributes: checks the given HTML attribute via the given method 183 | * - url: allows only valid urls (starting with http:// or https://) 184 | * - src: allows something like "/foobar.jpg", "http://google.com", ... 185 | * - href: allows something like "mailto:bert@foo.com", "http://google.com", "/foobar.jpg" 186 | * - alt: strips unwanted characters. if the attribute is not set, then it gets set (to ensure valid and compatible HTML) 187 | * - numbers: ensures that the attribute only contains numeric (integer) characters (no float values or units) 188 | * - dimension: for with/height attributes where floating point numbrs and percentages are allowed 189 | * - any: allows anything to pass 190 | */ 191 | "tags": { 192 | "tr": { 193 | "add_class": { 194 | "align": "align_text" 195 | } 196 | }, 197 | "strike": { 198 | "unwrap": 1 199 | }, 200 | "form": { 201 | "unwrap": 1 202 | }, 203 | "rt": { 204 | "rename_tag": "span" 205 | }, 206 | "code": {}, 207 | "acronym": { 208 | "rename_tag": "span" 209 | }, 210 | "br": { 211 | "add_class": { 212 | "clear": "clear_br" 213 | } 214 | }, 215 | "details": { 216 | "unwrap": 1 217 | }, 218 | "h4": { 219 | "add_class": { 220 | "align": "align_text" 221 | } 222 | }, 223 | "em": {}, 224 | "title": { 225 | "remove": 1 226 | }, 227 | "multicol": { 228 | "unwrap": 1 229 | }, 230 | "figure": { 231 | "unwrap": 1 232 | }, 233 | "xmp": { 234 | "unwrap": 1 235 | }, 236 | "small": { 237 | "rename_tag": "span", 238 | "set_class": "wysiwyg-font-size-smaller" 239 | }, 240 | "area": { 241 | "remove": 1 242 | }, 243 | "time": { 244 | "unwrap": 1 245 | }, 246 | "dir": { 247 | "rename_tag": "ul" 248 | }, 249 | "bdi": { 250 | "unwrap": 1 251 | }, 252 | "command": { 253 | "unwrap": 1 254 | }, 255 | "ul": {}, 256 | "progress": { 257 | "rename_tag": "span" 258 | }, 259 | "dfn": { 260 | "unwrap": 1 261 | }, 262 | "iframe": { 263 | "remove": 1 264 | }, 265 | "figcaption": { 266 | "unwrap": 1 267 | }, 268 | "a": { 269 | "check_attributes": { 270 | "href": "href", // if you compiled master manually then change this from 'url' to 'href' 271 | "target": "any" 272 | }, 273 | "set_attributes": { 274 | "rel": "nofollow" 275 | } 276 | }, 277 | "img": { 278 | "one_of_type": { 279 | "valid_image_src": 1 280 | }, 281 | "check_attributes": { 282 | "width": "dimension", 283 | "alt": "alt", 284 | "src": "src", // if you compiled master manually then change this from 'url' to 'src' 285 | "height": "dimension" 286 | }, 287 | "add_class": { 288 | "align": "align_img" 289 | } 290 | }, 291 | "rb": { 292 | "unwrap": 1 293 | }, 294 | "footer": { 295 | "rename_tag": "div" 296 | }, 297 | "noframes": { 298 | "remove": 1 299 | }, 300 | "abbr": { 301 | "unwrap": 1 302 | }, 303 | "u": {}, 304 | "bgsound": { 305 | "remove": 1 306 | }, 307 | "sup": {}, 308 | "address": { 309 | "unwrap": 1 310 | }, 311 | "basefont": { 312 | "remove": 1 313 | }, 314 | "nav": { 315 | "unwrap": 1 316 | }, 317 | "h1": { 318 | "add_class": { 319 | "align": "align_text" 320 | } 321 | }, 322 | "head": { 323 | "unwrap": 1 324 | }, 325 | "tbody": { 326 | "add_class": { 327 | "align": "align_text" 328 | } 329 | }, 330 | "dd": { 331 | "unwrap": 1 332 | }, 333 | "s": { 334 | "unwrap": 1 335 | }, 336 | "li": {}, 337 | "td": { 338 | "check_attributes": { 339 | "rowspan": "numbers", 340 | "colspan": "numbers", 341 | "valign": "any", 342 | "align": "any" 343 | }, 344 | "add_class": { 345 | "align": "align_text" 346 | } 347 | }, 348 | "object": { 349 | "remove": 1 350 | }, 351 | 352 | "div": { 353 | "one_of_type": { 354 | "visible_content_object": 1 355 | }, 356 | "remove_action": "unwrap", 357 | "keep_styles": { 358 | "textAlign": 1, 359 | "float": 1 360 | }, 361 | "add_class": { 362 | "align": "align_text" 363 | } 364 | }, 365 | 366 | "option": { 367 | "remove":1 368 | }, 369 | "select": { 370 | "remove":1 371 | }, 372 | "i": {}, 373 | "track": { 374 | "remove": 1 375 | }, 376 | "wbr": { 377 | "remove": 1 378 | }, 379 | "fieldset": { 380 | "unwrap": 1 381 | }, 382 | "big": { 383 | "rename_tag": "span", 384 | "set_class": "wysiwyg-font-size-larger" 385 | }, 386 | "button": { 387 | "unwrap": 1 388 | }, 389 | "noscript": { 390 | "remove": 1 391 | }, 392 | "svg": { 393 | "remove": 1 394 | }, 395 | "input": { 396 | "remove": 1 397 | }, 398 | "table": {}, 399 | "keygen": { 400 | "remove": 1 401 | }, 402 | "h5": { 403 | "add_class": { 404 | "align": "align_text" 405 | } 406 | }, 407 | "meta": { 408 | "remove": 1 409 | }, 410 | "map": { 411 | "remove": 1 412 | }, 413 | "isindex": { 414 | "remove": 1 415 | }, 416 | "mark": { 417 | "unwrap": 1 418 | }, 419 | "caption": { 420 | "add_class": { 421 | "align": "align_text" 422 | } 423 | }, 424 | "tfoot": { 425 | "add_class": { 426 | "align": "align_text" 427 | } 428 | }, 429 | "base": { 430 | "remove": 1 431 | }, 432 | "video": { 433 | "remove": 1 434 | }, 435 | "strong": {}, 436 | "canvas": { 437 | "remove": 1 438 | }, 439 | "output": { 440 | "unwrap": 1 441 | }, 442 | "marquee": { 443 | "unwrap": 1 444 | }, 445 | "b": {}, 446 | "q": { 447 | "check_attributes": { 448 | "cite": "url" 449 | } 450 | }, 451 | "applet": { 452 | "remove": 1 453 | }, 454 | "span": { 455 | "one_of_type": { 456 | "text_formatting_object": 1, 457 | "text_color_object": 1, 458 | "text_fontsize_object": 1 459 | }, 460 | "keep_styles": { 461 | "color": 1, 462 | "backgroundColor": 1, 463 | "fontSize": 1 464 | }, 465 | "remove_action": "unwrap" 466 | }, 467 | "rp": { 468 | "unwrap": 1 469 | }, 470 | "spacer": { 471 | "remove": 1 472 | }, 473 | "source": { 474 | "remove": 1 475 | }, 476 | "aside": { 477 | "rename_tag": "div" 478 | }, 479 | "frame": { 480 | "remove": 1 481 | }, 482 | "section": { 483 | "rename_tag": "div" 484 | }, 485 | "body": { 486 | "unwrap": 1 487 | }, 488 | "ol": {}, 489 | "nobr": { 490 | "unwrap": 1 491 | }, 492 | "html": { 493 | "unwrap": 1 494 | }, 495 | "summary": { 496 | "unwrap": 1 497 | }, 498 | "var": { 499 | "unwrap": 1 500 | }, 501 | "del": { 502 | "unwrap": 1 503 | }, 504 | "blockquote": { 505 | "check_attributes": { 506 | "cite": "url" 507 | } 508 | }, 509 | "style": { 510 | "remove": 1 511 | }, 512 | "device": { 513 | "remove": 1 514 | }, 515 | "meter": { 516 | "unwrap": 1 517 | }, 518 | "h3": { 519 | "add_class": { 520 | "align": "align_text" 521 | } 522 | }, 523 | "textarea": { 524 | "unwrap": 1 525 | }, 526 | "embed": { 527 | "remove": 1 528 | }, 529 | "hgroup": { 530 | "unwrap": 1 531 | }, 532 | "font": { 533 | "rename_tag": "span", 534 | "add_class": { 535 | "size": "size_font" 536 | } 537 | }, 538 | "tt": { 539 | "unwrap": 1 540 | }, 541 | "noembed": { 542 | "remove": 1 543 | }, 544 | "thead": { 545 | "add_class": { 546 | "align": "align_text" 547 | } 548 | }, 549 | "blink": { 550 | "unwrap": 1 551 | }, 552 | "plaintext": { 553 | "unwrap": 1 554 | }, 555 | "xml": { 556 | "remove": 1 557 | }, 558 | "h6": { 559 | "add_class": { 560 | "align": "align_text" 561 | } 562 | }, 563 | "param": { 564 | "remove": 1 565 | }, 566 | "th": { 567 | "check_attributes": { 568 | "rowspan": "numbers", 569 | "colspan": "numbers" 570 | }, 571 | "add_class": { 572 | "align": "align_text" 573 | } 574 | }, 575 | "legend": { 576 | "unwrap": 1 577 | }, 578 | "hr": {}, 579 | "label": { 580 | "unwrap": 1 581 | }, 582 | "dl": { 583 | "unwrap": 1 584 | }, 585 | "kbd": { 586 | "unwrap": 1 587 | }, 588 | "listing": { 589 | "unwrap": 1 590 | }, 591 | "dt": { 592 | "unwrap": 1 593 | }, 594 | "nextid": { 595 | "remove": 1 596 | }, 597 | "pre": {}, 598 | "center": { 599 | "rename_tag": "div", 600 | "set_class": "wysiwyg-text-align-center" 601 | }, 602 | "audio": { 603 | "remove": 1 604 | }, 605 | "datalist": { 606 | "unwrap": 1 607 | }, 608 | "samp": { 609 | "unwrap": 1 610 | }, 611 | "col": { 612 | "remove": 1 613 | }, 614 | "article": { 615 | "rename_tag": "div" 616 | }, 617 | "cite": {}, 618 | "link": { 619 | "remove": 1 620 | }, 621 | "script": { 622 | "remove": 1 623 | }, 624 | "bdo": { 625 | "unwrap": 1 626 | }, 627 | "menu": { 628 | "rename_tag": "ul" 629 | }, 630 | "colgroup": { 631 | "remove": 1 632 | }, 633 | "ruby": { 634 | "unwrap": 1 635 | }, 636 | "h2": { 637 | "add_class": { 638 | "align": "align_text" 639 | } 640 | }, 641 | "ins": { 642 | "unwrap": 1 643 | }, 644 | "p": { 645 | "add_class": { 646 | "align": "align_text" 647 | } 648 | }, 649 | "sub": {}, 650 | "comment": { 651 | "remove": 1 652 | }, 653 | "frameset": { 654 | "remove": 1 655 | }, 656 | "optgroup": { 657 | "unwrap": 1 658 | }, 659 | "header": { 660 | "rename_tag": "div" 661 | } 662 | } 663 | }; 664 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/parser_rules/simple.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Very simple basic rule set 3 | * 4 | * Allows 5 | * , , , ,

,

, ,
, ,
    ,
      ,
    • 6 | * 7 | * For a proper documentation of the format check advanced.js 8 | */ 9 | var wysihtmlParserRules = { 10 | tags: { 11 | strong: {}, 12 | b: {}, 13 | i: {}, 14 | em: {}, 15 | br: {}, 16 | p: {}, 17 | div: {}, 18 | span: {}, 19 | ul: {}, 20 | ol: {}, 21 | li: {}, 22 | a: { 23 | set_attributes: { 24 | target: "_blank", 25 | rel: "nofollow" 26 | }, 27 | check_attributes: { 28 | href: "url" // important to avoid XSS 29 | } 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/table_editing.js: -------------------------------------------------------------------------------- 1 | wysihtml.commands.addTableCells = { 2 | exec: function(composer, command, value) { 3 | if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) { 4 | 5 | // switches start and end if start is bigger than end (reverse selection) 6 | var tableSelect = wysihtml.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end); 7 | if (value == 'before' || value == 'above') { 8 | wysihtml.dom.table.addCells(tableSelect.start, value); 9 | } else if (value == 'after' || value == 'below') { 10 | wysihtml.dom.table.addCells(tableSelect.end, value); 11 | } 12 | setTimeout(function() { 13 | composer.tableSelection.select(tableSelect.start, tableSelect.end); 14 | },0); 15 | } 16 | }, 17 | 18 | state: function(composer, command) { 19 | return false; 20 | } 21 | }; 22 | 23 | wysihtml.commands.createTable = { 24 | exec: function(composer, command, value) { 25 | var col, row, html; 26 | if (value && value.cols && value.rows && parseInt(value.cols, 10) > 0 && parseInt(value.rows, 10) > 0) { 27 | if (value.tableStyle) { 28 | html = ''; 29 | } else { 30 | html = '
      '; 31 | } 32 | html += ''; 33 | for (row = 0; row < value.rows; row ++) { 34 | html += ''; 35 | for (col = 0; col < value.cols; col ++) { 36 | html += ''; 37 | } 38 | html += ''; 39 | } 40 | html += '

      '; 41 | composer.commands.exec('insertHTML', html); 42 | } 43 | }, 44 | 45 | state: function(composer, command) { 46 | return false; 47 | } 48 | }; 49 | 50 | wysihtml.commands.deleteTableCells = { 51 | exec: function(composer, command, value) { 52 | if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) { 53 | var tableSelect = wysihtml.dom.table.orderSelectionEnds(composer.tableSelection.start, composer.tableSelection.end), 54 | idx = wysihtml.dom.table.indexOf(tableSelect.start), 55 | selCell, 56 | table = composer.tableSelection.table; 57 | 58 | wysihtml.dom.table.removeCells(tableSelect.start, value); 59 | setTimeout(function() { 60 | // move selection to next or previous if not present 61 | selCell = wysihtml.dom.table.findCell(table, idx); 62 | 63 | if (!selCell) { 64 | if (value == 'row') { 65 | selCell = wysihtml.dom.table.findCell(table, { 66 | 'row': idx.row - 1, 67 | 'col': idx.col 68 | }); 69 | } 70 | 71 | if (value == 'column') { 72 | selCell = wysihtml.dom.table.findCell(table, { 73 | 'row': idx.row, 74 | 'col': idx.col - 1 75 | }); 76 | } 77 | } 78 | if (selCell) { 79 | composer.tableSelection.select(selCell, selCell); 80 | } 81 | }, 0); 82 | } 83 | }, 84 | 85 | state: function(composer, command) { 86 | return false; 87 | } 88 | }; 89 | 90 | wysihtml.commands.mergeTableCells = { 91 | exec: function(composer, command) { 92 | if (composer.tableSelection && composer.tableSelection.start && composer.tableSelection.end) { 93 | if (this.state(composer, command)) { 94 | wysihtml.dom.table.unmergeCell(composer.tableSelection.start); 95 | } else { 96 | wysihtml.dom.table.mergeCellsBetween(composer.tableSelection.start, composer.tableSelection.end); 97 | } 98 | } 99 | }, 100 | 101 | state: function(composer, command) { 102 | if (composer.tableSelection) { 103 | var start = composer.tableSelection.start, 104 | end = composer.tableSelection.end; 105 | if (start && end && start == end && 106 | (( 107 | wysihtml.dom.getAttribute(start, 'colspan') && 108 | parseInt(wysihtml.dom.getAttribute(start, 'colspan'), 10) > 1 109 | ) || ( 110 | wysihtml.dom.getAttribute(start, 'rowspan') && 111 | parseInt(wysihtml.dom.getAttribute(start, 'rowspan'), 10) > 1 112 | )) 113 | ) { 114 | return [start]; 115 | } 116 | } 117 | return false; 118 | } 119 | }; 120 | 121 | (function() { 122 | 123 | var api = wysihtml.dom; 124 | 125 | var MapCell = function(cell) { 126 | this.el = cell; 127 | this.isColspan= false; 128 | this.isRowspan= false; 129 | this.firstCol= true; 130 | this.lastCol= true; 131 | this.firstRow= true; 132 | this.lastRow= true; 133 | this.isReal= true; 134 | this.spanCollection= []; 135 | this.modified = false; 136 | }; 137 | 138 | var TableModifyerByCell = function (cell, table) { 139 | if (cell) { 140 | this.cell = cell; 141 | this.table = api.getParentElement(cell, { query: "table" }); 142 | } else if (table) { 143 | this.table = table; 144 | this.cell = this.table.querySelectorAll('th, td')[0]; 145 | } 146 | }; 147 | 148 | function queryInList(list, query) { 149 | var ret = [], 150 | q; 151 | for (var e = 0, len = list.length; e < len; e++) { 152 | q = list[e].querySelectorAll(query); 153 | if (q) { 154 | for(var i = q.length; i--; ret.unshift(q[i])); 155 | } 156 | } 157 | return ret; 158 | } 159 | 160 | function removeElement(el) { 161 | el.parentNode.removeChild(el); 162 | } 163 | 164 | function insertAfter(referenceNode, newNode) { 165 | referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); 166 | } 167 | 168 | function nextNode(node, tag) { 169 | var element = node.nextSibling; 170 | while (element.nodeType !=1) { 171 | element = element.nextSibling; 172 | if (!tag || tag == element.tagName.toLowerCase()) { 173 | return element; 174 | } 175 | } 176 | return null; 177 | } 178 | 179 | TableModifyerByCell.prototype = { 180 | 181 | addSpannedCellToMap: function(cell, map, r, c, cspan, rspan) { 182 | var spanCollect = [], 183 | rmax = r + ((rspan) ? parseInt(rspan, 10) - 1 : 0), 184 | cmax = c + ((cspan) ? parseInt(cspan, 10) - 1 : 0); 185 | 186 | for (var rr = r; rr <= rmax; rr++) { 187 | if (typeof map[rr] == "undefined") { map[rr] = []; } 188 | for (var cc = c; cc <= cmax; cc++) { 189 | map[rr][cc] = new MapCell(cell); 190 | map[rr][cc].isColspan = (cspan && parseInt(cspan, 10) > 1); 191 | map[rr][cc].isRowspan = (rspan && parseInt(rspan, 10) > 1); 192 | map[rr][cc].firstCol = cc == c; 193 | map[rr][cc].lastCol = cc == cmax; 194 | map[rr][cc].firstRow = rr == r; 195 | map[rr][cc].lastRow = rr == rmax; 196 | map[rr][cc].isReal = cc == c && rr == r; 197 | map[rr][cc].spanCollection = spanCollect; 198 | 199 | spanCollect.push(map[rr][cc]); 200 | } 201 | } 202 | }, 203 | 204 | setCellAsModified: function(cell) { 205 | cell.modified = true; 206 | if (cell.spanCollection.length > 0) { 207 | for (var s = 0, smax = cell.spanCollection.length; s < smax; s++) { 208 | cell.spanCollection[s].modified = true; 209 | } 210 | } 211 | }, 212 | 213 | setTableMap: function() { 214 | var map = []; 215 | var tableRows = this.getTableRows(), 216 | ridx, row, cells, cidx, cell, 217 | c, 218 | cspan, rspan; 219 | 220 | for (ridx = 0; ridx < tableRows.length; ridx++) { 221 | row = tableRows[ridx]; 222 | cells = this.getRowCells(row); 223 | c = 0; 224 | if (typeof map[ridx] == "undefined") { map[ridx] = []; } 225 | for (cidx = 0; cidx < cells.length; cidx++) { 226 | cell = cells[cidx]; 227 | 228 | // If cell allready set means it is set by col or rowspan, 229 | // so increase cols index until free col is found 230 | while (typeof map[ridx][c] != "undefined") { c++; } 231 | 232 | cspan = api.getAttribute(cell, 'colspan'); 233 | rspan = api.getAttribute(cell, 'rowspan'); 234 | 235 | if (cspan || rspan) { 236 | this.addSpannedCellToMap(cell, map, ridx, c, cspan, rspan); 237 | c = c + ((cspan) ? parseInt(cspan, 10) : 1); 238 | } else { 239 | map[ridx][c] = new MapCell(cell); 240 | c++; 241 | } 242 | } 243 | } 244 | this.map = map; 245 | return map; 246 | }, 247 | 248 | getRowCells: function(row) { 249 | var inlineTables = this.table.querySelectorAll('table'), 250 | inlineCells = (inlineTables) ? queryInList(inlineTables, 'th, td') : [], 251 | allCells = row.querySelectorAll('th, td'), 252 | tableCells = (inlineCells.length > 0) ? wysihtml.lang.array(allCells).without(inlineCells) : allCells; 253 | 254 | return tableCells; 255 | }, 256 | 257 | getTableRows: function() { 258 | var inlineTables = this.table.querySelectorAll('table'), 259 | inlineRows = (inlineTables) ? queryInList(inlineTables, 'tr') : [], 260 | allRows = this.table.querySelectorAll('tr'), 261 | tableRows = (inlineRows.length > 0) ? wysihtml.lang.array(allRows).without(inlineRows) : allRows; 262 | 263 | return tableRows; 264 | }, 265 | 266 | getMapIndex: function(cell) { 267 | var r_length = this.map.length, 268 | c_length = (this.map && this.map[0]) ? this.map[0].length : 0; 269 | 270 | for (var r_idx = 0;r_idx < r_length; r_idx++) { 271 | for (var c_idx = 0;c_idx < c_length; c_idx++) { 272 | if (this.map[r_idx][c_idx].el === cell) { 273 | return {'row': r_idx, 'col': c_idx}; 274 | } 275 | } 276 | } 277 | return false; 278 | }, 279 | 280 | getElementAtIndex: function(idx) { 281 | this.setTableMap(); 282 | if (this.map[idx.row] && this.map[idx.row][idx.col] && this.map[idx.row][idx.col].el) { 283 | return this.map[idx.row][idx.col].el; 284 | } 285 | return null; 286 | }, 287 | 288 | getMapElsTo: function(to_cell) { 289 | var els = []; 290 | this.setTableMap(); 291 | this.idx_start = this.getMapIndex(this.cell); 292 | this.idx_end = this.getMapIndex(to_cell); 293 | 294 | // switch indexes if start is bigger than end 295 | if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) { 296 | var temp_idx = this.idx_start; 297 | this.idx_start = this.idx_end; 298 | this.idx_end = temp_idx; 299 | } 300 | if (this.idx_start.col > this.idx_end.col) { 301 | var temp_cidx = this.idx_start.col; 302 | this.idx_start.col = this.idx_end.col; 303 | this.idx_end.col = temp_cidx; 304 | } 305 | 306 | if (this.idx_start != null && this.idx_end != null) { 307 | for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) { 308 | for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) { 309 | els.push(this.map[row][col].el); 310 | } 311 | } 312 | } 313 | return els; 314 | }, 315 | 316 | orderSelectionEnds: function(secondcell) { 317 | this.setTableMap(); 318 | this.idx_start = this.getMapIndex(this.cell); 319 | this.idx_end = this.getMapIndex(secondcell); 320 | 321 | // switch indexes if start is bigger than end 322 | if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) { 323 | var temp_idx = this.idx_start; 324 | this.idx_start = this.idx_end; 325 | this.idx_end = temp_idx; 326 | } 327 | if (this.idx_start.col > this.idx_end.col) { 328 | var temp_cidx = this.idx_start.col; 329 | this.idx_start.col = this.idx_end.col; 330 | this.idx_end.col = temp_cidx; 331 | } 332 | 333 | return { 334 | "start": this.map[this.idx_start.row][this.idx_start.col].el, 335 | "end": this.map[this.idx_end.row][this.idx_end.col].el 336 | }; 337 | }, 338 | 339 | createCells: function(tag, nr, attrs) { 340 | var doc = this.table.ownerDocument, 341 | frag = doc.createDocumentFragment(), 342 | cell; 343 | for (var i = 0; i < nr; i++) { 344 | cell = doc.createElement(tag); 345 | 346 | if (attrs) { 347 | for (var attr in attrs) { 348 | if (attrs.hasOwnProperty(attr)) { 349 | cell.setAttribute(attr, attrs[attr]); 350 | } 351 | } 352 | } 353 | 354 | // add non breaking space 355 | cell.appendChild(document.createTextNode("\u00a0")); 356 | frag.appendChild(cell); 357 | } 358 | return frag; 359 | }, 360 | 361 | // Returns next real cell (not part of spanned cell unless first) on row if selected index is not real. I no real cells -1 will be returned 362 | correctColIndexForUnreals: function(col, row) { 363 | var r = this.map[row], 364 | corrIdx = -1; 365 | for (var i = 0, max = col; i < col; i++) { 366 | if (r[i].isReal){ 367 | corrIdx++; 368 | } 369 | } 370 | return corrIdx; 371 | }, 372 | 373 | getLastNewCellOnRow: function(row, rowLimit) { 374 | var cells = this.getRowCells(row), 375 | cell, idx; 376 | 377 | for (var cidx = 0, cmax = cells.length; cidx < cmax; cidx++) { 378 | cell = cells[cidx]; 379 | idx = this.getMapIndex(cell); 380 | if (idx === false || (typeof rowLimit != "undefined" && idx.row != rowLimit)) { 381 | return cell; 382 | } 383 | } 384 | return null; 385 | }, 386 | 387 | removeEmptyTable: function() { 388 | var cells = this.table.querySelectorAll('td, th'); 389 | if (!cells || cells.length == 0) { 390 | removeElement(this.table); 391 | return true; 392 | } else { 393 | return false; 394 | } 395 | }, 396 | 397 | // Splits merged cell on row to unique cells 398 | splitRowToCells: function(cell) { 399 | if (cell.isColspan) { 400 | var colspan = parseInt(api.getAttribute(cell.el, 'colspan') || 1, 10), 401 | cType = cell.el.tagName.toLowerCase(); 402 | if (colspan > 1) { 403 | var newCells = this.createCells(cType, colspan -1); 404 | insertAfter(cell.el, newCells); 405 | } 406 | cell.el.removeAttribute('colspan'); 407 | } 408 | }, 409 | 410 | getRealRowEl: function(force, idx) { 411 | var r = null, 412 | c = null; 413 | 414 | idx = idx || this.idx; 415 | 416 | for (var cidx = 0, cmax = this.map[idx.row].length; cidx < cmax; cidx++) { 417 | c = this.map[idx.row][cidx]; 418 | if (c.isReal) { 419 | r = api.getParentElement(c.el, { query: "tr" }); 420 | if (r) { 421 | return r; 422 | } 423 | } 424 | } 425 | 426 | if (r === null && force) { 427 | r = api.getParentElement(this.map[idx.row][idx.col].el, { query: "tr" }) || null; 428 | } 429 | 430 | return r; 431 | }, 432 | 433 | injectRowAt: function(row, col, colspan, cType, c) { 434 | var r = this.getRealRowEl(false, {'row': row, 'col': col}), 435 | new_cells = this.createCells(cType, colspan); 436 | 437 | if (r) { 438 | var n_cidx = this.correctColIndexForUnreals(col, row); 439 | if (n_cidx >= 0) { 440 | insertAfter(this.getRowCells(r)[n_cidx], new_cells); 441 | } else { 442 | r.insertBefore(new_cells, r.firstChild); 443 | } 444 | } else { 445 | var rr = this.table.ownerDocument.createElement('tr'); 446 | rr.appendChild(new_cells); 447 | insertAfter(api.getParentElement(c.el, { query: "tr" }), rr); 448 | } 449 | }, 450 | 451 | canMerge: function(to) { 452 | this.to = to; 453 | this.setTableMap(); 454 | this.idx_start = this.getMapIndex(this.cell); 455 | this.idx_end = this.getMapIndex(this.to); 456 | 457 | // switch indexes if start is bigger than end 458 | if (this.idx_start.row > this.idx_end.row || (this.idx_start.row == this.idx_end.row && this.idx_start.col > this.idx_end.col)) { 459 | var temp_idx = this.idx_start; 460 | this.idx_start = this.idx_end; 461 | this.idx_end = temp_idx; 462 | } 463 | if (this.idx_start.col > this.idx_end.col) { 464 | var temp_cidx = this.idx_start.col; 465 | this.idx_start.col = this.idx_end.col; 466 | this.idx_end.col = temp_cidx; 467 | } 468 | 469 | for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) { 470 | for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) { 471 | if (this.map[row][col].isColspan || this.map[row][col].isRowspan) { 472 | return false; 473 | } 474 | } 475 | } 476 | return true; 477 | }, 478 | 479 | decreaseCellSpan: function(cell, span) { 480 | var nr = parseInt(api.getAttribute(cell.el, span), 10) - 1; 481 | if (nr >= 1) { 482 | cell.el.setAttribute(span, nr); 483 | } else { 484 | cell.el.removeAttribute(span); 485 | if (span == 'colspan') { 486 | cell.isColspan = false; 487 | } 488 | if (span == 'rowspan') { 489 | cell.isRowspan = false; 490 | } 491 | cell.firstCol = true; 492 | cell.lastCol = true; 493 | cell.firstRow = true; 494 | cell.lastRow = true; 495 | cell.isReal = true; 496 | } 497 | }, 498 | 499 | removeSurplusLines: function() { 500 | var row, cell, ridx, rmax, cidx, cmax, allRowspan; 501 | 502 | this.setTableMap(); 503 | if (this.map) { 504 | ridx = 0; 505 | rmax = this.map.length; 506 | for (;ridx < rmax; ridx++) { 507 | row = this.map[ridx]; 508 | allRowspan = true; 509 | cidx = 0; 510 | cmax = row.length; 511 | for (; cidx < cmax; cidx++) { 512 | cell = row[cidx]; 513 | if (!(api.getAttribute(cell.el, "rowspan") && parseInt(api.getAttribute(cell.el, "rowspan"), 10) > 1 && cell.firstRow !== true)) { 514 | allRowspan = false; 515 | break; 516 | } 517 | } 518 | if (allRowspan) { 519 | cidx = 0; 520 | for (; cidx < cmax; cidx++) { 521 | this.decreaseCellSpan(row[cidx], 'rowspan'); 522 | } 523 | } 524 | } 525 | 526 | // remove rows without cells 527 | var tableRows = this.getTableRows(); 528 | ridx = 0; 529 | rmax = tableRows.length; 530 | for (;ridx < rmax; ridx++) { 531 | row = tableRows[ridx]; 532 | if (row.childNodes.length == 0 && (/^\s*$/.test(row.textContent || row.innerText))) { 533 | removeElement(row); 534 | } 535 | } 536 | } 537 | }, 538 | 539 | fillMissingCells: function() { 540 | var r_max = 0, 541 | c_max = 0, 542 | prevcell = null; 543 | 544 | this.setTableMap(); 545 | if (this.map) { 546 | 547 | // find maximal dimensions of broken table 548 | r_max = this.map.length; 549 | for (var ridx = 0; ridx < r_max; ridx++) { 550 | if (this.map[ridx].length > c_max) { c_max = this.map[ridx].length; } 551 | } 552 | 553 | for (var row = 0; row < r_max; row++) { 554 | for (var col = 0; col < c_max; col++) { 555 | if (this.map[row] && !this.map[row][col]) { 556 | if (col > 0) { 557 | this.map[row][col] = new MapCell(this.createCells('td', 1)); 558 | prevcell = this.map[row][col-1]; 559 | if (prevcell && prevcell.el && prevcell.el.parent) { // if parent does not exist element is removed from dom 560 | insertAfter(this.map[row][col-1].el, this.map[row][col].el); 561 | } 562 | } 563 | } 564 | } 565 | } 566 | } 567 | }, 568 | 569 | rectify: function() { 570 | if (!this.removeEmptyTable()) { 571 | this.removeSurplusLines(); 572 | this.fillMissingCells(); 573 | return true; 574 | } else { 575 | return false; 576 | } 577 | }, 578 | 579 | unmerge: function() { 580 | if (this.rectify()) { 581 | this.setTableMap(); 582 | this.idx = this.getMapIndex(this.cell); 583 | 584 | if (this.idx) { 585 | var thisCell = this.map[this.idx.row][this.idx.col], 586 | colspan = (api.getAttribute(thisCell.el, "colspan")) ? parseInt(api.getAttribute(thisCell.el, "colspan"), 10) : 1, 587 | cType = thisCell.el.tagName.toLowerCase(); 588 | 589 | if (thisCell.isRowspan) { 590 | var rowspan = parseInt(api.getAttribute(thisCell.el, "rowspan"), 10); 591 | if (rowspan > 1) { 592 | for (var nr = 1, maxr = rowspan - 1; nr <= maxr; nr++){ 593 | this.injectRowAt(this.idx.row + nr, this.idx.col, colspan, cType, thisCell); 594 | } 595 | } 596 | thisCell.el.removeAttribute('rowspan'); 597 | } 598 | this.splitRowToCells(thisCell); 599 | } 600 | } 601 | }, 602 | 603 | // merges cells from start cell (defined in creating obj) to "to" cell 604 | merge: function(to) { 605 | if (this.rectify()) { 606 | if (this.canMerge(to)) { 607 | var rowspan = this.idx_end.row - this.idx_start.row + 1, 608 | colspan = this.idx_end.col - this.idx_start.col + 1; 609 | 610 | for (var row = this.idx_start.row, maxr = this.idx_end.row; row <= maxr; row++) { 611 | for (var col = this.idx_start.col, maxc = this.idx_end.col; col <= maxc; col++) { 612 | 613 | if (row == this.idx_start.row && col == this.idx_start.col) { 614 | if (rowspan > 1) { 615 | this.map[row][col].el.setAttribute('rowspan', rowspan); 616 | } 617 | if (colspan > 1) { 618 | this.map[row][col].el.setAttribute('colspan', colspan); 619 | } 620 | } else { 621 | // transfer content 622 | if (!(/^\s*\s*$/.test(this.map[row][col].el.innerHTML.toLowerCase()))) { 623 | this.map[this.idx_start.row][this.idx_start.col].el.innerHTML += ' ' + this.map[row][col].el.innerHTML; 624 | } 625 | removeElement(this.map[row][col].el); 626 | } 627 | 628 | } 629 | } 630 | this.rectify(); 631 | } else { 632 | if (window.console) { 633 | console.log('Do not know how to merge allready merged cells.'); 634 | } 635 | } 636 | } 637 | }, 638 | 639 | // Decreases rowspan of a cell if it is done on first cell of rowspan row (real cell) 640 | // Cell is moved to next row (if it is real) 641 | collapseCellToNextRow: function(cell) { 642 | var cellIdx = this.getMapIndex(cell.el), 643 | newRowIdx = cellIdx.row + 1, 644 | newIdx = {'row': newRowIdx, 'col': cellIdx.col}; 645 | 646 | if (newRowIdx < this.map.length) { 647 | 648 | var row = this.getRealRowEl(false, newIdx); 649 | if (row !== null) { 650 | var n_cidx = this.correctColIndexForUnreals(newIdx.col, newIdx.row); 651 | if (n_cidx >= 0) { 652 | insertAfter(this.getRowCells(row)[n_cidx], cell.el); 653 | } else { 654 | var lastCell = this.getLastNewCellOnRow(row, newRowIdx); 655 | if (lastCell !== null) { 656 | insertAfter(lastCell, cell.el); 657 | } else { 658 | row.insertBefore(cell.el, row.firstChild); 659 | } 660 | } 661 | if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) { 662 | cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1); 663 | } else { 664 | cell.el.removeAttribute('rowspan'); 665 | } 666 | } 667 | } 668 | }, 669 | 670 | // Removes a cell when removing a row 671 | // If is rowspan cell then decreases the rowspan 672 | // and moves cell to next row if needed (is first cell of rowspan) 673 | removeRowCell: function(cell) { 674 | if (cell.isReal) { 675 | if (cell.isRowspan) { 676 | this.collapseCellToNextRow(cell); 677 | } else { 678 | removeElement(cell.el); 679 | } 680 | } else { 681 | if (parseInt(api.getAttribute(cell.el, 'rowspan'), 10) > 2) { 682 | cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) - 1); 683 | } else { 684 | cell.el.removeAttribute('rowspan'); 685 | } 686 | } 687 | }, 688 | 689 | getRowElementsByCell: function() { 690 | var cells = []; 691 | this.setTableMap(); 692 | this.idx = this.getMapIndex(this.cell); 693 | if (this.idx !== false) { 694 | var modRow = this.map[this.idx.row]; 695 | for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) { 696 | if (modRow[cidx].isReal) { 697 | cells.push(modRow[cidx].el); 698 | } 699 | } 700 | } 701 | return cells; 702 | }, 703 | 704 | getColumnElementsByCell: function() { 705 | var cells = []; 706 | this.setTableMap(); 707 | this.idx = this.getMapIndex(this.cell); 708 | if (this.idx !== false) { 709 | for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) { 710 | if (this.map[ridx][this.idx.col] && this.map[ridx][this.idx.col].isReal) { 711 | cells.push(this.map[ridx][this.idx.col].el); 712 | } 713 | } 714 | } 715 | return cells; 716 | }, 717 | 718 | // Removes the row of selected cell 719 | removeRow: function() { 720 | var oldRow = api.getParentElement(this.cell, { query: "tr" }); 721 | if (oldRow) { 722 | this.setTableMap(); 723 | this.idx = this.getMapIndex(this.cell); 724 | if (this.idx !== false) { 725 | var modRow = this.map[this.idx.row]; 726 | for (var cidx = 0, cmax = modRow.length; cidx < cmax; cidx++) { 727 | if (!modRow[cidx].modified) { 728 | this.setCellAsModified(modRow[cidx]); 729 | this.removeRowCell(modRow[cidx]); 730 | } 731 | } 732 | } 733 | removeElement(oldRow); 734 | } 735 | }, 736 | 737 | removeColCell: function(cell) { 738 | if (cell.isColspan) { 739 | if (parseInt(api.getAttribute(cell.el, 'colspan'), 10) > 2) { 740 | cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) - 1); 741 | } else { 742 | cell.el.removeAttribute('colspan'); 743 | } 744 | } else if (cell.isReal) { 745 | removeElement(cell.el); 746 | } 747 | }, 748 | 749 | removeColumn: function() { 750 | this.setTableMap(); 751 | this.idx = this.getMapIndex(this.cell); 752 | if (this.idx !== false) { 753 | for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++) { 754 | if (!this.map[ridx][this.idx.col].modified) { 755 | this.setCellAsModified(this.map[ridx][this.idx.col]); 756 | this.removeColCell(this.map[ridx][this.idx.col]); 757 | } 758 | } 759 | } 760 | }, 761 | 762 | // removes row or column by selected cell element 763 | remove: function(what) { 764 | if (this.rectify()) { 765 | switch (what) { 766 | case 'row': 767 | this.removeRow(); 768 | break; 769 | case 'column': 770 | this.removeColumn(); 771 | break; 772 | } 773 | this.rectify(); 774 | } 775 | }, 776 | 777 | addRow: function(where) { 778 | var doc = this.table.ownerDocument; 779 | 780 | this.setTableMap(); 781 | this.idx = this.getMapIndex(this.cell); 782 | if (where == "below" && api.getAttribute(this.cell, 'rowspan')) { 783 | this.idx.row = this.idx.row + parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1; 784 | } 785 | 786 | if (this.idx !== false) { 787 | var modRow = this.map[this.idx.row], 788 | newRow = doc.createElement('tr'); 789 | 790 | for (var ridx = 0, rmax = modRow.length; ridx < rmax; ridx++) { 791 | if (!modRow[ridx].modified) { 792 | this.setCellAsModified(modRow[ridx]); 793 | this.addRowCell(modRow[ridx], newRow, where); 794 | } 795 | } 796 | 797 | switch (where) { 798 | case 'below': 799 | insertAfter(this.getRealRowEl(true), newRow); 800 | break; 801 | case 'above': 802 | var cr = api.getParentElement(this.map[this.idx.row][this.idx.col].el, { query: "tr" }); 803 | if (cr) { 804 | cr.parentNode.insertBefore(newRow, cr); 805 | } 806 | break; 807 | } 808 | } 809 | }, 810 | 811 | addRowCell: function(cell, row, where) { 812 | var colSpanAttr = (cell.isColspan) ? {"colspan" : api.getAttribute(cell.el, 'colspan')} : null; 813 | if (cell.isReal) { 814 | if (where != 'above' && cell.isRowspan) { 815 | cell.el.setAttribute('rowspan', parseInt(api.getAttribute(cell.el,'rowspan'), 10) + 1); 816 | } else { 817 | row.appendChild(this.createCells('td', 1, colSpanAttr)); 818 | } 819 | } else { 820 | if (where != 'above' && cell.isRowspan && cell.lastRow) { 821 | row.appendChild(this.createCells('td', 1, colSpanAttr)); 822 | } else if (c.isRowspan) { 823 | cell.el.attr('rowspan', parseInt(api.getAttribute(cell.el, 'rowspan'), 10) + 1); 824 | } 825 | } 826 | }, 827 | 828 | add: function(where) { 829 | if (this.rectify()) { 830 | if (where == 'below' || where == 'above') { 831 | this.addRow(where); 832 | } 833 | if (where == 'before' || where == 'after') { 834 | this.addColumn(where); 835 | } 836 | } 837 | }, 838 | 839 | addColCell: function (cell, ridx, where) { 840 | var doAdd, 841 | cType = cell.el.tagName.toLowerCase(); 842 | 843 | // defines add cell vs expand cell conditions 844 | // true means add 845 | switch (where) { 846 | case "before": 847 | doAdd = (!cell.isColspan || cell.firstCol); 848 | break; 849 | case "after": 850 | doAdd = (!cell.isColspan || cell.lastCol || (cell.isColspan && cell.el == this.cell)); 851 | break; 852 | } 853 | 854 | if (doAdd){ 855 | // adds a cell before or after current cell element 856 | switch (where) { 857 | case "before": 858 | cell.el.parentNode.insertBefore(this.createCells(cType, 1), cell.el); 859 | break; 860 | case "after": 861 | insertAfter(cell.el, this.createCells(cType, 1)); 862 | break; 863 | } 864 | 865 | // handles if cell has rowspan 866 | if (cell.isRowspan) { 867 | this.handleCellAddWithRowspan(cell, ridx+1, where); 868 | } 869 | 870 | } else { 871 | // expands cell 872 | cell.el.setAttribute('colspan', parseInt(api.getAttribute(cell.el, 'colspan'), 10) + 1); 873 | } 874 | }, 875 | 876 | addColumn: function(where) { 877 | var row, modCell; 878 | 879 | this.setTableMap(); 880 | this.idx = this.getMapIndex(this.cell); 881 | if (where == "after" && api.getAttribute(this.cell, 'colspan')) { 882 | this.idx.col = this.idx.col + parseInt(api.getAttribute(this.cell, 'colspan'), 10) - 1; 883 | } 884 | 885 | if (this.idx !== false) { 886 | for (var ridx = 0, rmax = this.map.length; ridx < rmax; ridx++ ) { 887 | row = this.map[ridx]; 888 | if (row[this.idx.col]) { 889 | modCell = row[this.idx.col]; 890 | if (!modCell.modified) { 891 | this.setCellAsModified(modCell); 892 | this.addColCell(modCell, ridx , where); 893 | } 894 | } 895 | } 896 | } 897 | }, 898 | 899 | handleCellAddWithRowspan: function (cell, ridx, where) { 900 | var addRowsNr = parseInt(api.getAttribute(this.cell, 'rowspan'), 10) - 1, 901 | crow = api.getParentElement(cell.el, { query: "tr" }), 902 | cType = cell.el.tagName.toLowerCase(), 903 | cidx, temp_r_cells, 904 | doc = this.table.ownerDocument, 905 | nrow; 906 | 907 | for (var i = 0; i < addRowsNr; i++) { 908 | cidx = this.correctColIndexForUnreals(this.idx.col, (ridx + i)); 909 | crow = nextNode(crow, 'tr'); 910 | if (crow) { 911 | if (cidx > 0) { 912 | switch (where) { 913 | case "before": 914 | temp_r_cells = this.getRowCells(crow); 915 | if (cidx > 0 && this.map[ridx + i][this.idx.col].el != temp_r_cells[cidx] && cidx == temp_r_cells.length - 1) { 916 | insertAfter(temp_r_cells[cidx], this.createCells(cType, 1)); 917 | } else { 918 | temp_r_cells[cidx].parentNode.insertBefore(this.createCells(cType, 1), temp_r_cells[cidx]); 919 | } 920 | 921 | break; 922 | case "after": 923 | insertAfter(this.getRowCells(crow)[cidx], this.createCells(cType, 1)); 924 | break; 925 | } 926 | } else { 927 | crow.insertBefore(this.createCells(cType, 1), crow.firstChild); 928 | } 929 | } else { 930 | nrow = doc.createElement('tr'); 931 | nrow.appendChild(this.createCells(cType, 1)); 932 | this.table.appendChild(nrow); 933 | } 934 | } 935 | } 936 | }; 937 | 938 | api.table = { 939 | getCellsBetween: function(cell1, cell2) { 940 | var c1 = new TableModifyerByCell(cell1); 941 | return c1.getMapElsTo(cell2); 942 | }, 943 | 944 | addCells: function(cell, where) { 945 | var c = new TableModifyerByCell(cell); 946 | c.add(where); 947 | }, 948 | 949 | removeCells: function(cell, what) { 950 | var c = new TableModifyerByCell(cell); 951 | c.remove(what); 952 | }, 953 | 954 | mergeCellsBetween: function(cell1, cell2) { 955 | var c1 = new TableModifyerByCell(cell1); 956 | c1.merge(cell2); 957 | }, 958 | 959 | unmergeCell: function(cell) { 960 | var c = new TableModifyerByCell(cell); 961 | c.unmerge(); 962 | }, 963 | 964 | orderSelectionEnds: function(cell, cell2) { 965 | var c = new TableModifyerByCell(cell); 966 | return c.orderSelectionEnds(cell2); 967 | }, 968 | 969 | indexOf: function(cell) { 970 | var c = new TableModifyerByCell(cell); 971 | c.setTableMap(); 972 | return c.getMapIndex(cell); 973 | }, 974 | 975 | findCell: function(table, idx) { 976 | var c = new TableModifyerByCell(null, table); 977 | return c.getElementAtIndex(idx); 978 | }, 979 | 980 | findRowByCell: function(cell) { 981 | var c = new TableModifyerByCell(cell); 982 | return c.getRowElementsByCell(); 983 | }, 984 | 985 | findColumnByCell: function(cell) { 986 | var c = new TableModifyerByCell(cell); 987 | return c.getColumnElementsByCell(); 988 | }, 989 | 990 | canMerge: function(cell1, cell2) { 991 | var c = new TableModifyerByCell(cell1); 992 | return c.canMerge(cell2); 993 | } 994 | }; 995 | 996 | })(); 997 | 998 | (function() { 999 | 1000 | // Keep the old composer.observe function. 1001 | var oldObserverFunction = wysihtml.views.Composer.prototype.observe; 1002 | 1003 | var extendedObserverFunction = function() { 1004 | oldObserverFunction.call(this); 1005 | // Bind the table user interaction tracking 1006 | if (this.config.handleTables) { 1007 | // If handleTables option is true, table handling functions are bound 1008 | initTableHandling.call(this); 1009 | } 1010 | }; 1011 | 1012 | // Table management. 1013 | // If present enableObjectResizing and enableInlineTableEditing command 1014 | // should be called with false to prevent native table handlers. 1015 | var initTableHandling = function() { 1016 | var hideHandlers = function() { 1017 | this.win.removeEventListener('load', hideHandlers); 1018 | this.doc.execCommand('enableObjectResizing', false, 'false'); 1019 | this.doc.execCommand('enableInlineTableEditing', false, 'false'); 1020 | }.bind(this), 1021 | iframeInitiator = (function() { 1022 | hideHandlers.call(this); 1023 | this.actions.removeListeners(this.sandbox.getIframe(), ['focus', 'mouseup', 'mouseover'], iframeInitiator); 1024 | }).bind(this); 1025 | 1026 | if ( 1027 | this.doc.execCommand && 1028 | wysihtml.browser.supportsCommand(this.doc, 'enableObjectResizing') && 1029 | wysihtml.browser.supportsCommand(this.doc, 'enableInlineTableEditing') 1030 | ) { 1031 | if (this.sandbox.getIframe) { 1032 | this.actions.addListeners(this.sandbox.getIframe(), ['focus', 'mouseup', 'mouseover'], iframeInitiator); 1033 | } else { 1034 | this.win.addEventListener('load', hideHandlers); 1035 | } 1036 | } 1037 | this.tableSelection = wysihtml.quirks.tableCellsSelection(this.element, this.parent); 1038 | }; 1039 | 1040 | // Cell selections handling 1041 | var tableCellsSelection = function(editable, editor) { 1042 | 1043 | var init = function() { 1044 | editable.addEventListener('mousedown', handleMouseDown); 1045 | return select; 1046 | }; 1047 | 1048 | var handleMouseDown = function(event) { 1049 | var target = wysihtml.dom.getParentElement(event.target, {query: 'td, th'}, false, editable); 1050 | if (target) { 1051 | handleSelectionMousedown(target); 1052 | } 1053 | }; 1054 | 1055 | var handleSelectionMousedown = function(target) { 1056 | select.start = target; 1057 | select.end = target; 1058 | select.cells = [target]; 1059 | select.table = dom.getParentElement(select.start, {query: 'table'}, false, editable); 1060 | 1061 | if (select.table) { 1062 | removeCellSelections(); 1063 | dom.addClass(target, selectionClass); 1064 | editable.addEventListener('mousemove', handleMouseMove); 1065 | editable.addEventListener('mouseup', handleMouseUp); 1066 | editor.fire('tableselectstart').fire('tableselectstart:composer'); 1067 | } 1068 | }; 1069 | 1070 | // remove all selection classes 1071 | var removeCellSelections = function() { 1072 | if (editable) { 1073 | var selectedCells = editable.querySelectorAll('.' + selectionClass); 1074 | if (selectedCells.length > 0) { 1075 | for (var i = 0; i < selectedCells.length; i++) { 1076 | dom.removeClass(selectedCells[i], selectionClass); 1077 | } 1078 | } 1079 | } 1080 | }; 1081 | 1082 | var addSelections = function(cells) { 1083 | for (var i = 0; i < cells.length; i++) { 1084 | dom.addClass(cells[i], selectionClass); 1085 | } 1086 | }; 1087 | 1088 | var handleMouseMove = function(event) { 1089 | var curTable = null, 1090 | cell = dom.getParentElement(event.target, {query: 'td, th'}, false, editable), 1091 | oldEnd; 1092 | 1093 | if (cell && select.table && select.start) { 1094 | curTable = dom.getParentElement(cell, {query: 'table'}, false, editable); 1095 | if (curTable && curTable === select.table) { 1096 | removeCellSelections(); 1097 | oldEnd = select.end; 1098 | select.end = cell; 1099 | select.cells = dom.table.getCellsBetween(select.start, cell); 1100 | if (select.cells.length > 1) { 1101 | editor.composer.selection.deselect(); 1102 | } 1103 | addSelections(select.cells); 1104 | if (select.end !== oldEnd) { 1105 | editor.fire('tableselectchange').fire('tableselectchange:composer'); 1106 | } 1107 | } 1108 | } 1109 | }; 1110 | 1111 | var handleMouseUp = function(event) { 1112 | editable.removeEventListener('mousemove', handleMouseMove); 1113 | editable.removeEventListener('mouseup', handleMouseUp); 1114 | editor.fire('tableselect').fire('tableselect:composer'); 1115 | setTimeout(function() { 1116 | bindSideclick(); 1117 | }, 0); 1118 | }; 1119 | 1120 | var sideClickHandler = function(event) { 1121 | editable.ownerDocument.removeEventListener('click', sideClickHandler); 1122 | if (dom.getParentElement(event.target, {query: 'table'}, false, editable) != select.table) { 1123 | removeCellSelections(); 1124 | select.table = null; 1125 | select.start = null; 1126 | select.end = null; 1127 | editor.fire('tableunselect').fire('tableunselect:composer'); 1128 | } 1129 | }; 1130 | 1131 | var bindSideclick = function() { 1132 | editable.ownerDocument.addEventListener('click', sideClickHandler); 1133 | }; 1134 | 1135 | var selectCells = function(start, end) { 1136 | select.start = start; 1137 | select.end = end; 1138 | select.table = dom.getParentElement(select.start, {query: 'table'}, false, editable); 1139 | selectedCells = dom.table.getCellsBetween(select.start, select.end); 1140 | addSelections(selectedCells); 1141 | bindSideclick(); 1142 | editor.fire('tableselect').fire('tableselect:composer'); 1143 | }; 1144 | 1145 | var dom = wysihtml.dom, 1146 | select = { 1147 | table: null, 1148 | start: null, 1149 | end: null, 1150 | cells: null, 1151 | select: selectCells 1152 | }, 1153 | selectionClass = 'wysiwyg-tmp-selected-cell'; 1154 | 1155 | return init(); 1156 | }; 1157 | 1158 | // Bind to wysihtml 1159 | wysihtml.Editor.prototype.defaults.handleTables = true; 1160 | wysihtml.quirks.tableCellsSelection = tableCellsSelection; 1161 | wysihtml.views.Composer.prototype.observe = extendedObserverFunction; 1162 | 1163 | })(); 1164 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/wysihtml/toolbar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Toolbar Dialog 3 | * 4 | * @param {Element} link The toolbar link which causes the dialog to show up 5 | * @param {Element} container The dialog container 6 | * 7 | * @example 8 | * 9 | * insert an image 10 | * 11 | * 12 | *
      13 | * 16 | * 19 | *
      20 | * 21 | * 30 | */ 31 | (function(wysihtml) { 32 | var dom = wysihtml.dom, 33 | CLASS_NAME_OPENED = "wysihtml-command-dialog-opened", 34 | SELECTOR_FORM_ELEMENTS = "input, select, textarea", 35 | SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 36 | ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 37 | 38 | 39 | wysihtml.toolbar.Dialog = wysihtml.lang.Dispatcher.extend( 40 | /** @scope wysihtml.toolbar.Dialog.prototype */ { 41 | constructor: function(link, container) { 42 | this.link = link; 43 | this.container = container; 44 | }, 45 | 46 | _observe: function() { 47 | if (this._observed) { 48 | return; 49 | } 50 | 51 | var that = this, 52 | callbackWrapper = function(event) { 53 | var attributes = that._serialize(); 54 | that.fire("save", attributes); 55 | that.hide(); 56 | event.preventDefault(); 57 | event.stopPropagation(); 58 | }; 59 | 60 | dom.observe(that.link, "click", function() { 61 | if (dom.hasClass(that.link, CLASS_NAME_OPENED)) { 62 | setTimeout(function() { that.hide(); }, 0); 63 | } 64 | }); 65 | 66 | dom.observe(this.container, "keydown", function(event) { 67 | var keyCode = event.keyCode; 68 | if (keyCode === wysihtml.ENTER_KEY) { 69 | callbackWrapper(event); 70 | } 71 | if (keyCode === wysihtml.ESCAPE_KEY) { 72 | that.cancel(); 73 | } 74 | }); 75 | 76 | dom.delegate(this.container, "[data-wysihtml-dialog-action=save]", "click", callbackWrapper); 77 | 78 | dom.delegate(this.container, "[data-wysihtml-dialog-action=cancel]", "click", function(event) { 79 | that.cancel(); 80 | event.preventDefault(); 81 | event.stopPropagation(); 82 | }); 83 | 84 | this._observed = true; 85 | }, 86 | 87 | /** 88 | * Grabs all fields in the dialog and puts them in key=>value style in an object which 89 | * then gets returned 90 | */ 91 | _serialize: function() { 92 | var data = {}, 93 | fields = this.container.querySelectorAll(SELECTOR_FIELDS), 94 | length = fields.length, 95 | i = 0; 96 | 97 | for (; ifoo 109 | * 110 | * and we have the following dialog: 111 | * 112 | * 113 | * 114 | * after calling _interpolate() the dialog will look like this 115 | * 116 | * 117 | * 118 | * Basically it adopted the attribute values into the corresponding input fields 119 | * 120 | */ 121 | _interpolate: function(avoidHiddenFields) { 122 | var field, 123 | fieldName, 124 | newValue, 125 | focusedElement = document.querySelector(":focus"), 126 | fields = this.container.querySelectorAll(SELECTOR_FIELDS), 127 | length = fields.length, 128 | i = 0; 129 | for (; i