Skip to content

Commit 6d01034

Browse files
committedJul 25, 2016
Step 2: select auto-suggestions.
1 parent ee045cf commit 6d01034

File tree

7 files changed

+140
-70
lines changed

7 files changed

+140
-70
lines changed
 

‎main.css

+32-10
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ svg#edges {
5252
border-radius: 10px;
5353
background-color: white;
5454
position: absolute;
55-
overflow: hidden;
5655
box-shadow: 3px 3px 10px #888;
5756
}
5857

@@ -61,22 +60,45 @@ svg#edges {
6160
}
6261

6362
.emptyItemForm {
64-
display: flex;
65-
margin-top: 10px;
63+
margin-top: 15px;
6664
}
6765

68-
.emptyItemForm > input[type=text] {
69-
flex-grow: 0.7;
70-
border: 0px;
66+
.emptyItemForm .emptyItemInput {
67+
width: 90%;
68+
margin-left: 5%;
69+
box-sizing: border-box;
70+
border: none;
7171
outline: none;
72-
padding: 5px 5px 5px 10px;
7372
font-family: mono;
7473
}
7574

76-
.emptyItem .autoCompleteSuggestion {
75+
.emptyItemForm ul.react-autosuggest__suggestions-container {
76+
padding-left: 20px;
77+
padding-right: 20px;
78+
list-style: none;
79+
}
80+
81+
.emptyItemForm li.react-autosuggest__suggestion {
82+
width: 100%;
83+
overflow: hidden;
84+
white-space: nowrap;
85+
text-overflow: ellipsis;
86+
padding: 5px;
87+
margin-top: 1px;
88+
background-color: #fff;
89+
box-shadow: 3px 3px 10px #888;
90+
}
91+
92+
.emptyItemForm li.react-autosuggest__suggestion:last-child {
93+
border-radius: 0 0 7px 7px;
94+
}
95+
96+
.emptyItemForm li.react-autosuggest__suggestion--focused {
97+
background-color: #ffe;
7798
}
7899

79100
.note {
101+
overflow: hidden;
80102
background-color: #FFD;
81103
width: 100%;
82104
height: 100%;
@@ -91,8 +113,8 @@ svg#edges {
91113
}
92114

93115
.webpage-iframe-wrapper-container {
94-
position:'relative';
95-
overflow: 'hidden';
116+
position: relative;
117+
overflow: hidden;
96118
}
97119

98120
.webpage-iframe-scaling-container {

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"lodash": "^4.11.1",
1818
"pouchdb": "^5.3.1",
1919
"react": "^0.14.8",
20+
"react-autosuggest": "^3.8.0",
2021
"react-contenteditable": "git://github.com/Treora/react-contenteditable#userIsKing",
2122
"react-dom": "^0.14.8",
2223
"react-motion": "^0.4.3",

‎src/actions.js

+12-15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createAction } from 'redux-act'
22

33
import canvas from './canvas'
44
import storage from './storage'
5+
import { getEmptyItemState } from './selectors'
56
import { asUrl, textToHtml } from './utils'
67

78
// Clean the canvas and show an empty item
@@ -181,22 +182,18 @@ export function handleDraggedOut({itemId, dir}) {
181182
}
182183
}
183184

184-
export function updateAutoComplete({value, itemId}) {
185+
export function updateAutoSuggest({itemId}) {
185186
return function(dispatch, getState) {
186-
let suggestions
187-
if (value.length < 3)
188-
suggestions = []
189-
else
190-
suggestions = dispatch(chooseAutoCompleteSuggestions({value}))
191-
dispatch(setAutoCompleteSuggestions({itemId, suggestions}))
187+
// Tell UI to show suggestions for the current input
188+
dispatch(updateEmptyItemSuggestions({itemId}))
189+
// Let store search for suggestions
190+
let inputValue = getEmptyItemState(getState(), itemId).inputValue
191+
let suggestions = storage.autoSuggestSearch(getState().storage, {inputValue})
192+
// Update list of suggestions for this user input
193+
dispatch(setAutoSuggestSuggestions({inputValue, suggestions}))
192194
}
193195
}
194196

195-
function chooseAutoCompleteSuggestions({value}) {
196-
return function (dispatch, getState) {
197-
let suggestions = storage.autoCompleteSearch(getState().storage, {text: value})
198-
return suggestions
199-
}
200-
}
201-
202-
export let setAutoCompleteSuggestions = createAction()
197+
export let setAutoSuggestSuggestions = createAction()
198+
export let setEmptyItemValue = createAction()
199+
export let updateEmptyItemSuggestions = createAction()

‎src/components/EmptyItem.jsx

+51-31
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,52 @@
11
import React from 'react'
22
import { bindActionCreators } from 'redux'
33
import { connect } from 'react-redux'
4+
import Autosuggest from 'react-autosuggest'
45

5-
import { navigateTo, updateAutoComplete } from '../actions'
6-
import { getAutoCompleteSuggestions } from '../selectors'
6+
import { navigateTo, updateAutoSuggest, setEmptyItemValue } from '../actions'
7+
import { getEmptyItemState, getAutoSuggestSuggestions } from '../selectors'
78

89
let EmptyItem = React.createClass({
910

1011
render() {
1112
const submitForm = event => {
1213
event.preventDefault()
13-
let value = this.refs['urlInput'].value.trim()
14-
this.refs['urlInput'].value = ''
15-
if (value) {
16-
this.props.submitForm(value)
14+
let inputValue = this.props.inputValue.trim()
15+
if (inputValue) {
16+
this.props.submitForm(inputValue)
1717
}
1818
}
19+
20+
const inputProps = {
21+
value: this.props.inputValue,
22+
type: 'text',
23+
placeholder: '.....',
24+
className: 'emptyItemInput',
25+
onFocus: () => this.props.focus(),
26+
onBlur: () => this.props.blur(),
27+
onChange: (e, {newValue}) => this.props.changed(newValue),
28+
}
29+
const suggestions = this.props.suggestions
30+
const renderSuggestion = suggestion => (
31+
<span dangerouslySetInnerHTML={{__html: suggestion}} />
32+
)
33+
const getSuggestionValue = s => s
34+
const onSuggestionSelected = (event, {suggestionValue}) => {
35+
event.preventDefault()
36+
this.props.submitForm(suggestionValue)
37+
}
1938
return (
2039
<div className='emptyItem'>
21-
<form className='emptyItemForm' onSubmit={submitForm}>
22-
<input
23-
ref='urlInput'
24-
type='text'
25-
placeholder='.....'
26-
onFocus={() => this.props.focus()}
27-
onBlur={() => this.props.blur()}
28-
onChange={(e) => this.props.changed(e.target.value)}
29-
></input>
40+
<form ref='form' className='emptyItemForm' onSubmit={submitForm}>
41+
<Autosuggest
42+
suggestions={suggestions}
43+
onSuggestionsUpdateRequested={this.props.updateAutoSuggest}
44+
onSuggestionSelected={onSuggestionSelected}
45+
getSuggestionValue={getSuggestionValue}
46+
renderSuggestion={renderSuggestion}
47+
inputProps={inputProps}
48+
/>
3049
</form>
31-
<ul className='autoCompleteSuggestionList'>
32-
{this.props.suggestions.map(suggestion =>
33-
<li
34-
className='autoCompleteSuggestion'
35-
key={suggestion}
36-
dangerouslySetInnerHTML={{__html: suggestion}}
37-
>
38-
</li>
39-
)}
40-
</ul>
4150
</div>
4251
)
4352
},
@@ -51,7 +60,7 @@ let EmptyItem = React.createClass({
5160

5261
updateBrowserFocus() {
5362
// Make browser state reflect application state.
54-
let el = this.refs['urlInput']
63+
let el = this.refs['form'].getElementsByClassName('emptyItemInput')[0]
5564
if (this.props.focussed && document.activeElement !== el) {
5665
el.focus()
5766
}
@@ -64,17 +73,28 @@ let EmptyItem = React.createClass({
6473

6574

6675
function mapStateToProps(state, {canvasItemId}) {
67-
let suggestions = getAutoCompleteSuggestions(state, canvasItemId)
76+
let itemState = getEmptyItemState(state, canvasItemId)
77+
let inputValue = itemState !== undefined ? itemState.inputValue : ''
78+
let suggestions = itemState !== undefined
79+
? getAutoSuggestSuggestions(state, itemState.inputValueForSuggestions)
80+
: []
6881
return {
6982
suggestions,
83+
inputValue
7084
}
7185
}
7286

7387
function mapDispatchToProps(dispatch, {canvasItemId}) {
74-
return bindActionCreators({
75-
changed: value => updateAutoComplete({itemId: canvasItemId, value}),
76-
submitForm: userInput => navigateTo({userInput, itemId: canvasItemId})
77-
}, dispatch)
88+
return {
89+
submitForm: userInput => {
90+
dispatch(setEmptyItemValue({inputValue: '', itemId: canvasItemId}))
91+
dispatch(navigateTo({userInput, itemId: canvasItemId}))
92+
},
93+
...bindActionCreators({
94+
changed: inputValue => setEmptyItemValue({inputValue, itemId: canvasItemId}),
95+
updateAutoSuggest: () => updateAutoSuggest({itemId: canvasItemId})
96+
}, dispatch)
97+
}
7898
}
7999

80100
export default connect(mapStateToProps, mapDispatchToProps)(EmptyItem)

‎src/reducer/volatile.js

+31-6
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,44 @@ import { createReducer } from 'redux-act';
33
import * as actions from '../actions'
44

55
let defaultState = {
6-
autoCompletions: {
7-
// [itemId]: {suggestions: [...]},
6+
emptyItems: {
7+
// [itemId]: {inputValue, inputValueForSuggestions}
88
},
9+
autoSuggestions: {
10+
// [inputValue]: {suggestions: [...]},
11+
},
12+
}
13+
14+
// Notes on our auto-suggest implementation:
15+
// Browsing through suggestions updates inputValue, so we remember the last
16+
// 'manually entered' input value in inputValueForSuggestions. We then look up
17+
// this value in state.autoSuggestions, which acts as a cache for search results.
18+
19+
function setEmptyItemValue(state, {itemId, inputValue}) {
20+
let item = {...state.emptyItems[itemId], inputValue}
21+
let emptyItems = {...state.emptyItems, [itemId]: item}
22+
return {...state, emptyItems}
23+
}
24+
25+
function updateEmptyItemSuggestions(state, {itemId}) {
26+
let item = state.emptyItems[itemId]
27+
28+
item.inputValueForSuggestions = item.inputValue
29+
30+
let emptyItems = {...state.emptyItems, [itemId]: item}
31+
return {...state, emptyItems}
932
}
1033

11-
function setAutoCompleteSuggestions(state, {itemId, suggestions}) {
12-
let autoCompletions = {...state.autoCompletions, [itemId]: {suggestions}}
13-
return {...state, autoCompletions}
34+
function setAutoSuggestSuggestions(state, {itemId, inputValue, suggestions}) {
35+
let autoSuggestions = {...state.autoSuggestions, [inputValue]: {suggestions}}
36+
return {...state, autoSuggestions}
1437
}
1538

1639
let reducer = createReducer(
1740
{
18-
[actions.setAutoCompleteSuggestions]: setAutoCompleteSuggestions,
41+
[actions.setAutoSuggestSuggestions]: setAutoSuggestSuggestions,
42+
[actions.setEmptyItemValue]: setEmptyItemValue,
43+
[actions.updateEmptyItemSuggestions]: updateEmptyItemSuggestions,
1944
},
2045
defaultState
2146
)

‎src/selectors.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
export function getAutoCompleteSuggestions(state, itemId) {
2-
let autoCompletion = state.volatile.autoCompletions[itemId]
3-
if (autoCompletion) {
4-
return autoCompletion.suggestions
1+
export function getEmptyItemState(state, itemId) {
2+
let itemState = state.volatile.emptyItems[itemId]
3+
return itemState
4+
}
5+
6+
export function getAutoSuggestSuggestions(state, inputValue) {
7+
let autoSuggestion = state.volatile.autoSuggestions[inputValue]
8+
if (autoSuggestion) {
9+
return autoSuggestion.suggestions
510
}
611
return []
712
}

‎src/storage/selectors.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ export function readGeneratedId(action) {
4848
return action.meta.generatedId
4949
}
5050

51-
export function autoCompleteSearch(state, {text, maxSuggestions=5}) {
52-
let lowText = text.toLowerCase()
51+
export function autoSuggestSearch(state, {inputValue, maxSuggestions=5}) {
52+
let lowText = inputValue.toLowerCase()
5353
let words = lowText.split(' ')
5454
let stripUrl = url => url.replace('http://', '').replace('https://','')
5555
let strippedUrl = stripUrl(lowText)
@@ -64,7 +64,7 @@ export function autoCompleteSearch(state, {text, maxSuggestions=5}) {
6464
words.every(word => url.toLowerCase().indexOf(word) > -1)
6565
)
6666
let caseSensitiveStartsWith = docText => (
67-
docText.startsWith(text)
67+
docText.startsWith(inputValue)
6868
)
6969
let caseInsensitiveStartsWith = docText => (
7070
docText.toLowerCase().startsWith(lowText)
@@ -96,6 +96,6 @@ export function autoCompleteSearch(state, {text, maxSuggestions=5}) {
9696
let matches = _(state.docs).map(doc=>doc.text).filter().filter(textMatchers[i]).value()
9797
suggestions = _.uniq(suggestions.concat(matches))
9898
}
99-
suggestions.slice(maxSuggestions)
99+
suggestions.splice(maxSuggestions)
100100
return suggestions
101101
}

0 commit comments

Comments
 (0)
Please sign in to comment.