mirror of
https://github.com/mattermost/focalboard.git
synced 2025-01-26 18:48:15 +02:00
All team users autocomplete new (#3016)
* WIP * Made search debounced * Lint fiX * Fixed tests * Calledn un-debounced version on first load
This commit is contained in:
parent
72eb7fa5a4
commit
859bc53cbb
@ -15,6 +15,8 @@ import {mockDOM, wrapDNDIntl, mockStateStore} from '../../testUtils'
|
|||||||
|
|
||||||
import {Utils} from '../../utils'
|
import {Utils} from '../../utils'
|
||||||
|
|
||||||
|
import {TestBlockFactory} from "../../test/testBlockFactory"
|
||||||
|
|
||||||
import TextElement from './textElement'
|
import TextElement from './textElement'
|
||||||
|
|
||||||
jest.mock('../../utils')
|
jest.mock('../../utils')
|
||||||
@ -41,6 +43,9 @@ describe('components/content/TextElement', () => {
|
|||||||
mockDOM()
|
mockDOM()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const board1 = TestBlockFactory.createBoard()
|
||||||
|
board1.id = 'board-id-1'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
users: {
|
users: {
|
||||||
boardUsers: {
|
boardUsers: {
|
||||||
@ -51,6 +56,12 @@ describe('components/content/TextElement', () => {
|
|||||||
5: {username: 'g'},
|
5: {username: 'g'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
boards: {
|
||||||
|
current: 'board-id-1',
|
||||||
|
boards: {
|
||||||
|
[board1.id]: board1,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const store = mockStateStore([], state)
|
const store = mockStateStore([], state)
|
||||||
|
|
||||||
|
@ -65,6 +65,9 @@ describe('components/contentBlock', () => {
|
|||||||
addBlock: jest.fn(),
|
addBlock: jest.fn(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const board1 = TestBlockFactory.createBoard()
|
||||||
|
board1.id = 'board-id-1'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
users: {
|
users: {
|
||||||
boardUsers: {
|
boardUsers: {
|
||||||
@ -75,6 +78,12 @@ describe('components/contentBlock', () => {
|
|||||||
5: {username: 'g'},
|
5: {username: 'g'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
boards: {
|
||||||
|
current: 'board-id-1',
|
||||||
|
boards: {
|
||||||
|
[board1.id]: board1,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const store = mockStateStore([], state)
|
const store = mockStateStore([], state)
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ import {Provider as ReduxProvider} from 'react-redux'
|
|||||||
|
|
||||||
import {mockDOM, wrapDNDIntl, mockStateStore} from '../testUtils'
|
import {mockDOM, wrapDNDIntl, mockStateStore} from '../testUtils'
|
||||||
|
|
||||||
|
import {TestBlockFactory} from "../test/testBlockFactory"
|
||||||
|
|
||||||
import {MarkdownEditor} from './markdownEditor'
|
import {MarkdownEditor} from './markdownEditor'
|
||||||
|
|
||||||
jest.mock('../utils')
|
jest.mock('../utils')
|
||||||
@ -16,6 +18,10 @@ jest.mock('draft-js/lib/generateRandomKey', () => () => '123')
|
|||||||
describe('components/markdownEditor', () => {
|
describe('components/markdownEditor', () => {
|
||||||
beforeAll(mockDOM)
|
beforeAll(mockDOM)
|
||||||
beforeEach(jest.clearAllMocks)
|
beforeEach(jest.clearAllMocks)
|
||||||
|
|
||||||
|
const board1 = TestBlockFactory.createBoard()
|
||||||
|
board1.id = 'board-id-1'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
users: {
|
users: {
|
||||||
boardUsers: {
|
boardUsers: {
|
||||||
@ -26,6 +32,12 @@ describe('components/markdownEditor', () => {
|
|||||||
5: {username: 'g'},
|
5: {username: 'g'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
boards: {
|
||||||
|
current: 'board-id-1',
|
||||||
|
boards: {
|
||||||
|
[board1.id]: board1,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const store = mockStateStore([], state)
|
const store = mockStateStore([], state)
|
||||||
test('should match snapshot', async () => {
|
test('should match snapshot', async () => {
|
||||||
|
@ -3,10 +3,7 @@
|
|||||||
import Editor from '@draft-js-plugins/editor'
|
import Editor from '@draft-js-plugins/editor'
|
||||||
import createEmojiPlugin from '@draft-js-plugins/emoji'
|
import createEmojiPlugin from '@draft-js-plugins/emoji'
|
||||||
import '@draft-js-plugins/emoji/lib/plugin.css'
|
import '@draft-js-plugins/emoji/lib/plugin.css'
|
||||||
import createMentionPlugin, {
|
import createMentionPlugin from '@draft-js-plugins/mention'
|
||||||
defaultSuggestionsFilter,
|
|
||||||
MentionData,
|
|
||||||
} from '@draft-js-plugins/mention'
|
|
||||||
import '@draft-js-plugins/mention/lib/plugin.css'
|
import '@draft-js-plugins/mention/lib/plugin.css'
|
||||||
import {ContentState, DraftHandleValue, EditorState, getDefaultKeyBinding} from 'draft-js'
|
import {ContentState, DraftHandleValue, EditorState, getDefaultKeyBinding} from 'draft-js'
|
||||||
import React, {
|
import React, {
|
||||||
@ -15,6 +12,8 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
|
||||||
|
import {debounce} from "lodash"
|
||||||
|
|
||||||
import {useAppSelector} from '../../store/hooks'
|
import {useAppSelector} from '../../store/hooks'
|
||||||
import {IUser} from '../../user'
|
import {IUser} from '../../user'
|
||||||
import {getBoardUsersList} from '../../store/users'
|
import {getBoardUsersList} from '../../store/users'
|
||||||
@ -22,10 +21,20 @@ import createLiveMarkdownPlugin from '../live-markdown-plugin/liveMarkdownPlugin
|
|||||||
|
|
||||||
import './markdownEditorInput.scss'
|
import './markdownEditorInput.scss'
|
||||||
|
|
||||||
|
import {BoardTypeOpen} from "../../blocks/board"
|
||||||
|
import {getCurrentBoard} from "../../store/boards"
|
||||||
|
import octoClient from "../../octoClient"
|
||||||
|
|
||||||
import Entry from './entryComponent/entryComponent'
|
import Entry from './entryComponent/entryComponent'
|
||||||
|
|
||||||
const imageURLForUser = (window as any).Components?.imageURLForUser
|
const imageURLForUser = (window as any).Components?.imageURLForUser
|
||||||
|
|
||||||
|
type MentionUser = {
|
||||||
|
name: string
|
||||||
|
avatar: string
|
||||||
|
is_bot: boolean
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onChange?: (text: string) => void
|
onChange?: (text: string) => void
|
||||||
onFocus?: () => void
|
onFocus?: () => void
|
||||||
@ -38,9 +47,38 @@ type Props = {
|
|||||||
const MarkdownEditorInput = (props: Props): ReactElement => {
|
const MarkdownEditorInput = (props: Props): ReactElement => {
|
||||||
const {onChange, onFocus, onBlur, initialText, id, isEditing} = props
|
const {onChange, onFocus, onBlur, initialText, id, isEditing} = props
|
||||||
const boardUsers = useAppSelector<IUser[]>(getBoardUsersList)
|
const boardUsers = useAppSelector<IUser[]>(getBoardUsersList)
|
||||||
const mentions: MentionData[] = useMemo(() => boardUsers.map((user) => ({name: user.username, avatar: `${imageURLForUser ? imageURLForUser(user.id) : ''}`, is_bot: user.is_bot})), [boardUsers])
|
const board = useAppSelector(getCurrentBoard)
|
||||||
const ref = useRef<Editor>(null)
|
const ref = useRef<Editor>(null)
|
||||||
|
|
||||||
|
const [suggestions, setSuggestions] = useState<Array<MentionUser>>([])
|
||||||
|
|
||||||
|
const loadSuggestions = async (term: string) => {
|
||||||
|
let users: Array<IUser>
|
||||||
|
|
||||||
|
if (board && board.type === BoardTypeOpen) {
|
||||||
|
users = await octoClient.searchTeamUsers(term)
|
||||||
|
} else {
|
||||||
|
users = boardUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
const mentions = users.map(
|
||||||
|
(user) => ({
|
||||||
|
name: user.username,
|
||||||
|
avatar: `${imageURLForUser ? imageURLForUser(user.id) : ''}`,
|
||||||
|
is_bot: user.is_bot}
|
||||||
|
))
|
||||||
|
setSuggestions(mentions)
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedLoadSuggestion = useMemo(() => debounce(loadSuggestions, 200), [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Get the ball rolling. Searching for empty string
|
||||||
|
// returns first 10 users in alphabetical order.
|
||||||
|
loadSuggestions('')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
const generateEditorState = (text?: string) => {
|
const generateEditorState = (text?: string) => {
|
||||||
const state = EditorState.createWithContent(ContentState.createFromText(text || ''))
|
const state = EditorState.createWithContent(ContentState.createFromText(text || ''))
|
||||||
return EditorState.moveSelectionToEnd(state)
|
return EditorState.moveSelectionToEnd(state)
|
||||||
@ -67,7 +105,6 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
|
|||||||
|
|
||||||
const [isMentionPopoverOpen, setIsMentionPopoverOpen] = useState(false)
|
const [isMentionPopoverOpen, setIsMentionPopoverOpen] = useState(false)
|
||||||
const [isEmojiPopoverOpen, setIsEmojiPopoverOpen] = useState(false)
|
const [isEmojiPopoverOpen, setIsEmojiPopoverOpen] = useState(false)
|
||||||
const [suggestions, setSuggestions] = useState(mentions)
|
|
||||||
|
|
||||||
const {MentionSuggestions, plugins, EmojiSuggestions} = useMemo(() => {
|
const {MentionSuggestions, plugins, EmojiSuggestions} = useMemo(() => {
|
||||||
const mentionPlugin = createMentionPlugin({mentionPrefix: '@'})
|
const mentionPlugin = createMentionPlugin({mentionPrefix: '@'})
|
||||||
@ -144,8 +181,8 @@ const MarkdownEditorInput = (props: Props): ReactElement => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const onSearchChange = useCallback(({value}: { value: string }) => {
|
const onSearchChange = useCallback(({value}: { value: string }) => {
|
||||||
setSuggestions(defaultSuggestionsFilter(value, mentions))
|
debouncedLoadSuggestion(value)
|
||||||
}, [mentions])
|
}, [suggestions])
|
||||||
|
|
||||||
let className = 'MarkdownEditorInput'
|
let className = 'MarkdownEditorInput'
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user