mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-27 08:21:03 +02:00
Improved autocompletion handling
This commit is contained in:
parent
9ed22265ba
commit
2868a28422
@ -14,7 +14,7 @@ import { _, setLocale, defaultLocale, closestSupportedLocale } from 'lib/locale.
|
||||
import os from 'os';
|
||||
import fs from 'fs-extra';
|
||||
import yargParser from 'yargs-parser';
|
||||
import { handleAutocompletion } from './autocompletion.js';
|
||||
import { handleAutocompletion, installAutocompletionFile } from './autocompletion.js';
|
||||
import { cliUtils } from './cli-utils.js';
|
||||
|
||||
class Application {
|
||||
@ -157,6 +157,12 @@ class Application {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == '--ac-install') {
|
||||
this.autocompletion_.install = true;
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == '--ac-current') {
|
||||
if (!nextArg) throw new Error(_('Usage: %s', '--ac-current <num>'));
|
||||
this.autocompletion_.current = nextArg;
|
||||
@ -309,6 +315,8 @@ class Application {
|
||||
let initArgs = startFlags.matched;
|
||||
if (argv.length) this.showPromptString_ = false;
|
||||
|
||||
Setting.setConstant('appName', initArgs.env == 'dev' ? 'joplindev' : 'joplin');
|
||||
|
||||
const profileDir = initArgs.profileDir ? initArgs.profileDir : os.homedir() + '/.config/' + Setting.value('appName');
|
||||
const resourceDir = profileDir + '/resources';
|
||||
const tempDir = profileDir + '/tmp';
|
||||
@ -364,11 +372,26 @@ class Application {
|
||||
Setting.setValue('activeFolderId', this.currentFolder_ ? this.currentFolder_.id : '');
|
||||
|
||||
if (this.autocompletion_.active) {
|
||||
let items = await handleAutocompletion(this.autocompletion_);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i] = items[i].replace(/ /g, '\\ ');
|
||||
if (this.autocompletion_.install) {
|
||||
try {
|
||||
installAutocompletionFile(Setting.value('appName'), Setting.value('profileDir'));
|
||||
} catch (error) {
|
||||
if (error.code == 'shellNotSupported') {
|
||||
console.info(error.message);
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
let items = await handleAutocompletion(this.autocompletion_);
|
||||
if (!items.length) return;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i] = items[i].replace(/ /g, '\\ ');
|
||||
}
|
||||
//console.info(items);
|
||||
console.info(items.join("\n"));
|
||||
}
|
||||
console.info(items.join("\n"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,57 @@
|
||||
import { app } from './app.js';
|
||||
import { Note } from 'lib/models/note.js';
|
||||
import { Folder } from 'lib/models/folder.js';
|
||||
import { Tag } from 'lib/models/tag.js';
|
||||
import { cliUtils } from './cli-utils.js';
|
||||
import { _ } from 'lib/locale.js';
|
||||
import fs from 'fs-extra';
|
||||
import os from 'os';
|
||||
import yargParser from 'yargs-parser';
|
||||
|
||||
function autocompletionFileContent(appName) {
|
||||
let content = fs.readFileSync(__dirname + '/autocompletion_template.txt', 'utf8');
|
||||
content = content.replace(/\|__APPNAME__\|/g, appName);
|
||||
return content;
|
||||
}
|
||||
|
||||
function installAutocompletionFile(appName, profileDir) {
|
||||
if (process.env.SHELL.indexOf('bash') < 0) {
|
||||
let error = new Error(_('Only Bash is currently supported for autocompletion.'));
|
||||
error.code = 'shellNotSupported';
|
||||
throw error;
|
||||
}
|
||||
|
||||
const content = autocompletionFileContent(appName);
|
||||
const filePath = profileDir + '/autocompletion.sh';
|
||||
fs.writeFileSync(filePath, content);
|
||||
|
||||
const bashProfilePath = os.homedir() + '/.bashrc';
|
||||
|
||||
let bashrcContent = fs.readFileSync(bashProfilePath, 'utf8');
|
||||
|
||||
const lineToAdd = 'source ' + filePath;
|
||||
|
||||
console.info(_('Adding autocompletion script to: "%s"', bashProfilePath));
|
||||
|
||||
if (bashrcContent.indexOf(lineToAdd) >= 0) {
|
||||
console.info(_('Autocompletion script is already installed.'));
|
||||
} else {
|
||||
bashrcContent += "\n" + lineToAdd + "\n";
|
||||
fs.writeFileSync(bashProfilePath, bashrcContent);
|
||||
console.info(_('Autocompletion has been installed.'));
|
||||
}
|
||||
|
||||
console.info(_('Sourcing "%s"...', filePath));
|
||||
|
||||
const spawnSync = require('child_process').spawnSync;
|
||||
spawnSync('source', [filePath]);
|
||||
}
|
||||
|
||||
async function handleAutocompletion(autocompletion) {
|
||||
let args = autocompletion.line.slice();
|
||||
args.splice(0, 1);
|
||||
let current = autocompletion.current - 1;
|
||||
const currentWord = args[current];
|
||||
const currentWord = args[current] ? args[current] : '';
|
||||
|
||||
// Auto-complete the command name
|
||||
|
||||
@ -32,21 +75,21 @@ async function handleAutocompletion(autocompletion) {
|
||||
|
||||
// Auto-complete the command options
|
||||
|
||||
if (!currentWord) return [];
|
||||
if (currentWord) {
|
||||
const includeLongs = currentWord.length == 1 ? currentWord.substr(0, 1) == '-' : currentWord.substr(0, 2) == '--';
|
||||
const includeShorts = currentWord.length <= 2 && currentWord.substr(0, 1) == '-' && currentWord.substr(0, 2) != '--';
|
||||
|
||||
const includeLongs = currentWord.length == 1 ? currentWord.substr(0, 1) == '-' : currentWord.substr(0, 2) == '--';
|
||||
const includeShorts = currentWord.length <= 2 && currentWord.substr(0, 1) == '-' && currentWord.substr(0, 2) != '--';
|
||||
|
||||
if (includeLongs || includeShorts) {
|
||||
const output = [];
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const flags = cliUtils.parseFlags(options[i][0]);
|
||||
const long = flags.long ? '--' + flags.long : null;
|
||||
const short = flags.short ? '-' + flags.short : null;
|
||||
if (includeLongs && long && long.indexOf(currentWord) === 0) output.push(long);
|
||||
if (includeShorts && short && short.indexOf(currentWord) === 0) output.push(short);
|
||||
if (includeLongs || includeShorts) {
|
||||
const output = [];
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const flags = cliUtils.parseFlags(options[i][0]);
|
||||
const long = flags.long ? '--' + flags.long : null;
|
||||
const short = flags.short ? '-' + flags.short : null;
|
||||
if (includeLongs && long && long.indexOf(currentWord) === 0) output.push(long);
|
||||
if (includeShorts && short && short.indexOf(currentWord) === 0) output.push(short);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// Auto-complete the command arguments
|
||||
@ -70,7 +113,7 @@ async function handleAutocompletion(autocompletion) {
|
||||
let argName = cmdUsage[argIndex];
|
||||
argName = cliUtils.parseCommandArg(argName).name;
|
||||
|
||||
if (argName == 'note') {
|
||||
if (argName == 'note' || argName == 'note-pattern') {
|
||||
if (!app().currentFolder()) return [];
|
||||
const notes = await Note.previews(app().currentFolder().id, { titlePattern: currentWord + '*' });
|
||||
return notes.map((n) => n.title);
|
||||
@ -81,7 +124,29 @@ async function handleAutocompletion(autocompletion) {
|
||||
return folders.map((n) => n.title);
|
||||
}
|
||||
|
||||
if (argName == 'tag') {
|
||||
let tags = await Tag.search({ titlePattern: currentWord + '*' });
|
||||
return tags.map((n) => n.title);
|
||||
}
|
||||
|
||||
if (argName == 'tag-command') {
|
||||
return filterList(['add', 'remove', 'list'], currentWord);
|
||||
}
|
||||
|
||||
if (argName == 'todo-command') {
|
||||
return filterList(['toggle', 'clear'], currentWord);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export { handleAutocompletion };
|
||||
function filterList(list, currentWord) {
|
||||
let output = [];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].indexOf(currentWord) !== 0) continue;
|
||||
output.push(list[i]);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export { handleAutocompletion, installAutocompletionFile };
|
40
CliClient/app/autocompletion_template.txt
Normal file
40
CliClient/app/autocompletion_template.txt
Normal file
@ -0,0 +1,40 @@
|
||||
export IFS=$'\n'
|
||||
|
||||
_|__APPNAME__|_completion() {
|
||||
|
||||
# COMP_WORDS contains each word in the current command, the last one
|
||||
# being the one that needs to be completed. Convert this array
|
||||
# to an "escaped" line which can be passed to the joplin CLI
|
||||
# which will provide the possible autocompletion words.
|
||||
|
||||
ESCAPED_LINE=""
|
||||
for WORD in "${COMP_WORDS[@]}"
|
||||
do
|
||||
if [[ -n $ESCAPED_LINE ]]; then
|
||||
ESCAPED_LINE="$ESCAPED_LINE|__SEP__|"
|
||||
fi
|
||||
WORD="${WORD/\"/|__QUOTE__|}"
|
||||
ESCAPED_LINE="$ESCAPED_LINE$WORD"
|
||||
done
|
||||
|
||||
# Call joplin with the --autocompletion flag to retrieve the autocompletion
|
||||
# candidates (each on its own line), and put these into COMREPLY.
|
||||
|
||||
# echo "joplindev --autocompletion --ac-current "$COMP_CWORD" --ac-line "$ESCAPED_LINE"" > ~/test.txt
|
||||
|
||||
COMPREPLY=()
|
||||
while read -r line; do
|
||||
COMPREPLY+=("$line")
|
||||
done <<< "$(|__APPNAME__| --autocompletion --ac-current "$COMP_CWORD" --ac-line "$ESCAPED_LINE")"
|
||||
|
||||
# If there's only one element and it's empty, make COMREPLY
|
||||
# completely empty so that default completion takes over
|
||||
# (i.e. regular file completion)
|
||||
# https://stackoverflow.com/a/19062943/561309
|
||||
|
||||
if [[ -z ${COMPREPLY[0]} ]]; then
|
||||
COMPREPLY=()
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o default -F _|__APPNAME__|_completion |__APPNAME__|
|
@ -1,32 +0,0 @@
|
||||
import { BaseCommand } from './base-command.js';
|
||||
import { app } from './app.js';
|
||||
import { _ } from 'lib/locale.js';
|
||||
import { Setting } from 'lib/models/setting.js';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'alias <name> <command>';
|
||||
}
|
||||
|
||||
description() {
|
||||
return 'Creates a new command alias which can then be used as a regular command (eg. `alias ll "ls -l"`)';
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let aliases = Setting.value('aliases').trim();
|
||||
aliases = aliases.length ? JSON.parse(aliases) : [];
|
||||
aliases.push({
|
||||
name: args.name,
|
||||
command: args.command,
|
||||
});
|
||||
Setting.setValue('aliases', JSON.stringify(aliases));
|
||||
}
|
||||
|
||||
enabled() {
|
||||
return false; // Doesn't work properly at the moment
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@ -1,54 +0,0 @@
|
||||
import { BaseCommand } from './base-command.js';
|
||||
import { app } from './app.js';
|
||||
import { _ } from 'lib/locale.js';
|
||||
import { Note } from 'lib/models/note.js';
|
||||
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'autocompletion <type> [arg1]';
|
||||
}
|
||||
|
||||
description() {
|
||||
return 'Helper for autocompletion';
|
||||
}
|
||||
|
||||
options() {
|
||||
return [
|
||||
[ '--before <before>', 'before' ],
|
||||
[ '--multiline', 'multiline' ],
|
||||
];
|
||||
}
|
||||
|
||||
hidden() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
console.info(args);
|
||||
|
||||
let output = [];
|
||||
if (args.type == 'notes') {
|
||||
// TODO:
|
||||
if (!app().currentFolder()) throw new Error('no current folder');
|
||||
let options = {};
|
||||
// this.log(JSON.stringify(['XX'+args.options.before+'XX', 'aa','bb']));
|
||||
// return;
|
||||
|
||||
//console.info(args.options.before);
|
||||
if (args.options.before) options.titlePattern = args.options.before + '*';
|
||||
const notes = await Note.previews(app().currentFolder().id, options);
|
||||
output = notes.map((n) => n.title);
|
||||
}
|
||||
|
||||
if (args.options.multiline) {
|
||||
output = output.map((s) => s.replace(/ /g, '\\ '));
|
||||
this.log(output.join("\n"));
|
||||
} else {
|
||||
this.log(JSON.stringify(output));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
@ -9,7 +9,7 @@ import { autocompleteItems } from './autocomplete.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'geoloc <title>';
|
||||
return 'geoloc <note>';
|
||||
}
|
||||
|
||||
description() {
|
||||
@ -21,7 +21,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let title = args['title'];
|
||||
let title = args['note'];
|
||||
|
||||
let item = await app().loadItem(BaseModel.TYPE_NOTE, title, { parent: app().currentFolder() });
|
||||
if (!item) throw new Error(_('Cannot find "%s".', title));
|
||||
|
@ -13,11 +13,11 @@ import { cliUtils } from './cli-utils.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'ls [pattern]';
|
||||
return 'ls [note-pattern]';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Displays the notes in [notebook]. Use `ls /` to display the list of notebooks.');
|
||||
return _('Displays the notes in the current notebook. Use `ls /` to display the list of notebooks.');
|
||||
}
|
||||
|
||||
options() {
|
||||
@ -36,7 +36,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let pattern = args['pattern'];
|
||||
let pattern = args['note-pattern'];
|
||||
let items = [];
|
||||
let options = args.options;
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { reg } from 'lib/registry.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'mkbook <notebook>';
|
||||
return 'mkbook <new-notebook>';
|
||||
}
|
||||
|
||||
description() {
|
||||
@ -19,7 +19,7 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let folder = await Folder.save({ title: args['notebook'] }, { userSideValidation: true });
|
||||
let folder = await Folder.save({ title: args['new-notebook'] }, { userSideValidation: true });
|
||||
app().switchCurrentFolder(folder);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { Note } from 'lib/models/note.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'mknote <note>';
|
||||
return 'mknote <new-note>';
|
||||
}
|
||||
|
||||
description() {
|
||||
@ -17,7 +17,7 @@ class Command extends BaseCommand {
|
||||
if (!app().currentFolder()) throw new Error(_('Notes can only be created within a notebook.'));
|
||||
|
||||
let note = {
|
||||
title: args.note,
|
||||
title: args['new-note'],
|
||||
parent_id: app().currentFolder().id,
|
||||
};
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { Note } from 'lib/models/note.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'mktodo <note>';
|
||||
return 'mktodo <new-todo>';
|
||||
}
|
||||
|
||||
description() {
|
||||
@ -17,7 +17,7 @@ class Command extends BaseCommand {
|
||||
if (!app().currentFolder()) throw new Error(_('Notes can only be created within a notebook.'));
|
||||
|
||||
let note = {
|
||||
title: args.note,
|
||||
title: args['new-todo'],
|
||||
parent_id: app().currentFolder().id,
|
||||
is_todo: 1,
|
||||
};
|
||||
|
@ -9,11 +9,11 @@ import { autocompleteItems } from './autocomplete.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'mv <pattern> <destination>';
|
||||
return 'mv <note-pattern> [notebook]';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Moves the notes matching <pattern> to <destination>. If <pattern> is a note, it will be moved to the notebook <destination>. If <pattern> is a notebook, it will be renamed to <destination>.');
|
||||
return _('Moves the notes matching <note-pattern> to [notebook].');
|
||||
}
|
||||
|
||||
autocomplete() {
|
||||
@ -21,26 +21,17 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
const pattern = args['pattern'];
|
||||
const destination = args['destination'];
|
||||
const pattern = args['note-pattern'];
|
||||
const destination = args['notebook'];
|
||||
|
||||
const folder = await Folder.loadByField('title', destination);
|
||||
if (!folder) throw new Error(_('Cannot find "%s".', destination));
|
||||
|
||||
const item = await app().guessTypeAndLoadItem(pattern);
|
||||
const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern);
|
||||
if (!notes.length) throw new Error(_('Cannot find "%s".', pattern));
|
||||
|
||||
if (!item) throw new Error(_('Cannot find "%s".', pattern));
|
||||
|
||||
if (item.type_ == BaseModel.TYPE_FOLDER) {
|
||||
await Folder.save({ id: item.id, title: destination }, { userSideValidation: true });
|
||||
await app().refreshCurrentFolder();
|
||||
} else { // TYPE_NOTE
|
||||
const folder = await Folder.loadByField('title', destination);
|
||||
if (!folder) throw new Error(_('Cannot find "%s".', destination));
|
||||
|
||||
const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern);
|
||||
if (!notes.length) throw new Error(_('Cannot find "%s".', pattern));
|
||||
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
await Note.moveToFolder(notes[i].id, folder.id);
|
||||
}
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
await Note.moveToFolder(notes[i].id, folder.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,11 @@ import { autocompleteItems } from './autocomplete.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'rm <pattern>';
|
||||
return 'rm <note-pattern>';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('Deletes the items matching <pattern>.');
|
||||
return _('Deletes the notes matching <note-pattern>.');
|
||||
}
|
||||
|
||||
autocomplete() {
|
||||
@ -24,30 +24,29 @@ class Command extends BaseCommand {
|
||||
options() {
|
||||
return [
|
||||
['-f, --force', _('Deletes the items without asking for confirmation.')],
|
||||
['-r, --recursive', _('Deletes a notebook.')],
|
||||
];
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
const pattern = args['pattern'].toString();
|
||||
const pattern = args['note-pattern'];
|
||||
const recursive = args.options && args.options.recursive === true;
|
||||
const force = true || args.options && args.options.force === true; // TODO
|
||||
|
||||
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 vorpalUtils.cmdPromptConfirm(this, _('%d notes match this pattern. Delete them?', notes.length));
|
||||
if (!ok) return;
|
||||
let ids = notes.map((n) => n.id);
|
||||
await Note.batchDelete(ids);
|
||||
}
|
||||
// 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 vorpalUtils.cmdPromptConfirm(this, _('%d notes match this pattern. Delete them?', notes.length));
|
||||
if (!ok) return;
|
||||
let ids = notes.map((n) => n.id);
|
||||
await Note.batchDelete(ids);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,10 @@ class Command extends BaseCommand {
|
||||
return { data: autocompleteItems };
|
||||
}
|
||||
|
||||
hidden() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
let title = args['note'];
|
||||
let propName = args['name'];
|
||||
|
@ -7,11 +7,11 @@ import { BaseModel } from 'lib/base-model.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'tag <command> [tag] [note]';
|
||||
return 'tag <tag-command> [tag] [note]';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('<command> can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags.');
|
||||
return _('<tag-command> can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags.');
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
@ -22,21 +22,23 @@ class Command extends BaseCommand {
|
||||
notes = await app().loadItems(BaseModel.TYPE_NOTE, args.note);
|
||||
}
|
||||
|
||||
if (args.command == 'remove' && !tag) throw new Error(_('Cannot find "%s".', args.tag));
|
||||
const command = args['tag-command'];
|
||||
|
||||
if (args.command == 'add') {
|
||||
if (command == 'remove' && !tag) throw new Error(_('Cannot find "%s".', args.tag));
|
||||
|
||||
if (command == 'add') {
|
||||
if (!notes.length) throw new Error(_('Cannot find "%s".', args.note));
|
||||
if (!tag) tag = await Tag.save({ title: args.tag });
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
await Tag.addNote(tag.id, notes[i].id);
|
||||
}
|
||||
} else if (args.command == 'remove') {
|
||||
} else if (command == 'remove') {
|
||||
if (!tag) throw new Error(_('Cannot find "%s".', args.tag));
|
||||
if (!notes.length) throw new Error(_('Cannot find "%s".', args.note));
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
await Tag.removeNote(tag.id, notes[i].id);
|
||||
}
|
||||
} else if (args.command == 'list') {
|
||||
} else if (command == 'list') {
|
||||
if (tag) {
|
||||
let notes = await Tag.notes(tag.id);
|
||||
notes.map((note) => { this.log(note.title); });
|
||||
@ -45,7 +47,7 @@ class Command extends BaseCommand {
|
||||
tags.map((tag) => { this.log(tag.title); });
|
||||
}
|
||||
} else {
|
||||
throw new Error(_('Invalid command: "%s"', args.command));
|
||||
throw new Error(_('Invalid command: "%s"', command));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,11 @@ import { autocompleteItems } from './autocomplete.js';
|
||||
class Command extends BaseCommand {
|
||||
|
||||
usage() {
|
||||
return 'todo <action> <pattern>';
|
||||
return 'todo <todo-command> <note-pattern>';
|
||||
}
|
||||
|
||||
description() {
|
||||
return _('<action> can either be "toggle" or "clear". Use "toggle" to toggle the given todo between completed and uncompleted state (If the target is a regular note it will be converted to a todo). Use "clear" to convert the todo back to a regular note.');
|
||||
return _('<todo-command> can either be "toggle" or "clear". Use "toggle" to toggle the given todo between completed and uncompleted state (If the target is a regular note it will be converted to a todo). Use "clear" to convert the todo back to a regular note.');
|
||||
}
|
||||
|
||||
autocomplete() {
|
||||
@ -22,8 +22,8 @@ class Command extends BaseCommand {
|
||||
}
|
||||
|
||||
async action(args) {
|
||||
const action = args.action;
|
||||
const pattern = args.pattern;
|
||||
const action = args['todo-command'];
|
||||
const pattern = args['note-pattern'];
|
||||
const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern);
|
||||
if (!notes.length) throw new Error(_('Cannot find "%s".', pattern));
|
||||
|
||||
|
@ -7,6 +7,7 @@ mkdir -p "$CLIENT_DIR/build"
|
||||
rm -f "$CLIENT_DIR/app/lib"
|
||||
ln -s "$CLIENT_DIR/../ReactNativeClient/lib" "$CLIENT_DIR/app"
|
||||
cp "$CLIENT_DIR/package.json" "$CLIENT_DIR/build"
|
||||
cp "$CLIENT_DIR/app/autocompletion_template.txt" "$CLIENT_DIR/build"
|
||||
|
||||
npm run build
|
||||
#yarn run build
|
||||
|
Loading…
Reference in New Issue
Block a user