From fa9d8b8881eea06263268a93e15d91a74faaa0df Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 26 Aug 2018 15:48:41 +0200 Subject: [PATCH] replace `awesomplete` with React component `react-autosuggest` --- browser/main/Detail/TagSelect.js | 220 ++++++++++++++++----------- browser/main/Detail/TagSelect.styl | 28 +--- browser/main/global.styl | 2 +- browser/styles/Detail/TagSelect.styl | 109 +++++++++++++ browser/styles/awesomplete.styl | 117 -------------- lib/main.html | 2 - package.json | 2 +- yarn.lock | 38 ++++- 8 files changed, 280 insertions(+), 238 deletions(-) create mode 100644 browser/styles/Detail/TagSelect.styl delete mode 100644 browser/styles/awesomplete.styl diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index 17494b93..e01d503b 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -6,80 +6,33 @@ import _ from 'lodash' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import i18n from 'browser/lib/i18n' import ee from 'browser/main/lib/eventEmitter' +import Autosuggest from 'react-autosuggest' class TagSelect extends React.Component { constructor (props) { super(props) this.state = { - newTag: '' + newTag: '', + suggestions: [] } - this.addtagHandler = this.handleAddTag.bind(this) + + this.handleAddTag = this.handleAddTag.bind(this) + this.onInputBlur = this.onInputBlur.bind(this) + this.onInputChange = this.onInputChange.bind(this) + this.onInputKeyDown = this.onInputKeyDown.bind(this) + this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this) + this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this) + this.onSuggestionSelected = this.onSuggestionSelected.bind(this) } - componentDidMount () { - this.value = this.props.value - ee.on('editor:add-tag', this.addtagHandler) - - this.awesomplete = new Awesomplete(this.refs.newTag, { - minChars: 1, - autoFirst: true, - list: '#datalist', - filter: (text, input) => !_.includes(this.value, text.value) && Awesomplete.FILTER_CONTAINS(text, input) - }) - } - - componentDidUpdate () { - this.value = this.props.value - } - - componentWillUnmount () { - ee.off('editor:add-tag', this.addtagHandler) - - this.awesomplete.destroy() - } - - handleAddTag () { - this.refs.newTag.focus() - } - - handleNewTagInputKeyDown (e) { - switch (e.keyCode) { - case 9: - e.preventDefault() - this.submitTag() - break - case 13: - this.submitTag() - break - case 8: - if (this.refs.newTag.value.length === 0) { - this.removeLastTag() - } - } - } - - handleNewTagBlur (e) { - this.submitTag() - } - - removeLastTag () { - this.removeTagByCallback((value) => { - value.pop() - }) - } - - reset () { - this.setState({ - newTag: '' - }) - } - - submitTag () { + addNewTag (newTag) { AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG') - let { value } = this.props - let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_') - newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag + + newTag = newTag.trim().replace(/ +/g, '_') + if (newTag.charAt(0) === '#') { + newTag.substring(1) + } if (newTag.length <= 0) { this.setState({ @@ -88,6 +41,7 @@ class TagSelect extends React.Component { return } + let { value } = this.props value = _.isArray(value) ? value.slice() : [] @@ -102,10 +56,36 @@ class TagSelect extends React.Component { }) } - handleNewTagInputChange (e) { - this.setState({ - newTag: this.refs.newTag.value - }) + buildSuggestions () { + this.suggestions = _.sortBy(this.props.data.tagNoteMap.map( + (tag, name) => ({ + name, + nameLC: name.toLowerCase(), + size: tag.size + }) + ).filter( + tag => tag.size > 0 + ), ['name']) + } + + componentDidMount () { + this.value = this.props.value + + this.buildSuggestions() + + ee.on('editor:add-tag', this.handleAddTag) + } + + componentDidUpdate () { + this.value = this.props.value + } + + componentWillUnmount () { + ee.off('editor:add-tag', this.handleAddTag) + } + + handleAddTag () { + this.refs.newTag.input.focus() } handleTagRemoveButtonClick (tag) { @@ -114,6 +94,60 @@ class TagSelect extends React.Component { }, tag) } + onInputBlur (e) { + this.submitNewTag() + } + + onInputChange (e, { newValue, method }) { + this.setState({ + newTag: newValue + }) + } + + onInputKeyDown (e) { + switch (e.keyCode) { + case 9: + e.preventDefault() + this.submitNewTag() + break + case 13: + this.submitNewTag() + break + case 8: + if (this.state.newTag.length === 0) { + this.removeLastTag() + } + } + } + + onSuggestionsClearRequested () { + this.setState({ + suggestions: [] + }) + } + + onSuggestionsFetchRequested ({ value }) { + const valueLC = value.toLowerCase() + const suggestions = _.filter( + this.suggestions, + tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1 + ) + + this.setState({ + suggestions + }) + } + + onSuggestionSelected (event, { suggestion, suggestionValue }) { + this.addNewTag(suggestionValue) + } + + removeLastTag () { + this.removeTagByCallback((value) => { + value.pop() + }) + } + removeTagByCallback (callback, tag = null) { let { value } = this.props @@ -127,8 +161,20 @@ class TagSelect extends React.Component { this.props.onChange() } + reset () { + this.buildSuggestions() + + this.setState({ + newTag: '' + }) + } + + submitNewTag () { + this.addNewTag(this.refs.newTag.input.value) + } + render () { - const { value, className, data } = this.props + const { value, className } = this.props const tagList = _.isArray(value) ? value.map((tag) => { @@ -147,13 +193,7 @@ class TagSelect extends React.Component { }) : [] - const datalist = _.sortBy(data.tagNoteMap.map( - (tag, name) => ({ name, size: tag.size }) - ).filter( - tag => tag.size > 0 - ), ['name']).map( - tag =>
  • {tag.name}
  • - ) + const { newTag, suggestions } = this.state return (
    {tagList} - this.handleNewTagInputChange(e)} - onKeyDown={(e) => this.handleNewTagInputKeyDown(e)} - onBlur={(e) => this.handleNewTagBlur(e)} + suggestions={suggestions} + onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} + onSuggestionsClearRequested={this.onSuggestionsClearRequested} + onSuggestionSelected={this.onSuggestionSelected} + getSuggestionValue={suggestion => suggestion.name} + renderSuggestion={suggestion => ( +
    + {suggestion.name} +
    + )} + inputProps={{ + placeholder: i18n.__('Add tag...'), + value: newTag, + onChange: this.onInputChange, + onKeyDown: this.onInputKeyDown, + onBlur: this.onInputBlur + }} /> -
    ) } @@ -183,7 +232,6 @@ TagSelect.propTypes = { className: PropTypes.string, value: PropTypes.arrayOf(PropTypes.string), onChange: PropTypes.func - } export default CSSModules(TagSelect, styles) diff --git a/browser/main/Detail/TagSelect.styl b/browser/main/Detail/TagSelect.styl index 052c86b6..7dc9dfe4 100644 --- a/browser/main/Detail/TagSelect.styl +++ b/browser/main/Detail/TagSelect.styl @@ -42,17 +42,6 @@ color: $ui-text-color padding 4px 16px 4px 8px -.newTag - box-sizing border-box - border none - background-color transparent - outline none - padding 0 4px - font-size 13px - -.datalist - display none - body[data-theme="dark"] .tag background-color alpha($ui-dark-tag-backgroundColor, 60%) @@ -65,11 +54,6 @@ body[data-theme="dark"] .tag-label color $ui-dark-text-color - .newTag - border-color none - background-color transparent - color $ui-dark-text-color - body[data-theme="solarized-dark"] .tag background-color $ui-solarized-dark-tag-backgroundColor @@ -81,11 +65,6 @@ body[data-theme="solarized-dark"] .tag-label color $ui-solarized-dark-text-color - .newTag - border-color none - background-color transparent - color $ui-solarized-dark-text-color - body[data-theme="monokai"] .tag background-color $ui-monokai-button-backgroundColor @@ -95,9 +74,4 @@ body[data-theme="monokai"] background-color transparent .tag-label - color $ui-monokai-text-color - - .newTag - border-color none - background-color transparent - color $ui-monokai-text-color + color $ui-monokai-text-color \ No newline at end of file diff --git a/browser/main/global.styl b/browser/main/global.styl index 6046861a..75ace0b3 100644 --- a/browser/main/global.styl +++ b/browser/main/global.styl @@ -157,4 +157,4 @@ body[data-theme="default"] .SideNav ::-webkit-scrollbar-thumb background-color rgba(255, 255, 255, 0.3) -@import '../styles/awesomplete.styl' \ No newline at end of file +@import '../styles/Detail/TagSelect.styl' \ No newline at end of file diff --git a/browser/styles/Detail/TagSelect.styl b/browser/styles/Detail/TagSelect.styl new file mode 100644 index 00000000..84fd74c2 --- /dev/null +++ b/browser/styles/Detail/TagSelect.styl @@ -0,0 +1,109 @@ +.TagSelect + .react-autosuggest__input + box-sizing border-box + border none + background-color transparent + outline none + padding 0 4px + font-size 13px + + ul + position fixed + z-index 999 + box-sizing border-box + list-style none + padding 0 + margin 0 + + border-radius 4px + margin .2em 0 0 + background-color $ui-noteList-backgroundColor + border 1px solid rgba(0,0,0,.3) + box-shadow .05em .2em .6em rgba(0,0,0,.2) + text-shadow none + + &:empty, + &[hidden] + display none + + &:before + content "" + position absolute + top -7px + left 14px + width 0 height 0 + padding 6px + background-color $ui-noteList-backgroundColor + border inherit + border-right 0 + border-bottom 0 + -webkit-transform rotate(45deg) + transform rotate(45deg) + + li + position relative + padding 6px 18px 6px 10px + cursor pointer + + li[aria-selected="true"] + background-color alpha($ui-button--active-backgroundColor, 40%) + color $ui-text-color + +body[data-theme="dark"] + .TagSelect + .react-autosuggest__input + color $ui-dark-text-color + + ul + border-color $ui-dark-borderColor + background-color $ui-dark-noteList-backgroundColor + color $ui-dark-text-color + + &:before + background-color $ui-dark-noteList-backgroundColor + + li[aria-selected="true"] + background-color $ui-dark-button--active-backgroundColor + color $ui-dark-text-color + +body[data-theme="monokai"] + .TagSelect + .react-autosuggest__input + color $ui-monokai-text-color + + ul + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteList-backgroundColor + color $ui-monokai-text-color + + &:before + background-color $ui-dark-noteList-backgroundColor + + li[aria-selected="true"] + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + +body[data-theme="solarized-dark"] + .TagSelect + .react-autosuggest__input + color $ui-solarized-dark-text-color + + ul + border-color $ui-solarized-dark-borderColor + background-color $ui-solarized-dark-noteList-backgroundColor + color $ui-solarized-dark-text-color + + &:before + background-color $ui-solarized-dark-noteList-backgroundColor + + li[aria-selected="true"] + background-color $ui-dark-button--active-backgroundColor + color $ui-solarized-dark-text-color + +body[data-theme="white"] + .TagSelect + ul + background-color $ui-white-noteList-backgroundColor + + li[aria-selected="true"] + background-color $ui-button--active-backgroundColor \ No newline at end of file diff --git a/browser/styles/awesomplete.styl b/browser/styles/awesomplete.styl deleted file mode 100644 index f46037a3..00000000 --- a/browser/styles/awesomplete.styl +++ /dev/null @@ -1,117 +0,0 @@ -.awesomplete - display inline-block - position relative - - .visually-hidden - position absolute - clip rect(0, 0, 0, 0) - - ul - position fixed - z-index 1 - box-sizing border-box - list-style none - padding 0 - margin 0 - - border-radius 4px - margin .2em 0 0 - background-color $ui-noteList-backgroundColor - border 1px solid rgba(0,0,0,.3) - box-shadow .05em .2em .6em rgba(0,0,0,.2) - text-shadow none - - &:empty, - &[hidden] - display none - - &:before - content "" - position absolute - top -.43em - left 1em - width 0 height 0 - padding .4em - background-color $ui-noteList-backgroundColor - border inherit - border-right 0 - border-bottom 0 - -webkit-transform rotate(45deg) - transform rotate(45deg) - - li - position relative - padding 6px 18px 6px 10px - cursor pointer - - li:hover - background-color alpha($ui-button--active-backgroundColor, 20%) - color $ui-text-color - - li[aria-selected="true"] - background-color alpha($ui-button--active-backgroundColor, 40%) - color $ui-text-color - - mark - background-color rgba(255, 255, 0, 0.8) - -body[data-theme="dark"] - .awesomplete ul - border-color $ui-dark-borderColor - background-color $ui-dark-noteList-backgroundColor - color $ui-dark-text-color - - &:before - background-color $ui-dark-noteList-backgroundColor - - li:hover - background-color alpha($ui-dark-button--active-backgroundColor, 20%) - color $ui-dark-text-color - - li[aria-selected="true"] - background-color $ui-dark-button--active-backgroundColor - color $ui-dark-text-color - -body[data-theme="white"] - .awesomplete ul - background-color $ui-white-noteList-backgroundColor - - li:hover - background-color alpha($ui-button--active-backgroundColor, 60%) - - li[aria-selected="true"] - background-color $ui-button--active-backgroundColor - -body[data-theme="solarized-dark"] - .awesomplete ul - border-color $ui-solarized-dark-borderColor - background-color $ui-solarized-dark-noteList-backgroundColor - color $ui-solarized-dark-text-color - - &:before - background-color $ui-solarized-dark-noteList-backgroundColor - - li:hover - background-color alpha($ui-dark-button--active-backgroundColor, 20%) - color $ui-solarized-dark-text-color - - li[aria-selected="true"] - background-color $ui-dark-button--active-backgroundColor - color $ui-solarized-dark-text-color - -body[data-theme="monokai"] - .awesomplete ul - border-color $ui-monokai-borderColor - background-color $ui-monokai-noteList-backgroundColor - color $ui-monokai-text-color - - &:before - background-color $ui-dark-noteList-backgroundColor - - li:hover - background-color alpha($ui-dark-button--active-backgroundColor, 20%) - color $ui-monokai-text-color - - li[aria-selected="true"] - background-color $ui-monokai-button-backgroundColor - color $ui-monokai-text-color \ No newline at end of file diff --git a/lib/main.html b/lib/main.html index 3c2d33eb..e03cac37 100644 --- a/lib/main.html +++ b/lib/main.html @@ -119,8 +119,6 @@ window._ = require('lodash') - - diff --git a/package.json b/package.json index 4ca816e1..408f6d90 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ "@rokt33r/markdown-it-math": "^4.0.1", "@rokt33r/season": "^5.3.0", "@susisu/mte-kernel": "^2.0.0", - "awesomplete": "^1.1.2", "aws-sdk": "^2.48.0", "aws-sdk-mobile-analytics": "^0.9.2", "chart.js": "^2.7.2", @@ -89,6 +88,7 @@ "node-ipc": "^8.1.0", "raphael": "^2.2.7", "react": "^15.5.4", + "react-autosuggest": "^9.4.0", "react-codemirror": "^0.3.0", "react-debounce-render": "^4.0.1", "react-dom": "^15.0.2", diff --git a/yarn.lock b/yarn.lock index 02207b14..50374f74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -568,10 +568,6 @@ ava@^0.25.0: unique-temp-dir "^1.0.0" update-notifier "^2.3.0" -awesomplete@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/awesomplete/-/awesomplete-1.1.2.tgz#b6e253f73474e46278bba5ae7f81d4262160fb75" - aws-sdk-mobile-analytics@^0.9.2: version "0.9.2" resolved "https://registry.yarnpkg.com/aws-sdk-mobile-analytics/-/aws-sdk-mobile-analytics-0.9.2.tgz#b56a6e5206fc8c3975a19170b41536c53f6d5d91" @@ -6356,6 +6352,10 @@ oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -7194,6 +7194,22 @@ rcedit@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-1.1.0.tgz#ae21c28d4efdd78e95fcab7309a5dd084920b16a" +react-autosuggest@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.0.tgz#3146bc9afa4f171bed067c542421edec5ca94294" + dependencies: + prop-types "^15.5.10" + react-autowhatever "^10.1.2" + shallow-equal "^1.0.0" + +react-autowhatever@^10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/react-autowhatever/-/react-autowhatever-10.1.2.tgz#200ffc41373b2189e3f6140ac7bdb82363a79fd3" + dependencies: + prop-types "^15.5.8" + react-themeable "^1.1.0" + section-iterator "^2.0.0" + react-codemirror@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/react-codemirror/-/react-codemirror-0.3.0.tgz#cd6bd6ef458ec1e035cfd8b3fe7b30c8c7883c6c" @@ -7294,6 +7310,12 @@ react-test-renderer@^15.6.2: fbjs "^0.8.9" object-assign "^4.1.0" +react-themeable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e" + dependencies: + object-assign "^3.0.0" + react-transform-catch-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/react-transform-catch-errors/-/react-transform-catch-errors-1.0.2.tgz#1b4d4a76e97271896fc16fe3086c793ec88a9eeb" @@ -7796,6 +7818,10 @@ scope-css@^1.0.5: version "1.1.0" resolved "http://registry.npm.taobao.org/scope-css/download/scope-css-1.1.0.tgz#74eff45461bc9d3f3b29ed575b798cd722fa1256" +section-iterator@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a" + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -7891,6 +7917,10 @@ sha.js@2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba" +shallow-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.0.0.tgz#508d1838b3de590ab8757b011b25e430900945f7" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"