Ext.namespace('Zarafa.plugins.quill');

/**
 * @class Zarafa.plugins.quill.QuillPlugin
 * @extends Zarafa.core.HtmlEditorPlugin
 *
 * Plugin that makes it possible to use an html editor plugin
 */
var editorPlugin = {};
if (Ext.isDefined(Zarafa.core.HtmlEditorPlugin)) {
	editorPlugin = Zarafa.core.HtmlEditorPlugin;
} else {
	// following code is for backword compatibility with older version of webapp.
	// in future we can remove this code
	if (Zarafa.plugins.htmleditor && Zarafa.plugins.htmleditor.Plugin) {
		editorPlugin = Zarafa.plugins.htmleditor.Plugin;
	}
	console.warn("Please upgrade Kopano WebApp to use the Quill plugin");
}

Zarafa.plugins.quill.QuillPlugin = Ext.extend(editorPlugin, {
	/**
	 * Editor plugins should overwrite this value with the xtype of their editor.
	 * @property
	 */
	editorXType: 'plugins.quilleditor',
});

// make the editorPlugin object to undefined
// after use of it.
editorPlugin = undefined;

// Don't load the plugin if browser is IE
if (!Ext.isIE) {
	Zarafa.onReady(function () {
		container.registerPlugin(new Zarafa.core.PluginMetaData({
			name: 'quill',
			allowUserDisable: false,
			allowUserVisible: false,
			displayName: _('Quill editor'),
			pluginConstructor: Zarafa.plugins.quill.QuillPlugin
		}));
	});
}
Ext.namespace('Zarafa.plugins.quill.ui');

var quillIdGen = 0;

Zarafa.plugins.quill.ui.QuillEditor = Ext.extend(Ext.Container, {
	/**
	 * The editor property which contains the instance of the Quill editor.
	 * @property
	 * @type Object
	 */
	editor: undefined,

	/**
	 * @constructor
	 * @param {Object} config configuration object.
	 */
	constructor: function(config) {
		this.id = 'k-quilleditor-' + quillIdGen++;

		config = Ext.applyIf(config || {}, {
			xtype: 'plugins.quilleditor',
			cls: 'k-quilltextarea',
			enableSystemContextMenu: true
		});

		Zarafa.plugins.quill.ui.QuillEditor.superclass.constructor.call(this, config);

		this.initEvents();
	},

	/**
	 * Function called after instance of {@link Ext.Container Container} has been created.
	 * @private
	 */
	initEvents: function() {
		this.on('activate', this.onActivateEditor, this);
		this.on('deactivate', this.onDeactivateEditor, this);
	},

	/**
	 * bind an {@link Zarafa.core.data.IPMRecord} to this component
	 * record is necessary to properly convert img tags, for which store and entryid are needed
	 * @param {Zarafa.core.data.IPMRecord} record
	 */
	bindRecord: function(record) {
		this.record = record;
	},

	/**
	 * Event handler which is fired when the {@link Ext.Container} has been rendered
	 * @private
	 */
	afterRender: function() {
		Zarafa.plugins.quill.ui.QuillEditor.superclass.afterRender.call(this, arguments);

		var useHtml = container.getSettingsModel().get('zarafa/v1/contexts/mail/dialogs/mailcreate/use_html_editor');
		if (Ext.isDefined(this.dialog) && this.dialog.isXType('zarafa.mailcreatecontentpanel') === false) {
			return;
		} else if (useHtml === false) {
			return;
		}

		this.createEditorInstance();

		// If the instance of the editor is signature-editor then disable it.
		if (this.editor.container.classList.contains('k-signature-editor')) {
			this.disable();
		}
	},

	/**
	 * Event handler triggered when deactivate event is fired by the editor instance.
	 * Function is used to hide the editor.
	 */
	onDeactivateEditor: function() {
		var editorContainer = this.editor.container;
		var editorToolbar = this.editor.getModule('toolbar');
		if (editorContainer.classList.contains('x-hide-display') === false) {
			editorContainer.setAttribute('class', editorContainer.getAttribute('class') + ' x-hide-display');
		}

		var toolbarContainer = editorToolbar.container;
		if (toolbarContainer.classList.contains('x-hide-display') === false) {
			toolbarContainer.setAttribute('class', toolbarContainer.getAttribute('class') + ' x-hide-display');
		}
	},

	/**
	 * Event handler triggered when activate event is fired by the editor instance.
	 * Function is used to show the editor else create new instance of Quill editor.
	 */
	onActivateEditor: function() {
		if (this.editor) {
			var editorContainer = this.editor.container;
			var toolbarContainer = this.editor.getModule('toolbar').container;
			editorContainer.classList.remove('x-hide-display');
			toolbarContainer.classList.remove('x-hide-display');
			return;
		}

		this.createEditorInstance();
	},

	/**
	 * Create a new instance of Quill editor.
	 */
	createEditorInstance: function() {
		var fontFamilies = this.getFontFamilies().map(function(font) {
			return font.name;
		});

		var formatting = {
			fontFamilies: fontFamilies,
			fontSize: Zarafa.common.ui.htmleditor.Fonts.getFontSizeString().split(' ')
		};
		
		var activeWindow = this.getActiveWindow();
		this.activeWindow = activeWindow;
		
		this.customizeQuill(formatting);

		var editorContainer = this.id;
		if (Zarafa.core.BrowserWindowMgr.isMainWindowActive() === false) {
			editorContainer = activeWindow.document.getElementById(editorContainer);
		} else {
			editorContainer = '#' + editorContainer;
		}

		this.editor = new activeWindow.Quill(editorContainer, {
			modules: {
				toolbar: {
					container:[
						['bold', 'italic', 'underline', 'strike'],
						[{ 'list': 'ordered'}, { 'list': 'bullet' }],
						['link', 'image', 'blockquote', 'clean']
					],
					handlers: {
						'clean': this.removeFormatting.bind(this)
					},
				},
				keyboard: {
					bindings: {
						selectAll: {
							// Ctrl + A
							key: 65,
							ctrlKey: true,
							handler: this.selectAllText
						},
						'blockquote empty enter': {
							key: 'Enter',
							format: [ 'blockquote' ],
							handler: this.removeEmptyBlockquote.bind(this)
						},
						'blockquote empty backspace': {
							key: 'Backspace',
							format: [ 'blockquote' ],
							empty: true,
							handler: this.removeEmptyBlockquote.bind(this)
						},
						tab: {
							key: 'Tab',
							handler: this.onPressTab.bind(this)
						},
						backspace: {
							key: 'Backspace',
							handler: this.onTextRemove.bind(this)
						},
						indent: {
							key: 'Tab',
							format: [ 'list' ],
							empty: true,
							handler: this.onEmptyListTab.bind(this)
						}
					}
				}
			},
			bounds: editorContainer,
			theme: 'snow'
		});

		var scrollDom = this.editor.scroll.domNode;
		if (!scrollDom.classList.contains('zarafa-contextmenu-enabled')) {
			scrollDom.classList.add('zarafa-contextmenu-enabled');
		}
		this.setDefaultFormatting(formatting);
		this.editor.emitter.emit('render', { scope: this });
		this.editor.on('text-change', this.onTextChange.bind(this));
		this.editor.root.addEventListener('click',this.onEditorClick.bind(this,Ext.EventObject));
        this.editor.root.addEventListener('paste',this.onPaste.bind(this));
        this.editor.on('editor-change', this.onEditorFocus.bind(this));
        this.editor.root.addEventListener('keydown', this.onLinkTextChange.bind(this));
	},
	
	/**
	 * Function disables the link format if the next character is a new line or a space.
	 */
	onLinkTextChange : function()
	{
		var editor = this.editor;
		var selectionRange = this.editor.getSelection();
		var currentFormat = editor.getFormat();
		var nextCharacter = editor.getText(selectionRange.index, 1);
		// If link format is enabled, we check if the next character is a new line or a space,
		// if true, we disable the link format.
		if (currentFormat['link']) {
			if (nextCharacter === '\n' || nextCharacter === ' ') {
				editor.format('link', false, 'user');
			}
		}
	},
	
	/**
	 * Function removes all the applied formatting on the selected text.
	 */
	removeFormatting : function() {
		var editor = this.editor;
		var quill = this.getQuillClass();
		var selectionRange = editor.getSelection();
		var defaultFormatting = this.getDefaultFontFormatting();
		
		//Scope of a blot can be INLINE or BLOCK
		var Scope = quill.import('parchment').Scope;
		var currentFormat = editor.getFormat();
		
		if (selectionRange === null) {
			return;
		}
		// In case no text is selected
		if (selectionRange.length === 0) {
			for (var format in currentFormat) {
				// query() searches for a Blot or Attributor
				// When given a specific scope, it finds blot with same name as the scope
				if (editor.scroll.query(format, Scope.INLINE) !== null) {
					// Remove the currently applied formatting
					editor.format(format, false, 'user');
				}
			}
		} else {
			editor.removeFormat(selectionRange, 'user');
			// Apply the default formatting to the selected text
			editor.formatText(selectionRange, {
				'font' : defaultFormatting.fontFamily,
				'size' : defaultFormatting.fontSize
			});
		}
		this.setDefaultFormatting();
	},

	/**
	 * Overridden default event handler triggered when user presses 'tab' key
	 * It will get the current format according to the index
	 * It will insert '\u00a0'(&nbsp) four times (to be treated as a tab) with the current formatting, as a delta in the editor and also set the selection accordingly.
	 */
    onPressTab : function()
    {
    	var editor = this.editor;
    	var quill = this.getQuillClass();
		var selectionRange = editor.getSelection();
		var Delta = quill.import('delta');
		var defaultFormat = editor.getFormat(selectionRange.index);
		var attributes = [];

		if (defaultFormat['link']) {
			defaultFormat['link'] = false;
		}
		
		for (var attribute in defaultFormat) {
			attributes[attribute] = defaultFormat[attribute];
		}

		//Changes made in short intervals are treated as a single change, and hence are reset at once on triggering undo,
		//using cutoff(), we can prevent undoing multiple changes at once by not merging them into one and
		//treating them as separate changes
		editor.history.cutoff();

		//'\u00a0' is the unicode for a whitespace character (nbsp), '\t' was treated as a single space by Quill,
		//so we use a hard-space(u00a0)
		var tabspace = '\u00a0';
		var delta = new Delta()
			.retain(selectionRange.index)
			.delete(selectionRange.length)
			.insert(tabspace.repeat(4), attributes);

		editor.updateContents(delta, 'user');
		editor.history.cutoff();
		editor.setSelection(selectionRange.index + 4, 'silent');
	},

	/**
	 * Event handler triggered when editor content has been changed by the user
	 * It will fire the 'keypress' event which used by the auto save functionality.
	 *
	 * @param {Array} delta The delta express format that can be used to describe Quill’s contents and changes
	 * @param {Array} oldDelta The delta express old content of quill.
	 * @param {String} source The source represent the change done by the either 'user', 'api' or 'silent'
	 */
	onTextChange: function(delta, oldDelta, source) {
		if (source === 'user') {
			this.fireEvent('keypress', this, Ext.EventObject);
		}
	},

	/**
     * Function will set the default formatting to pasted content.
     */
    onPaste : function() {
	    var quill = this.getQuillClass();
	    var defaultFormatting = this.getDefaultFontFormatting();
	    var Delta = quill.import('delta');
	    var currentFormat = this.editor.getFormat();
	
	    var formats = {
		    size : defaultFormatting.fontSize,
		    font : defaultFormatting.fontFamily,
		    // If the content, which is to be pasted, has some background and font color applied,
		    // we need to replace that with the default background and font color,
		    // because we haven't included the color formatting buttons in the editor,
		    // so it will not be possible to change colors using toolbar buttons.
		    background : 'white',
		    color : 'black'
	    };
	    
	    if (currentFormat['link']) {
	    	formats = {
			    size : defaultFormatting.fontSize,
			    font : defaultFormatting.fontFamily,
		    }
	    }
	
	    this.setDefaultFormatting();
	    // Apply the default format to the pasted content and update it.
	    var delta = new Delta().retain(this.editor.getSelection().index, formats);
	    this.editor.updateContents(delta, 'user');
	
	    // The background and font color, applied by Delta().retain(), formats only the pasted content,
	    // in case of code-blocks or other content which has some color applied, we need to set the default colors
	    // to the next character a user types.
	    this.editor.format('background', 'white', 'user');
	    this.editor.format('color', 'black', 'user');
	},

	/**
     * Overridden handler for 'backspace'.
	 * Function checks the range of the removed text,
	 * removes the text or lines and applies the format accordingly.
     *
     * @param {Object} range The selected range in the editor
	 * @param {Object} context Additional specifications for handler
     */
	onTextRemove: function(range, context) {
		var prevFormat = context.format; // Currently selected format
		var lines = this.editor.getLines(range); // The selected line(s)

		// If only one character is removed, then length would be 0
		if (lines.length === 0) {
			// If only one character is removed,
			// we get the format of the previous character
			prevFormat = this.editor.getFormat(range.index - 1);
			this.editor.deleteText(range.index - 1, 1, 'user');
		} else {
			// If more than one character is removed,
			// we get the format applied to the first line
			var lineIndex = this.editor.getIndex(lines[0]);
			prevFormat = this.editor.getFormat(lineIndex);
			this.editor.deleteText(range, 'user');
		}
		if (!prevFormat['link']) {
			for (var attribute in prevFormat) {
				this.editor.format(attribute, prevFormat[attribute]);
			}
		}
	},

	/**
	 * Event handler triggered when user clicks on the editor
	 * It will check whether the target of the click event is an Image and then set the selection according to the index and length of the image
	 * It also disables the toolbar when a user clicks on a disabled editor.
	 *
	 * @param {Event} e The event object passed on function call
	 */
	onEditorClick : function(e)
	{
		var quill = this.getQuillClass();
		var editor = this.editor;
		var parchment = quill.import('parchment');
		var imageBlot = quill.import('formats/image');

		// If the target is an image then select the image
		var img = parchment.Registry.find(e.target);

		if (img instanceof imageBlot) {
			editor.setSelection(img.offset(editor.scroll), 1, 'user');
		}

		// If the editor is disabled then disable the toolbar
		if (!editor.isEnabled()) {
			this.disableToolbar();
		}
	},

	/**
	 * Handler triggered whe select all text using Ctrl + A
	 * keyboard shortcut.
	 */
	selectAllText: function() {
		var quill = this.quill;
		quill.setSelection(0, quill.getLength());
		return true;
	},
	
	/**
	 * Function returns the currently active window object.
	 * @return {Object} object Current active Window object
	 */
	getActiveWindow : function()
	{
		return Zarafa.core.BrowserWindowMgr.getActive();
	},
	
	/**
	 * Function returns the Quill class of the currently active window.
	 * @return {Class} class Quill class of the active window
	 */
	getQuillClass : function()
	{
		return this.activeWindow.Quill;
	},

	/**
	 * Function which do some customization in Quill editor. Added webapp specific
	 * font type and size in Quill blots block(P tag) element.
	 *
	 * @params {Object} formatting The all supported formatting information like
	 * different font type and size.
	 */
	customizeQuill : function(formatting)
	{
		var quill = this.getQuillClass();
		// Added custom font size
		var Size = quill.import('attributors/style/size');
		Size.whitelist = formatting.fontSize;
		quill.register(Size, true);

		// Remove default font families 
		var Font = quill.import('attributors/style/font');
		// We need to remove the default values because the whitelist accepts 
		// only single values and not fallback fonts.
		Font.whitelist = undefined;
		quill.register(Font, true);

		// customize the p tag.
		var block = quill.import('blots/block');
		var defaultFormatting = this.getDefaultFontFormatting();
		block.fontSize = defaultFormatting.fontSize;
		block.fontType = defaultFormatting.fontFamily;

		//Created margin and padding StyleAttributor for Block (p tag).
		var parchment = quill.import('parchment');
		var margin = new parchment.StyleAttributor('margin', 'margin', {
			scope: parchment.Scope.BLOCK
		});
		quill.register(margin, true);
		var borderLeft = new parchment.StyleAttributor('border-left', 'border-left', {
			scope: parchment.Scope.BLOCK
		});
		quill.register(borderLeft, true);
		var paddingLeft = new parchment.StyleAttributor('padding-left', 'padding-left', {
			scope: parchment.Scope.BLOCK
		});
		quill.register(paddingLeft, true);
		
		quill.import("formats/link").sanitize = this.sanitizeURL;
		quill.import('themes/snow').prototype.buildButtons = this.buildToolbarButtons
	},
	
	/**
	 * Function will add a protocol to the given URL, if none is provided.
	 * @param {String} url The URL string in the link.
	 * @return {String} The URL with the appended protocol, else the default URL.
	 */
	sanitizeURL : function(url) {
		// Remove all whitespaces from the url
		url = url.trim();
		
		// Check if protocol is already provided or not
		var protocol = url.slice(0, url.indexOf(':'));
		
		// If no protocol is provided, append protocol to the URL
		if (this.PROTOCOL_WHITELIST.indexOf(protocol) === -1) {
			url = 'https://' + url;
		}
		// Quill's original 'sanitize' method's code snippet
		// which creates the anchor element of the link
		var anchor = document.createElement('a');
		anchor.href = url;
		protocol = anchor.href.slice(0, anchor.href.indexOf(':'));
		return (this.PROTOCOL_WHITELIST.indexOf(protocol) > -1) ? url : this.SANITIZED_URL;
	},

	/**
	 * Function will show the quill toolbar buttons.
	 * we set the tabIndex to -1 by default so tab key not set the
	 * focus on the button.
	 *
	 * @param {HTMLButtonElement} buttons The buttons contains the buttons of the quill toolbar
	 * @param {SVGElement} icons The icons contains the icons for the button.
	 */
	buildToolbarButtons: function(buttons, icons) {
		Array.from(buttons).forEach(function(button) {
			// Set tabIndex to -1 to stop focusing on button.
			button.tabIndex = -1;
			var className = button.getAttribute('class') || '';
			className.split(/\s+/).forEach(function(name) {
				if (!name.startsWith('ql-')) return;
				name = name.slice('ql-'.length);
				if (icons[name] == null) return;

				if (name === 'direction') {
					button.innerHTML = icons[name][''] + icons[name].rtl;
				} else if (typeof icons[name] === 'string') {
					button.innerHTML = icons[name];
				} else {
					var value = button.value || '';

					if (value != null && icons[name][value]) {
						button.innerHTML = icons[name][value];
					}
				}
			});
			var classname = button.className.replace('ql-', '');
			var tooltip = classname.charAt(0).toUpperCase() + classname.slice(1);
			var list = button.value;
			button.setAttribute("data-title", tooltip);
			if (list) {
				tooltip = list.charAt(0).toUpperCase() + list.slice(1);
				button.setAttribute("data-title", tooltip + " List");
			}
		});
	},

	/**
	 * Function used to get the default formatting used by the user.
	 *
	 * @return {Object} Object The object which contains the default font size and type.
	 */
	getDefaultFontFormatting: function() {
		var fontType = container.getSettingsModel().get('zarafa/v1/main/default_font').split(',')[0];
		switch (fontType) {
			case 'courier new':
				fontType = 'courier new,courier';
				break;
			case 'times new roman':
				fontType = 'times new roman,times';
				break;
			case 'tahoma':
				fontType = 'tahoma,arial,helvetica,sans-serif';
				break;
			case 'verdana':
				fontType = 'verdana,geneva';
				break;
			default:
				fontType = 'arial,helvetica,sans-serif';
		}

		return {
			fontSize: Zarafa.common.ui.htmleditor.Fonts.getDefaultFontSize(),
			fontFamily: fontType
		};
	},

	/**
	 * Function used to list down the currently supported font families by the webapp.
	 *
	 * @return {Array} returns the font families which are supported.
	 */
	getFontFamilies: function() {
		var fontTypes = Zarafa.common.ui.htmleditor.Fonts.getFontFamilies();
		var fontFamilies = fontTypes.split(';').map(function(font) {
			return {
				name: font.split('=')[0],
				value: font.split('=')[1].toLowerCase()
			};
		});
		return fontFamilies;
	},

	/**
	 * Set the default formatting while composing a new mail.
	 * @private
	 */
	setDefaultFormatting: function() {
		var defaultFormatting = this.getDefaultFontFormatting();
		this.editor.format('size', defaultFormatting.fontSize, 'user');
		this.editor.format('font', defaultFormatting.fontFamily, 'user');

		// As the 'code-block' button has been removed, any text wrapped in <pre> tag
		// should be displayed without the code-block. Hence, we set the 'code-block' attribute
		// to false, for the entire contents of the editor.
		this.editor.formatLine(0, this.editor.getLength(), { 'code-block': false });

		// HTML styles will be applied while selecting default values from comboboxes.
		// We need to set those styles into the record to avoid change detection in record.
		this.checkValueCorrection(this, '');
	},

	/**
	 * Function sets the 'indent' attribute in the given delta for new lines because the input delta
	 * has 'indent' attribute set in all the other ops except the new lines.
	 *
	 * @param {Delta Object} oldDelta The delta in which the 'indent' attribute is to be set
	 *
	 * @return {Delta Object} The new Delta
	 */
	indentNewLines: function(oldDelta) {
		var prevIndent;
		var quill = this.getQuillClass();
		var Delta = quill.import('delta');
		var newDelta = new Delta();
		newDelta.ops = oldDelta.map((op) => {
			// Store the indent value of the op which is before a new line
			if (op.insert !== '\n' && op.attributes) {
				prevIndent = op.attributes.indent;
			}
			// Set the indent value of a new line according to the previous indent value
			if (op.insert === '\n' && op.attributes) {
				op.attributes['indent'] = prevIndent;
			}
			return op;
		});
		return newDelta;
	},
	
	/**
	 * The function disables the indentation if the user presses tab after an empty list item.
	 * @param {Object} range The selected range in the editor
	 * @param {Object} context Additional specifications for handler
	 * @return {boolean} Returns false to stop calling additional handlers
	 */
	onEmptyListTab : function(range, context)
	{
		var currentListItem = context.line;
		var previousListItem = context.line.prev;
		var currentIndent = currentListItem.formats().indent;
		if (context.collapsed && context.offset !== 0) return true;
		if (previousListItem) {
			var previousIndent = previousListItem.formats().indent;
			if (previousIndent >= currentIndent || !currentIndent) {
				this.editor.format('indent', '+1', 'user');
			}
		}
        return false;
	},
	
	/**
	 * Function will set value in the editor, if a list is passed in the value then the list
	 * will be parsed according to the Quill format with necessary indentation and classes,
	 * and will set the value accordingly,
	 * and will also call {@link #checkValueCorrection}
	 * to update editor's editor value into record. If the value passed is an empty string,
	 * the function will set an empty string in the editor.
	 * for more information check code/comments
	 * of {@link #checkValueCorrection}.
	 * @param {String} value The value which was being set on the editor
	 */
	setValue: function(value) {
		var editor = this.editor;
		var newDelta;
		if (!Ext.isEmpty(value)) {
			var delta = editor.clipboard.convert({ html: value });
			newDelta = this.indentNewLines(delta);
			newDelta = this.removeCodeBlock(newDelta);
			editor.setContents(newDelta);
			this.setDefaultFormatting();
			this.checkValueCorrection(this, value);
		} else {
			editor.setContents([ { insert: '' } ]);
		}
	},
	
	/**
	 * Function will remove code-block, if applied, from the content.
	 * Quill converts all content wrapped in <pre> into <div> elements.
	 * Since, we removed the 'code-block' button from toolbar,
	 * we need to remove all code-blocks in the editor.
	 * @return {Array} newDelta The delta with the removed code-block attribute.
	 */
	removeCodeBlock : function(oldDelta)
	{
		var quill = this.getQuillClass();
		var Delta = quill.import('delta');
		var newDelta = new Delta();
		// Initially, 'code-block' was being removed using the 'formatLine' method of Quill,
		// but the scroll-optimize event and the optimize() method of Quill
		// can be called for a limited number of iterations only.
		// This produced error if the length of the content in the editor was too long.
		// Hence, we now remove the code-block in the delta format itself.
		newDelta.ops = oldDelta.map((op) => {
			var attribute = op.attributes;
			if (attribute && attribute['code-block']) {
				// Remove the attribute 'code-block' from the delta
				attribute['code-block'] = false;
			}
			return op;
		});
		return newDelta;
	},
	
	/**
     * Function will set the selection to the current selected index (initially to 0) on 'selection-change',
     * so that when the editor gets in focus, the cursor is already set on index 0.
     * Also, the function formats the selected line with the required margin.
     * @param {String} eventName The name of the event fired, 'selection-change' or 'text-change'
     * @param {Array} delta The delta express format that can be used to describe Quill’s contents and changes
     * @param {Array} oldDelta The delta express old content of quill.
     * @param {String} source The source represent the change done by the either 'user', 'api' or 'silent'
     */
	onEditorFocus: function(eventName, delta, oldDelta, source) {
		var editor = this.editor;
		if (eventName === 'selection-change') {
			if (source === 'user') {
				// Get the currently selected range
				var selectedRange = editor.getSelection();
				// If the editor is in focus, set the selection according to the current index (initially to 0)
				if (editor.hasFocus()) {
					editor.setSelection(selectedRange.index, selectedRange.length);
				}
			}
		}
	},

	/**
	 * Function will disable the toolbar
	 */
	disableToolbar: function() {
		var editor = this.editor;
		var toolbarModule = editor.getModule('toolbar');
		var toolbarContainer = toolbarModule.container;
		toolbarContainer.classList.add('disable-toolbar');
	},

	/**
	 * Function will disable the editor and remove focus from toolbar buttons
	 */
	disable: function() {
		if (!this.editor) {
			this.createEditorInstance();
		}
		this.editor.disable();
		this.disableToolbar();
	},

	/**
	 * Function will enable the editor
	 */
	enable: function() {
		this.editor.enable();
		this.editor.getModule('toolbar').container.classList.remove('disable-toolbar');
	},

	/**
	 * Sets the focus on the editor.
	 */
	focus: function() {
		this.editor.focus();
	},

	/**
	 * Returns the normalized data value (undefined or emptyText will be returned as '').
	 * To return the raw value see {@link #getRawValue}.
	 * @return {Mixed} value The field value
	 */
	getValue: function() {
		return this.editor.getSemanticHTMLContent();
	},

	/**
	 * This is called after {@link #setValue} and determines if the value has been adjusted by the quill editor.
	 * Normally the value is applied to the quill editor container element where it is being rendered, quill editor will
	 * however also apply extra formatting, and some attributes. Because we only want to detect when the user made changes
	 * rather then the browser, we will check here what {@link #getValue} returns and check if that is different then
	 * the value which was being set. If a difference was found, the {@link #valuecorrection} event will be fired to inform the
	 * component user that he might need to implement some workaround.
	 *
	 * @param {Zarafa.common.ui.htmleditor.HtmlEditor} editor The HTML editor
	 * @param {String} value The value which was being set on the quill editor.
	 * @private
	 */
	checkValueCorrection: function(editor, value) {
		var correctedValue = editor.getValue();

		if (value !== correctedValue) {
			editor.fireEvent('valuecorrection', editor, correctedValue, value);
		}
	},

	/**
	 * Function inserts HTML text into the editor field where cursor is positioned.
	 * @param {String} value The text which must be inserted at the cursor position
	 */
	insertAtCursor : function(value)
	{
		var quill = this.getQuillClass();
		var editor = this.editor;
		editor.focus();
		var Delta = quill.import('delta');
		var newDelta = new Delta();
		var delta = newDelta
			.retain(editor.getSelection().index)
			.insert('\n')
			.concat(editor.clipboard.convert({ html: value }));
		editor.updateContents(delta);
	},

	/**
	 * Event handler triggered when blockquote is empty
	 * and enter or backspace key was pressed.
	 */
	removeEmptyBlockquote: function() {
		this.editor.format('blockquote', false, 'user');
		this.editor.format('border-left', '', 'user');
	},

	/**
	 * Function sets the cursor position to the start of the text
	 */
	setCursorLocation: Ext.emptyFn
});

Ext.reg('plugins.quilleditor', Zarafa.plugins.quill.ui.QuillEditor);
