You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
667 lines
13 KiB
667 lines
13 KiB
/*
|
|
* Emoji plugin for summernote [https://github.com/summernote/summernote]
|
|
* Canonical - https://github.com/JustinEldracher/summernote-plugins
|
|
*/
|
|
|
|
odoo.define('muk_web_utils.summernote_ext_specialchars', function (require) {
|
|
'use strict';
|
|
|
|
//template
|
|
var tmpl = $.summernote.renderer.getTemplate();
|
|
|
|
// core functions: range, dom
|
|
var range = $.summernote.core.range;
|
|
var dom = $.summernote.core.dom;
|
|
|
|
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 = [
|
|
"!",
|
|
""",
|
|
"#",
|
|
"$",
|
|
"%",
|
|
"&",
|
|
"'",
|
|
"(",
|
|
")",
|
|
"*",
|
|
"+",
|
|
",",
|
|
"-",
|
|
".",
|
|
"/",
|
|
"0",
|
|
"1",
|
|
"2",
|
|
"3",
|
|
"4",
|
|
"5",
|
|
"6",
|
|
"7",
|
|
"8",
|
|
"9",
|
|
":",
|
|
";",
|
|
"<",
|
|
"=",
|
|
">",
|
|
"?",
|
|
"@",
|
|
"A",
|
|
"B",
|
|
"C",
|
|
"D",
|
|
"E",
|
|
"F",
|
|
"G",
|
|
"H",
|
|
"I",
|
|
"J",
|
|
"K",
|
|
"L",
|
|
"M",
|
|
"N",
|
|
"O",
|
|
"P",
|
|
"Q",
|
|
"R",
|
|
"S",
|
|
"T",
|
|
"U",
|
|
"V",
|
|
"W",
|
|
"X",
|
|
"Y",
|
|
"Z",
|
|
"[",
|
|
"\",
|
|
"]",
|
|
"^",
|
|
"_",
|
|
"`",
|
|
"a",
|
|
"b",
|
|
"c",
|
|
"d",
|
|
"e",
|
|
"f",
|
|
"g",
|
|
"h",
|
|
"i",
|
|
"j",
|
|
"k",
|
|
"l",
|
|
"m",
|
|
"n",
|
|
"o",
|
|
"p",
|
|
"q",
|
|
"r",
|
|
"s",
|
|
"t",
|
|
"u",
|
|
"v",
|
|
"w",
|
|
"x",
|
|
"y",
|
|
"z",
|
|
"{",
|
|
"|",
|
|
"}",
|
|
"~",
|
|
"À",
|
|
"Á",
|
|
"Â",
|
|
"Ã",
|
|
"Ä",
|
|
"Å",
|
|
"Æ",
|
|
"Ç",
|
|
"È",
|
|
"É",
|
|
"Ê",
|
|
"Ë",
|
|
"Ì",
|
|
"Í",
|
|
"Î",
|
|
"Ï",
|
|
"Ð",
|
|
"Ñ",
|
|
"Ò",
|
|
"Ó",
|
|
"Ô",
|
|
"Õ",
|
|
"Ö",
|
|
"Ø",
|
|
"Ù",
|
|
"Ú",
|
|
"Û",
|
|
"Ü",
|
|
"Ý",
|
|
"Þ",
|
|
"ß",
|
|
"à",
|
|
"á",
|
|
"â",
|
|
"ã",
|
|
"ä",
|
|
"å",
|
|
"æ",
|
|
"ç",
|
|
"è",
|
|
"é",
|
|
"ê",
|
|
"ë",
|
|
"ì",
|
|
"í",
|
|
"î",
|
|
"ï",
|
|
"ð",
|
|
"ñ",
|
|
"ò",
|
|
"ó",
|
|
"ô",
|
|
"õ",
|
|
"ö",
|
|
"ø",
|
|
"ù",
|
|
"ú",
|
|
"û",
|
|
"ü",
|
|
"ý",
|
|
"þ",
|
|
"ÿ",
|
|
"¡",
|
|
"¢",
|
|
"£",
|
|
"¤",
|
|
"¥",
|
|
"¦",
|
|
"§",
|
|
"¨",
|
|
"©",
|
|
"ª",
|
|
"«",
|
|
"¬",
|
|
"®",
|
|
"¯",
|
|
"°",
|
|
"±",
|
|
"²",
|
|
"³",
|
|
"´",
|
|
"µ",
|
|
"¶",
|
|
"¸",
|
|
"¹",
|
|
"º",
|
|
"»",
|
|
"¼",
|
|
"½",
|
|
"¾",
|
|
"¿",
|
|
"×",
|
|
"÷",
|
|
"∀",
|
|
"∂",
|
|
"∃",
|
|
"∅",
|
|
"∇",
|
|
"∈",
|
|
"∉",
|
|
"∋",
|
|
"∏",
|
|
"∑",
|
|
"−",
|
|
"∗",
|
|
"√",
|
|
"∝",
|
|
"∞",
|
|
"∠",
|
|
"∧",
|
|
"∨",
|
|
"∩",
|
|
"∪",
|
|
"∫",
|
|
"∴",
|
|
"∼",
|
|
"≅",
|
|
"≈",
|
|
"≠",
|
|
"≡",
|
|
"≤",
|
|
"≥",
|
|
"⊂",
|
|
"⊃",
|
|
"⊄",
|
|
"⊆",
|
|
"⊇",
|
|
"⊕",
|
|
"⊗",
|
|
"⊥",
|
|
"⋅",
|
|
"Α",
|
|
"Β",
|
|
"Γ",
|
|
"Δ",
|
|
"Ε",
|
|
"Ζ",
|
|
"Η",
|
|
"Θ",
|
|
"Ι",
|
|
"Κ",
|
|
"Λ",
|
|
"Μ",
|
|
"Ν",
|
|
"Ξ",
|
|
"Ο",
|
|
"Π",
|
|
"Ρ",
|
|
"Σ",
|
|
"Τ",
|
|
"Υ",
|
|
"Φ",
|
|
"Χ",
|
|
"Ψ",
|
|
"Ω",
|
|
"α",
|
|
"β",
|
|
"γ",
|
|
"δ",
|
|
"ε",
|
|
"ζ",
|
|
"η",
|
|
"θ",
|
|
"ι",
|
|
"κ",
|
|
"λ",
|
|
"μ",
|
|
"ν",
|
|
"ξ",
|
|
"ο",
|
|
"π",
|
|
"ρ",
|
|
"ς",
|
|
"σ",
|
|
"τ",
|
|
"υ",
|
|
"φ",
|
|
"χ",
|
|
"ψ",
|
|
"ω",
|
|
"ϑ",
|
|
"ϒ",
|
|
"ϖ",
|
|
"Œ",
|
|
"œ",
|
|
"Š",
|
|
"š",
|
|
"Ÿ",
|
|
"ƒ",
|
|
"ˆ",
|
|
"˜",
|
|
" ",
|
|
" ",
|
|
" ",
|
|
"‌",
|
|
"‍",
|
|
"‎",
|
|
"‏",
|
|
"–",
|
|
"—",
|
|
"‘",
|
|
"’",
|
|
"‚",
|
|
"“",
|
|
"”",
|
|
"„",
|
|
"†",
|
|
"‡",
|
|
"•",
|
|
"…",
|
|
"‰",
|
|
"′",
|
|
"″",
|
|
"‹",
|
|
"›",
|
|
"‾",
|
|
"€",
|
|
"™",
|
|
"←",
|
|
"↑",
|
|
"→",
|
|
"↓",
|
|
"↔",
|
|
"↵",
|
|
"⌈",
|
|
"⌉",
|
|
"⌊",
|
|
"⌋",
|
|
"◊",
|
|
"♠",
|
|
"♣",
|
|
"♥",
|
|
"♦",
|
|
];
|
|
|
|
/**
|
|
* @member plugin.specialChar
|
|
* @private
|
|
* @param {jQuery} $editable
|
|
* @return {String}
|
|
*/
|
|
var getTextOnRange = function ($editable) {
|
|
$editable.focus();
|
|
|
|
var rng = range.create();
|
|
|
|
// if range on anchor, expand range with anchor
|
|
if (rng.isOnAnchor()) {
|
|
var anchor = dom.ancestor(rng.sc, dom.isAnchor);
|
|
rng = range.createFromNode(anchor);
|
|
}
|
|
|
|
return rng.toString();
|
|
};
|
|
|
|
/**
|
|
* Make Special Characters Table
|
|
*
|
|
* @member plugin.specialChar
|
|
* @private
|
|
* @return {jQuery}
|
|
*/
|
|
var makeSpecialCharSetTable = function () {
|
|
var $table = $("<div/>").attr("id", "specialCharTable");
|
|
$.each(specialCharDataSet, function (idx, text) {
|
|
var $block = $("<span/>").attr("style", "border:1px solid black;display:inline-block;height:50px;width:35px;text-align:center;font-size:14pt;color:black;padding-top:10px;cursor:pointer;")
|
|
.addClass("note-specialchar-node char-" + idx).attr("title", text).attr("id", "char-" + idx);
|
|
$block.append(text);
|
|
$table.append($block);
|
|
});
|
|
|
|
return $table;
|
|
};
|
|
|
|
/**
|
|
* Show Special Characters and set event handlers on dialog controls.
|
|
*
|
|
* @member plugin.specialChar
|
|
* @private
|
|
* @param {jQuery} $dialog
|
|
* @param {jQuery} $dialog
|
|
* @param {Object} text
|
|
* @return {Promise}
|
|
*/
|
|
var showSpecialCharDialog = function ($editable, $dialog, text) {
|
|
return $.Deferred(function (deferred) {
|
|
var $specialCharDialog = $dialog.find('.note-specialchar-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;
|
|
var pos = 0;
|
|
var end = specialCharDataSet.length;
|
|
|
|
function addActiveClass($target) {
|
|
if (!$target) {
|
|
return;
|
|
}
|
|
$target.find('span').addClass('active');
|
|
$selectedNode = $target;
|
|
}
|
|
|
|
function removeActiveClass($target) {
|
|
$target.find('span').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 w = $("#specialCharTable").css("width") + "";
|
|
w = w.substr(0, w.length - 2);
|
|
var cols = Math.floor(w / 35);
|
|
pos = parseInt(pos);
|
|
|
|
if (KEY.LEFT === keyCode) {
|
|
if (pos > 0) {
|
|
pos--;
|
|
clear();
|
|
$(".char-" + pos).css("border", "1px solid blue").css("background-color", "aliceblue");
|
|
$selectedNode = $(".char-" + pos);
|
|
}
|
|
} else if (KEY.RIGHT === keyCode) {
|
|
if (pos < end - 1) {
|
|
pos++;
|
|
clear();
|
|
$(".char-" + pos).css("border", "1px solid blue").css("background-color", "aliceblue");
|
|
$selectedNode = $(".char-" + pos);
|
|
}
|
|
} else if (KEY.UP === keyCode) {
|
|
if (pos - cols >= 0) {
|
|
clear();
|
|
pos = pos - cols;
|
|
$(".char-" + pos).css("border", "1px solid blue").css("background-color", "aliceblue");
|
|
$selectedNode = $(".char-" + pos);
|
|
}
|
|
} else if (KEY.DOWN === keyCode) {
|
|
if (pos + cols <= end) {
|
|
clear();
|
|
pos = pos + cols;
|
|
$(".char-" + pos).css("border", "1px solid blue").css("background-color", "aliceblue");
|
|
$selectedNode = $(".char-" + pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
function enterKeyHandler() {
|
|
if (!$selectedNode) {
|
|
return;
|
|
}
|
|
|
|
pos = 0;
|
|
deferred.resolve(decodeURIComponent($selectedNode.attr("title")));
|
|
$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) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
$specialCharDialog.one('shown.bs.modal', function () {
|
|
$(document).on('keydown', keyDownEventHandler);
|
|
$specialCharNode.on('click', function (event) {
|
|
event.preventDefault();
|
|
pos = 0;
|
|
deferred.resolve(decodeURIComponent(event.currentTarget.title));
|
|
$specialCharDialog.modal('hide');
|
|
});
|
|
$specialCharNode.mouseenter(function() {
|
|
clear();
|
|
$(this).css("border", "1px solid blue").css("background-color", "aliceblue");
|
|
$selectedNode = $(this);
|
|
var thisid = $(this).attr("id") + "";
|
|
pos = thisid.substr(5);
|
|
});
|
|
$specialCharNode.mouseleave(function() {
|
|
clear();
|
|
});
|
|
}).one('hidden.bs.modal', function () {
|
|
$specialCharNode.off('click');
|
|
$(document).off('keydown', keyDownEventHandler);
|
|
if (deferred.state() === 'pending') {
|
|
deferred.reject();
|
|
}
|
|
}).modal('show');
|
|
|
|
// tooltip
|
|
/*$dialog.find('span').tooltip({
|
|
container: $specialCharDialog.find('.form-group'),
|
|
trigger: 'hover',
|
|
placement: 'top'
|
|
});*/
|
|
|
|
// $editable blur
|
|
$editable.blur();
|
|
|
|
function clear() {
|
|
$specialCharNode.css("border", "1px solid black").css("background-color", "white");
|
|
$selectedNode = null;
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @class plugin.specialChar
|
|
*
|
|
* Special Characters Plugin
|
|
*
|
|
* ### load script
|
|
*
|
|
* ```
|
|
* < script src="plugin/summernote-ext-specialchar.js"></script >
|
|
* ```
|
|
*
|
|
* ### use a plugin in toolbar
|
|
* ```
|
|
* $("#editor").summernote({
|
|
* ...
|
|
* toolbar : [
|
|
* ['group', [ 'specialChar' ]]
|
|
* ]
|
|
* ...
|
|
* });
|
|
* ```
|
|
*/
|
|
$.summernote.addPlugin({
|
|
/** @property {String} name name of plugin */
|
|
name: 'specialChar',
|
|
/**
|
|
* @property {Object} buttons
|
|
* @property {function(object): string} buttons.specialChar
|
|
*/
|
|
buttons: {
|
|
specialChar: function (lang, options) {
|
|
return tmpl.iconButton(options.iconPrefix + 'circle-o ' + options.iconPrefix, {
|
|
event: 'showSpecialCharDialog',
|
|
title: lang.specialChar.specialChar,
|
|
hide: true
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @property {Object} dialogs
|
|
* @property {function(object, object): string} dialogs.specialChar
|
|
*/
|
|
dialogs: {
|
|
specialChar: function (lang) {
|
|
var body = '<div class="form-group row-fluid">' +
|
|
makeSpecialCharSetTable()[0].outerHTML +
|
|
'</div>';
|
|
return tmpl.dialog('note-specialchar-dialog', lang.specialChar.select, body);
|
|
}
|
|
},
|
|
/**
|
|
* @property {Object} events
|
|
* @property {Function} events.showSpecialCharDialog
|
|
*/
|
|
events: {
|
|
showSpecialCharDialog: function (event, editor, layoutInfo) {
|
|
var $dialog = layoutInfo.dialog(),
|
|
$editable = layoutInfo.editable(),
|
|
currentSpecialChar = getTextOnRange($editable);
|
|
|
|
// save current range
|
|
editor.saveRange($editable);
|
|
|
|
showSpecialCharDialog($editable, $dialog, currentSpecialChar).then(function (selectChar) {
|
|
// when ok button clicked
|
|
|
|
// restore range
|
|
editor.restoreRange($editable);
|
|
|
|
// build node
|
|
var $node = $('<span></span>').html(selectChar)[0];
|
|
//var $node = $(selectChar)[0];
|
|
|
|
if ($node) {
|
|
// insert character node
|
|
editor.insertNode($editable, $node);
|
|
}
|
|
}).fail(function () {
|
|
// when cancel button clicked
|
|
editor.restoreRange($editable);
|
|
});
|
|
}
|
|
},
|
|
|
|
// define language
|
|
langs: {
|
|
'en-US': {
|
|
specialChar: {
|
|
specialChar: 'Special Characters',
|
|
select: 'Select Special characters'
|
|
}
|
|
},
|
|
'ko-KR': {
|
|
specialChar: {
|
|
specialChar: '특수문자',
|
|
select: '특수문자를 선택하세요'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|