mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +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.handleRenameTag = this.handleRenameTag.bind(this)
|
||||
this.onInputBlur = this.onInputBlur.bind(this)
|
||||
this.onInputChange = this.onInputChange.bind(this)
|
||||
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
||||
@@ -88,6 +89,7 @@ class TagSelect extends React.Component {
|
||||
this.buildSuggestions()
|
||||
|
||||
ee.on('editor:add-tag', this.handleAddTag)
|
||||
ee.on('sidebar:rename-tag', this.handleRenameTag)
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -96,12 +98,23 @@ class TagSelect extends React.Component {
|
||||
|
||||
componentWillUnmount() {
|
||||
ee.off('editor:add-tag', this.handleAddTag)
|
||||
ee.off('sidebar:rename-tag', this.handleRenameTag)
|
||||
}
|
||||
|
||||
handleAddTag() {
|
||||
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) {
|
||||
const { dispatch } = this.props
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './SideNav.styl'
|
||||
import { openModal } from 'browser/main/lib/modal'
|
||||
import PreferencesModal from '../modals/PreferencesModal'
|
||||
import RenameTagModal from 'browser/main/modals/RenameTagModal'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import StorageItem from './StorageItem'
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -193,6 +199,16 @@ class SideNav extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleRenameTagClick(tagName) {
|
||||
const { data, dispatch } = this.props
|
||||
|
||||
openModal(RenameTagModal, {
|
||||
tagName,
|
||||
data,
|
||||
dispatch
|
||||
})
|
||||
}
|
||||
|
||||
handleColorPickerConfirm(color) {
|
||||
const {
|
||||
dispatch,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RenameFolderModal.styl'
|
||||
import styles from './RenameModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import { store } from 'browser/main/store'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
|
||||
@@ -46,13 +46,18 @@
|
||||
font-size 14px
|
||||
colorPrimaryButton()
|
||||
|
||||
.error
|
||||
text-align center
|
||||
color #F44336
|
||||
height 20px
|
||||
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
background-color transparent
|
||||
|
||||
.header
|
||||
background-color get-theme-var(theme, 'button--hover-backgroundColor')
|
||||
background-color transparent
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
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