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.js
pull/1257/head
Caleb John 2019-02-24 03:55:19 -07:00 committed by Laurent Cozic
parent 1e0c4cc5cd
commit 229dd7a6dd
6 changed files with 142 additions and 11 deletions

View File

@ -132,15 +132,17 @@ class MainScreenComponent extends React.Component {
} else if (command.name === 'setTags') {
const tags = await Tag.tagsByNoteId(command.noteId);
const tagTitles = tags.map((a) => { return a.title }).sort();
const allTags = await Tag.allWithNotes();
this.setState({
promptOptions: {
label: _('Add or remove tags:'),
description: _('Separate each tag by a comma.'),
value: tagTitles.join(', '),
inputType: 'tags',
value: tagTitles,
autocomplete: allTags,
onClose: async (answer) => {
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);
}
this.setState({ promptOptions: null });

View File

@ -5,13 +5,21 @@ const moment = require('moment');
const { themeStyle } = require('../theme.js');
const { time } = require('lib/time-utils.js');
const Datetime = require('react-datetime');
const TagList = require('./TagList.min.js');
const Tag = require('lib/models/Tag.js');
class PromptDialog extends React.Component {
componentWillMount() {
let answer = ''
if (this.props.inputType !== 'tags' && this.props.defaultValue) {
answer = this.props.defaultValue;
}
this.setState({
visible: false,
answer: this.props.defaultValue ? this.props.defaultValue : '',
answer: answer,
tags: this.props.inputType === 'tags' ? this.props.defaultValue : null,
});
this.focusInput_ = true;
}
@ -23,7 +31,11 @@ class PromptDialog extends React.Component {
}
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,
padding: 16,
display: 'inline-block',
maxWidth: width * 0.5,
boxShadow: '6px 6px 20px rgba(0,0,0,0.5)',
};
@ -92,6 +105,11 @@ class PromptDialog extends React.Component {
borderColor: theme.dividerColor,
};
this.styles_.tagList = {
marginBottom: 10,
marginTop: 10,
};
this.styles_.desc = Object.assign({}, theme.textStyle, {
marginTop: 10,
});
@ -113,6 +131,9 @@ class PromptDialog extends React.Component {
// outputAnswer = anythingToDate(outputAnswer);
outputAnswer = time.anythingToDateTime(outputAnswer);
}
else if (this.props.inputType === 'tags') {
outputAnswer = this.state.tags;
}
this.props.onClose(accept ? outputAnswer : null, buttonType);
}
this.setState({ visible: false, answer: '' });
@ -137,15 +158,35 @@ class PromptDialog extends React.Component {
const onKeyDown = (event) => {
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') {
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;
let inputComp = null;
let dataList = null;
let tagList = null;
if (this.props.inputType === 'datetime') {
inputComp = <Datetime
@ -161,11 +202,31 @@ class PromptDialog extends React.Component {
ref={input => this.answerInput_ = input}
value={this.state.answer}
type="text"
list={this.props.inputType === "tags" ? "tags" : null}
onChange={(event) => onChange(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 = [];
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>);
@ -177,7 +238,9 @@ class PromptDialog extends React.Component {
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
<div style={{display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor}}>
{inputComp}
{dataList}
{descComp}
{tagList}
</div>
<div style={{ textAlign: 'right', marginTop: 10 }}>
{buttonComps}

View File

@ -1,14 +1,68 @@
const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
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() {
const theme = themeStyle(this.props.theme);
const style = Object.assign({}, theme.tagStyle);
const title = this.props.title;
let style = Object.assign({textAlign: 'center'}, theme.tagStyle);
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>
);
}
}

View File

@ -10,7 +10,7 @@ class TagListComponent extends React.Component {
const tags = this.props.items;
style.display = 'flex';
style.flexDirection = 'row';
style.flexWrap = 'wrap';
style.borderBottom = '1px solid ' + theme.dividerColor;
style.boxSizing = 'border-box';
style.fontSize = theme.fontSize;
@ -23,7 +23,8 @@ class TagListComponent extends React.Component {
for (let i = 0; i < tags.length; i++) {
const props = {
title: tags[i].title,
key: tags[i].id
key: tags[i].id,
onDelete: this.props.onDeleteItem,
};
tagItems.push(<TagItem {...props} />);
}

View File

@ -59,6 +59,14 @@ table td, table th {
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 .synchronize-button:hover {
background-color: #01427B;

View File

@ -70,6 +70,7 @@ const lightStyle = {
dividerColor: "#dddddd",
selectedColor: '#e5e5e5',
urlColor: '#155BDA',
removeColor: '#B01C2E',
backgroundColor2: "#162B3D",
color2: "#ffffff",
@ -106,6 +107,7 @@ const darkStyle = {
dividerColor: '#555555',
selectedColor: '#333333',
urlColor: '#4E87EE',
removeColor: '#B01C2E',
backgroundColor2: "#181A1D",
color2: "#ffffff",
@ -142,6 +144,7 @@ function addExtraStyles(style) {
paddingRight: style.tagItemPadding * 2,
paddingLeft: style.tagItemPadding * 2,
backgroundColor: style.raisedBackgroundColor,
borderRadius: '10%',
color: style.raisedColor,
};