500 lines
13 KiB
JavaScript
500 lines
13 KiB
JavaScript
/*
|
|
* Axelor Business Solutions
|
|
*
|
|
* Copyright (C) 2005-2019 Axelor (<http://axelor.com>).
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
(function() {
|
|
|
|
"use strict";
|
|
|
|
var ui = angular.module('axelor.ui');
|
|
|
|
function getStylePopup(element, styles) {
|
|
|
|
return function($popup, $button) {
|
|
|
|
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
|
|
|
|
$.each(styles, function(format, name) {
|
|
var $link = $('<a/>').attr('href', '#').html(name).click(
|
|
function(event) {
|
|
$(element).wysiwyg('shell').format(format).closePopup();
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
return false;
|
|
});
|
|
$list.append($link);
|
|
});
|
|
|
|
$popup.append($list);
|
|
};
|
|
}
|
|
|
|
function getFontNamePopup(element, fonts) {
|
|
|
|
return function($popup, $button) {
|
|
|
|
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
|
|
|
|
$.each(fonts, function(font, name) {
|
|
var $link = $('<a/>').attr('href', '#').html(name).click(
|
|
function(event) {
|
|
try {
|
|
document.execCommand('styleWithCSS', false, false);
|
|
$(element).wysiwyg('shell').fontName(font).closePopup();
|
|
} finally {
|
|
document.execCommand('styleWithCSS', false, true);
|
|
}
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
return false;
|
|
});
|
|
$list.append($link);
|
|
});
|
|
|
|
$popup.append($list);
|
|
};
|
|
}
|
|
|
|
function getFontSizePopup(element, sizes) {
|
|
|
|
return function($popup, $button) {
|
|
|
|
var $list = $('<div/>').addClass('wysiwyg-plugin-list').attr('unselectable', 'on');
|
|
|
|
$.each(sizes, function(size, name) {
|
|
var $link = $('<a/>').attr('href', '#').html(name).click(
|
|
function(event) {
|
|
$(element).focus();
|
|
try {
|
|
document.execCommand('styleWithCSS', false, false);
|
|
$(element).wysiwyg('shell').fontSize(size).closePopup();
|
|
} finally {
|
|
document.execCommand('styleWithCSS', false, true);
|
|
}
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
return false;
|
|
});
|
|
$list.append($link);
|
|
});
|
|
|
|
$popup.append($list);
|
|
};
|
|
}
|
|
|
|
function getButtons(scope, element) {
|
|
|
|
var lite = scope.field.lite;
|
|
|
|
return {
|
|
style: lite ? false : {
|
|
title: _t('Style'),
|
|
image: '\uf1dd',
|
|
popup: getStylePopup(element, {
|
|
'<p>' : _t('Normal'),
|
|
'<pre>' : '<pre>' + _t('Formatted') + '</pre>',
|
|
'<blockquote>' : '<blockquote>' + _t('Blockquote') + '</blockquote>',
|
|
'<h1>': '<h1>' + _t('Header 1') + '</h1>',
|
|
'<h2>': '<h2>' + _t('Header 2') + '</h2>',
|
|
'<h3>': '<h3>' + _t('Header 3') + '</h3>',
|
|
'<h4>': '<h4>' + _t('Header 4') + '</h4>',
|
|
'<h5>': '<h5>' + _t('Header 5') + '</h5>',
|
|
'<h6>': '<h6>' + _t('Header 6') + '</h6>'
|
|
})
|
|
},
|
|
fontName: lite ? false : {
|
|
title: _t('Font'),
|
|
image: '\uf031',
|
|
popup: getFontNamePopup(element, {
|
|
'"Times New Roman", Times, serif': '<span style="font-family: \"Times New Roman\", Times, serif">Times New Roman</span>',
|
|
'Arial, Helvetica, sans-serif': '<span style="font-family: Arial, Helvetica, sans-serif">Arial</span>',
|
|
'"Courier New", Courier, monospace': '<span style="font-family: \"Courier New\", Courier, monospace">Courier New</span>',
|
|
'Comic Sans, Comic Sans MS, cursive': '<span style="font-family: Comic Sans, Comic Sans MS, cursive">Comic Sans</span>',
|
|
'Impact, fantasy': '<span style="font-family: Impact, fantasy">Impact</span>'
|
|
})
|
|
},
|
|
fontSize: lite ? false : {
|
|
title: _t('Font size'),
|
|
image: '\uf035',
|
|
popup: getFontSizePopup(element, {
|
|
'1': '<span style="font-size: x-small">' + _t('Smaller') + '</span>',
|
|
'2': '<span style="font-size: small">' + _t('Small') + '</span>',
|
|
'3': '<span style="font-size: medium">' + _t('Medium') + '</span>',
|
|
'4': '<span style="font-size: large">' + _t('Large') + '</span>',
|
|
'5': '<span style="font-size: x-large">' + _t('Larger') + '</span>'
|
|
})
|
|
},
|
|
d1: lite ? false : {
|
|
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
|
},
|
|
bold: {
|
|
title: _t('Bold (Ctrl+B)'),
|
|
image: '\uf032'
|
|
},
|
|
italic: {
|
|
title: _t('Italic (Ctrl+I)'),
|
|
image: '\uf033'
|
|
},
|
|
underline: {
|
|
title: _t('Underline (Ctrl+U)'),
|
|
image: '\uf0cd'
|
|
},
|
|
strikethrough: {
|
|
title: _t('Strikethrough (Ctrl+S)'),
|
|
image: '\uf0cc'
|
|
},
|
|
removeformat: {
|
|
title: _t('Remove format'),
|
|
image: '\uf12d'
|
|
},
|
|
d2: {
|
|
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
|
},
|
|
forecolor: lite ? false : {
|
|
title: _t('Text color'),
|
|
image: '\uf1fc'
|
|
},
|
|
highlight: lite ? false : {
|
|
title: _t('Background color'),
|
|
image: '\uf043'
|
|
},
|
|
d3: lite ? false : {
|
|
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
|
},
|
|
alignleft: {
|
|
title: _t('Left'),
|
|
image: '\uf036'
|
|
},
|
|
aligncenter: {
|
|
title: _t('Center'),
|
|
image: '\uf037'
|
|
},
|
|
alignright: {
|
|
title: _t('Right'),
|
|
image: '\uf038'
|
|
},
|
|
alignjustify: {
|
|
title: _t('Justify'),
|
|
image: '\uf039'
|
|
},
|
|
d4: {
|
|
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
|
},
|
|
orderedList: {
|
|
title: _t('Ordered list'),
|
|
image: '\uf0cb'
|
|
},
|
|
unorderedList: {
|
|
title: _t('Unordered list'),
|
|
image: '\uf0ca'
|
|
},
|
|
d6: lite ? false : {
|
|
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
|
},
|
|
indent: lite ? false : {
|
|
title: _t('Indent'),
|
|
image: '\uf03c'
|
|
},
|
|
outdent: lite ? false : {
|
|
title: _t('Outdent'),
|
|
image: '\uf03b'
|
|
},
|
|
d7: lite ? false : {
|
|
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
|
},
|
|
insertimage: lite ? false : {
|
|
title: _t('Insert image'),
|
|
image: '\uf030'
|
|
},
|
|
insertlink: lite ? false : {
|
|
title: _t('Insert link'),
|
|
image: '\uf08e'
|
|
},
|
|
d8: lite ? false : {
|
|
html: $('<span class="wysiwyg-toolbar-divider"></span>')
|
|
},
|
|
normalize: lite ? false : {
|
|
title: _t('Normalize'),
|
|
image: '\uf0d0',
|
|
click: function () {
|
|
scope.normalize();
|
|
}
|
|
},
|
|
showCode: lite ? false : {
|
|
title: _t('Code'),
|
|
image: '\uf121',
|
|
click: function () {
|
|
scope.toggleCode();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
ui.formInput('Html', {
|
|
|
|
css: "html-item",
|
|
metaWidget: true,
|
|
|
|
init: function(scope) {
|
|
|
|
scope.parse = function(value) {
|
|
return value ? value : null;
|
|
};
|
|
},
|
|
|
|
link_editable: function(scope, element, attrs, model) {
|
|
this._super(scope, element, attrs, model);
|
|
var textElement = element.find('textarea');
|
|
var buttons = getButtons(scope, textElement);
|
|
|
|
var props = scope.field,
|
|
minSize = +props.minSize,
|
|
maxSize = +props.maxSize,
|
|
height = +(scope.field.height) || null;
|
|
|
|
if (height) {
|
|
height = Math.max(100, height);
|
|
}
|
|
|
|
function isParagraph(node) {
|
|
return node && /^DIV|^P|^LI|^H[1-7]/.test(node.nodeName.toUpperCase());
|
|
}
|
|
|
|
function findParagraph(node) {
|
|
if (!node) return null;
|
|
if (node.classList && node.classList.contains('wysiwyg-editor')) return null;
|
|
if (isParagraph(node)) return node;
|
|
return findParagraph(node.parentNode);
|
|
}
|
|
|
|
textElement.wysiwyg({
|
|
toolbar: 'top',
|
|
buttons: buttons,
|
|
submit: {
|
|
title: _t('Submit'),
|
|
image: '\uf00c'
|
|
},
|
|
selectImage: _t('Click or drop image'),
|
|
placeholderUrl: 'www.example.com',
|
|
maxImageSize: [600, 200],
|
|
hijackContextmenu: false,
|
|
onKeyPress: function(key, character, shiftKey, altKey, ctrlKey, metaKey) {
|
|
if (key !== 13 || shiftKey) {
|
|
return;
|
|
}
|
|
var parent = findParagraph(document.getSelection().anchorNode);
|
|
if (!parent) {
|
|
document.execCommand('formatBlock', false, '<p>');
|
|
}
|
|
}
|
|
});
|
|
|
|
var shell = textElement.wysiwyg('shell');
|
|
var shellElement = $(shell.getElement());
|
|
var shellActive = true;
|
|
|
|
shellElement.addClass('html-content');
|
|
|
|
if (scope.field.height) {
|
|
shellElement.parent().height(height).resizable({
|
|
handles: 's',
|
|
resize: function () {
|
|
shellElement.parent().width('');
|
|
}
|
|
});
|
|
}
|
|
|
|
function onChange(e) {
|
|
|
|
var value = shellActive ? shell.getHTML() : textElement.val();
|
|
var text = (value || '').trim()
|
|
|
|
if (text === '<p><br></p>' || text === '<br>') {
|
|
value = null;
|
|
}
|
|
|
|
var old = scope.getValue() || null;
|
|
var txt = scope.parse(value) || null;
|
|
|
|
if (old === txt) {
|
|
return;
|
|
}
|
|
|
|
scope.setValue(value, true);
|
|
scope.$applyAsync();
|
|
}
|
|
|
|
scope.$render_editable = function () {
|
|
var value = scope.getValue() || "";
|
|
scope.text = scope.format(value);
|
|
|
|
var current = shellActive ? shell : textElement;
|
|
var getter = shellActive ? 'getHTML' : 'val';
|
|
var setter = shellActive ? 'setHTML' : 'val';
|
|
|
|
var html = current[getter]();
|
|
if (value !== html) {
|
|
current[setter](value);
|
|
}
|
|
};
|
|
|
|
textElement.on('input paste change blur', _.debounce(onChange, 100));
|
|
|
|
textElement.on("focus", _.once(function (e) {
|
|
|
|
// Chrome and Edge supports this
|
|
document.execCommand('defaultParagraphSeparator', false, 'p');
|
|
|
|
// firefox uses attributes for some commands
|
|
document.execCommand('styleWithCSS', false, true);
|
|
document.execCommand('insertBrOnReturn', false, false);
|
|
}));
|
|
|
|
scope.toggleCode = function () {
|
|
|
|
shellActive = !shellActive;
|
|
|
|
element.parent().find('.wysiwyg-toolbar-icon')
|
|
.toggleClass('disabled', !shellActive)
|
|
.last().removeClass('disabled');
|
|
|
|
if (shellActive) {
|
|
textElement.hide();
|
|
shellElement.show();
|
|
shell.setHTML(textElement.val());
|
|
} else {
|
|
var height = Math.max(100, shellElement.outerHeight());
|
|
shellElement.hide();
|
|
textElement.show();
|
|
|
|
if (!scope.field.height) {
|
|
textElement.height(height);
|
|
}
|
|
}
|
|
};
|
|
|
|
scope.normalize = function () {
|
|
|
|
var html = shell.getHTML();
|
|
var div = $('<div>').html(html);
|
|
|
|
div.find('p').css({
|
|
'margin-top': 0,
|
|
'margin-bottom': '1em'});
|
|
|
|
div.find('ol,ul').each(function() {
|
|
var el = $(this);
|
|
if (el.parents('ol,ul').length) return;
|
|
el.css({
|
|
'margin-top': 0,
|
|
'margin-bottom': '1em'});
|
|
});
|
|
|
|
div.find('blockquote').each(function() {
|
|
var el = $(this);
|
|
el.css({
|
|
'margin': el.parents('blockquote').length ? '0 0 0 2em' : '0 0 1em 2em',
|
|
'border': 'none',
|
|
'padding': 0
|
|
});
|
|
});
|
|
|
|
shellElement.focus();
|
|
shell.setHTML(div[0].innerHTML);
|
|
|
|
div.remove();
|
|
};
|
|
|
|
scope.validate = function(value) {
|
|
if (_.isEmpty(value)) {
|
|
return true;
|
|
}
|
|
var length = value.length,
|
|
valid = true;
|
|
|
|
if (minSize) {
|
|
valid = length >= minSize;
|
|
}
|
|
if(valid && maxSize) {
|
|
valid = length <= maxSize;
|
|
}
|
|
|
|
return valid;
|
|
};
|
|
|
|
},
|
|
|
|
template_readonly:
|
|
'<div class="form-item-container">'+
|
|
'<div class="html-viewer html-content" ui-bind-template x-text="text" x-locals="record" x-live="field.live"></div>'+
|
|
'</div>',
|
|
template_editable:
|
|
'<div class="form-item-container">'+
|
|
'<textarea class="html-editor html-content"></textarea>'+
|
|
'</div>'
|
|
|
|
});
|
|
|
|
ui.directive('uiBindTemplate', ['$interpolate', function($interpolate){
|
|
|
|
function expand(scope, template) {
|
|
if (!template || !template.match(/{{.*?}}/)) {
|
|
return template;
|
|
}
|
|
return $interpolate(template)(scope.locals());
|
|
}
|
|
|
|
return {
|
|
terminal: true,
|
|
scope: {
|
|
locals: "&",
|
|
text: "=text",
|
|
live: "&"
|
|
},
|
|
link: function(scope, element, attrs) {
|
|
|
|
var template = null;
|
|
|
|
function update() {
|
|
var output = expand(scope, template) || "";
|
|
element.html(output);
|
|
}
|
|
|
|
scope.$watch("text", function(text, old) {
|
|
|
|
if (text === template) {
|
|
return;
|
|
}
|
|
template = text;
|
|
update();
|
|
});
|
|
|
|
var live = false;
|
|
scope.$watch("live()", function(value) {
|
|
if (live || !value) {
|
|
return;
|
|
}
|
|
live = true;
|
|
scope.$watch("locals()", function(value) {
|
|
update();
|
|
}, true);
|
|
});
|
|
}
|
|
};
|
|
}]);
|
|
|
|
})();
|