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:
parent
568a5f01b6
commit
1932acb628
@ -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: "%"
|
||||||
|
@ -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",
|
||||||
|
13
webapp/package-lock.json
generated
13
webapp/package-lock.json
generated
@ -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",
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
`;
|
`;
|
||||||
|
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.
|
// 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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user