diff --git a/client/res/templates/wysiwyg/modals/insert-image.tpl b/client/res/templates/wysiwyg/modals/insert-image.tpl
new file mode 100644
index 0000000000..d7b18453a3
--- /dev/null
+++ b/client/res/templates/wysiwyg/modals/insert-image.tpl
@@ -0,0 +1,18 @@
+
+
+
diff --git a/client/res/templates/wysiwyg/modals/insert-link.tpl b/client/res/templates/wysiwyg/modals/insert-link.tpl
new file mode 100644
index 0000000000..08ebdb4e4c
--- /dev/null
+++ b/client/res/templates/wysiwyg/modals/insert-link.tpl
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/client/src/views/fields/wysiwyg.js b/client/src/views/fields/wysiwyg.js
index 80f0c585fa..0e6541361b 100644
--- a/client/src/views/fields/wysiwyg.js
+++ b/client/src/views/fields/wysiwyg.js
@@ -47,6 +47,10 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
setup: function () {
Dep.prototype.setup.call(this);
+ if ($.summernote.options && !('espoImage' in $.summernote.options)) {
+ this.initEspoPlugin();
+ }
+
this.hasBodyPlainField = !!~this.getFieldManager().getEntityTypeFieldList(this.model.entityType).indexOf(this.name + 'Plain');
if ('height' in this.params) {
@@ -66,7 +70,7 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']],
- ['table', ['table', 'link', 'picture', 'hr']],
+ ['table', ['table', 'espoLink', 'espoImage', 'hr']],
['misc',['codeview', 'fullscreen']]
];
@@ -147,7 +151,7 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
return data;
},
- getValueForDisplay: function () {
+ getValueForDisplay: function () {
var value = Dep.prototype.getValueForDisplay.call(this);
return this.sanitizeHtml(value);
},
@@ -349,8 +353,14 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
this.$summernote.find('style').remove();
this.$summernote.find('link[ref="stylesheet"]').remove();
+ var keyMap = Espo.Utils.cloneDeep($.summernote.options.keyMap);
+ keyMap.pc['CTRL+K'] = 'espoLink.show';
+ keyMap.mac['CMD+K'] = 'espoLink.show';
+
var options = {
+ espoView: this,
lang: this.getConfig().get('language'),
+ keyMap: keyMap,
callbacks: {
onImageUpload: function (files) {
var file = files[0];
@@ -389,7 +399,7 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
},
toolbar: this.toolbar,
buttons: this.buttons,
- dialogsInBody: $('body'),
+ dialogsInBody: this.$el,
codeviewFilter: true,
};
@@ -526,6 +536,122 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
attachFile: function () {
var $form = this.$el.closest('.record');
$form.find('.field[data-name="'+this.params.attachmentField+'"] input.file').click();
- }
+ },
+
+ initEspoPlugin: function () {
+ var langSets = this.getLanguage().get('Global', 'sets', 'summernote') || {
+ image: {},
+ link: {},
+ video: {},
+ };
+ $.extend($.summernote.options, {
+ espoImage: {
+ icon: '',
+ tooltip: langSets.image.image,
+ },
+ espoLink: {
+ icon: '',
+ tooltip: langSets.link.link,
+ },
+ });
+
+ $.extend($.summernote.plugins, {
+ 'espoImage': function (context) {
+ var ui = $.summernote.ui;
+ var options = context.options;
+
+ var self = options.espoView;
+
+ this.lang = options.langInfo;
+
+ context.memo('button.espoImage', function () {
+ var button = ui.button({
+ contents: options.espoImage.icon,
+ tooltip: options.espoImage.tooltip,
+ click: function (e) {
+ context.invoke('espoImage.show');
+ },
+ });
+ return button.render();
+ });
+
+ this.initialize = function () {
+ };
+
+ this.destroy = function () {
+ self.clearView('insertImageDialog');
+ }
+
+ this.show = function () {
+ self.createView('insertImageDialog', 'views/wysiwyg/modals/insert-image', {
+ labels: {
+ insert: this.lang.image.insert,
+ url: this.lang.image.url,
+ selectFromFiles: this.lang.image.selectFromFiles,
+ },
+ }, function (view) {
+ view.render();
+
+ self.listenToOnce(view, 'upload', function (target) {
+ self.$summernote.summernote('insertImagesOrCallback', target);
+ });
+ self.listenToOnce(view, 'insert', function (target) {
+ self.$summernote.summernote('insertImage', target);
+ });
+ });
+ }
+ },
+
+ 'espoLink': function (context) {
+ var ui = $.summernote.ui;
+ var options = context.options;
+
+ var self = options.espoView;
+
+ this.lang = options.langInfo;
+
+ var isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
+
+ context.memo('button.espoLink', function () {
+ var button = ui.button({
+ contents: options.espoLink.icon,
+ tooltip: options.espoLink.tooltip + ' (' + (isMacLike ? 'CMD+K': 'CTRL+K') +')',
+ click: function (e) {
+ context.invoke('espoLink.show');
+ },
+ });
+ return button.render();
+ });
+
+ this.initialize = function () {
+ };
+
+ this.destroy = function () {
+ self.clearView('dialogInsertLink');
+ }
+
+ this.show = function () {
+ var linkInfo = context.invoke('editor.getLinkInfo');
+
+ self.createView('dialogInsertLink', 'views/wysiwyg/modals/insert-link', {
+ labels: {
+ insert: this.lang.link.insert,
+ openInNewWindow: this.lang.link.openInNewWindow,
+ url: this.lang.link.url,
+ textToDisplay: this.lang.link.textToDisplay,
+ },
+ linkInfo: linkInfo,
+ }, function (view) {
+ view.render();
+
+ self.listenToOnce(view, 'insert', function (data) {
+ self.$summernote.summernote('createLink', data);
+ });
+ });
+ }
+ },
+
+ });
+ },
});
});
diff --git a/client/src/views/wysiwyg/modals/insert-image.js b/client/src/views/wysiwyg/modals/insert-image.js
new file mode 100644
index 0000000000..8b769bfc73
--- /dev/null
+++ b/client/src/views/wysiwyg/modals/insert-image.js
@@ -0,0 +1,94 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: https://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+
+define('views/wysiwyg/modals/insert-image', 'views/modal', function (Dep) {
+
+ return Dep.extend({
+
+ backdrop: true,
+
+ template: 'wysiwyg/modals/insert-image',
+
+ events: {
+ 'click [data-action="insert"]': function () {
+ this.actionInsert();
+ },
+ 'input [data-name="url"]': function () {
+ this.toggleInsertButton();
+ },
+ 'paste [data-name="url"]': function () {
+ this.toggleInsertButton();
+ },
+ },
+
+ data: function () {
+ return {
+ labels: this.options.labels || {},
+ };
+ },
+
+ setup: function () {
+ var labels = this.options.labels || {};
+
+ var insertLabel = this.getHelper().escapeString(labels.insert);
+
+ this.headerHtml = insertLabel;
+
+ this.buttonList = [];
+ },
+
+ afterRender: function () {
+ var $files = this.$el.find('[data-name="files"]');
+
+ $files.replaceWith(
+ $files.clone().on('change', function (e) {
+ this.trigger('upload', e.target.files || e.target.value);
+ this.close();
+ }.bind(this)).val('')
+ );
+ },
+
+ toggleInsertButton: function () {
+ var value = this.$el.find('[data-name="url"]').val().trim();
+
+ var $button = this.$el.find('[data-name="insert"]')
+ if (value) {
+ $button.removeClass('disabled').removeAttr('disabled');
+ } else {
+ $button.addClass('disabled').attr('disabled', 'disabled');
+ }
+ },
+
+ actionInsert: function () {
+ var url = this.$el.find('[data-name="url"]').val().trim();
+
+ this.trigger('insert', url);
+ this.close();
+ },
+ });
+});
\ No newline at end of file
diff --git a/client/src/views/wysiwyg/modals/insert-link.js b/client/src/views/wysiwyg/modals/insert-link.js
new file mode 100644
index 0000000000..c813ceb45b
--- /dev/null
+++ b/client/src/views/wysiwyg/modals/insert-link.js
@@ -0,0 +1,116 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: https://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+
+define('views/wysiwyg/modals/insert-link', 'views/modal', function (Dep) {
+
+ return Dep.extend({
+
+ backdrop: true,
+
+ template: 'wysiwyg/modals/insert-link',
+
+ events: {
+ 'input [data-name="url"]': function () {
+ this.controlInputs();
+ },
+ 'paste [data-name="url"]': function () {
+ this.controlInputs();
+ },
+ },
+
+ data: function () {
+ return {
+ labels: this.options.labels || {},
+ };
+ },
+
+ setup: function () {
+ var labels = this.options.labels || {};
+
+ var insertLabel = this.getHelper().escapeString(labels.insert);
+
+ this.headerHtml = insertLabel;
+
+ this.buttonList = [
+ {
+ name: 'insert',
+ html: this.translate('Insert'),
+ style: 'primary',
+ disabled: true,
+ }
+ ];
+
+ this.linkInfo = this.options.linkInfo || {};
+
+ if (this.linkInfo.url) {
+ this.enableButton('insert');
+ }
+ },
+
+ afterRender: function () {
+ this.$url = this.$el.find('[data-name="url"]');
+ this.$text = this.$el.find('[data-name="text"]');
+ this.$openInNewWindow = this.$el.find('[data-name="openInNewWindow"]');
+
+ var linkInfo = this.linkInfo;
+
+ this.$url.val(linkInfo.url || '');
+ this.$text.val(linkInfo.text || '');
+
+ if ('isNewWindow' in linkInfo) {
+ this.$openInNewWindow.get(0).checked = !!linkInfo.isNewWindow;
+ }
+ },
+
+ controlInputs: function () {
+ var url = this.$url.val().trim();
+
+ if (url) {
+ this.enableButton('insert');
+ } else {
+ this.disableButton('insert');
+ }
+ },
+
+ actionInsert: function () {
+ var url = this.$url.val().trim();
+ var text = this.$text.val().trim();
+ var openInNewWindow = this.$openInNewWindow.get(0).checked;
+
+ var data = {
+ url: url,
+ text: text || url,
+ isNewWindow: openInNewWindow,
+ range: this.linkInfo.range,
+ };
+
+ this.trigger('insert', data);
+ this.close();
+ },
+ });
+});
\ No newline at end of file