├── alignment ├── alignment.css └── alignment.js ├── clips ├── clips.css └── clips.js ├── textdirection.js ├── limiter.js ├── LICENSE ├── counter.js ├── inlinestyle.js ├── textexpander.js ├── imagemanager.js ├── definedlinks.js ├── filemanager.js ├── video.js ├── README.md ├── fullscreen.js ├── source.js ├── codemirror.js ├── properties.js └── table.js /alignment/alignment.css: -------------------------------------------------------------------------------- 1 | .text-center { 2 | text-align: center; 3 | } 4 | .text-right { 5 | text-align: right; 6 | } -------------------------------------------------------------------------------- /clips/clips.css: -------------------------------------------------------------------------------- 1 | b.label-red { 2 | color: #fff; 3 | background: #c92020; 4 | padding: 0 7px; 5 | font-size: 11px; 6 | text-transform: uppercase; 7 | font-weight: normal; 8 | display: inline-block; 9 | border-radius: 4px; 10 | } -------------------------------------------------------------------------------- /textdirection.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.textdirection = function() 4 | { 5 | return { 6 | langs: { 7 | en: { 8 | "change-text-direction": "RTL-LTR", 9 | "left-to-right": "Left to Right", 10 | "right-to-left": "Right to Left" 11 | } 12 | }, 13 | init: function() 14 | { 15 | var that = this; 16 | var dropdown = {}; 17 | 18 | dropdown.ltr = { title: that.lang.get('left-to-right'), func: that.textdirection.setLtr }; 19 | dropdown.rtl = { title: that.lang.get('right-to-left'), func: that.textdirection.setRtl }; 20 | 21 | var button = this.button.add('textdirection', this.lang.get('change-text-direction')); 22 | this.button.addDropdown(button, dropdown); 23 | }, 24 | setRtl: function() 25 | { 26 | this.buffer.set(); 27 | this.block.addAttr('dir', 'rtl'); 28 | }, 29 | setLtr: function() 30 | { 31 | this.buffer.set(); 32 | this.block.removeAttr('dir'); 33 | } 34 | }; 35 | }; 36 | })(jQuery); -------------------------------------------------------------------------------- /limiter.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.limiter = function() 4 | { 5 | return { 6 | init: function() 7 | { 8 | if (!this.opts.limiter) 9 | { 10 | return; 11 | } 12 | 13 | this.core.editor().on('keydown.redactor-plugin-limiter', $.proxy(function(e) 14 | { 15 | var key = e.which; 16 | var ctrl = e.ctrlKey || e.metaKey; 17 | 18 | if (key === this.keyCode.BACKSPACE 19 | || key === this.keyCode.DELETE 20 | || key === this.keyCode.ESC 21 | || key === this.keyCode.SHIFT 22 | || (ctrl && key === 65) 23 | || (ctrl && key === 82) 24 | || (ctrl && key === 116) 25 | ) 26 | { 27 | return; 28 | } 29 | 30 | var text = this.core.editor().text(); 31 | text = text.replace(/\u200B/g, ''); 32 | 33 | var count = text.length; 34 | if (count >= this.opts.limiter) 35 | { 36 | return false; 37 | } 38 | 39 | 40 | }, this)); 41 | 42 | } 43 | }; 44 | }; 45 | })(jQuery); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Imperavi LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /counter.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.counter = function() 4 | { 5 | return { 6 | init: function() 7 | { 8 | if (typeof this.opts.callbacks.counter === 'undefined') 9 | { 10 | return; 11 | } 12 | 13 | 14 | 15 | this.core.editor().on('keyup.redactor-plugin-counter', $.proxy(this.counter.count, this)); 16 | }, 17 | count: function() 18 | { 19 | var words = 0, characters = 0, spaces = 0; 20 | var html = this.code.get(); 21 | 22 | var text = html.replace(/<\/(.*?)>/gi, ' '); 23 | text = text.replace(/<(.*?)>/gi, ''); 24 | text = text.replace(/\t/gi, ''); 25 | text = text.replace(/\n/gi, ' '); 26 | text = text.replace(/\r/gi, ' '); 27 | text = text.replace(/\u200B/g, ''); 28 | text = $.trim(text); 29 | 30 | if (text !== '') 31 | { 32 | var arrWords = text.split(/\s+/); 33 | var arrSpaces = text.match(/\s/g); 34 | 35 | words = (arrWords) ? arrWords.length : 0; 36 | spaces = (arrSpaces) ? arrSpaces.length : 0; 37 | 38 | characters = text.length; 39 | 40 | } 41 | 42 | this.core.callback('counter', { words: words, characters: characters, spaces: spaces }); 43 | 44 | } 45 | }; 46 | }; 47 | })(jQuery); -------------------------------------------------------------------------------- /inlinestyle.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.inlinestyle = function() 4 | { 5 | return { 6 | langs: { 7 | en: { 8 | "style": "Style" 9 | } 10 | }, 11 | init: function() 12 | { 13 | var tags = { 14 | "marked": { 15 | title: "Marked", 16 | args: ['mark'] 17 | }, 18 | "code": { 19 | title: "Code", 20 | args: ['code'] 21 | }, 22 | "sample": { 23 | title: "Sample", 24 | args: ['samp'] 25 | }, 26 | "variable": { 27 | title: "Variable", 28 | args: ['var'] 29 | }, 30 | "shortcut": { 31 | title: "Shortcut", 32 | args: ['kbd'] 33 | }, 34 | "cite": { 35 | title: "Cite", 36 | args: ['cite'] 37 | }, 38 | "sup": { 39 | title: "Superscript", 40 | args: ['sup'] 41 | }, 42 | "sub": { 43 | title: "Subscript", 44 | args: ['sub'] 45 | } 46 | }; 47 | 48 | 49 | var that = this; 50 | var dropdown = {}; 51 | 52 | $.each(tags, function(i, s) 53 | { 54 | dropdown[i] = { title: s.title, func: 'inline.format', args: s.args }; 55 | }); 56 | 57 | 58 | var button = this.button.addAfter('format', 'inline', this.lang.get('style')); 59 | this.button.addDropdown(button, dropdown); 60 | 61 | } 62 | 63 | 64 | }; 65 | }; 66 | })(jQuery); -------------------------------------------------------------------------------- /textexpander.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.textexpander = function() 4 | { 5 | return { 6 | init: function() 7 | { 8 | if (!this.opts.textexpander) 9 | { 10 | return; 11 | } 12 | 13 | this.$editor.on('keyup.redactor-plugin-textexpander', $.proxy(function(e) 14 | { 15 | var key = e.which; 16 | if (key === this.keyCode.SPACE) 17 | { 18 | var current = this.selection.current(); 19 | var cloned = $(current).clone(); 20 | 21 | var $div = $('
'); 22 | $div.html(cloned); 23 | 24 | var text = $div.html(); 25 | $div.remove(); 26 | 27 | var len = this.opts.textexpander.length; 28 | var replaced = 0; 29 | 30 | for (var i = 0; i < len; i++) 31 | { 32 | var re = new RegExp(this.opts.textexpander[i][0]); 33 | if (text.search(re) !== -1) 34 | { 35 | replaced++; 36 | text = text.replace(re, this.opts.textexpander[i][1]); 37 | 38 | $div = $('
'); 39 | $div.html(text); 40 | $div.append(this.selection.marker()); 41 | 42 | var html = $div.html().replace(/ /, ''); 43 | 44 | $(current).replaceWith(html); 45 | $div.remove(); 46 | } 47 | } 48 | 49 | if (replaced !== 0) 50 | { 51 | this.selection.restore(); 52 | } 53 | } 54 | 55 | 56 | }, this)); 57 | 58 | } 59 | }; 60 | }; 61 | })(jQuery); -------------------------------------------------------------------------------- /alignment/alignment.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.alignment = function() 4 | { 5 | return { 6 | langs: { 7 | en: { 8 | "align": "Align", 9 | "align-left": "Align Left", 10 | "align-center": "Align Center", 11 | "align-right": "Align Right" 12 | } 13 | }, 14 | init: function() 15 | { 16 | var that = this; 17 | var dropdown = {}; 18 | 19 | dropdown.left = { title: that.lang.get('align-left'), func: that.alignment.setLeft }; 20 | dropdown.center = { title: that.lang.get('align-center'), func: that.alignment.setCenter }; 21 | dropdown.right = { title: that.lang.get('align-right'), func: that.alignment.setRight }; 22 | 23 | var button = this.button.add('alignment', this.lang.get('align')); 24 | this.button.addDropdown(button, dropdown); 25 | }, 26 | removeAlign: function() 27 | { 28 | this.block.removeClass('text-center'); 29 | this.block.removeClass('text-right'); 30 | }, 31 | setLeft: function() 32 | { 33 | this.buffer.set(); 34 | this.alignment.removeAlign(); 35 | }, 36 | setCenter: function() 37 | { 38 | this.buffer.set(); 39 | this.alignment.removeAlign(); 40 | this.block.addClass('text-center'); 41 | }, 42 | setRight: function() 43 | { 44 | this.buffer.set(); 45 | this.alignment.removeAlign(); 46 | this.block.addClass('text-right'); 47 | } 48 | }; 49 | }; 50 | })(jQuery); -------------------------------------------------------------------------------- /imagemanager.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.imagemanager = function() 4 | { 5 | return { 6 | langs: { 7 | en: { 8 | "upload": "Upload", 9 | "choose": "Choose" 10 | } 11 | }, 12 | init: function() 13 | { 14 | if (!this.opts.imageManagerJson) 15 | { 16 | return; 17 | } 18 | 19 | this.modal.addCallback('image', this.imagemanager.load); 20 | }, 21 | load: function() 22 | { 23 | var $box = $(''; 27 | }, 28 | init: function() 29 | { 30 | var button = this.button.addAfter('image', 'video', this.lang.get('video')); 31 | this.button.addCallback(button, this.video.show); 32 | }, 33 | show: function() 34 | { 35 | this.modal.addTemplate('video', this.video.getTemplate()); 36 | 37 | this.modal.load('video', this.lang.get('video'), 700); 38 | 39 | // action button 40 | this.modal.getActionButton().text(this.lang.get('insert')).on('click', this.video.insert); 41 | this.modal.show(); 42 | 43 | // focus 44 | if (this.detect.isDesktop()) 45 | { 46 | setTimeout(function() 47 | { 48 | $('#redactor-insert-video-area').focus(); 49 | 50 | }, 1); 51 | } 52 | 53 | 54 | }, 55 | insert: function() 56 | { 57 | var data = $('#redactor-insert-video-area').val(); 58 | 59 | if (!data.match(/
'; 68 | 69 | if (data.match(this.video.reUrlYoutube)) 70 | { 71 | data = data.replace(this.video.reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd); 72 | } 73 | else if (data.match(this.video.reUrlVimeo)) 74 | { 75 | data = data.replace(this.video.reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd); 76 | } 77 | } 78 | 79 | this.modal.close(); 80 | this.placeholder.hide(); 81 | 82 | // buffer 83 | this.buffer.set(); 84 | 85 | // insert 86 | this.air.collapsed(); 87 | this.insert.html(data); 88 | 89 | } 90 | 91 | }; 92 | }; 93 | })(jQuery); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Official [Redactor II WYSIWYG](https://imperavi.com/redactor) Plugins 2 | 3 | Redactor II is advanced, clean and smooth Rich Text Editor. It offers an excellent immersive user experience. Small and robust, Redactor is favorite choice for thousands of professional developers worldwide. 4 | 5 | Redactor offers well-documented, [powerful and flexible API](https://imperavi.com/redactor/docs/api/), which extends and enhances user experinece and allows developers to do wonderful things with Redactor. 6 | 7 | ![Redactor Demo](https://dl.dropboxusercontent.com/u/127064/Plugin_Demo.png) 8 | 9 | This repo contains latest versions of the official [Redactor II WYSIWYG](https://imperavi.com/redactor) Plugins. 10 | 11 | NB. You may need to [purchase a licence](https://imperavi.com/redactor/buy) for Redactor to use these plugins (they won't work without Redactor itself). 12 | 13 | Here's the latest list of official Redactor II plugins with short descriptions. 14 | 15 | ## Inline Style 16 | Apply inline formatting to style text using "Style" dropdown. 17 | 18 | ## Source Code 19 | This plugin allows users to look through and edit text's HTML source code. Developers may extend and enhance this plugin to bring even more HTML-related source code features to the users. 20 | 21 | ## Codemirror 22 | This plugin enables source code highlighting, powered by [CodeMirror](http://codemirror.net). For all available settings and commands, please refer to CodeMirror User Manual. 23 | 24 | ## Text Alignment 25 | Align text left, center or right 26 | This plugin applies a specific class to selected text, allowing developers to tweak alignment precisely. 27 | 28 | ## Table 29 | Insert and format tables with ease. 30 | 31 | ## Fullscreen 32 | Expand Redactor to fill the whole screen. Also known as "distraction free" mode. 33 | 34 | ## Video 35 | Enrich text with embedded video. 36 | 37 | ## Image Manager 38 | Upload or choose and insert images to tell a more visual story. 39 | 40 | ## File Manager 41 | Manage, upload, select files and place them anywhere in Redactor. 42 | 43 | ## Properties 44 | This plugin allows you to assign any id or class to any block tag (selected or containing cursor). 45 | 46 | ## Predefined Links 47 | Allow users to select predefined links from a list. 48 | 49 | ## [Uploadcare](https://github.com/uploadcare/uploadcare-redactor/) 50 | Upload media from multiple cloud sources and social networks, and manage files with [Uploadcare.com](http://uploadcare.com) plugin. No backend required 51 | 52 | ## Clips 53 | Create a dropdown of frequently used "snippets" of code, text, icons, emoji, you name it. 54 | 55 | ## Limiter 56 | Limit the number of characters a user can enter. 57 | 58 | ## Text Expander 59 | Enter a short snippet of text or a word and this plugin will replace it to a frequently used predefined text. For example, enter "addrr" to have it replaced with your mailing address. 60 | 61 | ## Text Direction 62 | Easily change the direction of the text in a block element (paragraph, header, blockquote etc.). 63 | 64 | ## Counter 65 | Add a character counter. 66 | 67 | All plugins are released under MIT license: feel free to do whatever you like with them. 68 | 69 | -------------------------------------------------------------------------------- /fullscreen.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.fullscreen = function() 4 | { 5 | return { 6 | langs: { 7 | en: { 8 | "fullscreen": "Fullscreen" 9 | } 10 | }, 11 | init: function() 12 | { 13 | this.fullscreen.isOpen = false; 14 | 15 | var button = this.button.add('fullscreen', this.lang.get('fullscreen')); 16 | this.button.addCallback(button, this.fullscreen.toggle); 17 | 18 | if (this.opts.fullscreen) 19 | { 20 | this.fullscreen.toggle(); 21 | } 22 | 23 | }, 24 | enable: function() 25 | { 26 | this.fullscreen.isOpened = false; 27 | this.button.setActive('fullscreen'); 28 | this.fullscreen.isOpen = true; 29 | 30 | if (!this.opts.fullscreen) 31 | { 32 | this.selection.save(); 33 | } 34 | 35 | if (this.opts.toolbarExternal) 36 | { 37 | this.fullscreen.toolcss = {}; 38 | this.fullscreen.boxcss = {}; 39 | this.fullscreen.toolcss.width = this.$toolbar.css('width'); 40 | this.fullscreen.toolcss.top = this.$toolbar.css('top'); 41 | this.fullscreen.toolcss.position = this.$toolbar.css('position'); 42 | this.fullscreen.boxcss.top = this.$box.css('top'); 43 | } 44 | 45 | this.fullscreen.height = this.core.editor().height(); 46 | 47 | if (this.opts.maxHeight) 48 | { 49 | this.core.editor().css('max-height', ''); 50 | } 51 | 52 | if (this.opts.minHeight) 53 | { 54 | this.core.editor().css('min-height', ''); 55 | } 56 | 57 | if (!this.$fullscreenPlaceholder) 58 | { 59 | this.$fullscreenPlaceholder = $('
'); 60 | } 61 | 62 | this.$fullscreenPlaceholder.insertAfter(this.$box); 63 | 64 | this.core.box().appendTo(document.body); 65 | this.core.box().addClass('redactor-box-fullscreen'); 66 | 67 | $('body').addClass('redactor-body-fullscreen'); 68 | $('body, html').css('overflow', 'hidden'); 69 | 70 | this.fullscreen.resize(); 71 | 72 | if (!this.opts.fullscreen) 73 | { 74 | this.selection.restore(); 75 | } 76 | 77 | this.toolbar.observeScrollDisable(); 78 | $(window).on('resize.redactor-plugin-fullscreen', $.proxy(this.fullscreen.resize, this)); 79 | $(document).scrollTop(0, 0); 80 | 81 | var self = this; 82 | setTimeout(function() 83 | { 84 | self.fullscreen.isOpened = true; 85 | }, 10); 86 | 87 | }, 88 | disable: function() 89 | { 90 | this.button.setInactive('fullscreen'); 91 | this.fullscreen.isOpened = undefined; 92 | this.fullscreen.isOpen = false; 93 | this.selection.save(); 94 | 95 | $(window).off('resize.redactor-plugin-fullscreen'); 96 | $('body, html').css('overflow', ''); 97 | 98 | this.core.box().insertBefore(this.$fullscreenPlaceholder); 99 | this.$fullscreenPlaceholder.remove(); 100 | 101 | this.core.box().removeClass('redactor-box-fullscreen').css({ width: 'auto', height: 'auto' }); 102 | this.core.box().removeClass('redactor-box-fullscreen'); 103 | 104 | if (this.opts.toolbarExternal) 105 | { 106 | this.core.box().css('top', this.fullscreen.boxcss.top); 107 | this.core.toolbar().css({ 108 | 'width': this.fullscreen.toolcss.width, 109 | 'top': this.fullscreen.toolcss.top, 110 | 'position': this.fullscreen.toolcss.position 111 | }); 112 | } 113 | 114 | if (this.opts.minHeight) 115 | { 116 | this.core.editor().css('minHeight', this.opts.minHeight); 117 | } 118 | 119 | if (this.opts.maxHeight) 120 | { 121 | this.core.editor().css('maxHeight', this.opts.maxHeight); 122 | } 123 | 124 | this.core.editor().css('height', 'auto'); 125 | this.selection.restore(); 126 | }, 127 | toggle: function() 128 | { 129 | return (this.fullscreen.isOpen) ? this.fullscreen.disable() : this.fullscreen.enable(); 130 | }, 131 | resize: function() 132 | { 133 | if (!this.fullscreen.isOpen) 134 | { 135 | return; 136 | } 137 | 138 | var toolbarHeight = this.button.toolbar().height(); 139 | var padding = parseInt(this.core.editor().css('padding-top')) + parseInt(this.core.editor().css('padding-bottom')); 140 | var height = $(window).height() - toolbarHeight - padding; 141 | 142 | this.core.box().width($(window).width()).height(height); 143 | 144 | if (this.opts.toolbarExternal) 145 | { 146 | this.core.toolbar().css({ 147 | 'top': '0px', 148 | 'position': 'absolute', 149 | 'width': '100%' 150 | }); 151 | 152 | this.core.box().css('top', toolbarHeight + 'px'); 153 | } 154 | 155 | this.core.editor().height(height); 156 | } 157 | }; 158 | }; 159 | })(jQuery); -------------------------------------------------------------------------------- /source.js: -------------------------------------------------------------------------------- 1 | (function($) 2 | { 3 | $.Redactor.prototype.source = function() 4 | { 5 | return { 6 | init: function() 7 | { 8 | var button = this.button.addFirst('html', 'HTML'); 9 | this.button.addCallback(button, this.source.toggle); 10 | 11 | var style = { 12 | 'width': '100%', 13 | 'margin': '0', 14 | 'background': '#111', 15 | 'box-sizing': 'border-box', 16 | 'color': 'rgba(255, 255, 255, .8)', 17 | 'font-size': '14px', 18 | 'outline': 'none', 19 | 'padding': '16px', 20 | 'line-height': '22px', 21 | 'font-family': 'Menlo, Monaco, Consolas, "Courier New", monospace' 22 | }; 23 | 24 | this.source.$textarea = $('