mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
new feature: filter tags and folder list
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
.menu
|
||||
margin-bottom 30px
|
||||
margin-bottom 20px
|
||||
|
||||
.menu-button
|
||||
navButtonColor()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
.storageList
|
||||
absolute left right
|
||||
bottom 37px
|
||||
top 180px
|
||||
margin-bottom 37px
|
||||
overflow-y auto
|
||||
|
||||
.storageList-folded
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
.top-menu-preference
|
||||
navButtonColor()
|
||||
position absolute
|
||||
top 22px
|
||||
right 10px
|
||||
width 2em
|
||||
background-color transparent
|
||||
&:hover
|
||||
|
||||
22
browser/main/SideNav/SearchButton.js
Normal file
22
browser/main/SideNav/SearchButton.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SearchButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const SearchButton = ({ onClick, isActive }) => (
|
||||
<button styleName='top-menu-search' onClick={(e) => onClick(e)}>
|
||||
<img
|
||||
styleName='icon-search'
|
||||
src={isActive ? '../resources/icon/icon-search-active.svg' : '../resources/icon/icon-search.svg'}
|
||||
/>
|
||||
<span styleName='tooltip'>{i18n.__('Search')}</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
SearchButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isActive: PropTypes.bool
|
||||
}
|
||||
|
||||
export default CSSModules(SearchButton, styles)
|
||||
55
browser/main/SideNav/SearchButton.styl
Normal file
55
browser/main/SideNav/SearchButton.styl
Normal file
@@ -0,0 +1,55 @@
|
||||
.top-menu-search
|
||||
navButtonColor()
|
||||
position relative
|
||||
margin-right 6px
|
||||
top 3px
|
||||
width 2em
|
||||
background-color transparent
|
||||
&:hover
|
||||
color $ui-button-default--active-backgroundColor
|
||||
background-color transparent
|
||||
.tooltip
|
||||
opacity 1
|
||||
&:active, &:active:hover
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
.icon-search
|
||||
width 16px
|
||||
|
||||
body[data-theme="white"]
|
||||
.top-menu-search
|
||||
navWhiteButtonColor()
|
||||
background-color transparent
|
||||
&:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
&:active, &:active:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
|
||||
body[data-theme="dark"]
|
||||
.top-menu-search
|
||||
navDarkButtonColor()
|
||||
background-color transparent
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
|
||||
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
left -20px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
white-space nowrap
|
||||
@@ -18,7 +18,37 @@
|
||||
display flex
|
||||
text-align center
|
||||
|
||||
.extra-buttons
|
||||
position absolute
|
||||
display flex
|
||||
align-items center
|
||||
justify-content flex-end
|
||||
right 10px
|
||||
top 24px
|
||||
|
||||
.search
|
||||
position relative
|
||||
flex 1
|
||||
display flex
|
||||
max-height 0
|
||||
overflow hidden
|
||||
transition max-height .4s
|
||||
margin -5px 10px 0
|
||||
.search-input
|
||||
flex 1
|
||||
height 2em
|
||||
vertical-align middle
|
||||
font-size 14px
|
||||
border solid 1px $border-color
|
||||
border-radius 2px
|
||||
padding 2px 6px
|
||||
outline none
|
||||
.search-clear
|
||||
width 10px
|
||||
position absolute
|
||||
right 8px
|
||||
top 9px
|
||||
cursor pointer
|
||||
|
||||
.top-menu-label
|
||||
margin-left 5px
|
||||
@@ -98,6 +128,17 @@
|
||||
.top-menu-preference
|
||||
position absolute
|
||||
left 7px
|
||||
.search
|
||||
height 28px
|
||||
.search-input
|
||||
display none
|
||||
.search-clear
|
||||
display none
|
||||
.search-folded
|
||||
width 16px
|
||||
padding-left 4px
|
||||
margin-bottom 8px
|
||||
cursor pointer
|
||||
|
||||
body[data-theme="white"]
|
||||
.root, .root--folded
|
||||
|
||||
@@ -13,6 +13,7 @@ import StorageList from 'browser/components/StorageList'
|
||||
import NavToggleButton from 'browser/components/NavToggleButton'
|
||||
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import PreferenceButton from './PreferenceButton'
|
||||
import SearchButton from './SearchButton'
|
||||
import ListButton from './ListButton'
|
||||
import TagButton from './TagButton'
|
||||
import {SortableContainer} from 'react-sortable-hoc'
|
||||
@@ -36,21 +37,26 @@ class SideNav extends React.Component {
|
||||
show: false,
|
||||
color: null,
|
||||
tagName: null,
|
||||
targetRect: null
|
||||
targetRect: null,
|
||||
showSearch: false,
|
||||
searchText: ''
|
||||
}
|
||||
}
|
||||
|
||||
this.dismissColorPicker = this.dismissColorPicker.bind(this)
|
||||
this.handleColorPickerConfirm = this.handleColorPickerConfirm.bind(this)
|
||||
this.handleColorPickerReset = this.handleColorPickerReset.bind(this)
|
||||
this.handleSearchButtonClick = this.handleSearchButtonClick.bind(this)
|
||||
this.handleSearchInputChange = this.handleSearchInputChange.bind(this)
|
||||
this.handleSearchInputClear = this.handleSearchInputClear.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
|
||||
EventEmitter.on('side:preferences', this.handlePreferenceButtonClick)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
||||
EventEmitter.off('side:preferences', this.handlePreferenceButtonClick)
|
||||
}
|
||||
|
||||
deleteTag (tag) {
|
||||
@@ -99,10 +105,30 @@ class SideNav extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleMenuButtonClick (e) {
|
||||
handlePreferenceButtonClick (e) {
|
||||
openModal(PreferencesModal)
|
||||
}
|
||||
|
||||
handleSearchButtonClick (e) {
|
||||
const { showSearch } = this.state
|
||||
this.setState({
|
||||
showSearch: !showSearch,
|
||||
searchText: ''
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchInputClear (e) {
|
||||
this.setState({
|
||||
searchText: ''
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchInputChange (e) {
|
||||
this.setState({
|
||||
searchText: e.target.value
|
||||
})
|
||||
}
|
||||
|
||||
handleHomeButtonClick (e) {
|
||||
const { router } = this.context
|
||||
router.push('/home')
|
||||
@@ -181,12 +207,19 @@ class SideNav extends React.Component {
|
||||
|
||||
handleToggleButtonClick (e) {
|
||||
const { dispatch, config } = this.props
|
||||
const { showSearch, searchText } = this.state
|
||||
|
||||
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
|
||||
dispatch({
|
||||
type: 'SET_IS_SIDENAV_FOLDED',
|
||||
isFolded: !config.isSideNavFolded
|
||||
})
|
||||
|
||||
if (showSearch && searchText.length === 0) {
|
||||
this.setState({
|
||||
showSearch: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleTrashedButtonClick (e) {
|
||||
@@ -215,8 +248,9 @@ class SideNav extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
SideNavComponent (isFolded, storageList) {
|
||||
const { location, data, config } = this.props
|
||||
SideNavComponent (isFolded) {
|
||||
const { location, data, config, dispatch } = this.props
|
||||
const { showSearch, searchText } = this.state
|
||||
|
||||
const isHomeActive = !!location.pathname.match(/^\/home$/)
|
||||
const isStarredActive = !!location.pathname.match(/^\/starred$/)
|
||||
@@ -226,6 +260,28 @@ class SideNav extends React.Component {
|
||||
|
||||
// TagsMode is not selected
|
||||
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
|
||||
let storageMap = data.storageMap
|
||||
if (showSearch && searchText.length > 0) {
|
||||
storageMap = storageMap.map((storage) => {
|
||||
const folders = storage.folders.filter(folder => folder.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1)
|
||||
return Object.assign({}, storage, { folders })
|
||||
})
|
||||
}
|
||||
|
||||
const storageList = storageMap.map((storage, key) => {
|
||||
const SortableStorageItem = SortableContainer(StorageItem)
|
||||
return <SortableStorageItem
|
||||
key={storage.key}
|
||||
storage={storage}
|
||||
data={data}
|
||||
location={location}
|
||||
isFolded={isFolded}
|
||||
dispatch={dispatch}
|
||||
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
||||
useDragHandle
|
||||
/>
|
||||
})
|
||||
|
||||
component = (
|
||||
<div>
|
||||
<SideNavFilter
|
||||
@@ -279,7 +335,7 @@ class SideNav extends React.Component {
|
||||
|
||||
tagListComponent () {
|
||||
const { data, location, config } = this.props
|
||||
const { colorPicker } = this.state
|
||||
const { colorPicker, showSearch, searchText } = this.state
|
||||
const activeTags = this.getActiveTags(location.pathname)
|
||||
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
|
||||
let tagList = _.sortBy(data.tagNoteMap.map(
|
||||
@@ -287,6 +343,9 @@ class SideNav extends React.Component {
|
||||
).filter(
|
||||
tag => tag.size > 0
|
||||
), ['name'])
|
||||
if (showSearch && searchText.length > 0) {
|
||||
tagList = tagList.filter(tag => tag.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1)
|
||||
}
|
||||
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
|
||||
const notesTags = data.noteMap.map(note => note.tags)
|
||||
tagList = tagList.map(tag => {
|
||||
@@ -406,24 +465,8 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, location, config, dispatch } = this.props
|
||||
const { colorPicker: colorPickerState } = this.state
|
||||
|
||||
const isFolded = config.isSideNavFolded
|
||||
|
||||
const storageList = data.storageMap.map((storage, key) => {
|
||||
const SortableStorageItem = SortableContainer(StorageItem)
|
||||
return <SortableStorageItem
|
||||
key={storage.key}
|
||||
storage={storage}
|
||||
data={data}
|
||||
location={location}
|
||||
isFolded={isFolded}
|
||||
dispatch={dispatch}
|
||||
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
||||
useDragHandle
|
||||
/>
|
||||
})
|
||||
const { location, config } = this.props
|
||||
const { showSearch, searchText, colorPicker: colorPickerState } = this.state
|
||||
|
||||
let colorPicker
|
||||
if (colorPickerState.show) {
|
||||
@@ -438,9 +481,25 @@ class SideNav extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
const isFolded = config.isSideNavFolded
|
||||
const style = {}
|
||||
if (!isFolded) style.width = this.props.width
|
||||
const isTagActive = location.pathname.match(/tag/)
|
||||
|
||||
const navSearch = (
|
||||
<div styleName='search' style={{maxHeight: showSearch ? '3em' : '0'}}>
|
||||
<input
|
||||
styleName='search-input'
|
||||
type='text'
|
||||
ref={dom => { this.searchInput = dom }}
|
||||
onChange={this.handleSearchInputChange}
|
||||
value={searchText}
|
||||
/>
|
||||
<img styleName='search-clear' src='../resources/icon/icon-x.svg' onClick={this.handleSearchInputClear} />
|
||||
{isFolded && <img styleName='search-folded' src='../resources/icon/icon-search-active.svg' onClick={this.handleSearchButtonClick} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='SideNav'
|
||||
styleName={isFolded ? 'root--folded' : 'root'}
|
||||
@@ -452,11 +511,16 @@ class SideNav extends React.Component {
|
||||
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||
</div>
|
||||
<div>
|
||||
<PreferenceButton onClick={this.handleMenuButtonClick} />
|
||||
<div styleName='extra-buttons'>
|
||||
<SearchButton
|
||||
onClick={this.handleSearchButtonClick}
|
||||
isActive={showSearch}
|
||||
/>
|
||||
<PreferenceButton onClick={this.handlePreferenceButtonClick} />
|
||||
</div>
|
||||
</div>
|
||||
{this.SideNavComponent(isFolded, storageList)}
|
||||
{navSearch}
|
||||
{this.SideNavComponent(isFolded)}
|
||||
{colorPicker}
|
||||
</div>
|
||||
)
|
||||
|
||||
14
resources/icon/icon-search-active.svg
Normal file
14
resources/icon/icon-search-active.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1546833265368" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="1101" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path
|
||||
d="M384 768c-213.333333 0-384-170.666667-384-384s170.666667-384 384-384 384 170.666667 384 384S597.333333 768 384 768zM384 85.333333C217.6 85.333333 85.333333 217.6 85.333333 384s132.266667 298.666667 298.666667 298.666667 298.666667-132.266667 298.666667-298.666667S550.4 85.333333 384 85.333333z"
|
||||
p-id="1102" fill="#1EC38B"></path>
|
||||
<path
|
||||
d="M981.333333 1024c-12.8 0-21.333333-4.266667-29.866667-12.8l-341.333333-341.333333c-17.066667-17.066667-17.066667-42.666667 0-59.733333s42.666667-17.066667 59.733333 0l341.333333 341.333333c17.066667 17.066667 17.066667 42.666667 0 59.733333C1002.666667 1019.733333 994.133333 1024 981.333333 1024z"
|
||||
p-id="1103" fill="#1EC38B"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
14
resources/icon/icon-search.svg
Normal file
14
resources/icon/icon-search.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg t="1546833265368" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="1101" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
|
||||
<defs>
|
||||
<style type="text/css"></style>
|
||||
</defs>
|
||||
<path
|
||||
d="M384 768c-213.333333 0-384-170.666667-384-384s170.666667-384 384-384 384 170.666667 384 384S597.333333 768 384 768zM384 85.333333C217.6 85.333333 85.333333 217.6 85.333333 384s132.266667 298.666667 298.666667 298.666667 298.666667-132.266667 298.666667-298.666667S550.4 85.333333 384 85.333333z"
|
||||
p-id="1102" fill="#8A8C8D"></path>
|
||||
<path
|
||||
d="M981.333333 1024c-12.8 0-21.333333-4.266667-29.866667-12.8l-341.333333-341.333333c-17.066667-17.066667-17.066667-42.666667 0-59.733333s42.666667-17.066667 59.733333 0l341.333333 341.333333c17.066667 17.066667 17.066667 42.666667 0 59.733333C1002.666667 1019.733333 994.133333 1024 981.333333 1024z"
|
||||
p-id="1103" fill="#8A8C8D"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
Reference in New Issue
Block a user