1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

add feature: colored tags

This commit is contained in:
HarlanLuo
2018-12-27 21:36:20 +08:00
parent 13c2f471aa
commit 3e645db324
13 changed files with 205 additions and 13 deletions

View File

@@ -0,0 +1,63 @@
import React from 'react'
import PropTypes from 'prop-types'
import { SketchPicker } from 'react-color'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ColorPicker.styl'
const componentHeight = 333
class ColorPicker extends React.Component {
constructor (props) {
super(props)
this.state = {
color: this.props.color || '#888888'
}
this.onColorChange = this.onColorChange.bind(this)
this.handleConfirm = this.handleConfirm.bind(this)
}
onColorChange (color) {
this.setState({
color
})
}
handleConfirm () {
this.props.onConfirm(this.state.color)
}
render () {
const { onReset, onCancel, targetRect } = this.props
const { color } = this.state
const clientHeight = document.body.clientHeight
const alignX = targetRect.right + 4
let alignY = targetRect.top
if (targetRect.top + componentHeight > clientHeight) {
alignY = targetRect.bottom - componentHeight
}
return (
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
<SketchPicker color={color} onChange={this.onColorChange} />
<div styleName='footer'>
<button styleName='btn-reset' onClick={onReset}>Reset</button>
<button styleName='btn-cancel' onClick={onCancel}>Cancel</button>
<button styleName='btn-confirm' onClick={this.handleConfirm}>Confirm</button>
</div>
</div>
)
}
}
ColorPicker.propTypes = {
color: PropTypes.string,
targetRect: PropTypes.object,
onConfirm: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
onReset: PropTypes.func.isRequired
}
export default CSSModules(ColorPicker, styles)

View File

@@ -0,0 +1,25 @@
.colorPicker
position fixed
z-index 10000
display flex
flex-direction column
.footer
display flex
justify-content center
align-items center
padding 5px
& > button + button
margin-left 10px
.btn-cancel,
.btn-confirm,
.btn-reset
height 1.6em
border 1px solid #888888
background-color #fff
font-size 16px
border-radius 4px
.btn-confirm
background-color $ui-button-default--active-backgroundColor

View File

@@ -15,8 +15,8 @@ import i18n from 'browser/lib/i18n'
* @param {string} tagName
* @return {React.Component}
*/
const TagElement = ({ tagName }) => (
<span styleName='item-bottom-tagList-item' key={tagName}>
const TagElement = ({ tagName, color }) => (
<span styleName='item-bottom-tagList-item' key={tagName} style={{backgroundColor: color}}>
#{tagName}
</span>
)
@@ -27,7 +27,7 @@ const TagElement = ({ tagName }) => (
* @param {boolean} showTagsAlphabetically
* @return {React.Component}
*/
const TagElementList = (tags, showTagsAlphabetically) => {
const TagElementList = (tags, showTagsAlphabetically, tagConfig) => {
if (!isArray(tags)) {
return []
}
@@ -35,7 +35,7 @@ const TagElementList = (tags, showTagsAlphabetically) => {
if (showTagsAlphabetically) {
return _.sortBy(tags).map(tag => TagElement({ tagName: tag }))
} else {
return tags.map(tag => TagElement({ tagName: tag }))
return tags.map(tag => TagElement({ tagName: tag, color: tagConfig[tag] }))
}
}
@@ -59,7 +59,8 @@ const NoteItem = ({
storageName,
folderName,
viewType,
showTagsAlphabetically
showTagsAlphabetically,
tagConfig
}) => (
<div
styleName={isActive ? 'item--active' : 'item'}
@@ -97,7 +98,7 @@ const NoteItem = ({
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags, showTagsAlphabetically)
? TagElementList(note.tags, showTagsAlphabetically, tagConfig)
: <span
style={{ fontStyle: 'italic', opacity: 0.5 }}
styleName='item-bottom-tagList-empty'
@@ -127,6 +128,7 @@ const NoteItem = ({
NoteItem.propTypes = {
isActive: PropTypes.bool.isRequired,
dateDisplay: PropTypes.string.isRequired,
tagConfig: PropTypes.object,
note: PropTypes.shape({
storage: PropTypes.string.isRequired,
key: PropTypes.string.isRequired,

View File

@@ -12,9 +12,10 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {Function} handleClickNarrowToTag
* @param {bool} isActive
* @param {bool} isRelated
* @param {string} bgColor tab backgroundColor
*/
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => (
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count, color}) => (
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
{isRelated
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
@@ -23,7 +24,7 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, hand
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
}
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-name'>
<span styleName='tagList-item-name' style={{color}}>
{`# ${name}`}
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
</span>
@@ -33,7 +34,8 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, hand
TagListItem.propTypes = {
name: PropTypes.string.isRequired,
handleClickTagListItem: PropTypes.func.isRequired
handleClickTagListItem: PropTypes.func.isRequired,
color: PropTypes.string
}
export default CSSModules(TagListItem, styles)

View File

@@ -437,6 +437,7 @@ class MarkdownNoteDetail extends React.Component {
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data}
onChange={this.handleUpdateTag.bind(this)}
tagConfig={config.tag}
/>
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>

View File

@@ -788,6 +788,7 @@ class SnippetNoteDetail extends React.Component {
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data}
onChange={(e) => this.handleChange(e)}
tagConfig={config.tag}
/>
</div>
<div styleName='info-right'>

View File

@@ -179,13 +179,14 @@ class TagSelect extends React.Component {
}
render () {
const { value, className, showTagsAlphabetically } = this.props
const { value, className, showTagsAlphabetically, tagConfig } = this.props
const tagList = _.isArray(value)
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
return (
<span styleName='tag'
key={tag}
style={{backgroundColor: tagConfig[tag]}}
>
<span styleName='tag-label' onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
<button styleName='tag-removeButton'
@@ -240,7 +241,8 @@ TagSelect.contextTypes = {
TagSelect.propTypes = {
className: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func
onChange: PropTypes.func,
tagConfig: PropTypes.object
}
export default CSSModules(TagSelect, styles)

View File

@@ -1047,6 +1047,7 @@ class NoteList extends React.Component {
storageName={this.getNoteStorage(note).name}
viewType={viewType}
showTagsAlphabetically={config.ui.showTagsAlphabetically}
tagConfig={config.tag}
/>
)
}

View File

@@ -20,6 +20,7 @@ import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context'
import { remote } from 'electron'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import ColorPicker from 'browser/components/ColorPicker'
function matchActiveTags (tags, activeTags) {
return _.every(activeTags, v => tags.indexOf(v) >= 0)
@@ -27,6 +28,22 @@ function matchActiveTags (tags, activeTags) {
class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7
constructor (props) {
super(props)
this.state = {
colorPickerState: {
show: false,
color: null,
tagName: null,
targetRect: null
}
}
this.dismissColorPicker = this.dismissColorPicker.bind(this)
this.handleColorPickerConfirm = this.handleColorPickerConfirm.bind(this)
this.handleColorPickerReset = this.handleColorPickerReset.bind(this)
}
componentDidMount () {
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
@@ -104,9 +121,63 @@ class SideNav extends React.Component {
click: this.deleteTag.bind(this, tag)
})
menu.push({
label: i18n.__('Customize Color'),
click: this.displayColorPicker.bind(this, tag, e.target.getBoundingClientRect())
})
context.popup(menu)
}
dismissColorPicker () {
this.setState({
colorPickerState: {
show: false
}
})
}
displayColorPicker (tagName, rect) {
const { config } = this.props
this.setState({
colorPickerState: {
show: true,
color: config.tag && config.tag[tagName],
tagName,
targetRect: rect
}
})
}
handleColorPickerConfirm (color) {
const { dispatch, config: {tag} } = this.props
const { colorPickerState: { tagName } } = this.state
const tagConfig = Object.assign({}, tag, {[tagName]: color.hex})
const config = {tag: tagConfig}
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
this.dismissColorPicker()
}
handleColorPickerReset () {
const { dispatch, config: {tag} } = this.props
const { colorPickerState: { tagName } } = this.state
const tagConfig = Object.assign({}, tag)
delete tagConfig[tagName]
const config = {tag: tagConfig}
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
this.dismissColorPicker()
}
handleToggleButtonClick (e) {
const { dispatch, config } = this.props
@@ -241,6 +312,7 @@ class SideNav extends React.Component {
isRelated={tag.related}
key={tag.name}
count={tag.size}
color={config.tag[tag.name]}
/>
)
})
@@ -333,6 +405,7 @@ class SideNav extends React.Component {
render () {
const { data, location, config, dispatch } = this.props
const { colorPickerState } = this.state
const isFolded = config.isSideNavFolded
@@ -349,6 +422,20 @@ class SideNav extends React.Component {
useDragHandle
/>
})
let colorPicker
if (colorPickerState.show) {
colorPicker = (
<ColorPicker
color={colorPickerState.color}
targetRect={colorPickerState.targetRect}
onConfirm={this.handleColorPickerConfirm}
onCancel={this.dismissColorPicker}
onReset={this.handleColorPickerReset}
/>
)
}
const style = {}
if (!isFolded) style.width = this.props.width
const isTagActive = location.pathname.match(/tag/)
@@ -368,6 +455,7 @@ class SideNav extends React.Component {
</div>
</div>
{this.SideNavComponent(isFolded, storageList)}
{colorPicker}
</div>
)
}

View File

@@ -86,7 +86,8 @@ export const DEFAULT_CONFIG = {
token: '',
username: '',
password: ''
}
},
tag: {}
}
function validate (config) {

View File

@@ -97,6 +97,7 @@
"react": "^15.5.4",
"react-autosuggest": "^9.4.0",
"react-codemirror": "^0.3.0",
"react-color": "^2.17.0",
"react-debounce-render": "^4.0.1",
"react-dom": "^15.0.2",
"react-image-carousel": "^2.0.18",

View File

@@ -3,7 +3,7 @@ import renderer from 'react-test-renderer'
import TagListItem from 'browser/components/TagListItem'
it('TagListItem renders correctly', () => {
const tagListItem = renderer.create(<TagListItem name='Test' handleClickTagListItem={jest.fn()} />)
const tagListItem = renderer.create(<TagListItem name='Test' handleClickTagListItem={jest.fn()} color='#000' />)
expect(tagListItem.toJSON()).toMatchSnapshot()
})

View File

@@ -14,6 +14,11 @@ exports[`TagListItem renders correctly 1`] = `
>
<span
className="tagList-item-name"
style={
Object {
"color": "#000",
}
}
>
# Test
<span