Source: resources/js/stylieeditor.js

/*
 * stylie.treeview
 * https://github.com/typesettin/stylie.treeview
 *
 * Copyright (c) 2015 Yaw Joseph Etse. All rights reserved.
 */
'use strict';

var extend = require('util-extend'),
	CodeMirror = require('codemirror'),
	StylieModals = require('stylie.modals'),
	editorModals,
	events = require('events'),
	classie = require('classie'),
	util = require('util');

require('../../node_modules/codemirror/addon/edit/matchbrackets');
require('../../node_modules/codemirror/addon/hint/css-hint');
require('../../node_modules/codemirror/addon/hint/html-hint');
require('../../node_modules/codemirror/addon/hint/javascript-hint');
require('../../node_modules/codemirror/addon/hint/show-hint');
require('../../node_modules/codemirror/addon/lint/css-lint');
require('../../node_modules/codemirror/addon/lint/javascript-lint');
// require('../../node_modules/codemirror/addon/lint/json-lint');
require('../../node_modules/codemirror/addon/lint/lint');
// require('../../node_modules/codemirror/addon/lint/html-lint');
require('../../node_modules/codemirror/addon/comment/comment');
require('../../node_modules/codemirror/addon/comment/continuecomment');
require('../../node_modules/codemirror/addon/fold/foldcode');
require('../../node_modules/codemirror/addon/fold/comment-fold');
require('../../node_modules/codemirror/addon/fold/indent-fold');
require('../../node_modules/codemirror/addon/fold/brace-fold');
require('../../node_modules/codemirror/addon/fold/foldgutter');
require('../../node_modules/codemirror/mode/css/css');
require('../../node_modules/codemirror/mode/htmlembedded/htmlembedded');
require('../../node_modules/codemirror/mode/htmlmixed/htmlmixed');
require('../../node_modules/codemirror/mode/javascript/javascript');

var saveSelection = function () {
	if (window.getSelection) {
		var sel = window.getSelection();
		if (sel.getRangeAt && sel.rangeCount) {
			return sel.getRangeAt(0);
		}
	}
	else if (document.selection && document.selection.createRange) {
		return document.selection.createRange();
	}
	return null;
};

var restoreSelection = function (range) {
	if (range) {
		if (window.getSelection) {
			var sel = window.getSelection();
			sel.removeAllRanges();
			sel.addRange(range);
		}
		else if (document.selection && range.select) {
			range.select();
		}
	}
};

var getInsertTextModal = function (orignialid, mtype) {
	var returnDiv = document.createElement('div'),
		modaltype = (mtype === 'image') ? 'image' : 'text',
		// execcmd = (mtype === 'image') ? 'insertImage' : 'createLink',
		samplelink = (mtype === 'image') ? 'https://developers.google.com/+/images/branding/g+138.png' : 'http://example.com',
		linktype = (mtype === 'image') ? 'image' : 'link';
	returnDiv.setAttribute('id', orignialid + '-insert' + modaltype + '-modal');
	returnDiv.setAttribute('data-name', orignialid + '-insert' + modaltype + '-modal');
	returnDiv.setAttribute('class', 'ts-modal ts-modal-effect-1 insert' + modaltype + '-modal');
	var divInnerHTML = '<section class="ts-modal-content ts-bg-text-primary-color  ts-no-heading-margin ts-padding-lg ts-shadow ">';
	divInnerHTML += '<div class="ts-form">';
	divInnerHTML += '<div class="ts-row">';
	divInnerHTML += '<div class="ts-col-span12">';
	divInnerHTML += '<h6>Insert a link</h6>';
	divInnerHTML += '</div>';
	divInnerHTML += '</div>';
	// divInnerHTML += '<div class="ts-row">';
	// divInnerHTML += '<div class="ts-col-span4">';
	// divInnerHTML += '<label class="ts-label">text</label>';
	// divInnerHTML += '</div>';
	// divInnerHTML += '<div class="ts-col-span8">';
	// divInnerHTML += '<input type="text" name="link_url" placeholder="some web link" value="some web link"/>';
	// divInnerHTML += '</div>';
	// divInnerHTML += '</div>';
	divInnerHTML += '<div class="ts-row">';
	divInnerHTML += '<div class="ts-col-span4">';
	divInnerHTML += '<label class="ts-label">url</label>';
	divInnerHTML += '</div>';
	divInnerHTML += '<div class="ts-col-span8">';
	divInnerHTML += '<input type="text" class="ts-input ts-' + linktype + '_url" name="' + linktype + '_url" placeholder="' + samplelink + '" value="' + samplelink + '"/>';
	divInnerHTML += '</div>';
	divInnerHTML += '</div>';
	divInnerHTML += '<div class="ts-row ts-text-center">';
	divInnerHTML += '<div class="ts-col-span6">';
	// divInnerHTML += '<button type="button" class="ts-button ts-modal-close ts-button-primary-color" onclick="document.execCommand(\'insertImage\', false, \'http://lorempixel.com/40/20/sports/\');">insert link</button>';
	divInnerHTML += '<button type="button" class="ts-button ts-modal-close ts-button-primary-color add-' + linktype + '-button" >insert ' + linktype + '</button>';
	divInnerHTML += '</div>';
	divInnerHTML += '<div class="ts-col-span6">';
	divInnerHTML += '<a class="ts-button ts-modal-close">close</a>';
	divInnerHTML += '</div>';
	divInnerHTML += '</div>';
	divInnerHTML += '</div>';
	divInnerHTML += '</section>';
	returnDiv.innerHTML = divInnerHTML;

	return returnDiv;
};

/**
 * A module that represents a StylieTextEditor object, a componentTab is a page composition tool.
 * @{@link https://github.com/typesettin/stylie.treeview}
 * @author Yaw Joseph Etse
 * @copyright Copyright (c) 2015 Typesettin. All rights reserved.
 * @license MIT
 * @constructor StylieTextEditor
 * @requires module:util-extent
 * @requires module:util
 * @requires module:events
 * @param {object} el element of tab container
 * @param {object} options configuration options
 */
var StylieTextEditor = function (options) {
	events.EventEmitter.call(this);
	var defaultOptions = {
		type: 'html',
		updateOnChange: true
	};

	this.options = extend(defaultOptions, options);
	return this;
	// this.getTreeHTML = this.getTreeHTML;
};

util.inherits(StylieTextEditor, events.EventEmitter);

var createButton = function (options) {
	var buttonElement = document.createElement('button');
	buttonElement.setAttribute('class', 'ts-button ts-text-xs ' + options.classes);
	buttonElement.setAttribute('type', 'button');
	buttonElement.innerHTML = options.innerHTML;
	for (var key in options) {
		if (key !== 'classes' && key !== 'innerHTML' && key !== 'innerhtml') {
			buttonElement.setAttribute(key, options[key]);
		}
	}

	return buttonElement;
};

StylieTextEditor.prototype.addMenuButtons = function () {
	this.options.buttons.boldButton = createButton({
		classes: ' flaticon-bold17 ',
		title: 'Bold text',
		innerHTML: ' ',
		'data-attribute-action': 'bold'
	});
	this.options.buttons.italicButton = createButton({
		classes: ' flaticon-italic9 ',
		title: 'Italic text',
		innerHTML: ' ',
		'data-attribute-action': 'italic'
	});
	this.options.buttons.underlineButton = createButton({
		classes: ' flaticon-underlined5 ',
		title: 'Underline text',
		innerHTML: ' ',
		'data-attribute-action': 'underline'
	});
	this.options.buttons.unorderedLIButton = createButton({
		classes: ' flaticon-list82 ',
		innerHTML: ' ',
		title: ' Insert unordered list ',
		'data-attribute-action': 'unorderedLI'
	});
	this.options.buttons.orderedLIButton = createButton({
		classes: ' flaticon-numbered8 ',
		title: ' Insert ordered list ',
		innerHTML: ' ',
		'data-attribute-action': 'orderedLI'
	});
	this.options.buttons.lefttextalignButton = createButton({
		classes: ' flaticon-text141 ',
		innerHTML: ' ',
		title: ' left align text ',
		'data-attribute-action': 'left-textalign'
	}); //flaticon-text141
	this.options.buttons.centertextalignButton = createButton({
		classes: ' flaticon-text136 ',
		innerHTML: ' ',
		title: ' center align text ',
		'data-attribute-action': 'center-textalign'
	}); //flaticon-text136
	this.options.buttons.righttextalignButton = createButton({
		classes: ' flaticon-text134 ',
		innerHTML: ' ',
		title: ' right align text ',
		'data-attribute-action': 'right-textalign'
	}); //flaticon-text134
	this.options.buttons.justifytextalignButton = createButton({
		classes: ' flaticon-text146 ',
		innerHTML: ' ',
		title: ' justify align text ',
		'data-attribute-action': 'justify-textalign'
	}); //flaticon-text146
	//flaticon-characters - font
	this.options.buttons.textcolorButton = createButton({
		classes: ' flaticon-text137 ',
		innerHTML: ' ',
		title: ' change text color ',
		'data-attribute-action': 'text-color'
	}); //flaticon-text137 - text color
	this.options.buttons.texthighlightButton = createButton({
		classes: ' flaticon-paintbrush13 ',
		innerHTML: ' ',
		title: ' change text highlight ',
		'data-attribute-action': 'text-highlight'
	}); //flaticon-paintbrush13 - text background color(highlight)
	this.options.buttons.linkButton = createButton({
		classes: ' flaticon-link56 ',
		title: ' Insert a link ',
		innerHTML: ' ',
		'data-attribute-action': 'link'
	});
	this.options.buttons.imageButton = createButton({
		classes: ' flaticon-images25 ',
		title: ' Insert image ',
		innerHTML: ' ',
		'data-attribute-action': 'image'
	});
	this.options.buttons.codeButton = createButton({
		innerHTML: ' ',
		classes: ' flaticon-code39 ',
		title: 'Source code editor',
		'data-attribute-action': 'code'
	});
	this.options.buttons.fullscreenButton = createButton({
		title: 'Maximize and fullscreen editor',
		classes: ' flaticon-logout18 ',
		innerHTML: ' ',
		'data-attribute-action': 'fullscreen'
	});
	this.options.buttons.outdentButton = createButton({
		title: 'Outdent button',
		classes: ' flaticon-paragraph19 ',
		innerHTML: ' ',
		'data-attribute-action': 'outdent'
	});
	this.options.buttons.indentButton = createButton({
		title: 'Indent button',
		classes: ' flaticon-right195 ',
		innerHTML: ' ',
		'data-attribute-action': 'indent'
	});
};

var button_gobold = function () {
	document.execCommand('bold', false, '');
};
var button_gounderline = function () {
	document.execCommand('underline', false, '');
};
var button_goitalic = function () {
	document.execCommand('italic', false, '');
};
var button_golink = function () {
	document.execCommand('createLink', true, '');
};
var button_golist = function () {
	document.execCommand('insertOrderedList', true, '');
};
var button_gobullet = function () {
	document.execCommand('insertUnorderedList', true, '');
};
var button_goimg = function () {
	// document.execCommand('insertImage', false, 'http://lorempixel.com/40/20/sports/');
	this.saveSelection();
	window.editorModals.show(this.options.elementContainer.getAttribute('data-original-id') + '-insertimage-modal');
};
var button_gotextlink = function () {
	// console.log(this.options.elementContainer.getAttribute('data-original-id'));
	this.saveSelection();
	window.editorModals.show(this.options.elementContainer.getAttribute('data-original-id') + '-inserttext-modal');
};
var add_link_to_editor = function () {
	this.restoreSelection();
	document.execCommand('createLink', false, this.options.forms.add_link_form.querySelector('.ts-link_url').value);
};
var add_image_to_editor = function () {
	this.restoreSelection();
	document.execCommand('insertImage', false, this.options.forms.add_image_form.querySelector('.ts-image_url').value);
};
var button_gofullscreen = function () {
	// console.log('button_gofullscreen this', this);
	// if()
	classie.toggle(this.options.elementContainer, 'ts-editor-fullscreen');
	classie.toggle(this.options.buttons.fullscreenButton, 'ts-button-primary-text-color');
};
var button_togglecodeeditor = function () {
	classie.toggle(this.options.codemirror.getWrapperElement(), 'ts-hidden');
	classie.toggle(this.options.buttons.codeButton, 'ts-button-primary-text-color');
	this.options.codemirror.refresh();
};
var button_gotext_left = function () {
	document.execCommand('justifyLeft', true, '');
};
var button_gotext_center = function () {
	document.execCommand('justifyCenter', true, '');
};
var button_gotext_right = function () {
	document.execCommand('justifyRight', true, '');
};
var button_gotext_justifyfull = function () {
	document.execCommand('justifyFull', true, '');
};
// var button_gotext_left = function () {
// 	document.execCommand('justifyLeft', true, '');
// };
var button_go_outdent = function () {
	document.execCommand('outdent', true, '');
};
var button_go_indent = function () {
	document.execCommand('indent', true, '');
};

StylieTextEditor.prototype.initButtonEvents = function () {
	this.options.buttons.boldButton.addEventListener('click', button_gobold, false);
	this.options.buttons.underlineButton.addEventListener('click', button_gounderline, false);
	this.options.buttons.italicButton.addEventListener('click', button_goitalic, false);
	this.options.buttons.linkButton.addEventListener('click', button_golink, false);
	this.options.buttons.unorderedLIButton.addEventListener('click', button_gobullet, false);
	this.options.buttons.orderedLIButton.addEventListener('click', button_golist, false);
	this.options.buttons.imageButton.addEventListener('click', button_goimg.bind(this), false);
	this.options.buttons.linkButton.addEventListener('click', button_gotextlink.bind(this), false);

	this.options.buttons.lefttextalignButton.addEventListener('click', button_gotext_left, false);
	this.options.buttons.centertextalignButton.addEventListener('click', button_gotext_center, false);
	this.options.buttons.righttextalignButton.addEventListener('click', button_gotext_right, false);
	this.options.buttons.justifytextalignButton.addEventListener('click', button_gotext_justifyfull, false);

	this.options.buttons.outdentButton.addEventListener('click', button_go_outdent, false);
	this.options.buttons.indentButton.addEventListener('click', button_go_indent, false);

	this.options.buttons.fullscreenButton.addEventListener('click', button_gofullscreen.bind(this), false);
	this.options.buttons.codeButton.addEventListener('click', button_togglecodeeditor.bind(this), false);

	this.options.buttons.addlinkbutton.addEventListener('click', add_link_to_editor.bind(this), false);
	this.options.buttons.addimagebutton.addEventListener('click', add_image_to_editor.bind(this), false);
};

StylieTextEditor.prototype.init = function () {
	try {
		var previewEditibleDiv = document.createElement('div'),
			previewEditibleMenu = document.createElement('div'),
			insertImageURLModal = getInsertTextModal(this.options.element.getAttribute('id'), 'image'),
			insertTextLinkModal = getInsertTextModal(this.options.element.getAttribute('id'), 'text'),
			previewEditibleContainer = document.createElement('div');
		this.options.buttons = {};
		this.addMenuButtons();
		previewEditibleMenu.appendChild(this.options.buttons.boldButton);
		previewEditibleMenu.appendChild(this.options.buttons.italicButton);
		previewEditibleMenu.appendChild(this.options.buttons.underlineButton);
		previewEditibleMenu.appendChild(this.options.buttons.unorderedLIButton);
		previewEditibleMenu.appendChild(this.options.buttons.orderedLIButton);
		previewEditibleMenu.appendChild(this.options.buttons.lefttextalignButton);
		previewEditibleMenu.appendChild(this.options.buttons.centertextalignButton);
		previewEditibleMenu.appendChild(this.options.buttons.righttextalignButton);
		previewEditibleMenu.appendChild(this.options.buttons.justifytextalignButton);
		// previewEditibleMenu.appendChild(this.options.buttons.textcolorButton);
		// previewEditibleMenu.appendChild(this.options.buttons.texthighlightButton);
		previewEditibleMenu.appendChild(this.options.buttons.outdentButton);
		previewEditibleMenu.appendChild(this.options.buttons.indentButton);
		previewEditibleMenu.appendChild(this.options.buttons.linkButton);
		previewEditibleMenu.appendChild(this.options.buttons.imageButton);
		previewEditibleMenu.appendChild(this.options.buttons.codeButton);
		previewEditibleMenu.appendChild(this.options.buttons.fullscreenButton);
		previewEditibleMenu.setAttribute('class', 'ts-input ts-editor-menu ts-padding-sm');
		previewEditibleMenu.setAttribute('style', 'font-family: monospace, Arial,"Times New Roman";');
		previewEditibleDiv.setAttribute('class', 'ts-input ts-texteditor');
		previewEditibleDiv.setAttribute('contenteditable', 'true');
		previewEditibleDiv.setAttribute('tabindex', '1');
		previewEditibleContainer.setAttribute('id', this.options.element.getAttribute('id') + '_container');
		previewEditibleContainer.setAttribute('data-original-id', this.options.element.getAttribute('id'));
		previewEditibleContainer.setAttribute('class', 'ts-editor-container');
		previewEditibleContainer.appendChild(previewEditibleMenu);
		previewEditibleContainer.appendChild(previewEditibleDiv);
		document.querySelector('.ts-modal-hidden-container').appendChild(insertTextLinkModal);
		document.querySelector('.ts-modal-hidden-container').appendChild(insertImageURLModal);
		this.options.element = this.options.element || document.querySelector(this.options.elementSelector);
		previewEditibleDiv.innerHTML = this.options.element.innerText;
		this.options.previewElement = previewEditibleDiv;
		this.options.forms = {
			add_link_form: document.querySelector('.inserttext-modal .ts-form'),
			add_image_form: document.querySelector('.insertimage-modal .ts-form')
		};
		this.options.buttons.addlinkbutton = document.querySelector('.inserttext-modal').querySelector('.add-link-button');
		this.options.buttons.addimagebutton = document.querySelector('.insertimage-modal').querySelector('.add-image-button');
		//now add code mirror

		this.options.elementContainer = previewEditibleContainer;
		this.options.element.parentNode.appendChild(previewEditibleContainer);
		previewEditibleContainer.appendChild(this.options.element);
		this.options.codemirror = CodeMirror.fromTextArea(
			this.options.element, {
				lineNumbers: true,
				lineWrapping: true,
				matchBrackets: true,
				autoCloseBrackets: true,
				mode: (this.options.type === 'ejs') ? 'text/ejs' : 'text/html',
				indentUnit: 2,
				indentWithTabs: true,
				'overflow-y': 'hidden',
				'overflow-x': 'auto',
				lint: true,
				gutters: [
					'CodeMirror-linenumbers',
					'CodeMirror-foldgutter',
					// 'CodeMirror-lint-markers'
				],
				foldGutter: true
			}
		);
		// this.options.element.parentNode.insertBefore(previewEditibleDiv, this.options.element);
		this.options.codemirror.on('blur', function (instance) {
			// console.log('editor lost focuss', instance, change);
			this.options.previewElement.innerHTML = instance.getValue();
		}.bind(this));
		this.options.previewElement.addEventListener('blur', function () {
			this.options.codemirror.getDoc().setValue(this.options.previewElement.innerHTML);
		}.bind(this));

		if (this.options.updateOnChange) {
			this.options.codemirror.on('change', function (instance) {
				// console.log('editor lost focuss', instance, change);
				// console.log('document.activeElement === this.options.previewElement', document.activeElement === this.options.previewElement);
				setTimeout(function () {
					if (document.activeElement !== this.options.previewElement) {
						this.options.previewElement.innerHTML = instance.getValue();
					}
				}.bind(this), 5000);
			}.bind(this));
			this.options.previewElement.addEventListener('change', function () {
				this.options.codemirror.getDoc().setValue(this.options.previewElement.innerHTML);
			}.bind(this));
		}
		//set initial code mirror
		this.options.codemirror.getDoc().setValue(this.options.previewElement.innerHTML);
		this.options.codemirror.refresh();
		classie.add(this.options.codemirror.getWrapperElement(), 'ts-hidden');
		// setTimeout(this.options.codemirror.refresh, 1000);


		this.initButtonEvents();
		editorModals = new StylieModals({});
		window.editorModals = editorModals;
		return this;
	}
	catch (e) {
		console.error(e);
	}
};

StylieTextEditor.prototype.getValue = function () {
	return this.options.previewElement.innerText || this.options.codemirror.getValue();
};

StylieTextEditor.prototype.saveSelection = function () {
	this.options.selection = (saveSelection()) ? saveSelection() : null;
};

StylieTextEditor.prototype.restoreSelection = function () {
	this.options.preview_selection = this.options.selection;
	restoreSelection(this.options.selection);
};

module.exports = StylieTextEditor;