1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-11-24 08:22:29 +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:
Rajat Dabade 2024-08-07 17:03:06 +05:30 committed by GitHub
parent 568a5f01b6
commit 1932acb628
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 85 additions and 23 deletions

View File

@ -1,7 +1,7 @@
version: '2.4' version: '2.4'
services: services:
mysql: mysql:
image: "mysql/mysql-server:5.7.12" image: "mysql/mysql-server:8.0.32"
restart: always restart: always
environment: environment:
MYSQL_ROOT_HOST: "%" MYSQL_ROOT_HOST: "%"

View File

@ -133,6 +133,7 @@
"ContentBlock.editCardCheckboxText": "edit card text", "ContentBlock.editCardCheckboxText": "edit card text",
"ContentBlock.editCardText": "edit card text", "ContentBlock.editCardText": "edit card text",
"ContentBlock.editText": "Edit 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.image": "image",
"ContentBlock.insertAbove": "Insert above", "ContentBlock.insertAbove": "Insert above",
"ContentBlock.moveBlock": "move card content", "ContentBlock.moveBlock": "move card content",

View File

@ -1,6 +1,6 @@
{ {
"name": "focalboard", "name": "focalboard",
"version": "7.12.0", "version": "8.0.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
@ -18,6 +18,7 @@
"@mattermost/compass-icons": "^0.1.39", "@mattermost/compass-icons": "^0.1.39",
"@reduxjs/toolkit": "^1.8.0", "@reduxjs/toolkit": "^1.8.0",
"@tippyjs/react": "4.2.6", "@tippyjs/react": "4.2.6",
"classnames": "^2.5.1",
"color": "^4.2.1", "color": "^4.2.1",
"draft-js": "^0.11.7", "draft-js": "^0.11.7",
"emoji-mart": "^3.0.1", "emoji-mart": "^3.0.1",
@ -5106,6 +5107,11 @@
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
"dev": true "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": { "node_modules/clean-css": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",
@ -20955,6 +20961,11 @@
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==",
"dev": true "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": { "clean-css": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.0.tgz",

View File

@ -35,6 +35,7 @@
"@mattermost/compass-icons": "^0.1.39", "@mattermost/compass-icons": "^0.1.39",
"@reduxjs/toolkit": "^1.8.0", "@reduxjs/toolkit": "^1.8.0",
"@tippyjs/react": "4.2.6", "@tippyjs/react": "4.2.6",
"classnames": "^2.5.1",
"color": "^4.2.1", "color": "^4.2.1",
"draft-js": "^0.11.7", "draft-js": "^0.11.7",
"emoji-mart": "^3.0.1", "emoji-mart": "^3.0.1",

View File

@ -476,12 +476,16 @@ exports[`components/contentBlock should match snapshot with textBlock 1`] = `
style="flex: 0 0 auto; height: 100%;" style="flex: 0 0 auto; height: 100%;"
/> />
<div <div
class="MarkdownEditor octo-editor " class="TextElement"
> >
<div <div
class="octo-editor-preview" class="MarkdownEditor octo-editor "
data-testid="preview-element" >
/> <div
class="octo-editor-preview"
data-testid="preview-element"
/>
</div>
</div> </div>
</div> </div>
<div <div

View File

@ -3,12 +3,16 @@
exports[`components/content/TextElement return a textElement 1`] = ` exports[`components/content/TextElement return a textElement 1`] = `
<div> <div>
<div <div
class="MarkdownEditor octo-editor " class="TextElement"
> >
<div <div
class="octo-editor-preview octo-placeholder" class="MarkdownEditor octo-editor "
data-testid="preview-element" >
/> <div
class="octo-editor-preview octo-placeholder"
data-testid="preview-element"
/>
</div>
</div> </div>
</div> </div>
`; `;

View 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;
}
}

View File

@ -1,8 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react' import React, {useState} from 'react'
import {useIntl} from 'react-intl' import {useIntl} from 'react-intl'
import cx from 'classnames'
import {ContentBlock} from '../../blocks/contentBlock' import {ContentBlock} from '../../blocks/contentBlock'
import {createTextBlock} from '../../blocks/textBlock' import {createTextBlock} from '../../blocks/textBlock'
import mutator from '../../mutator' import mutator from '../../mutator'
@ -11,26 +13,52 @@ import {MarkdownEditor} from '../markdownEditor'
import {contentRegistry} from './contentRegistry' import {contentRegistry} from './contentRegistry'
import './textElement.scss'
type Props = { type Props = {
block: ContentBlock block: ContentBlock
readonly: boolean readonly: boolean
} }
const TextElement = (props: Props): JSX.Element => { const BlockTitleMaxBytes = 65535 // Maximum size of a TEXT column in MySQL
const {block, readonly} = props const BlockTitleMaxRunes = BlockTitleMaxBytes / 4 // Assume a worst-case representation
const TextElement = ({block, readonly}: Props): JSX.Element => {
const intl = useIntl() 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 ( return (
<MarkdownEditor <div className='TextElement'>
text={block.title} <MarkdownEditor
placeholderText={intl.formatMessage({id: 'ContentBlock.editText', defaultMessage: 'Edit text...'})} className={cx({'markdown-editor-error': isError})}
onBlur={(text) => { text={blockTitle}
if (text !== block.title) { placeholderText={intl.formatMessage({id: 'ContentBlock.editText', defaultMessage: 'Edit text...'})}
mutator.changeBlockTitle(block.boardId, block.id, block.title, text, intl.formatMessage({id: 'ContentBlock.editCardText', defaultMessage: 'edit card text'})) onChange={textChangedHandler}
} onBlur={handleBlur}
}} readonly={readonly}
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>
) )
} }