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

Added cp command

This commit is contained in:
Laurent Cozic 2017-07-11 18:17:23 +00:00
parent 4fa65de31d
commit 8c5f0622a2
17 changed files with 221 additions and 150 deletions

View File

@ -42,7 +42,7 @@ class Application {
updatePrompt() {
if (!this.showPromptString_) return '';
let path = '~';
let path = '';
if (this.currentFolder()) {
path += '/' + this.currentFolder().title;
}
@ -57,45 +57,38 @@ class Application {
this.updatePrompt();
}
async parseNotePattern(pattern) {
if (pattern.indexOf('..') === 0) {
let pieces = pattern.split('/');
if (pieces.length != 3) throw new Error(_('Invalid pattern: %s', pattern));
let parent = await this.loadItem(BaseModel.TYPE_FOLDER, pieces[1]);
if (!parent) throw new Error(_('Notebook not found: %s', pieces[1]));
return {
parent: parent,
title: pieces[2],
};
} else {
return {
parent: null,
title: pattern,
};
}
}
async loadItem(type, pattern) {
let output = await this.loadItems(type, pattern);
return output.length ? output[0] : null;
}
async loadItems(type, pattern) {
let ItemClass = BaseItem.itemClass(type);
let item = null;
if (type == BaseModel.TYPE_NOTE) {
if (!app().currentFolder()) throw new Error(_('No notebook has been created.'));
item = await ItemClass.loadFolderNoteByField(app().currentFolder().id, 'title', pattern);
} else {
item = await ItemClass.loadByTitle(pattern);
async loadItems(type, pattern, options = null) {
if (!options) options = {};
const parent = options.parent ? options.parent : app().currentFolder();
const ItemClass = BaseItem.itemClass(type);
if (type == BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) { // Handle it as pattern
if (!parent) throw new Error(_('No notebook selected.'));
return await Note.previews(parent.id, { titlePattern: pattern });
} else { // Single item
let item = null;
if (type == BaseModel.TYPE_NOTE) {
if (!parent) throw new Error(_('No notebook has been specified.'));
item = await ItemClass.loadFolderNoteByField(parent.id, 'title', pattern);
} else {
item = await ItemClass.loadByTitle(pattern);
}
if (item) return [item];
item = await ItemClass.load(pattern); // Load by id
if (item) return [item];
if (pattern.length >= 4) {
item = await ItemClass.loadByPartialId(pattern);
if (item) return [item];
}
}
if (item) return [item];
item = await ItemClass.load(pattern); // Load by id
if (item) return [item];
item = await ItemClass.loadByPartialId(pattern);
if (item) return [item];
return [];
}

View File

@ -23,22 +23,10 @@ class Command extends BaseCommand {
async action(args) {
let title = args['title'];
let item = null;
if (!app().currentFolder()) {
item = await Folder.loadByField('title', title);
} else {
item = await app().loadItem(BaseModel.TYPE_NOTE, title);
}
if (!item) throw new Error(_('No item with title "%s" found.', title));
let content = null;
if (!app().currentFolder()) {
content = await Folder.serialize(item);
} else {
content = await Note.serialize(item);
}
let item = await app().loadItem(BaseModel.TYPE_NOTE, title);
if (!item) throw new Error(_('No item "%s" found.', title));
const content = await Note.serialize(item);
this.log(content);
}

View File

@ -0,0 +1,48 @@
import { BaseCommand } from './base-command.js';
import { app } from './app.js';
import { _ } from 'lib/locale.js';
import { BaseModel } from 'lib/base-model.js';
import { Folder } from 'lib/models/folder.js';
import { Note } from 'lib/models/note.js';
import { autocompleteItems } from './autocomplete.js';
class Command extends BaseCommand {
usage() {
return 'cp <pattern> [notebook]';
}
description() {
return 'Duplicates the notes matching <pattern> to [notebook]. If no notebook is specified the note is duplicated in the current notebook.';
}
autocomplete() {
return { data: autocompleteItems };
}
async action(args) {
let folder = null;
if (args['notebook']) {
folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
} else {
folder = app().currentFolder();
}
if (!folder) throw new Error(_('No notebook "%s"', args['notebook']));
const notes = await app().loadItems(BaseModel.TYPE_NOTE, args['pattern']);
if (!notes.length) throw new Error(_('No note matches this pattern: "%s"', args['pattern']));
for (let i = 0; i < notes.length; i++) {
const newNote = await Note.duplicate(notes[i].id, {
changes: {
parent_id: folder.id
},
});
Note.updateGeolocation(newNote.id);
}
}
}
module.exports = Command;

View File

@ -13,7 +13,7 @@ class Command extends BaseCommand {
}
description() {
return 'Displays the notes in [notebook]. Use `ls ..` to display the list of notebooks.';
return 'Displays the notes in [notebook]. Use `ls /` to display the list of notebooks.';
}
options() {
@ -51,7 +51,7 @@ class Command extends BaseCommand {
}
if (pattern) queryOptions.titlePattern = pattern;
if (pattern == '..' || !app().currentFolder()) {
if (pattern == '/' || !app().currentFolder()) {
items = await Folder.all(queryOptions);
suffix = '/';
} else {

View File

@ -20,11 +20,9 @@ class Command extends BaseCommand {
async action(args) {
if (!app().currentFolder()) throw new Error(_('Notes can only be created within a notebook.'));
let path = await app().parseNotePattern(args['note']);
let note = {
title: path.title,
parent_id: path.parent ? path.parent.id : app().currentFolder().id,
title: args.note,
parent_id: app().currentFolder().id,
};
note = await Note.save(note);

View File

@ -1,6 +1,7 @@
import { BaseCommand } from './base-command.js';
import { app } from './app.js';
import { _ } from 'lib/locale.js';
import { BaseModel } from 'lib/base-model.js';
import { Folder } from 'lib/models/folder.js';
import { Note } from 'lib/models/note.js';
import { autocompleteItems } from './autocomplete.js';
@ -20,13 +21,12 @@ class Command extends BaseCommand {
}
async action(args) {
if (!app().currentFolder()) throw new Error(_('Please select a notebook first.'));
const pattern = args['pattern'];
let pattern = args['pattern'];
const folder = await Folder.loadByField('title', args['notebook']);
if (!folder) throw new Error(_('No notebook "%s"', args['notebook']));
let folder = await Folder.loadByField('title', args['notebook']);
if (!folder) throw new Error(_('No folder with title "%s"', args['notebook']));
let notes = await Note.previews(app().currentFolder().id, { titlePattern: pattern });
const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern);
if (!notes.length) throw new Error(_('No note matches this pattern: "%s"', pattern));
for (let i = 0; i < notes.length; i++) {

View File

@ -15,7 +15,7 @@ class Command extends BaseCommand {
}
description() {
return 'Deletes the given item. For a notebook, all the notes within that notebook will be deleted. Use `rm ../<notebook>` to delete a notebook.';
return 'Deletes the items matching <pattern>.';
}
autocomplete() {
@ -25,44 +25,28 @@ class Command extends BaseCommand {
options() {
return [
['-f, --force', 'Deletes the items without asking for confirmation.'],
['-r, --recursive', 'Deletes a notebook.'],
];
}
async action(args) {
let pattern = args['pattern'].toString();
let itemType = null;
let force = args.options && args.options.force === true;
const pattern = args['pattern'].toString();
const recursive = args.options && args.options.recursive === true;
const force = args.options && args.options.force === true;
if (pattern.indexOf('*') < 0) { // Handle it as a simple title
if (pattern.substr(0, 3) == '../') {
itemType = BaseModel.TYPE_FOLDER;
pattern = pattern.substr(3);
} else {
itemType = BaseModel.TYPE_NOTE;
}
let item = item = await app().loadItem(itemType, pattern); // await BaseItem.loadItemByField(itemType, 'title', pattern);
if (!item) throw new Error(_('No item "%s" found.', pattern));
let ok = force ? true : await vorpalUtils.cmdPromptConfirm(this, _('Delete "%s"?', item.title));
if (ok) {
await BaseItem.deleteItem(itemType, item.id);
if (app().currentFolder() && app().currentFolder().id == item.id) {
let f = await Folder.defaultFolder();
app().switchCurrentFolder(f);
}
}
} else { // Handle it as a glob pattern
if (app().currentFolder()) {
let notes = await Note.previews(app().currentFolder().id, { titlePattern: pattern });
if (!notes.length) throw new Error(_('No note matches this pattern: "%s"', pattern));
let ok = force ? true : await vorpalUtils.cmdPromptConfirm(this, _('%d notes match this pattern. Delete them?', notes.length));
if (ok) {
for (let i = 0; i < notes.length; i++) {
await Note.delete(notes[i].id);
}
}
}
if (recursive) {
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
if (!folder) throw new Error(_('No notebook matchin pattern "%s"', pattern));
const ok = force ? true : await vorpalUtils.cmdPromptConfirm(this, _('Delete notebook "%s"?', folder.title));
if (!ok) return;
await Folder.delete(folder.id);
} else {
const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern);
if (!notes.length) throw new Error(_('No note matchin pattern "%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);
}
}

View File

@ -1,6 +1,7 @@
import { BaseCommand } from './base-command.js';
import { app } from './app.js';
import { _ } from 'lib/locale.js';
import { BaseModel } from 'lib/base-model.js';
import { Folder } from 'lib/models/folder.js';
import { Note } from 'lib/models/note.js';
import { BaseItem } from 'lib/models/base-item.js';
@ -9,11 +10,11 @@ import { autocompleteItems } from './autocomplete.js';
class Command extends BaseCommand {
usage() {
return 'set <item> <name> [value]';
return 'set <note> <name> [value]';
}
description() {
return 'Sets the property <name> of the given <item> to the given [value].';
return 'Sets the property <name> of the given <note> to the given [value].';
}
autocomplete() {
@ -21,31 +22,22 @@ class Command extends BaseCommand {
}
async action(args) {
let title = args['item'];
let title = args['note'];
let propName = args['name'];
let propValue = args['value'];
if (!propValue) propValue = '';
let item = null;
if (!app().currentFolder()) {
item = await Folder.loadByField('title', title);
} else {
item = await Note.loadFolderNoteByField(app().currentFolder().id, 'title', title);
let notes = await app().loadItems(BaseModel.TYPE_NOTE, title);
if (!notes.length) throw new Error(_('No note "%s" found.', title));
for (let i = 0; i < notes.length; i++) {
let newNote = {
id: notes[i].id,
type_: notes[i].type_,
};
newNote[propName] = propValue;
await Note.save(newNote);
}
if (!item) {
item = await BaseItem.loadItemById(title);
}
if (!item) throw new Error(_('No item with title "%s" found.', title));
let newItem = {
id: item.id,
type_: item.type_,
};
newItem[propName] = propValue;
let ItemClass = BaseItem.itemClass(newItem);
await ItemClass.save(newItem);
}
}

View File

@ -56,6 +56,7 @@ class Command extends BaseCommand {
this.log(_('Starting synchronization...'));
await sync.start(options);
vorpalUtils.redrawDone();
this.log(_('Done.'));
}
}

View File

@ -17,19 +17,25 @@ class Command extends BaseCommand {
async action(args) {
let tag = null;
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
let note = null;
if (args.note) note = await app().loadItem(BaseModel.TYPE_NOTE, args.note);
let notes = [];
if (args.note) {
notes = await app().loadItems(BaseModel.TYPE_NOTE, args.note);
}
if (args.command == 'remove' && !tag) throw new Error(_('Tag does not exist: "%s"', args.tag));
if (args.command == 'add') {
if (!note) throw new Error(_('Note does not exist: "%s"', args.note));
if (!notes.length) throw new Error(_('Note does not exist: "%s"', args.note));
if (!tag) tag = await Tag.save({ title: args.tag });
await Tag.addNote(tag.id, note.id);
for (let i = 0; i < notes.length; i++) {
await Tag.addNote(tag.id, notes[i].id);
}
} else if (args.command == 'remove') {
if (!tag) throw new Error(_('Tag does not exist: "%s"', args.tag));
if (!note) throw new Error(_('Note does not exist: "%s"', args.note));
await Tag.removeNote(tag.id, note.id);
if (!notes.length) throw new Error(_('Note does not exist: "%s"', args.note));
for (let i = 0; i < notes.length; i++) {
await Tag.removeNote(tag.id, notes[i].id);
}
} else if (args.command == 'list') {
if (tag) {
let notes = await Tag.notes(tag.id);

View File

@ -1,6 +1,7 @@
import { BaseCommand } from './base-command.js';
import { app } from './app.js';
import { _ } from 'lib/locale.js';
import { BaseModel } from 'lib/base-model.js';
import { Folder } from 'lib/models/folder.js';
import { autocompleteFolders } from './autocomplete.js';
@ -23,10 +24,8 @@ class Command extends BaseCommand {
}
async action(args) {
let title = args['notebook'];
let folder = await Folder.loadByField('title', title);
if (!folder) throw new Error(_('Invalid folder title: %s', title));
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
if (!folder) throw new Error(_('No folder "%s"', title));
app().switchCurrentFolder(folder);
}

View File

@ -134,7 +134,7 @@ async function execRandomCommand(client) {
if (item.type_ == 1) {
return execCommand(client, 'rm -f ' + item.id);
} else if (item.type_ == 2) {
return execCommand(client, 'rm -f ' + '../' + item.id);
return execCommand(client, 'rm -r -f ' + item.id);
} else if (item.type_ == 5) {
// tag
} else {
@ -153,7 +153,7 @@ async function execRandomCommand(client) {
}, 30],
[async () => { // UPDATE RANDOM ITEM
let items = await clientItems(client);
let item = randomElement(items);
let item = randomNote(items);
if (!item) return;
return execCommand(client, 'set ' + item.id + ' title "' + randomWord() + '"');

View File

@ -14,9 +14,9 @@ function initialize(vorpal) {
}
function redrawEnabled() {
// Always disabled for now - doesn't play well with command.cancel()
// function (it makes the whole app quit instead of just the
// current command).
// // Always disabled for now - doesn't play well with command.cancel()
// // function (it makes the whole app quit instead of just the
// // current command).
return false;
return redrawEnabled_;
@ -33,7 +33,7 @@ function setStackTraceEnabled(v) {
function redraw(s) {
if (!redrawEnabled()) {
const now = time.unixMs();
if (now - redrawLastUpdateTime_ > 1000) {
if (now - redrawLastUpdateTime_ > 4000) {
if (vorpal_.activeCommand) {
vorpal_.activeCommand.log(s);
} else {

View File

@ -7,7 +7,7 @@
"url": "https://github.com/laurent22/joplin"
},
"url": "git://github.com/laurent22/joplin.git",
"version": "0.8.35",
"version": "0.8.36",
"bin": {
"joplin": "./main_launcher.js"
},

View File

@ -277,6 +277,12 @@ class BaseModel {
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]);
}
static batchDelete(ids, options = null) {
options = this.modOptions(options);
if (!ids.length) throw new Error('Cannot delete object without an ID');
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id IN ("' + ids.join('","') + '")');
}
static db() {
if (!this.db_) throw new Error('Accessing database before it has been initialised');
return this.db_;

View File

@ -133,13 +133,33 @@ class BaseItem extends BaseModel {
}
static async delete(id, options = null) {
return this.batchDelete([id], options);
// let trackDeleted = true;
// if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
// await super.delete(id, options);
// if (trackDeleted) {
// await this.db().exec('INSERT INTO deleted_items (item_type, item_id, deleted_time) VALUES (?, ?, ?)', [this.modelType(), id, time.unixMs()]);
// }
}
static async batchDelete(ids, options = null) {
let trackDeleted = true;
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
await super.delete(id, options);
await super.batchDelete(ids, options);
if (trackDeleted) {
await this.db().exec('INSERT INTO deleted_items (item_type, item_id, deleted_time) VALUES (?, ?, ?)', [this.modelType(), id, time.unixMs()]);
let queries = [];
let now = time.unixMs();
for (let i = 0; i < ids.length; i++) {
queries.push({
sql: 'INSERT INTO deleted_items (item_type, item_id, deleted_time) VALUES (?, ?, ?)',
params: [this.modelType(), ids[i], now],
});
}
await this.db().transactionExecBatch(queries);
}
}

View File

@ -4,6 +4,7 @@ import { Folder } from 'lib/models/folder.js';
import { BaseItem } from 'lib/models/base-item.js';
import { Setting } from 'lib/models/setting.js';
import { shim } from 'lib/shim.js';
import { time } from 'lib/time-utils.js';
import moment from 'moment';
import lodash from 'lodash';
@ -95,25 +96,41 @@ class Note extends BaseItem {
return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 0');
}
static updateGeolocation(noteId) {
static async updateGeolocation(noteId) {
if (!Note.updateGeolocationEnabled_) return;
let startWait = time.unixMs();
while (true) {
if (!this.geolocationUpdating_) break;
this.logger().info('Waiting for geolocation update...');
await time.sleep(1);
if (startWait + 1000 * 20 < time.unixMs()) {
this.logger().warn('Failed to update geolocation for: timeout: ' + noteId);
return;
}
}
let geoData = null;
if (this.geolocationCache_ && this.geolocationCache_.timestamp + 1000 * 60 * 10 > time.unixMs()) {
geoData = Object.assign({}, this.geolocationCache_);
} else {
this.geolocationUpdating_ = true;
this.logger().info('Fetching geolocation...');
geoData = await shim.Geolocation.currentPosition();
this.logger().info('Got lat/long');
this.geolocationCache_ = geoData;
this.geolocationUpdating_ = false;
}
this.logger().info('Updating lat/long of note ' + noteId);
let geoData = null;
return shim.Geolocation.currentPosition().then((data) => {
this.logger().info('Got lat/long');
geoData = data;
return Note.load(noteId);
}).then((note) => {
if (!note) return; // Race condition - note has been deleted in the meantime
note.longitude = geoData.coords.longitude;
note.latitude = geoData.coords.latitude;
note.altitude = geoData.coords.altitude;
return Note.save(note);
}).catch((error) => {
this.logger().warn('Cannot get location:', error);
});
let note = Note.load(noteId);
if (!note) return; // Race condition - note has been deleted in the meantime
note.longitude = geoData.coords.longitude;
note.latitude = geoData.coords.latitude;
note.altitude = geoData.coords.altitude;
return Note.save(note);
}
static filter(note) {
@ -126,6 +143,24 @@ class Note extends BaseItem {
return output;
}
static async duplicate(noteId, options = null) {
const changes = options && options.changes;
const originalNote = await Note.load(noteId);
if (!originalNote) throw new Error('Unknown note: ' + noteId);
let newNote = Object.assign({}, originalNote);
delete newNote.id;
newNote.sync_time = 0;
for (let n in changes) {
if (!changes.hasOwnProperty(n)) continue;
newNote[n] = changes[n];
}
return this.save(newNote);
}
static save(o, options = null) {
let isNew = this.isNew(o, options);
if (isNew && !o.source) o.source = Setting.value('appName');
@ -147,5 +182,6 @@ class Note extends BaseItem {
}
Note.updateGeolocationEnabled_ = true;
Note.geolocationUpdating_ = false;
export { Note };