1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

Narrow list of tags to related ones

When a tag is selected, the tag list narrows to show only the related
ones: all tags associated to the currently visible notes. Clicking on
the plus sign near another tag narrows the list again to the tags of
notes associated with the firstly AND secondly selected tag. To show
every tags again, press the tag icon on the top-left corner of
Boostnote.

Before:
![screencast](https://i.imgur.com/PwAdhLe.gif)

After:
![screencast](https://i.imgur.com/s3JCaFq.gif)

NOTE: Tags are joined with `&` character (`#` not works) in
`location.pathname` thus it will make the tags with this character
unavailable. Any suggestion to pass multiple values via pathname?
This commit is contained in:
bimlas
2018-03-26 16:48:07 +02:00
parent 25440a26ee
commit 6c542750f4
5 changed files with 90 additions and 26 deletions

View File

@@ -12,13 +12,18 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {bool} isActive * @param {bool} isActive
*/ */
const TagListItem = ({name, handleClickTagListItem, isActive, count}) => ( const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, count}) => (
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}> <div styleName='tagList-itemContainer'>
<span styleName='tagList-item-name'> <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
{`# ${name}`} <i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
<span styleName='tagList-item-count'>{count}</span> </button>
</span> <button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
</button> <span styleName='tagList-item-name'>
{`# ${name}`}
<span styleName='tagList-item-count'>{count}</span>
</span>
</button>
</div>
) )
TagListItem.propTypes = { TagListItem.propTypes = {

View File

@@ -1,10 +1,10 @@
.tagList-item .tagList-itemContainer
display flex display flex
width 100%
.tagList-item, .tagList-itemNarrow
height 26px height 26px
background-color transparent background-color transparent
color $ui-inactive-text-color color $ui-inactive-text-color
padding 0
margin-bottom 5px margin-bottom 5px
text-align left text-align left
border none border none
@@ -20,12 +20,18 @@
color $ui-button-default-color color $ui-button-default-color
background-color $ui-button-default--active-backgroundColor background-color $ui-button-default--active-backgroundColor
.tagList-item-active .tagList-item
background-color $ui-button-default--active-backgroundColor
display flex display flex
flex 1
width 100% width 100%
height 26px
padding 0 padding 0
.tagList-itemNarrow
padding 0 4px
.tagList-item-active, .tagList-itemNarrow-active
background-color $ui-button-default--active-backgroundColor
height 26px
margin-bottom 5px margin-bottom 5px
text-align left text-align left
border none border none
@@ -36,10 +42,19 @@
background-color alpha($ui-button-default--active-backgroundColor, 60%) background-color alpha($ui-button-default--active-backgroundColor, 60%)
transition 0.2s transition 0.2s
.tagList-item-active
display flex
flex 1
width 100%
padding 0
.tagList-itemNarrow-active
padding 0 4px
.tagList-item-name .tagList-item-name
display block display block
flex 1 flex 1
padding 0 15px padding 0 8px 0 4px
height 26px height 26px
line-height 26px line-height 26px
border-width 0 0 0 2px border-width 0 0 0 2px
@@ -55,7 +70,7 @@
font-size 13px font-size 13px
body[data-theme="white"] body[data-theme="white"]
.tagList-item .tagList-item, .tagList-itemNarrow
color $ui-inactive-text-color color $ui-inactive-text-color
&:hover &:hover
color $ui-text-color color $ui-text-color
@@ -64,7 +79,7 @@ body[data-theme="white"]
color $ui-text-color color $ui-text-color
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
.tagList-item-active .tagList-item-active, .tagList-itemNarrow-active
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
color $ui-text-color color $ui-text-color
&:hover &:hover
@@ -73,7 +88,7 @@ body[data-theme="white"]
color $ui-text-color color $ui-text-color
body[data-theme="dark"] body[data-theme="dark"]
.tagList-item .tagList-item, .tagList-itemNarrow
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
&:hover &:hover
color $ui-dark-text-color color $ui-dark-text-color
@@ -82,7 +97,7 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
.tagList-item-active .tagList-item-active, .tagList-itemNarrow-active
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
&:active &:active

View File

@@ -343,10 +343,11 @@ class NoteList extends React.Component {
} }
if (location.pathname.match(/\/tags/)) { if (location.pathname.match(/\/tags/)) {
const listOfTags = params.tagname.split('&')
return data.noteMap.map(note => { return data.noteMap.map(note => {
return note return note
}).filter(note => { }).filter(note => {
return note.tags.includes(params.tagname) return listOfTags.every((tag) => note.tags.includes(tag))
}) })
} }

View File

@@ -91,7 +91,7 @@ class SideNav extends React.Component {
let component let 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') && !location.pathname.match('/narrowToTag')) {
component = ( component = (
<div> <div>
<SideNavFilter <SideNavFilter
@@ -145,10 +145,12 @@ class SideNav extends React.Component {
tagListComponent () { tagListComponent () {
const { data, location, config } = this.props const { data, location, config } = this.props
let tagList = _.sortBy(data.tagNoteMap.map( const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap)
(tag, name) => ({name, size: tag.size})), let tagList = _.sortBy(data.tagNoteMap.map((tag, name) => {
['name'] return { name, size: tag.size }
) }), ['name']).filter(tag => {
return (relatedTags.size === 0) ? true : relatedTags.has(tag.name)
})
if (config.sortTagsBy === 'COUNTER') { if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size)) tagList = _.sortBy(tagList, item => (0 - item.size))
} }
@@ -158,6 +160,7 @@ class SideNav extends React.Component {
<TagListItem <TagListItem
name={tag.name} name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)} handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name)} isActive={this.getTagActive(location.pathname, tag.name)}
key={tag.name} key={tag.name}
count={tag.size} count={tag.size}
@@ -167,10 +170,33 @@ class SideNav extends React.Component {
) )
} }
getRelatedTags (activeTags, noteMap) {
const relatedNotes = noteMap.map(note => {
return {key: note.key, tags: note.tags}
}).filter((note) => {
return activeTags.every((tag) => note.tags.includes(tag))
})
let relatedTags = new Set()
relatedNotes.forEach(note => {
note.tags.map(tag => {
relatedTags.add(tag)
})
})
return relatedTags
}
getTagActive (path, tag) { getTagActive (path, tag) {
const pathTag = this.getActiveTags(path)
return pathTag.includes(tag)
}
getActiveTags (path) {
const pathSegments = path.split('/') const pathSegments = path.split('/')
const pathTag = pathSegments[pathSegments.length - 1] const tags = pathSegments[pathSegments.length - 1]
return pathTag === tag if (tags === 'alltags') {
return []
}
return tags.split('&')
} }
handleClickTagListItem (name) { handleClickTagListItem (name) {
@@ -192,6 +218,20 @@ class SideNav extends React.Component {
}) })
} }
handleClickNarrowToTag (name) {
const { router } = this.context
const { location } = this.props
let listOfTags = this.getActiveTags(location.pathname)
if (listOfTags.includes(name)) {
listOfTags = listOfTags.filter(function (currentTag) {
return name !== currentTag
})
} else {
listOfTags.push(name)
}
router.push(`/tags/${listOfTags.join('&')}`)
}
emptyTrash (entries) { emptyTrash (entries) {
const { dispatch } = this.props const { dispatch } = this.props
const deletionPromises = entries.map((note) => { const deletionPromises = entries.map((note) => {

View File

@@ -112,6 +112,9 @@ ReactDOM.render((
<IndexRedirect to='/alltags' /> <IndexRedirect to='/alltags' />
<Route path=':tagname' /> <Route path=':tagname' />
</Route> </Route>
<Route path='narrowToTag'>
<Route path=':tag' />
</Route>
<Route path='storages'> <Route path='storages'>
<IndexRedirect to='/home' /> <IndexRedirect to='/home' />
<Route path=':storageKey'> <Route path=':storageKey'>