mirror of https://github.com/laurent22/joplin.git
New tag adding dialogue (#1206)
* Update tag adding dialogue - use datalist to autocomplete tags - display tags with TagList * Move the tagItem highlight color to theme.jspull/1257/head
parent
1e0c4cc5cd
commit
229dd7a6dd
|
@ -132,15 +132,17 @@ class MainScreenComponent extends React.Component {
|
||||||
} else if (command.name === 'setTags') {
|
} else if (command.name === 'setTags') {
|
||||||
const tags = await Tag.tagsByNoteId(command.noteId);
|
const tags = await Tag.tagsByNoteId(command.noteId);
|
||||||
const tagTitles = tags.map((a) => { return a.title }).sort();
|
const tagTitles = tags.map((a) => { return a.title }).sort();
|
||||||
|
const allTags = await Tag.allWithNotes();
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
promptOptions: {
|
promptOptions: {
|
||||||
label: _('Add or remove tags:'),
|
label: _('Add or remove tags:'),
|
||||||
description: _('Separate each tag by a comma.'),
|
inputType: 'tags',
|
||||||
value: tagTitles.join(', '),
|
value: tagTitles,
|
||||||
|
autocomplete: allTags,
|
||||||
onClose: async (answer) => {
|
onClose: async (answer) => {
|
||||||
if (answer !== null) {
|
if (answer !== null) {
|
||||||
const tagTitles = answer.split(',').map((a) => { return a.trim() });
|
const tagTitles = answer.map((a) => { return a.trim() });
|
||||||
await Tag.setNoteTagsByTitles(command.noteId, tagTitles);
|
await Tag.setNoteTagsByTitles(command.noteId, tagTitles);
|
||||||
}
|
}
|
||||||
this.setState({ promptOptions: null });
|
this.setState({ promptOptions: null });
|
||||||
|
|
|
@ -5,13 +5,21 @@ const moment = require('moment');
|
||||||
const { themeStyle } = require('../theme.js');
|
const { themeStyle } = require('../theme.js');
|
||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
const Datetime = require('react-datetime');
|
const Datetime = require('react-datetime');
|
||||||
|
const TagList = require('./TagList.min.js');
|
||||||
|
const Tag = require('lib/models/Tag.js');
|
||||||
|
|
||||||
class PromptDialog extends React.Component {
|
class PromptDialog extends React.Component {
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
let answer = ''
|
||||||
|
if (this.props.inputType !== 'tags' && this.props.defaultValue) {
|
||||||
|
answer = this.props.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
visible: false,
|
visible: false,
|
||||||
answer: this.props.defaultValue ? this.props.defaultValue : '',
|
answer: answer,
|
||||||
|
tags: this.props.inputType === 'tags' ? this.props.defaultValue : null,
|
||||||
});
|
});
|
||||||
this.focusInput_ = true;
|
this.focusInput_ = true;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +31,11 @@ class PromptDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('defaultValue' in newProps && newProps.defaultValue !== this.props.defaultValue) {
|
if ('defaultValue' in newProps && newProps.defaultValue !== this.props.defaultValue) {
|
||||||
this.setState({ answer: newProps.defaultValue });
|
if ('inputType' in newProps && newProps.inputType === 'tags') {
|
||||||
|
this.setState({ answer: '', tags: newProps.defaultValue });
|
||||||
|
} else {
|
||||||
|
this.setState({ answer: newProps.defaultValue });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +74,7 @@ class PromptDialog extends React.Component {
|
||||||
backgroundColor: theme.backgroundColor,
|
backgroundColor: theme.backgroundColor,
|
||||||
padding: 16,
|
padding: 16,
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
|
maxWidth: width * 0.5,
|
||||||
boxShadow: '6px 6px 20px rgba(0,0,0,0.5)',
|
boxShadow: '6px 6px 20px rgba(0,0,0,0.5)',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -92,6 +105,11 @@ class PromptDialog extends React.Component {
|
||||||
borderColor: theme.dividerColor,
|
borderColor: theme.dividerColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.styles_.tagList = {
|
||||||
|
marginBottom: 10,
|
||||||
|
marginTop: 10,
|
||||||
|
};
|
||||||
|
|
||||||
this.styles_.desc = Object.assign({}, theme.textStyle, {
|
this.styles_.desc = Object.assign({}, theme.textStyle, {
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
});
|
});
|
||||||
|
@ -113,6 +131,9 @@ class PromptDialog extends React.Component {
|
||||||
// outputAnswer = anythingToDate(outputAnswer);
|
// outputAnswer = anythingToDate(outputAnswer);
|
||||||
outputAnswer = time.anythingToDateTime(outputAnswer);
|
outputAnswer = time.anythingToDateTime(outputAnswer);
|
||||||
}
|
}
|
||||||
|
else if (this.props.inputType === 'tags') {
|
||||||
|
outputAnswer = this.state.tags;
|
||||||
|
}
|
||||||
this.props.onClose(accept ? outputAnswer : null, buttonType);
|
this.props.onClose(accept ? outputAnswer : null, buttonType);
|
||||||
}
|
}
|
||||||
this.setState({ visible: false, answer: '' });
|
this.setState({ visible: false, answer: '' });
|
||||||
|
@ -137,15 +158,35 @@ class PromptDialog extends React.Component {
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
onClose(true);
|
if (this.state.answer.trim() !== '') {
|
||||||
|
let newTags = this.state.tags;
|
||||||
|
if (newTags.indexOf(this.state.answer) === -1) {
|
||||||
|
newTags.push(this.state.answer);
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
tags: newTags,
|
||||||
|
answer: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
} else if (event.key === 'Escape') {
|
} else if (event.key === 'Escape') {
|
||||||
onClose(false);
|
onClose(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onDeleteTag = (tag) => {
|
||||||
|
let newTags = this.state.tags;
|
||||||
|
var index = newTags.indexOf(tag);
|
||||||
|
if (index !== -1) newTags.splice(index, 1);
|
||||||
|
this.setState({
|
||||||
|
tags: newTags,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const descComp = this.props.description ? <div style={styles.desc}>{this.props.description}</div> : null;
|
const descComp = this.props.description ? <div style={styles.desc}>{this.props.description}</div> : null;
|
||||||
|
|
||||||
let inputComp = null;
|
let inputComp = null;
|
||||||
|
let dataList = null;
|
||||||
|
let tagList = null;
|
||||||
|
|
||||||
if (this.props.inputType === 'datetime') {
|
if (this.props.inputType === 'datetime') {
|
||||||
inputComp = <Datetime
|
inputComp = <Datetime
|
||||||
|
@ -161,11 +202,31 @@ class PromptDialog extends React.Component {
|
||||||
ref={input => this.answerInput_ = input}
|
ref={input => this.answerInput_ = input}
|
||||||
value={this.state.answer}
|
value={this.state.answer}
|
||||||
type="text"
|
type="text"
|
||||||
|
list={this.props.inputType === "tags" ? "tags" : null}
|
||||||
onChange={(event) => onChange(event)}
|
onChange={(event) => onChange(event)}
|
||||||
onKeyDown={(event) => onKeyDown(event)}
|
onKeyDown={(event) => onKeyDown(event)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.inputType === 'tags') {
|
||||||
|
tagList = <TagList
|
||||||
|
style={styles.tagList}
|
||||||
|
onDeleteItem={onDeleteTag}
|
||||||
|
items={this.state.tags.map((a) => {
|
||||||
|
return {title: a, id: a}
|
||||||
|
})}
|
||||||
|
/>;
|
||||||
|
|
||||||
|
dataList = <datalist id="tags">
|
||||||
|
{this.props.autocomplete.map((a) => {
|
||||||
|
if (this.state.tags.indexOf(a.title) === -1) {
|
||||||
|
return <option value={a.title} key={a.id} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</datalist>
|
||||||
|
}
|
||||||
|
|
||||||
const buttonComps = [];
|
const buttonComps = [];
|
||||||
if (buttonTypes.indexOf('ok') >= 0) buttonComps.push(<button key="ok" style={styles.button} onClick={() => onClose(true, 'ok')}>{_('OK')}</button>);
|
if (buttonTypes.indexOf('ok') >= 0) buttonComps.push(<button key="ok" style={styles.button} onClick={() => onClose(true, 'ok')}>{_('OK')}</button>);
|
||||||
if (buttonTypes.indexOf('cancel') >= 0) buttonComps.push(<button key="cancel" style={styles.button} onClick={() => onClose(false, 'cancel')}>{_('Cancel')}</button>);
|
if (buttonTypes.indexOf('cancel') >= 0) buttonComps.push(<button key="cancel" style={styles.button} onClick={() => onClose(false, 'cancel')}>{_('Cancel')}</button>);
|
||||||
|
@ -177,7 +238,9 @@ class PromptDialog extends React.Component {
|
||||||
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
|
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
|
||||||
<div style={{display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor}}>
|
<div style={{display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor}}>
|
||||||
{inputComp}
|
{inputComp}
|
||||||
|
{dataList}
|
||||||
{descComp}
|
{descComp}
|
||||||
|
{tagList}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'right', marginTop: 10 }}>
|
<div style={{ textAlign: 'right', marginTop: 10 }}>
|
||||||
{buttonComps}
|
{buttonComps}
|
||||||
|
|
|
@ -1,14 +1,68 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { themeStyle } = require('../theme.js');
|
const { themeStyle } = require('../theme.js');
|
||||||
|
const { _ } = require('lib/locale.js');
|
||||||
|
|
||||||
class TagItemComponent extends React.Component {
|
class TagItemComponent extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.clickAway = this.clickAway.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.setState({
|
||||||
|
title: this.props.title,
|
||||||
|
toDelete: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clickAway(event) {
|
||||||
|
if (this.span_ && !this.span_.contains(event.target)) {
|
||||||
|
this.setState({
|
||||||
|
title: this.props.title,
|
||||||
|
toDelete: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.addEventListener('mousedown', this.clickAway);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener('mousedown', this.clickAway);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const theme = themeStyle(this.props.theme);
|
const theme = themeStyle(this.props.theme);
|
||||||
const style = Object.assign({}, theme.tagStyle);
|
let style = Object.assign({textAlign: 'center'}, theme.tagStyle);
|
||||||
const title = this.props.title;
|
|
||||||
|
|
||||||
return <span style={style}>{title}</span>;
|
if (this.state.toDelete) {
|
||||||
|
style = Object.assign({}, style, {backgroundColor: theme.removeColor});
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClick = (event) => {
|
||||||
|
if (this.state.toDelete) {
|
||||||
|
this.props.onDelete(this.props.title);
|
||||||
|
}
|
||||||
|
else if (this.props.onDelete) {
|
||||||
|
this.setState({
|
||||||
|
title: _("Remove?"),
|
||||||
|
toDelete: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
ref={span => this.span_ = span}
|
||||||
|
onClick={onClick}
|
||||||
|
style={style}
|
||||||
|
value={this.state.title}>
|
||||||
|
{this.state.title}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class TagListComponent extends React.Component {
|
||||||
const tags = this.props.items;
|
const tags = this.props.items;
|
||||||
|
|
||||||
style.display = 'flex';
|
style.display = 'flex';
|
||||||
style.flexDirection = 'row';
|
style.flexWrap = 'wrap';
|
||||||
style.borderBottom = '1px solid ' + theme.dividerColor;
|
style.borderBottom = '1px solid ' + theme.dividerColor;
|
||||||
style.boxSizing = 'border-box';
|
style.boxSizing = 'border-box';
|
||||||
style.fontSize = theme.fontSize;
|
style.fontSize = theme.fontSize;
|
||||||
|
@ -23,7 +23,8 @@ class TagListComponent extends React.Component {
|
||||||
for (let i = 0; i < tags.length; i++) {
|
for (let i = 0; i < tags.length; i++) {
|
||||||
const props = {
|
const props = {
|
||||||
title: tags[i].title,
|
title: tags[i].title,
|
||||||
key: tags[i].id
|
key: tags[i].id,
|
||||||
|
onDelete: this.props.onDeleteItem,
|
||||||
};
|
};
|
||||||
tagItems.push(<TagItem {...props} />);
|
tagItems.push(<TagItem {...props} />);
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,14 @@ table td, table th {
|
||||||
background-color: rgba(0,160,255,0.1) !important;
|
background-color: rgba(0,160,255,0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input::-webkit-calendar-picker-indicator {
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
input::-webkit-calendar-picker-indicator:hover {
|
||||||
|
background-color: rgba(100, 100, 100, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
/*.side-bar .list-item:hover,
|
/*.side-bar .list-item:hover,
|
||||||
.side-bar .synchronize-button:hover {
|
.side-bar .synchronize-button:hover {
|
||||||
background-color: #01427B;
|
background-color: #01427B;
|
||||||
|
|
|
@ -70,6 +70,7 @@ const lightStyle = {
|
||||||
dividerColor: "#dddddd",
|
dividerColor: "#dddddd",
|
||||||
selectedColor: '#e5e5e5',
|
selectedColor: '#e5e5e5',
|
||||||
urlColor: '#155BDA',
|
urlColor: '#155BDA',
|
||||||
|
removeColor: '#B01C2E',
|
||||||
|
|
||||||
backgroundColor2: "#162B3D",
|
backgroundColor2: "#162B3D",
|
||||||
color2: "#ffffff",
|
color2: "#ffffff",
|
||||||
|
@ -106,6 +107,7 @@ const darkStyle = {
|
||||||
dividerColor: '#555555',
|
dividerColor: '#555555',
|
||||||
selectedColor: '#333333',
|
selectedColor: '#333333',
|
||||||
urlColor: '#4E87EE',
|
urlColor: '#4E87EE',
|
||||||
|
removeColor: '#B01C2E',
|
||||||
|
|
||||||
backgroundColor2: "#181A1D",
|
backgroundColor2: "#181A1D",
|
||||||
color2: "#ffffff",
|
color2: "#ffffff",
|
||||||
|
@ -142,6 +144,7 @@ function addExtraStyles(style) {
|
||||||
paddingRight: style.tagItemPadding * 2,
|
paddingRight: style.tagItemPadding * 2,
|
||||||
paddingLeft: style.tagItemPadding * 2,
|
paddingLeft: style.tagItemPadding * 2,
|
||||||
backgroundColor: style.raisedBackgroundColor,
|
backgroundColor: style.raisedBackgroundColor,
|
||||||
|
borderRadius: '10%',
|
||||||
color: style.raisedColor,
|
color: style.raisedColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue