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