316 lines
10 KiB
JavaScript
316 lines
10 KiB
JavaScript
|
(function (factory) {
|
||
|
/* global define */
|
||
|
if (typeof define === 'function' && define.amd) {
|
||
|
// AMD. Register as an anonymous module.
|
||
|
define(['jquery'], factory);
|
||
|
} else if (typeof module === 'object' && module.exports) {
|
||
|
// Node/CommonJS
|
||
|
module.exports = factory(require('jquery'));
|
||
|
} else {
|
||
|
// Browser globals
|
||
|
factory(window.jQuery);
|
||
|
}
|
||
|
}(function ($) {
|
||
|
$.extend($.summernote.plugins, {
|
||
|
'specialchars': function (context) {
|
||
|
var self = this;
|
||
|
var ui = $.summernote.ui;
|
||
|
|
||
|
var $editor = context.layoutInfo.editor;
|
||
|
var options = context.options;
|
||
|
var lang = options.langInfo;
|
||
|
|
||
|
var KEY = {
|
||
|
UP: 38,
|
||
|
DOWN: 40,
|
||
|
LEFT: 37,
|
||
|
RIGHT: 39,
|
||
|
ENTER: 13
|
||
|
};
|
||
|
var COLUMN_LENGTH = 15;
|
||
|
var COLUMN_WIDTH = 35;
|
||
|
|
||
|
var currentColumn, currentRow, totalColumn, totalRow = 0;
|
||
|
|
||
|
// special characters data set
|
||
|
var specialCharDataSet = [
|
||
|
'"', '&', '<', '>', '¡', '¢',
|
||
|
'£', '¤', '¥', '¦', '§',
|
||
|
'¨', '©', 'ª', '«', '¬',
|
||
|
'®', '¯', '°', '±', '²',
|
||
|
'³', '´', 'µ', '¶', '·',
|
||
|
'¸', '¹', 'º', '»', '¼',
|
||
|
'½', '¾', '¿', '×', '÷',
|
||
|
'ƒ', 'ˆ', '˜', '–', '—',
|
||
|
'‘', '’', '‚', '“', '”',
|
||
|
'„', '†', '‡', '•', '…',
|
||
|
'‰', '′', '″', '‹', '›',
|
||
|
'‾', '⁄', '€', 'ℑ', '℘',
|
||
|
'ℜ', '™', 'ℵ', '←', '↑',
|
||
|
'→', '↓', '↔', '↵', '⇐',
|
||
|
'⇑', '⇒', '⇓', '⇔', '∀',
|
||
|
'∂', '∃', '∅', '∇', '∈',
|
||
|
'∉', '∋', '∏', '∑', '−',
|
||
|
'∗', '√', '∝', '∞', '∠',
|
||
|
'∧', '∨', '∩', '∪', '∫',
|
||
|
'∴', '∼', '≅', '≈', '≠',
|
||
|
'≡', '≤', '≥', '⊂', '⊃',
|
||
|
'⊄', '⊆', '⊇', '⊕', '⊗',
|
||
|
'⊥', '⋅', '⌈', '⌉', '⌊',
|
||
|
'⌋', '◊', '♠', '♣', '♥',
|
||
|
'♦'
|
||
|
];
|
||
|
|
||
|
context.memo('button.specialCharacter', function () {
|
||
|
return ui.button({
|
||
|
contents: '<i class="fa fa-font fa-flip-vertical">',
|
||
|
tooltip: lang.specialChar.specialChar,
|
||
|
click: function () {
|
||
|
self.show();
|
||
|
}
|
||
|
}).render();
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Make Special Characters Table
|
||
|
*
|
||
|
* @member plugin.specialChar
|
||
|
* @private
|
||
|
* @return {jQuery}
|
||
|
*/
|
||
|
this.makeSpecialCharSetTable = function () {
|
||
|
var $table = $('<table/>');
|
||
|
$.each(specialCharDataSet, function (idx, text) {
|
||
|
var $td = $('<td/>').addClass('note-specialchar-node');
|
||
|
var $tr = (idx % COLUMN_LENGTH === 0) ? $('<tr/>') : $table.find('tr').last();
|
||
|
|
||
|
var $button = ui.button({
|
||
|
callback: function ($node) {
|
||
|
$node.html(text);
|
||
|
$node.attr('title', text);
|
||
|
$node.attr('data-value', encodeURIComponent(text));
|
||
|
$node.css({
|
||
|
width: COLUMN_WIDTH,
|
||
|
'margin-right': '2px',
|
||
|
'margin-bottom': '2px'
|
||
|
});
|
||
|
}
|
||
|
}).render();
|
||
|
|
||
|
$td.append($button);
|
||
|
|
||
|
$tr.append($td);
|
||
|
if (idx % COLUMN_LENGTH === 0) {
|
||
|
$table.append($tr);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
totalRow = $table.find('tr').length;
|
||
|
totalColumn = COLUMN_LENGTH;
|
||
|
|
||
|
return $table;
|
||
|
};
|
||
|
|
||
|
this.initialize = function () {
|
||
|
var $container = options.dialogsInBody ? $(document.body) : $editor;
|
||
|
|
||
|
var body = '<div class="form-group row-fluid">' + this.makeSpecialCharSetTable()[0].outerHTML + '</div>';
|
||
|
|
||
|
this.$dialog = ui.dialog({
|
||
|
title: lang.specialChar.select,
|
||
|
body: body
|
||
|
}).render().appendTo($container);
|
||
|
};
|
||
|
|
||
|
this.show = function () {
|
||
|
var text = context.invoke('editor.getSelectedText');
|
||
|
context.invoke('editor.saveRange');
|
||
|
this.showSpecialCharDialog(text).then(function (selectChar) {
|
||
|
context.invoke('editor.restoreRange');
|
||
|
|
||
|
// build node
|
||
|
var $node = $('<span></span>').html(selectChar)[0];
|
||
|
|
||
|
if ($node) {
|
||
|
// insert video node
|
||
|
context.invoke('editor.insertNode', $node);
|
||
|
}
|
||
|
}).fail(function () {
|
||
|
context.invoke('editor.restoreRange');
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* show image dialog
|
||
|
*
|
||
|
* @param {jQuery} $dialog
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
this.showSpecialCharDialog = function (text) {
|
||
|
return $.Deferred(function (deferred) {
|
||
|
var $specialCharDialog = self.$dialog;
|
||
|
var $specialCharNode = $specialCharDialog.find('.note-specialchar-node');
|
||
|
var $selectedNode = null;
|
||
|
var ARROW_KEYS = [KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT];
|
||
|
var ENTER_KEY = KEY.ENTER;
|
||
|
|
||
|
function addActiveClass($target) {
|
||
|
if (!$target) {
|
||
|
return;
|
||
|
}
|
||
|
$target.find('button').addClass('active');
|
||
|
$selectedNode = $target;
|
||
|
}
|
||
|
|
||
|
function removeActiveClass($target) {
|
||
|
$target.find('button').removeClass('active');
|
||
|
$selectedNode = null;
|
||
|
}
|
||
|
|
||
|
// find next node
|
||
|
function findNextNode(row, column) {
|
||
|
var findNode = null;
|
||
|
$.each($specialCharNode, function (idx, $node) {
|
||
|
var findRow = Math.ceil((idx + 1) / COLUMN_LENGTH);
|
||
|
var findColumn = ((idx + 1) % COLUMN_LENGTH === 0) ? COLUMN_LENGTH : (idx + 1) % COLUMN_LENGTH;
|
||
|
if (findRow === row && findColumn === column) {
|
||
|
findNode = $node;
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
return $(findNode);
|
||
|
}
|
||
|
|
||
|
function arrowKeyHandler(keyCode) {
|
||
|
// left, right, up, down key
|
||
|
var $nextNode;
|
||
|
var lastRowColumnLength = $specialCharNode.length % totalColumn;
|
||
|
|
||
|
if (KEY.LEFT === keyCode) {
|
||
|
|
||
|
if (currentColumn > 1) {
|
||
|
currentColumn = currentColumn - 1;
|
||
|
} else if (currentRow === 1 && currentColumn === 1) {
|
||
|
currentColumn = lastRowColumnLength;
|
||
|
currentRow = totalRow;
|
||
|
} else {
|
||
|
currentColumn = totalColumn;
|
||
|
currentRow = currentRow - 1;
|
||
|
}
|
||
|
|
||
|
} else if (KEY.RIGHT === keyCode) {
|
||
|
|
||
|
if (currentRow === totalRow && lastRowColumnLength === currentColumn) {
|
||
|
currentColumn = 1;
|
||
|
currentRow = 1;
|
||
|
} else if (currentColumn < totalColumn) {
|
||
|
currentColumn = currentColumn + 1;
|
||
|
} else {
|
||
|
currentColumn = 1;
|
||
|
currentRow = currentRow + 1;
|
||
|
}
|
||
|
|
||
|
} else if (KEY.UP === keyCode) {
|
||
|
if (currentRow === 1 && lastRowColumnLength < currentColumn) {
|
||
|
currentRow = totalRow - 1;
|
||
|
} else {
|
||
|
currentRow = currentRow - 1;
|
||
|
}
|
||
|
} else if (KEY.DOWN === keyCode) {
|
||
|
currentRow = currentRow + 1;
|
||
|
}
|
||
|
|
||
|
if (currentRow === totalRow && currentColumn > lastRowColumnLength) {
|
||
|
currentRow = 1;
|
||
|
} else if (currentRow > totalRow) {
|
||
|
currentRow = 1;
|
||
|
} else if (currentRow < 1) {
|
||
|
currentRow = totalRow;
|
||
|
}
|
||
|
|
||
|
$nextNode = findNextNode(currentRow, currentColumn);
|
||
|
|
||
|
if ($nextNode) {
|
||
|
removeActiveClass($selectedNode);
|
||
|
addActiveClass($nextNode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function enterKeyHandler() {
|
||
|
if (!$selectedNode) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
deferred.resolve(decodeURIComponent($selectedNode.find('button').attr('data-value')));
|
||
|
$specialCharDialog.modal('hide');
|
||
|
}
|
||
|
|
||
|
function keyDownEventHandler(event) {
|
||
|
event.preventDefault();
|
||
|
var keyCode = event.keyCode;
|
||
|
if (keyCode === undefined || keyCode === null) {
|
||
|
return;
|
||
|
}
|
||
|
// check arrowKeys match
|
||
|
if (ARROW_KEYS.indexOf(keyCode) > -1) {
|
||
|
if ($selectedNode === null) {
|
||
|
addActiveClass($specialCharNode.eq(0));
|
||
|
currentColumn = 1;
|
||
|
currentRow = 1;
|
||
|
return;
|
||
|
}
|
||
|
arrowKeyHandler(keyCode);
|
||
|
} else if (keyCode === ENTER_KEY) {
|
||
|
enterKeyHandler();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// remove class
|
||
|
removeActiveClass($specialCharNode);
|
||
|
|
||
|
// find selected node
|
||
|
if (text) {
|
||
|
for (var i = 0; i < $specialCharNode.length; i++) {
|
||
|
var $checkNode = $($specialCharNode[i]);
|
||
|
if ($checkNode.text() === text) {
|
||
|
addActiveClass($checkNode);
|
||
|
currentRow = Math.ceil((i + 1) / COLUMN_LENGTH);
|
||
|
currentColumn = (i + 1) % COLUMN_LENGTH;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ui.onDialogShown(self.$dialog, function () {
|
||
|
|
||
|
$(document).on('keydown', keyDownEventHandler);
|
||
|
|
||
|
self.$dialog.find('button').tooltip();
|
||
|
|
||
|
$specialCharNode.on('click', function (event) {
|
||
|
event.preventDefault();
|
||
|
deferred.resolve(decodeURIComponent($(event.currentTarget).find('button').attr('data-value')));
|
||
|
ui.hideDialog(self.$dialog);
|
||
|
});
|
||
|
|
||
|
});
|
||
|
|
||
|
ui.onDialogHidden(self.$dialog, function () {
|
||
|
$specialCharNode.off('click');
|
||
|
|
||
|
self.$dialog.find('button').tooltip('destroy');
|
||
|
|
||
|
$(document).off('keydown', keyDownEventHandler);
|
||
|
|
||
|
if (deferred.state() === 'pending') {
|
||
|
deferred.reject();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
ui.showDialog(self.$dialog);
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
});
|
||
|
}));
|