mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
All: Allow sorting notes by various fields
This commit is contained in:
parent
74d255c056
commit
8a96cf3434
@ -175,6 +175,22 @@ class Application extends BaseApplication {
|
|||||||
updateMenu(screen) {
|
updateMenu(screen) {
|
||||||
if (this.lastMenuScreen_ === screen) return;
|
if (this.lastMenuScreen_ === screen) return;
|
||||||
|
|
||||||
|
const sortNoteItems = [];
|
||||||
|
const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field');
|
||||||
|
for (let field in sortNoteOptions) {
|
||||||
|
if (!sortNoteOptions.hasOwnProperty(field)) continue;
|
||||||
|
sortNoteItems.push({
|
||||||
|
label: sortNoteOptions[field],
|
||||||
|
screens: ['Main'],
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: Setting.value('notes.sortOrder.field') === field,
|
||||||
|
click: () => {
|
||||||
|
Setting.setValue('notes.sortOrder.field', field);
|
||||||
|
this.refreshMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const template = [
|
const template = [
|
||||||
{
|
{
|
||||||
label: _('File'),
|
label: _('File'),
|
||||||
@ -287,6 +303,29 @@ class Application extends BaseApplication {
|
|||||||
name: 'toggleVisiblePanes',
|
name: 'toggleVisiblePanes',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
type: 'separator',
|
||||||
|
screens: ['Main'],
|
||||||
|
}, {
|
||||||
|
label: Setting.settingMetadata('notes.sortOrder.field').label(),
|
||||||
|
screens: ['Main'],
|
||||||
|
submenu: sortNoteItems,
|
||||||
|
}, {
|
||||||
|
label: Setting.settingMetadata('notes.sortOrder.reverse').label(),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: Setting.setValue('notes.sortOrder.reverse'),
|
||||||
|
screens: ['Main'],
|
||||||
|
click: () => {
|
||||||
|
Setting.setValue('notes.sortOrder.reverse', !Setting.value('notes.sortOrder.reverse'));
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
label: Setting.settingMetadata('uncompletedTodosOnTop').label(),
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: Setting.setValue('uncompletedTodosOnTop'),
|
||||||
|
screens: ['Main'],
|
||||||
|
click: () => {
|
||||||
|
Setting.setValue('uncompletedTodosOnTop', !Setting.value('uncompletedTodosOnTop'));
|
||||||
|
},
|
||||||
}],
|
}],
|
||||||
}, {
|
}, {
|
||||||
label: _('Tools'),
|
label: _('Tools'),
|
||||||
|
@ -604,7 +604,7 @@ class NoteTextComponent extends React.Component {
|
|||||||
let bodyToRender = body;
|
let bodyToRender = body;
|
||||||
if (!bodyToRender.trim() && visiblePanes.indexOf('viewer') >= 0 && visiblePanes.indexOf('editor') < 0) {
|
if (!bodyToRender.trim() && visiblePanes.indexOf('viewer') >= 0 && visiblePanes.indexOf('editor') < 0) {
|
||||||
// Fixes https://github.com/laurent22/joplin/issues/217
|
// Fixes https://github.com/laurent22/joplin/issues/217
|
||||||
bodyToRender = '*' + _('This not has no content. Click on "%s" to toggle the editor and edit the note.', _('Layout')) + '*';
|
bodyToRender = '*' + _('This note has no content. Click on "%s" to toggle the editor and edit the note.', _('Layout')) + '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
const html = this.mdToHtml().render(bodyToRender, theme, mdOptions);
|
const html = this.mdToHtml().render(bodyToRender, theme, mdOptions);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const { createStore, applyMiddleware } = require('redux');
|
const { createStore, applyMiddleware } = require('redux');
|
||||||
const { reducer, defaultState } = require('lib/reducer.js');
|
const { reducer, defaultState, stateUtils } = require('lib/reducer.js');
|
||||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||||
const { Database } = require('lib/database.js');
|
const { Database } = require('lib/database.js');
|
||||||
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
const { FoldersScreenUtils } = require('lib/folders-screen-utils.js');
|
||||||
@ -184,8 +184,9 @@ class BaseApplication {
|
|||||||
this.logger().debug('Refreshing notes:', parentType, parentId);
|
this.logger().debug('Refreshing notes:', parentType, parentId);
|
||||||
|
|
||||||
let options = {
|
let options = {
|
||||||
order: state.notesOrder,
|
order: stateUtils.notesOrder(state.settings),
|
||||||
uncompletedTodosOnTop: Setting.value('uncompletedTodosOnTop'),
|
uncompletedTodosOnTop: Setting.value('uncompletedTodosOnTop'),
|
||||||
|
caseInsensitive: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const source = JSON.stringify({
|
const source = JSON.stringify({
|
||||||
@ -255,14 +256,31 @@ class BaseApplication {
|
|||||||
|
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
const newState = store.getState();
|
const newState = store.getState();
|
||||||
|
let refreshNotes = false;
|
||||||
|
|
||||||
if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE') {
|
if (action.type == 'FOLDER_SELECT' || action.type === 'FOLDER_DELETE') {
|
||||||
Setting.setValue('activeFolderId', newState.selectedFolderId);
|
Setting.setValue('activeFolderId', newState.selectedFolderId);
|
||||||
this.currentFolder_ = newState.selectedFolderId ? await Folder.load(newState.selectedFolderId) : null;
|
this.currentFolder_ = newState.selectedFolderId ? await Folder.load(newState.selectedFolderId) : null;
|
||||||
await this.refreshNotes(newState);
|
refreshNotes = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop' || action.type == 'SETTING_UPDATE_ALL') {
|
if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key == 'uncompletedTodosOnTop') || action.type == 'SETTING_UPDATE_ALL')) {
|
||||||
|
refreshNotes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.hasGui() && ((action.type == 'SETTING_UPDATE_ONE' && action.key.indexOf('notes.sortOrder') === 0) || action.type == 'SETTING_UPDATE_ALL')) {
|
||||||
|
refreshNotes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
|
||||||
|
refreshNotes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') {
|
||||||
|
refreshNotes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refreshNotes) {
|
||||||
await this.refreshNotes(newState);
|
await this.refreshNotes(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,14 +306,6 @@ class BaseApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
|
|
||||||
await this.refreshNotes(newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type == 'SEARCH_SELECT' || action.type === 'SEARCH_DELETE') {
|
|
||||||
await this.refreshNotes(newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.type === 'NOTE_UPDATE_ONE') {
|
if (action.type === 'NOTE_UPDATE_ONE') {
|
||||||
// If there is a conflict, we refresh the folders so as to display "Conflicts" folder
|
// If there is a conflict, we refresh the folders so as to display "Conflicts" folder
|
||||||
if (action.note && action.note.is_conflict) {
|
if (action.note && action.note.is_conflict) {
|
||||||
@ -303,11 +313,6 @@ class BaseApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (action.type === 'NOTE_DELETE') {
|
|
||||||
// // Update folders if a note is deleted in case the deleted note was a conflict
|
|
||||||
// await FoldersScreenUtils.refreshFolders();
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') {
|
if (this.hasGui() && action.type == 'SETTING_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTING_UPDATE_ALL') {
|
||||||
reg.setupRecurrentSync();
|
reg.setupRecurrentSync();
|
||||||
}
|
}
|
||||||
|
81
ReactNativeClient/lib/components/ModalDialog.js
Normal file
81
ReactNativeClient/lib/components/ModalDialog.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
const React = require('react');
|
||||||
|
const { Text, Modal, View, StyleSheet, Button } = require('react-native');
|
||||||
|
const { themeStyle } = require('lib/components/global-style.js');
|
||||||
|
const { _ } = require('lib/locale');
|
||||||
|
|
||||||
|
class ModalDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.styles_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
styles() {
|
||||||
|
const themeId = this.props.theme;
|
||||||
|
const theme = themeStyle(themeId);
|
||||||
|
|
||||||
|
if (this.styles_[themeId]) return this.styles_[themeId];
|
||||||
|
this.styles_ = {};
|
||||||
|
|
||||||
|
let styles = {
|
||||||
|
modalWrapper: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
modalContentWrapper: {
|
||||||
|
flex:1,
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: theme.backgroundColor,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor:theme.dividerColor,
|
||||||
|
margin: 20,
|
||||||
|
padding: 10,
|
||||||
|
},
|
||||||
|
modalContentWrapper2: {
|
||||||
|
paddingTop: 10,
|
||||||
|
flex:1,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: theme.dividerColor,
|
||||||
|
paddingBottom: 10,
|
||||||
|
},
|
||||||
|
buttonRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: theme.dividerColor,
|
||||||
|
paddingTop: 10,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.styles_[themeId] = StyleSheet.create(styles);
|
||||||
|
return this.styles_[themeId];
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const ContentComponent = this.props.ContentComponent;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={this.styles().modalWrapper}>
|
||||||
|
<Modal transparent={true} visible={true} onRequestClose={() => { }} >
|
||||||
|
<View style={this.styles().modalContentWrapper}>
|
||||||
|
<Text style={this.styles().title}>Title</Text>
|
||||||
|
<View style={this.styles().modalContentWrapper2}>
|
||||||
|
{ContentComponent}
|
||||||
|
</View>
|
||||||
|
<View style={this.styles().buttonRow}>
|
||||||
|
<View style={{flex:1}}>
|
||||||
|
<Button title={_('OK')} onPress={() => {}}></Button>
|
||||||
|
</View>
|
||||||
|
<View style={{flex:1, marginLeft: 5}}>
|
||||||
|
<Button title={_('Cancel')} onPress={() => {}}></Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ModalDialog;
|
@ -283,6 +283,16 @@ class ScreenHeaderComponent extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sortButton(styles, onPress) {
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={onPress}>
|
||||||
|
<View style={styles.iconButton}>
|
||||||
|
<Icon name='md-funnel' style={styles.topIcon} />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let key = 0;
|
let key = 0;
|
||||||
let menuOptionComponents = [];
|
let menuOptionComponents = [];
|
||||||
|
|
||||||
@ -424,6 +434,7 @@ class ScreenHeaderComponent extends Component {
|
|||||||
const backButtonComp = backButton(this.styles(), () => this.backButton_press(), !this.props.historyCanGoBack);
|
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 = this.props.noteSelectionEnabled ? null : searchButton(this.styles(), () => this.searchButton_press());
|
||||||
const deleteButtonComp = this.props.noteSelectionEnabled ? deleteButton(this.styles(), () => this.deleteButton_press()) : null;
|
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 windowHeight = Dimensions.get('window').height - 50;
|
||||||
|
|
||||||
const menuComp = (
|
const menuComp = (
|
||||||
@ -448,6 +459,7 @@ class ScreenHeaderComponent extends Component {
|
|||||||
{ titleComp }
|
{ titleComp }
|
||||||
{ searchButtonComp }
|
{ searchButtonComp }
|
||||||
{ deleteButtonComp }
|
{ deleteButtonComp }
|
||||||
|
{ sortButtonComp }
|
||||||
{ menuComp }
|
{ menuComp }
|
||||||
</View>
|
</View>
|
||||||
{ warningComp }
|
{ warningComp }
|
||||||
|
@ -65,9 +65,9 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
const saveDialog = async () => {
|
const saveDialog = async () => {
|
||||||
if (this.isModified()) {
|
if (this.isModified()) {
|
||||||
let buttonId = await dialogs.pop(this, _('This note has been modified:'), [
|
let buttonId = await dialogs.pop(this, _('This note has been modified:'), [
|
||||||
{ title: _('Save changes'), id: 'save' },
|
{ text: _('Save changes'), id: 'save' },
|
||||||
{ title: _('Discard changes'), id: 'discard' },
|
{ text: _('Discard changes'), id: 'discard' },
|
||||||
{ title: _('Cancel'), id: 'cancel' },
|
{ text: _('Cancel'), id: 'cancel' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (buttonId == 'cancel') return true;
|
if (buttonId == 'cancel') return true;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const React = require('react'); const Component = React.Component;
|
const React = require('react'); const Component = React.Component;
|
||||||
const { View, Button } = require('react-native');
|
const { View, Button, Text } = require('react-native');
|
||||||
|
const { stateUtils } = require('lib/reducer.js');
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const { reg } = require('lib/registry.js');
|
const { reg } = require('lib/registry.js');
|
||||||
const { Log } = require('lib/log.js');
|
const { Log } = require('lib/log.js');
|
||||||
@ -10,7 +11,7 @@ const Note = require('lib/models/Note.js');
|
|||||||
const Setting = require('lib/models/Setting.js');
|
const Setting = require('lib/models/Setting.js');
|
||||||
const { themeStyle } = require('lib/components/global-style.js');
|
const { themeStyle } = require('lib/components/global-style.js');
|
||||||
const { ScreenHeader } = require('lib/components/screen-header.js');
|
const { ScreenHeader } = require('lib/components/screen-header.js');
|
||||||
const { MenuOption, Text } = require('react-native-popup-menu');
|
const { MenuOption } = require('react-native-popup-menu');
|
||||||
const { _ } = require('lib/locale.js');
|
const { _ } = require('lib/locale.js');
|
||||||
const { ActionButton } = require('lib/components/action-button.js');
|
const { ActionButton } = require('lib/components/action-button.js');
|
||||||
const { dialogs } = require('lib/dialogs.js');
|
const { dialogs } = require('lib/dialogs.js');
|
||||||
@ -23,6 +24,43 @@ class NotesScreenComponent extends BaseScreenComponent {
|
|||||||
return { header: null };
|
return { header: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.sortButton_press = async () => {
|
||||||
|
const buttons = [];
|
||||||
|
const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field');
|
||||||
|
|
||||||
|
const makeCheckboxText = function(selected, sign, label) {
|
||||||
|
const s = sign === 'tick' ? '✓' : '⬤'
|
||||||
|
return (selected ? (s + ' ') : '') + label;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let field in sortNoteOptions) {
|
||||||
|
if (!sortNoteOptions.hasOwnProperty(field)) continue;
|
||||||
|
buttons.push({
|
||||||
|
text: makeCheckboxText(Setting.value('notes.sortOrder.field') === field, 'bullet', sortNoteOptions[field]),
|
||||||
|
id: { name: 'notes.sortOrder.field', value: field },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
text: makeCheckboxText(Setting.value('notes.sortOrder.reverse'), 'tick', '[ ' + Setting.settingMetadata('notes.sortOrder.reverse').label() + ' ]'),
|
||||||
|
id: { name: 'notes.sortOrder.reverse', value: !Setting.value('notes.sortOrder.reverse') },
|
||||||
|
});
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
text: makeCheckboxText(Setting.value('uncompletedTodosOnTop'), 'tick', '[ ' + Setting.settingMetadata('uncompletedTodosOnTop').label() + ' ]'),
|
||||||
|
id: { name: 'uncompletedTodosOnTop', value: !Setting.value('uncompletedTodosOnTop') },
|
||||||
|
});
|
||||||
|
|
||||||
|
const r = await dialogs.pop(this, Setting.settingMetadata('notes.sortOrder.field').label(), buttons);
|
||||||
|
if (!r) return;
|
||||||
|
|
||||||
|
Setting.setValue(r.name, r.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
await this.refreshNotes();
|
await this.refreshNotes();
|
||||||
}
|
}
|
||||||
@ -42,6 +80,7 @@ class NotesScreenComponent extends BaseScreenComponent {
|
|||||||
let options = {
|
let options = {
|
||||||
order: props.notesOrder,
|
order: props.notesOrder,
|
||||||
uncompletedTodosOnTop: props.uncompletedTodosOnTop,
|
uncompletedTodosOnTop: props.uncompletedTodosOnTop,
|
||||||
|
caseInsensitive: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const parent = this.parentItem(props);
|
const parent = this.parentItem(props);
|
||||||
@ -155,6 +194,7 @@ class NotesScreenComponent extends BaseScreenComponent {
|
|||||||
title={title}
|
title={title}
|
||||||
menuOptions={this.menuOptions()}
|
menuOptions={this.menuOptions()}
|
||||||
parentComponent={thisComp}
|
parentComponent={thisComp}
|
||||||
|
sortButton_press={this.sortButton_press}
|
||||||
folderPickerOptions={{
|
folderPickerOptions={{
|
||||||
enabled: this.props.noteSelectionEnabled,
|
enabled: this.props.noteSelectionEnabled,
|
||||||
mustSelect: true,
|
mustSelect: true,
|
||||||
@ -178,11 +218,11 @@ const NotesScreen = connect(
|
|||||||
selectedTagId: state.selectedTagId,
|
selectedTagId: state.selectedTagId,
|
||||||
notesParentType: state.notesParentType,
|
notesParentType: state.notesParentType,
|
||||||
notes: state.notes,
|
notes: state.notes,
|
||||||
notesOrder: state.notesOrder,
|
|
||||||
notesSource: state.notesSource,
|
notesSource: state.notesSource,
|
||||||
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
|
uncompletedTodosOnTop: state.settings.uncompletedTodosOnTop,
|
||||||
theme: state.settings.theme,
|
theme: state.settings.theme,
|
||||||
noteSelectionEnabled: state.noteSelectionEnabled,
|
noteSelectionEnabled: state.noteSelectionEnabled,
|
||||||
|
notesOrder: stateUtils.notesOrder(state.settings),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)(NotesScreenComponent)
|
)(NotesScreenComponent)
|
||||||
|
@ -33,17 +33,20 @@ dialogs.confirm = (parentComponent, message) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
dialogs.pop = (parentComponent, message, buttons) => {
|
dialogs.pop = (parentComponent, message, buttons, options = null) => {
|
||||||
if (!parentComponent) throw new Error('parentComponent is required');
|
if (!parentComponent) throw new Error('parentComponent is required');
|
||||||
if (!('dialogbox' in parentComponent)) throw new Error('A "dialogbox" component must be defined on the parent component!');
|
if (!('dialogbox' in parentComponent)) throw new Error('A "dialogbox" component must be defined on the parent component!');
|
||||||
|
|
||||||
|
if (!options) options = {};
|
||||||
|
if (!('buttonFlow' in options)) options.buttonFlow = 'auto';
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
|
|
||||||
let btns = [];
|
let btns = [];
|
||||||
for (let i = 0; i < buttons.length; i++) {
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
btns.push({
|
btns.push({
|
||||||
text: buttons[i].title,
|
text: buttons[i].text,
|
||||||
callback: () => {
|
callback: () => {
|
||||||
parentComponent.dialogbox.close();
|
parentComponent.dialogbox.close();
|
||||||
resolve(buttons[i].id);
|
resolve(buttons[i].id);
|
||||||
@ -54,6 +57,7 @@ dialogs.pop = (parentComponent, message, buttons) => {
|
|||||||
parentComponent.dialogbox.pop({
|
parentComponent.dialogbox.pop({
|
||||||
content: message,
|
content: message,
|
||||||
btns: btns,
|
btns: btns,
|
||||||
|
buttonFlow: options.buttonFlow,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,16 @@ class Note extends BaseItem {
|
|||||||
return 'notes';
|
return 'notes';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fieldToLabel(field) {
|
||||||
|
const fieldsToLabels = {
|
||||||
|
title: 'title',
|
||||||
|
user_updated_time: 'updated date',
|
||||||
|
user_created_time: 'created date',
|
||||||
|
};
|
||||||
|
|
||||||
|
return field in fieldsToLabels ? fieldsToLabels[field] : field;
|
||||||
|
}
|
||||||
|
|
||||||
static async serialize(note, type = null, shownKeys = null) {
|
static async serialize(note, type = null, shownKeys = null) {
|
||||||
let fieldNames = this.fieldNames();
|
let fieldNames = this.fieldNames();
|
||||||
fieldNames.push('type_');
|
fieldNames.push('type_');
|
||||||
|
@ -5,6 +5,7 @@ const SyncTargetRegistry = require('lib/SyncTargetRegistry.js');
|
|||||||
const { time } = require('lib/time-utils.js');
|
const { time } = require('lib/time-utils.js');
|
||||||
const { sprintf } = require('sprintf-js');
|
const { sprintf } = require('sprintf-js');
|
||||||
const ObjectUtils = require('lib/ObjectUtils');
|
const ObjectUtils = require('lib/ObjectUtils');
|
||||||
|
const { toTitleCase } = require('lib/string-utils.js');
|
||||||
const { _, supportedLocalesToLanguages, defaultLocale } = require('lib/locale.js');
|
const { _, supportedLocalesToLanguages, defaultLocale } = require('lib/locale.js');
|
||||||
|
|
||||||
class Setting extends BaseModel {
|
class Setting extends BaseModel {
|
||||||
@ -20,6 +21,10 @@ class Setting extends BaseModel {
|
|||||||
static metadata() {
|
static metadata() {
|
||||||
if (this.metadata_) return this.metadata_;
|
if (this.metadata_) return this.metadata_;
|
||||||
|
|
||||||
|
// A "public" setting means that it will show up in the various config screens (or config command for the CLI tool), however
|
||||||
|
// if if private a setting might still be handled and modified by the app. For instance, the settings related to sorting notes are not
|
||||||
|
// public for the mobile and desktop apps because they are handled separately in menus.
|
||||||
|
|
||||||
this.metadata_ = {
|
this.metadata_ = {
|
||||||
'activeFolderId': { value: '', type: Setting.TYPE_STRING, public: false },
|
'activeFolderId': { value: '', type: Setting.TYPE_STRING, public: false },
|
||||||
'firstStart': { value: true, type: Setting.TYPE_BOOL, public: false },
|
'firstStart': { value: true, type: Setting.TYPE_BOOL, public: false },
|
||||||
@ -50,16 +55,17 @@ class Setting extends BaseModel {
|
|||||||
output[Setting.THEME_DARK] = _('Dark');
|
output[Setting.THEME_DARK] = _('Dark');
|
||||||
return output;
|
return output;
|
||||||
}},
|
}},
|
||||||
// 'logLevel': { value: Logger.LEVEL_INFO, type: Setting.TYPE_STRING, isEnum: true, public: true, label: () => _('Log level'), options: () => {
|
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, appTypes: ['cli'], label: () => _('Uncompleted to-dos on top') },
|
||||||
// return Logger.levelEnum();
|
'notes.sortOrder.field': { value: 'user_updated_time', type: Setting.TYPE_STRING, isEnum: true, public: true, appTypes: ['cli'], label: () => _('Sort notes by'), options: () => {
|
||||||
// }},
|
const Note = require('lib/models/Note');
|
||||||
// Not used for now:
|
const noteSortFields = ['user_updated_time', 'user_created_time', 'title'];
|
||||||
// 'todoFilter': { value: 'all', type: Setting.TYPE_STRING, isEnum: true, public: false, appTypes: ['mobile'], label: () => _('Todo filter'), options: () => ({
|
const options = {};
|
||||||
// all: _('Show all'),
|
for (let i = 0; i < noteSortFields.length; i++) {
|
||||||
// recent: _('Non-completed and recently completed ones'),
|
options[noteSortFields[i]] = toTitleCase(Note.fieldToLabel(noteSortFields[i]));
|
||||||
// nonCompleted: _('Non-completed ones only'),
|
}
|
||||||
// })},
|
return options;
|
||||||
'uncompletedTodosOnTop': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Show uncompleted to-dos on top of the lists') },
|
}},
|
||||||
|
'notes.sortOrder.reverse': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Reverse sort order'), appTypes: ['cli'] },
|
||||||
'trackLocation': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Save geo-location with notes') },
|
'trackLocation': { value: true, type: Setting.TYPE_BOOL, public: true, label: () => _('Save geo-location with notes') },
|
||||||
'newTodoFocus': { value: 'title', type: Setting.TYPE_STRING, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('When creating a new to-do:'), options: () => {
|
'newTodoFocus': { value: 'title', type: Setting.TYPE_STRING, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('When creating a new to-do:'), options: () => {
|
||||||
return {
|
return {
|
||||||
|
@ -19,19 +19,24 @@ const defaultState = {
|
|||||||
showSideMenu: false,
|
showSideMenu: false,
|
||||||
screens: {},
|
screens: {},
|
||||||
historyCanGoBack: false,
|
historyCanGoBack: false,
|
||||||
notesOrder: [
|
|
||||||
{ by: 'user_updated_time', dir: 'DESC' },
|
|
||||||
],
|
|
||||||
syncStarted: false,
|
syncStarted: false,
|
||||||
syncReport: {},
|
syncReport: {},
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
settings: {},
|
settings: {},
|
||||||
appState: 'starting',
|
appState: 'starting',
|
||||||
//windowContentSize: { width: 0, height: 0 },
|
|
||||||
hasDisabledSyncItems: false,
|
hasDisabledSyncItems: false,
|
||||||
newNote: null,
|
newNote: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const stateUtils = {};
|
||||||
|
|
||||||
|
stateUtils.notesOrder = function(stateSettings) {
|
||||||
|
return [{
|
||||||
|
by: stateSettings['notes.sortOrder.field'],
|
||||||
|
dir: stateSettings['notes.sortOrder.reverse'] ? 'DESC' : 'ASC',
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
function arrayHasEncryptedItems(array) {
|
function arrayHasEncryptedItems(array) {
|
||||||
for (let i = 0; i < array.length; i++) {
|
for (let i = 0; i < array.length; i++) {
|
||||||
if (!!array[i].encryption_applied) return true;
|
if (!!array[i].encryption_applied) return true;
|
||||||
@ -90,8 +95,6 @@ function handleItemDelete(state, action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateOneItem(state, action) {
|
function updateOneItem(state, action) {
|
||||||
// let newItems = action.type === 'TAG_UPDATE_ONE' ? state.tags.splice(0) : state.folders.splice(0);
|
|
||||||
// let item = action.type === 'TAG_UPDATE_ONE' ? action.tag : action.folder;
|
|
||||||
let itemsKey = null;
|
let itemsKey = null;
|
||||||
if (action.type === 'TAG_UPDATE_ONE') itemsKey = 'tags';
|
if (action.type === 'TAG_UPDATE_ONE') itemsKey = 'tags';
|
||||||
if (action.type === 'FOLDER_UPDATE_ONE') itemsKey = 'folders';
|
if (action.type === 'FOLDER_UPDATE_ONE') itemsKey = 'folders';
|
||||||
@ -116,12 +119,6 @@ function updateOneItem(state, action) {
|
|||||||
|
|
||||||
newState[itemsKey] = newItems;
|
newState[itemsKey] = newItems;
|
||||||
|
|
||||||
// if (action.type === 'TAG_UPDATE_ONE') {
|
|
||||||
// newState.tags = newItems;
|
|
||||||
// } else {
|
|
||||||
// newState.folders = newItems;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +313,8 @@ const reducer = (state = defaultState, action) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newNotes = Note.sortNotes(newNotes, state.notesOrder, newState.settings.uncompletedTodosOnTop);
|
//newNotes = Note.sortNotes(newNotes, state.notesOrder, newState.settings.uncompletedTodosOnTop);
|
||||||
|
newNotes = Note.sortNotes(newNotes, stateUtils.notesOrder(state.settings), newState.settings.uncompletedTodosOnTop);
|
||||||
newState = Object.assign({}, state);
|
newState = Object.assign({}, state);
|
||||||
newState.notes = newNotes;
|
newState.notes = newNotes;
|
||||||
|
|
||||||
@ -481,4 +479,4 @@ const reducer = (state = defaultState, action) => {
|
|||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { reducer, defaultState };
|
module.exports = { reducer, defaultState, stateUtils };
|
@ -201,4 +201,9 @@ function padLeft(string, length, padString) {
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { removeDiacritics, escapeFilename, wrap, splitCommandString, padLeft };
|
function toTitleCase(string) {
|
||||||
|
if (!string) return string;
|
||||||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { removeDiacritics, escapeFilename, wrap, splitCommandString, padLeft, toTitleCase };
|
@ -21,6 +21,7 @@
|
|||||||
"docs/*.html",
|
"docs/*.html",
|
||||||
"docs/*.svg",
|
"docs/*.svg",
|
||||||
"ReactNativeClient/lib/mime-utils.js",
|
"ReactNativeClient/lib/mime-utils.js",
|
||||||
|
"_mydocs/EnexSamples/*.enex",
|
||||||
],
|
],
|
||||||
"folder_exclude_patterns":
|
"folder_exclude_patterns":
|
||||||
[
|
[
|
||||||
|
Loading…
Reference in New Issue
Block a user