You've already forked focalboard
							
							
				mirror of
				https://github.com/mattermost/focalboard.git
				synced 2025-10-31 00:17:42 +02:00 
			
		
		
		
	MM-58502 Error on crossing block limit on client side (#5015)
* refactor: error on crossing block limit * fix: linter * chore: updated snapshot * refactor: updated mysql/mysql-server docker version * chore: changed blob to text.length
This commit is contained in:
		| @@ -1,7 +1,7 @@ | ||||
| version: '2.4' | ||||
| services: | ||||
|   mysql: | ||||
|     image: "mysql/mysql-server:5.7.12" | ||||
|     image: "mysql/mysql-server:8.0.32" | ||||
|     restart: always | ||||
|     environment: | ||||
|       MYSQL_ROOT_HOST: "%" | ||||
|   | ||||
| @@ -133,6 +133,7 @@ | ||||
|   "ContentBlock.editCardCheckboxText": "edit card text", | ||||
|   "ContentBlock.editCardText": "edit card text", | ||||
|   "ContentBlock.editText": "Edit text...", | ||||
|   "ContentBlock.errorText": "You've exceeded the size limit for this content. Please shorten it to avoid losing data.", | ||||
|   "ContentBlock.image": "image", | ||||
|   "ContentBlock.insertAbove": "Insert above", | ||||
|   "ContentBlock.moveBlock": "move card content", | ||||
|   | ||||
							
								
								
									
										13
									
								
								webapp/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								webapp/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
| 	"name": "focalboard", | ||||
| 	"version": "7.12.0", | ||||
| 	"version": "8.0.0", | ||||
| 	"lockfileVersion": 2, | ||||
| 	"requires": true, | ||||
| 	"packages": { | ||||
| @@ -18,6 +18,7 @@ | ||||
| 				"@mattermost/compass-icons": "^0.1.39", | ||||
| 				"@reduxjs/toolkit": "^1.8.0", | ||||
| 				"@tippyjs/react": "4.2.6", | ||||
| 				"classnames": "^2.5.1", | ||||
| 				"color": "^4.2.1", | ||||
| 				"draft-js": "^0.11.7", | ||||
| 				"emoji-mart": "^3.0.1", | ||||
| @@ -5106,6 +5107,11 @@ | ||||
| 			"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"node_modules/classnames": { | ||||
| 			"version": "2.5.1", | ||||
| 			"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", | ||||
| 			"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" | ||||
| 		}, | ||||
| 		"node_modules/clean-css": { | ||||
| 			"version": "5.3.0", | ||||
| 			"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", | ||||
| @@ -20955,6 +20961,11 @@ | ||||
| 			"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"classnames": { | ||||
| 			"version": "2.5.1", | ||||
| 			"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", | ||||
| 			"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" | ||||
| 		}, | ||||
| 		"clean-css": { | ||||
| 			"version": "5.3.0", | ||||
| 			"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", | ||||
|   | ||||
| @@ -35,6 +35,7 @@ | ||||
| 		"@mattermost/compass-icons": "^0.1.39", | ||||
| 		"@reduxjs/toolkit": "^1.8.0", | ||||
| 		"@tippyjs/react": "4.2.6", | ||||
| 		"classnames": "^2.5.1", | ||||
| 		"color": "^4.2.1", | ||||
| 		"draft-js": "^0.11.7", | ||||
| 		"emoji-mart": "^3.0.1", | ||||
|   | ||||
| @@ -476,12 +476,16 @@ exports[`components/contentBlock should match snapshot with textBlock 1`] = ` | ||||
|         style="flex: 0 0 auto; height: 100%;" | ||||
|       /> | ||||
|       <div | ||||
|         class="MarkdownEditor octo-editor  " | ||||
|         class="TextElement" | ||||
|       > | ||||
|         <div | ||||
|           class="octo-editor-preview" | ||||
|           data-testid="preview-element" | ||||
|         /> | ||||
|           class="MarkdownEditor octo-editor  " | ||||
|         > | ||||
|           <div | ||||
|             class="octo-editor-preview" | ||||
|             data-testid="preview-element" | ||||
|           /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div | ||||
|   | ||||
| @@ -3,12 +3,16 @@ | ||||
| exports[`components/content/TextElement return a textElement 1`] = ` | ||||
| <div> | ||||
|   <div | ||||
|     class="MarkdownEditor octo-editor  " | ||||
|     class="TextElement" | ||||
|   > | ||||
|     <div | ||||
|       class="octo-editor-preview octo-placeholder" | ||||
|       data-testid="preview-element" | ||||
|     /> | ||||
|       class="MarkdownEditor octo-editor  " | ||||
|     > | ||||
|       <div | ||||
|         class="octo-editor-preview octo-placeholder" | ||||
|         data-testid="preview-element" | ||||
|       /> | ||||
|     </div> | ||||
|   </div> | ||||
| </div> | ||||
| `; | ||||
|   | ||||
							
								
								
									
										13
									
								
								webapp/src/components/content/textElement.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								webapp/src/components/content/textElement.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| .TextElement { | ||||
|     display: inline; | ||||
|  | ||||
|     .markdown-editor-error { | ||||
|         background-color: rgba(210, 75, 78, 0.08); | ||||
|         border: 1px solid var(--error-text); | ||||
|     } | ||||
|  | ||||
|     .error-message { | ||||
|         color: var(--dnd-indicator, rgba(210, 75, 78, 1)); | ||||
|         margin: 16px 0; | ||||
|     } | ||||
| } | ||||
| @@ -1,8 +1,10 @@ | ||||
| // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | ||||
| // See LICENSE.txt for license information. | ||||
| import React from 'react' | ||||
| import React, {useState} from 'react' | ||||
| import {useIntl} from 'react-intl' | ||||
|  | ||||
| import cx from 'classnames' | ||||
|  | ||||
| import {ContentBlock} from '../../blocks/contentBlock' | ||||
| import {createTextBlock} from '../../blocks/textBlock' | ||||
| import mutator from '../../mutator' | ||||
| @@ -11,26 +13,52 @@ import {MarkdownEditor} from '../markdownEditor' | ||||
|  | ||||
| import {contentRegistry} from './contentRegistry' | ||||
|  | ||||
| import './textElement.scss' | ||||
|  | ||||
| type Props = { | ||||
|     block: ContentBlock | ||||
|     readonly: boolean | ||||
| } | ||||
|  | ||||
| const TextElement = (props: Props): JSX.Element => { | ||||
|     const {block, readonly} = props | ||||
| const BlockTitleMaxBytes = 65535 // Maximum size of a TEXT column in MySQL | ||||
| const BlockTitleMaxRunes = BlockTitleMaxBytes / 4 // Assume a worst-case representation | ||||
|  | ||||
| const TextElement = ({block, readonly}: Props): JSX.Element => { | ||||
|     const intl = useIntl() | ||||
|  | ||||
|     const [isError, setIsError] = useState<boolean>(false) | ||||
|     const [blockTitle, setBlockTitle] = useState(block.title) | ||||
|  | ||||
|     const textChangedHandler = (text: string): void => { | ||||
|         setBlockTitle(text) | ||||
|         const textSize = text.length | ||||
|         setIsError(textSize > BlockTitleMaxRunes) | ||||
|     } | ||||
|  | ||||
|     const handleBlur = (text: string): void => { | ||||
|         if (text !== block.title || blockTitle !== block.title) { | ||||
|             const textSize = new Blob([text]).size | ||||
|             if (textSize <= BlockTitleMaxRunes) { | ||||
|                 mutator.changeBlockTitle(block.boardId, block.id, block.title, text, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card text'})). | ||||
|                     finally(() => { | ||||
|                         setIsError(false) | ||||
|                     }) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return ( | ||||
|         <MarkdownEditor | ||||
|             text={block.title} | ||||
|             placeholderText={intl.formatMessage({id: 'ContentBlock.editText', defaultMessage: 'Edit text...'})} | ||||
|             onBlur={(text) => { | ||||
|                 if (text !== block.title) { | ||||
|                     mutator.changeBlockTitle(block.boardId, block.id, block.title, text, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card text'})) | ||||
|                 } | ||||
|             }} | ||||
|             readonly={readonly} | ||||
|         /> | ||||
|         <div className='TextElement'> | ||||
|             <MarkdownEditor | ||||
|                 className={cx({'markdown-editor-error': isError})} | ||||
|                 text={blockTitle} | ||||
|                 placeholderText={intl.formatMessage({id: 'ContentBlock.editText', defaultMessage: 'Edit text...'})} | ||||
|                 onChange={textChangedHandler} | ||||
|                 onBlur={handleBlur} | ||||
|                 readonly={readonly} | ||||
|             /> | ||||
|             {isError && <div className='error-message'>{intl.formatMessage({id: 'ContentBlock.errorText', defaultMessage: 'You\'ve exceeded the size limit for this content. Please shorten it to avoid losing data.'})}</div>} | ||||
|         </div> | ||||
|     ) | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user