From a26a48fac776f396cd2e3d59ecd63c8e73277b91 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 6 May 2024 15:40:37 +0300 Subject: [PATCH] wysiwyg cell params --- .../Espo/Resources/i18n/en_US/Global.json | 10 +- client/src/helpers/misc/summernote-custom.js | 110 +++++++++-- client/src/views/wysiwyg/modals/edit-cell.js | 173 ++++++++++++++++++ client/src/views/wysiwyg/modals/edit-table.js | 52 ++++-- 4 files changed, 316 insertions(+), 29 deletions(-) create mode 100644 client/src/views/wysiwyg/modals/edit-cell.js diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index 6a1e2de098..d9b410d528 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -922,18 +922,26 @@ "Activities": "Activities" }, "wysiwygLabels": { + "cell": "Cell", "align": "Align", "width": "Width", + "height": "Height", "borderWidth": "Border Width", "borderColor": "Border Color", "cellPadding": "Cell Padding", - "backgroundColor": "Background Color" + "backgroundColor": "Background Color", + "verticalAlign": "Vertical Align" }, "wysiwygOptions": { "align": { "left": "Left", "center": "Center", "right": "Right" + }, + "verticalAlign": { + "top": "Top", + "middle": "Middle", + "bottom": "Bottom" } } } diff --git a/client/src/helpers/misc/summernote-custom.js b/client/src/helpers/misc/summernote-custom.js index 0c02022e16..5ca47c4451 100644 --- a/client/src/helpers/misc/summernote-custom.js +++ b/client/src/helpers/misc/summernote-custom.js @@ -28,6 +28,7 @@ import $ from 'jquery'; import EditTableModalView from 'views/wysiwyg/modals/edit-table'; +import EditCellModalView from 'views/wysiwyg/modals/edit-cell'; /** * @type {{ @@ -53,7 +54,7 @@ function init(langSets) { }, popover: { table: [ - ['custom', ['tableParams']], + ['custom', ['tableParams', 'cellParams']], ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']], ['delete', ['deleteRow', 'deleteCol', 'deleteTable']], ], @@ -61,6 +62,81 @@ function init(langSets) { }); $.extend($.summernote.plugins, { + 'cellParams': function (/** Record */context) { + const ui = $.summernote.ui; + + const options = context.options; + const view = /** @type {import('view').default} */options.espoView; + + context.memo('button.cellParams', () => { + return ui.button({ + className: '', + contents: '', + tooltip: view.translate('cell', 'wysiwygLabels'), + click: () => { + context.invoke('cellParams.show'); + }, + }).render(); + }); + + this.show = function () { + const range = context.invoke('editor.getLastRange'); + const $td = $(range.ec).closest('td,th'); + const td = /** @type {HTMLTableCellElement} */$td[0]; + + const width = td.style.width; + const height = td.style.height; + const backgroundColor = td.style.backgroundColor; + const verticalAlign = td.style.verticalAlign; + + const params = { + width, + height, + backgroundColor, + verticalAlign, + }; + + //const prevParams = params; + + const modalView = new EditCellModalView({ + params, + headerText: view.translate('cell', 'wysiwygLabels'), + onApply: params => applyParams(params), + }); + + view.assignView('dialog', modalView) + .then(() => { + modalView.render(); + }); + + /** + * @param {{ + * width: string|null, + * height: string|null, + * backgroundColor: string|null, + * verticalAlign: string|null, + * }} params + */ + const applyParams = params => { + let backgroundColor = td.style.backgroundColor; + + if (backgroundColor === 'transparent') { + backgroundColor = null; + } + + td.style.backgroundColor = params.backgroundColor; + td.style.width = params.width; + td.style.height = params.height; + td.style.verticalAlign = params.verticalAlign; + }; + }; + + this.destroy = function () { + if (view) { + view.clearView('dialog'); + } + }; + }, 'tableParams': function (/** Record */context) { const ui = $.summernote.ui; @@ -71,7 +147,7 @@ function init(langSets) { return ui.button({ className: '', contents: '', - tooltip: langSets.table.table, // @todo + tooltip: langSets.table.table, click: () => { context.invoke('tableParams.show'); }, @@ -81,7 +157,7 @@ function init(langSets) { this.show = function () { const range = context.invoke('editor.getLastRange'); const $table = $(range.ec).closest('table'); - const table = /** @type {HTMLTableElement}*/$table[0]; + const table = /** @type {HTMLTableElement} */$table[0]; let borderWidth = table.style.borderWidth; @@ -136,10 +212,12 @@ function init(langSets) { } const width = table.style.width; + const height = table.style.height; const params = { align, width, + height, borderWidth, borderColor, cellPadding, @@ -159,17 +237,18 @@ function init(langSets) { modalView.render(); }); - const applyParams = - /** { - * align: string, - * width: string, - * borderWidth: string, - * borderColor: string, - * cellPadding: string, - * backgroundColor: string, - * } */ - params => { - + /** + * @param {{ + * align: string|null, + * width: string|null, + * height: string|null, + * borderWidth: string|null, + * borderColor: string|null, + * cellPadding: string|null, + * backgroundColor: string|null, + * }} params + */ + const applyParams = params => { if (params.align === 'left') { table.style.marginLeft = '0'; table.style.marginRight = 'auto'; @@ -186,6 +265,7 @@ function init(langSets) { table.style.backgroundColor = params.backgroundColor; table.style.width = params.width; + table.style.height = params.height; if (params.borderWidth !== null || prevParams.borderWidth !== null) { table.style.borderWidth = params.borderWidth; @@ -205,7 +285,7 @@ function init(langSets) { } } - if (params.padding !== null || prevParams.padding !== null) { + if (params.cellPadding !== null || prevParams.padding !== null) { for (const /** HTMLTableCellElement */cell of table.querySelectorAll('td, th')) { cell.style.padding = params.cellPadding; } diff --git a/client/src/views/wysiwyg/modals/edit-cell.js b/client/src/views/wysiwyg/modals/edit-cell.js new file mode 100644 index 0000000000..a6923790a5 --- /dev/null +++ b/client/src/views/wysiwyg/modals/edit-cell.js @@ -0,0 +1,173 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM – Open Source CRM application. + * Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko + * Website: https://www.espocrm.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + * + * 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 Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +import ModalView from 'views/modal'; +import EditForModal from 'views/record/edit-for-modal'; +import Model from 'model'; +import VarcharFieldView from 'views/fields/varchar'; +import ColorpickerFieldView from 'views/fields/colorpicker'; +import EnumFieldView from 'views/fields/enum'; + +class EditCellModalView extends ModalView { + + templateContent = ` +
{{{record}}}
+ ` + /** + * @param {{ + * params: { + * width: null|string, + * height: null|height, + * backgroundColor: null|string, + * verticalAlign: null|'top'|'middle'|'bottom', + * }, + * onApply: function({ + * width: null|string, + * height: null|height, + * backgroundColor: null|string, + * verticalAlign: null|'top'|'middle'|'bottom', + * }), + * }} options + */ + constructor(options) { + super(options); + + this.params = options.params; + this.onApply = options.onApply; + } + + setup() { + this.addButton({ + name: 'apply', + style: 'primary', + label: 'Apply', + onClick: () => this.apply(), + }); + + this.addButton({ + name: 'cancel', + label: 'Cancel', + onClick: () => this.close(), + }); + + this.shortcutKeys = { + 'Control+Enter': () => this.apply(), + }; + + this.model = new Model({ + width: this.params.width, + height: this.params.height, + backgroundColor: this.params.backgroundColor, + verticalAlign: this.params.verticalAlign, + }); + + this.recordView = new EditForModal({ + model: this.model, + detailLayout: [ + { + rows: [ + [ + { + view: new VarcharFieldView({ + name: 'width', + labelText: this.translate('width', 'wysiwygLabels'), + params: { + maxLength: 12, + }, + }), + }, + { + view: new VarcharFieldView({ + name: 'height', + labelText: this.translate('height', 'wysiwygLabels'), + params: { + maxLength: 12, + }, + }), + }, + ], + [ + { + view: new EnumFieldView({ + name: 'verticalAlign', + labelText: this.translate('verticalAlign', 'wysiwygLabels'), + params: { + options: [ + '', + 'top', + 'middle', + 'bottom', + ], + translation: 'Global.wysiwygOptions.verticalAlign', + }, + }), + }, + { + view: new ColorpickerFieldView({ + name: 'backgroundColor', + labelText: this.translate('backgroundColor', 'wysiwygLabels'), + }), + }, + ] + ], + }, + ], + }); + + this.assignView('record', this.recordView, '.record'); + } + + apply() { + if (this.recordView.validate()) { + return; + } + + let width = this.model.attributes.width; + + if (/^\d+$/.test(width)) { + width += 'px'; + } + + let height = this.model.attributes.height; + + if (/^\d+$/.test(height)) { + height += 'px'; + } + + this.onApply({ + width: width, + height: height, + backgroundColor: this.model.attributes.backgroundColor, + verticalAlign: this.model.attributes.verticalAlign, + }); + + this.close(); + } +} + +export default EditCellModalView diff --git a/client/src/views/wysiwyg/modals/edit-table.js b/client/src/views/wysiwyg/modals/edit-table.js index e55232abac..b29867ad43 100644 --- a/client/src/views/wysiwyg/modals/edit-table.js +++ b/client/src/views/wysiwyg/modals/edit-table.js @@ -43,6 +43,7 @@ class EditTableModalView extends ModalView { * params: { * align: null|'left'|'center'|'right', * width: null|string, + * height: null|string, * borderWidth: null|string, * borderColor: null|string, * cellPadding: null|string, @@ -51,6 +52,7 @@ class EditTableModalView extends ModalView { * onApply: function({ * align: null|'left'|'center'|'right', * width: null|string, + * height: null|string, * borderWidth: null|string, * borderColor: null|string, * cellPadding: null|string, @@ -86,6 +88,7 @@ class EditTableModalView extends ModalView { this.model = new Model({ align: this.params.align, width: this.params.width, + height: this.params.height, borderWidth: this.params.borderWidth, borderColor: this.params.borderColor, cellPadding: this.params.cellPadding, @@ -99,24 +102,18 @@ class EditTableModalView extends ModalView { rows: [ [ { - view: new EnumFieldView({ - name: 'align', - labelText: this.translate('align', 'wysiwygLabels'), + view: new VarcharFieldView({ + name: 'width', + labelText: this.translate('width', 'wysiwygLabels'), params: { - options: [ - '', - 'left', - 'center', - 'right', - ], - translation: 'Global.wysiwygOptions.align', + maxLength: 12, }, }), }, { view: new VarcharFieldView({ - name: 'width', - labelText: this.translate('width', 'wysiwygLabels'), + name: 'height', + labelText: this.translate('height', 'wysiwygLabels'), params: { maxLength: 12, }, @@ -157,6 +154,24 @@ class EditTableModalView extends ModalView { }), }, ], + [ + { + view: new EnumFieldView({ + name: 'align', + labelText: this.translate('align', 'wysiwygLabels'), + params: { + options: [ + '', + 'left', + 'center', + 'right', + ], + translation: 'Global.wysiwygOptions.align', + }, + }), + }, + false + ], ], }, ], @@ -172,6 +187,8 @@ class EditTableModalView extends ModalView { let borderWidth = this.model.attributes.borderWidth; let cellPadding = this.model.attributes.cellPadding; + let width = this.model.attributes.width; + let height = this.model.attributes.height; if (/^\d+$/.test(borderWidth)) { borderWidth += 'px'; @@ -181,9 +198,18 @@ class EditTableModalView extends ModalView { cellPadding += 'px'; } + if (/^\d+$/.test(width)) { + width += 'px'; + } + + if (/^\d+$/.test(height)) { + height += 'px'; + } + this.onApply({ align: this.model.attributes.align, - width: this.model.attributes.width, + width: width, + height: height, borderWidth: borderWidth, borderColor: this.model.attributes.borderColor, cellPadding: cellPadding,