mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-15 02:36:36 +00:00
* allow a tag to be renamed and update all notes that use that tag • repurpose RenameFolderModal.styl to RenameModal so it is more generic * call handleConfirmButtonClick directly instead of sending through a confirm method * better name for method to confirm the rename * use close prop instead of a new method * use callback ref instead of legacy string refs * bind the handleChange in the constructor to allow for direct function assignment * update the tag in the URL upon change * use the eventEmitter to update the tags in the SnippetNoteDetail header via the TagSelect component * respect themes when modal is opened * show error message when trying to rename to an existing tag * lint fix, const over let * add missing letter * fix routing and add merge warning dialog * fix space-before-parens lint error * change theming * add check if tag changed Co-authored-by: Khaliq Gant <khaliqgant@gmail.com>
This commit is contained in:
@@ -20,6 +20,7 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.handleAddTag = this.handleAddTag.bind(this)
|
this.handleAddTag = this.handleAddTag.bind(this)
|
||||||
|
this.handleRenameTag = this.handleRenameTag.bind(this)
|
||||||
this.onInputBlur = this.onInputBlur.bind(this)
|
this.onInputBlur = this.onInputBlur.bind(this)
|
||||||
this.onInputChange = this.onInputChange.bind(this)
|
this.onInputChange = this.onInputChange.bind(this)
|
||||||
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
||||||
@@ -88,6 +89,7 @@ class TagSelect extends React.Component {
|
|||||||
this.buildSuggestions()
|
this.buildSuggestions()
|
||||||
|
|
||||||
ee.on('editor:add-tag', this.handleAddTag)
|
ee.on('editor:add-tag', this.handleAddTag)
|
||||||
|
ee.on('sidebar:rename-tag', this.handleRenameTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@@ -96,12 +98,23 @@ class TagSelect extends React.Component {
|
|||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ee.off('editor:add-tag', this.handleAddTag)
|
ee.off('editor:add-tag', this.handleAddTag)
|
||||||
|
ee.off('sidebar:rename-tag', this.handleRenameTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddTag() {
|
handleAddTag() {
|
||||||
this.refs.newTag.input.focus()
|
this.refs.newTag.input.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRenameTag(event, tagChange) {
|
||||||
|
const { value } = this.props
|
||||||
|
const { tag, updatedTag } = tagChange
|
||||||
|
const newTags = value.slice()
|
||||||
|
|
||||||
|
newTags[value.indexOf(tag)] = updatedTag
|
||||||
|
this.value = newTags
|
||||||
|
this.props.onChange()
|
||||||
|
}
|
||||||
|
|
||||||
handleTagLabelClick(tag) {
|
handleTagLabelClick(tag) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
|||||||
import styles from './SideNav.styl'
|
import styles from './SideNav.styl'
|
||||||
import { openModal } from 'browser/main/lib/modal'
|
import { openModal } from 'browser/main/lib/modal'
|
||||||
import PreferencesModal from '../modals/PreferencesModal'
|
import PreferencesModal from '../modals/PreferencesModal'
|
||||||
|
import RenameTagModal from 'browser/main/modals/RenameTagModal'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import StorageItem from './StorageItem'
|
import StorageItem from './StorageItem'
|
||||||
import TagListItem from 'browser/components/TagListItem'
|
import TagListItem from 'browser/components/TagListItem'
|
||||||
@@ -170,6 +171,11 @@ class SideNav extends React.Component {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
menu.push({
|
||||||
|
label: i18n.__('Rename Tag'),
|
||||||
|
click: this.handleRenameTagClick.bind(this, tag)
|
||||||
|
})
|
||||||
|
|
||||||
context.popup(menu)
|
context.popup(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +199,16 @@ class SideNav extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRenameTagClick(tagName) {
|
||||||
|
const { data, dispatch } = this.props
|
||||||
|
|
||||||
|
openModal(RenameTagModal, {
|
||||||
|
tagName,
|
||||||
|
data,
|
||||||
|
dispatch
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleColorPickerConfirm(color) {
|
handleColorPickerConfirm(color) {
|
||||||
const {
|
const {
|
||||||
dispatch,
|
dispatch,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './RenameFolderModal.styl'
|
import styles from './RenameModal.styl'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import { store } from 'browser/main/store'
|
import { store } from 'browser/main/store'
|
||||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
|
|||||||
@@ -46,13 +46,18 @@
|
|||||||
font-size 14px
|
font-size 14px
|
||||||
colorPrimaryButton()
|
colorPrimaryButton()
|
||||||
|
|
||||||
|
.error
|
||||||
|
text-align center
|
||||||
|
color #F44336
|
||||||
|
height 20px
|
||||||
|
|
||||||
apply-theme(theme)
|
apply-theme(theme)
|
||||||
body[data-theme={theme}]
|
body[data-theme={theme}]
|
||||||
.root
|
.root
|
||||||
background-color transparent
|
background-color transparent
|
||||||
|
|
||||||
.header
|
.header
|
||||||
background-color get-theme-var(theme, 'button--hover-backgroundColor')
|
background-color transparent
|
||||||
border-color get-theme-var(theme, 'borderColor')
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
color get-theme-var(theme, 'text-color')
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
196
browser/main/modals/RenameTagModal.js
Normal file
196
browser/main/modals/RenameTagModal.js
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './RenameModal.styl'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import { replace } from 'connected-react-router'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import { isEmpty } from 'lodash'
|
||||||
|
import electron from 'electron'
|
||||||
|
|
||||||
|
const { remote } = electron
|
||||||
|
const { dialog } = remote
|
||||||
|
|
||||||
|
class RenameTagModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.nameInput = null
|
||||||
|
|
||||||
|
this.handleChange = this.handleChange.bind(this)
|
||||||
|
|
||||||
|
this.setTextInputRef = el => {
|
||||||
|
this.nameInput = el
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
name: props.tagName,
|
||||||
|
oldName: props.tagName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.nameInput.focus()
|
||||||
|
this.nameInput.select()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e) {
|
||||||
|
this.setState({
|
||||||
|
name: this.nameInput.value,
|
||||||
|
showerror: false,
|
||||||
|
errormessage: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(e) {
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
this.props.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputKeyDown(e) {
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case 13:
|
||||||
|
this.handleConfirm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirm() {
|
||||||
|
if (this.state.name.trim().length > 0) {
|
||||||
|
const { name, oldName } = this.state
|
||||||
|
this.renameTag(oldName, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(message) {
|
||||||
|
this.setState({
|
||||||
|
showerror: true,
|
||||||
|
errormessage: message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renameTag(tag, updatedTag) {
|
||||||
|
const { data, dispatch } = this.props
|
||||||
|
|
||||||
|
if (tag === updatedTag) {
|
||||||
|
// confirm with-out any change - just dismiss the modal
|
||||||
|
this.props.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
data.noteMap
|
||||||
|
.map(note => note)
|
||||||
|
.some(note => note.tags.indexOf(updatedTag) !== -1)
|
||||||
|
) {
|
||||||
|
const alertConfig = {
|
||||||
|
type: 'warning',
|
||||||
|
message: i18n.__('Confirm tag merge'),
|
||||||
|
detail: i18n.__(
|
||||||
|
`Tag ${tag} will be merged with existing tag ${updatedTag}`
|
||||||
|
),
|
||||||
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogButtonIndex = dialog.showMessageBox(
|
||||||
|
remote.getCurrentWindow(),
|
||||||
|
alertConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
if (dialogButtonIndex === 1) {
|
||||||
|
return // bail early on cancel click
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const notes = data.noteMap
|
||||||
|
.map(note => note)
|
||||||
|
.filter(
|
||||||
|
note => note.tags.indexOf(tag) !== -1 && note.tags.indexOf(updatedTag)
|
||||||
|
)
|
||||||
|
.map(note => {
|
||||||
|
note = Object.assign({}, note)
|
||||||
|
note.tags = note.tags.slice()
|
||||||
|
|
||||||
|
note.tags[note.tags.indexOf(tag)] = updatedTag
|
||||||
|
|
||||||
|
return note
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isEmpty(notes)) {
|
||||||
|
this.showError(i18n.__('Tag exists'))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(
|
||||||
|
notes.map(note => dataApi.updateNote(note.storage, note.key, note))
|
||||||
|
)
|
||||||
|
.then(updatedNotes => {
|
||||||
|
updatedNotes.forEach(note => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (window.location.hash.includes(tag)) {
|
||||||
|
dispatch(replace(`/tags/${updatedTag}`))
|
||||||
|
}
|
||||||
|
ee.emit('sidebar:rename-tag', { tag, updatedTag })
|
||||||
|
this.props.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { close } = this.props
|
||||||
|
const { errormessage } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
styleName='root'
|
||||||
|
tabIndex='-1'
|
||||||
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
|
>
|
||||||
|
<div styleName='header'>
|
||||||
|
<div styleName='title'>{i18n.__('Rename Tag')}</div>
|
||||||
|
</div>
|
||||||
|
<ModalEscButton handleEscButtonClick={close} />
|
||||||
|
|
||||||
|
<div styleName='control'>
|
||||||
|
<input
|
||||||
|
styleName='control-input'
|
||||||
|
placeholder={i18n.__('Tag Name')}
|
||||||
|
ref={this.setTextInputRef}
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
onKeyDown={e => this.handleInputKeyDown(e)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
styleName='control-confirmButton'
|
||||||
|
onClick={() => this.handleConfirm()}
|
||||||
|
>
|
||||||
|
{i18n.__('Confirm')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='error' styleName='error'>
|
||||||
|
{errormessage}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenameTagModal.propTypes = {
|
||||||
|
storage: PropTypes.shape({
|
||||||
|
key: PropTypes.string
|
||||||
|
}),
|
||||||
|
folder: PropTypes.shape({
|
||||||
|
key: PropTypes.string,
|
||||||
|
name: PropTypes.string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(RenameTagModal, styles)
|
||||||
Reference in New Issue
Block a user