define([
'summernote/core/agent',
'summernote/core/dom',
'summernote/core/func',
'summernote/core/list'
], function (agent, dom, func, list) {
/**
* @class Renderer
*
* renderer
*
* rendering toolbar and editable
*/
var Renderer = function () {
/**
* bootstrap button template
* @private
* @param {String} label button name
* @param {Object} [options] button options
* @param {String} [options.event] data-event
* @param {String} [options.className] button's class name
* @param {String} [options.value] data-value
* @param {String} [options.title] button's title for popup
* @param {String} [options.dropdown] dropdown html
* @param {String} [options.hide] data-hide
*/
var tplButton = function (label, options) {
var event = options.event;
var value = options.value;
var title = options.title;
var className = options.className;
var dropdown = options.dropdown;
var hide = options.hide;
return (dropdown ? '
',
leftButton + centerButton + rightButton + justifyButton,
'
');
for (var i = 0, lenGroup = group[1].length; i < lenGroup; i++) {
var $button = $(tplButtonInfo[group[1][i]](lang, options));
$button.attr('data-name', group[1][i]);
$group.append($button);
}
$content.append($group);
}
return tplPopover('note-air-popover', $content.children());
};
var $notePopover = $('
');
$notePopover.append(tplLinkPopover());
$notePopover.append(tplImagePopover());
if (options.airMode) {
$notePopover.append(tplAirPopover());
}
return $notePopover;
};
var tplHandles = function (options) {
return '
' +
'
' +
'
' +
'
' +
'
' +
'
' +
'
' +
(options.disableResizeImage ? '' : '
') +
'
' +
'
';
};
/**
* shortcut table template
* @param {String} title
* @param {String} body
*/
var tplShortcut = function (title, keys) {
var keyClass = 'note-shortcut-col col-xs-6 note-shortcut-';
var body = [];
for (var i in keys) {
if (keys.hasOwnProperty(i)) {
body.push(
'
' + keys[i].kbd + '
' +
'
' + keys[i].text + '
'
);
}
}
return '
' +
'
' + body.join('
') + '
';
};
var tplShortcutText = function (lang) {
var keys = [
{ kbd: '⌘ + B', text: lang.font.bold },
{ kbd: '⌘ + I', text: lang.font.italic },
{ kbd: '⌘ + U', text: lang.font.underline },
{ kbd: '⌘ + \\', text: lang.font.clear }
];
return tplShortcut(lang.shortcut.textFormatting, keys);
};
var tplShortcutAction = function (lang) {
var keys = [
{ kbd: '⌘ + Z', text: lang.history.undo },
{ kbd: '⌘ + ⇧ + Z', text: lang.history.redo },
{ kbd: '⌘ + ]', text: lang.paragraph.indent },
{ kbd: '⌘ + [', text: lang.paragraph.outdent },
{ kbd: '⌘ + ENTER', text: lang.hr.insert }
];
return tplShortcut(lang.shortcut.action, keys);
};
var tplShortcutPara = function (lang) {
var keys = [
{ kbd: '⌘ + ⇧ + L', text: lang.paragraph.left },
{ kbd: '⌘ + ⇧ + E', text: lang.paragraph.center },
{ kbd: '⌘ + ⇧ + R', text: lang.paragraph.right },
{ kbd: '⌘ + ⇧ + J', text: lang.paragraph.justify },
{ kbd: '⌘ + ⇧ + NUM7', text: lang.lists.ordered },
{ kbd: '⌘ + ⇧ + NUM8', text: lang.lists.unordered }
];
return tplShortcut(lang.shortcut.paragraphFormatting, keys);
};
var tplShortcutStyle = function (lang) {
var keys = [
{ kbd: '⌘ + NUM0', text: lang.style.normal },
{ kbd: '⌘ + NUM1', text: lang.style.h1 },
{ kbd: '⌘ + NUM2', text: lang.style.h2 },
{ kbd: '⌘ + NUM3', text: lang.style.h3 },
{ kbd: '⌘ + NUM4', text: lang.style.h4 },
{ kbd: '⌘ + NUM5', text: lang.style.h5 },
{ kbd: '⌘ + NUM6', text: lang.style.h6 }
];
return tplShortcut(lang.shortcut.documentStyle, keys);
};
var tplExtraShortcuts = function (lang, options) {
var extraKeys = options.extraKeys;
var keys = [];
for (var key in extraKeys) {
if (extraKeys.hasOwnProperty(key)) {
keys.push({ kbd: key, text: extraKeys[key] });
}
}
return tplShortcut(lang.shortcut.extraKeys, keys);
};
var tplShortcutTable = function (lang, options) {
var colClass = 'class="note-shortcut note-shortcut-col col-sm-6 col-xs-12"';
var template = [
'
' + tplShortcutAction(lang, options) + '
' +
'
' + tplShortcutText(lang, options) + '
',
'
' + tplShortcutStyle(lang, options) + '
' +
'
' + tplShortcutPara(lang, options) + '
'
];
if (options.extraKeys) {
template.push('
' + tplExtraShortcuts(lang, options) + '
');
}
return '
' +
template.join('
') +
'
';
};
var replaceMacKeys = function (sHtml) {
return sHtml.replace(/⌘/g, 'Ctrl').replace(/⇧/g, 'Shift');
};
var tplDialogInfo = {
image: function (lang, options) {
var imageLimitation = '';
if (options.maximumImageFileSize) {
var unit = Math.floor(Math.log(options.maximumImageFileSize) / Math.log(1024));
var readableSize = (options.maximumImageFileSize / Math.pow(1024, unit)).toFixed(2) * 1 +
' ' + ' KMGTP'[unit] + 'B';
imageLimitation = '
' + lang.image.maximumFileSize + ' : ' + readableSize + ' ';
}
var body = '
' +
'' + lang.image.selectFromFiles + ' ' +
' ' +
imageLimitation +
'
' +
'
' +
'' + lang.image.url + ' ' +
' ' +
'
';
var footer = '
' + lang.image.insert + ' ';
return tplDialog('note-image-dialog', lang.image.insert, body, footer);
},
link: function (lang, options) {
var body = '
' +
'' + lang.link.textToDisplay + ' ' +
' ' +
'
' +
'
' +
'' + lang.link.url + ' ' +
' ' +
'
' +
(!options.disableLinkTarget ?
'
' +
'' + ' ' +
lang.link.openInNewWindow +
' ' +
'
' : ''
);
var footer = '
' + lang.link.insert + ' ';
return tplDialog('note-link-dialog', lang.link.insert, body, footer);
},
help: function (lang, options) {
var body = '
' + lang.shortcut.close + ' ' +
'
' + lang.shortcut.shortcuts + '
' +
(agent.isMac ? tplShortcutTable(lang, options) : replaceMacKeys(tplShortcutTable(lang, options))) +
'
' +
'Summernote @VERSION · ' +
'Project · ' +
'Issues ' +
'
';
return tplDialog('note-help-dialog', '', body, '');
}
};
var tplDialogs = function (lang, options) {
var dialogs = '';
$.each(tplDialogInfo, function (idx, tplDialog) {
dialogs += tplDialog(lang, options);
});
return '
' + dialogs + '
';
};
var tplStatusbar = function () {
return '
';
};
var representShortcut = function (str) {
if (agent.isMac) {
str = str.replace('CMD', '⌘').replace('SHIFT', '⇧');
}
return str.replace('BACKSLASH', '\\')
.replace('SLASH', '/')
.replace('LEFTBRACKET', '[')
.replace('RIGHTBRACKET', ']');
};
/**
* createTooltip
*
* @param {jQuery} $container
* @param {Object} keyMap
* @param {String} [sPlacement]
*/
var createTooltip = function ($container, keyMap, sPlacement) {
var invertedKeyMap = func.invertObject(keyMap);
var $buttons = $container.find('button');
$buttons.each(function (i, elBtn) {
var $btn = $(elBtn);
var sShortcut = invertedKeyMap[$btn.data('event')];
if (sShortcut) {
$btn.attr('title', function (i, v) {
return v + ' (' + representShortcut(sShortcut) + ')';
});
}
// bootstrap tooltip on btn-group bug
// https://github.com/twbs/bootstrap/issues/5687
}).tooltip({
container: 'body',
trigger: 'hover',
placement: sPlacement || 'top'
}).on('click', function () {
$(this).tooltip('hide');
});
};
// createPalette
var createPalette = function ($container, options) {
var colorInfo = options.colors;
$container.find('.note-color-palette').each(function () {
var $palette = $(this), eventName = $palette.attr('data-target-event');
var paletteContents = [];
for (var row = 0, lenRow = colorInfo.length; row < lenRow; row++) {
var colors = colorInfo[row];
var buttons = [];
for (var col = 0, lenCol = colors.length; col < lenCol; col++) {
var color = colors[col];
buttons.push(['
'].join(''));
}
paletteContents.push('
' + buttons.join('') + '
');
}
$palette.html(paletteContents.join(''));
});
};
/**
* create summernote layout (air mode)
*
* @param {jQuery} $holder
* @param {Object} options
*/
this.createLayoutByAirMode = function ($holder, options) {
var langInfo = options.langInfo;
var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
var id = func.uniqueId();
$holder.addClass('note-air-editor note-editable panel-body');
$holder.attr({
'id': 'note-editor-' + id,
'contentEditable': true
});
var body = document.body;
// create Popover
var $popover = $(tplPopovers(langInfo, options));
$popover.addClass('note-air-layout');
$popover.attr('id', 'note-popover-' + id);
$popover.appendTo(body);
createTooltip($popover, keyMap);
createPalette($popover, options);
// create Handle
var $handle = $(tplHandles(options));
$handle.addClass('note-air-layout');
$handle.attr('id', 'note-handle-' + id);
$handle.appendTo(body);
// create Dialog
var $dialog = $(tplDialogs(langInfo, options));
$dialog.addClass('note-air-layout');
$dialog.attr('id', 'note-dialog-' + id);
$dialog.find('button.close, a.modal-close').click(function () {
$(this).closest('.modal').modal('hide');
});
$dialog.appendTo(body);
};
/**
* create summernote layout (normal mode)
*
* @param {jQuery} $holder
* @param {Object} options
*/
this.createLayoutByFrame = function ($holder, options) {
var langInfo = options.langInfo;
//01. create Editor
var $editor = $('
');
if (options.width) {
$editor.width(options.width);
}
//02. statusbar (resizebar)
if (options.height > 0) {
$('
' + (options.disableResizeEditor ? '' : tplStatusbar()) + '
').prependTo($editor);
}
//03 editing area
var $editingArea = $('
');
//03. create editable
var isContentEditable = !$holder.is(':disabled');
var $editable = $('
').prependTo($editingArea);
if (options.height) {
$editable.height(options.height);
}
if (options.direction) {
$editable.attr('dir', options.direction);
}
var placeholder = $holder.attr('placeholder') || options.placeholder;
if (placeholder) {
$editable.attr('data-placeholder', placeholder);
}
$editable.html(dom.html($holder) || dom.emptyPara);
//031. create codable
$('
').prependTo($editingArea);
//04. create Popover
var $popover = $(tplPopovers(langInfo, options)).prependTo($editingArea);
createPalette($popover, options);
createTooltip($popover, keyMap);
//05. handle(control selection, ...)
$(tplHandles(options)).prependTo($editingArea);
$editingArea.prependTo($editor);
//06. create Toolbar
var $toolbar = $('
');
for (var idx = 0, len = options.toolbar.length; idx < len; idx ++) {
var groupName = options.toolbar[idx][0];
var groupButtons = options.toolbar[idx][1];
var $group = $('
');
for (var i = 0, btnLength = groupButtons.length; i < btnLength; i++) {
var buttonInfo = tplButtonInfo[groupButtons[i]];
// continue creating toolbar even if a button doesn't exist
if (!$.isFunction(buttonInfo)) { continue; }
var $button = $(buttonInfo(langInfo, options));
$button.attr('data-name', groupButtons[i]); // set button's alias, becuase to get button element from $toolbar
$group.append($button);
}
$toolbar.append($group);
}
var keyMap = options.keyMap[agent.isMac ? 'mac' : 'pc'];
createPalette($toolbar, options);
createTooltip($toolbar, keyMap, 'bottom');
$toolbar.prependTo($editor);
//07. create Dropzone
$('
').prependTo($editor);
//08. create Dialog
var $dialogContainer = options.dialogsInBody ? $(document.body) : $editor;
var $dialog = $(tplDialogs(langInfo, options)).prependTo($dialogContainer);
$dialog.find('button.close, a.modal-close').click(function () {
$(this).closest('.modal').modal('hide');
});
//09. Editor/Holder switch
$editor.insertAfter($holder);
$holder.hide();
};
this.hasNoteEditor = function ($holder) {
return this.noteEditorFromHolder($holder).length > 0;
};
this.noteEditorFromHolder = function ($holder) {
if ($holder.hasClass('note-air-editor')) {
return $holder;
} else if ($holder.next().hasClass('note-editor')) {
return $holder.next();
} else {
return $();
}
};
/**
* create summernote layout
*
* @param {jQuery} $holder
* @param {Object} options
*/
this.createLayout = function ($holder, options) {
if (options.airMode) {
this.createLayoutByAirMode($holder, options);
} else {
this.createLayoutByFrame($holder, options);
}
};
/**
* returns layoutInfo from holder
*
* @param {jQuery} $holder - placeholder
* @return {Object}
*/
this.layoutInfoFromHolder = function ($holder) {
var $editor = this.noteEditorFromHolder($holder);
if (!$editor.length) {
return;
}
// connect $holder to $editor
$editor.data('holder', $holder);
return dom.buildLayoutInfo($editor);
};
/**
* removeLayout
*
* @param {jQuery} $holder - placeholder
* @param {Object} layoutInfo
* @param {Object} options
*
*/
this.removeLayout = function ($holder, layoutInfo, options) {
if (options.airMode) {
$holder.removeClass('note-air-editor note-editable')
.removeAttr('id contentEditable');
layoutInfo.popover().remove();
layoutInfo.handle().remove();
layoutInfo.dialog().remove();
} else {
$holder.html(layoutInfo.editable().html());
if (options.dialogsInBody) {
layoutInfo.dialog().remove();
}
layoutInfo.editor().remove();
$holder.show();
}
};
/**
*
* @return {Object}
* @return {function(label, options=):string} return.button {@link #tplButton function to make text button}
* @return {function(iconClass, options=):string} return.iconButton {@link #tplIconButton function to make icon button}
* @return {function(className, title=, body=, footer=):string} return.dialog {@link #tplDialog function to make dialog}
*/
this.getTemplate = function () {
return {
button: tplButton,
iconButton: tplIconButton,
dialog: tplDialog
};
};
/**
* add button information
*
* @param {String} name button name
* @param {Function} buttonInfo function to make button, reference to {@link #tplButton},{@link #tplIconButton}
*/
this.addButtonInfo = function (name, buttonInfo) {
tplButtonInfo[name] = buttonInfo;
};
/**
*
* @param {String} name
* @param {Function} dialogInfo function to make dialog, reference to {@link #tplDialog}
*/
this.addDialogInfo = function (name, dialogInfo) {
tplDialogInfo[name] = dialogInfo;
};
};
return Renderer;
});