-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
360 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ | |
*/ | ||
|
||
export * from './image/plugin' | ||
export mention from './mention/plugin' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* Copyright (c) 2016, vace.nz (https://github.com/vacenz) | ||
* | ||
* License: MIT | ||
*/ | ||
|
||
var SearchItemInArrayObjects = function (items, input, searchKey) { | ||
if (input.trim() === '' || searchKey === undefined) { | ||
return [] | ||
} | ||
var reg = new RegExp(input.split('').join('\\w*').replace(/\W/, ''), 'i') | ||
|
||
return items.filter(function (item) { | ||
if (reg.test(item[searchKey])) { | ||
return item | ||
} | ||
}) | ||
} | ||
|
||
import React, {Component} from "react" | ||
import styled from 'styled-components' | ||
|
||
export default class extends Component { | ||
constructor (props) { | ||
super(props) | ||
this.state = { | ||
foundUsers: [] | ||
} | ||
} | ||
|
||
componentDidMount() { | ||
this.findUsers() | ||
} | ||
|
||
componentDidUpdate(prevProps, prevState) { | ||
if (prevProps.searchValue !== this.props.searchValue) { | ||
this.findUsers() | ||
} | ||
} | ||
|
||
selectAutoComplete (user) { | ||
if(typeof this.props.onClick !== 'undefined'){ | ||
this.props.onClick(user) | ||
} | ||
} | ||
|
||
findUsers () { | ||
const {mentionUsersAsync, mentionUsers, searchValue, searchKey} = this.props | ||
|
||
if (mentionUsersAsync !== undefined) { | ||
/* async */ | ||
mentionUsersAsync(searchValue) | ||
.then((result) => { | ||
this.setState({foundUsers: result.mentionUsers}) | ||
}) | ||
} else { | ||
/* static list of users */ | ||
let users = SearchItemInArrayObjects(mentionUsers, searchValue, searchKey) | ||
this.setState({foundUsers: users}) | ||
} | ||
|
||
} | ||
|
||
renderUsers () { | ||
const {foundUsers} = this.state | ||
const {searchValue} = this.props | ||
|
||
return foundUsers.map((item, i) => { | ||
let name = item.name | ||
let avatarSrc = item.avatar | ||
return ( | ||
<li key={i}> | ||
<MentionItem> | ||
<Avatar src={avatarSrc} /> | ||
<MentionName key={name} onClick={() => this.selectAutoComplete(item)}> | ||
{name} | ||
</MentionName> | ||
</MentionItem> | ||
</li> | ||
) | ||
}) | ||
} | ||
|
||
render() { | ||
const {searchValue} = this.props | ||
let menuStyle = { border: '1px solid #b7b7b7' } | ||
if (searchValue.length < 1) { menuStyle = { border: 'none' } } | ||
|
||
return ( | ||
<Search> | ||
<Menu style={menuStyle}> | ||
<List>{this.renderUsers()}</List> | ||
</Menu> | ||
</Search> | ||
) | ||
} | ||
} | ||
|
||
const Search = styled.div` | ||
margin: 0; | ||
font-weight: 200; | ||
line-height: 1.5; | ||
position: relative; | ||
width: 100%; | ||
min-width: 10rem; | ||
` | ||
|
||
const Menu = styled.div` | ||
background: white; | ||
display: block; | ||
text-decoration: none; | ||
white-space: nowrap; | ||
padding: 0; | ||
max-height: 30rem; | ||
overflow-x: hidden; | ||
overflow-y: auto; | ||
position: absolute; | ||
width: 100%; | ||
visibility: visible; | ||
z-index: 100; | ||
` | ||
|
||
const List = styled.ul` | ||
list-style-type: none; | ||
padding: 0; | ||
margin: 0; | ||
` | ||
|
||
const MentionItem = styled.div` | ||
display: flex; | ||
flex-direction: row; | ||
align-items: center; | ||
cursor: pointer; | ||
padding: 0 0.5rem; | ||
&:hover { | ||
background: rgb(236, 236, 234); | ||
} | ||
` | ||
|
||
const MentionName = styled.p` | ||
padding: 0 1rem; | ||
` | ||
|
||
const Avatar = styled.img` | ||
width: 24px; | ||
height: 24px; | ||
border-radius: 12px; | ||
` |
157 changes: 157 additions & 0 deletions
157
src/plugins/mention/Autocomplete/MentionAutocomplete.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/* | ||
* Copyright (c) 2016, vace.nz (https://github.com/vacenz) | ||
* | ||
* License: MIT | ||
*/ | ||
|
||
import React, {Component} from 'react' | ||
import styled from 'styled-components' | ||
import {EditorState, Modifier, SelectionState, getVisibleSelectionRect} from 'draft-js' | ||
import Autocomplete from './Autocomplete' | ||
|
||
export function getSelectionCoords (editor, toolbarHeight = 34, maxOffsetLeft = 250) { | ||
const editorBounds = editor.getBoundingClientRect() | ||
const rangeBounds = getVisibleSelectionRect(window) | ||
if (!rangeBounds) { return null } | ||
const rangeWidth = rangeBounds.right - rangeBounds.left | ||
|
||
let offsetLeft = (rangeBounds.left - editorBounds.left) + (rangeWidth / 2) | ||
if (offsetLeft < maxOffsetLeft) { offsetLeft = maxOffsetLeft } | ||
const offsetTop = rangeBounds.top - editorBounds.top - toolbarHeight | ||
const offsetBottom = editorBounds.bottom - rangeBounds.top | ||
const rangeLeft = rangeBounds.left | ||
return { offsetLeft, offsetTop, offsetBottom, rangeLeft } | ||
} | ||
|
||
export default class extends Component { | ||
constructor (props) { | ||
super(props) | ||
this.state = { | ||
position: {}, | ||
rangeLeft: 0 | ||
} | ||
} | ||
|
||
static get defaultProps () { | ||
return { | ||
mentionUsers: [{ name: '', link: '', avatar: '' }], | ||
mentionUsersAsync: undefined | ||
} | ||
} | ||
|
||
componentDidUpdate () { | ||
this.setBarPosition() | ||
} | ||
|
||
setMention (user) { | ||
const {editorState, onChange, mentionSearchValue} = this.props | ||
|
||
if (user === null || user === undefined) { | ||
this.props.closeMentionList() | ||
return | ||
} | ||
|
||
let selectionState = editorState.getSelection() | ||
let contentState = editorState.getCurrentContent() | ||
let block = contentState.getBlockForKey(selectionState.getStartKey()) | ||
let start = selectionState.getEndOffset() - (mentionSearchValue.length + 1) | ||
let end = selectionState.getEndOffset() | ||
|
||
const contentStateWithEntity = contentState.createEntity('MENTION', 'IMMUTABLE', { | ||
url: user.link, | ||
avatar: user.avatar, | ||
name: user.name, | ||
className: 'ld-mention' | ||
}) | ||
const entityKey = contentStateWithEntity.getLastCreatedEntityKey() | ||
|
||
const targetRange = new SelectionState({ | ||
anchorKey: block.getKey(), | ||
anchorOffset: start, | ||
focusKey: block.getKey(), | ||
focusOffset: end | ||
}) | ||
|
||
let updatedState = Modifier.replaceText( | ||
editorState.getCurrentContent(), | ||
targetRange, | ||
user.name, | ||
editorState.getCurrentInlineStyle(), | ||
entityKey | ||
) | ||
onChange(EditorState.push(editorState, updatedState, 'insert-characters')) | ||
this.props.closeMentionList() | ||
} | ||
|
||
setBarPosition () { | ||
const editorWrapper = this.props.editorWrapper | ||
const selectionCoords = getSelectionCoords(editorWrapper, 0, 0) | ||
const hasFocus = this.props.editorState.getSelection().getHasFocus() | ||
|
||
if (!selectionCoords) { return null } | ||
if (!hasFocus) { return null } | ||
|
||
if (selectionCoords && | ||
!this.state.position || | ||
this.state.position.top !== selectionCoords.offsetTop || | ||
this.state.position.left !== selectionCoords.offsetLeft) { | ||
this.setState({ | ||
rangeLeft: selectionCoords.rangeLeft, | ||
position: { | ||
top: selectionCoords.offsetTop, | ||
left: selectionCoords.offsetLeft | ||
} | ||
}) | ||
} | ||
} | ||
|
||
render () { | ||
const { position } = this.state | ||
const { mentionSearchValue } = this.props | ||
let showMentions = mentionSearchValue.length > 0 | ||
|
||
if (this.props.readOnly) { return null } | ||
|
||
let menuStyle = { display: showMentions ? 'block' : 'none' } | ||
if (position !== undefined) { | ||
menuStyle = Object.assign(position, menuStyle) | ||
menuStyle = {...menuStyle} | ||
} | ||
|
||
return ( | ||
<MentionListWrapper style={menuStyle} className='ld-mention-list-wrapper'> | ||
<div style={{position: 'absolute', bottom: '0'}}> | ||
<MentionList className='ld-mention-list'> | ||
{ | ||
showMentions && | ||
<div style={{whiteSpace: 'nowrap'}}> | ||
<Autocomplete | ||
searchValue={this.props.mentionSearchValue} | ||
mentionUsers={this.props.mentionUsers} | ||
mentionUsersAsync={this.props.mentionUsersAsync} | ||
searchKey='name' | ||
closeMentionList={::this.props.closeMentionList} | ||
onClick={::this.setMention} /> | ||
</div> | ||
} | ||
</MentionList> | ||
</div> | ||
</MentionListWrapper> | ||
) | ||
} | ||
} | ||
|
||
const MentionListWrapper = styled.div` | ||
font-family: Open Sans, sans-serif; | ||
letter-spacing: -0.037rem; | ||
line-height: 1.75rem; | ||
height: 0; | ||
position: relative; | ||
z-index: 10; | ||
transform: translateY(8px); | ||
` | ||
|
||
const MentionList = styled.div` | ||
position: relative; | ||
transition: background-color 0.2s ease-in-out; | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* | ||
* Copyright (c) 2016, vace.nz (https://github.com/vacenz) | ||
* | ||
* License: MIT | ||
*/ | ||
|
||
import MentionAutocomplete from './Autocomplete/MentionAutocomplete' | ||
|
||
export default { | ||
type: 'mention', | ||
autocomplete: MentionAutocomplete | ||
} |
Oops, something went wrong.