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
|
.menu
|
||||||
margin-bottom 30px
|
margin-bottom 20px
|
||||||
|
|
||||||
.menu-button
|
.menu-button
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
.storageList
|
.storageList
|
||||||
absolute left right
|
margin-bottom 37px
|
||||||
bottom 37px
|
|
||||||
top 180px
|
|
||||||
overflow-y auto
|
overflow-y auto
|
||||||
|
|
||||||
.storageList-folded
|
.storageList-folded
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
.top-menu-preference
|
.top-menu-preference
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
position absolute
|
|
||||||
top 22px
|
|
||||||
right 10px
|
|
||||||
width 2em
|
width 2em
|
||||||
background-color transparent
|
background-color transparent
|
||||||
&:hover
|
&: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
|
display flex
|
||||||
text-align center
|
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
|
.top-menu-label
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
@@ -98,6 +128,17 @@
|
|||||||
.top-menu-preference
|
.top-menu-preference
|
||||||
position absolute
|
position absolute
|
||||||
left 7px
|
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"]
|
body[data-theme="white"]
|
||||||
.root, .root--folded
|
.root, .root--folded
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import StorageList from 'browser/components/StorageList'
|
|||||||
import NavToggleButton from 'browser/components/NavToggleButton'
|
import NavToggleButton from 'browser/components/NavToggleButton'
|
||||||
import EventEmitter from 'browser/main/lib/eventEmitter'
|
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import PreferenceButton from './PreferenceButton'
|
import PreferenceButton from './PreferenceButton'
|
||||||
|
import SearchButton from './SearchButton'
|
||||||
import ListButton from './ListButton'
|
import ListButton from './ListButton'
|
||||||
import TagButton from './TagButton'
|
import TagButton from './TagButton'
|
||||||
import {SortableContainer} from 'react-sortable-hoc'
|
import {SortableContainer} from 'react-sortable-hoc'
|
||||||
@@ -36,21 +37,26 @@ class SideNav extends React.Component {
|
|||||||
show: false,
|
show: false,
|
||||||
color: null,
|
color: null,
|
||||||
tagName: null,
|
tagName: null,
|
||||||
targetRect: null
|
targetRect: null,
|
||||||
|
showSearch: false,
|
||||||
|
searchText: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dismissColorPicker = this.dismissColorPicker.bind(this)
|
this.dismissColorPicker = this.dismissColorPicker.bind(this)
|
||||||
this.handleColorPickerConfirm = this.handleColorPickerConfirm.bind(this)
|
this.handleColorPickerConfirm = this.handleColorPickerConfirm.bind(this)
|
||||||
this.handleColorPickerReset = this.handleColorPickerReset.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 () {
|
componentDidMount () {
|
||||||
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
|
EventEmitter.on('side:preferences', this.handlePreferenceButtonClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
EventEmitter.off('side:preferences', this.handlePreferenceButtonClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTag (tag) {
|
deleteTag (tag) {
|
||||||
@@ -99,10 +105,30 @@ class SideNav extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMenuButtonClick (e) {
|
handlePreferenceButtonClick (e) {
|
||||||
openModal(PreferencesModal)
|
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) {
|
handleHomeButtonClick (e) {
|
||||||
const { router } = this.context
|
const { router } = this.context
|
||||||
router.push('/home')
|
router.push('/home')
|
||||||
@@ -181,12 +207,19 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
handleToggleButtonClick (e) {
|
handleToggleButtonClick (e) {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
const { showSearch, searchText } = this.state
|
||||||
|
|
||||||
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
|
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_IS_SIDENAV_FOLDED',
|
type: 'SET_IS_SIDENAV_FOLDED',
|
||||||
isFolded: !config.isSideNavFolded
|
isFolded: !config.isSideNavFolded
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (showSearch && searchText.length === 0) {
|
||||||
|
this.setState({
|
||||||
|
showSearch: false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTrashedButtonClick (e) {
|
handleTrashedButtonClick (e) {
|
||||||
@@ -215,8 +248,9 @@ class SideNav extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SideNavComponent (isFolded, storageList) {
|
SideNavComponent (isFolded) {
|
||||||
const { location, data, config } = this.props
|
const { location, data, config, dispatch } = this.props
|
||||||
|
const { showSearch, searchText } = this.state
|
||||||
|
|
||||||
const isHomeActive = !!location.pathname.match(/^\/home$/)
|
const isHomeActive = !!location.pathname.match(/^\/home$/)
|
||||||
const isStarredActive = !!location.pathname.match(/^\/starred$/)
|
const isStarredActive = !!location.pathname.match(/^\/starred$/)
|
||||||
@@ -226,6 +260,28 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
// TagsMode is not selected
|
// TagsMode is not selected
|
||||||
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
|
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 = (
|
component = (
|
||||||
<div>
|
<div>
|
||||||
<SideNavFilter
|
<SideNavFilter
|
||||||
@@ -279,7 +335,7 @@ class SideNav extends React.Component {
|
|||||||
|
|
||||||
tagListComponent () {
|
tagListComponent () {
|
||||||
const { data, location, config } = this.props
|
const { data, location, config } = this.props
|
||||||
const { colorPicker } = this.state
|
const { colorPicker, showSearch, searchText } = this.state
|
||||||
const activeTags = this.getActiveTags(location.pathname)
|
const activeTags = this.getActiveTags(location.pathname)
|
||||||
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
|
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
|
||||||
let tagList = _.sortBy(data.tagNoteMap.map(
|
let tagList = _.sortBy(data.tagNoteMap.map(
|
||||||
@@ -287,6 +343,9 @@ class SideNav extends React.Component {
|
|||||||
).filter(
|
).filter(
|
||||||
tag => tag.size > 0
|
tag => tag.size > 0
|
||||||
), ['name'])
|
), ['name'])
|
||||||
|
if (showSearch && searchText.length > 0) {
|
||||||
|
tagList = tagList.filter(tag => tag.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1)
|
||||||
|
}
|
||||||
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
|
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
|
||||||
const notesTags = data.noteMap.map(note => note.tags)
|
const notesTags = data.noteMap.map(note => note.tags)
|
||||||
tagList = tagList.map(tag => {
|
tagList = tagList.map(tag => {
|
||||||
@@ -406,24 +465,8 @@ class SideNav extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { data, location, config, dispatch } = this.props
|
const { location, config } = this.props
|
||||||
const { colorPicker: colorPickerState } = this.state
|
const { showSearch, searchText, 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
|
|
||||||
/>
|
|
||||||
})
|
|
||||||
|
|
||||||
let colorPicker
|
let colorPicker
|
||||||
if (colorPickerState.show) {
|
if (colorPickerState.show) {
|
||||||
@@ -438,9 +481,25 @@ class SideNav extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isFolded = config.isSideNavFolded
|
||||||
const style = {}
|
const style = {}
|
||||||
if (!isFolded) style.width = this.props.width
|
if (!isFolded) style.width = this.props.width
|
||||||
const isTagActive = location.pathname.match(/tag/)
|
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 (
|
return (
|
||||||
<div className='SideNav'
|
<div className='SideNav'
|
||||||
styleName={isFolded ? 'root--folded' : 'root'}
|
styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
@@ -452,11 +511,16 @@ class SideNav extends React.Component {
|
|||||||
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
|
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||||
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
|
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div styleName='extra-buttons'>
|
||||||
<PreferenceButton onClick={this.handleMenuButtonClick} />
|
<SearchButton
|
||||||
|
onClick={this.handleSearchButtonClick}
|
||||||
|
isActive={showSearch}
|
||||||
|
/>
|
||||||
|
<PreferenceButton onClick={this.handlePreferenceButtonClick} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.SideNavComponent(isFolded, storageList)}
|
{navSearch}
|
||||||
|
{this.SideNavComponent(isFolded)}
|
||||||
{colorPicker}
|
{colorPicker}
|
||||||
</div>
|
</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