Skip to content

Commit fe011e8

Browse files
authored
Merge pull request #2758 from elfman/colorTag
add feature: colored tags
2 parents 5a5563f + b4e4d70 commit fe011e8

File tree

16 files changed

+288
-23
lines changed

16 files changed

+288
-23
lines changed

browser/components/ColorPicker.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import { SketchPicker } from 'react-color'
4+
import CSSModules from 'browser/lib/CSSModules'
5+
import styles from './ColorPicker.styl'
6+
7+
const componentHeight = 330
8+
9+
class ColorPicker extends React.Component {
10+
constructor (props) {
11+
super(props)
12+
13+
this.state = {
14+
color: this.props.color || '#939395'
15+
}
16+
17+
this.onColorChange = this.onColorChange.bind(this)
18+
this.handleConfirm = this.handleConfirm.bind(this)
19+
}
20+
21+
componentWillReceiveProps (nextProps) {
22+
this.onColorChange(nextProps.color)
23+
}
24+
25+
onColorChange (color) {
26+
this.setState({
27+
color
28+
})
29+
}
30+
31+
handleConfirm () {
32+
this.props.onConfirm(this.state.color)
33+
}
34+
35+
render () {
36+
const { onReset, onCancel, targetRect } = this.props
37+
const { color } = this.state
38+
39+
const clientHeight = document.body.clientHeight
40+
const alignX = targetRect.right + 4
41+
let alignY = targetRect.top
42+
if (targetRect.top + componentHeight > clientHeight) {
43+
alignY = targetRect.bottom - componentHeight
44+
}
45+
46+
return (
47+
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
48+
<div styleName='cover' onClick={onCancel} />
49+
<SketchPicker color={color} onChange={this.onColorChange} />
50+
<div styleName='footer'>
51+
<button styleName='btn-reset' onClick={onReset}>Reset</button>
52+
<button styleName='btn-cancel' onClick={onCancel}>Cancel</button>
53+
<button styleName='btn-confirm' onClick={this.handleConfirm}>Confirm</button>
54+
</div>
55+
</div>
56+
)
57+
}
58+
}
59+
60+
ColorPicker.propTypes = {
61+
color: PropTypes.string,
62+
targetRect: PropTypes.object,
63+
onConfirm: PropTypes.func.isRequired,
64+
onCancel: PropTypes.func.isRequired,
65+
onReset: PropTypes.func.isRequired
66+
}
67+
68+
export default CSSModules(ColorPicker, styles)

browser/components/ColorPicker.styl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.colorPicker
2+
position fixed
3+
z-index 2
4+
display flex
5+
flex-direction column
6+
7+
.cover
8+
position fixed
9+
top 0
10+
right 0
11+
bottom 0
12+
left 0
13+
14+
.footer
15+
display flex
16+
justify-content center
17+
z-index 2
18+
align-items center
19+
& > button + button
20+
margin-left 10px
21+
22+
.btn-cancel,
23+
.btn-confirm,
24+
.btn-reset
25+
vertical-align middle
26+
height 25px
27+
margin-top 2.5px
28+
border-radius 2px
29+
border none
30+
padding 0 5px
31+
background-color $default-button-background
32+
&:hover
33+
background-color $default-button-background--hover
34+
.btn-confirm
35+
background-color #1EC38B
36+
&:hover
37+
background-color darken(#1EC38B, 25%)
38+
39+

browser/components/NoteItem.js

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import PropTypes from 'prop-types'
55
import React from 'react'
66
import { isArray } from 'lodash'
7+
import invertColor from 'invert-color'
78
import CSSModules from 'browser/lib/CSSModules'
89
import { getTodoStatus } from 'browser/lib/getTodoStatus'
910
import styles from './NoteItem.styl'
@@ -13,29 +14,38 @@ import i18n from 'browser/lib/i18n'
1314
/**
1415
* @description Tag element component.
1516
* @param {string} tagName
17+
* @param {string} color
1618
* @return {React.Component}
1719
*/
18-
const TagElement = ({ tagName }) => (
19-
<span styleName='item-bottom-tagList-item' key={tagName}>
20-
#{tagName}
21-
</span>
22-
)
20+
const TagElement = ({ tagName, color }) => {
21+
const style = {}
22+
if (color) {
23+
style.backgroundColor = color
24+
style.color = invertColor(color, { black: '#222', white: '#f1f1f1', threshold: 0.3 })
25+
}
26+
return (
27+
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
28+
#{tagName}
29+
</span>
30+
)
31+
}
2332

2433
/**
2534
* @description Tag element list component.
2635
* @param {Array|null} tags
2736
* @param {boolean} showTagsAlphabetically
37+
* @param {Object} coloredTags
2838
* @return {React.Component}
2939
*/
30-
const TagElementList = (tags, showTagsAlphabetically) => {
40+
const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
3141
if (!isArray(tags)) {
3242
return []
3343
}
3444

3545
if (showTagsAlphabetically) {
36-
return _.sortBy(tags).map(tag => TagElement({ tagName: tag }))
46+
return _.sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
3747
} else {
38-
return tags.map(tag => TagElement({ tagName: tag }))
48+
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
3949
}
4050
}
4151

@@ -46,6 +56,7 @@ const TagElementList = (tags, showTagsAlphabetically) => {
4656
* @param {Function} handleNoteClick
4757
* @param {Function} handleNoteContextMenu
4858
* @param {Function} handleDragStart
59+
* @param {Object} coloredTags
4960
* @param {string} dateDisplay
5061
*/
5162
const NoteItem = ({
@@ -59,7 +70,8 @@ const NoteItem = ({
5970
storageName,
6071
folderName,
6172
viewType,
62-
showTagsAlphabetically
73+
showTagsAlphabetically,
74+
coloredTags
6375
}) => (
6476
<div
6577
styleName={isActive ? 'item--active' : 'item'}
@@ -97,7 +109,7 @@ const NoteItem = ({
97109
<div styleName='item-bottom'>
98110
<div styleName='item-bottom-tagList'>
99111
{note.tags.length > 0
100-
? TagElementList(note.tags, showTagsAlphabetically)
112+
? TagElementList(note.tags, showTagsAlphabetically, coloredTags)
101113
: <span
102114
style={{ fontStyle: 'italic', opacity: 0.5 }}
103115
styleName='item-bottom-tagList-empty'
@@ -127,6 +139,7 @@ const NoteItem = ({
127139
NoteItem.propTypes = {
128140
isActive: PropTypes.bool.isRequired,
129141
dateDisplay: PropTypes.string.isRequired,
142+
coloredTags: PropTypes.object,
130143
note: PropTypes.shape({
131144
storage: PropTypes.string.isRequired,
132145
key: PropTypes.string.isRequired,

browser/components/TagListItem.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import CSSModules from 'browser/lib/CSSModules'
1010
* @param {string} name
1111
* @param {Function} handleClickTagListItem
1212
* @param {Function} handleClickNarrowToTag
13-
* @param {bool} isActive
14-
* @param {bool} isRelated
13+
* @param {boolean} isActive
14+
* @param {boolean} isRelated
15+
* @param {string} bgColor tab backgroundColor
1516
*/
1617

17-
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => (
18+
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count, color}) => (
1819
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
1920
{isRelated
2021
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
@@ -23,6 +24,7 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, hand
2324
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
2425
}
2526
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
27+
<span styleName='tagList-item-color' style={{backgroundColor: color || 'transparent'}} />
2628
<span styleName='tagList-item-name'>
2729
{`# ${name}`}
2830
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
@@ -33,7 +35,8 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, hand
3335

3436
TagListItem.propTypes = {
3537
name: PropTypes.string.isRequired,
36-
handleClickTagListItem: PropTypes.func.isRequired
38+
handleClickTagListItem: PropTypes.func.isRequired,
39+
color: PropTypes.string
3740
}
3841

3942
export default CSSModules(TagListItem, styles)

browser/components/TagListItem.styl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
padding-right 15px
7272
font-size 13px
7373

74+
.tagList-item-color
75+
height 26px
76+
width 3px
77+
display inline-block
78+
7479
body[data-theme="white"]
7580
.tagList-item
7681
color $ui-inactive-text-color

browser/main/Detail/MarkdownNoteDetail.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ class MarkdownNoteDetail extends React.Component {
437437
showTagsAlphabetically={config.ui.showTagsAlphabetically}
438438
data={data}
439439
onChange={this.handleUpdateTag.bind(this)}
440+
coloredTags={config.coloredTags}
440441
/>
441442
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
442443
</div>

browser/main/Detail/SnippetNoteDetail.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,7 @@ class SnippetNoteDetail extends React.Component {
792792
showTagsAlphabetically={config.ui.showTagsAlphabetically}
793793
data={data}
794794
onChange={(e) => this.handleChange(e)}
795+
coloredTags={config.coloredTags}
795796
/>
796797
</div>
797798
<div styleName='info-right'>

browser/main/Detail/TagSelect.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import PropTypes from 'prop-types'
22
import React from 'react'
3+
import invertColor from 'invert-color'
34
import CSSModules from 'browser/lib/CSSModules'
45
import styles from './TagSelect.styl'
56
import _ from 'lodash'
@@ -185,19 +186,34 @@ class TagSelect extends React.Component {
185186
}
186187

187188
render () {
188-
const { value, className, showTagsAlphabetically } = this.props
189+
const { value, className, showTagsAlphabetically, coloredTags } = this.props
189190

190191
const tagList = _.isArray(value)
191192
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
193+
const wrapperStyle = {}
194+
const textStyle = {}
195+
const BLACK = '#333333'
196+
const WHITE = '#f1f1f1'
197+
const color = coloredTags[tag]
198+
const invertedColor = color && invertColor(color, { black: BLACK, white: WHITE })
199+
let iconRemove = '../resources/icon/icon-x.svg'
200+
if (color) {
201+
wrapperStyle.backgroundColor = color
202+
textStyle.color = invertedColor
203+
}
204+
if (invertedColor === WHITE) {
205+
iconRemove = '../resources/icon/icon-x-light.svg'
206+
}
192207
return (
193208
<span styleName='tag'
194209
key={tag}
210+
style={wrapperStyle}
195211
>
196-
<span styleName='tag-label' onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
212+
<span styleName='tag-label' style={textStyle} onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
197213
<button styleName='tag-removeButton'
198214
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
199215
>
200-
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
216+
<img className='tag-removeButton-icon' src={iconRemove} width='8px' />
201217
</button>
202218
</span>
203219
)
@@ -246,7 +262,8 @@ TagSelect.contextTypes = {
246262
TagSelect.propTypes = {
247263
className: PropTypes.string,
248264
value: PropTypes.arrayOf(PropTypes.string),
249-
onChange: PropTypes.func
265+
onChange: PropTypes.func,
266+
coloredTags: PropTypes.object
250267
}
251268

252269
export default CSSModules(TagSelect, styles)

browser/main/NoteList/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ class NoteList extends React.Component {
10471047
storageName={this.getNoteStorage(note).name}
10481048
viewType={viewType}
10491049
showTagsAlphabetically={config.ui.showTagsAlphabetically}
1050+
coloredTags={config.coloredTags}
10501051
/>
10511052
)
10521053
}

0 commit comments

Comments
 (0)