Skip to content

Commit

Permalink
move to Draft 0.10.0
Browse files Browse the repository at this point in the history
  • Loading branch information
svnm committed Mar 1, 2017
1 parent cdb25fc commit 5c3b92c
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 14 deletions.
2 changes: 1 addition & 1 deletion example/Example.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import emoji from 'ld-emoji'
import mention from 'ld-mention'
import html from 'ld-html'
import todo from 'ld-todo'
let plugins = [video, audio, color, emoji, mention, html, todo]
let plugins = [video, audio, color, emoji, html, todo]

export default class ExampleEditor extends Component {
constructor(props) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
},
"dependencies": {
"draft-convert": "^1.3.1",
"draft-js": "^0.9.1",
"draft-js": "0.10.0",
"immutable": "~3.7.4",
"linkify-it": "^2.0.2",
"styled-components": "^1.2.1",
Expand Down
4 changes: 2 additions & 2 deletions src/components/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Sidebar from './Sidebar/Sidebar'
import Atomic from './Blocks/Atomic'
import Media from './Blocks/Media'

import {image, placeholder} from '../plugins/'
import {image, placeholder, mention} from '../plugins/'
import Actions from '../actions/'

import insertDataBlock from '../utils/insertDataBlock'
Expand Down Expand Up @@ -132,7 +132,7 @@ export default class extends Component {

getValidPlugins () {
/* default image plugin */
let plugins = [image, placeholder]
let plugins = [image, placeholder, mention]

if (!this.props.plugins) { return plugins }

Expand Down
4 changes: 2 additions & 2 deletions src/components/Entities/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ linkify.tlds(tlds)

export default class extends Component {
render () {
const {entityKey, decoratedText} = this.props
const {contentState, decoratedText, entityKey} = this.props

if (entityKey) {
const data = Entity.get(entityKey).getData()
const data = contentState.getEntity(entityKey).getData()
/* Links */
return (
<Link href={data.url} title={data.title} target='_self' className='ld-link'>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Entities/Mention.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import styled from 'styled-components'

export default class extends Component {
render () {
const {entityKey} = this.props
const {contentState, entityKey} = this.props
if (entityKey) {
const data = Entity.get(entityKey).getData()
const data = contentState.getEntity(entityKey).getData()
return (
<Mention href={data.url} title={data.name} className='ld-mention'>
{this.props.children}
Expand Down
1 change: 1 addition & 0 deletions src/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
*/

export * from './image/plugin'
export mention from './mention/plugin'
149 changes: 149 additions & 0 deletions src/plugins/mention/Autocomplete/Autocomplete.js
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 src/plugins/mention/Autocomplete/MentionAutocomplete.js
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;
`
12 changes: 12 additions & 0 deletions src/plugins/mention/plugin.js
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
}
Loading

0 comments on commit 5c3b92c

Please sign in to comment.