Вход Регистрация
Файл: framework/javascript/HtmlEditorField.js
Строк: 2139
<?php
/**
 * Functions for HtmlEditorFields in the back end.
 * Includes the JS for the ImageUpload forms. 
 * 
 * Relies on the jquery.form.js plugin to power the 
 * ajax / iframe submissions
 */

var ss ss || {};
/**
 * Wrapper for HTML WYSIWYG libraries, which abstracts library internals
 * from interface concerns like inserting and editing links.
 * Caution: Incomplete and unstable API.
 */
ss.editorWrappers = {};
ss.editorWrappers.tinyMCE = (function() {
    
    var 
instance;

    return {
        
init: function(config) {
            if(!
ss.editorWrappers.tinyMCE.initialized) {
                
tinyMCE.init(config);

                
ss.editorWrappers.tinyMCE.initialized true;
            }
        },
        
/**
         * @return Mixed Implementation specific object
         */
        
getInstance: function() {
            return 
this.instance;
        },
        
/**
         * Invoked when a content-modifying UI is opened.
         */
        
onopen: function() {
        },
        
/**
         * Invoked when a content-modifying UI is closed.
         */
        
onclose: function() {
        },
        
/**
         * Write the HTML back to the original text area field.
         */
        
save: function() {
            
tinyMCE.triggerSave();
        },
        
/**
         * Create a new instance based on a textarea field.
         *
         * Please proxy the events from your editor implementation into JS events
         * on the textarea field. For events that do not map directly, use the 
         * following naming scheme: editor<event>.
         *
         * @param String
         * @param Object Implementation specific configuration
         * @param Function
         */
        
create: function(domIDconfig) {
            
this.instance = new tinymce.Editor(domIDconfig);

            
// Patch TinyMCE events into underlying textarea field.
            
this.instance.onInit.add(function(ed) {
                if(!
ss.editorWrappers.tinyMCE.patched) {
                    
// Not ideal, but there's a memory leak we need to patch
                    
var originalDestroy tinymce.themes.AdvancedTheme.prototype.destroy;

                    
tinymce.themes.AdvancedTheme.prototype.destroy = function() {
                        
originalDestroy.apply(thisarguments);

                        if (
this.statusKeyboardNavigation) {
                            
this.statusKeyboardNavigation.destroy();
                            
this.statusKeyboardNavigation null;
                        }
                    };

                    
ss.editorWrappers.tinyMCE.patched true;
                }

                
jQuery(ed.getElement()).trigger('editorinit');

                
// Periodically check for inline changes when focused,
                // since TinyMCE's onChange only fires on certain actions
                // like inserting a new paragraph, as opposed to any user input.
                // This also works around an issue where the "save" button
                // wouldn't trigger if the click is the cause of a "blur" event
                // after an (undetected) inline change. This "blur" causes onChange
                // to trigger, which will change the button markup to show "alternative" styles,
                // effectively cancelling the original click event.
                
if(ed.settings.update_interval) {
                    var 
interval;
                    
jQuery(ed.getBody()).on('focus', function() {
                        
interval setInterval(function() {
                            
// Update underlying element as necessary
                            
var element jQuery(ed.getElement());
                            if(
ed.isDirty()) {
                                
// Set content without triggering editor content cleanup
                                
element.val(ed.getContent({format 'raw'no_events 1}));
                            }
                        }, 
ed.settings.update_interval);
                    });
                    
jQuery(ed.getBody()).on('blur', function() {
                        
clearInterval(interval);
                    });
                }
            });
            
this.instance.onChange.add(function(edl) {
                
// Update underlying textarea on every change, so external handlers
                // such as changetracker have a chance to trigger properly.
                
ed.save();
                
jQuery(ed.getElement()).trigger('change');
            });
            
// Add more events here as needed.

            
this.instance.render();
        },
        
/**
         * Redraw the editor contents
         */
        
repaint: function() {
            
tinyMCE.execCommand("mceRepaint");
        },
        
/**
         * @return boolean
         */
        
isDirty: function() {
            return 
this.getInstance().isDirty();
        },
        
/**
         * HTML representation of the edited content.
         * 
         * Returns: {String}
         */
        
getContent: function() {
            return 
this.getInstance().getContent();
        },
        
/**
         * DOM tree of the edited content
         * 
         * Returns: DOMElement
         */
        
getDOM: function() {
            return 
this.getInstance().dom;
        },
        
/**
         * Returns: DOMElement
         */
        
getContainer: function() {
            return 
this.getInstance().getContainer();
        },
        
/**
         * Get the closest node matching the current selection.
         * 
         * Returns: {jQuery} DOMElement
         */
        
getSelectedNode: function() {
            return 
this.getInstance().selection.getNode();
        },
        
/**
         * Select the given node within the editor DOM
         * 
         * Parameters: {DOMElement}
         */
        
selectNode: function(node) {
            
this.getInstance().selection.select(node);
        },
        
/**
         * Replace entire content
         * 
         * @param String HTML
         * @param Object opts
         */
        
setContent: function(htmlopts) {
            
this.getInstance().execCommand('mceSetContent'falsehtmlopts);
        },
        
/**
         * Insert content at the current caret position
         * 
         * @param String HTML
         */
        
insertContent: function(htmlopts) {
            
this.getInstance().execCommand('mceInsertContent'falsehtmlopts);
        },
        
/**
         * Replace currently selected content
         * 
         * @param {String} html
         */
        
replaceContent: function(htmlopts) {
            
this.getInstance().execCommand('mceReplaceContent'falsehtmlopts);
        },
        
/**
         * Insert or update a link in the content area (based on current editor selection)
         * 
         * Parameters: {Object} attrs
         */
        
insertLink: function(attrsopts) {
            
this.getInstance().execCommand("mceInsertLink"falseattrsopts);
        },
        
/**
         * Remove the link from the currently selected node (if any).
         */
        
removeLink: function() {
            
this.getInstance().execCommand('unlink'false);
        },
        
/**
         * Strip any editor-specific notation from link in order to make it presentable in the UI.
         * 
         * Parameters: 
         *  {Object} 
         *  {DOMElement}
         */
        
cleanLink: function(hrefnode) {
            var 
cb tinyMCE.settings['urlconverter_callback'];
            if(
cbhref = eval(cb "(href, node, true);");

            
// Turn into relative
            
if(href.match(new RegExp('^' tinyMCE.settings['document_base_url'] + '(.*)$'))) {
                
href RegExp.$1;
            }
            
            
// Get rid of TinyMCE's temporary URLs
            
if(href.match(/^javascript:s*mctmp/)) href '';

            return 
href;
        },
        
/**
         * Creates a bookmark for the currently selected range,
         * which can be used to reselect this range at a later point.
         * @return {mixed}
         */
        
createBookmark: function() {
            return 
this.getInstance().selection.getBookmark();
        },
        
/**
         * Selects a bookmarked range previously saved through createBookmark().
         * @param  {mixed} bookmark
         */
        
moveToBookmark: function(bookmark) {
            
this.getInstance().selection.moveToBookmark(bookmark);
            
this.getInstance().focus();
        },
        
/**
         * Removes any selection & de-focuses this editor
         */
        
blur: function() {
            
this.getInstance().selection.collapse();
        },
        
/**
         * Add new undo point with the current DOM content.
         */
        
addUndo: function() {
            
this.getInstance().undoManager.add();
        }
    };
});
// Override this to switch editor wrappers
ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;


(function($) {

    $.
entwine('ss', function($) {

        
/**
         * Class: textarea.htmleditor
         * 
         * Add tinymce to HtmlEditorFields within the CMS. Works in combination
         * with a TinyMCE.init() call which is prepopulated with the used HTMLEditorConfig settings,
         * and included in the page as an inline <script> tag.
         */
        
$('textarea.htmleditor').entwine({

            
Editornull,

            
/**
             * Constructor: onmatch
             */
            
onadd: function() {
                var 
edClass this.data('editor') || 'default'ed ss.editorWrappers[edClass]();
                
this.setEditor(ed);

                
// Using a global config (generated through HTMLEditorConfig PHP logic).
                // Depending on browser cache load behaviour, entwine's DOMMaybeChanged
                // can be called before the bottom-most inline script tag is executed,
                // which defines the global. If that's the case, wait for the window load.
                
if(typeof ssTinyMceConfig != 'undefined'this.redraw();

                
this._super();
            },
            
onremove: function() {
                var 
ed tinyMCE.get(this.attr('id'));
                if (
ed) {
                    try {
                        
ed.remove();
                    } catch(
ex) {}
                    try {
                        
ed.destroy();
                    } catch(
ex) {}
                    
                    
// Remove any residual tinyMCE editor element
                    
this.next('.mceEditor').remove();

                    
// TinyMCE leaves behind events. We should really fix TinyMCE, but lets brute force it for now
                    
$.each(jQuery.cache, function(){
                        var 
source this.handle && this.handle.elem;
                        if (!
source) return;

                        var 
parent source;
                        try {
                            while (
parent && parent.nodeType == 1parent parent.parentNode;
                        }
                        catch(
err) {}

                        if (!
parent) $(source).unbind().remove();
                    });
                }

                
this._super();
            },

            
getContainingForm: function(){
                return 
this.closest('form');
            },

            
fromWindow: {
                
onload: function(){
                    
this.redraw();
                }
            },

            
redraw: function() {
                
// Using textarea config ID from global config object (generated through HTMLEditorConfig PHP logic)
                
var config ssTinyMceConfig[this.data('config')], self thised this.getEditor();

                
ed.init(config);

                
// Create editor instance and render it.
                // Similar logic to adapter/jquery/jquery.tinymce.js, but doesn't rely on monkey-patching
                // jQuery methods, and avoids replicate the script lazyloading which is already in place with jQuery.ondemand.
                
ed.create(this.attr('id'), config);

                
this._super();
            },

            
/**
             * Make sure the editor has flushed all it's buffers before the form is submitted.
             */
            
'from .cms-edit-form': {
                
onbeforesubmitform: function(e) {
                    
this.getEditor().save();
                    
this._super();
                }
            },

            
oneditorinit: function() {
                
// Delayed show because TinyMCE calls hide() via setTimeout on removing an element,
                // which is called in quick succession with adding a new editor after ajax loading new markup

                //storing the container object before setting timeout
                
var redrawObj = $(this.getEditor().getInstance().getContainer());
                
setTimeout(function() {
                    
redrawObj.show();
                }, 
10);
            },

            
'from .cms-container': {
                
onbeforestatechange: function(){
                    
this.css('visibility''hidden');

                    var 
ed this.getEditor(), container = (ed && ed.getInstance()) ? ed.getContainer() : null;
                    if(
container && container.lengthcontainer.remove();
                }
            },

            
isChanged: function() {
                var 
ed this.getEditor();
                return (
ed && ed.getInstance() && ed.isDirty());
            },
            
resetChanged: function() {
                var 
ed this.getEditor();
                if(
typeof tinyMCE == 'undefined') return;

                
// TODO Abstraction layer
                
var inst tinyMCE.getInstanceById(this.attr('id'));
                if (
instinst.startContent tinymce.trim(inst.getContent({format 'raw'no_events 1}));
            },
            
openLinkDialog: function() {
                
this.openDialog('link');
            },
            
openMediaDialog: function() {
                
this.openDialog('media');
            },
            
openDialog: function(type) {
                var 
capitalize = function(text) {
                    return 
text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
                };

                var 
self thisurl = $('#cms-editor-dialogs').data('url' capitalize(type) + 'form'),
                    
dialog = $('.htmleditorfield-' type 'dialog');

                if(
dialog.length) {
                    
dialog.getForm().setElement(this);
                    
dialog.open();
                } else {
                    
// Show a placeholder for instant feedback. Will be replaced with actual
                    // form dialog once its loaded.
                    
dialog = $('<div class="htmleditorfield-dialog htmleditorfield-' type 'dialog loading">');
                    $(
'body').append(dialog);
                    $.
ajax({
                        
urlurl,
                        
complete: function() {
                            
dialog.removeClass('loading');
                        },
                        
success: function(html) {
                            
dialog.html(html);
                            
dialog.getForm().setElement(self);
                            
dialog.trigger('ssdialogopen');
                        }
                    });
                }
            }
        });

        $(
'.htmleditorfield-dialog').entwine({
            
onadd: function() {
                
// Create jQuery dialog
                
if (!this.is('.ui-dialog-content')) {
                    
this.ssdialog({autoOpentrue});
                }

                
this._super();
            },

            
getForm: function() {
                return 
this.find('form');
            },
            
open: function() {
                
this.ssdialog('open');
            },
            
close: function() {
                
this.ssdialog('close');
            },
            
toggle: function(bool) {
                if(
this.is(':visible')) this.close();
                else 
this.open();
            }
        });

        
/**
         * Base form implementation for interactions with an editor instance,
         * mostly geared towards modification and insertion of content.
         */
        
$('form.htmleditorfield-form').entwine({
            
Selectionnull,

            
// Implementation-dependent serialization of the current editor selection state
            
Bookmarknull,
            
            
// DOMElement pointing to the currently active textarea
            
Elementnull,

            
setSelection: function(node) {
                return 
this._super($(node));
            },

            
onadd: function() {
                
// Move title from headline to (jQuery compatible) title attribute
                
var titleEl this.find(':header:first');
                
this.getDialog().attr('title'titleEl.text());

                
this._super();
            },
            
onremove: function() {
                
this.setSelection(null);
                
this.setBookmark(null);
                
this.setElement(null);

                
this._super();
            },

            
getDialog: function() {
                
// TODO Refactor to listen to form events to remove two-way coupling
                
return this.closest('.htmleditorfield-dialog');
            },

            
fromDialog: {
                
onssdialogopen: function(){
                    var 
ed this.getEditor();
                    
ed.onopen();

                    
this.setSelection(ed.getSelectedNode());
                    
this.setBookmark(ed.createBookmark());

                    
ed.blur();

                    
this.find(':input:not(:submit)[data-skip-autofocus!="true"]').filter(':visible:enabled').eq(0).focus();

                    
this.redraw();
                    
this.updateFromEditor();
                },

                
onssdialogclose: function(){
                    var 
ed this.getEditor();
                    
ed.onclose();

                    
ed.moveToBookmark(this.getBookmark());

                    
this.setSelection(null);
                    
this.setBookmark(null);

                    
this.resetFields();
                }
            },

            
/**
             * @return Object ss.editorWrapper instance
             */
            
getEditor: function(){
                return 
this.getElement().getEditor();
            },

            
modifySelection: function(callback) {
                var 
ed this.getEditor();

                
ed.moveToBookmark(this.getBookmark());
                
callback.call(thised);

                
this.setSelection(ed.getSelectedNode());
                
this.setBookmark(ed.createBookmark());

                
ed.blur();
            },

            
updateFromEditor: function() {
                
/* NOP */
            
},
            
redraw: function() {
                
/* NOP */
            
},
            
resetFields: function() {
                
// Flush the tree drop down fields, as their content might get changed in other parts of the CMS, ie in Files and images
                
this.find('.tree-holder').empty();
            }
        });

        
/**
         * Inserts and edits links in an html editor, including internal/external web links,
         * links to files on the webserver, email addresses, and anchors in the existing html content.
         * Every variation has its own fields (e.g. a "target" attribute doesn't make sense for an email link),
         * which are toggled through a type dropdown. Variations share fields, so there's only one "title" field in the form.
         */
        
$('form.htmleditorfield-linkform').entwine({

            
// TODO Entwine doesn't respect submits triggered by ENTER key
            
onsubmit: function(e) {
                
this.insertLink();
                
this.getDialog().close();
                return 
false;
            },
            
resetFields: function() {
                
this._super();

                
// Reset the form using a native call. This will also correctly reset checkboxes and radio buttons.
                
this[0].reset();
            },
            
redraw: function() {
                
this._super();

                var 
linkType this.find(':input[name=LinkType]:checked').val();

                
this.addAnchorSelector();

                
this.resetFileField();

                
// Toggle field visibility depending on the link type.
                
this.find('div.content .field').hide();
                
this.find('.field[id$="LinkType"]').show();
                
this.find('.field[id$="' linkType +'_Holder"]').show();

                if(
linkType == 'internal' || linkType == 'anchor') {
                    
this.find('.field[id$="Anchor_Holder"]').show();
                }

                if(
linkType == 'email') {
                    
this.find('.field[id$="Subject_Holder"]').show();
                } else {
                    
this.find('.field[id$="TargetBlank_Holder"]').show();
                }

                if(
linkType == 'anchor') {
                    
this.find('.field[id$="AnchorSelector_Holder"]').show();
                }
                
this.find('.field[id$="Description_Holder"]').show();
            },
            
/**
             * @return Object Keys: 'href', 'target', 'title'
             */
            
getLinkAttributes: function() {
                var 
href,
                    
target null,
                    
subject this.find(':input[name=Subject]').val(),
                    
anchor this.find(':input[name=Anchor]').val();
                
                
// Determine target
                
if(this.find(':input[name=TargetBlank]').is(':checked')) {
                    
target '_blank';
                }

                
// All other attributes
                
switch(this.find(':input[name=LinkType]:checked').val()) {
                    case 
'internal':
                        
href '[sitetree_link,id=' this.find(':input[name=internal]').val() + ']';

                        if(
anchor) {
                            
href += '#' anchor;
                        }

                        break;

                    case 
'anchor':
                        
href '#' anchor
                        break;
                    
                    case 
'file':
                        
href '[file_link,id=' this.find('.ss-uploadfield .ss-uploadfield-item').attr('data-fileid') + ']';
                        
target '_blank';
                        break;
                    
                    case 
'email':
                        
href 'mailto:' this.find(':input[name=email]').val();
                        if(
subject) {
                            
href += '?subject=' encodeURIComponent(subject);
                        }
                        
target null;
                        break;

                    
// case 'external':
                    
default:
                        
href this.find(':input[name=external]').val();
                        
// Prefix the URL with "http://" if no prefix is found
                        
if(href.indexOf('://') == -1href 'http://' href;
                        break;
                }

                return {
                    
href href,
                    
target target,
                    
title this.find(':input[name=Description]').val()
                };
            },
            
insertLink: function() {
                
this.modifySelection(function(ed){
                    
ed.insertLink(this.getLinkAttributes());
                });
            },
            
removeLink: function() {
                
this.modifySelection(function(ed){
                    
ed.removeLink();
                });

                
this.resetFileField();
                
this.close();
            },

            
resetFileField: function() {
                
// If there's an attached item, remove it
                
var fileField this.find('.ss-uploadfield[id$="file_Holder"]'),
                    
fileUpload fileField.data('fileupload'),
                    
currentItem fileField.find('.ss-uploadfield-item[data-fileid]');

                if(
currentItem.length) {
                    
fileUpload._trigger('destroy'null, {contextcurrentItem});
                    
fileField.find('.ss-uploadfield-addfile').removeClass('borderTop');
                }
            },

            
/**
             * Builds an anchor selector element and injects it into the DOM next to the anchor field.
             */
            
addAnchorSelector: function() {
                
// Avoid adding twice
                
if(this.find(':input[name=AnchorSelector]').length) return;

                var 
self this;
                var 
anchorSelector = $(
                    
'<select id="Form_EditorToolbarLinkForm_AnchorSelector" name="AnchorSelector"></select>'
                
);
                
this.find(':input[name=Anchor]').parent().append(anchorSelector);

                
// Initialise the anchor dropdown.
                
this.updateAnchorSelector();

                
// copy the value from dropdown to the text field
                
anchorSelector.change(function(e) {
                    
self.find(':input[name="Anchor"]').val($(this).val());
                });
            },

            
/**
             * Fetch relevant anchors, depending on the link type.
             *
             * @return $.Deferred A promise of an anchor array, or an error message.
             */
            
getAnchors: function() {
                var 
linkType this.find(':input[name=LinkType]:checked').val();
                var 
dfdAnchors = $.Deferred();

                switch (
linkType) {
                    case 
'anchor':
                        
// Fetch from the local editor.
                        
var collectedAnchors = [];
                        var 
ed this.getEditor();
                        
// name attribute is defined as CDATA, should accept all characters and entities
                        // http://www.w3.org/TR/1999/REC-html401-19991224/struct/links.html#h-12.2

                        
if(ed) {
                            var 
raw ed.getContent().match(/s(name|id)="([^"]+?)"|s(name|id)='([^']+?)'/gim);
                            if (raw && raw.length) {
                                for(var i = 0; i < raw.length; i++) {
                                    var indexStart = (raw[i].indexOf('id=') == -1) ? 7 : 5;
                                    collectedAnchors.push(raw[i].substr(indexStart).replace(/"
$/, ''));
                                }
                            }
                        }

                        
dfdAnchors.resolve(collectedAnchors);
                        break;

                    case 
'internal':
                        
// Fetch available anchors from the target internal page.
                        
var pageId this.find(':input[name=internal]').val();

                        if (
pageId) {
                            $.
ajax({
                                
url: $.path.addSearchParams(
                                    
this.attr('action').replace('LinkForm''getanchors'),
                                    {
'PageID'parseInt(pageId)}
                                ),
                                
success: function(bodystatusxhr) {
                                    
dfdAnchors.resolve($.parseJSON(body));
                                },
                                
error: function(xhrstatus) {
                                    
dfdAnchors.reject(xhr.responseText);
                                }
                            });
                        } else {
                            
dfdAnchors.resolve([]);
                        }
                        break;

                    default:
                        
// This type does not support anchors at all.
                        
dfdAnchors.reject(ss.i18n._t(
                            
'HtmlEditorField.ANCHORSNOTSUPPORTED',
                            
'Anchors are not supported for this link type.'
                        
));
                        break;
                }

                return 
dfdAnchors.promise();
            },

            
/**
             * Update the anchor list in the dropdown.
             */
            
updateAnchorSelector: function() {
                var 
self this;
                var 
selector this.find(':input[name=AnchorSelector]');
                var 
dfdAnchors this.getAnchors();

                
// Inform the user we are loading.
                
selector.empty();
                
selector.append($(
                    
'<option value="" selected="1">' +
                    
ss.i18n._t('HtmlEditorField.LOOKINGFORANCHORS''Looking for anchors...') +
                    
'</option>'
                
));

                
dfdAnchors.done(function(anchors) {
                    
selector.empty();
                    
selector.append($(
                        
'<option value="" selected="1">' +
                        
ss.i18n._t('HtmlEditorField.SelectAnchor') +
                        
'</option>'
                    
));

                    if (
anchors) {
                        for (var 
0anchors.lengthj++) {
                            
selector.append($('<option value="'+anchors[j]+'">'+anchors[j]+'</option>'));
                        }
                    }

                }).
fail(function(message) {
                    
selector.empty();
                    
selector.append($(
                        
'<option value="" selected="1">' +
                        
message +
                        
'</option>'
                    
));
                });

                
// Poke the selector for IE8, otherwise the changes won't be noticed.
                
if ($.browser.msieselector.hide().show();
            },

            
/**
             * Updates the state of the dialog inputs to match the editor selection.
             * If selection does not contain a link, resets the fields.
             */
            
updateFromEditor: function() {
                var 
htmlTagPattern = /<S[^><]*>/gfieldNamedata this.getCurrentLink();

                if(
data) {
                    for(
fieldName in data) {
                        var 
el this.find(':input[name=' fieldName ']'), selected data[fieldName];
                        
// Remove html tags in the selected text that occurs on IE browsers
                        
if(typeof(selected) == 'string'selected selected.replace(htmlTagPattern''); 

                        
// Set values and invoke the triggers (e.g. for TreeDropdownField).
                        
if(el.is(':checkbox')) {
                            
el.prop('checked'selected).change();
                        } else if(
el.is(':radio')) {
                            
el.val([selected]).change();
                        } else if(
fieldName == 'file') {
                            
// UploadField inputs have a slightly different naming convention
                            
el this.find(':input[name="' fieldName '[Uploads][]"]');
                            
// We need the UploadField "field", not just the input
                            
el el.parents('.ss-uploadfield');
                            
                            
// We have to wait for the UploadField to initialise
                            
(function attach(elselected) {
                                if( ! 
el.getConfig()) {
                                    
setTimeout(function(){ attach(elselected); }, 50);
                                } else {
                                    
el.attachFiles([selected]);
                                }
                            })(
elselected);
                        } else {
                            
el.val(selected).change();
                        }
                    }
                }
            },

            
/**
             * Return information about the currently selected link, suitable for population of the link form.
             *
             * Returns null if no link was currently selected.
             */
            
getCurrentLink: function() {
                var 
selectedEl this.getSelection(),
                    
href ""target ""title ""action "insert"style_class "";

                
// We use a separate field for linkDataSource from tinyMCE.linkElement.
                // If we have selected beyond the range of an <a> element, then use use that <a> element to get the link data source,
                // but we don't use it as the destination for the link insertion
                
var linkDataSource null;
                if(
selectedEl.length) {
                    if(
selectedEl.is('a')) {
                        
// Element is a link
                        
linkDataSource selectedEl;
                    
// TODO Limit to inline elements, otherwise will also apply to e.g. paragraphs which already contain one or more links
                    // } else if((selectedEl.find('a').length)) {
                        //     // Element contains a link
                        //     var firstLinkEl = selectedEl.find('a:first');
                        //     if(firstLinkEl.length) linkDataSource = firstLinkEl;
                    
} else {
                        
// Element is a child of a link
                        
linkDataSource selectedEl selectedEl.parents('a:first');
                    }
                }
                if(
linkDataSource && linkDataSource.lengththis.modifySelection(function(ed){
                    
ed.selectNode(linkDataSource[0]);
                });

                
// Is anchor not a link
                
if (!linkDataSource.attr('href')) linkDataSource null;

                if (
linkDataSource) {
                    
href linkDataSource.attr('href');
                    
target linkDataSource.attr('target');
                    
title linkDataSource.attr('title');
                    
style_class linkDataSource.attr('class');
                    
href this.getEditor().cleanLink(hreflinkDataSource);
                    
action "update";
                }

                if(
href.match(/^mailto:(.*)$/)) {
                    return {
                        
LinkType'email',
                        
emailRegExp.$1,
                        
Descriptiontitle
                    
};
                } else if(
href.match(/^(assets/.*)$/) || href.match(/^[file_links*(?:s*|%20|,)?id=([0-9]+)]?(#.*)?$/)) {
                    
return {
                        
LinkType'file',
                        
fileRegExp.$1,
                        
Descriptiontitle,
                        
TargetBlanktarget true false
                    
};
                } else if(
href.match(/^#(.*)$/)) {
                    
return {
                        
LinkType'anchor',
                        
AnchorRegExp.$1,
                        
Descriptiontitle,
                        
TargetBlanktarget true false
                    
};
                } else if(
href.match(/^[sitetree_link(?:s*|%20|,)?id=([0-9]+)]?(#.*)?$/i)) {
                    
return {
                        
LinkType'internal',
                        
internalRegExp.$1,
                        
AnchorRegExp.$RegExp.$2.substr(1) : '',
                        
Descriptiontitle,
                        
TargetBlanktarget true false
                    
};
                } else if(
href) {
                    return {
                        
LinkType'external',
                        
externalhref,
                        
Descriptiontitle,
                        
TargetBlanktarget true false
                    
};
                } else {
                    
// No link/invalid link selected.
                    
return null;
                }
            }
        });

        $(
'form.htmleditorfield-linkform input[name=LinkType]').entwine({
            
onclick: function(e) {
                
this.parents('form:first').redraw();
                
this._super();
            },
            
onchange: function() {
                
this.parents('form:first').redraw();

                
// Update if a anchor-supporting link type is selected.
                
var linkType this.parent().find(':checked').val();
                if (
linkType==='anchor' || linkType==='internal') {
                    
this.parents('form.htmleditorfield-linkform').updateAnchorSelector();
                }
                
this._super();
            }
        });

        $(
'form.htmleditorfield-linkform input[name=internal]').entwine({
            
/**
             * Update the anchor dropdown if a different page is selected in the "internal" dropdown.
             */
            
onvalueupdated: function() {
                
this.parents('form.htmleditorfield-linkform').updateAnchorSelector();
                
this._super();
            }
        });

        $(
'form.htmleditorfield-linkform :submit[name=action_remove]').entwine({
            
onclick: function(e) {
                
this.parents('form:first').removeLink();
                
this._super();
                return 
false;
            }
        });

        
/**
         * Responsible for inserting media files, although only images are supported so far.
         * Allows to select one or more files, and load form fields for each file via ajax.
         * This allows us to tailor the form fields to the file type (e.g. different ones for images and flash),
         * as well as add new form fields via framework extensions.
         * The inputs on each of those files are used for constructing the HTML to insert into
         * the rich text editor. Also allows editing the properties of existing files if any are selected in the editor.
         * Note: Not each file has a representation on the webserver filesystem, supports insertion and editing
         * of remove files as well.
         */
        
$('form.htmleditorfield-mediaform').entwine({
            
toggleCloseButton: function(){
                var 
updateExisting Boolean(this.find('.ss-htmleditorfield-file').length);
                
this.find('.overview .action-delete')[updateExisting 'hide' 'show']();
            },
            
onsubmit: function() {                
                
this.modifySelection(function(ed){
                    
this.find('.ss-htmleditorfield-file').each(function() {
                        $(
this).insertHTML(ed);
                    });

                    
ed.repaint();
                });

                
this.getDialog().close();
                return 
false;
            },
            
updateFromEditor: function() {            
                var 
self thisnode this.getSelection();

                
// TODO Depends on managed mime type
                
if(node.is('img')) {
                    
this.showFileView(node.data('url') || node.attr('src')).done(function(filefield) {
                        
filefield.updateFromNode(node);
                        
self.toggleCloseButton();
                        
self.redraw();
                    });
                }
                
this.redraw();
            },
            
redraw: function(updateExisting) {
                
this._super();
            
                var 
node this.getSelection(),
                    
hasItems Boolean(this.find('.ss-htmleditorfield-file').length),
                    
editingSelected node.is('img'),
                    
header this.find('.header-edit');

                
// Only show second step if files are selected
                
header[(hasItems) ? 'show' 'hide']();

                
// Disable "insert" button if no files are selected
                
this.find('.Actions :submit')
                    .
button(hasItems 'enable' 'disable')
                    .
toggleClass('ui-state-disabled', !hasItems); 
                    
                
// Hide file selection and step labels when editing an existing file
                
this.find('#MediaFormInsertMediaTabs,.header-edit')[editingSelected 'hide' 'show']();

                
// TODO Way too much knowledge on UploadField internals, use viewfile URL directly instead
                
this.find('.htmleditorfield-mediaform-heading.insert')[editingSelected 'hide' 'show']();
                
this.find('.ss-uploadfield-item-actions')[editingSelected 'hide' 'show']();
                
this.find('.ss-uploadfield-item-name')[editingSelected 'hide' 'show']();
                
this.find('.ss-uploadfield-item-preview')[editingSelected 'hide' 'show']();
                
this.find('.Actions .media-insert')[editingSelected 'hide' 'show']();
                
this.find('.htmleditorfield-mediaform-heading.update')[editingSelected 'show' 'hide']();
                
this.find('.Actions .media-update')[editingSelected 'show' 'hide']();
                
this.find('.ss-uploadfield-item-editform').toggleEditForm(editingSelected);
            },
            
resetFields: function() {
                
this.find('.ss-htmleditorfield-file').remove(); // Remove any existing views
                
this.find('.ss-gridfield-items .ui-selected').removeClass('ui-selected'); // Unselect all items
                
this.find('li.ss-uploadfield-item').remove(); // Remove all selected items
                
this.redraw();

                
this._super();
            },
            
getFileView: function(idOrUrl) {
                return 
this.find('.ss-htmleditorfield-file[data-id=' idOrUrl ']');
            },
            
showFileView: function(idOrUrl) {
                var 
self thisparams = (Number(idOrUrl) == idOrUrl) ? {IDidOrUrl} : {FileURLidOrUrl};

                var 
item = $('<div class="ss-htmleditorfield-file loading" />');
                
this.find('.content-edit').prepend(item);
                
                var 
dfr = $.Deferred();
                
                $.
ajax({
                    
url: $.path.addSearchParams(this.attr('action').replace(/MediaForm/, 'viewfile'), params),
                    
success: function(htmlstatusxhr) {
                        var 
newItem = $(html).filter('.ss-htmleditorfield-file');
                        
item.replaceWith(newItem);
                        
self.redraw();
                        
dfr.resolve(newItem);
                    },
                    
error: function() {
                        
item.remove();
                        
dfr.reject();
                    }
                });
                
                return 
dfr.promise();
            }
        });

        $(
'form.htmleditorfield-mediaform .ss-gridfield-items').entwine({
            
onselectableselected: function(eui) {
                var 
form this.closest('form'), item = $(ui.selected);
                if(!
item.is('.ss-gridfield-item')) return;
                
form.closest('form').showFileView(item.data('id'));
                
form.redraw();
            },
            
onselectableunselected: function(eui) {
                var 
form this.closest('form'), item = $(ui.unselected);
                if(!
item.is('.ss-gridfield-item')) return;
                
form.getFileView(item.data('id')).remove();
                
form.redraw();
            }
        });

        
/**
         * Show the second step after uploading an image
         */
        
$('form.htmleditorfield-form.htmleditorfield-mediaform div.ss-assetuploadfield').entwine({
            
//the UploadField div.ss-uploadfield-editandorganize is hidden in CSS,
            // because we use the detail view for each individual file instead
            
onfileuploadstop: function(e) {
                var 
form this.closest('form');

                
//update the editFields to show those Files that are newly uploaded
                
var editFieldIDs = [];
                
form.find('div.content-edit').find('div.ss-htmleditorfield-file').each(function(){
                    
//get the uploaded file ID when this event triggers, signaling the upload has compeleted successfully
                    
editFieldIDs.push($(this).data('id'));
                });
                
// we only want this .ss-uploadfield-files - else we get all ss-uploadfield-files wich include the ones not related to #tinymce insertmedia
                
var uploadedFiles = $('.ss-uploadfield-files'this).children('.ss-uploadfield-item');
                
uploadedFiles.each(function(){
                    var 
uploadedID = $(this).data('fileid');
                    if (
uploadedID && $.inArray(uploadedIDeditFieldIDs) == -1) {
                        
//trigger the detail view for filling out details about the file we are about to insert into TinyMCE
                        
$(this).remove(); // Remove successfully added item from the queue
                        
form.showFileView(uploadedID);
                    }
                });

                
form.redraw();
            }

        });

        $(
'form.htmleditorfield-form.htmleditorfield-mediaform input.remoteurl').entwine({
            
onadd: function() {
                
this._super();
                
this.validate();
            },

            
onkeyup: function() {
                
this.validate();
            },

            
onchange: function() {
                
this.validate();
            },

            
getAddButton: function() {
                return 
this.closest('.CompositeField').find('button.add-url');
            },

            
validate: function() {
                var 
val this.val(), orig val;
                
                
val = $.trim(val);
                
val val.replace(/^https?:///i, '');
                
if (orig !== valthis.val(val);

                
this.getAddButton().button(!!val 'enable' 'disable');
                return !!
val;
            }
        });

        
/**
         * Show the second step after adding a URL
         */
        
$('form.htmleditorfield-form.htmleditorfield-mediaform .add-url').entwine({
            
getURLField: function() {
                return 
this.closest('.CompositeField').find('input.remoteurl');
            },

            
onclick: function(e) {
                var 
urlField this.getURLField(), container this.closest('.CompositeField'), form this.closest('form');

                if (
urlField.validate()) {
                    
container.addClass('loading');
                    
form.showFileView('http://' urlField.val()).done(function() {
                        
container.removeClass('loading');
                    });
                    
form.redraw();
                }

                return 
false;
            }
        });

        
/**
         * Represents a single selected file, together with a set of form fields to edit its properties.
         * Overload this based on the media type to determine how the HTML should be created.
         */
        
$('form.htmleditorfield-mediaform .ss-htmleditorfield-file').entwine({
            
/**
             * @return {Object} Map of HTML attributes which can be set on the created DOM node.
             */
            
getAttributes: function() {
            },
            
/**
             * @return {Object} Map of additional properties which can be evaluated
             * by the specific media type.
             */
            
getExtraData: function() {
            },
            
/**
             * @return {String} HTML suitable for insertion into the rich text editor
             */
            
getHTML: function() {
                
// Assumes UploadField markup structure
                
return $('<div>').append(
                    $(
'<a/>').attr({hrefthis.data('url')}).text(this.find('.name').text())
                ).
html();
            },
            
/**
             * Insert updated HTML content into the rich text editor
             */
            
insertHTML: function(ed) {
                
// Insert content
                
ed.replaceContent(this.getHTML());
            },
            
/**
             * Updates the form values from an existing node in the editor.
             * 
             * @param {DOMElement}
             */
            
updateFromNode: function(node) {
            },
            
/**
             * Transforms values set on the dimensions form fields based on two constraints:
             * An aspect ration, and max width/height values. Writes back to the field properties as required.
             * 
             * @param {String} The dimension to constrain the other value by, if any ("Width" or "Height")
             * @param {Int} Optional max width
             * @param {Int} Optional max height
             */
            
updateDimensions: function(constrainBymaxWmaxH) {
                var 
widthEl this.find(':input[name=Width]'),
                    
heightEl this.find(':input[name=Height]'),
                    
widthEl.val(),
                    
heightEl.val(),
                    
aspect;

                
// Proportionate updating of heights, using the original values
                
if(&& h) {
                    if(
constrainBy) {
                        
aspect heightEl.getOrigVal() / widthEl.getOrigVal();
                        
// Uses floor() and ceil() to avoid both fields constantly lowering each other's values in rounding situations
                        
if(constrainBy == 'Width') {
                            if(
maxW && maxWmaxW;
                            
Math.floor(aspect);
                        } else if(
constrainBy == 'Height') {
                            if(
maxH && maxHmaxH;
                            
Math.ceil(aspect);
                        }
                    } else {
                        if(
maxW && maxWmaxW;
                        if(
maxH && maxHmaxH;
                    }

                    
widthEl.val(w);
                    
heightEl.val(h);
                }
            }
        });

        $(
'form.htmleditorfield-mediaform .ss-htmleditorfield-file.image').entwine({
            
getAttributes: function() {
                var 
width this.find(':input[name=Width]').val(),
                    
height this.find(':input[name=Height]').val();
                return {
                    
'src' this.find(':input[name=URL]').val(),
                    
'alt' this.find(':input[name=AltText]').val(),
                    
'width' width parseInt(width10) : null,
                    
'height' height parseInt(height10) : null,
                    
'title' this.find(':input[name=Title]').val(),
                    
'class' this.find(':input[name=CSSClass]').val()
                };
            },
            
getExtraData: function() {
                return {
                    
'CaptionText'this.find(':input[name=CaptionText]').val()
                };
            },
            
getHTML: function() {
                
/* NOP */
            
},
            
/**
             * Logic similar to TinyMCE 'advimage' plugin, insertAndClose() method.
             */
            
insertHTML: function(ed) {
                var 
form this.closest('form');
                var 
node form.getSelection();
                if (!
eded form.getEditor();

                
// Get the attributes & extra data
                
var attrs this.getAttributes(), extraData this.getExtraData();

                
// Find the element we are replacing - either the img, it's wrapper parent, or nothing (if creating)
                
var replacee = (node && node.is('img')) ? node null;
                if (
replacee && replacee.parent().is('.captionImage')) replacee replacee.parent();

                
// Find the img node - either the existing img or a new one, and update it
                
var img = (node && node.is('img')) ? node : $('<img />');
                
img.attr(attrs);

                
// Any existing figure or caption node
                
var container img.parent('.captionImage'), caption container.find('.caption');

                
// If we've got caption text, we need a wrapping div.captionImage and sibling p.caption
                
if (extraData.CaptionText) {
                    if (!
container.length) {
                        
container = $('<div></div>');
                    }

                    
container.attr('class''captionImage '+attrs['class']).css('width'attrs.width);

                    if (!
caption.length) {
                        
caption = $('<p class="caption"></p>').appendTo(container);
                    }

                    
caption.attr('class''caption '+attrs['class']).text(extraData.CaptionText);
                }
                
// Otherwise forget they exist
                
else {
                    
container caption null;
                }

                
// The element we are replacing the replacee with
                
var replacer container container img;

                
// If we're replacing something, and it's not with itself, do so
                
if (replacee && replacee.not(replacer).length) {
                    
replacee.replaceWith(replacer);
                }

                
// If we have a wrapper element, make sure the img is the first child - img might be the
                // replacee, and the wrapper the replacer, and we can't do this till after the replace has happened
                
if (container) {
                    
container.prepend(img);
                }

                
// If we don't have a replacee, then we need to insert the whole HTML
                
if (!replacee) {
                    
// Otherwise insert the whole HTML content
                    
ed.repaint();
                    
ed.insertContent($('<div />').append(replacer).html(), {skip_undo 1});
                }

                
ed.addUndo();
                
ed.repaint();
            },
            
updateFromNode: function(node) {
                
this.find(':input[name=AltText]').val(node.attr('alt'));
                
this.find(':input[name=Title]').val(node.attr('title'));
                
this.find(':input[name=CSSClass]').val(node.attr('class'));
                
this.find(':input[name=Width]').val(node.width());
                
this.find(':input[name=Height]').val(node.height());
                
this.find(':input[name=CaptionText]').val(node.siblings('.caption:first').text());
            }
        });


        
/**
         * Insert a flash object tag into the content.
         * Requires the 'media' plugin for serialization of tags into <img> placeholders.
         */
        
$('form.htmleditorfield-mediaform .ss-htmleditorfield-file.flash').entwine({
            
getAttributes: function() {
                var 
width this.find(':input[name=Width]').val(),
                    
height this.find(':input[name=Height]').val();
                return {
                    
'src' this.find(':input[name=URL]').val(),
                    
'width' width parseInt(width10) : null,
                    
'height' height parseInt(height10) : null
                
};
            },
            
getHTML: function() {
                var 
attrs this.getAttributes();

                
// Emulate serialization from 'media' plugin
                
var el tinyMCE.activeEditor.plugins.media.dataToImg({
                    
'type''flash',
                    
'width'attrs.width,
                    
'height'attrs.height,
                    
'params': {'src'attrs.src},
                    
'video': {'sources': []}
                });
                
                return $(
'<div />').append(el).html(); // Little hack to get outerHTML string
            
},
            
updateFromNode: function(node) {
                
// TODO Not implemented
            
}
        });


        
/**
         * Insert an oembed object tag into the content.
         * Requires the 'media' plugin for serialization of tags into <img> placeholders.
         */
        
$('form.htmleditorfield-mediaform .ss-htmleditorfield-file.embed').entwine({
            
getAttributes: function() {
                var 
width this.find(':input[name=Width]').val(),
                    
height this.find(':input[name=Height]').val();
                return {
                    
'src' this.find('.thumbnail-preview').attr('src'),
                    
'width' width parseInt(width10) : null,
                    
'height' height parseInt(height10) : null,
                    
'class' this.find(':input[name=CSSClass]').val(),
                    
'alt' this.find(':input[name=AltText]').val(),
                    
'title' this.find(':input[name=Title]').val()
                };
            },
            
getExtraData: function() {
                var 
width this.find(':input[name=Width]').val(),
                    
height this.find(':input[name=Height]').val();
                return {
                    
'CaptionText'this.find(':input[name=CaptionText]').val(),
                    
'Url'this.find(':input[name=URL]').val(),
                    
'thumbnail'this.find('.thumbnail-preview').attr('src'),
                    
'width' width parseInt(width10) : null,
                    
'height' height parseInt(height10) : null,
                    
'cssclass'this.find(':input[name=CSSClass]').val()
                };
            },
            
getHTML: function() {
                var 
el,
                    
attrs this.getAttributes(),
                    
extraData this.getExtraData(),
                    
// imgEl = $('<img id="_ss_tmp_img" />');
                    
imgEl = $('<img />').attr(attrs).addClass('ss-htmleditorfield-file embed');

                $.
each(extraData, function (keyvalue) {
                    
imgEl.attr('data-' keyvalue);
                });

                if(
extraData.CaptionText) {
                    
el = $('<div style="width: ' attrs['width'] + 'px;" class="captionImage ' attrs['class'] + '"><p class="caption">' extraData.CaptionText '</p></div>').prepend(imgEl);
                } else {
                    
el imgEl;
                }
                return $(
'<div />').append(el).html(); // Little hack to get outerHTML string
            
},
            
updateFromNode: function(node) {
                
this.find(':input[name=AltText]').val(node.attr('alt'));
                
this.find(':input[name=Title]').val(node.attr('title'));
                
this.find(':input[name=Width]').val(node.width());
                
this.find(':input[name=Height]').val(node.height());
                
this.find(':input[name=Title]').val(node.attr('title'));
                
this.find(':input[name=CSSClass]').val(node.data('cssclass'));
            }
        });

        $(
'form.htmleditorfield-mediaform .ss-htmleditorfield-file .dimensions :input').entwine({
            
OrigValnull,
            
onmatch: function () {
                
this._super();

                
this.setOrigVal(parseInt(this.val(), 10));
            },
            
onunmatch: function() {
                
this._super();
            },
            
onfocusout: function(e) {
                
this.closest('.ss-htmleditorfield-file').updateDimensions(this.attr('name'));
            }
        });

        
/**
         * Deselect item and remove the 'edit' view
         */
        
$('form.htmleditorfield-mediaform .ss-uploadfield-item .ss-uploadfield-item-cancel').entwine({
            
onclick: function(e) {
                var 
form this.closest('form'), file this.closest('ss-uploadfield-item');
                
form.find('.ss-gridfield-item[data-id=' file.data('id') + ']').removeClass('ui-selected');
                
this.closest('.ss-uploadfield-item').remove();
                
form.redraw();
                
e.preventDefault();
            }
        });

        $(
'div.ss-assetuploadfield .ss-uploadfield-item-edit, div.ss-assetuploadfield .ss-uploadfield-item-name').entwine({
            
getEditForm: function() {
                return 
this.closest('.ss-uploadfield-item').find('.ss-uploadfield-item-editform');
            },

            
fromEditForm: {
                
onchange: function(e){
                    var 
form = $(e.target);
                    
form.removeClass('edited'); //so edited class is only there once
                    
form.addClass('edited');
                }
            },

            
onclick: function(e) {
                var 
editForm this.getEditForm();
        
                
// Make sure we're in an HtmlEditorField here, or fall-back to _super(). HtmlEditorField with 
                // AssetUploadField doesn't use iframes, so needs its own toggleEditForm() logic
                
if (this.closest('.ss-uploadfield-item').hasClass('ss-htmleditorfield-file')) {
                    
editForm.parent('ss-uploadfield-item').removeClass('ui-state-warning');

                    
editForm.toggleEditForm();

                    
e.preventDefault(); // Avoid a form submit

                    
return false// Avoid duplication from button
                
}

                
this._super(e);
            }
        });

        $(
'div.ss-assetuploadfield .ss-uploadfield-item-editform').entwine({
            
toggleEditForm: function(bool) {
                var 
itemInfo this.prev('.ss-uploadfield-item-info'), status itemInfo.find('.ss-uploadfield-item-status');
                var 
text="";

                if(
bool === true || (bool !== false && this.height() === 0)) {
                    
text ss.i18n._t('UploadField.Editing'"Editing ...");
                    
this.height('auto');
                    
itemInfo.find('.toggle-details-icon').addClass('opened');                    
                    
status.removeClass('ui-state-success-text').removeClass('ui-state-warning-text');
                } else {
                    
this.height(0);                    
                    
itemInfo.find('.toggle-details-icon').removeClass('opened');
                    if(!
this.hasClass('edited')){
                        
text ss.i18n._t('UploadField.NOCHANGES''No Changes');
                        
status.addClass('ui-state-success-text');
                    }else{                        
                        
text ss.i18n._t('UploadField.CHANGESSAVED''Changes Made');
                        
this.removeClass('edited');
                        
status.addClass('ui-state-success-text');    
                    }
                
                }
                
status.attr('title',text).text(text);    
            }
        });


        $(
'form.htmleditorfield-mediaform .field[id$="ParentID_Holder"] .TreeDropdownField').entwine({
            
onadd: function() {
                
this._super();

                
// TODO Custom event doesn't fire in IE if registered through object literal
                
var self this;
                
this.bind('change', function() {
                    var 
fileList self.closest('form').find('.ss-gridfield');
                    
fileList.setState('ParentID'self.getValue());
                    
fileList.reload();
                });
            }
        });
        
    });
})(
jQuery);


/**
 * These callback globals hook it into tinymce.  They need to be referenced in the TinyMCE config.
 */
function sapphiremce_cleanup(typevalue) {
    if(
type == 'get_from_editor') {
    
        
// replace VML pixel image references with image tags - experimental
        
value value.replace(/<[a-z0-9]+:imagedata[^>]+src="?([^> "]+)"?[^>]*>/ig,"<img src="$1">");
        
        // Word comments
        value = value.replace(new RegExp('<(!--)([^>]*)(--)>', 'g'), ""); 
            
        // kill class=mso??? and on mouse* tags  
        value = value.replace(/([ frtn'"
])class=mso[a-z0-9]+[^ >]+/ig"$1"); 
        
value value.replace(/([ frtn'"]class=")mso[a-z0-9]+[^ ">]+ /ig, "$1"); 
        value = value.replace(/([ frtn'"])class="
mso[a-z0-9]+[^">]+"/ig"$1"); 
        
value value.replace(/([ frtn'"])on[a-z]+=[^ >]+/ig, "$1");
        value = value.replace(/ >/ig, ">"); 
    
        // remove everything that'
s in a closing tag
        value 
value.replace(/<(/[A-Za-z0-9]+)[ frtn]+[^>]*>/ig,"<$1>");        
    }

    if(
type == 'get_from_editor_dom') {
        
jQuery(value).find('img').each(function() {
            
this.onresizestart null;
            
this.onresizeend null;
            
this.removeAttribute('onresizestart');
            
this.removeAttribute('onresizeend');
        });
    }

    return 
value;
}
?>
Онлайн: 0
Реклама