Merge branch 'master' into boards-playbooks-i18n-sync
Этот коммит содержится в:
@@ -102,12 +102,11 @@
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-transition-group": "4.4.4",
|
||||
"@types/redux-mock-store": "1.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.16.0",
|
||||
"@typescript-eslint/parser": "5.16.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.57.1",
|
||||
"@typescript-eslint/parser": "5.57.1",
|
||||
"babel-eslint": "10.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "6.7.1",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-import-resolver-webpack": "0.13.2",
|
||||
"eslint-plugin-babel": "^5.3.1",
|
||||
"eslint-plugin-formatjs": "4.9.0",
|
||||
|
||||
@@ -873,7 +873,7 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
|
||||
>
|
||||
<div
|
||||
class="octo-board-header-cell KanbanColumnHeader"
|
||||
draggable="true"
|
||||
draggable="false"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<span
|
||||
@@ -901,7 +901,7 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
|
||||
</div>
|
||||
<div
|
||||
class="octo-board-header-cell KanbanColumnHeader"
|
||||
draggable="true"
|
||||
draggable="false"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<span
|
||||
@@ -1927,7 +1927,7 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
|
||||
>
|
||||
<div
|
||||
class="octo-board-header-cell KanbanColumnHeader"
|
||||
draggable="true"
|
||||
draggable="false"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<span
|
||||
@@ -1955,7 +1955,7 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
|
||||
</div>
|
||||
<div
|
||||
class="octo-board-header-cell KanbanColumnHeader"
|
||||
draggable="true"
|
||||
draggable="false"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -29,7 +29,14 @@ jest.mock('react-router-dom', () => {
|
||||
return {
|
||||
...originalModule,
|
||||
useRouteMatch: jest.fn(() => {
|
||||
return {url: '/board/view'}
|
||||
return {
|
||||
params: {
|
||||
teamId: 'team1',
|
||||
boardId: 'boardId1',
|
||||
viewId: 'viewId1',
|
||||
cardId: 'cardId1',
|
||||
},
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -469,7 +469,7 @@ exports[`src/components/kanban/kanbanColumnHeader should match snapshot readonly
|
||||
<div>
|
||||
<div
|
||||
class="octo-board-header-cell KanbanColumnHeader"
|
||||
draggable="true"
|
||||
draggable="false"
|
||||
style="opacity: 1;"
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -52,8 +52,12 @@ const defaultProperty: IPropertyTemplate = {
|
||||
export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||
const intl = useIntl()
|
||||
const {board, activeView, group, groupByProperty} = props
|
||||
let readonly = props.readonly
|
||||
if(!readonly){
|
||||
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||
}
|
||||
|
||||
const [groupTitle, setGroupTitle] = useState(group.option.value)
|
||||
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||
const canEditOption = groupByProperty?.type !== 'person' && group.option.id
|
||||
|
||||
const headerRef = useRef<HTMLDivElement>(null)
|
||||
@@ -79,7 +83,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||
setGroupTitle(group.option.value)
|
||||
}, [group.option.value])
|
||||
|
||||
if (canEditBoardProperties) {
|
||||
if (!readonly) {
|
||||
drop(drag(headerRef))
|
||||
}
|
||||
|
||||
@@ -97,7 +101,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||
ref={headerRef}
|
||||
style={{opacity: isDragging ? 0.5 : 1}}
|
||||
className={className}
|
||||
draggable={!props.readonly && canEditBoardProperties}
|
||||
draggable={!readonly}
|
||||
>
|
||||
{!group.option.id &&
|
||||
<Label
|
||||
@@ -133,7 +137,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||
onCancel={() => {
|
||||
setGroupTitle(group.option.value)
|
||||
}}
|
||||
readonly={props.readonly || !canEditBoardProperties}
|
||||
readonly={readonly}
|
||||
spellCheck={true}
|
||||
/>
|
||||
</Label>}
|
||||
@@ -145,7 +149,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||
onMenuClose={props.onCalculationMenuClose}
|
||||
onMenuOpen={props.onCalculationMenuOpen}
|
||||
cardProperties={board.cardProperties}
|
||||
readonly={props.readonly || !canEditBoardProperties}
|
||||
readonly={readonly}
|
||||
onChange={(data: {calculation: string, propertyId: string}) => {
|
||||
if (data.calculation === calculationValue && data.propertyId === calculationProperty.id) {
|
||||
return
|
||||
|
||||
@@ -983,11 +983,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Regene
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost:8065/team/team-id/shared/1/1?r=anotherToken"
|
||||
href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=anotherToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost:8065/team/team-id/shared/1/1?r=anotherToken
|
||||
http://localhost:8065/boards/public/team/team-id/shared/1/1?r=anotherToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
@@ -3188,11 +3188,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoard, and click switc
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost:8065/team/team-id/shared/1/1?r=oneToken"
|
||||
href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost:8065/team/team-id/shared/1/1?r=oneToken
|
||||
http://localhost:8065/boards/public/team/team-id/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
@@ -3461,11 +3461,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoardComponent and cli
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost:8065/team/team-id/shared/1/1?r=aToken"
|
||||
href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=aToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost:8065/team/team-id/shared/1/1?r=aToken
|
||||
http://localhost:8065/boards/public/team/team-id/shared/1/1?r=aToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
|
||||
@@ -259,7 +259,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
||||
viewId: match.params.viewId,
|
||||
teamId: match.params.teamId,
|
||||
})
|
||||
shareUrl.pathname = newPath
|
||||
shareUrl.pathname = `/boards/public${newPath}`
|
||||
|
||||
const boardPath = generatePath('/team/:teamId/:boardId/:viewId', {
|
||||
boardId: match.params.boardId,
|
||||
|
||||
@@ -21,10 +21,12 @@ jest.mock('react-router-dom', () => {
|
||||
...originalModule,
|
||||
useRouteMatch: jest.fn(() => {
|
||||
return {
|
||||
teamId: 'team1',
|
||||
boardId: 'boardId1',
|
||||
viewId: 'viewId1',
|
||||
cardId: 'cardId1',
|
||||
params: {
|
||||
teamId: 'team1',
|
||||
boardId: 'boardId1',
|
||||
viewId: 'viewId1',
|
||||
cardId: 'cardId1',
|
||||
},
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React, {useCallback} from 'react'
|
||||
import {FormattedMessage} from 'react-intl'
|
||||
import {generatePath, useRouteMatch, useHistory} from 'react-router-dom'
|
||||
import {generatePath, useRouteMatch} from 'react-router-dom'
|
||||
|
||||
import Button from 'src/widgets/buttons/button'
|
||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient'
|
||||
@@ -12,14 +12,17 @@ import './shareBoardLoginButton.scss'
|
||||
|
||||
const ShareBoardLoginButton = () => {
|
||||
const match = useRouteMatch<{teamId: string, boardId: string, viewId?: string, cardId?: string}>()
|
||||
const history = useHistory()
|
||||
|
||||
const redirectQueryParam = 'r=' + encodeURIComponent(generatePath('/:boardId?/:viewId?/:cardId?', match.params))
|
||||
const loginPath = '/login?' + redirectQueryParam
|
||||
// Mattermost login doesn't respect the redirect query parameter
|
||||
// if the user is already logged in, so we send the user to the
|
||||
// board and if they are not logged in, the webapp will take care
|
||||
// of the redirection
|
||||
const baseURL = window.location.href.split('/boards/public')[0]
|
||||
const loginPath = `${baseURL}/${generatePath('/boards/team/:teamId/:boardId?/:viewId?/:cardId?', match.params)}`
|
||||
|
||||
const onLoginClick = useCallback(() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardLogin)
|
||||
history.push(loginPath)
|
||||
location.assign(loginPath)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
@@ -52,19 +52,22 @@ const TeamPermissionsRow = (): JSX.Element => {
|
||||
}
|
||||
}
|
||||
|
||||
let currentRoleName = intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'})
|
||||
if (board.type === BoardTypeOpen && board.minimumRole === MemberRole.Admin) {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})
|
||||
} else if (board.type === BoardTypeOpen && board.minimumRole === MemberRole.Editor) {
|
||||
if (board.isTemplate) {
|
||||
let currentRoleName = intl.formatMessage({id: 'BoardMember.schemeAdmin', defaultMessage: 'Admin'})
|
||||
if (board.type === BoardTypeOpen) {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})
|
||||
if (board.minimumRole === MemberRole.Editor) {
|
||||
if (board.isTemplate) {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})
|
||||
} else {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})
|
||||
}
|
||||
} else if (board.minimumRole === MemberRole.Commenter) {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})
|
||||
} else if (board.minimumRole === MemberRole.Viewer) {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})
|
||||
} else {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeEditor', defaultMessage: 'Editor'})
|
||||
}
|
||||
} else if (board.type === BoardTypeOpen && board.minimumRole === MemberRole.Commenter) {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeCommenter', defaultMessage: 'Commenter'})
|
||||
} else if (board.type === BoardTypeOpen && board.minimumRole === MemberRole.Viewer) {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeViewer', defaultMessage: 'Viewer'})
|
||||
} else {
|
||||
currentRoleName = intl.formatMessage({id: 'BoardMember.schemeNone', defaultMessage: 'None'})
|
||||
}
|
||||
|
||||
const confirmationDialog = (
|
||||
|
||||
@@ -59,9 +59,12 @@ const ViewHeader = (props: Props) => {
|
||||
const [showFilter, setShowFilter] = useState(false)
|
||||
const [lockFilterOnClose, setLockFilterOnClose] = useState(false)
|
||||
const intl = useIntl()
|
||||
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||
|
||||
const {board, activeView, views, groupByProperty, cards, dateDisplayProperty} = props
|
||||
let readonly = props.readonly
|
||||
if(!readonly){
|
||||
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||
}
|
||||
|
||||
const withGroupBy = activeView.fields.viewType === 'board' || activeView.fields.viewType === 'table'
|
||||
const withDisplayBy = activeView.fields.viewType === 'calendar'
|
||||
@@ -137,7 +140,7 @@ const ViewHeader = (props: Props) => {
|
||||
}}
|
||||
onChange={setViewTitle}
|
||||
saveOnEsc={true}
|
||||
readonly={props.readonly || !canEditBoardProperties}
|
||||
readonly={readonly}
|
||||
spellCheck={true}
|
||||
autoExpand={false}
|
||||
/>
|
||||
@@ -148,7 +151,7 @@ const ViewHeader = (props: Props) => {
|
||||
board={board}
|
||||
activeView={activeView}
|
||||
views={views}
|
||||
readonly={props.readonly || !canEditBoardProperties}
|
||||
readonly={readonly}
|
||||
allowCreateView={allowCreateView}
|
||||
/>
|
||||
</MenuWrapper>
|
||||
@@ -159,7 +162,7 @@ const ViewHeader = (props: Props) => {
|
||||
|
||||
<div className='octo-spacer'/>
|
||||
|
||||
{!props.readonly && canEditBoardProperties &&
|
||||
{!readonly &&
|
||||
<>
|
||||
{/* Card properties */}
|
||||
|
||||
|
||||
@@ -34,9 +34,11 @@ const ViewTitle = (props: Props) => {
|
||||
}, [board.id, board.icon])
|
||||
const onShowDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), true), [board.id, board.showDescription])
|
||||
const onHideDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), false), [board.id, board.showDescription])
|
||||
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||
|
||||
const readonly = props.readonly || !canEditBoardProperties
|
||||
let readonly = props.readonly
|
||||
if(!readonly){
|
||||
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||
}
|
||||
|
||||
const intl = useIntl()
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ jest.mock('react-router-dom', () => {
|
||||
useRouteMatch: jest.fn(() => {
|
||||
return {
|
||||
params: {
|
||||
teamId: board.teamId,
|
||||
boardId: board.id,
|
||||
viewId: activeView.id,
|
||||
},
|
||||
|
||||
@@ -12,13 +12,12 @@ export const useHasPermissions = (teamId: string, boardId: string, permissions:
|
||||
return false
|
||||
}
|
||||
|
||||
const member = useAppSelector(getMyBoardMembership(boardId))
|
||||
const board = useAppSelector(getBoard(boardId))
|
||||
|
||||
if (!board) {
|
||||
return false
|
||||
}
|
||||
|
||||
const member = useAppSelector(getMyBoardMembership(boardId))
|
||||
if (!member) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ windowAny.baseURL = '/plugins/boards'
|
||||
windowAny.frontendBaseURL = '/boards'
|
||||
|
||||
import App from 'src/app'
|
||||
import PublicApp, {publicBaseURL} from 'src/public/app'
|
||||
|
||||
import store from 'src/store'
|
||||
import WithWebSockets from 'src/components/withWebSockets'
|
||||
@@ -175,10 +176,44 @@ const MainApp = (props: Props) => {
|
||||
)
|
||||
}
|
||||
|
||||
const PublicMainApp = () => {
|
||||
useEffect(() => {
|
||||
document.body.classList.add('focalboard-body')
|
||||
const root = document.getElementById('root')
|
||||
if (root) {
|
||||
while (root.firstElementChild) {
|
||||
if( root.firstElementChild.id === 'focalboard-app'){
|
||||
break
|
||||
}
|
||||
root.removeChild(root.firstElementChild)
|
||||
}
|
||||
root.classList.add('focalboard-plugin-root')
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.body.classList.remove('focalboard-body')
|
||||
if (root) {
|
||||
root.classList.remove('focalboard-plugin-root')
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<ReduxProvider store={store}>
|
||||
<div id='focalboard-app'>
|
||||
<PublicApp />
|
||||
</div>
|
||||
<div id='focalboard-root-portal' />
|
||||
</ReduxProvider>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
const HeaderComponent = () => {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<GlobalHeader history={browserHistory}/>
|
||||
<GlobalHeader history={browserHistory} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
@@ -243,8 +278,6 @@ export default class Plugin {
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const initCurrentChannelId = mmStore.getState().entities.channels.currentChannelId
|
||||
const initCurrentChannel = mmStore.getState().entities.channels.channels[initCurrentChannelId]
|
||||
let lastViewedChannelId = initCurrentChannelId
|
||||
@@ -274,7 +307,9 @@ export default class Plugin {
|
||||
prevTeamId = currentTeamId
|
||||
store.dispatch(setTeam(currentTeamId))
|
||||
octoClient.teamId = currentTeamId
|
||||
store.dispatch(initialLoad())
|
||||
if(!window.location.pathname.includes(publicBaseURL())){
|
||||
store.dispatch(initialLoad())
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTeamId && currentTeamId !== prevTeamId) {
|
||||
@@ -336,6 +371,9 @@ export default class Plugin {
|
||||
HeaderComponent,
|
||||
() => null,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
PublicMainApp,
|
||||
)
|
||||
|
||||
const goToFocalboardTemplate = () => {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`properties/number should match snapshot for number with empty value 1`] = `
|
||||
<div>
|
||||
<input
|
||||
class="Editable octo-propertyvalue"
|
||||
placeholder=""
|
||||
style="width: 100%;"
|
||||
title=""
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
70
webapp/boards/src/properties/number/number.test.tsx
Обычный файл
70
webapp/boards/src/properties/number/number.test.tsx
Обычный файл
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {ComponentProps} from 'react'
|
||||
import {screen} from '@testing-library/react'
|
||||
import {mocked} from 'jest-mock'
|
||||
|
||||
import {setup, wrapIntl} from 'src/testUtils'
|
||||
import {TestBlockFactory} from 'src/test/testBlockFactory'
|
||||
import mutator from 'src/mutator'
|
||||
|
||||
import {Board, IPropertyTemplate} from 'src/blocks/board'
|
||||
import {Card} from 'src/blocks/card'
|
||||
|
||||
import NumberProperty from './property'
|
||||
import NumberEditor from './number'
|
||||
|
||||
jest.mock('src/components/flashMessages')
|
||||
jest.mock('src/mutator')
|
||||
|
||||
const mockedMutator = mocked(mutator)
|
||||
|
||||
describe('properties/number', () => {
|
||||
let board: Board
|
||||
let card: Card
|
||||
let propertyTemplate: IPropertyTemplate
|
||||
let baseProps: ComponentProps<typeof NumberEditor>
|
||||
|
||||
beforeEach(() => {
|
||||
board = TestBlockFactory.createBoard()
|
||||
card = TestBlockFactory.createCard()
|
||||
propertyTemplate = board.cardProperties[0]
|
||||
|
||||
baseProps = {
|
||||
property: new NumberProperty(),
|
||||
card,
|
||||
board,
|
||||
propertyTemplate,
|
||||
propertyValue: '',
|
||||
readOnly: false,
|
||||
showEmptyPlaceholder: false,
|
||||
}
|
||||
})
|
||||
|
||||
it('should match snapshot for number with empty value', () => {
|
||||
const {container} = setup(
|
||||
wrapIntl((
|
||||
<NumberEditor
|
||||
{...baseProps}
|
||||
/>
|
||||
))
|
||||
)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('should fire change event when valid number value is entered', async () => {
|
||||
const {user} = setup(
|
||||
wrapIntl(
|
||||
<NumberEditor
|
||||
{...baseProps}
|
||||
/>
|
||||
)
|
||||
)
|
||||
const value = '42'
|
||||
const input = screen.getByRole('textbox')
|
||||
await user.type(input, `${value}{Enter}`)
|
||||
|
||||
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, `${value}`)
|
||||
})
|
||||
})
|
||||
@@ -10,7 +10,7 @@ const Number = (props: PropertyProps): JSX.Element => {
|
||||
return (
|
||||
<BaseTextEditor
|
||||
{...props}
|
||||
validator={() => !isNaN(parseInt(props.propertyValue as string, 10))}
|
||||
validator={() => props.propertyValue === '' || !isNaN(parseInt(props.propertyValue as string, 10))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
61
webapp/boards/src/public/app.tsx
Обычный файл
61
webapp/boards/src/public/app.tsx
Обычный файл
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import {Router, Switch} from 'react-router-dom'
|
||||
|
||||
import {IntlProvider} from 'react-intl'
|
||||
import {DndProvider} from 'react-dnd'
|
||||
import {HTML5Backend} from 'react-dnd-html5-backend'
|
||||
import {TouchBackend} from 'react-dnd-touch-backend'
|
||||
import {createBrowserHistory} from 'history'
|
||||
|
||||
import BoardPage from 'src/pages/boardPage/boardPage'
|
||||
import FBRoute from 'src/route'
|
||||
|
||||
import {getMessages} from 'src/i18n'
|
||||
import FlashMessages from 'src/components/flashMessages'
|
||||
import NewVersionBanner from 'src/components/newVersionBanner'
|
||||
import {Utils} from 'src/utils'
|
||||
import {getLanguage} from 'src/store/language'
|
||||
import {useAppSelector} from 'src/store/hooks'
|
||||
|
||||
export const publicBaseURL = () => {
|
||||
return Utils.getFrontendBaseURL() + '/public'
|
||||
}
|
||||
|
||||
const PublicRouter = () => {
|
||||
const history = createBrowserHistory({basename: publicBaseURL()})
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<FBRoute path={['/team/:teamId/shared/:boardId?/:viewId?/:cardId?', '/shared/:boardId?/:viewId?/:cardId?']}>
|
||||
<BoardPage readonly={true}/>
|
||||
</FBRoute>
|
||||
</Switch>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
const PublicApp = (): JSX.Element => {
|
||||
const language = useAppSelector<string>(getLanguage)
|
||||
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={language.split(/[_]/)[0]}
|
||||
messages={getMessages(language)}
|
||||
>
|
||||
<DndProvider backend={Utils.isMobile() ? TouchBackend : HTML5Backend}>
|
||||
<FlashMessages milliseconds={2000}/>
|
||||
<div id='frame'>
|
||||
<div id='main'>
|
||||
<NewVersionBanner/>
|
||||
<PublicRouter/>
|
||||
</div>
|
||||
</div>
|
||||
</DndProvider>
|
||||
</IntlProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(PublicApp)
|
||||
4
webapp/boards/src/types/mattermost-webapp/index.d.ts
поставляемый
4
webapp/boards/src/types/mattermost-webapp/index.d.ts
поставляемый
@@ -14,7 +14,9 @@ export interface PluginRegistry {
|
||||
registerCustomRoute(route: string, component: ReactResolvable): any
|
||||
registerProductRoute(route: string, component: ReactResolvable): any
|
||||
unregisterComponent(componentId: string): any
|
||||
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string, mainComponent: ReactResolvable, headerCentreComponent: ReactResolvable, headerRightComponent: ReactResolvable, showTeamSidebar: boolean): any
|
||||
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string,
|
||||
mainComponent: ReactResolvable, headerCentreComponent: ReactResolvable, headerRightComponent: ReactResolvable,
|
||||
showTeamSidebar: boolean, showAppBar: boolean, wrapped: boolean, publicComponent: ReactResolvable | null): any
|
||||
registerPostWillRenderEmbedComponent(match: (embed: {type: string, data: any}) => void, component: any, toggleable: boolean): any
|
||||
registerWebSocketEventHandler(event: string, handler: (e: any) => void): any
|
||||
unregisterWebSocketEventHandler(event: string): any
|
||||
|
||||
Ссылка в новой задаче
Block a user