You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-13 22:12:50 +02:00
Removed default folder concept
This commit is contained in:
@@ -63,12 +63,12 @@ async function main() {
|
|||||||
// console.info('DELETING ALL DATA');
|
// console.info('DELETING ALL DATA');
|
||||||
// await db.exec('DELETE FROM notes');
|
// await db.exec('DELETE FROM notes');
|
||||||
// await db.exec('DELETE FROM changes');
|
// await db.exec('DELETE FROM changes');
|
||||||
// await db.exec('DELETE FROM folders WHERE is_default != 1');
|
// await db.exec('DELETE FROM folders');
|
||||||
// await db.exec('DELETE FROM resources');
|
// await db.exec('DELETE FROM resources');
|
||||||
// await db.exec('DELETE FROM deleted_items');
|
// await db.exec('DELETE FROM deleted_items');
|
||||||
// await db.exec('DELETE FROM tags');
|
// await db.exec('DELETE FROM tags');
|
||||||
// await db.exec('DELETE FROM note_tags');
|
// await db.exec('DELETE FROM note_tags');
|
||||||
// let folder1 = await Folder.save({ title: 'test1', is_default: 1 });
|
// let folder1 = await Folder.save({ title: 'test1' });
|
||||||
// let folder2 = await Folder.save({ title: 'test2' });
|
// let folder2 = await Folder.save({ title: 'test2' });
|
||||||
// await importEnex(folder1.id, '/mnt/c/Users/Laurent/Desktop/Laurent.enex');
|
// await importEnex(folder1.id, '/mnt/c/Users/Laurent/Desktop/Laurent.enex');
|
||||||
// return;
|
// return;
|
||||||
@@ -324,13 +324,27 @@ async function main() {
|
|||||||
|
|
||||||
commands.push({
|
commands.push({
|
||||||
usage: 'rm <item-title>',
|
usage: 'rm <item-title>',
|
||||||
description: 'Deletes the given item. For a notebook, all the notes within that notebook will be deleted.',
|
description: 'Deletes the given item. For a notebook, all the notes within that notebook will be deleted. Use `rm ../<notebook-name>` to delete a notebook.',
|
||||||
action: async function(args, end) {
|
action: async function(args, end) {
|
||||||
let title = args['item-title'];
|
let title = args['item-title'];
|
||||||
let itemType = currentFolder ? BaseModel.MODEL_TYPE_NOTE : BaseModel.MODEL_TYPE_FOLDER;
|
let itemType = null;
|
||||||
|
|
||||||
|
if (title.substr(0, 3) == '../') {
|
||||||
|
itemType = BaseModel.MODEL_TYPE_FOLDER;
|
||||||
|
title = title.substr(3);
|
||||||
|
} else {
|
||||||
|
itemType = BaseModel.MODEL_TYPE_NOTE;
|
||||||
|
}
|
||||||
|
|
||||||
let item = await BaseItem.loadItemByField(itemType, 'title', title);
|
let item = await BaseItem.loadItemByField(itemType, 'title', title);
|
||||||
if (!item) return commandError(this, _('No item with title "%s" found.', title), end);
|
if (!item) return commandError(this, _('No item with title "%s" found.', title), end);
|
||||||
await BaseItem.deleteItem(itemType, item.id);
|
await BaseItem.deleteItem(itemType, item.id);
|
||||||
|
|
||||||
|
if (currentFolder && currentFolder.id == item.id) {
|
||||||
|
let f = await Folder.defaultFolder();
|
||||||
|
switchCurrentFolder(f);
|
||||||
|
}
|
||||||
|
|
||||||
end();
|
end();
|
||||||
},
|
},
|
||||||
autocomplete: autocompleteItems,
|
autocomplete: autocompleteItems,
|
||||||
|
@@ -9,44 +9,4 @@ import { BaseModel } from 'lib/base-model.js';
|
|||||||
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
console.error('Unhandled promise rejection at: Promise', p, 'reason:', reason);
|
console.error('Unhandled promise rejection at: Promise', p, 'reason:', reason);
|
||||||
});
|
|
||||||
|
|
||||||
async function thereIsOnlyOneDefaultFolder() {
|
|
||||||
let count = 0;
|
|
||||||
let folders = await Folder.all();
|
|
||||||
for (let i = 0; i < folders.length; i++) {
|
|
||||||
if (!!folders[i].is_default) count++;
|
|
||||||
}
|
|
||||||
return count === 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Folder', function() {
|
|
||||||
|
|
||||||
beforeEach( async (done) => {
|
|
||||||
await setupDatabase(1);
|
|
||||||
switchClient(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have one default folder only', async (done) => {
|
|
||||||
let f1 = await Folder.save({ title: 'folder1', is_default: 1 });
|
|
||||||
let f2 = await Folder.save({ title: 'folder2' });
|
|
||||||
let f3 = await Folder.save({ title: 'folder3' });
|
|
||||||
|
|
||||||
await Folder.save({ id: f2.id, is_default: 1 });
|
|
||||||
f2 = await Folder.load(f2.id);
|
|
||||||
|
|
||||||
expect(f2.is_default).toBe(1);
|
|
||||||
|
|
||||||
let r = await thereIsOnlyOneDefaultFolder();
|
|
||||||
expect(r).toBe(true);
|
|
||||||
|
|
||||||
await Folder.save({ id: f2.id, is_default: 0 });
|
|
||||||
f2 = await Folder.load(f2.id);
|
|
||||||
|
|
||||||
expect(f2.is_default).toBe(1);
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
@@ -53,7 +53,7 @@ class SideMenuContentComponent extends Component {
|
|||||||
let buttons = [];
|
let buttons = [];
|
||||||
for (let i = 0; i < this.props.folders.length; i++) {
|
for (let i = 0; i < this.props.folders.length; i++) {
|
||||||
let f = this.props.folders[i];
|
let f = this.props.folders[i];
|
||||||
let title = f.title + (f.is_default ? ' *' : '');
|
let title = f.title;
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<Button style={styles.button} title={title} onPress={() => { this.folder_press(f) }} key={f.id} />
|
<Button style={styles.button} title={title} onPress={() => { this.folder_press(f) }} key={f.id} />
|
||||||
);
|
);
|
||||||
|
@@ -254,10 +254,7 @@ class BaseModel {
|
|||||||
static delete(id, options = null) {
|
static delete(id, options = null) {
|
||||||
options = this.modOptions(options);
|
options = this.modOptions(options);
|
||||||
|
|
||||||
if (!id) {
|
if (!id) throw new Error('Cannot delete object without an ID');
|
||||||
Log.warn('Cannot delete object without an ID');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]).then(() => {
|
return this.db().exec('DELETE FROM ' + this.tableName() + ' WHERE id = ?', [id]).then(() => {
|
||||||
let trackDeleted = this.trackDeleted();
|
let trackDeleted = this.trackDeleted();
|
||||||
|
@@ -10,8 +10,7 @@ CREATE TABLE folders (
|
|||||||
title TEXT NOT NULL DEFAULT "",
|
title TEXT NOT NULL DEFAULT "",
|
||||||
created_time INT NOT NULL DEFAULT 0,
|
created_time INT NOT NULL DEFAULT 0,
|
||||||
updated_time INT NOT NULL DEFAULT 0,
|
updated_time INT NOT NULL DEFAULT 0,
|
||||||
sync_time INT NOT NULL DEFAULT 0,
|
sync_time INT NOT NULL DEFAULT 0
|
||||||
is_default INT NOT NULL DEFAULT 0
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE notes (
|
CREATE TABLE notes (
|
||||||
@@ -437,7 +436,7 @@ class Database {
|
|||||||
|
|
||||||
let queries = this.wrapQueries(this.sqlStringToLines(structureSql));
|
let queries = this.wrapQueries(this.sqlStringToLines(structureSql));
|
||||||
queries.push(this.wrapQuery('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumId('settings', 'string') + '")'));
|
queries.push(this.wrapQuery('INSERT INTO settings (`key`, `value`, `type`) VALUES ("clientId", "' + uuid.create() + '", "' + Database.enumId('settings', 'string') + '")'));
|
||||||
queries.push(this.wrapQuery('INSERT INTO folders (`id`, `title`, `is_default`, `created_time`) VALUES ("' + uuid.create() + '", "' + _('Notebook') + '", 1, ' + (new Date()).getTime() + ')'));
|
queries.push(this.wrapQuery('INSERT INTO folders (`id`, `title`, `created_time`) VALUES ("' + uuid.create() + '", "' + _('Notebook') + '", ' + (new Date()).getTime() + ')'));
|
||||||
|
|
||||||
return this.transactionExecBatch(queries).then(() => {
|
return this.transactionExecBatch(queries).then(() => {
|
||||||
this.logger().info('Database schema created successfully');
|
this.logger().info('Database schema created successfully');
|
||||||
|
@@ -43,6 +43,21 @@ class BaseItem extends BaseModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static loadItemByField(itemType, field, value) {
|
||||||
|
let ItemClass = this.itemClass(itemType);
|
||||||
|
return ItemClass.loadByField(field, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadItem(itemType, id) {
|
||||||
|
let ItemClass = this.itemClass(itemType);
|
||||||
|
return ItemClass.load(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static deleteItem(itemType, id) {
|
||||||
|
let ItemClass = this.itemClass(itemType);
|
||||||
|
return ItemClass.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
static serialize_format(propName, propValue) {
|
static serialize_format(propName, propValue) {
|
||||||
if (['created_time', 'updated_time'].indexOf(propName) >= 0) {
|
if (['created_time', 'updated_time'].indexOf(propName) >= 0) {
|
||||||
if (!propValue) return '';
|
if (!propValue) return '';
|
||||||
|
@@ -57,31 +57,23 @@ class Folder extends BaseItem {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static delete(folderId, options = null) {
|
static async delete(folderId, options = null) {
|
||||||
return this.load(folderId).then((folder) => {
|
let folder = await Folder.load(folderId);
|
||||||
if (!folder) throw new Error('Trying to delete non-existing folder: ' + folderId);
|
if (!folder) throw new Error('Trying to delete non-existing notebook: ' + folderId);
|
||||||
|
|
||||||
if (!!folder.is_default) {
|
let count = await Folder.count();
|
||||||
throw new Error(_('Cannot delete the default list'));
|
if (count <= 1) throw new Error(_('Cannot delete the last notebook'));
|
||||||
}
|
|
||||||
}).then(() => {
|
|
||||||
return this.noteIds(folderId);
|
|
||||||
}).then((ids) => {
|
|
||||||
let chain = [];
|
|
||||||
for (let i = 0; i < ids.length; i++) {
|
|
||||||
chain.push(() => {
|
|
||||||
return Note.delete(ids[i]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return promiseChain(chain);
|
let noteIds = await Folder.noteIds(folderId);
|
||||||
}).then(() => {
|
for (let i = 0; i < noteIds.length; i++) {
|
||||||
return super.delete(folderId, options);
|
await Note.delete(noteIds[i]);
|
||||||
}).then(() => {
|
}
|
||||||
this.dispatch({
|
|
||||||
type: 'FOLDER_DELETE',
|
super.delete(folderId, options);
|
||||||
folderId: folderId,
|
|
||||||
});
|
this.dispatch({
|
||||||
|
type: 'FOLDER_DELETE',
|
||||||
|
folderId: folderId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,25 +90,12 @@ class Folder extends BaseItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async defaultFolder() {
|
static async defaultFolder() {
|
||||||
return this.modelSelectOne('SELECT * FROM folders WHERE is_default = 1');
|
return this.modelSelectOne('SELECT * FROM folders ORDER BY created_time DESC LIMIT 1');
|
||||||
}
|
}
|
||||||
|
|
||||||
static save(o, options = null) {
|
static save(o, options = null) {
|
||||||
return Folder.loadByField('title', o.title).then((existingFolder) => {
|
return Folder.loadByField('title', o.title).then((existingFolder) => {
|
||||||
if (existingFolder && existingFolder.id != o.id) throw new Error(_('A folder with title "%s" already exists', o.title));
|
if (existingFolder && existingFolder.id != o.id) throw new Error(_('A notebook with title "%s" already exists', o.title));
|
||||||
|
|
||||||
if ('is_default' in o) {
|
|
||||||
if (!o.is_default) {
|
|
||||||
o = Object.assign({}, o);
|
|
||||||
delete o.is_default;
|
|
||||||
Log.warn('is_default property cannot be set to 0 directly. Instead, set the folder that should become the default to 1.');
|
|
||||||
} else {
|
|
||||||
options = this.modOptions(options);
|
|
||||||
options.transactionNextQueries.push(
|
|
||||||
{ sql: 'UPDATE folders SET is_default = 0 WHERE id != ?', params: [o.id] },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.save(o, options).then((folder) => {
|
return super.save(o, options).then((folder) => {
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
|
Reference in New Issue
Block a user