Merge pull request #2664 from jespino/issue-2617
Adding file size limit
This commit is contained in:
commit
dc72e3d3bd
20 changed files with 167 additions and 44 deletions
|
@ -219,6 +219,7 @@ func (p *Plugin) createBoardsConfig(mmconfig mmModel.Config, baseURL string, ser
|
|||
FilesDriver: *mmconfig.FileSettings.DriverName,
|
||||
FilesPath: *mmconfig.FileSettings.Directory,
|
||||
FilesS3Config: filesS3Config,
|
||||
MaxFileSize: *mmconfig.FileSettings.MaxFileSize,
|
||||
Telemetry: enableTelemetry,
|
||||
TelemetryID: serverID,
|
||||
WebhookUpdate: []string{},
|
||||
|
|
|
@ -1769,8 +1769,16 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if a.app.GetConfig().MaxFileSize > 0 {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, a.app.GetConfig().MaxFileSize)
|
||||
}
|
||||
|
||||
file, handle, err := r.FormFile(UploadFormFileKey)
|
||||
if err != nil {
|
||||
if strings.HasSuffix(err.Error(), "http: request body too large") {
|
||||
a.errorResponse(w, r.URL.Path, http.StatusRequestEntityTooLarge, "", err)
|
||||
return
|
||||
}
|
||||
a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@ func (a *App) SetConfig(config *config.Configuration) {
|
|||
a.config = config
|
||||
}
|
||||
|
||||
func (a *App) GetConfig() *config.Configuration {
|
||||
return a.config
|
||||
}
|
||||
|
||||
func New(config *config.Configuration, wsAdapter ws.Adapter, services Services) *App {
|
||||
app := &App{
|
||||
config: config,
|
||||
|
|
|
@ -384,6 +384,11 @@ func (th *TestHelper) CheckForbidden(r *client.Response) {
|
|||
require.Error(th.T, r.Error)
|
||||
}
|
||||
|
||||
func (th *TestHelper) CheckRequestEntityTooLarge(r *client.Response) {
|
||||
require.Equal(th.T, http.StatusRequestEntityTooLarge, r.StatusCode)
|
||||
require.Error(th.T, r.Error)
|
||||
}
|
||||
|
||||
func (th *TestHelper) CheckNotImplemented(r *client.Response) {
|
||||
require.Equal(th.T, http.StatusNotImplemented, r.StatusCode)
|
||||
require.Error(th.T, r.Error)
|
||||
|
|
71
server/integrationtests/file_test.go
Normal file
71
server/integrationtests/file_test.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package integrationtests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUploadFile(t *testing.T) {
|
||||
const (
|
||||
testTeamID = "team-id"
|
||||
)
|
||||
|
||||
t.Run("a non authenticated user should be rejected", func(t *testing.T) {
|
||||
th := SetupTestHelper(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
th.Logout(th.Client)
|
||||
|
||||
file, resp := th.Client.TeamUploadFile(testTeamID, "test-board-id", bytes.NewBuffer([]byte("test")))
|
||||
th.CheckUnauthorized(resp)
|
||||
require.Nil(t, file)
|
||||
})
|
||||
|
||||
t.Run("upload a file to an existing team and board without permissions", func(t *testing.T) {
|
||||
th := SetupTestHelper(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
file, resp := th.Client.TeamUploadFile(testTeamID, "not-valid-board", bytes.NewBuffer([]byte("test")))
|
||||
th.CheckForbidden(resp)
|
||||
require.Nil(t, file)
|
||||
})
|
||||
|
||||
t.Run("upload a file to an existing team and board with permissions", func(t *testing.T) {
|
||||
th := SetupTestHelper(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
testBoard := th.CreateBoard(testTeamID, model.BoardTypeOpen)
|
||||
file, resp := th.Client.TeamUploadFile(testTeamID, testBoard.ID, bytes.NewBuffer([]byte("test")))
|
||||
th.CheckOK(resp)
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, file)
|
||||
require.NotNil(t, file.FileID)
|
||||
})
|
||||
|
||||
t.Run("upload a file to an existing team and board with permissions but reaching the MaxFileLimit", func(t *testing.T) {
|
||||
th := SetupTestHelper(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
testBoard := th.CreateBoard(testTeamID, model.BoardTypeOpen)
|
||||
|
||||
config := th.Server.App().GetConfig()
|
||||
config.MaxFileSize = 1
|
||||
th.Server.App().SetConfig(config)
|
||||
|
||||
file, resp := th.Client.TeamUploadFile(testTeamID, testBoard.ID, bytes.NewBuffer([]byte("test")))
|
||||
th.CheckRequestEntityTooLarge(resp)
|
||||
require.Nil(t, file)
|
||||
|
||||
config.MaxFileSize = 100000
|
||||
th.Server.App().SetConfig(config)
|
||||
|
||||
file, resp = th.Client.TeamUploadFile(testTeamID, testBoard.ID, bytes.NewBuffer([]byte("test")))
|
||||
th.CheckOK(resp)
|
||||
require.NoError(t, resp.Error)
|
||||
require.NotNil(t, file)
|
||||
require.NotNil(t, file.FileID)
|
||||
})
|
||||
}
|
|
@ -37,6 +37,7 @@ type Configuration struct {
|
|||
FilesDriver string `json:"filesdriver" mapstructure:"filesdriver"`
|
||||
FilesS3Config AmazonS3Config `json:"filess3config" mapstructure:"filess3config"`
|
||||
FilesPath string `json:"filespath" mapstructure:"filespath"`
|
||||
MaxFileSize int64 `json:"maxfilesize" mapstructure:"mafilesize"`
|
||||
Telemetry bool `json:"telemetry" mapstructure:"telemetry"`
|
||||
TelemetryID string `json:"telemetryid" mapstructure:"telemetryid"`
|
||||
PrometheusAddress string `json:"prometheus_address" mapstructure:"prometheus_address"`
|
||||
|
|
|
@ -7,6 +7,7 @@ describe('Card badges', () => {
|
|||
cy.apiResetBoards()
|
||||
cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))
|
||||
localStorage.setItem('welcomePageViewed', 'true')
|
||||
localStorage.setItem('language', 'en')
|
||||
})
|
||||
|
||||
it('Shows and hides card badges', () => {
|
||||
|
|
|
@ -7,6 +7,7 @@ describe('Card URL Property', () => {
|
|||
cy.apiResetBoards()
|
||||
cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))
|
||||
localStorage.setItem('welcomePageViewed', 'true')
|
||||
localStorage.setItem('language', 'en')
|
||||
})
|
||||
|
||||
const url = 'https://mattermost.com'
|
||||
|
|
|
@ -11,6 +11,7 @@ describe('Create and delete board / card', () => {
|
|||
cy.apiResetBoards()
|
||||
cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))
|
||||
localStorage.setItem('welcomePageViewed', 'true')
|
||||
localStorage.setItem('language', 'en')
|
||||
})
|
||||
|
||||
it('MM-T4274 Create an Empty Board', () => {
|
||||
|
@ -29,7 +30,7 @@ describe('Create and delete board / card', () => {
|
|||
// Create empty board
|
||||
cy.contains('Create empty board').should('exist').click({force: true})
|
||||
cy.get('.BoardComponent').should('exist')
|
||||
cy.get('.Editable.title').invoke('attr', 'placeholder').should('contain', 'Untitled board')
|
||||
cy.get('.Editable.title').invoke('attr', 'placeholder').should('contain', 'Untitled Board')
|
||||
|
||||
// Change Title
|
||||
cy.get('.Editable.title').
|
||||
|
|
|
@ -7,6 +7,7 @@ describe('Group board by different properties', () => {
|
|||
cy.apiResetBoards()
|
||||
cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))
|
||||
localStorage.setItem('welcomePageViewed', 'true')
|
||||
localStorage.setItem('language', 'en')
|
||||
})
|
||||
|
||||
it('MM-T4291 Group by different property', () => {
|
||||
|
|
|
@ -6,6 +6,10 @@ describe('Login actions', () => {
|
|||
const email = Cypress.env('email')
|
||||
const password = Cypress.env('password')
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.setItem('language', 'en')
|
||||
})
|
||||
|
||||
it('Can perform login/register actions', () => {
|
||||
// Redirects to login page
|
||||
cy.log('**Redirects to login page (except plugin mode) **')
|
||||
|
|
|
@ -7,6 +7,7 @@ describe('Manage groups', () => {
|
|||
cy.apiResetBoards()
|
||||
cy.apiGetMe().then((userID) => cy.apiSkipTour(userID))
|
||||
localStorage.setItem('welcomePageViewed', 'true')
|
||||
localStorage.setItem('language', 'en')
|
||||
})
|
||||
|
||||
it('MM-T4284 Adding a group', () => {
|
||||
|
|
|
@ -119,11 +119,11 @@ Cypress.Commands.add('uiCreateNewBoard', (title?: string) => {
|
|||
cy.log('**Create new empty board**')
|
||||
cy.uiCreateEmptyBoard()
|
||||
|
||||
cy.findByPlaceholderText('Untitled board').should('exist')
|
||||
cy.findByPlaceholderText('Untitled Board').should('exist')
|
||||
cy.wait(10)
|
||||
if (title) {
|
||||
cy.log('**Rename board**')
|
||||
cy.findByPlaceholderText('Untitled board').type(`${title}{enter}`)
|
||||
cy.findByPlaceholderText('Untitled Board').type(`${title}{enter}`)
|
||||
cy.findByRole('textbox', {name: title}).should('exist')
|
||||
}
|
||||
cy.wait(500)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"BoardMember.schemeAdmin": "Admin",
|
||||
"BoardMember.schemeEditor": "Editor",
|
||||
"BoardMember.schemeNone": "None",
|
||||
"BoardMember.schemeViewer": "Viewer",
|
||||
"BoardPage.newVersion": "A new version of Boards is available, click here to reload.",
|
||||
"BoardPage.syncFailed": "Board may be deleted or access revoked.",
|
||||
"BoardTemplateSelector.add-template": "New template",
|
||||
|
@ -89,6 +90,7 @@
|
|||
"Categories.CreateCategoryDialog.CreateText": "Create",
|
||||
"Categories.CreateCategoryDialog.Placeholder": "Name your category",
|
||||
"Categories.CreateCategoryDialog.UpdateText": "Update",
|
||||
"CenterPanel.Login": "Login",
|
||||
"CenterPanel.Share": "Share",
|
||||
"ColorOption.selectColor": "Select {color} Color",
|
||||
"Comment.delete": "Delete",
|
||||
|
@ -109,6 +111,10 @@
|
|||
"ContentBlock.moveDown": "Move down",
|
||||
"ContentBlock.moveUp": "Move up",
|
||||
"ContentBlock.text": "text",
|
||||
"DateRange.clear": "Clear",
|
||||
"DateRange.empty": "Empty",
|
||||
"DateRange.endDate": "End date",
|
||||
"DateRange.today": "Today",
|
||||
"DeleteBoardDialog.confirm-cancel": "Cancel",
|
||||
"DeleteBoardDialog.confirm-delete": "Delete",
|
||||
"DeleteBoardDialog.confirm-info": "Are you sure you want to delete the board “{boardTitle}”? Deleting it will delete all cards in the board.",
|
||||
|
@ -133,7 +139,6 @@
|
|||
"GalleryCard.copyLink": "Copy link",
|
||||
"GalleryCard.delete": "Delete",
|
||||
"GalleryCard.duplicate": "Duplicate",
|
||||
"General.BoardCount": "{count, plural, one {# Board} other {# Boards}}",
|
||||
"GroupBy.hideEmptyGroups": "Hide {count} empty groups",
|
||||
"GroupBy.showHiddenGroups": "Show {count} hidden groups",
|
||||
"GroupBy.ungroup": "Ungroup",
|
||||
|
@ -144,6 +149,20 @@
|
|||
"KanbanCard.untitled": "Untitled",
|
||||
"Mutator.new-card-from-template": "new card from template",
|
||||
"Mutator.new-template-from-card": "new template from card",
|
||||
"OnboardingTour.AddComments.Body": "You can comment on issues, and even @mention your fellow Mattermost users to get their attention.",
|
||||
"OnboardingTour.AddComments.Title": "Add comments",
|
||||
"OnboardingTour.AddDescription.Body": "Add a description to your card so your teammates know what the card is about.",
|
||||
"OnboardingTour.AddDescription.Title": "Add description",
|
||||
"OnboardingTour.AddProperties.Body": "Add various properties to cards to make them more powerful!",
|
||||
"OnboardingTour.AddProperties.Title": "Add properties",
|
||||
"OnboardingTour.AddView.Body": "Go here to create a new view to organise your board using different layouts.",
|
||||
"OnboardingTour.AddView.Title": "Add a new view",
|
||||
"OnboardingTour.CopyLink.Body": "You can share your cards with teammates by copying the link and pasting it in a channel, Direct Message, or Group Message.",
|
||||
"OnboardingTour.CopyLink.Title": "Copy link",
|
||||
"OnboardingTour.OpenACard.Body": "Open a card to explore the powerful ways that Boards can help you organize your work.",
|
||||
"OnboardingTour.OpenACard.Title": "Open a card",
|
||||
"OnboardingTour.ShareBoard.Body": "You can share your board internally, within your team, or publish it publicly for visibility outside of your organization.",
|
||||
"OnboardingTour.ShareBoard.Title": "Share board",
|
||||
"PropertyMenu.Delete": "Delete",
|
||||
"PropertyMenu.changeType": "Change property type",
|
||||
"PropertyMenu.selectType": "Select property type",
|
||||
|
@ -172,6 +191,8 @@
|
|||
"RegistrationLink.tokenRegenerated": "Registration link regenerated",
|
||||
"ShareBoard.PublishDescription": "Publish and share a “read only” link with everyone on the web",
|
||||
"ShareBoard.PublishTitle": "Publish to the web",
|
||||
"ShareBoard.ShareInternal": "Share internally",
|
||||
"ShareBoard.ShareInternalDescription": "Users who have permissions will be able to use this link",
|
||||
"ShareBoard.Title": "Share Board",
|
||||
"ShareBoard.confirmRegenerateToken": "This will invalidate previously shared links. Continue?",
|
||||
"ShareBoard.copiedLink": "Copied!",
|
||||
|
@ -187,18 +208,20 @@
|
|||
"Sidebar.changePassword": "Change password",
|
||||
"Sidebar.delete-board": "Delete board",
|
||||
"Sidebar.duplicate-board": "Duplicate board",
|
||||
"Sidebar.template-from-board": "New template from board",
|
||||
"Sidebar.export-archive": "Export archive",
|
||||
"Sidebar.import": "Import",
|
||||
"Sidebar.import-archive": "Import archive",
|
||||
"Sidebar.invite-users": "Invite users",
|
||||
"Sidebar.logout": "Log out",
|
||||
"Sidebar.no-boards-in-category": "No boards inside",
|
||||
"Sidebar.product-tour": "Product tour",
|
||||
"Sidebar.random-icons": "Random icons",
|
||||
"Sidebar.set-language": "Set language",
|
||||
"Sidebar.set-theme": "Set theme",
|
||||
"Sidebar.settings": "Settings",
|
||||
"Sidebar.template-from-board": "New template from board",
|
||||
"Sidebar.untitled-board": "(Untitled Board)",
|
||||
"Sidebar.untitled-view": "(Untitled View)",
|
||||
"SidebarCategories.BlocksMenu.Move": "Move To...",
|
||||
"SidebarCategories.CategoryMenu.CreateNew": "Create New Category",
|
||||
"SidebarCategories.CategoryMenu.Delete": "Delete Category",
|
||||
|
@ -217,6 +240,9 @@
|
|||
"TableHeaderMenu.sort-descending": "Sort descending",
|
||||
"TableRow.open": "Open",
|
||||
"TopBar.give-feedback": "Give Feedback",
|
||||
"URLProperty.copiedLink": "Copied!",
|
||||
"URLProperty.copy": "Copy",
|
||||
"URLProperty.edit": "Edit",
|
||||
"ValueSelector.noOptions": "No options. Start typing to add the first one!",
|
||||
"ValueSelector.valueSelector": "Value selector",
|
||||
"ValueSelectorLabel.openMenu": "Open menu",
|
||||
|
@ -257,49 +283,32 @@
|
|||
"ViewTitle.random-icon": "Random",
|
||||
"ViewTitle.remove-icon": "Remove icon",
|
||||
"ViewTitle.show-description": "show description",
|
||||
"ViewTitle.untitled-board": "Untitled board",
|
||||
"ViewTitle.untitled-board": "Untitled Board",
|
||||
"WelcomePage.Description": "Boards is a project management tool that helps define, organize, track and manage work across teams, using a familiar kanban board view",
|
||||
"WelcomePage.Explore.Button": "Take a tour",
|
||||
"WelcomePage.NoThanks.Text": "No thanks, I'll figure it out myself",
|
||||
"WelcomePage.Heading": "Welcome To Boards",
|
||||
"WelcomePage.NoThanks.Text": "No thanks, I'll figure it out myself",
|
||||
"Workspace.editing-board-template": "You're editing a board template.",
|
||||
"calendar.month": "Month",
|
||||
"calendar.today": "TODAY",
|
||||
"calendar.week": "Week",
|
||||
"createImageBlock.failed": "Unable to upload the file. File size limit reached.",
|
||||
"default-properties.badges": "Comments and Description",
|
||||
"default-properties.title": "Title",
|
||||
"error.relogin": "Log in again",
|
||||
"error.page.title": "Sorry, something went wrong",
|
||||
"generic.previous": "Previous",
|
||||
"imagePaste.upload-failed": "Some files not uploaded. File size limit reached",
|
||||
"login.log-in-button": "Log in",
|
||||
"login.log-in-title": "Log in",
|
||||
"error.workspace-undefined": "Not a valid workspace.",
|
||||
"error.page.title": "Sorry, something went wrong",
|
||||
"error.not-logged-in": "Your session may have expired or you're not logged in.",
|
||||
"error.back-to-home": "Back to Home",
|
||||
"error.back-to-boards": "Back to boards",
|
||||
"error.go-login": "Log in",
|
||||
"error.unknown": "An error occurred.",
|
||||
"login.register-button": "or create an account if you don't have one",
|
||||
"register.login-button": "or log in if you already have an account",
|
||||
"OnboardingTour.OpenACard.Title": "Open a card",
|
||||
"OnboardingTour.OpenACard.Body": "Open a card to explore the powerful ways that Boards can help you organize your work.",
|
||||
"OnboardingTour.AddProperties.Title": "Add properties",
|
||||
"OnboardingTour.AddProperties.Body": "Add various properties to cards to make them more powerful!",
|
||||
"OnboardingTour.AddComments.Title": "Add comments",
|
||||
"OnboardingTour.AddComments.Body": "You can comment on issues, and even @mention your fellow Mattermost users to get their attention.",
|
||||
"OnboardingTour.AddDescription.Title": "Add description",
|
||||
"OnboardingTour.AddDescription.Body": "Add a description to your card so your teammates know what the card is about.",
|
||||
"OnboardingTour.AddView.Title": "Add a new view",
|
||||
"OnboardingTour.AddView.Body": "Go here to create a new view to organise your board using different layouts.",
|
||||
"OnboardingTour.CopyLink.Title": "Copy link",
|
||||
"OnboardingTour.CopyLink.Body": "You can share your cards with teammates by copying the link and pasting it in a channel, Direct Message, or Group Message.",
|
||||
"OnboardingTour.ShareBoard.Title": "Share board",
|
||||
"OnboardingTour.ShareBoard.Body": "You can share your board internally, within your team, or publish it publicly for visibility outside of your organization.",
|
||||
"register.signup-title": "Sign up for your account",
|
||||
"share-board.publish": "Publish",
|
||||
"share-board.share": "Share",
|
||||
"shareBoard.lastAdmin": "Boards must have at least one Administrator",
|
||||
"tutorial_tip.finish_tour": "Done",
|
||||
"tutorial_tip.got_it": "Got it",
|
||||
"tutorial_tip.ok": "Next",
|
||||
"generic.previous": "Previous",
|
||||
"tutorial_tip.seen": "Seen this before?",
|
||||
"tutorial_tip.out": "Opt out of these tips",
|
||||
"register.signup-title": "Sign up for your account",
|
||||
"shareBoard.lastAdmin": "Boards must have at least one Administrator"
|
||||
}
|
||||
"tutorial_tip.out": "Opt out of these tips.",
|
||||
"tutorial_tip.seen": "Seen this before?"
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
"check": "eslint --ext .tsx,.ts . --quiet --cache && stylelint **/*.scss",
|
||||
"fix": "eslint --ext .tsx,.ts . --quiet --fix --cache && stylelint --fix **/*.scss",
|
||||
"fix:scss": "prettier --write './src/**/*.scss'",
|
||||
"i18n-extract": "formatjs extract ../mattermost-plugin/webapp/src/*/*/*.ts? src/*/*/*.ts? src/*/*.ts? src/*.ts? --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json",
|
||||
"i18n-extract": "formatjs extract ../mattermost-plugin/webapp/src/*/*/*.ts? src/*.ts? src/*/*.ts? src/*/*/*.ts? src/*/*/*/*.ts? --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json",
|
||||
"runserver-test": "cd cypress && \"../../bin/focalboard-server\"",
|
||||
"cypress:ci": "start-server-and-test runserver-test http://localhost:8088 cypress:run",
|
||||
"cypress:run": "cypress run",
|
||||
|
|
|
@ -37,7 +37,7 @@ const AddContentMenuItem = (props:Props): JSX.Element => {
|
|||
name={handler.getDisplayText(intl)}
|
||||
icon={handler.getIcon()}
|
||||
onClick={async () => {
|
||||
const newBlock = await handler.createBlock(card.boardId)
|
||||
const newBlock = await handler.createBlock(card.boardId, intl)
|
||||
newBlock.parentId = card.id
|
||||
newBlock.boardId = card.boardId
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ export const CardDetailProvider = (props: CardDetailProps): ReactElement => {
|
|||
})
|
||||
const {card} = props
|
||||
const addBlock = useCallback(async (handler: ContentHandler, index: number, auto: boolean) => {
|
||||
const block = await handler.createBlock(card.boardId)
|
||||
const block = await handler.createBlock(card.boardId, intl)
|
||||
block.parentId = card.id
|
||||
block.boardId = card.boardId
|
||||
const typeName = handler.getDisplayText(intl)
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useEffect, useCallback} from 'react'
|
||||
import {useIntl} from 'react-intl'
|
||||
|
||||
import {ImageBlock, createImageBlock} from '../../blocks/imageBlock'
|
||||
import {sendFlashMessage} from '../flashMessages'
|
||||
import octoClient from '../../octoClient'
|
||||
import mutator from '../../mutator'
|
||||
|
||||
export default function useImagePaste(boardId: string, cardId: string, contentOrder: Array<string | string[]>): void {
|
||||
const intl = useIntl()
|
||||
const uploadItems = useCallback(async (items: FileList) => {
|
||||
let newImage: File|null = null
|
||||
const uploads: Promise<string|undefined>[] = []
|
||||
|
@ -25,8 +28,10 @@ export default function useImagePaste(boardId: string, cardId: string, contentOr
|
|||
|
||||
const uploaded = await Promise.all(uploads)
|
||||
const blocksToInsert: ImageBlock[] = []
|
||||
let someFilesNotUploaded = false
|
||||
for (const fileId of uploaded) {
|
||||
if (!fileId) {
|
||||
someFilesNotUploaded = true
|
||||
continue
|
||||
}
|
||||
const block = createImageBlock()
|
||||
|
@ -36,6 +41,10 @@ export default function useImagePaste(boardId: string, cardId: string, contentOr
|
|||
blocksToInsert.push(block)
|
||||
}
|
||||
|
||||
if (someFilesNotUploaded) {
|
||||
sendFlashMessage({content: intl.formatMessage({id: 'imagePaste.upload-failed', defaultMessage: 'Some files not uploaded. File size limit reached'}), severity: 'normal'})
|
||||
}
|
||||
|
||||
mutator.performAsUndoGroup(async () => {
|
||||
const newContentBlocks = await mutator.insertBlocks(boardId, blocksToInsert, 'pasted images')
|
||||
const newContentOrder = JSON.parse(JSON.stringify(contentOrder))
|
||||
|
|
|
@ -11,7 +11,7 @@ export type ContentHandler = {
|
|||
type: BlockTypes,
|
||||
getDisplayText: (intl: IntlShape) => string,
|
||||
getIcon: () => JSX.Element,
|
||||
createBlock: (boardId: string) => Promise<ContentBlock>,
|
||||
createBlock: (boardId: string, intl: IntlShape) => Promise<ContentBlock>,
|
||||
createComponent: (block: ContentBlock, readonly: boolean, onAddElement?: () => void, onDeleteElement?: () => void) => JSX.Element,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {IntlShape} from 'react-intl'
|
||||
|
||||
import {ContentBlock} from '../../blocks/contentBlock'
|
||||
import {ImageBlock, createImageBlock} from '../../blocks/imageBlock'
|
||||
import octoClient from '../../octoClient'
|
||||
import {Utils} from '../../utils'
|
||||
import ImageIcon from '../../widgets/icons/image'
|
||||
import {sendFlashMessage} from '../../components/flashMessages'
|
||||
|
||||
import {contentRegistry} from './contentRegistry'
|
||||
|
||||
|
@ -44,17 +46,21 @@ const ImageElement = (props: Props): JSX.Element|null => {
|
|||
|
||||
contentRegistry.registerContentType({
|
||||
type: 'image',
|
||||
getDisplayText: (intl) => intl.formatMessage({id: 'ContentBlock.image', defaultMessage: 'image'}),
|
||||
getDisplayText: (intl: IntlShape) => intl.formatMessage({id: 'ContentBlock.image', defaultMessage: 'image'}),
|
||||
getIcon: () => <ImageIcon/>,
|
||||
createBlock: async (boardId: string) => {
|
||||
createBlock: async (boardId: string, intl: IntlShape) => {
|
||||
return new Promise<ImageBlock>(
|
||||
(resolve) => {
|
||||
Utils.selectLocalFile(async (file) => {
|
||||
const fileId = await octoClient.uploadFile(boardId, file)
|
||||
|
||||
const block = createImageBlock()
|
||||
block.fields.fileId = fileId || ''
|
||||
resolve(block)
|
||||
if (fileId) {
|
||||
const block = createImageBlock()
|
||||
block.fields.fileId = fileId || ''
|
||||
resolve(block)
|
||||
} else {
|
||||
sendFlashMessage({content: intl.formatMessage({id: 'createImageBlock.failed', defaultMessage: 'Unable to upload the file. File size limit reached.'}), severity: 'normal'})
|
||||
}
|
||||
},
|
||||
'.jpg,.jpeg,.png,.gif')
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue