You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-09-16 08:56:40 +02:00
Added cp command
This commit is contained in:
@@ -42,7 +42,7 @@ class Application {
|
|||||||
updatePrompt() {
|
updatePrompt() {
|
||||||
if (!this.showPromptString_) return '';
|
if (!this.showPromptString_) return '';
|
||||||
|
|
||||||
let path = '~';
|
let path = '';
|
||||||
if (this.currentFolder()) {
|
if (this.currentFolder()) {
|
||||||
path += '/' + this.currentFolder().title;
|
path += '/' + this.currentFolder().title;
|
||||||
}
|
}
|
||||||
@@ -57,45 +57,38 @@ class Application {
|
|||||||
this.updatePrompt();
|
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) {
|
async loadItem(type, pattern) {
|
||||||
let output = await this.loadItems(type, pattern);
|
let output = await this.loadItems(type, pattern);
|
||||||
return output.length ? output[0] : null;
|
return output.length ? output[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadItems(type, pattern) {
|
async loadItems(type, pattern, options = null) {
|
||||||
let ItemClass = BaseItem.itemClass(type);
|
if (!options) options = {};
|
||||||
let item = null;
|
|
||||||
if (type == BaseModel.TYPE_NOTE) {
|
const parent = options.parent ? options.parent : app().currentFolder();
|
||||||
if (!app().currentFolder()) throw new Error(_('No notebook has been created.'));
|
const ItemClass = BaseItem.itemClass(type);
|
||||||
item = await ItemClass.loadFolderNoteByField(app().currentFolder().id, 'title', pattern);
|
|
||||||
} else {
|
if (type == BaseModel.TYPE_NOTE && pattern.indexOf('*') >= 0) { // Handle it as pattern
|
||||||
item = await ItemClass.loadByTitle(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 [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@@ -23,22 +23,10 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
let title = args['title'];
|
let title = args['title'];
|
||||||
|
|
||||||
let item = null;
|
let item = await app().loadItem(BaseModel.TYPE_NOTE, title);
|
||||||
if (!app().currentFolder()) {
|
if (!item) throw new Error(_('No item "%s" found.', title));
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const content = await Note.serialize(item);
|
||||||
this.log(content);
|
this.log(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
48
CliClient/app/command-cp.js
Normal file
48
CliClient/app/command-cp.js
Normal 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;
|
@@ -13,7 +13,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
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() {
|
options() {
|
||||||
@@ -51,7 +51,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
if (pattern) queryOptions.titlePattern = pattern;
|
if (pattern) queryOptions.titlePattern = pattern;
|
||||||
|
|
||||||
if (pattern == '..' || !app().currentFolder()) {
|
if (pattern == '/' || !app().currentFolder()) {
|
||||||
items = await Folder.all(queryOptions);
|
items = await Folder.all(queryOptions);
|
||||||
suffix = '/';
|
suffix = '/';
|
||||||
} else {
|
} else {
|
||||||
|
@@ -20,11 +20,9 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
if (!app().currentFolder()) throw new Error(_('Notes can only be created within a notebook.'));
|
if (!app().currentFolder()) throw new Error(_('Notes can only be created within a notebook.'));
|
||||||
|
|
||||||
let path = await app().parseNotePattern(args['note']);
|
|
||||||
|
|
||||||
let note = {
|
let note = {
|
||||||
title: path.title,
|
title: args.note,
|
||||||
parent_id: path.parent ? path.parent.id : app().currentFolder().id,
|
parent_id: app().currentFolder().id,
|
||||||
};
|
};
|
||||||
|
|
||||||
note = await Note.save(note);
|
note = await Note.save(note);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { BaseCommand } from './base-command.js';
|
import { BaseCommand } from './base-command.js';
|
||||||
import { app } from './app.js';
|
import { app } from './app.js';
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
import { Folder } from 'lib/models/folder.js';
|
import { Folder } from 'lib/models/folder.js';
|
||||||
import { Note } from 'lib/models/note.js';
|
import { Note } from 'lib/models/note.js';
|
||||||
import { autocompleteItems } from './autocomplete.js';
|
import { autocompleteItems } from './autocomplete.js';
|
||||||
@@ -20,13 +21,12 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
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']);
|
const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern);
|
||||||
if (!folder) throw new Error(_('No folder with title "%s"', args['notebook']));
|
|
||||||
let notes = await Note.previews(app().currentFolder().id, { titlePattern: pattern });
|
|
||||||
if (!notes.length) throw new Error(_('No note matches this pattern: "%s"', pattern));
|
if (!notes.length) throw new Error(_('No note matches this pattern: "%s"', pattern));
|
||||||
|
|
||||||
for (let i = 0; i < notes.length; i++) {
|
for (let i = 0; i < notes.length; i++) {
|
||||||
|
@@ -15,7 +15,7 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
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() {
|
autocomplete() {
|
||||||
@@ -25,44 +25,28 @@ class Command extends BaseCommand {
|
|||||||
options() {
|
options() {
|
||||||
return [
|
return [
|
||||||
['-f, --force', 'Deletes the items without asking for confirmation.'],
|
['-f, --force', 'Deletes the items without asking for confirmation.'],
|
||||||
|
['-r, --recursive', 'Deletes a notebook.'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let pattern = args['pattern'].toString();
|
const pattern = args['pattern'].toString();
|
||||||
let itemType = null;
|
const recursive = args.options && args.options.recursive === true;
|
||||||
let force = args.options && args.options.force === true;
|
const force = args.options && args.options.force === true;
|
||||||
|
|
||||||
if (pattern.indexOf('*') < 0) { // Handle it as a simple title
|
if (recursive) {
|
||||||
if (pattern.substr(0, 3) == '../') {
|
const folder = await app().loadItem(BaseModel.TYPE_FOLDER, pattern);
|
||||||
itemType = BaseModel.TYPE_FOLDER;
|
if (!folder) throw new Error(_('No notebook matchin pattern "%s"', pattern));
|
||||||
pattern = pattern.substr(3);
|
const ok = force ? true : await vorpalUtils.cmdPromptConfirm(this, _('Delete notebook "%s"?', folder.title));
|
||||||
} else {
|
if (!ok) return;
|
||||||
itemType = BaseModel.TYPE_NOTE;
|
await Folder.delete(folder.id);
|
||||||
}
|
} else {
|
||||||
|
const notes = await app().loadItems(BaseModel.TYPE_NOTE, pattern);
|
||||||
let item = item = await app().loadItem(itemType, pattern); // await BaseItem.loadItemByField(itemType, 'title', pattern);
|
if (!notes.length) throw new Error(_('No note matchin pattern "%s"', pattern));
|
||||||
if (!item) throw new Error(_('No item "%s" found.', pattern));
|
const ok = force ? true : await vorpalUtils.cmdPromptConfirm(this, _('%d notes match this pattern. Delete them?', notes.length));
|
||||||
|
if (!ok) return;
|
||||||
let ok = force ? true : await vorpalUtils.cmdPromptConfirm(this, _('Delete "%s"?', item.title));
|
let ids = notes.map((n) => n.id);
|
||||||
if (ok) {
|
await Note.batchDelete(ids);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { BaseCommand } from './base-command.js';
|
import { BaseCommand } from './base-command.js';
|
||||||
import { app } from './app.js';
|
import { app } from './app.js';
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
import { Folder } from 'lib/models/folder.js';
|
import { Folder } from 'lib/models/folder.js';
|
||||||
import { Note } from 'lib/models/note.js';
|
import { Note } from 'lib/models/note.js';
|
||||||
import { BaseItem } from 'lib/models/base-item.js';
|
import { BaseItem } from 'lib/models/base-item.js';
|
||||||
@@ -9,11 +10,11 @@ import { autocompleteItems } from './autocomplete.js';
|
|||||||
class Command extends BaseCommand {
|
class Command extends BaseCommand {
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
return 'set <item> <name> [value]';
|
return 'set <note> <name> [value]';
|
||||||
}
|
}
|
||||||
|
|
||||||
description() {
|
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() {
|
autocomplete() {
|
||||||
@@ -21,31 +22,22 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let title = args['item'];
|
let title = args['note'];
|
||||||
let propName = args['name'];
|
let propName = args['name'];
|
||||||
let propValue = args['value'];
|
let propValue = args['value'];
|
||||||
if (!propValue) propValue = '';
|
if (!propValue) propValue = '';
|
||||||
|
|
||||||
let item = null;
|
let notes = await app().loadItems(BaseModel.TYPE_NOTE, title);
|
||||||
if (!app().currentFolder()) {
|
if (!notes.length) throw new Error(_('No note "%s" found.', title));
|
||||||
item = await Folder.loadByField('title', title);
|
|
||||||
} else {
|
for (let i = 0; i < notes.length; i++) {
|
||||||
item = await Note.loadFolderNoteByField(app().currentFolder().id, 'title', title);
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -56,6 +56,7 @@ class Command extends BaseCommand {
|
|||||||
this.log(_('Starting synchronization...'));
|
this.log(_('Starting synchronization...'));
|
||||||
|
|
||||||
await sync.start(options);
|
await sync.start(options);
|
||||||
|
vorpalUtils.redrawDone();
|
||||||
this.log(_('Done.'));
|
this.log(_('Done.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,19 +17,25 @@ class Command extends BaseCommand {
|
|||||||
async action(args) {
|
async action(args) {
|
||||||
let tag = null;
|
let tag = null;
|
||||||
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
|
if (args.tag) tag = await app().loadItem(BaseModel.TYPE_TAG, args.tag);
|
||||||
let note = null;
|
let notes = [];
|
||||||
if (args.note) note = await app().loadItem(BaseModel.TYPE_NOTE, args.note);
|
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 == 'remove' && !tag) throw new Error(_('Tag does not exist: "%s"', args.tag));
|
||||||
|
|
||||||
if (args.command == 'add') {
|
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 });
|
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') {
|
} else if (args.command == 'remove') {
|
||||||
if (!tag) throw new Error(_('Tag does not exist: "%s"', args.tag));
|
if (!tag) throw new Error(_('Tag does not exist: "%s"', args.tag));
|
||||||
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));
|
||||||
await Tag.removeNote(tag.id, note.id);
|
for (let i = 0; i < notes.length; i++) {
|
||||||
|
await Tag.removeNote(tag.id, notes[i].id);
|
||||||
|
}
|
||||||
} else if (args.command == 'list') {
|
} else if (args.command == 'list') {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
let notes = await Tag.notes(tag.id);
|
let notes = await Tag.notes(tag.id);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { BaseCommand } from './base-command.js';
|
import { BaseCommand } from './base-command.js';
|
||||||
import { app } from './app.js';
|
import { app } from './app.js';
|
||||||
import { _ } from 'lib/locale.js';
|
import { _ } from 'lib/locale.js';
|
||||||
|
import { BaseModel } from 'lib/base-model.js';
|
||||||
import { Folder } from 'lib/models/folder.js';
|
import { Folder } from 'lib/models/folder.js';
|
||||||
import { autocompleteFolders } from './autocomplete.js';
|
import { autocompleteFolders } from './autocomplete.js';
|
||||||
|
|
||||||
@@ -23,10 +24,8 @@ class Command extends BaseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async action(args) {
|
async action(args) {
|
||||||
let title = args['notebook'];
|
let folder = await app().loadItem(BaseModel.TYPE_FOLDER, args['notebook']);
|
||||||
|
if (!folder) throw new Error(_('No folder "%s"', title));
|
||||||
let folder = await Folder.loadByField('title', title);
|
|
||||||
if (!folder) throw new Error(_('Invalid folder title: %s', title));
|
|
||||||
app().switchCurrentFolder(folder);
|
app().switchCurrentFolder(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -134,7 +134,7 @@ async function execRandomCommand(client) {
|
|||||||
if (item.type_ == 1) {
|
if (item.type_ == 1) {
|
||||||
return execCommand(client, 'rm -f ' + item.id);
|
return execCommand(client, 'rm -f ' + item.id);
|
||||||
} else if (item.type_ == 2) {
|
} 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) {
|
} else if (item.type_ == 5) {
|
||||||
// tag
|
// tag
|
||||||
} else {
|
} else {
|
||||||
@@ -153,7 +153,7 @@ async function execRandomCommand(client) {
|
|||||||
}, 30],
|
}, 30],
|
||||||
[async () => { // UPDATE RANDOM ITEM
|
[async () => { // UPDATE RANDOM ITEM
|
||||||
let items = await clientItems(client);
|
let items = await clientItems(client);
|
||||||
let item = randomElement(items);
|
let item = randomNote(items);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
return execCommand(client, 'set ' + item.id + ' title "' + randomWord() + '"');
|
return execCommand(client, 'set ' + item.id + ' title "' + randomWord() + '"');
|
||||||
|
@@ -14,9 +14,9 @@ function initialize(vorpal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function redrawEnabled() {
|
function redrawEnabled() {
|
||||||
// Always disabled for now - doesn't play well with command.cancel()
|
// // Always disabled for now - doesn't play well with command.cancel()
|
||||||
// function (it makes the whole app quit instead of just the
|
// // function (it makes the whole app quit instead of just the
|
||||||
// current command).
|
// // current command).
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return redrawEnabled_;
|
return redrawEnabled_;
|
||||||
@@ -33,7 +33,7 @@ function setStackTraceEnabled(v) {
|
|||||||
function redraw(s) {
|
function redraw(s) {
|
||||||
if (!redrawEnabled()) {
|
if (!redrawEnabled()) {
|
||||||
const now = time.unixMs();
|
const now = time.unixMs();
|
||||||
if (now - redrawLastUpdateTime_ > 1000) {
|
if (now - redrawLastUpdateTime_ > 4000) {
|
||||||
if (vorpal_.activeCommand) {
|
if (vorpal_.activeCommand) {
|
||||||
vorpal_.activeCommand.log(s);
|
vorpal_.activeCommand.log(s);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
"url": "https://github.com/laurent22/joplin"
|
"url": "https://github.com/laurent22/joplin"
|
||||||
},
|
},
|
||||||
"url": "git://github.com/laurent22/joplin.git",
|
"url": "git://github.com/laurent22/joplin.git",
|
||||||
"version": "0.8.35",
|
"version": "0.8.36",
|
||||||
"bin": {
|
"bin": {
|
||||||
"joplin": "./main_launcher.js"
|
"joplin": "./main_launcher.js"
|
||||||
},
|
},
|
||||||
|
@@ -277,6 +277,12 @@ class BaseModel {
|
|||||||
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]);
|
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() {
|
static db() {
|
||||||
if (!this.db_) throw new Error('Accessing database before it has been initialised');
|
if (!this.db_) throw new Error('Accessing database before it has been initialised');
|
||||||
return this.db_;
|
return this.db_;
|
||||||
|
@@ -133,13 +133,33 @@ class BaseItem extends BaseModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async delete(id, options = null) {
|
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;
|
let trackDeleted = true;
|
||||||
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
|
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
|
||||||
|
|
||||||
await super.delete(id, options);
|
await super.batchDelete(ids, options);
|
||||||
|
|
||||||
if (trackDeleted) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import { Folder } from 'lib/models/folder.js';
|
|||||||
import { BaseItem } from 'lib/models/base-item.js';
|
import { BaseItem } from 'lib/models/base-item.js';
|
||||||
import { Setting } from 'lib/models/setting.js';
|
import { Setting } from 'lib/models/setting.js';
|
||||||
import { shim } from 'lib/shim.js';
|
import { shim } from 'lib/shim.js';
|
||||||
|
import { time } from 'lib/time-utils.js';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
|
|
||||||
@@ -95,25 +96,41 @@ class Note extends BaseItem {
|
|||||||
return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 0');
|
return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateGeolocation(noteId) {
|
static async updateGeolocation(noteId) {
|
||||||
if (!Note.updateGeolocationEnabled_) return;
|
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);
|
this.logger().info('Updating lat/long of note ' + noteId);
|
||||||
|
|
||||||
let geoData = null;
|
let note = Note.load(noteId);
|
||||||
return shim.Geolocation.currentPosition().then((data) => {
|
if (!note) return; // Race condition - note has been deleted in the meantime
|
||||||
this.logger().info('Got lat/long');
|
|
||||||
geoData = data;
|
note.longitude = geoData.coords.longitude;
|
||||||
return Note.load(noteId);
|
note.latitude = geoData.coords.latitude;
|
||||||
}).then((note) => {
|
note.altitude = geoData.coords.altitude;
|
||||||
if (!note) return; // Race condition - note has been deleted in the meantime
|
return Note.save(note);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static filter(note) {
|
static filter(note) {
|
||||||
@@ -126,6 +143,24 @@ class Note extends BaseItem {
|
|||||||
return output;
|
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) {
|
static save(o, options = null) {
|
||||||
let isNew = this.isNew(o, options);
|
let isNew = this.isNew(o, options);
|
||||||
if (isNew && !o.source) o.source = Setting.value('appName');
|
if (isNew && !o.source) o.source = Setting.value('appName');
|
||||||
@@ -147,5 +182,6 @@ class Note extends BaseItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Note.updateGeolocationEnabled_ = true;
|
Note.updateGeolocationEnabled_ = true;
|
||||||
|
Note.geolocationUpdating_ = false;
|
||||||
|
|
||||||
export { Note };
|
export { Note };
|
Reference in New Issue
Block a user