/* ===================================================
* bootstrap-markdown.js v2.10.0
* http://github.com/toopay/bootstrap-markdown
* ===================================================
* Copyright 2013-2016 Taufan Aditya
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
(function(factory) {
if (typeof define === "function" && define.amd) {
// RequireJS
define(["jquery"], factory);
} else if (typeof exports === 'object') {
// Backbone.js
factory(require('jquery'));
} else {
// jQuery plugin
factory(jQuery);
}
}(function($) {
"use strict";
/* MARKDOWN CLASS DEFINITION
* ========================== */
var Markdown = function(element, options) {
// @TODO : remove this BC on next major release
// @see : https://github.com/toopay/bootstrap-markdown/issues/109
var opts = ['autofocus', 'savable', 'hideable', 'width',
'height', 'resize', 'iconlibrary', 'language',
'footer', 'fullscreen', 'hiddenButtons', 'disabledButtons'
];
$.each(opts, function(_, opt) {
if (typeof $(element).data(opt) !== 'undefined') {
options = typeof options == 'object' ? options : {};
options[opt] = $(element).data(opt);
}
});
// End BC
// Class Properties
this.$ns = 'bootstrap-markdown';
this.$element = $(element);
this.$editable = {
el: null,
type: null,
attrKeys: [],
attrValues: [],
content: null
};
this.$options = $.extend(true, {}, $.fn.markdown.defaults, options, this.$element.data('options'));
this.$oldContent = null;
this.$isPreview = false;
this.$isFullscreen = false;
this.$editor = null;
this.$textarea = null;
this.$handler = [];
this.$callback = [];
this.$nextTab = [];
this.showEditor();
};
Markdown.prototype = {
constructor: Markdown,
__alterButtons: function(name, alter) {
var handler = this.$handler,
isAll = (name == 'all'),
that = this;
$.each(handler, function(k, v) {
var halt = true;
if (isAll) {
halt = false;
} else {
halt = v.indexOf(name) < 0;
}
if (halt === false) {
alter(that.$editor.find('button[data-handler="' + v + '"]'));
}
});
},
__buildButtons: function(buttonsArray, container) {
var i,
ns = this.$ns,
handler = this.$handler,
callback = this.$callback;
for (i = 0; i < buttonsArray.length; i++) {
// Build each group container
var y, btnGroups = buttonsArray[i];
for (y = 0; y < btnGroups.length; y++) {
// Build each button group
var z,
buttons = btnGroups[y].data,
btnGroupContainer = $('
', {
'class': 'btn-group'
});
for (z = 0; z < buttons.length; z++) {
var button = buttons[z],
buttonContainer, buttonIconContainer,
buttonHandler = ns + '-' + button.name,
buttonIcon = this.__getIcon(button),
btnText = button.btnText ? button.btnText : '',
btnClass = button.btnClass ? button.btnClass : 'btn',
tabIndex = button.tabIndex ? button.tabIndex : '-1',
hotkey = typeof button.hotkey !== 'undefined' ? button.hotkey : '',
hotkeyCaption = typeof jQuery.hotkeys !== 'undefined' && hotkey !== '' ? ' (' + hotkey + ')' : '';
// Construct the button object
buttonContainer = $('');
buttonContainer.text(' ' + this.__localize(btnText)).addClass('btn-default btn-sm').addClass(btnClass);
if (btnClass.match(/btn\-(primary|success|info|warning|danger|link)/)) {
buttonContainer.removeClass('btn-default');
}
buttonContainer.attr({
'type': 'button',
'title': this.__localize(button.title) + hotkeyCaption,
'tabindex': tabIndex,
'data-provider': ns,
'data-handler': buttonHandler,
'data-hotkey': hotkey
});
if (button.toggle === true) {
buttonContainer.attr('data-toggle', 'button');
}
buttonIconContainer = $('');
buttonIconContainer.addClass(buttonIcon);
buttonIconContainer.prependTo(buttonContainer);
// Attach the button object
btnGroupContainer.append(buttonContainer);
// Register handler and callback
handler.push(buttonHandler);
callback.push(button.callback);
}
// Attach the button group into container DOM
container.append(btnGroupContainer);
}
}
return container;
},
__setListener: function() {
// Set size and resizable Properties
var hasRows = typeof this.$textarea.attr('rows') !== 'undefined',
maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5',
rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows;
this.$textarea.attr('rows', rowsVal);
if (this.$options.resize) {
this.$textarea.css('resize', this.$options.resize);
}
// Re-attach markdown data
this.$textarea.data('markdown', this);
},
__setEventListeners: function() {
this.$textarea.on({
'focus': $.proxy(this.focus, this),
'keyup': $.proxy(this.keyup, this),
'change': $.proxy(this.change, this),
'select': $.proxy(this.select, this)
});
if (this.eventSupported('keydown')) {
this.$textarea.on('keydown', $.proxy(this.keydown, this));
}
if (this.eventSupported('keypress')) {
this.$textarea.on('keypress', $.proxy(this.keypress, this));
}
},
__handle: function(e) {
var target = $(e.currentTarget),
handler = this.$handler,
callback = this.$callback,
handlerName = target.attr('data-handler'),
callbackIndex = handler.indexOf(handlerName),
callbackHandler = callback[callbackIndex];
// Trigger the focusin
$(e.currentTarget).focus();
callbackHandler(this);
// Trigger onChange for each button handle
this.change(this);
// Unless it was the save handler,
// focusin the textarea
if (handlerName.indexOf('cmdSave') < 0) {
this.$textarea.focus();
}
e.preventDefault();
},
__localize: function(string) {
var messages = $.fn.markdown.messages,
language = this.$options.language;
if (
typeof messages !== 'undefined' &&
typeof messages[language] !== 'undefined' &&
typeof messages[language][string] !== 'undefined'
) {
return messages[language][string];
}
return string;
},
__getIcon: function(src) {
if(typeof src == 'object'){
var customIcon = this.$options.customIcons[src.name];
return typeof customIcon == 'undefined' ? src.icon[this.$options.iconlibrary] : customIcon;
} else {
return src;
}
},
setFullscreen: function(mode) {
var $editor = this.$editor,
$textarea = this.$textarea;
if (mode === true) {
$editor.addClass('md-fullscreen-mode');
$('body').addClass('md-nooverflow');
this.$options.onFullscreen(this);
} else {
$editor.removeClass('md-fullscreen-mode');
$('body').removeClass('md-nooverflow');
this.$options.onFullscreenExit(this);
if (this.$isPreview === true)
this.hidePreview().showPreview();
}
this.$isFullscreen = mode;
$textarea.focus();
},
showEditor: function() {
var instance = this,
textarea,
ns = this.$ns,
container = this.$element,
originalHeigth = container.css('height'),
originalWidth = container.css('width'),
editable = this.$editable,
handler = this.$handler,
callback = this.$callback,
options = this.$options,
editor = $('', {
'class': 'md-editor',
click: function() {
instance.focus();
}
});
// Prepare the editor
if (this.$editor === null) {
// Create the panel
var editorHeader = $('', {
'class': 'md-header btn-toolbar'
});
// Merge the main & additional button groups together
var allBtnGroups = [];
if (options.buttons.length > 0) allBtnGroups = allBtnGroups.concat(options.buttons[0]);
if (options.additionalButtons.length > 0) {
// iterate the additional button groups
$.each(options.additionalButtons[0], function(idx, buttonGroup) {
// see if the group name of the additional group matches an existing group
var matchingGroups = $.grep(allBtnGroups, function(allButtonGroup, allIdx) {
return allButtonGroup.name === buttonGroup.name;
});
// if it matches add the additional buttons to that group, if not just add it to the all buttons group
if (matchingGroups.length > 0) {
matchingGroups[0].data = matchingGroups[0].data.concat(buttonGroup.data);
} else {
allBtnGroups.push(options.additionalButtons[0][idx]);
}
});
}
// Reduce and/or reorder the button groups
if (options.reorderButtonGroups.length > 0) {
allBtnGroups = allBtnGroups
.filter(function(btnGroup) {
return options.reorderButtonGroups.indexOf(btnGroup.name) > -1;
})
.sort(function(a, b) {
if (options.reorderButtonGroups.indexOf(a.name) < options.reorderButtonGroups.indexOf(b.name)) return -1;
if (options.reorderButtonGroups.indexOf(a.name) > options.reorderButtonGroups.indexOf(b.name)) return 1;
return 0;
});
}
// Build the buttons
if (allBtnGroups.length > 0) {
editorHeader = this.__buildButtons([allBtnGroups], editorHeader);
}
if (options.fullscreen.enable) {
editorHeader.append('
').on('click', '.md-control-fullscreen', function(e) {
e.preventDefault();
instance.setFullscreen(true);
});
}
editor.append(editorHeader);
// Wrap the textarea
if (container.is('textarea')) {
container.before(editor);
textarea = container;
textarea.addClass('md-input');
editor.append(textarea);
} else {
var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(),
currentContent = $.trim(rawContent);
// This is some arbitrary content that could be edited
textarea = $('', {
'class': 'md-input',
'val': currentContent
});
editor.append(textarea);
// Save the editable
editable.el = container;
editable.type = container.prop('tagName').toLowerCase();
editable.content = container.html();
$(container[0].attributes).each(function() {
editable.attrKeys.push(this.nodeName);
editable.attrValues.push(this.nodeValue);
});
// Set editor to block the original container
container.replaceWith(editor);
}
var editorFooter = $('', {
'class': 'md-footer'
}),
createFooter = false,
footer = '';
// Create the footer if savable
if (options.savable) {
createFooter = true;
var saveHandler = 'cmdSave';
// Register handler and callback
handler.push(saveHandler);
callback.push(options.onSave);
editorFooter.append('');
}
footer = typeof options.footer === 'function' ? options.footer(this) : options.footer;
if ($.trim(footer) !== '') {
createFooter = true;
editorFooter.append(footer);
}
if (createFooter) editor.append(editorFooter);
// Set width
if (options.width && options.width !== 'inherit') {
if (jQuery.isNumeric(options.width)) {
editor.css('display', 'table');
textarea.css('width', options.width + 'px');
} else {
editor.addClass(options.width);
}
}
// Set height
if (options.height && options.height !== 'inherit') {
if (jQuery.isNumeric(options.height)) {
var height = options.height;
if (editorHeader) height = Math.max(0, height - editorHeader.outerHeight());
if (editorFooter) height = Math.max(0, height - editorFooter.outerHeight());
textarea.css('height', height + 'px');
} else {
editor.addClass(options.height);
}
}
// Reference
this.$editor = editor;
this.$textarea = textarea;
this.$editable = editable;
this.$oldContent = this.getContent();
this.__setListener();
this.__setEventListeners();
// Set editor attributes, data short-hand API and listener
this.$editor.attr('id', (new Date()).getTime());
this.$editor.on('click', '[data-provider="bootstrap-markdown"]', $.proxy(this.__handle, this));
if (this.$element.is(':disabled') || this.$element.is('[readonly]')) {
this.$editor.addClass('md-editor-disabled');
this.disableButtons('all');
}
if (this.eventSupported('keydown') && typeof jQuery.hotkeys === 'object') {
editorHeader.find('[data-provider="bootstrap-markdown"]').each(function() {
var $button = $(this),
hotkey = $button.attr('data-hotkey');
if (hotkey.toLowerCase() !== '') {
textarea.bind('keydown', hotkey, function() {
$button.trigger('click');
return false;
});
}
});
}
if (options.initialstate === 'preview') {
this.showPreview();
} else if (options.initialstate === 'fullscreen' && options.fullscreen.enable) {
this.setFullscreen(true);
}
} else {
this.$editor.show();
}
if (options.autofocus) {
this.$textarea.focus();
this.$editor.addClass('active');
}
if (options.fullscreen.enable && options.fullscreen !== false) {
this.$editor.append('