1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-15 09:04:04 +02:00
joplin/ReactNativeClient/lib/models/Folder.js

211 lines
6.0 KiB
JavaScript
Raw Normal View History

const BaseModel = require('lib/BaseModel.js');
const { promiseChain } = require('lib/promise-utils.js');
const { time } = require('lib/time-utils.js');
const Note = require('lib/models/Note.js');
const Setting = require('lib/models/Setting.js');
const { Database } = require('lib/database.js');
const { _ } = require('lib/locale.js');
const moment = require('moment');
const BaseItem = require('lib/models/BaseItem.js');
const lodash = require('lodash');
2017-05-15 21:10:00 +02:00
2017-06-15 20:18:48 +02:00
class Folder extends BaseItem {
2017-05-15 21:10:00 +02:00
static tableName() {
return 'folders';
2017-05-15 21:10:00 +02:00
}
2017-07-02 17:46:03 +02:00
static async serialize(folder) {
2017-06-29 22:52:52 +02:00
let fieldNames = this.fieldNames();
fieldNames.push('type_');
//lodash.pull(fieldNames, 'parent_id');
return super.serialize(folder, 'folder', fieldNames);
2017-05-15 21:10:00 +02:00
}
2017-07-03 21:50:45 +02:00
static modelType() {
return BaseModel.TYPE_FOLDER;
2017-05-18 21:58:01 +02:00
}
2017-05-15 21:10:00 +02:00
static newFolder() {
return {
id: null,
title: '',
}
2017-05-15 21:10:00 +02:00
}
2017-11-11 19:36:47 +02:00
static async findUniqueFolderTitle(title) {
let counter = 1;
let titleToTry = title;
while (true) {
const folder = await this.loadByField('title', titleToTry);
2017-11-11 19:36:47 +02:00
if (!folder) return titleToTry;
titleToTry = title + ' (' + counter + ')';
2017-11-11 19:36:47 +02:00
counter++;
if (counter >= 100) titleToTry = title + ' (' + ((new Date()).getTime()) + ')';
if (counter >= 1000) throw new Error('Cannot find unique title');
2017-11-11 19:36:47 +02:00
}
}
2017-06-20 00:18:24 +02:00
static noteIds(parentId) {
return this.db().selectAll('SELECT id FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]).then((rows) => {
let output = [];
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
output.push(row.id);
}
return output;
});
2017-05-18 22:31:40 +02:00
}
static async subFolderIds(parentId) {
const rows = await this.db().selectAll('SELECT id FROM folders WHERE parent_id = ?', [parentId]);
return rows.map(r => r.id);
}
2017-07-12 22:39:47 +02:00
static async noteCount(parentId) {
let r = await this.db().selectOne('SELECT count(*) as total FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]);
2017-07-12 22:39:47 +02:00
return r ? r.total : 0;
}
static markNotesAsConflict(parentId) {
let query = Database.updateQuery('notes', { is_conflict: 1 }, { parent_id: parentId });
return this.db().exec(query);
}
2017-06-25 09:52:25 +02:00
static async delete(folderId, options = null) {
if (!options) options = {};
if (!('deleteChildren' in options)) options.deleteChildren = true;
2017-06-25 09:52:25 +02:00
let folder = await Folder.load(folderId);
if (!folder) return; // noop
if (options.deleteChildren) {
let noteIds = await Folder.noteIds(folderId);
for (let i = 0; i < noteIds.length; i++) {
await Note.delete(noteIds[i]);
}
let subFolderIds = await Folder.subFolderIds(folderId);
for (let i = 0; i < subFolderIds.length; i++) {
await Folder.delete(subFolderIds[i]);
}
2017-06-25 09:52:25 +02:00
}
2017-06-25 17:17:40 +02:00
await super.delete(folderId, options);
2017-06-25 09:52:25 +02:00
this.dispatch({
type: 'FOLDER_DELETE',
2017-11-08 23:39:07 +02:00
id: folderId,
});
}
2017-07-15 17:35:40 +02:00
static conflictFolderTitle() {
return _('Conflicts');
2017-07-15 17:35:40 +02:00
}
2017-06-18 01:49:52 +02:00
2017-07-15 17:35:40 +02:00
static conflictFolderId() {
return 'c04f1c7c04f1c7c04f1c7c04f1c7c04f';
2017-07-15 17:35:40 +02:00
}
static conflictFolder() {
return {
type_: this.TYPE_FOLDER,
id: this.conflictFolderId(),
parent_id: '',
2017-07-15 17:35:40 +02:00
title: this.conflictFolderTitle(),
updated_time: time.unixMs(),
2017-08-20 22:11:32 +02:00
user_updated_time: time.unixMs(),
2017-07-15 17:35:40 +02:00
};
}
2017-06-25 11:00:54 +02:00
2017-07-15 17:35:40 +02:00
static async all(options = null) {
let output = await super.all(options);
if (options && options.includeConflictFolder) {
let conflictCount = await Note.conflictedCount();
if (conflictCount) output.push(this.conflictFolder());
}
return output;
}
2017-06-25 11:00:54 +02:00
2017-07-15 17:35:40 +02:00
static load(id) {
if (id == this.conflictFolderId()) return this.conflictFolder();
return super.load(id);
2017-06-19 20:58:49 +02:00
}
2017-06-27 22:16:03 +02:00
static defaultFolder() {
return this.modelSelectOne('SELECT * FROM folders ORDER BY created_time DESC LIMIT 1');
2017-06-25 01:19:11 +02:00
}
static async canNestUnder(folderId, targetFolderId) {
if (folderId === targetFolderId) return false;
const conflictFolderId = Folder.conflictFolderId();
if (folderId == conflictFolderId || targetFolderId == conflictFolderId) return false;
if (!targetFolderId) return true;
while (true) {
let folder = await Folder.load(targetFolderId);
if (!folder.parent_id) break;
if (folder.parent_id === folderId) return false;
targetFolderId = folder.parent_id;
}
return true;
}
static async moveToFolder(folderId, targetFolderId) {
if (!(await this.canNestUnder(folderId, targetFolderId))) throw new Error(_('Cannot move notebook to this location'));
// When moving a note to a different folder, the user timestamp is not updated.
// However updated_time is updated so that the note can be synced later on.
const modifiedFolder = {
id: folderId,
parent_id: targetFolderId,
updated_time: time.unixMs(),
};
return Folder.save(modifiedFolder, { autoTimestamp: false });
}
2017-07-15 17:35:40 +02:00
// These "duplicateCheck" and "reservedTitleCheck" should only be done when a user is
// manually creating a folder. They shouldn't be done for example when the folders
// are being synced to avoid any strange side-effects. Technically it's possible to
2017-07-17 21:19:01 +02:00
// have folders and notes with duplicate titles (or no title), or with reserved words.
2017-07-03 20:58:01 +02:00
static async save(o, options = null) {
2017-07-17 21:19:01 +02:00
if (!options) options = {};
if (options.userSideValidation === true) {
if (!('duplicateCheck' in options)) options.duplicateCheck = true;
if (!('reservedTitleCheck' in options)) options.reservedTitleCheck = true;
if (!('stripLeftSlashes' in options)) options.stripLeftSlashes = true;
2017-07-17 21:19:01 +02:00
}
if (options.stripLeftSlashes === true && o.title) {
while (o.title.length && (o.title[0] == '/' || o.title[0] == "\\")) {
2017-07-17 21:19:01 +02:00
o.title = o.title.substr(1);
}
}
if (options.duplicateCheck === true && o.title) {
2017-07-03 20:58:01 +02:00
let existingFolder = await Folder.loadByTitle(o.title);
2017-07-15 18:25:33 +02:00
if (existingFolder && existingFolder.id != o.id) throw new Error(_('A notebook with this title already exists: "%s"', o.title));
2017-07-03 20:58:01 +02:00
}
2017-07-17 21:19:01 +02:00
if (options.reservedTitleCheck === true && o.title) {
2017-07-15 17:35:40 +02:00
if (o.title == Folder.conflictFolderTitle()) throw new Error(_('Notebooks cannot be named "%s", which is a reserved title.', o.title));
}
return super.save(o, options).then((folder) => {
this.dispatch({
type: 'FOLDER_UPDATE_ONE',
item: folder,
2017-05-18 22:31:40 +02:00
});
return folder;
2017-05-18 22:31:40 +02:00
});
}
2017-05-15 21:10:00 +02:00
}
module.exports = Folder;