Upgraded to Redactor 10.1.0
authorAlexander Ebert <ebert@woltlab.com>
Sat, 18 Apr 2015 12:37:17 +0000 (14:37 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 18 Apr 2015 12:37:17 +0000 (14:37 +0200)
wcfsetup/install/files/js/3rdParty/redactor/redactor.js

index 99e89a5b982e8f4c5af543aa3eb9425e08d20148..0de969aa8612827eb7a00dded94aab2b34a1be0d 100644 (file)
@@ -1,6 +1,6 @@
 /*
-       Redactor v10.0.9
-       Updated: March 16, 2015
+       Redactor v10.1.0
+       Updated: April 16, 2015
 
        http://imperavi.com/redactor/
 
@@ -28,9 +28,6 @@
 
        var uuid = 0;
 
-       var reUrlYoutube = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig;
-       var reUrlVimeo = /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/;
-
        // Plugin
        $.fn.redactor = function(options)
        {
 
        // Functionality
        $.Redactor = Redactor;
-       $.Redactor.VERSION = '10.0.9';
+       $.Redactor.VERSION = '10.1.0';
        $.Redactor.modules = ['alignment', 'autosave', 'block', 'buffer', 'build', 'button',
                                                  'caret', 'clean', 'code', 'core', 'dropdown', 'file', 'focus',
                                                  'image', 'indent', 'inline', 'insert', 'keydown', 'keyup',
                                                  'lang', 'line', 'link', 'list', 'modal', 'observe', 'paragraphize',
                                                  'paste', 'placeholder', 'progress', 'selection', 'shortcuts',
-                                                 'tabifier', 'tidy',  'toolbar', 'upload', 'utils'];
+                                                 'tabifier', 'tidy',  'toolbar', 'upload', 'utils', 'linkify'];
 
        $.Redactor.opts = {
 
                                upload_label: 'Drop file here or '
 
                        }
-               }
+               },
+
+               linkify: {
+                       regexps: {
+                               youtube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
+                               vimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
+                               image: /((https?|www)[^\s]+\.)(jpe?g|png|gif)(\?[^\s-]+)?/ig,
+                               url: /((?:http[s]?:\/\/(?:www\.)?|www\.){1}(?:[0-9A-Za-z\-%_]+\.)+[a-zA-Z]{2,}(?::[0-9]+)?(?:(?:\/[0-9A-Za-z\-#\.%\+_]*)+)?(?:\?(?:[0-9A-Za-z\-\.%_]+(?:=[0-9A-Za-z\-\.%_\+]*)?)?(?:&(?:[0-9A-Za-z\-\.%_]+(?:=[0-9A-Za-z\-\.%_\+]*)?)?)*)?(?:#[0-9A-Za-z\-\.%_\+=\?&;]*)?)/ig,
+                       }
+               },
+
+               codemirror: false
        };
 
        // Functionality
                autosave: function()
                {
                        return {
+                               html: false,
                                enable: function()
                                {
                                        if (!this.opts.autosave) return;
 
-                                       this.autosave.html = false;
                                        this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name');
 
                                        if (this.opts.autosaveOnChange) return;
                                        this.autosave.source = this.code.get();
 
                                        if (this.autosave.html === this.autosave.source) return;
-                                       if (this.utils.isEmpty(this.autosave.source)) return;
+                                       //if (this.utils.isEmpty(this.autosave.source)) return;
 
                                        // data
                                        var data = {};
                                },
                                formatListToBlockquote: function()
                                {
-                                       var block = $(this.block.blocks[0]).closest('ul, ol');
+                                       var block = $(this.block.blocks[0]).closest('ul, ol', this.$editor[0]);
 
                                        $(block).find('ul, ol').contents().unwrap();
                                        $(block).find('li').append($('<br>')).contents().unwrap();
                                },
                                formatTableWrapping: function($formatted)
                                {
-                                       if ($formatted.closest('table').length === 0) return;
+                                       if ($formatted.closest('table', this.$editor[0]).length === 0) return;
 
-                                       if ($formatted.closest('tr').length === 0) $formatted.wrap('<tr>');
-                                       if ($formatted.closest('td').length === 0 && $formatted.closest('th').length === 0)
+                                       if ($formatted.closest('tr', this.$editor[0]).length === 0) $formatted.wrap('<tr>');
+                                       if ($formatted.closest('td', this.$editor[0]).length === 0 && $formatted.closest('th').length === 0)
                                        {
                                                $formatted.wrap('<td>');
                                        }
                                },
                                setHelpers: function()
                                {
-                                       // autosave
-                                       this.autosave.enable();
+                                       // linkify
+                                       if (this.linkify.isEnabled())
+                                       {
+                                               this.linkify.format();
+                                       }
 
                                        // placeholder
                                        this.placeholder.enable();
                                        // dropdown
                                        if (btnObject.dropdown)
                                        {
-                                               var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' +  + this.uuid + ' redactor-dropdown-box-' + btnName + '" style="display: none;">');
+                                               var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + btnName + '" style="display: none;">');
                                                $button.data('dropdown', $dropdown);
                                                this.dropdown.build(btnName, $dropdown, btnObject.dropdown);
                                        }
                                        var key = $btn.attr('rel');
                                        this.button.addCallback($btn, 'dropdown');
 
-                                       var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' +  + this.uuid + ' redactor-dropdown-box-' + key + '" style="display: none;">');
+                                       var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + key + '" style="display: none;">');
                                        $btn.data('dropdown', $dropdown);
 
                                        // build dropdown
                                        html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>');
                                        html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>');
                                        // remove verified
-                                       html = html.replace(new RegExp('<div(.*?[^>]) data-tagblock="redactor"(.*?[^>])>', 'gi'), '<div$1$2>');
-                                       html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
-                                       html = html.replace(new RegExp('<span(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>', 'gi'), '<span$1$3>');
-                                       html = html.replace(new RegExp('<img(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>', 'gi'), '<img$1$3>');
-                                       html = html.replace(new RegExp('<img(.*?[^>])\sstyle="" (.*?[^>])>', 'gi'), '<img$1 $2>');
-                                       html = html.replace(new RegExp('<img(.*?[^>])\sstyle (.*?[^>])>', 'gi'), '<img$1 $2>');
-                                       html = html.replace(new RegExp('<span class="redactor-invisible-space">(.*?)</span>', 'gi'), '$1');
+                                       html = html.replace(/<div(.*?[^>]) data-tagblock="redactor"(.*?[^>])>/gi, '<div$1$2>');
+                                       html = html.replace(/<(.*?) data-verified="redactor"(.*?[^>])>/gi, '<$1$2>');
+                                       html = html.replace(/<span(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>/gi, '<span$1$3>');
+                                       html = html.replace(/<img(.*?[^>])\srel="(.*?[^>])"(.*?[^>])>/gi, '<img$1$3>');
+                                       html = html.replace(/<img(.*?[^>])\sstyle="" (.*?[^>])>'/gi, '<img$1 $2>');
+                                       html = html.replace(/<img(.*?[^>])\sstyle (.*?[^>])>'/gi, '<img$1 $2>');
+                                       html = html.replace(/<span class="redactor-invisible-space">(.*?)<\/span>/gi, '$1');
                                        html = html.replace(/ data-save-url="(.*?[^>])"/gi, '');
 
                                        // remove image resize
                                        });
 
                                },
+                               cleanEmptyParagraph: function()
+                               {
+                                       var p = this.$editor.find("p").first();
+
+                                       if (this.utils.isEmpty(p.html()))
+                                       {
+                                               p.remove();
+                                       }
+                               },
                                setVerified: function(html)
                                {
                                        if (this.utils.browser('msie')) return html;
 
                                        this.start = false;
 
-                                       // autosave on change
+                                       if (this.autosave.html == false)
+                                       {
+                                               this.autosave.html = this.code.get();
+                                       }
+
+                                       //autosave
                                        this.autosave.onChange();
+                                       this.autosave.enable();
                                },
                                toggle: function()
                                {
                                        this.code.offset = this.caret.getOffset();
                                        var scroll = $(window).scrollTop();
 
-                                       var height = this.$editor.innerHeight();
+                                       var     width = this.$editor.innerWidth(),
+                                               height = this.$editor.innerHeight();
 
                                        this.$editor.hide();
 
                                        // indent code
                                        html = this.tabifier.get(html);
 
-                                       this.$textarea.val(html).height(height).show().focus();
-                                       this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
-
-                                       $(window).scrollTop(scroll);
+                                       this.$textarea.val(html);
 
-                                       if (this.$textarea[0].setSelectionRange)
+                                       if (this.opts.codemirror)
                                        {
-                                               this.$textarea[0].setSelectionRange(0, 0);
+                                               this.$textarea.next('.CodeMirror').each(function(i, el)
+                                               {
+                                                       $(el).show();
+                                                       el.CodeMirror.setValue(html);
+                                                       el.CodeMirror.setSize(width, height);
+                                                       el.CodeMirror.refresh();
+                                                       el.CodeMirror.focus();
+                                               });
                                        }
+                                       else
+                                       {
+                                               this.$textarea.height(height).show().focus();
+                                               this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
+
+                                               $(window).scrollTop(scroll);
+
+                                               if (this.$textarea[0].setSelectionRange)
+                                               {
+                                                       this.$textarea[0].setSelectionRange(0, 0);
+                                               }
 
-                                       this.$textarea[0].scrollTop = 0;
+                                               this.$textarea[0].scrollTop = 0;
+                                       }
 
                                        this.opts.visual = false;
 
                                },
                                showVisual: function()
                                {
+                                       var html;
+
                                        if (this.opts.visual) return;
 
-                                       var html = this.$textarea.hide().val();
+                                       if (this.opts.codemirror)
+                                       {
+                                               this.$textarea.next('.CodeMirror').each(function(i, el)
+                                               {
+                                                       html = el.CodeMirror.getValue();
+                                               });
+                                       }
+                                       else
+                                       {
+                                               html = this.$textarea.hide().val();
+                                       }
 
                                        if (this.modified !== this.clean.removeSpaces(html))
                                        {
                                                this.code.set(html);
                                        }
 
+                                       if (this.opts.codemirror)
+                                       {
+                                               this.$textarea.next('.CodeMirror').hide();
+                                       }
+
                                        this.$editor.show();
 
                                        if (!this.utils.isEmpty(html))
                                        {
                                                $.each(this.opts.formattingAdd, $.proxy(function(i,s)
                                                {
-                                                       var name = s.tag;
+                                                       var name = s.tag,
+                                                               func;
+
                                                        if (typeof s['class'] != 'undefined')
                                                        {
                                                                name = name + '-' + s['class'];
                                                        }
 
                                                        s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline';
-                                                       var func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting';
+
+                                                       if (typeof s.func !== "undefined")
+                                                       {
+                                                               func = s.func;
+                                                       }
+                                                       else
+                                                       {
+                                                               func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting';
+                                                       }
 
                                                        if (this.opts.linebreaks && s.type == 'block' && s.tag == 'p') return;
 
                                },
                                showEdit: function($image)
                                {
-                                       var $link = $image.closest('a');
+                                       var $link = $image.closest('a', this.$editor[0]);
 
                                        this.modal.load('imageEdit', this.lang.get('edit'), 705);
 
                                        this.image.hideResize();
                                        this.buffer.set();
 
-                                       var $link = $image.closest('a');
+                                       var $link = $image.closest('a', this.$editor[0]);
 
                                        $image.attr('alt', $('#redactor-image-title').val());
 
                                },
                                hideResize: function(e)
                                {
-                                       if (e && $(e.target).closest('#redactor-image-box').length !== 0) return;
+                                       if (e && $(e.target).closest('#redactor-image-box', this.$editor[0]).length !== 0) return;
                                        if (e && e.target.tagName == 'IMG')
                                        {
                                                var $image = $(e.target);
                                remove: function(image)
                                {
                                        var $image = $(image);
-                                       var $link = $image.closest('a');
-                                       var $figure = $image.closest('figure');
+                                       var $link = $image.closest('a', this.$editor[0]);
+                                       var $figure = $image.closest('figure', this.$editor[0]);
                                        var $parent = $image.parent();
                                        if ($('#redactor-image-box').length !== 0)
                                        {
 
                                        var current = this.selection.getCurrent();
 
-                                       var $item = $(current).closest('li');
+                                       var $item = $(current).closest('li', this.$editor[0]);
                                        var $parent = $item.parent();
                                        if ($item.length !== 0 && $parent.length !== 0 && $parent[0].tagName == 'LI')
                                        {
                                formatCollapsed: function(tag)
                                {
                                        var current = this.selection.getCurrent();
-                                       var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']');
+                                       var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']', this.$editor[0]);
 
                                        // inline there is
                                        if ($parent.length !== 0 && (this.inline.type != 'style' && $parent[0].tagName != 'SPAN'))
                                                var $table = false;
                                                if (this.keydown.block && this.keydown.block.tagName === 'TD')
                                                {
-                                                       $table = $(this.keydown.block).closest('table');
+                                                       $table = $(this.keydown.block).closest('table', this.$editor[0]);
                                                }
 
                                                if ($table && $table.find('td').last()[0] === this.keydown.block)
                                                        else
                                                        {
                                                                current = this.selection.getCurrent();
-                                                               var $parent = $(current).closest('li');
-                                                               var $list = $parent.closest('ul,ol');
+                                                               var $parent = $(current).closest('li', this.$editor[0]);
+                                                               var $list = $parent.closest('ul,ol', this.$editor[0]);
 
-                                                               if ($parent.length !== 0 && this.utils.isEmpty($parent.html()) && $list.next().length === 0)
+                                                               if ($parent.length !== 0 && this.utils.isEmpty($parent.html()) && $list.next().length === 0 && this.utils.isEmpty($list.find("li").last().html()))
                                                                {
+                                                                       $list.find("li").last().remove();
+
                                                                        var node = $(this.opts.emptyHtml);
                                                                        $list.after(node);
                                                                        this.caret.setStart(node);
                                {
                                        var $current = $(this.keydown.current);
                                        var $parent = $(this.keydown.parent);
-                                       var td = $current.closest('td');
+                                       var td = $current.closest('td', this.$editor[0]);
 
-                                       if (td.length !== 0 && $current.closest('li') && $parent.children('li').length === 1)
+                                       if (td.length !== 0 && $current.closest('li', this.$editor[0]) && $parent.children('li').length === 1)
                                        {
                                                if (!this.utils.isEmpty($current.text())) return;
 
                                        }
 
                                        // linkify
-                                       if (this.keyup.isLinkify(key))
-                                       {
-                                               this.formatLinkify(this.opts.linkProtocol, this.opts.convertLinks, this.opts.convertUrlLinks, this.opts.convertImageLinks, this.opts.convertVideoLinks, this.opts.linkSize);
-
-                                               this.observe.load();
-                                               this.code.sync();
-                                       }
+                                       if (this.linkify.isEnabled() && this.linkify.isKey(key))
+                                               this.linkify.format();
 
                                        if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
                                        {
                                                return this.keyup.formatEmpty(e);
                                        }
                                },
-                               isLinkify: function(key)
-                               {
-                                       return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && key === this.keyCode.ENTER && !this.utils.isCurrentOrParent('PRE');
-                               },
                                replaceToParagraph: function(clone)
                                {
                                        var $current = $(this.keyup.current);
                                {
                                        this.link.$node = false;
 
-                                       var $el = $(this.selection.getCurrent()).closest('a');
+                                       var $el = $(this.selection.getCurrent()).closest('a', this.$editor[0]);
                                        if ($el.length !== 0 && $el[0].tagName === 'A')
                                        {
                                                this.link.$node = $el;
                                },
                                insert: function()
                                {
+                                       this.placeholder.remove();
+
                                        var target = '';
                                        var link = this.link.$inputUrl.val();
                                        var text = this.link.$inputText.val();
                                        {
                                                this.buffer.set();
 
-                                               this.link.$node.text(text).attr('href', link);
+                                               var $link = this.link.$node,
+                                                       $el   = $link.children();
+
+                                               if ($el.length > 0)
+                                               {
+                                                       while ($el.length)
+                                                       {
+                                                               $el = $el.children();
+                                                       }
+
+                                                       $el = $el.end();
+                                               }
+                                               else
+                                               {
+                                                       $el = $link;
+                                               }
+
+                                               $link.attr('href', link);
+                                               $el.text(text);
+
                                                if (target !== '')
                                                {
-                                                       this.link.$node.attr('target', target);
+                                                       $link.attr('target', target);
                                                }
                                                else
                                                {
-                                                       this.link.$node.removeAttr('target');
+                                                       $link.removeAttr('target');
                                                }
 
+                                               this.selection.selectElement($link);
+
                                                this.code.sync();
                                        }
                                        else
                                                                if (target !== '') $a.attr('target', target);
 
                                                                $a = $(this.insert.node($a));
+
+                                                               if (this.selection.getText().match(/\s$/))
+                                                               {
+                                                                       $a.after(" ");
+                                                               }
+
                                                                this.selection.selectElement($a);
                                                        }
                                                        else
                                                        {
                                                                document.execCommand('createLink', false, link);
 
-                                                               $a = $(this.selection.getCurrent()).closest('a');
+                                                               $a = $(this.selection.getCurrent()).closest('a', this.$editor[0]);
                                                                if (this.utils.browser('mozilla'))
                                                                {
                                                                        $a = $('a[_moz_dirty=""]');
                                                                if (target !== '') $a.attr('target', target);
                                                                $a.removeAttr('style').removeAttr('_moz_dirty');
 
-                                                               if (this.link.text !== '' || this.link.text != text)
+                                                               if (this.selection.getText().match(/\s$/))
                                                                {
+                                                                       $a.after(" ");
+                                                               }
 
+                                                               if (this.link.text !== '' || this.link.text != text)
+                                                               {
                                                                        $a.text(text);
+
                                                                        this.selection.selectElement($a);
                                                                }
                                                        }
                                        var len = nodes.length;
                                        for (var i = 0; i < len; i++)
                                        {
-                                               var $node = $(nodes[i]).closest('a');
+                                               var $node = $(nodes[i]).closest('a', this.$editor[0]);
                                                $node.replaceWith($node.contents());
                                        }
 
                                        this.selection.save();
 
                                        var parent = this.selection.getParent();
-                                       var $list = $(parent).closest('ol, ul');
+                                       var $list = $(parent).closest('ol, ul', this.$editor[0]);
 
                                        if (!this.utils.isRedactorParent($list) && $list.length !== 0)
                                        {
                                                }
                                        }
 
-
                                        this.selection.restore();
                                        this.code.sync();
                                },
                                {
                                        var parent = this.selection.getParent();
                                        var current = this.selection.getCurrent();
-                                       var $td = $(current).closest('td, th');
+                                       var $td = $(current).closest('td, th', this.$editor[0]);
 
                                        if (this.utils.browser('msie') && this.opts.linebreaks)
                                        {
                                                document.execCommand('insert' + cmd);
                                        }
 
-                                       var $list = $(this.selection.getParent()).closest('ol, ul');
+                                       var $list = $(this.selection.getParent()).closest('ol, ul', this.$editor[0]);
 
                                        if ($td.length !== 0)
                                        {
 
                                        this.indent.fixEmptyIndent();
 
-                                       if (!this.opts.linebreaks && $current.closest('li, th, td').length === 0)
+                                       if (!this.opts.linebreaks && $current.closest('li, th, td', this.$editor[0]).length === 0)
                                        {
                                                document.execCommand('formatblock', false, 'p');
                                                this.$editor.find('ul, ol, blockquote').each($.proxy(this.utils.removeEmpty, this));
                                        }
 
-                                       var $table = $(this.selection.getCurrent()).closest('table');
+                                       var $table = $(this.selection.getCurrent()).closest('table', this.$editor[0]);
                                        var $prev = $table.prev();
                                        if (!this.opts.linebreaks && $table.length !== 0 && $prev.length !== 0 && $prev[0].tagName == 'BR')
                                        {
 
                                        $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
                                        {
-                                               var parentEl = $(parent).closest(key);
-                                               var currentEl = $(current).closest(key);
+                                               var parentEl = $(parent).closest(key, this.$editor[0]);
+                                               var currentEl = $(current).closest(key, this.$editor[0]);
 
                                                if (parentEl.length !== 0 && !this.utils.isRedactorParent(parentEl)) return;
                                                if (!this.utils.isRedactorParent(currentEl)) return;
-                                               if (parentEl.length !== 0 || currentEl.closest(key).length !== 0)
+                                               if (parentEl.length !== 0 || currentEl.closest(key, this.$editor[0]).length !== 0)
                                                {
                                                        this.button.setActive(value);
                                                }
 
                                        }, this));
 
-                                       var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase());
+                                       var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
                                        if (this.utils.isRedactorParent(parent) && $parent.length)
                                        {
                                                var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align');
                                                var $img = $(img);
 
                                                // IE fix (when we clicked on an image and then press backspace IE does goes to image's url)
-                                               $img.closest('a').on('click', function(e) { e.preventDefault(); });
+                                               $img.closest('a', this.$editor[0]).on('click', function(e) { e.preventDefault(); });
 
                                                if (this.utils.browser('msie')) $img.attr('unselectable', 'on');
 
                                },
                                showTooltip: function(e)
                                {
-                                       var $link = $(e.target);
-                                       var $parent = $link.closest('a');
-                                       var tag = ($link.length !== 0) ? $link[0].tagName : false;
+                                       var $el = $(e.target);
 
-                                       if ($parent[0].tagName === 'A')
-                                       {
-                                               if (tag === 'IMG') return;
-                                               else if (tag !== 'A') $link = $parent;
-                                       }
+                                       if ($el[0].tagName == 'IMG')
+                                               return;
 
-                                       if (tag !== 'A')
-                                       {
+                                       if ($el[0].tagName !== 'A')
+                                               $el = $el.closest('a', this.$editor[0]);
+
+                                       if ($el[0].tagName !== 'A')
                                                return;
-                                       }
+
+                                       var $link = $el;
 
                                        var pos = this.observe.getTooltipPosition($link);
                                        var tooltip = $('<span class="redactor-link-tooltip"></span>');
                                        e = e.originalEvent || e;
 
                                        var target = e.target;
-                                       var $parent = $(target).closest('a');
+                                       var $parent = $(target).closest('a', this.$editor[0]);
                                        if ($parent.length !== 0 && $parent[0].tagName === 'A' && target.tagName !== 'A')
                                        {
                                                return;
 
                                                $(window).off('scroll.redactor-freeze');
 
-                                       }, this), 1);
+                                               if (this.linkify.isEnabled())
+                                                       this.linkify.format();
 
+                                       }, this), 1);
                                },
                                createPasteBox: function()
                                {
                                                left: left
                                        });
 
+                                       if (scrollTop > end)
+                                               $('.redactor-dropdown-' + this.uuid + ':visible').hide();
+
                                        this.toolbar.setDropdownsFixed();
                                        this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden');
                                },
 
                                        this.toolbar.unsetDropdownsFixed();
                                        this.$toolbar.removeClass('toolbar-fixed-box');
-
-
                                },
                                setDropdownsFixed: function()
                                {
 
                                        var xhr = new XMLHttpRequest();
                                        xhr.open('POST', this.upload.url);
+                                       xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
 
                                        // complete
                                        xhr.onreadystatechange = $.proxy(function()
                                // tag detection
                                isTag: function(current, tag)
                                {
-                                       var element = $(current).closest(tag);
+                                       var element = $(current).closest(tag, this.$editor[0]);
                                        if (element.length == 1)
                                        {
                                                return element[0];
                                        return browser == match[1];
                                }
                        };
-               }
-       };
+               },
+               linkify: function()
+               {
+                       return {
+                               isKey: function(key)
+                               {
+                                       return key == this.keyCode.ENTER || key == this.keyCode.SPACE;
+                               },
+                               isEnabled: function()
+                               {
+                                       return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && !this.utils.isCurrentOrParent('PRE');
+                               },
+                               format: function()
+                               {
+                                       var linkify = this.linkify,
+                                               opts    = this.opts;
 
-       $(window).on('load.tools.redactor', function()
-       {
-               $('[data-tools="redactor"]').redactor();
-       });
+                                       this.$editor
+                                               .find(":not(iframe,img,a,pre)")
+                                               .addBack()
+                                               .contents()
+                                               .filter(function()
+                                               {
+                                                       return this.nodeType === 3 && $.trim(this.nodeValue) != "" && !$(this).parent().is("pre") && (this.nodeValue.match(opts.linkify.regexps.youtube) || this.nodeValue.match(opts.linkify.regexps.vimeo) || this.nodeValue.match(opts.linkify.regexps.image) || this.nodeValue.match(opts.linkify.regexps.url));
+                                               })
+                                               .each(function()
+                                               {
+                                                       var text = $(this).text(),
+                                                               html = text;
 
-       // constructor
-       Redactor.prototype.init.prototype = Redactor.prototype;
 
-       // LINKIFY
-       $.Redactor.fn.formatLinkify = function(protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize)
-       {
-               var urlCheck = '((?:http[s]?:\\/\\/(?:www\\.)?|www\\.){1}(?:[0-9A-Za-z\\-%_]+\\.)+[a-zA-Z]{2,}(?::[0-9]+)?(?:(?:/[0-9A-Za-z\\-#\\.%\+_]*)+)?(?:\\?(?:[0-9A-Za-z\\-\\.%_]+(?:=[0-9A-Za-z\\-\\.%_\\+]*)?)?(?:&(?:[0-9A-Za-z\\-\\.%_]+(?:=[0-9A-Za-z\\-\\.%_\\+]*)?)?)*)?(?:#[0-9A-Za-z\\-\\.%_\\+=\\?&;]*)?)';
-               var regex = new RegExp(urlCheck, 'gi');
-               var rProtocol = /(https?|ftp):\/\//i;
-               var urlImage = new RegExp('(?:([^:/?#]+):)?(?:\/\/([^/?#]*))?([^?#]*\\.(?:jpg|gif|png))(?:\\?([^#]*))?(?:#(.*))?', 'gi');
+                                                       if (opts.convertImageLinks && html.match(opts.linkify.regexps.image))
+                                                       {
+                                                               html = linkify.convertImages(html);
+                                                       }
+                                                       else if (opts.convertUrlLinks && !html.match(opts.linkify.regexps.youtube) && !html.match(opts.linkify.regexps.vimeo))
+                                                       {
+                                                               html = linkify.convertLinks(html);
+                                                       }
 
-               var childNodes = (this.$editor ? this.$editor[0] : this).childNodes, i = childNodes.length;
-               while (i--)
-               {
-                       var n = childNodes[i];
+                                                       if (opts.convertVideoLinks)
+                                                       {
+                                                               html = linkify.convertVideoLinks(html);
+                                                       }
 
-                       if (n.nodeType === 3 && n.parentNode !== 'PRE')
-                       {
-                               var html = n.nodeValue;
+                                                       $(this).before(text.replace(text, html))
+                                                                  .remove();
+                                               });
 
-                               // youtube & vimeo
-                               if (convertVideoLinks && html)
+                                       this.linkify.after();
+                               },
+                               convertVideoLinks: function(html)
                                {
                                        var iframeStart = '<iframe width="500" height="281" src="',
                                                iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
 
-                                       if (html.match(reUrlYoutube))
+                                       if (html.match(this.opts.linkify.regexps.youtube))
                                        {
-                                               html = html.replace(reUrlYoutube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
-                                               $(n).after(html).remove();
+                                               html = html.replace(this.opts.linkify.regexps.youtube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
                                        }
-                                       else if (html.match(reUrlVimeo))
+
+                                       if (html.match(this.opts.linkify.regexps.vimeo))
                                        {
-                                               html = html.replace(reUrlVimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
-                                               $(n).after(html).remove();
+                                               html = html.replace(this.opts.linkify.regexps.vimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
                                        }
-                               }
 
-                               // image
-                               if (convertImageLinks && html && html.match(urlImage))
+                                       return html;
+                               },
+                               convertImages: function(html)
                                {
-                                       var matches = html.match(urlImage);
-                                       html = html.replace(urlImage, '<img src="' + matches + '" />');
-
-                                       $(n).after(html).remove();
-                                       return;
-                               }
+                                       var matches = html.match(this.opts.linkify.regexps.image);
 
-                               // link
-                               if (html.search(/\$/g) != -1) html = html.replace(/\$/g, '&#36;');
+                                       if (matches)
+                                               html = html.replace(html, '<img src="' + matches + '" />');
 
-                               var matches = html.match(regex);
-                               if (convertUrlLinks && html && matches)
+                                       return html;
+                               },
+                               convertLinks: function(html)
                                {
-                                       var len = matches.length;
-                                       for (var z = 0; z < len; z++)
+                                       var matches = html.match(this.opts.linkify.regexps.url);
+
+                                       if (matches)
                                        {
-                                               // remove dot in the end
-                                               if (matches[z].match(/\.$/) !== null) matches[z] = matches[z].replace(/\.$/, '');
+                                               matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; });
 
-                                               var href = matches[z];
-                                               var text = href;
+                                               var length = matches.length;
 
-                                               var space = '';
-                                               if (href.match(/\s$/) !== null) space = ' ';
+                                               for (var i = 0; i < length; i++)
+                                               {
+                                                       var href = matches[i],
+                                                               text = href,
+                                                               linkProtocol = this.opts.linkProtocol + '://';
 
-                                               var addProtocol = protocol + '://';
-                                               if (href.match(rProtocol) !== null) addProtocol = '';
+                                                       if (href.match(/(https?|ftp):\/\//i) !== null)
+                                                               linkProtocol = "";
 
-                                               if (text.length > linkSize) text = text.substring(0, linkSize) + '...';
-                                               text = text.replace(/&#36;/g, '$').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+                                                       if (text.length > this.opts.linkSize)
+                                                               text = text.substring(0, this.opts.linkSize) + '...';
 
-                                               // buffer
-                                               var buffer = [];
-                                               var links = html.match('<a(.*?)</a>');
-                                               if (links !== null)
-                                               {
-                                                       var len = links.length;
-                                                       for (i = 0; i < len; i++)
-                                                       {
-                                                               buffer[i] = links[i];
-                                                               html = html.replace(links[i], '{abuffer' + i + '}');
-                                                       }
-                                               }
+                                                       text = decodeURIComponent(text);
 
-                                               html = html.replace(href, '<a href=\"' + addProtocol + $.trim(href) + '\">' + $.trim(text) + '</a>' + space);
+                                                       // escaping url
+                                                       var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + ')\\b', 'g');
 
-                                               // rebuffer
-                                               $.each(buffer, function(i,s)
-                                               {
-                                                       html = html.replace('{abuffer' + i + '}', s);
-                                               });
+                                                       html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '">' + $.trim(text) + '</a>');
+                                               }
                                        }
 
-                                       $(n).after(html).remove();
+                                       return html;
+                               },
+                               after: function()
+                               {
+                                       this.observe.load();
+                                       this.code.sync();
                                }
                        }
-                       else if (n.nodeType === 1 && !/^(pre|a|button|textarea)$/i.test(n.tagName))
-                       {
-                               $.Redactor.fn.formatLinkify.call(n, protocol, convertLinks, convertUrlLinks, convertImageLinks, convertVideoLinks, linkSize);
-                       }
                }
        };
 
+       $(window).on('load.tools.redactor', function()
+       {
+               $('[data-tools="redactor"]').redactor();
+       });
+
+       // constructor
+       Redactor.prototype.init.prototype = Redactor.prototype;
 })(jQuery);
\ No newline at end of file