From e4decd8f08317ce8a927a45db6880662a5f65d69 Mon Sep 17 00:00:00 2001 From: Gagik Papikyan Date: Fri, 8 Jan 2021 00:53:51 +0300 Subject: [PATCH] Added maxLines config option to MultilineInput (#4477) * Added maxLines config option * Made maxLines_ protected * Added parentheses * fontsize fix * Enabled scrollbars at overflow of y axis * Added ellipsis handling for overflow Y * Deleted unused variable * Added an explanation comment --- core/field_multilineinput.js | 119 ++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/core/field_multilineinput.js b/core/field_multilineinput.js index 38b8db6c4..f94e11d50 100644 --- a/core/field_multilineinput.js +++ b/core/field_multilineinput.js @@ -51,10 +51,33 @@ Blockly.FieldMultilineInput = function(opt_value, opt_validator, opt_config) { * @type {SVGGElement} */ this.textGroup_ = null; + + /** + * Defines the maximum number of lines of field. + * If exceeded, scrolling functionality is enabled. + * @type {number} + * @protected + */ + this.maxLines_ = Infinity; + + /** + * Whether Y overflow is currently occuring. + * @type {boolean} + * @protected + */ + this.isOverflowedY_ = false; }; Blockly.utils.object.inherits(Blockly.FieldMultilineInput, Blockly.FieldTextInput); +/** + * @override + */ +Blockly.FieldMultilineInput.prototype.configure_ = function(config) { + Blockly.FieldMultilineInput.superClass_.configure_.call(this, config); + config.maxLines && this.setMaxLines(config.maxLines); +}; + /** * Construct a FieldMultilineInput from a JSON arg object, * dereferencing any string table references. @@ -121,17 +144,20 @@ Blockly.FieldMultilineInput.prototype.getDisplayText_ = function() { } var lines = textLines.split('\n'); textLines = ''; - for (var i = 0; i < lines.length; i++) { + var displayLinesNumber = this.isOverflowedY_ ? this.maxLines_ : lines.length; + for (var i = 0; i < displayLinesNumber; i++) { var text = lines[i]; if (text.length > this.maxDisplayLength) { // Truncate displayed string and add an ellipsis ('...'). text = text.substring(0, this.maxDisplayLength - 4) + '...'; + } else if (this.isOverflowedY_ && i === displayLinesNumber - 1) { + text = text.substring(0, text.length - 3) + '...'; } // Replace whitespace with non-breaking spaces so the text doesn't collapse. text = text.replace(/\s/g, Blockly.Field.NBSP); textLines += text; - if (i !== lines.length - 1) { + if (i !== displayLinesNumber - 1) { textLines += '\n'; } } @@ -142,6 +168,20 @@ Blockly.FieldMultilineInput.prototype.getDisplayText_ = function() { return textLines; }; +/** + * Called by setValue if the text input is valid. Updates the value of the + * field, and updates the text of the field if it is not currently being + * edited (i.e. handled by the htmlInput_). Is being redefined here to update + * overflow state of the field. + * @param {*} newValue The value to be saved. The default validator guarantees + * that this is a string. + * @protected + */ +Blockly.FieldMultilineInput.prototype.doValueUpdate_ = function(newValue) { + Blockly.FieldMultilineInput.superClass_.doValueUpdate_.call(this, newValue); + this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_; +}; + /** * Updates the text of the textElement. * @protected @@ -170,6 +210,15 @@ Blockly.FieldMultilineInput.prototype.render_ = function() { y += lineHeight; } + if (this.isBeingEdited_) { + var htmlInput = /** @type {!HTMLElement} */(this.htmlInput_); + if (this.isOverflowedY_) { + Blockly.utils.dom.addClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY'); + } else { + Blockly.utils.dom.removeClass(htmlInput, 'blocklyHtmlTextAreaInputOverflowedY'); + } + } + this.updateSize_(); if (this.isBeingEdited_) { @@ -211,6 +260,34 @@ Blockly.FieldMultilineInput.prototype.updateSize_ = function() { totalHeight += this.getConstants().FIELD_TEXT_HEIGHT + (i > 0 ? this.getConstants().FIELD_BORDER_RECT_Y_PADDING : 0); } + if (this.isBeingEdited_) { + // The default width is based on the longest line in the display text, + // but when it's being edited, width should be calculated based on the + // absolute longest line, even if it would be truncated after editing. + // Otherwise we would get wrong editor width when there are more + // lines than this.maxLines_. + var actualEditorLines = this.value_.split('\n'); + var dummyTextElement = Blockly.utils.dom.createSvgElement( + Blockly.utils.Svg.TEXT,{'class': 'blocklyText blocklyMultilineText'}); + var fontSize = this.getConstants().FIELD_TEXT_FONTSIZE; + var fontWeight = this.getConstants().FIELD_TEXT_FONTWEIGHT; + var fontFamily = this.getConstants().FIELD_TEXT_FONTFAMILY; + + for (var i = 0; i < actualEditorLines.length; i++) { + if (actualEditorLines[i].length > this.maxDisplayLength) { + actualEditorLines[i] = actualEditorLines[i].substring(0, this.maxDisplayLength); + } + dummyTextElement.textContent = actualEditorLines[i]; + var lineWidth = Blockly.utils.dom.getFastTextWidth( + dummyTextElement, fontSize, fontWeight, fontFamily); + if (lineWidth > totalWidth) { + totalWidth = lineWidth; + } + } + + var scrollbarWidth = this.htmlInput_.offsetWidth - this.htmlInput_.clientWidth; + totalWidth += scrollbarWidth; + } if (this.borderRect_) { totalHeight += this.getConstants().FIELD_BORDER_RECT_Y_PADDING * 2; totalWidth += this.getConstants().FIELD_BORDER_RECT_X_PADDING * 2; @@ -223,6 +300,21 @@ Blockly.FieldMultilineInput.prototype.updateSize_ = function() { this.positionBorderRect_(); }; +/** + * Show the inline free-text editor on top of the text. + * Overrides the default behaviour to force rerender in order to + * correct block size, based on editor text. + * @param {Event=} _opt_e Optional mouse event that triggered the field to open, + * or undefined if triggered programmatically. + * @param {boolean=} opt_quietInput True if editor should be created without + * focus. Defaults to false. + * @override + */ +Blockly.FieldMultilineInput.prototype.showEditor_ = function(_opt_e, opt_quietInput) { + Blockly.FieldMultilineInput.superClass_.showEditor_.call(this, _opt_e, opt_quietInput); + this.forceRerender(); +}; + /** * Create the text input editor widget. * @return {!HTMLTextAreaElement} The newly created text input editor. @@ -266,6 +358,26 @@ Blockly.FieldMultilineInput.prototype.widgetCreate_ = function() { return htmlInput; }; +/** + * Sets the maxLines config for this field. + * @param {number} maxLines Defines the maximum number of lines allowed, + * before scrolling functionality is enabled. + */ +Blockly.FieldMultilineInput.prototype.setMaxLines = function(maxLines) { + if (typeof maxLines === 'number' && maxLines > 0 && maxLines !== this.maxLines_) { + this.maxLines_ = maxLines; + this.forceRerender(); + } +}; + +/** + * Returns the maxLines config of this field. + * @return {number} The maxLines config value. + */ +Blockly.FieldMultilineInput.prototype.getMaxLines = function() { + return this.maxLines_; +}; + /** * Handle key down to the editor. Override the text input definition of this * so as to not close the editor when enter is typed in. @@ -289,6 +401,9 @@ Blockly.Css.register([ 'overflow: hidden;', 'height: 100%;', 'text-align: left;', + '}', + '.blocklyHtmlTextAreaInputOverflowedY {', + 'overflow-y: scroll;', '}' /* eslint-enable indent */ ]);