1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-12-02 22:49:09 +02:00

Mobile: Resolves #285: Create, edit and remove tags from notes

This commit is contained in:
Laurent Cozic
2018-03-16 20:17:52 +00:00
parent 544f93bf22
commit aabb9be7de
9 changed files with 270 additions and 17 deletions

View File

@@ -46,6 +46,13 @@ globalStyle.lineInput = {
backgroundColor: globalStyle.backgroundColor,
};
globalStyle.buttonRow = {
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: globalStyle.dividerColor,
paddingTop: 10,
};
let themeCache_ = {};
function themeStyle(theme) {

View File

@@ -128,6 +128,8 @@ class ScreenHeaderComponent extends Component {
color: theme.raisedHighlightedColor,
fontWeight: 'bold',
fontSize: theme.fontSize,
paddingTop: 15,
paddingBottom: 15,
},
warningBox: {
backgroundColor: "#ff9900",
@@ -428,15 +430,19 @@ class ScreenHeaderComponent extends Component {
</TouchableOpacity>
) : null;
const showSideMenuButton = this.props.showSideMenuButton !== false && !this.props.noteSelectionEnabled;
const showSearchButton = this.props.showSearchButton !== false && !this.props.noteSelectionEnabled;
const showContextMenuButton = this.props.showContextMenuButton !== false;
const titleComp = createTitleComponent();
const sideMenuComp = this.props.noteSelectionEnabled ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
const sideMenuComp = !showSideMenuButton ? null : sideMenuButton(this.styles(), () => this.sideMenuButton_press());
const backButtonComp = backButton(this.styles(), () => this.backButton_press(), !this.props.historyCanGoBack);
const searchButtonComp = this.props.noteSelectionEnabled ? null : searchButton(this.styles(), () => this.searchButton_press());
const searchButtonComp = !showSearchButton ? null : searchButton(this.styles(), () => this.searchButton_press());
const deleteButtonComp = this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press()) : null;
const sortButtonComp = this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
const windowHeight = Dimensions.get('window').height - 50;
const menuComp = (
const menuComp = !showContextMenuButton ? null : (
<Menu onSelect={(value) => this.menu_select(value)} style={this.styles().contextMenu}>
<MenuTrigger style={{ paddingTop: PADDING_V, paddingBottom: PADDING_V }}>
<Text style={this.styles().contextMenuTrigger}> &#8942;</Text>

View File

@@ -0,0 +1,201 @@
const React = require('react'); const Component = React.Component;
const { ListView, StyleSheet, View, Text, Button, FlatList, TouchableOpacity, TextInput } = require('react-native');
const Setting = require('lib/models/Setting.js');
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { ScreenHeader } = require('lib/components/screen-header.js');
const { time } = require('lib/time-utils');
const { Logger } = require('lib/logger.js');
const BaseItem = require('lib/models/BaseItem.js');
const Tag = require('lib/models/Tag.js');
const { Database } = require('lib/database.js');
const Folder = require('lib/models/Folder.js');
const { ReportService } = require('lib/services/report.js');
const { _ } = require('lib/locale.js');
const { BaseScreenComponent } = require('lib/components/base-screen.js');
const { globalStyle, themeStyle } = require('lib/components/global-style.js');
const Icon = require('react-native-vector-icons/Ionicons').default;
const styles = StyleSheet.create({
body: {
flex: 1,
margin: globalStyle.margin,
},
});
class NoteTagsScreenComponent extends BaseScreenComponent {
constructor() {
super();
this.styles_ = {};
this.state = {
noteTagIds: [],
noteId: null,
tagListData: [],
newTags: '',
savingTags: false,
};
const noteHasTag = (tagId) => {
for (let i = 0; i < this.state.tagListData.length; i++) {
if (this.state.tagListData[i].id === tagId) return this.state.tagListData[i].selected;
}
return false;
}
const newTagTitles = () => {
return this.state.newTags
.split(',')
.map(t => t.trim().toLowerCase())
.filter(t => !!t);
}
this.tag_press = (tagId) => {
const newData = this.state.tagListData.slice();
for (let i = 0; i < newData.length; i++) {
const t = newData[i];
if (t.id === tagId) {
const newTag = Object.assign({}, t);
newTag.selected = !newTag.selected;
newData[i] = newTag;
break;
}
}
this.setState({ tagListData: newData });
}
this.renderTag = (data) => {
const tag = data.item;
const iconName = noteHasTag(tag.id) ? 'md-checkbox-outline' : 'md-square-outline';
return (
<TouchableOpacity key={tag.id} onPress={() => this.tag_press(tag.id)} style={this.styles().tag}>
<View style={this.styles().tagIconText}>
<Icon name={iconName} style={this.styles().tagCheckbox}/><Text>{tag.title}</Text>
</View>
</TouchableOpacity>
);
}
this.tagKeyExtractor = (tag, index) => tag.id;
this.okButton_press = async () => {
this.setState({ savingTags: true });
try {
const tagIds = this.state.tagListData.filter(t => t.selected).map(t => t.id);
await Tag.setNoteTagsByIds(this.state.noteId, tagIds);
const extraTitles = newTagTitles();
for (let i = 0; i < extraTitles.length; i++) {
await Tag.addNoteTagByTitle(this.state.noteId, extraTitles[i]);
}
} finally {
this.setState({ savingTags: false });
}
this.props.dispatch({
type: 'NAV_BACK',
});
}
this.cancelButton_press = () => {
this.props.dispatch({
type: 'NAV_BACK',
});
}
}
componentWillMount() {
const noteId = this.props.noteId;
this.setState({ noteId: noteId });
this.loadNoteTags(noteId);
}
async loadNoteTags(noteId) {
const tags = await Tag.tagsByNoteId(noteId);
const tagIds = tags.map(t => t.id);
const tagListData = this.props.tags.map(tag => { return {
id: tag.id,
title: tag.title,
selected: tagIds.indexOf(tag.id) >= 0,
}});
this.setState({ tagListData: tagListData });
}
styles() {
const themeId = this.props.theme;
const theme = themeStyle(themeId);
if (this.styles_[themeId]) return this.styles_[themeId];
this.styles_ = {};
let styles = {
tag: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: theme.dividerColor,
},
tagIconText: {
flexDirection: 'row',
alignItems: 'center',
},
tagCheckbox: {
marginRight: 5,
fontSize: 20,
},
newTagBox: {
flexDirection:'row',
alignItems: 'center',
paddingLeft: theme.marginLeft,
paddingRight: theme.marginRight,
borderTopWidth: 1,
borderTopColor: theme.dividerColor
},
};
this.styles_[themeId] = StyleSheet.create(styles);
return this.styles_[themeId];
}
render() {
const theme = themeStyle(this.props.theme);
return (
<View style={this.rootStyle(this.props.theme).root}>
<ScreenHeader title={_('Note tags')} showSideMenuButton={false} showSearchButton={false} showContextMenuButton={false}/>
<FlatList
data={this.state.tagListData}
renderItem={this.renderTag}
keyExtractor={this.tagKeyExtractor}
/>
<View style={this.styles().newTagBox}>
<Text>{_('Or type tags:')}</Text><TextInput value={this.state.newTags} onChangeText={value => { this.setState({ newTags: value }) }} style={{flex:1}}/>
</View>
<View style={theme.buttonRow}>
<View style={{flex:1}}>
<Button disabled={this.state.savingTags} title={_('OK')} onPress={this.okButton_press}></Button>
</View>
<View style={{flex:1, marginLeft: 5}}>
<Button disabled={this.state.savingTags} title={_('Cancel')} onPress={this.cancelButton_press}></Button>
</View>
</View>
</View>
);
}
}
const NoteTagsScreen = connect(
(state) => {
return {
theme: state.settings.theme,
tags: state.tags,
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
};
}
)(NoteTagsScreenComponent)
module.exports = { NoteTagsScreen };

View File

@@ -357,6 +357,16 @@ class NoteScreenComponent extends BaseScreenComponent {
shared.toggleIsTodo_onPress(this);
}
tags_onPress() {
if (!this.state.note || !this.state.note.id) return;
this.props.dispatch({
type: 'NAV_GO',
routeName: 'NoteTags',
noteId: this.state.note.id,
});
}
setAlarm_onPress() {
this.setState({ alarmDialogShown: true });
}
@@ -393,6 +403,7 @@ class NoteScreenComponent extends BaseScreenComponent {
menuOptions() {
const note = this.state.note;
const isTodo = note && !!note.is_todo;
const isSaved = note && note.id;
let output = [];
@@ -410,6 +421,7 @@ class NoteScreenComponent extends BaseScreenComponent {
output.push({ title: _('Set alarm'), onPress: () => { this.setState({ alarmDialogShown: true }) }});;
}
if (isSaved) output.push({ title: _('Tags'), onPress: () => { this.tags_onPress(); } });
output.push({ title: isTodo ? _('Convert to note') : _('Convert to todo'), onPress: () => { this.toggleIsTodo_onPress(); } });
output.push({ isDivider: true });
if (this.props.showAdvancedOptions) output.push({ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } });