1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Improved item list and implemented delete

This commit is contained in:
Laurent Cozic 2017-10-23 22:48:29 +01:00
parent 01d01b76cd
commit b89746f6d3
11 changed files with 265 additions and 74 deletions

View File

@ -72,6 +72,10 @@ class AppGui {
reg.setupRecurrentSync();
}
store() {
return this.store_;
}
renderer() {
return this.renderer_;
}
@ -96,23 +100,38 @@ class AppGui {
};
folderList.name = 'folderList';
folderList.vStretch = true;
folderList.on('currentItemChange', async () => {
folderList.on('currentItemChange', async (event) => {
const item = folderList.currentItem;
if (item.type_ === Folder.modelType()) {
if (item === '-') {
let newIndex = event.currentIndex + (event.previousIndex < event.currentIndex ? +1 : -1);
let nextItem = folderList.itemAt(newIndex);
if (!nextItem) nextItem = folderList.itemAt(event.previousIndex);
if (!nextItem) return; // Normally not possible
let actionType = 'FOLDERS_SELECT';
if (nextItem.type_ === BaseModel.TYPE_TAG) actionType = 'TAGS_SELECT';
if (nextItem.type_ === BaseModel.TYPE_SEARCH) actionType = 'SEARCH_SELECT';
this.store_.dispatch({
type: actionType,
id: nextItem.id,
});
} else if (item.type_ === Folder.modelType()) {
this.store_.dispatch({
type: 'FOLDERS_SELECT',
folderId: item ? item.id : 0,
id: item ? item.id : null,
});
} else if (item.type_ === Tag.modelType()) {
this.store_.dispatch({
type: 'TAGS_SELECT',
tagId: item ? item.id : 0,
id: item ? item.id : null,
});
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
this.store_.dispatch({
type: 'SEARCH_SELECT',
searchId: item ? item.id : 0,
id: item ? item.id : null,
});
}
});
@ -214,8 +233,26 @@ class AppGui {
const shortcuts = {};
shortcuts['DELETE'] = {
description: _('Delete a note'),
action: 'rm $n',
description: _('Delete the currently selected note or notebook.'),
action: async () => {
if (this.widget('folderList').hasFocus) {
const item = this.widget('folderList').selectedJoplinItem;
if (item.type_ === BaseModel.TYPE_FOLDER) {
await this.processCommand('rmbook ' + item.id);
} else if (item.type_ === BaseModel.TYPE_TAG) {
this.stdout(_('To delete a tag, untag the associated notes.'));
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
this.store().dispatch({
type: 'SEARCH_REMOVE',
id: item.id,
});
}
} else if (this.widget('noteList').hasFocus) {
await this.processCommand('rmnote $n');
} else {
this.stdout(_('Please select the note or notebook to be deleted first.'));
}
}
};
shortcuts[' '] = {

View File

@ -73,7 +73,7 @@ class Application {
this.dispatch({
type: 'FOLDERS_SELECT',
folderId: folder ? folder.id : '',
id: folder ? folder.id : '',
});
}
@ -301,7 +301,11 @@ class Application {
if (options.type === 'boolean') {
if (answer === null) return false;
return answer === '' || answer.toLowerCase() == options.answers[0].toLowerCase();
let output = false;
if (answer === '' || answer.toLowerCase() == options.answers[0].toLowerCase()) {
output = true;
}
return options.booleanAnswerDefault === 'y' ? output : !output;
}
});
@ -405,9 +409,9 @@ class Application {
reg.logger().info('execCommand()', argv);
const commandName = argv[0];
this.activeCommand_ = this.findCommandByName(commandName);
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
let outException = null;
try {
const cmdArgs = cliUtils.makeCommandArgs(this.activeCommand_, argv);
await this.activeCommand_.action(cmdArgs);
} catch (error) {
outException = error;
@ -421,6 +425,8 @@ class Application {
}
async refreshNotes(parentType, parentId) {
this.logger().debug('Refreshing notes:', parentType, parentId);
const state = this.store().getState();
let options = {
@ -435,18 +441,19 @@ class Application {
let notes = [];
if (parentType === Folder.modelType()) {
notes = await Note.previews(parentId, options);
} else if (parentType === Tag.modelType()) {
notes = await Tag.notes(parentId);
} else if (parentType === BaseModel.TYPE_SEARCH) {
this.logger().info('DOING SEARCH');
let fields = Note.previewFields();
let search = BaseModel.byId(state.searches, parentId);
notes = await Note.previews(null, {
fields: fields,
anywherePattern: '*' + search.query_pattern + '*',
});
if (parentId) {
if (parentType === Folder.modelType()) {
notes = await Note.previews(parentId, options);
} else if (parentType === Tag.modelType()) {
notes = await Tag.notes(parentId);
} else if (parentType === BaseModel.TYPE_SEARCH) {
let fields = Note.previewFields();
let search = BaseModel.byId(state.searches, parentId);
notes = await Note.previews(null, {
fields: fields,
anywherePattern: '*' + search.query_pattern + '*',
});
}
}
this.store().dispatch({
@ -489,7 +496,7 @@ class Application {
}
if (action.type == 'SEARCH_SELECT') {
await this.refreshNotes(BaseModel.TYPE_SEARCH, action.searchId);
await this.refreshNotes(BaseModel.TYPE_SEARCH, action.id);
}
if (this.gui() && action.type == 'SETTINGS_UPDATE_ONE' && action.key == 'sync.interval' || action.type == 'SETTINGS_UPDATE_ALL') {
@ -623,7 +630,7 @@ class Application {
this.store().dispatch({
type: 'FOLDERS_SELECT',
folderId: Setting.value('activeFolderId'),
id: Setting.value('activeFolderId'),
});
}
}

View File

@ -72,7 +72,8 @@ class BaseCommand {
if (!this.prompt_) throw new Error('Prompt is undefined');
if (!options) options = {};
if (!options.type) options.type = 'boolean';
if (!options.answers) options.answers = [_('Y'), _('n')];
if (!options.booleanAnswerDefault) options.booleanAnswerDefault = 'y';
if (!options.answers) options.answers = options.booleanAnswerDefault === 'y' ? [_('Y'), _('n')] : [_('N'), _('y')];
return await this.prompt_(message, options);
}

View File

@ -0,0 +1,40 @@
import { BaseCommand } from './base-command.js';
import { app } from './app.js';
import { _ } from 'lib/locale.js';
import { BaseItem } from 'lib/models/base-item.js';
import { Folder } from 'lib/models/folder.js';
import { Note } from 'lib/models/note.js';
import { BaseModel } from 'lib/base-model.js';
import { cliUtils } from './cli-utils.js';
class Command extends BaseCommand {
usage() {
return 'rmbook <notebook>';
}
description() {
return _('Deletes the given notebook.');
}
options() {
return [
['-f, --force', _('Deletes the notebook without asking for confirmation.')],
];
}
async action(args) {
const pattern = args['notebook'];
const force = args.options && args.options.force === true;
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
if (!folder) throw new Error(_('Cannot find "%s".', pattern));
const ok = force ? true : await this.prompt(_('Delete notebook "%s"?', folder.title), { booleanAnswerDefault: 'n' });
if (!ok) return;
await Folder.delete(folder.id);
}
}
module.exports = Command;

View File

@ -7,13 +7,14 @@ import { Note } from 'lib/models/note.js';
import { BaseModel } from 'lib/base-model.js';
import { cliUtils } from './cli-utils.js';
import { reg } from 'lib/registry.js';
class Command extends BaseCommand {
usage() {
return 'rm <note-pattern>';
return 'rmnote <note-pattern>';
}
aliases() {
return ['rm'];
}
description() {
@ -22,28 +23,18 @@ class Command extends BaseCommand {
options() {
return [
['-f, --force', _('Deletes the items without asking for confirmation.')],
['-f, --force', _('Deletes the notes without asking for confirmation.')],
];
}
async action(args) {
const pattern = args['note-pattern'];
const recursive = args.options && args.options.recursive === true;
const force = args.options && args.options.force === true;
// if (recursive) {
// const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
// if (!folder) throw new Error(_('Cannot find "%s".', pattern));
// //const ok = force ? true : await vorpalUtils.cmdPromptConfirm(this, _('Delete notebook "%s"?', folder.title));
// if (!ok) return;
// await Folder.delete(folder.id);
// await app().refreshCurrentFolder();
// } else {
const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern);
if (!notes.length) throw new Error(_('Cannot find "%s".', pattern));
const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'));
const ok = force ? true : await this.prompt(notes.length > 1 ? _('%d notes match this pattern. Delete them?', notes.length) : _('Delete note?'), { booleanAnswerDefault: 'n' });
if (!ok) return;
let ids = notes.map((n) => n.id);
await Note.batchDelete(ids);

View File

@ -43,7 +43,7 @@ class Command extends BaseCommand {
this.dispatch({
type: 'SEARCH_SELECT',
searchId: searchId,
id: searchId,
});
// let fields = Note.previewFields();

View File

@ -2,6 +2,7 @@ const Folder = require('lib/models/folder.js').Folder;
const Tag = require('lib/models/tag.js').Tag;
const BaseModel = require('lib/base-model.js').BaseModel;
const ListWidget = require('tkwidgets/ListWidget.js');
const _ = require('lib/locale.js')._;
class FolderListWidget extends ListWidget {
@ -20,14 +21,16 @@ class FolderListWidget extends ListWidget {
this.itemRenderer = (item) => {
let output = [];
if (item.type_ === Folder.modelType()) {
output.push('[n]');
if (item === '-') {
output.push('-'.repeat(this.innerWidth));
} else if (item.type_ === Folder.modelType()) {
output.push(item.title);
} else if (item.type_ === Tag.modelType()) {
output.push('[t]');
output.push('[' + item.title + ']');
} else if (item.type_ === BaseModel.TYPE_SEARCH) {
output.push('[s]');
}
output.push(item.title);
output.push(_('Search:'));
output.push(item.title);
}
// output.push(item.id.substr(0, 5));
return output.join(' ');
};
@ -115,17 +118,31 @@ class FolderListWidget extends ListWidget {
async onWillRender() {
if (this.updateItems_) {
this.logger().info('Rebuilding items...', this.notesParentType, this.selectedItemId, this.selectedSearchId);
const wasSelectedItemId = this.selectedItemId;
this.logger().debug('Rebuilding items...', this.notesParentType, this.selectedJoplinItemId, this.selectedSearchId);
const wasSelectedItemId = this.selectedJoplinItemId;
const previousParentType = this.notesParentType;
this.items = this.folders.concat(this.tags).concat(this.searches);
let newItems = this.folders.slice();
if (this.tags.length) {
if (newItems.length) newItems.push('-');
newItems = newItems.concat(this.tags);
}
if (this.searches.length) {
if (newItems.length) newItems.push('-');
newItems = newItems.concat(this.searches);
}
this.items = newItems;
this.notesParentType = previousParentType;
this.updateIndexFromSelectedItemId(wasSelectedItemId)
this.updateItems_ = false;
}
}
get selectedItemId() {
get selectedJoplinItemId() {
if (!this.notesParentType) return '';
if (this.notesParentType === 'Folder') return this.selectedFolderId;
if (this.notesParentType === 'Tag') return this.selectedTagId;
@ -133,10 +150,15 @@ class FolderListWidget extends ListWidget {
throw new Error('Unknown parent type: ' + this.notesParentType);
}
get selectedJoplinItem() {
const id = this.selectedJoplinItemId;
const index = this.itemIndexByKey('id', id);
return this.itemAt(index);
}
updateIndexFromSelectedItemId(itemId = null) {
if (itemId === null) itemId = this.selectedItemId;
if (itemId === null) itemId = this.selectedJoplinItemId;
const index = this.itemIndexByKey('id', itemId);
//this.logger().info('Setting index to', this.notesParentType, index);
this.currentIndex = index >= 0 ? index : 0;
}

View File

@ -15,7 +15,13 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Delete a note"
msgid "Delete the currently selected note or notebook."
msgstr ""
msgid "To delete a tag, untag the associated notes."
msgstr ""
msgid "Please select the note or notebook to be deleted first."
msgstr ""
msgid "Set a todo as completed / not completed"
@ -127,6 +133,12 @@ msgstr ""
msgid "n"
msgstr ""
msgid "N"
msgstr ""
msgid "y"
msgstr ""
#, javascript-format
msgid "Missing required argument: %s"
msgstr ""
@ -330,10 +342,20 @@ msgstr ""
msgid "Moves the notes matching <note-pattern> to [notebook]."
msgstr ""
msgid "Deletes the given notebook."
msgstr ""
msgid "Deletes the notebook without asking for confirmation."
msgstr ""
#, javascript-format
msgid "Delete notebook \"%s\"?"
msgstr ""
msgid "Deletes the notes matching <note-pattern>."
msgstr ""
msgid "Deletes the items without asking for confirmation."
msgid "Deletes the notes without asking for confirmation."
msgstr ""
#, javascript-format

View File

@ -15,9 +15,15 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
msgid "Delete the currently selected note or notebook."
msgstr ""
msgid "To delete a tag, untag the associated notes."
msgstr ""
#, fuzzy
msgid "Delete a note"
msgstr "Supprimer la note"
msgid "Please select the note or notebook to be deleted first."
msgstr "Veuillez d'abord sélectionner un carnet."
#, fuzzy
msgid "Set a todo as completed / not completed"
@ -137,6 +143,12 @@ msgstr ""
msgid "n"
msgstr ""
msgid "N"
msgstr ""
msgid "y"
msgstr ""
#, javascript-format
msgid "Missing required argument: %s"
msgstr ""
@ -370,11 +382,24 @@ msgstr "Créer une nouvelle tâche."
msgid "Moves the notes matching <note-pattern> to [notebook]."
msgstr "Supprime les objets correspondants à <motif>."
#, fuzzy
msgid "Deletes the given notebook."
msgstr "Supprime le carnet."
#, fuzzy
msgid "Deletes the notebook without asking for confirmation."
msgstr "Supprime les objets sans demander la confirmation."
#, javascript-format
msgid "Delete notebook \"%s\"?"
msgstr "Supprimer le carnet \"%s\" ?"
#, fuzzy
msgid "Deletes the notes matching <note-pattern>."
msgstr "Supprime les objets correspondants à <motif>."
msgid "Deletes the items without asking for confirmation."
#, fuzzy
msgid "Deletes the notes without asking for confirmation."
msgstr "Supprime les objets sans demander la confirmation."
#, javascript-format
@ -789,6 +814,10 @@ msgstr ""
msgid "Welcome"
msgstr "Bienvenue"
#, fuzzy
#~ msgid "Delete a note"
#~ msgstr "Supprimer la note"
#~ msgid "%s (%s)"
#~ msgstr "%s (%s)"
@ -835,12 +864,6 @@ msgstr "Bienvenue"
#~ "<motif> est une note, elle sera déplacée vers le carnet <destination>. Si "
#~ "<motif> est un carnet, il sera renommé <destination>."
#~ msgid "Deletes a notebook."
#~ msgstr "Supprime le carnet."
#~ msgid "Delete notebook \"%s\"?"
#~ msgstr "Supprimer le carnet \"%s\" ?"
#~ msgid "No notebook is defined. Create one with `mkbook <notebook>`."
#~ msgstr "Aucun carnet n'est défini. Créez-en un avec `mkbook <carnet>`."

View File

@ -15,7 +15,13 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Delete a note"
msgid "Delete the currently selected note or notebook."
msgstr ""
msgid "To delete a tag, untag the associated notes."
msgstr ""
msgid "Please select the note or notebook to be deleted first."
msgstr ""
msgid "Set a todo as completed / not completed"
@ -127,6 +133,12 @@ msgstr ""
msgid "n"
msgstr ""
msgid "N"
msgstr ""
msgid "y"
msgstr ""
#, javascript-format
msgid "Missing required argument: %s"
msgstr ""
@ -330,10 +342,20 @@ msgstr ""
msgid "Moves the notes matching <note-pattern> to [notebook]."
msgstr ""
msgid "Deletes the given notebook."
msgstr ""
msgid "Deletes the notebook without asking for confirmation."
msgstr ""
#, javascript-format
msgid "Delete notebook \"%s\"?"
msgstr ""
msgid "Deletes the notes matching <note-pattern>."
msgstr ""
msgid "Deletes the items without asking for confirmation."
msgid "Deletes the notes without asking for confirmation."
msgstr ""
#, javascript-format

View File

@ -66,6 +66,20 @@ function updateOneTagOrFolder(state, action) {
return newState;
}
function defaultNotesParentType(state, exclusion) {
let newNotesParentType = null;
if (exclusion !== 'Folder' && state.selectedFolderId) {
newNotesParentType = 'Folder';
} else if (exclusion !== 'Tag' && state.selectedTagId) {
newNotesParentType = 'Tag';
} else if (exclusion !== 'Search' && state.selectedSearchId) {
newNotesParentType = 'Search';
}
return newNotesParentType;
}
const reducer = (state = defaultState, action) => {
let newState = state;
let historyGoingBack = false;
@ -156,8 +170,12 @@ const reducer = (state = defaultState, action) => {
case 'FOLDERS_SELECT':
newState = Object.assign({}, state);
newState.selectedFolderId = action.folderId;
newState.notesParentType = 'Folder';
newState.selectedFolderId = action.id;
if (!action.id) {
newState.notesParentType = defaultNotesParentType(state, 'Folder');
} else {
newState.notesParentType = 'Folder';
}
break;
case 'SETTINGS_UPDATE_ALL':
@ -264,8 +282,12 @@ const reducer = (state = defaultState, action) => {
case 'TAGS_SELECT':
newState = Object.assign({}, state);
newState.selectedTagId = action.tagId;
newState.notesParentType = 'Tag';
newState.selectedTagId = action.id;
if (!action.id) {
newState.notesParentType = defaultNotesParentType(state, 'Tag');
} else {
newState.notesParentType = 'Tag';
}
break;
case 'TAGS_UPDATE_ONE':
@ -376,7 +398,7 @@ const reducer = (state = defaultState, action) => {
let foundIndex = -1;
for (let i = 0; i < state.searches.length; i++) {
if (state.searches[i].id === action.searchId) {
if (state.searches[i].id === action.id) {
foundIndex = i;
break;
}
@ -393,8 +415,12 @@ const reducer = (state = defaultState, action) => {
case 'SEARCH_SELECT':
newState = Object.assign({}, state);
newState.selectedSearchId = action.searchId;
newState.notesParentType = 'Search';
newState.selectedSearchId = action.id;
if (!action.id) {
newState.notesParentType = defaultNotesParentType(state, 'Search');
} else {
newState.notesParentType = 'Search';
}
break;
case 'SET_APP_STATE':