1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-06 09:19:22 +02:00

Tools: Add class member accessibility modifiers and converted rule @typescript-eslint/explicit-member-accessibility to an error

This commit is contained in:
Laurent Cozic
2023-03-06 14:22:01 +00:00
parent aa4af69afc
commit c1db7182ac
129 changed files with 1252 additions and 1296 deletions

View File

@@ -10,23 +10,23 @@ export interface Notification {
}
export default class Alarm extends BaseModel {
static tableName() {
public static tableName() {
return 'alarms';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_ALARM;
}
static byNoteId(noteId: string) {
public static byNoteId(noteId: string) {
return this.modelSelectOne('SELECT * FROM alarms WHERE note_id = ?', [noteId]);
}
static async deleteExpiredAlarms() {
public static async deleteExpiredAlarms() {
return this.db().exec('DELETE FROM alarms WHERE trigger_time <= ?', [Date.now()]);
}
static async alarmIdsWithoutNotes() {
public static async alarmIdsWithoutNotes() {
// https://stackoverflow.com/a/4967229/561309
const alarms = await this.db().selectAll('SELECT alarms.id FROM alarms LEFT JOIN notes ON alarms.note_id = notes.id WHERE notes.id IS NULL');
return alarms.map((a: any) => {
@@ -34,7 +34,7 @@ export default class Alarm extends BaseModel {
});
}
static async makeNotification(alarm: any, note: any = null): Promise<Notification> {
public static async makeNotification(alarm: any, note: any = null): Promise<Notification> {
if (!note) {
note = await Note.load(alarm.note_id);
} else if (!note.todo_due) {
@@ -55,7 +55,7 @@ export default class Alarm extends BaseModel {
return output;
}
static async allDue() {
public static async allDue() {
return this.modelSelectAll('SELECT * FROM alarms WHERE trigger_time >= ?', [Date.now()]);
}
}

View File

@@ -63,15 +63,15 @@ export default class BaseItem extends BaseModel {
public static SYNC_ITEM_LOCATION_REMOTE = 2;
static useUuid() {
public static useUuid() {
return true;
}
static encryptionSupported() {
public static encryptionSupported() {
return true;
}
static loadClass(className: string, classRef: any) {
public static loadClass(className: string, classRef: any) {
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
if (BaseItem.syncItemDefinitions_[i].className === className) {
BaseItem.syncItemDefinitions_[i].classRef = classRef;
@@ -82,7 +82,7 @@ export default class BaseItem extends BaseModel {
throw new Error(`Invalid class name: ${className}`);
}
static async findUniqueItemTitle(title: string, parentId: string = null) {
public static async findUniqueItemTitle(title: string, parentId: string = null) {
let counter = 1;
let titleToTry = title;
while (true) {
@@ -106,7 +106,7 @@ export default class BaseItem extends BaseModel {
}
// Need to dynamically load the classes like this to avoid circular dependencies
static getClass(name: string) {
public static getClass(name: string) {
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
if (BaseItem.syncItemDefinitions_[i].className === name) {
const classRef = BaseItem.syncItemDefinitions_[i].classRef;
@@ -118,7 +118,7 @@ export default class BaseItem extends BaseModel {
throw new Error(`Invalid class name: ${name}`);
}
static getClassByItemType(itemType: ModelType) {
public static getClassByItemType(itemType: ModelType) {
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
if (BaseItem.syncItemDefinitions_[i].type === itemType) {
return BaseItem.syncItemDefinitions_[i].classRef;
@@ -128,7 +128,7 @@ export default class BaseItem extends BaseModel {
throw new Error(`Invalid item type: ${itemType}`);
}
static async syncedCount(syncTarget: number) {
public static async syncedCount(syncTarget: number) {
const ItemClass = this.itemClass(this.modelType());
const itemType = ItemClass.modelType();
// The fact that we don't check if the item_id still exist in the corresponding item table, means
@@ -145,7 +145,7 @@ export default class BaseItem extends BaseModel {
else return `${itemOrId.id}.${extension}`;
}
static isSystemPath(path: string) {
public static isSystemPath(path: string) {
// 1b175bb38bba47baac22b0b47f778113.md
if (!path || !path.length) return false;
let p: any = path.split('/');
@@ -155,7 +155,7 @@ export default class BaseItem extends BaseModel {
return p[0].length === 32 && p[1] === 'md';
}
static itemClass(item: any): any {
public static itemClass(item: any): any {
if (!item) throw new Error('Item cannot be null');
if (typeof item === 'object') {
@@ -171,7 +171,7 @@ export default class BaseItem extends BaseModel {
}
// Returns the IDs of the items that have been synced at least once
static async syncedItemIds(syncTarget: number) {
public static async syncedItemIds(syncTarget: number) {
if (!syncTarget) throw new Error('No syncTarget specified');
const temp = await this.db().selectAll('SELECT item_id FROM sync_items WHERE sync_time > 0 AND sync_target = ?', [syncTarget]);
const output = [];
@@ -181,12 +181,12 @@ export default class BaseItem extends BaseModel {
return output;
}
static async allSyncItems(syncTarget: number) {
public static async allSyncItems(syncTarget: number) {
const output = await this.db().selectAll('SELECT * FROM sync_items WHERE sync_target = ?', [syncTarget]);
return output;
}
static pathToId(path: string) {
public static pathToId(path: string) {
const p = path.split('/');
const s = p[p.length - 1].split('.');
let name: any = s[0];
@@ -195,11 +195,11 @@ export default class BaseItem extends BaseModel {
return name[name.length - 1];
}
static loadItemByPath(path: string) {
public static loadItemByPath(path: string) {
return this.loadItemById(this.pathToId(path));
}
static async loadItemById(id: string) {
public static async loadItemById(id: string) {
const classes = this.syncItemClassNames();
for (let i = 0; i < classes.length; i++) {
const item = await this.getClass(classes[i]).load(id);
@@ -208,7 +208,7 @@ export default class BaseItem extends BaseModel {
return null;
}
static async loadItemsByIds(ids: string[]) {
public static async loadItemsByIds(ids: string[]) {
if (!ids.length) return [];
const classes = this.syncItemClassNames();
@@ -222,26 +222,26 @@ export default class BaseItem extends BaseModel {
return output;
}
static loadItemByField(itemType: number, field: string, value: any) {
public static loadItemByField(itemType: number, field: string, value: any) {
const ItemClass = this.itemClass(itemType);
return ItemClass.loadByField(field, value);
}
static loadItem(itemType: ModelType, id: string) {
public static loadItem(itemType: ModelType, id: string) {
const ItemClass = this.itemClass(itemType);
return ItemClass.load(id);
}
static deleteItem(itemType: ModelType, id: string) {
public static deleteItem(itemType: ModelType, id: string) {
const ItemClass = this.itemClass(itemType);
return ItemClass.delete(id);
}
static async delete(id: string, options: DeleteOptions = null) {
public static async delete(id: string, options: DeleteOptions = null) {
return this.batchDelete([id], options);
}
static async batchDelete(ids: string[], options: DeleteOptions = null) {
public static async batchDelete(ids: string[], options: DeleteOptions = null) {
if (!options) options = {};
let trackDeleted = true;
if (options && options.trackDeleted !== null && options.trackDeleted !== undefined) trackDeleted = options.trackDeleted;
@@ -287,20 +287,20 @@ export default class BaseItem extends BaseModel {
// - Client 1 syncs with target 2 only => the note is *not* deleted from target 2 because no information
// that it was previously deleted exist (deleted_items entry has been deleted).
// The solution would be to permanently store the list of deleted items on each client.
static deletedItems(syncTarget: number) {
public static deletedItems(syncTarget: number) {
return this.db().selectAll('SELECT * FROM deleted_items WHERE sync_target = ?', [syncTarget]);
}
static async deletedItemCount(syncTarget: number) {
public static async deletedItemCount(syncTarget: number) {
const r = await this.db().selectOne('SELECT count(*) as total FROM deleted_items WHERE sync_target = ?', [syncTarget]);
return r['total'];
}
static remoteDeletedItem(syncTarget: number, itemId: string) {
public static remoteDeletedItem(syncTarget: number, itemId: string) {
return this.db().exec('DELETE FROM deleted_items WHERE item_id = ? AND sync_target = ?', [itemId, syncTarget]);
}
static serialize_format(propName: string, propValue: any) {
public static serialize_format(propName: string, propValue: any) {
if (['created_time', 'updated_time', 'sync_time', 'user_updated_time', 'user_created_time'].indexOf(propName) >= 0) {
if (!propValue) return '';
propValue = `${moment.unix(propValue / 1000).utc().format('YYYY-MM-DDTHH:mm:ss.SSS')}Z`;
@@ -322,7 +322,7 @@ export default class BaseItem extends BaseModel {
.replace(/\r/g, '\\r');
}
static unserialize_format(type: ModelType, propName: string, propValue: any) {
public static unserialize_format(type: ModelType, propName: string, propValue: any) {
if (propName[propName.length - 1] === '_') return propValue; // Private property
const ItemClass = this.itemClass(type);
@@ -350,7 +350,7 @@ export default class BaseItem extends BaseModel {
: propValue;
}
static async serialize(item: any, shownKeys: any[] = null) {
public static async serialize(item: any, shownKeys: any[] = null) {
if (shownKeys === null) {
shownKeys = this.itemClass(item).fieldNames();
shownKeys.push('type_');
@@ -395,12 +395,12 @@ export default class BaseItem extends BaseModel {
return temp.join('\n\n');
}
static encryptionService() {
public static encryptionService() {
if (!this.encryptionService_) throw new Error('BaseItem.encryptionService_ is not set!!');
return this.encryptionService_;
}
static revisionService() {
public static revisionService() {
if (!this.revisionService_) throw new Error('BaseItem.revisionService_ is not set!!');
return this.revisionService_;
}
@@ -460,7 +460,7 @@ export default class BaseItem extends BaseModel {
return ItemClass.serialize(reducedItem);
}
static async decrypt(item: any) {
public static async decrypt(item: any) {
if (!item.encryption_cipher_text) throw new Error(`Item is not encrypted: ${item.id}`);
const ItemClass = this.itemClass(item);
@@ -474,7 +474,7 @@ export default class BaseItem extends BaseModel {
return ItemClass.save(plainItem, { autoTimestamp: false, changeSource: ItemChange.SOURCE_DECRYPTION });
}
static async unserialize(content: string) {
public static async unserialize(content: string) {
const lines = content.split('\n');
let output: any = {};
let state = 'readingProps';
@@ -539,7 +539,7 @@ export default class BaseItem extends BaseModel {
};
}
static async encryptedItemsCount() {
public static async encryptedItemsCount() {
const classNames = this.encryptableItemClassNames();
let output = 0;
@@ -553,7 +553,7 @@ export default class BaseItem extends BaseModel {
return output;
}
static async hasEncryptedItems() {
public static async hasEncryptedItems() {
const classNames = this.encryptableItemClassNames();
for (let i = 0; i < classNames.length; i++) {
@@ -567,7 +567,7 @@ export default class BaseItem extends BaseModel {
return false;
}
static async itemsThatNeedDecryption(exclusions: string[] = [], limit = 100): Promise<ItemsThatNeedDecryptionResult> {
public static async itemsThatNeedDecryption(exclusions: string[] = [], limit = 100): Promise<ItemsThatNeedDecryptionResult> {
const classNames = this.encryptableItemClassNames();
for (let i = 0; i < classNames.length; i++) {
@@ -703,13 +703,13 @@ export default class BaseItem extends BaseModel {
throw new Error('Unreachable');
}
static syncItemClassNames(): string[] {
public static syncItemClassNames(): string[] {
return BaseItem.syncItemDefinitions_.map((def: any) => {
return def.className;
});
}
static encryptableItemClassNames() {
public static encryptableItemClassNames() {
const temp = this.syncItemClassNames();
const output = [];
for (let i = 0; i < temp.length; i++) {
@@ -725,14 +725,14 @@ export default class BaseItem extends BaseModel {
});
}
static modelTypeToClassName(type: number) {
public static modelTypeToClassName(type: number) {
for (let i = 0; i < BaseItem.syncItemDefinitions_.length; i++) {
if (BaseItem.syncItemDefinitions_[i].type === type) return BaseItem.syncItemDefinitions_[i].className;
}
throw new Error(`Invalid type: ${type}`);
}
static async syncDisabledItems(syncTargetId: number) {
public static async syncDisabledItems(syncTargetId: number) {
const rows = await this.db().selectAll('SELECT * FROM sync_items WHERE sync_disabled = 1 AND sync_target = ?', [syncTargetId]);
const output = [];
for (let i = 0; i < rows.length; i++) {
@@ -749,7 +749,7 @@ export default class BaseItem extends BaseModel {
return output;
}
static updateSyncTimeQueries(syncTarget: number, item: any, syncTime: number, syncDisabled = false, syncDisabledReason = '', itemLocation: number = null) {
public static updateSyncTimeQueries(syncTarget: number, item: any, syncTime: number, syncDisabled = false, syncDisabledReason = '', itemLocation: number = null) {
const itemType = item.type_;
const itemId = item.id;
if (!itemType || !itemId || syncTime === undefined) throw new Error(sprintf('Invalid parameters in updateSyncTimeQueries(): %d, %s, %d', syncTarget, JSON.stringify(item), syncTime));
@@ -768,12 +768,12 @@ export default class BaseItem extends BaseModel {
];
}
static async saveSyncTime(syncTarget: number, item: any, syncTime: number) {
public static async saveSyncTime(syncTarget: number, item: any, syncTime: number) {
const queries = this.updateSyncTimeQueries(syncTarget, item, syncTime);
return this.db().transactionExecBatch(queries);
}
static async saveSyncDisabled(syncTargetId: number, item: any, syncDisabledReason: string, itemLocation: number = null) {
public static async saveSyncDisabled(syncTargetId: number, item: any, syncDisabledReason: string, itemLocation: number = null) {
const syncTime = 'sync_time' in item ? item.sync_time : 0;
const queries = this.updateSyncTimeQueries(syncTargetId, item, syncTime, true, syncDisabledReason, itemLocation);
return this.db().transactionExecBatch(queries);
@@ -786,7 +786,7 @@ export default class BaseItem extends BaseModel {
// When an item is deleted, its associated sync_items data is not immediately deleted for
// performance reason. So this function is used to look for these remaining sync_items and
// delete them.
static async deleteOrphanSyncItems() {
public static async deleteOrphanSyncItems() {
const classNames = this.syncItemClassNames();
const queries = [];
@@ -803,13 +803,13 @@ export default class BaseItem extends BaseModel {
await this.db().transactionExecBatch(queries);
}
static displayTitle(item: any) {
public static displayTitle(item: any) {
if (!item) return '';
if (item.encryption_applied) return `🔑 ${_('Encrypted')}`;
return item.title ? item.title : _('Untitled');
}
static async markAllNonEncryptedForSync() {
public static async markAllNonEncryptedForSync() {
const classNames = this.encryptableItemClassNames();
for (let i = 0; i < classNames.length; i++) {
@@ -834,7 +834,7 @@ export default class BaseItem extends BaseModel {
}
}
static async updateShareStatus(item: BaseItemEntity, isShared: boolean) {
public static async updateShareStatus(item: BaseItemEntity, isShared: boolean) {
if (!item.id || !item.type_) throw new Error('Item must have an ID and a type');
if (!!item.is_shared === !!isShared) return false;
const ItemClass = this.getClassByItemType(item.type_);
@@ -853,15 +853,15 @@ export default class BaseItem extends BaseModel {
return true;
}
static async forceSync(itemId: string) {
public static async forceSync(itemId: string) {
await this.db().exec('UPDATE sync_items SET force_sync = 1 WHERE item_id = ?', [itemId]);
}
static async forceSyncAll() {
public static async forceSyncAll() {
await this.db().exec('UPDATE sync_items SET force_sync = 1');
}
static async save(o: any, options: any = null) {
public static async save(o: any, options: any = null) {
if (!options) options = {};
if (options.userSideValidation === true) {
@@ -871,7 +871,7 @@ export default class BaseItem extends BaseModel {
return super.save(o, options);
}
static markdownTag(itemOrId: any) {
public static markdownTag(itemOrId: any) {
const item = typeof itemOrId === 'object' ? itemOrId : {
id: itemOrId,
title: '',
@@ -885,7 +885,7 @@ export default class BaseItem extends BaseModel {
return output.join('');
}
static isMarkdownTag(md: any) {
public static isMarkdownTag(md: any) {
if (!md) return false;
return !!md.match(/^\[.*?\]\(:\/[0-9a-zA-Z]{32}\)$/);
}

View File

@@ -19,22 +19,22 @@ export interface FolderEntityWithChildren extends FolderEntity {
}
export default class Folder extends BaseItem {
static tableName() {
public static tableName() {
return 'folders';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_FOLDER;
}
static newFolder(): FolderEntity {
public static newFolder(): FolderEntity {
return {
id: null,
title: '',
};
}
static fieldToLabel(field: string) {
public static fieldToLabel(field: string) {
const fieldsToLabels: any = {
title: _('title'),
last_note_user_updated_time: _('updated date'),
@@ -43,7 +43,7 @@ export default class Folder extends BaseItem {
return field in fieldsToLabels ? fieldsToLabels[field] : field;
}
static noteIds(parentId: string, options: any = null) {
public static noteIds(parentId: string, options: any = null) {
options = Object.assign({}, {
includeConflicts: false,
}, options);
@@ -66,17 +66,17 @@ export default class Folder extends BaseItem {
});
}
static async subFolderIds(parentId: string) {
public static async subFolderIds(parentId: string) {
const rows = await this.db().selectAll('SELECT id FROM folders WHERE parent_id = ?', [parentId]);
return rows.map((r: FolderEntity) => r.id);
}
static async noteCount(parentId: string) {
public static async noteCount(parentId: string) {
const r = await this.db().selectOne('SELECT count(*) as total FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]);
return r ? r.total : 0;
}
static markNotesAsConflict(parentId: string) {
public static markNotesAsConflict(parentId: string) {
const query = Database.updateQuery('notes', { is_conflict: 1 }, { parent_id: parentId });
return this.db().exec(query);
}
@@ -108,15 +108,15 @@ export default class Folder extends BaseItem {
});
}
static conflictFolderTitle() {
public static conflictFolderTitle() {
return _('Conflicts');
}
static conflictFolderId() {
public static conflictFolderId() {
return 'c04f1c7c04f1c7c04f1c7c04f1c7c04f';
}
static conflictFolder(): FolderEntity {
public static conflictFolder(): FolderEntity {
return {
type_: this.TYPE_FOLDER,
id: this.conflictFolderId(),
@@ -129,7 +129,7 @@ export default class Folder extends BaseItem {
// Calculates note counts for all folders and adds the note_count attribute to each folder
// Note: this only calculates the overall number of nodes for this folder and all its descendants
static async addNoteCounts(folders: any[], includeCompletedTodos = true) {
public static async addNoteCounts(folders: any[], includeCompletedTodos = true) {
const foldersById: any = {};
for (const f of folders) {
foldersById[f.id] = f;
@@ -170,7 +170,7 @@ export default class Folder extends BaseItem {
// Folders that contain notes that have been modified recently go on top.
// The remaining folders, that don't contain any notes are sorted by their own user_updated_time
static async orderByLastModified(folders: FolderEntity[], dir = 'DESC') {
public static async orderByLastModified(folders: FolderEntity[], dir = 'DESC') {
dir = dir.toUpperCase();
const sql = 'select parent_id, max(user_updated_time) content_updated_time from notes where parent_id != "" group by parent_id';
const rows = await this.db().selectAll(sql);
@@ -228,7 +228,7 @@ export default class Folder extends BaseItem {
return output;
}
static async all(options: any = null) {
public static async all(options: any = null) {
const output = await super.all(options);
if (options && options.includeConflictFolder) {
const conflictCount = await Note.conflictedCount();
@@ -237,7 +237,7 @@ export default class Folder extends BaseItem {
return output;
}
static async childrenIds(folderId: string) {
public static async childrenIds(folderId: string) {
const folders = await this.db().selectAll('SELECT id FROM folders WHERE parent_id = ?', [folderId]);
let output: string[] = [];
@@ -252,7 +252,7 @@ export default class Folder extends BaseItem {
return output;
}
static async expandTree(folders: FolderEntity[], parentId: string) {
public static async expandTree(folders: FolderEntity[], parentId: string) {
const folderPath = await this.folderPath(folders, parentId);
folderPath.pop(); // We don't expand the leaft notebook
@@ -542,7 +542,7 @@ export default class Folder extends BaseItem {
logger.debug('updateNoLongerSharedItems:', report);
}
static async allAsTree(folders: FolderEntity[] = null, options: any = null) {
public static async allAsTree(folders: FolderEntity[] = null, options: any = null) {
const all = folders ? folders : await this.all(options);
if (options && options.includeNotes) {
@@ -576,7 +576,7 @@ export default class Folder extends BaseItem {
return getNestedChildren(all, '');
}
static folderPath(folders: FolderEntity[], folderId: string) {
public static folderPath(folders: FolderEntity[], folderId: string) {
const idToFolders: Record<string, FolderEntity> = {};
for (let i = 0; i < folders.length; i++) {
idToFolders[folders[i].id] = folders[i];
@@ -595,7 +595,7 @@ export default class Folder extends BaseItem {
return path;
}
static folderPathString(folders: FolderEntity[], folderId: string, maxTotalLength = 80) {
public static folderPathString(folders: FolderEntity[], folderId: string, maxTotalLength = 80) {
const path = this.folderPath(folders, folderId);
let currentTotalLength = 0;
@@ -616,7 +616,7 @@ export default class Folder extends BaseItem {
return output.join(' / ');
}
static buildTree(folders: FolderEntity[]): FolderEntityWithChildren[] {
public static buildTree(folders: FolderEntity[]): FolderEntityWithChildren[] {
const idToFolders: Record<string, any> = {};
for (let i = 0; i < folders.length; i++) {
idToFolders[folders[i].id] = Object.assign({}, folders[i]);
@@ -644,7 +644,7 @@ export default class Folder extends BaseItem {
return rootFolders;
}
static async sortFolderTree(folders: FolderEntityWithChildren[] = null) {
public static async sortFolderTree(folders: FolderEntityWithChildren[] = null) {
const output = folders ? folders : await this.allAsTree();
const sortFoldersAlphabetically = (folders: FolderEntityWithChildren[]) => {
@@ -672,16 +672,16 @@ export default class Folder extends BaseItem {
return output;
}
static load(id: string, _options: any = null): Promise<FolderEntity> {
public static load(id: string, _options: any = null): Promise<FolderEntity> {
if (id === this.conflictFolderId()) return Promise.resolve(this.conflictFolder());
return super.load(id);
}
static defaultFolder() {
public static defaultFolder() {
return this.modelSelectOne('SELECT * FROM folders ORDER BY created_time DESC LIMIT 1');
}
static async canNestUnder(folderId: string, targetFolderId: string) {
public static async canNestUnder(folderId: string, targetFolderId: string) {
if (folderId === targetFolderId) return false;
const folder = await Folder.load(folderId);
@@ -702,7 +702,7 @@ export default class Folder extends BaseItem {
return true;
}
static async moveToFolder(folderId: string, targetFolderId: string) {
public static async moveToFolder(folderId: string, targetFolderId: string) {
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.
@@ -721,7 +721,7 @@ export default class Folder extends BaseItem {
// 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
// have folders and notes with duplicate titles (or no title), or with reserved words.
static async save(o: FolderEntity, options: any = null) {
public static async save(o: FolderEntity, options: any = null) {
if (!options) options = {};
if (options.userSideValidation === true) {

View File

@@ -22,11 +22,11 @@ export default class ItemChange extends BaseModel {
public static SOURCE_SYNC = 2;
public static SOURCE_DECRYPTION = 2; // CAREFUL - SAME ID AS SOURCE_SYNC!
static tableName() {
public static tableName() {
return 'item_changes';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_ITEM_CHANGE;
}

View File

@@ -5,15 +5,15 @@ import BaseItem from './BaseItem';
import uuid from '../uuid';
export default class MasterKey extends BaseItem {
static tableName() {
public static tableName() {
return 'master_keys';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_MASTER_KEY;
}
static encryptionSupported() {
public static encryptionSupported() {
return false;
}
@@ -28,7 +28,7 @@ export default class MasterKey extends BaseItem {
return output;
}
static allWithoutEncryptionMethod(masterKeys: MasterKeyEntity[], methods: number[]) {
public static allWithoutEncryptionMethod(masterKeys: MasterKeyEntity[], methods: number[]) {
return masterKeys.filter(m => !methods.includes(m.encryption_method));
}

View File

@@ -10,19 +10,19 @@ const migrationScripts: Record<number, any> = {
};
export default class Migration extends BaseModel {
static tableName() {
public static tableName() {
return 'migrations';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_MIGRATION;
}
static migrationsToDo() {
public static migrationsToDo() {
return this.modelSelectAll('SELECT * FROM migrations ORDER BY number ASC');
}
static script(number: number) {
public static script(number: number) {
if (!migrationScripts[number]) throw new Error('Migration script has not been added to "migrationScripts" array');
return migrationScripts[number];
}

View File

@@ -26,11 +26,11 @@ export default class Note extends BaseItem {
private static geolocationCache_: any;
private static dueDateObjects_: any;
static tableName() {
public static tableName() {
return 'notes';
}
static fieldToLabel(field: string) {
public static fieldToLabel(field: string) {
const fieldsToLabels: Record<string, string> = {
title: _('title'),
user_updated_time: _('updated date'),
@@ -41,11 +41,11 @@ export default class Note extends BaseItem {
return field in fieldsToLabels ? fieldsToLabels[field] : field;
}
static async serializeForEdit(note: NoteEntity) {
public static async serializeForEdit(note: NoteEntity) {
return this.replaceResourceInternalToExternalLinks(await super.serialize(note, ['title', 'body']));
}
static async unserializeForEdit(content: string) {
public static async unserializeForEdit(content: string) {
content += `\n\ntype_: ${BaseModel.TYPE_NOTE}`;
const output = await super.unserialize(content);
if (!output.title) output.title = '';
@@ -54,14 +54,14 @@ export default class Note extends BaseItem {
return output;
}
static async serializeAllProps(note: NoteEntity) {
public static async serializeAllProps(note: NoteEntity) {
const fieldNames = this.fieldNames();
fieldNames.push('type_');
pull(fieldNames, 'title', 'body');
return super.serialize(note, fieldNames);
}
static minimalSerializeForDisplay(note: NoteEntity) {
public static minimalSerializeForDisplay(note: NoteEntity) {
const n = Object.assign({}, note);
const fieldNames = this.fieldNames();
@@ -89,25 +89,25 @@ export default class Note extends BaseItem {
return super.serialize(n, fieldNames);
}
static defaultTitle(noteBody: string) {
public static defaultTitle(noteBody: string) {
return this.defaultTitleFromBody(noteBody);
}
static defaultTitleFromBody(body: string) {
public static defaultTitleFromBody(body: string) {
return markdownUtils.titleFromBody(body);
}
static geolocationUrl(note: NoteEntity) {
public static geolocationUrl(note: NoteEntity) {
if (!('latitude' in note) || !('longitude' in note)) throw new Error('Latitude or longitude is missing');
if (!Number(note.latitude) && !Number(note.longitude)) throw new Error(_('This note does not have geolocation information.'));
return this.geoLocationUrlFromLatLong(note.latitude, note.longitude);
}
static geoLocationUrlFromLatLong(lat: number, long: number) {
public static geoLocationUrlFromLatLong(lat: number, long: number) {
return sprintf('https://www.openstreetmap.org/?lat=%s&lon=%s&zoom=20', lat, long);
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_NOTE;
}
@@ -119,13 +119,13 @@ export default class Note extends BaseItem {
return unique(itemIds);
}
static async linkedItems(body: string) {
public static async linkedItems(body: string) {
const itemIds = this.linkedItemIds(body);
const r = await BaseItem.loadItemsByIds(itemIds);
return r;
}
static async linkedItemIdsByType(type: ModelType, body: string) {
public static async linkedItemIdsByType(type: ModelType, body: string) {
const items = await this.linkedItems(body);
const output: string[] = [];
@@ -137,15 +137,15 @@ export default class Note extends BaseItem {
return output;
}
static async linkedResourceIds(body: string) {
public static async linkedResourceIds(body: string) {
return this.linkedItemIdsByType(BaseModel.TYPE_RESOURCE, body);
}
static async linkedNoteIds(body: string) {
public static async linkedNoteIds(body: string) {
return this.linkedItemIdsByType(BaseModel.TYPE_NOTE, body);
}
static async replaceResourceInternalToExternalLinks(body: string, options: any = null) {
public static async replaceResourceInternalToExternalLinks(body: string, options: any = null) {
options = Object.assign({}, {
useAbsolutePaths: false,
}, options);
@@ -175,7 +175,7 @@ export default class Note extends BaseItem {
return body;
}
static async replaceResourceExternalToInternalLinks(body: string, options: any = null) {
public static async replaceResourceExternalToInternalLinks(body: string, options: any = null) {
options = Object.assign({}, {
useAbsolutePaths: false,
}, options);
@@ -239,20 +239,20 @@ export default class Note extends BaseItem {
return body;
}
static new(parentId = '') {
public static new(parentId = '') {
const output = super.new();
output.parent_id = parentId;
return output;
}
static newTodo(parentId = '') {
public static newTodo(parentId = '') {
const output = this.new(parentId);
output.is_todo = true;
return output;
}
// Note: sort logic must be duplicated in previews().
static sortNotes(notes: NoteEntity[], orders: any[], uncompletedTodosOnTop: boolean) {
public static sortNotes(notes: NoteEntity[], orders: any[], uncompletedTodosOnTop: boolean) {
const noteOnTop = (note: NoteEntity) => {
return uncompletedTodosOnTop && note.is_todo && !note.todo_completed;
};
@@ -308,11 +308,11 @@ export default class Note extends BaseItem {
});
}
static previewFieldsWithDefaultValues(options: any = null) {
public static previewFieldsWithDefaultValues(options: any = null) {
return Note.defaultValues(this.previewFields(options));
}
static previewFields(options: any = null) {
public static previewFields(options: any = null) {
options = Object.assign({
includeTimestamps: true,
}, options);
@@ -328,13 +328,13 @@ export default class Note extends BaseItem {
return output;
}
static previewFieldsSql(fields: string[] = null) {
public static previewFieldsSql(fields: string[] = null) {
if (fields === null) fields = this.previewFields();
const escaped = this.db().escapeFields(fields);
return Array.isArray(escaped) ? escaped.join(',') : escaped;
}
static async loadFolderNoteByField(folderId: string, field: string, value: any) {
public static async loadFolderNoteByField(folderId: string, field: string, value: any) {
if (!folderId) throw new Error('folderId is undefined');
const options = {
@@ -347,7 +347,7 @@ export default class Note extends BaseItem {
return results.length ? results[0] : null;
}
static async previews(parentId: string, options: any = null) {
public static async previews(parentId: string, options: any = null) {
// Note: ordering logic must be duplicated in sortNotes(), which is used
// to sort already loaded notes.
@@ -436,12 +436,12 @@ export default class Note extends BaseItem {
return results;
}
static preview(noteId: string, options: any = null) {
public static preview(noteId: string, options: any = null) {
if (!options) options = { fields: null };
return this.modelSelectOne(`SELECT ${this.previewFieldsSql(options.fields)} FROM notes WHERE is_conflict = 0 AND id = ?`, [noteId]);
}
static async search(options: any = null) {
public static async search(options: any = null) {
if (!options) options = {};
if (!options.conditions) options.conditions = [];
if (!options.conditionsParams) options.conditionsParams = [];
@@ -455,16 +455,16 @@ export default class Note extends BaseItem {
return super.search(options);
}
static conflictedNotes() {
public static conflictedNotes() {
return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 1');
}
static async conflictedCount() {
public static async conflictedCount() {
const r = await this.db().selectOne('SELECT count(*) as total FROM notes WHERE is_conflict = 1');
return r && r.total ? r.total : 0;
}
static unconflictedNotes() {
public static unconflictedNotes() {
return this.modelSelectAll('SELECT * FROM notes WHERE is_conflict = 0');
}
@@ -518,7 +518,7 @@ export default class Note extends BaseItem {
return note;
}
static filter(note: NoteEntity) {
public static filter(note: NoteEntity) {
if (!note) return note;
const output = super.filter(note);
@@ -528,7 +528,7 @@ export default class Note extends BaseItem {
return output;
}
static async copyToFolder(noteId: string, folderId: string) {
public static async copyToFolder(noteId: string, folderId: string) {
if (folderId === this.getClass('Folder').conflictFolderId()) throw new Error(_('Cannot copy note to "%s" notebook', this.getClass('Folder').conflictFolderTitle()));
return Note.duplicate(noteId, {
@@ -540,7 +540,7 @@ export default class Note extends BaseItem {
});
}
static async moveToFolder(noteId: string, folderId: string) {
public static async moveToFolder(noteId: string, folderId: string) {
if (folderId === this.getClass('Folder').conflictFolderId()) throw new Error(_('Cannot move note to "%s" notebook', this.getClass('Folder').conflictFolderTitle()));
// When moving a note to a different folder, the user timestamp is not updated.
@@ -557,7 +557,7 @@ export default class Note extends BaseItem {
return Note.save(modifiedNote, { autoTimestamp: false });
}
static changeNoteType(note: NoteEntity, type: string) {
public static changeNoteType(note: NoteEntity, type: string) {
if (!('is_todo' in note)) throw new Error('Missing "is_todo" property');
const newIsTodo = type === 'todo' ? 1 : 0;
@@ -572,11 +572,11 @@ export default class Note extends BaseItem {
return output;
}
static toggleIsTodo(note: NoteEntity) {
public static toggleIsTodo(note: NoteEntity) {
return this.changeNoteType(note, note.is_todo ? 'note' : 'todo');
}
static toggleTodoCompleted(note: NoteEntity) {
public static toggleTodoCompleted(note: NoteEntity) {
if (!('todo_completed' in note)) throw new Error('Missing "todo_completed" property');
note = Object.assign({}, note);
@@ -589,7 +589,7 @@ export default class Note extends BaseItem {
return note;
}
static async duplicateMultipleNotes(noteIds: string[], options: any = null) {
public static async duplicateMultipleNotes(noteIds: string[], options: any = null) {
// if options.uniqueTitle is true, a unique title for the duplicated file will be assigned.
const ensureUniqueTitle = options && options.ensureUniqueTitle;
@@ -655,7 +655,7 @@ export default class Note extends BaseItem {
return this.save(newNoteSaved);
}
static async noteIsOlderThan(noteId: string, date: number) {
public static async noteIsOlderThan(noteId: string, date: number) {
const n = await this.db().selectOne('SELECT updated_time FROM notes WHERE id = ?', [noteId]);
if (!n) throw new Error(`No such note: ${noteId}`);
return n.updated_time < date;
@@ -737,7 +737,7 @@ export default class Note extends BaseItem {
return note;
}
static async batchDelete(ids: string[], options: any = null) {
public static async batchDelete(ids: string[], options: any = null) {
ids = ids.slice();
while (ids.length) {
@@ -763,7 +763,7 @@ export default class Note extends BaseItem {
}
}
static async deleteMessage(noteIds: string[]): Promise<string|null> {
public static async deleteMessage(noteIds: string[]): Promise<string|null> {
let msg = '';
if (noteIds.length === 1) {
const note = await Note.load(noteIds[0]);
@@ -775,15 +775,15 @@ export default class Note extends BaseItem {
return msg;
}
static dueNotes() {
public static dueNotes() {
return this.modelSelectAll('SELECT id, title, body, is_todo, todo_due, todo_completed, is_conflict FROM notes WHERE is_conflict = 0 AND is_todo = 1 AND todo_completed = 0 AND todo_due > ?', [time.unixMs()]);
}
static needAlarm(note: NoteEntity) {
public static needAlarm(note: NoteEntity) {
return note.is_todo && !note.todo_completed && note.todo_due >= time.unixMs() && !note.is_conflict;
}
static dueDateObject(note: NoteEntity) {
public static dueDateObject(note: NoteEntity) {
if (!!note.is_todo && note.todo_due) {
if (!this.dueDateObjects_) this.dueDateObjects_ = {};
if (this.dueDateObjects_[note.todo_due]) return this.dueDateObjects_[note.todo_due];
@@ -795,7 +795,7 @@ export default class Note extends BaseItem {
}
// Tells whether the conflict between the local and remote note can be ignored.
static mustHandleConflict(localNote: NoteEntity, remoteNote: NoteEntity) {
public static mustHandleConflict(localNote: NoteEntity, remoteNote: NoteEntity) {
// That shouldn't happen so throw an exception
if (localNote.id !== remoteNote.id) throw new Error('Cannot handle conflict for two different notes');
@@ -809,7 +809,7 @@ export default class Note extends BaseItem {
return false;
}
static markupLanguageToLabel(markupLanguageId: number) {
public static markupLanguageToLabel(markupLanguageId: number) {
if (markupLanguageId === MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN) return 'Markdown';
if (markupLanguageId === MarkupToHtml.MARKUP_LANGUAGE_HTML) return 'HTML';
throw new Error(`Invalid markup language ID: ${markupLanguageId}`);
@@ -818,13 +818,13 @@ export default class Note extends BaseItem {
// When notes are sorted in "custom order", they are sorted by the "order" field first and,
// in those cases, where the order field is the same for some notes, by created time.
// Further sorting by todo completion status, if enabled, is handled separately.
static customOrderByColumns() {
public static customOrderByColumns() {
return [{ by: 'order', dir: 'DESC' }, { by: 'user_created_time', dir: 'DESC' }];
}
// Update the note "order" field without changing the user timestamps,
// which is generally what we want.
static async updateNoteOrder_(note: NoteEntity, order: any) {
private static async updateNoteOrder_(note: NoteEntity, order: any) {
return Note.save(Object.assign({}, note, {
order: order,
user_updated_time: note.user_updated_time,
@@ -836,7 +836,7 @@ export default class Note extends BaseItem {
// of unecessary updates, so it's the caller's responsability to update
// the UI once the call is finished. This is done by listening to the
// NOTE_IS_INSERTING_NOTES action in the application middleware.
static async insertNotesAt(folderId: string, noteIds: string[], index: number, uncompletedTodosOnTop: boolean, showCompletedTodos: boolean) {
public static async insertNotesAt(folderId: string, noteIds: string[], index: number, uncompletedTodosOnTop: boolean, showCompletedTodos: boolean) {
if (!noteIds.length) return;
const defer = () => {
@@ -985,19 +985,19 @@ export default class Note extends BaseItem {
}
}
static handleTitleNaturalSorting(items: NoteEntity[], options: any) {
public static handleTitleNaturalSorting(items: NoteEntity[], options: any) {
if (options.order.length > 0 && options.order[0].by === 'title') {
const collator = this.getNaturalSortingCollator();
items.sort((a, b) => ((options.order[0].dir === 'ASC') ? 1 : -1) * collator.compare(a.title, b.title));
}
}
static getNaturalSortingCollator() {
public static getNaturalSortingCollator() {
return new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
}
static async createConflictNote(sourceNote: NoteEntity, changeSource: number): Promise<NoteEntity> {
public static async createConflictNote(sourceNote: NoteEntity, changeSource: number): Promise<NoteEntity> {
const conflictNote = Object.assign({}, sourceNote);
delete conflictNote.id;
conflictNote.is_conflict = 1;

View File

@@ -8,11 +8,11 @@ import BaseItem from './BaseItem';
// - If last_seen_time is 0, it means the resource has never been associated with any note.
export default class NoteResource extends BaseModel {
static tableName() {
public static tableName() {
return 'note_resources';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_NOTE_RESOURCE;
}
@@ -71,12 +71,12 @@ export default class NoteResource extends BaseModel {
// await this.db().transactionExecBatch(queries);
// }
static async associatedNoteIds(resourceId: string): Promise<string[]> {
public static async associatedNoteIds(resourceId: string): Promise<string[]> {
const rows = await this.modelSelectAll('SELECT note_id FROM note_resources WHERE resource_id = ? AND is_associated = 1', [resourceId]);
return rows.map((r: any) => r.note_id);
}
static async setAssociatedResources(noteId: string, resourceIds: string[]) {
public static async setAssociatedResources(noteId: string, resourceIds: string[]) {
const existingRows = await this.modelSelectAll('SELECT * FROM note_resources WHERE note_id = ?', [noteId]);
const notProcessedResourceIds = resourceIds.slice();
@@ -100,7 +100,7 @@ export default class NoteResource extends BaseModel {
await this.db().transactionExecBatch(queries);
}
static async addOrphanedResources() {
public static async addOrphanedResources() {
const missingResources = await this.db().selectAll('SELECT id FROM resources WHERE id NOT IN (SELECT DISTINCT resource_id FROM note_resources)');
const queries = [];
for (let i = 0; i < missingResources.length; i++) {
@@ -125,11 +125,11 @@ export default class NoteResource extends BaseModel {
await this.db().transactionExecBatch(queries);
}
static async remove(noteId: string) {
public static async remove(noteId: string) {
await this.db().exec({ sql: 'UPDATE note_resources SET is_associated = 0 WHERE note_id = ?', params: [noteId] });
}
static async orphanResources(expiryDelay: number = null) {
public static async orphanResources(expiryDelay: number = null) {
if (expiryDelay === null) expiryDelay = 1000 * 60 * 60 * 24 * 10;
const cutOffTime = Date.now() - expiryDelay;
const output = await this.modelSelectAll(
@@ -146,7 +146,7 @@ export default class NoteResource extends BaseModel {
return output.map((r: any) => r.resource_id);
}
static async deleteByResource(resourceId: string) {
public static async deleteByResource(resourceId: string) {
await this.db().exec('DELETE FROM note_resources WHERE resource_id = ?', [resourceId]);
}
}

View File

@@ -2,20 +2,20 @@ import BaseItem from './BaseItem';
import BaseModel from '../BaseModel';
export default class NoteTag extends BaseItem {
static tableName() {
public static tableName() {
return 'note_tags';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_NOTE_TAG;
}
static async byNoteIds(noteIds: string[]) {
public static async byNoteIds(noteIds: string[]) {
if (!noteIds.length) return [];
return this.modelSelectAll(`SELECT * FROM note_tags WHERE note_id IN ("${noteIds.join('","')}")`);
}
static async tagIdsByNoteId(noteId: string) {
public static async tagIdsByNoteId(noteId: string) {
const rows = await this.db().selectAll('SELECT tag_id FROM note_tags WHERE note_id = ?', [noteId]);
const output = [];
for (let i = 0; i < rows.length; i++) {

View File

@@ -29,15 +29,15 @@ export default class Resource extends BaseItem {
public static fsDriver_: any;
static tableName() {
public static tableName() {
return 'resources';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_RESOURCE;
}
static encryptionService() {
public static encryptionService() {
if (!this.encryptionService_) throw new Error('Resource.encryptionService_ is not set!!');
return this.encryptionService_;
}
@@ -47,12 +47,12 @@ export default class Resource extends BaseItem {
return this.shareService_;
}
static isSupportedImageMimeType(type: string) {
public static isSupportedImageMimeType(type: string) {
const imageMimeTypes = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif', 'image/svg+xml', 'image/webp'];
return imageMimeTypes.indexOf(type.toLowerCase()) >= 0;
}
static fetchStatuses(resourceIds: string[]): Promise<any[]> {
public static fetchStatuses(resourceIds: string[]): Promise<any[]> {
if (!resourceIds.length) return Promise.resolve([]);
return this.db().selectAll(`SELECT resource_id, fetch_status FROM resource_local_states WHERE resource_id IN ("${resourceIds.join('","')}")`);
}
@@ -61,7 +61,7 @@ export default class Resource extends BaseItem {
return this.db().selectAllFields('SELECT id FROM resources WHERE is_shared = 1', [], 'id');
}
static errorFetchStatuses() {
public static errorFetchStatuses() {
return this.db().selectAll(`
SELECT title AS resource_title, resource_id, fetch_error
FROM resource_local_states
@@ -70,7 +70,7 @@ export default class Resource extends BaseItem {
`, [Resource.FETCH_STATUS_ERROR]);
}
static needToBeFetched(resourceDownloadMode: string = null, limit: number = null) {
public static needToBeFetched(resourceDownloadMode: string = null, limit: number = null) {
const sql = ['SELECT * FROM resources WHERE encryption_applied = 0 AND id IN (SELECT resource_id FROM resource_local_states WHERE fetch_status = ?)'];
if (resourceDownloadMode !== 'always') {
sql.push('AND resources.id IN (SELECT resource_id FROM resources_to_download)');
@@ -80,21 +80,21 @@ export default class Resource extends BaseItem {
return this.modelSelectAll(sql.join(' '), [Resource.FETCH_STATUS_IDLE]);
}
static async resetStartedFetchStatus() {
public static async resetStartedFetchStatus() {
return await this.db().exec('UPDATE resource_local_states SET fetch_status = ? WHERE fetch_status = ?', [Resource.FETCH_STATUS_IDLE, Resource.FETCH_STATUS_STARTED]);
}
static resetErrorStatus(resourceId: string) {
public static resetErrorStatus(resourceId: string) {
return this.db().exec('UPDATE resource_local_states SET fetch_status = ?, fetch_error = "" WHERE resource_id = ?', [Resource.FETCH_STATUS_IDLE, resourceId]);
}
static fsDriver() {
public static fsDriver() {
if (!Resource.fsDriver_) Resource.fsDriver_ = new FsDriverDummy();
return Resource.fsDriver_;
}
// DEPRECATED IN FAVOUR OF friendlySafeFilename()
static friendlyFilename(resource: ResourceEntity) {
public static friendlyFilename(resource: ResourceEntity) {
let output = safeFilename(resource.title); // Make sure not to allow spaces or any special characters as it's not supported in HTTP headers
if (!output) output = resource.id;
let extension = resource.file_extension;
@@ -103,22 +103,22 @@ export default class Resource extends BaseItem {
return output + extension;
}
static baseDirectoryPath() {
public static baseDirectoryPath() {
return Setting.value('resourceDir');
}
static baseRelativeDirectoryPath() {
public static baseRelativeDirectoryPath() {
return Setting.value('resourceDirName');
}
static filename(resource: ResourceEntity, encryptedBlob = false) {
public static filename(resource: ResourceEntity, encryptedBlob = false) {
let extension = encryptedBlob ? 'crypted' : resource.file_extension;
if (!extension) extension = resource.mime ? mime.toFileExtension(resource.mime) : '';
extension = extension ? `.${extension}` : '';
return resource.id + extension;
}
static friendlySafeFilename(resource: ResourceEntity) {
public static friendlySafeFilename(resource: ResourceEntity) {
let ext = resource.file_extension;
if (!ext) ext = resource.mime ? mime.toFileExtension(resource.mime) : '';
const safeExt = ext ? pathUtils.safeFileExtension(ext).toLowerCase() : '';
@@ -127,20 +127,20 @@ export default class Resource extends BaseItem {
return pathUtils.friendlySafeFilename(title) + (safeExt ? `.${safeExt}` : '');
}
static relativePath(resource: ResourceEntity, encryptedBlob = false) {
public static relativePath(resource: ResourceEntity, encryptedBlob = false) {
return `${Setting.value('resourceDirName')}/${this.filename(resource, encryptedBlob)}`;
}
static fullPath(resource: ResourceEntity, encryptedBlob = false) {
public static fullPath(resource: ResourceEntity, encryptedBlob = false) {
return `${Setting.value('resourceDir')}/${this.filename(resource, encryptedBlob)}`;
}
static async isReady(resource: ResourceEntity) {
public static async isReady(resource: ResourceEntity) {
const r = await this.readyStatus(resource);
return r === 'ok';
}
static async readyStatus(resource: ResourceEntity) {
public static async readyStatus(resource: ResourceEntity) {
const ls = await this.localState(resource);
if (!resource) return 'notFound';
if (ls.fetch_status !== Resource.FETCH_STATUS_DONE) return 'notDownloaded';
@@ -148,13 +148,13 @@ export default class Resource extends BaseItem {
return 'ok';
}
static async requireIsReady(resource: ResourceEntity) {
public static async requireIsReady(resource: ResourceEntity) {
const readyStatus = await Resource.readyStatus(resource);
if (readyStatus !== 'ok') throw new Error(`Resource is not ready. Status: ${readyStatus}`);
}
// For resources, we need to decrypt the item (metadata) and the resource binary blob.
static async decrypt(item: ResourceEntity) {
public static async decrypt(item: ResourceEntity) {
// The item might already be decrypted but not the blob (for instance if it crashes while
// decrypting the blob or was otherwise interrupted).
const decryptedItem = item.encryption_cipher_text ? await super.decrypt(item) : Object.assign({}, item);
@@ -230,7 +230,7 @@ export default class Resource extends BaseItem {
return { path: encryptedPath, resource: resourceCopy };
}
static markdownTag(resource: any) {
public static markdownTag(resource: any) {
let tagAlt = resource.alt ? resource.alt : resource.title;
if (!tagAlt) tagAlt = '';
const lines = [];
@@ -246,48 +246,48 @@ export default class Resource extends BaseItem {
return lines.join('');
}
static internalUrl(resource: ResourceEntity) {
public static internalUrl(resource: ResourceEntity) {
return `:/${resource.id}`;
}
static pathToId(path: string) {
public static pathToId(path: string) {
return filename(path);
}
static async content(resource: ResourceEntity) {
public static async content(resource: ResourceEntity) {
return this.fsDriver().readFile(this.fullPath(resource), 'Buffer');
}
static isResourceUrl(url: string) {
public static isResourceUrl(url: string) {
return url && url.length === 34 && url[0] === ':' && url[1] === '/';
}
static urlToId(url: string) {
public static urlToId(url: string) {
if (!this.isResourceUrl(url)) throw new Error(`Not a valid resource URL: ${url}`);
return url.substr(2);
}
static async localState(resourceOrId: any) {
public static async localState(resourceOrId: any) {
return ResourceLocalState.byResourceId(typeof resourceOrId === 'object' ? resourceOrId.id : resourceOrId);
}
static async setLocalState(resourceOrId: any, state: ResourceLocalStateEntity) {
public static async setLocalState(resourceOrId: any, state: ResourceLocalStateEntity) {
const id = typeof resourceOrId === 'object' ? resourceOrId.id : resourceOrId;
await ResourceLocalState.save(Object.assign({}, state, { resource_id: id }));
}
static async needFileSizeSet() {
public static async needFileSizeSet() {
return this.modelSelectAll('SELECT * FROM resources WHERE `size` < 0 AND encryption_blob_encrypted = 0');
}
// Only set the `size` field and nothing else, not even the update_time
// This is because it's only necessary to do it once after migration 20
// and each client does it so there's no need to sync the resource.
static async setFileSizeOnly(resourceId: string, fileSize: number) {
public static async setFileSizeOnly(resourceId: string, fileSize: number) {
return this.db().exec('UPDATE resources set `size` = ? WHERE id = ?', [fileSize, resourceId]);
}
static async batchDelete(ids: string[], options: any = null) {
public static async batchDelete(ids: string[], options: any = null) {
// For resources, there's not really batch deleting since there's the file data to delete
// too, so each is processed one by one with the item being deleted last (since the db
// call is the less likely to fail).
@@ -305,13 +305,13 @@ export default class Resource extends BaseItem {
await ResourceLocalState.batchDelete(ids);
}
static async markForDownload(resourceId: string) {
public static async markForDownload(resourceId: string) {
// Insert the row only if it's not already there
const t = Date.now();
await this.db().exec('INSERT INTO resources_to_download (resource_id, updated_time, created_time) SELECT ?, ?, ? WHERE NOT EXISTS (SELECT 1 FROM resources_to_download WHERE resource_id = ?)', [resourceId, t, t, resourceId]);
}
static async downloadedButEncryptedBlobCount(excludedIds: string[] = null) {
public static async downloadedButEncryptedBlobCount(excludedIds: string[] = null) {
let excludedSql = '';
if (excludedIds && excludedIds.length) {
excludedSql = `AND resource_id NOT IN ("${excludedIds.join('","')}")`;
@@ -328,7 +328,7 @@ export default class Resource extends BaseItem {
return r ? r.total : 0;
}
static async downloadStatusCounts(status: number) {
public static async downloadStatusCounts(status: number) {
const r = await this.db().selectOne(`
SELECT count(*) as total
FROM resource_local_states
@@ -338,7 +338,7 @@ export default class Resource extends BaseItem {
return r ? r.total : 0;
}
static async createdLocallyCount() {
public static async createdLocallyCount() {
const r = await this.db().selectOne(`
SELECT count(*) as total
FROM resources
@@ -349,7 +349,7 @@ export default class Resource extends BaseItem {
return r ? r.total : 0;
}
static fetchStatusToLabel(status: number) {
public static fetchStatusToLabel(status: number) {
if (status === Resource.FETCH_STATUS_IDLE) return _('Not downloaded');
if (status === Resource.FETCH_STATUS_STARTED) return _('Downloading');
if (status === Resource.FETCH_STATUS_DONE) return _('Downloaded');
@@ -357,7 +357,7 @@ export default class Resource extends BaseItem {
throw new Error(`Invalid status: ${status}`);
}
static async updateResourceBlobContent(resourceId: string, newBlobFilePath: string) {
public static async updateResourceBlobContent(resourceId: string, newBlobFilePath: string) {
const resource = await Resource.load(resourceId);
await this.requireIsReady(resource);
@@ -370,7 +370,7 @@ export default class Resource extends BaseItem {
});
}
static async resourceBlobContent(resourceId: string, encoding = 'Buffer') {
public static async resourceBlobContent(resourceId: string, encoding = 'Buffer') {
const resource = await Resource.load(resourceId);
await this.requireIsReady(resource);
return await this.fsDriver().readFile(Resource.fullPath(resource), encoding);

View File

@@ -3,15 +3,15 @@ import { ResourceLocalStateEntity } from '../services/database/types';
import Database from '../database';
export default class ResourceLocalState extends BaseModel {
static tableName() {
public static tableName() {
return 'resource_local_states';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_RESOURCE_LOCAL_STATE;
}
static async byResourceId(resourceId: string) {
public static async byResourceId(resourceId: string) {
if (!resourceId) throw new Error('Resource ID not provided'); // Sanity check
const result = await this.modelSelectOne('SELECT * FROM resource_local_states WHERE resource_id = ?', [resourceId]);
@@ -26,13 +26,13 @@ export default class ResourceLocalState extends BaseModel {
return result;
}
static async save(o: ResourceLocalStateEntity) {
public static async save(o: ResourceLocalStateEntity) {
const queries = [{ sql: 'DELETE FROM resource_local_states WHERE resource_id = ?', params: [o.resource_id] }, Database.insertQuery(this.tableName(), o)];
return this.db().transactionExecBatch(queries);
}
static batchDelete(ids: string[], options: any = null) {
public static batchDelete(ids: string[], options: any = null) {
options = options ? Object.assign({}, options) : {};
options.idFieldName = 'resource_id';
return super.batchDelete(ids, options);

View File

@@ -14,11 +14,11 @@ export interface ObjectPatch {
}
export default class Revision extends BaseItem {
static tableName() {
public static tableName() {
return 'revisions';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_REVISION;
}
@@ -181,7 +181,7 @@ export default class Revision extends BaseItem {
};
}
static revisionPatchStatsText(rev: RevisionEntity) {
public static revisionPatchStatsText(rev: RevisionEntity) {
const titleStats = this.patchStats(rev.title_diff);
const bodyStats = this.patchStats(rev.body_diff);
const total = {
@@ -195,28 +195,28 @@ export default class Revision extends BaseItem {
return output.join(', ');
}
static async countRevisions(itemType: ModelType, itemId: string) {
public static async countRevisions(itemType: ModelType, itemId: string) {
const r = await this.db().selectOne('SELECT count(*) as total FROM revisions WHERE item_type = ? AND item_id = ?', [itemType, itemId]);
return r ? r.total : 0;
}
static latestRevision(itemType: ModelType, itemId: string) {
public static latestRevision(itemType: ModelType, itemId: string) {
return this.modelSelectOne('SELECT * FROM revisions WHERE item_type = ? AND item_id = ? ORDER BY item_updated_time DESC LIMIT 1', [itemType, itemId]);
}
static allByType(itemType: ModelType, itemId: string) {
public static allByType(itemType: ModelType, itemId: string) {
return this.modelSelectAll('SELECT * FROM revisions WHERE item_type = ? AND item_id = ? ORDER BY item_updated_time ASC', [itemType, itemId]);
}
static async itemsWithRevisions(itemType: ModelType, itemIds: string[]) {
public static async itemsWithRevisions(itemType: ModelType, itemIds: string[]) {
if (!itemIds.length) return [];
const rows = await this.db().selectAll(`SELECT distinct item_id FROM revisions WHERE item_type = ? AND item_id IN ("${itemIds.join('","')}")`, [itemType]);
return rows.map((r: RevisionEntity) => r.item_id);
}
static async itemsWithNoRevisions(itemType: ModelType, itemIds: string[]) {
public static async itemsWithNoRevisions(itemType: ModelType, itemIds: string[]) {
const withRevs = await this.itemsWithRevisions(itemType, itemIds);
const output = [];
for (let i = 0; i < itemIds.length; i++) {
@@ -225,7 +225,7 @@ export default class Revision extends BaseItem {
return ArrayUtils.unique(output);
}
static moveRevisionToTop(revision: RevisionEntity, revs: RevisionEntity[]) {
public static moveRevisionToTop(revision: RevisionEntity, revs: RevisionEntity[]) {
let targetIndex = -1;
for (let i = revs.length - 1; i >= 0; i--) {
const rev = revs[i];
@@ -295,7 +295,7 @@ export default class Revision extends BaseItem {
return output;
}
static async deleteOldRevisions(ttl: number) {
public static async deleteOldRevisions(ttl: number) {
// When deleting old revisions, we need to make sure that the oldest surviving revision
// is a "merged" one (as opposed to a diff from a now deleted revision). So every time
// we deleted a revision, we need to find if there's a corresponding surviving revision
@@ -342,7 +342,7 @@ export default class Revision extends BaseItem {
}
}
static async revisionExists(itemType: ModelType, itemId: string, updatedTime: number) {
public static async revisionExists(itemType: ModelType, itemId: string, updatedTime: number) {
const existingRev = await Revision.latestRevision(itemType, itemId);
return existingRev && existingRev.item_updated_time === updatedTime;
}

View File

@@ -3,15 +3,15 @@
import BaseModel from '../BaseModel';
export default class Search extends BaseModel {
static tableName(): string {
public static tableName(): string {
throw new Error('Not using database');
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_SEARCH;
}
static keywords(query: string) {
public static keywords(query: string) {
let output: any = query.trim();
output = output.split(/[\s\t\n]+/);
output = output.filter((o: any) => !!o);

View File

@@ -313,11 +313,11 @@ class Setting extends BaseModel {
private static rootFileHandler_: FileHandler = null;
private static settingFilename_: string = 'settings.json';
static tableName() {
public static tableName() {
return 'settings';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_SETTING;
}
@@ -365,16 +365,16 @@ class Setting extends BaseModel {
return this.rootFileHandler_;
}
static keychainService() {
public static keychainService() {
if (!this.keychainService_) throw new Error('keychainService has not been set!!');
return this.keychainService_;
}
static setKeychainService(s: any) {
public static setKeychainService(s: any) {
this.keychainService_ = s;
}
static metadata(): SettingItems {
public static metadata(): SettingItems {
if (this.metadata_) return this.metadata_;
const platform = shim.platformName();
@@ -1750,7 +1750,7 @@ class Setting extends BaseModel {
if (type < 0) throw new Error(`Invalid setting type: ${type}`);
}
static async registerSetting(key: string, metadataItem: SettingItem) {
public static async registerSetting(key: string, metadataItem: SettingItem) {
try {
if (metadataItem.isEnum && !metadataItem.options) throw new Error('The `options` property is required for enum types');
@@ -1786,11 +1786,11 @@ class Setting extends BaseModel {
}
}
static async registerSection(name: string, source: SettingSectionSource, section: SettingSection) {
public static async registerSection(name: string, source: SettingSectionSource, section: SettingSection) {
this.customSections_[name] = { ...section, name: name, source: source };
}
static settingMetadata(key: string): SettingItem {
public static settingMetadata(key: string): SettingItem {
const metadata = this.metadata();
if (!(key in metadata)) throw new Error(`Unknown key: ${key}`);
const output = Object.assign({}, metadata[key]);
@@ -1812,17 +1812,17 @@ class Setting extends BaseModel {
return !!this.cache_.find(d => d.key === key);
}
static keyDescription(key: string, appType: AppType = null) {
public static keyDescription(key: string, appType: AppType = null) {
const md = this.settingMetadata(key);
if (!md.description) return null;
return md.description(appType);
}
static isSecureKey(key: string) {
public static isSecureKey(key: string) {
return this.metadata()[key] && this.metadata()[key].secure === true;
}
static keys(publicOnly: boolean = false, appType: AppType = null, options: KeysOptions = null) {
public static keys(publicOnly: boolean = false, appType: AppType = null, options: KeysOptions = null) {
options = Object.assign({}, {
secureOnly: false,
}, options);
@@ -1851,7 +1851,7 @@ class Setting extends BaseModel {
}
}
static isPublic(key: string) {
public static isPublic(key: string) {
return this.keys(true).indexOf(key) >= 0;
}
@@ -1967,7 +1967,7 @@ class Setting extends BaseModel {
return md.storage || SettingStorage.Database;
}
static toPlainObject() {
public static toPlainObject() {
const keys = this.keys();
const keyToValues: any = {};
for (let i = 0; i < keys.length; i++) {
@@ -1976,14 +1976,14 @@ class Setting extends BaseModel {
return keyToValues;
}
static dispatchUpdateAll() {
public static dispatchUpdateAll() {
this.dispatch({
type: 'SETTING_UPDATE_ALL',
settings: this.toPlainObject(),
});
}
static setConstant(key: string, value: any) {
public static setConstant(key: string, value: any) {
if (!(key in this.constants_)) throw new Error(`Unknown constant key: ${key}`);
(this.constants_ as any)[key] = value;
}
@@ -2046,11 +2046,11 @@ class Setting extends BaseModel {
this.scheduleChangeEvent();
}
static incValue(key: string, inc: any) {
public static incValue(key: string, inc: any) {
return this.setValue(key, this.value(key) + inc);
}
static toggle(key: string) {
public static toggle(key: string) {
return this.setValue(key, !this.value(key));
}
@@ -2065,27 +2065,27 @@ class Setting extends BaseModel {
return false;
}
static objectValue(settingKey: string, objectKey: string, defaultValue: any = null) {
public static objectValue(settingKey: string, objectKey: string, defaultValue: any = null) {
const o = this.value(settingKey);
if (!o || !(objectKey in o)) return defaultValue;
return o[objectKey];
}
static setObjectValue(settingKey: string, objectKey: string, value: any) {
public static setObjectValue(settingKey: string, objectKey: string, value: any) {
let o = this.value(settingKey);
if (typeof o !== 'object') o = {};
o[objectKey] = value;
this.setValue(settingKey, o);
}
static deleteObjectValue(settingKey: string, objectKey: string) {
public static deleteObjectValue(settingKey: string, objectKey: string) {
const o = this.value(settingKey);
if (typeof o !== 'object') return;
delete o[objectKey];
this.setValue(settingKey, o);
}
static async deleteKeychainPasswords() {
public static async deleteKeychainPasswords() {
const secureKeys = this.keys(false, null, { secureOnly: true });
for (const key of secureKeys) {
await this.keychainService().deletePassword(`setting.${key}`);
@@ -2121,7 +2121,7 @@ class Setting extends BaseModel {
return output;
}
static valueToString(key: string, value: any) {
public static valueToString(key: string, value: any) {
const md = this.settingMetadata(key);
value = this.formatValue(key, value);
if (md.type === SettingItemType.Int) return value.toFixed(0);
@@ -2133,12 +2133,12 @@ class Setting extends BaseModel {
throw new Error(`Unhandled value type: ${md.type}`);
}
static filterValue(key: string, value: any) {
public static filterValue(key: string, value: any) {
const md = this.settingMetadata(key);
return md.filter ? md.filter(value) : value;
}
static formatValue(key: string | SettingItemType, value: any) {
public static formatValue(key: string | SettingItemType, value: any) {
const type = typeof key === 'string' ? this.settingMetadata(key).type : key;
if (type === SettingItemType.Int) return !value ? 0 : Math.floor(Number(value));
@@ -2175,7 +2175,7 @@ class Setting extends BaseModel {
throw new Error(`Unhandled value type: ${type}`);
}
static value(key: string) {
public static value(key: string) {
// Need to copy arrays and objects since in setValue(), the old value and new one is compared
// with strict equality and the value is updated only if changed. However if the caller acquire
// and object and change a key, the objects will be detected as equal. By returning a copy
@@ -2212,12 +2212,12 @@ class Setting extends BaseModel {
return this.value(key);
}
static isEnum(key: string) {
public static isEnum(key: string) {
const md = this.settingMetadata(key);
return md.isEnum === true;
}
static enumOptionValues(key: string) {
public static enumOptionValues(key: string) {
const options = this.enumOptions(key);
const output = [];
for (const n in options) {
@@ -2227,7 +2227,7 @@ class Setting extends BaseModel {
return output;
}
static enumOptionLabel(key: string, value: any) {
public static enumOptionLabel(key: string, value: any) {
const options = this.enumOptions(key);
for (const n in options) {
if (n === value) return options[n];
@@ -2235,14 +2235,14 @@ class Setting extends BaseModel {
return '';
}
static enumOptions(key: string) {
public static enumOptions(key: string) {
const metadata = this.metadata();
if (!metadata[key]) throw new Error(`Unknown key: ${key}`);
if (!metadata[key].options) throw new Error(`No options for: ${key}`);
return metadata[key].options();
}
static enumOptionsDoc(key: string, templateString: string = null) {
public static enumOptionsDoc(key: string, templateString: string = null) {
if (templateString === null) templateString = '%s: %s';
const options = this.enumOptions(key);
const output = [];
@@ -2253,7 +2253,7 @@ class Setting extends BaseModel {
return output.join(', ');
}
static isAllowedEnumOption(key: string, value: any) {
public static isAllowedEnumOption(key: string, value: any) {
const options = this.enumOptions(key);
return !!options[value];
}
@@ -2262,7 +2262,7 @@ class Setting extends BaseModel {
// { sync.5.path: 'http://example', sync.5.username: 'testing' }
// and baseKey is 'sync.5', the function will return
// { path: 'http://example', username: 'testing' }
static subValues(baseKey: string, settings: any, options: any = null) {
public static subValues(baseKey: string, settings: any, options: any = null) {
const includeBaseKeyInName = !!options && !!options.includeBaseKeyInName;
const output: any = {};
@@ -2358,7 +2358,7 @@ class Setting extends BaseModel {
logger.debug('Settings have been saved.');
}
static scheduleChangeEvent() {
public static scheduleChangeEvent() {
if (this.changeEventTimeoutId_) shim.clearTimeout(this.changeEventTimeoutId_);
this.changeEventTimeoutId_ = shim.setTimeout(() => {
@@ -2366,7 +2366,7 @@ class Setting extends BaseModel {
}, 1000);
}
static cancelScheduleChangeEvent() {
public static cancelScheduleChangeEvent() {
if (this.changeEventTimeoutId_) shim.clearTimeout(this.changeEventTimeoutId_);
this.changeEventTimeoutId_ = null;
}
@@ -2388,7 +2388,7 @@ class Setting extends BaseModel {
eventManager.emit('settingsChange', { keys });
}
static scheduleSave() {
public static scheduleSave() {
if (!Setting.autoSaveEnabled) return;
if (this.saveTimeoutId_) shim.clearTimeout(this.saveTimeoutId_);
@@ -2402,12 +2402,12 @@ class Setting extends BaseModel {
}, 500);
}
static cancelScheduleSave() {
public static cancelScheduleSave() {
if (this.saveTimeoutId_) shim.clearTimeout(this.saveTimeoutId_);
this.saveTimeoutId_ = null;
}
static publicSettings(appType: AppType) {
public static publicSettings(appType: AppType) {
if (!appType) throw new Error('appType is required');
const metadata = this.metadata();
@@ -2424,7 +2424,7 @@ class Setting extends BaseModel {
return output;
}
static typeToString(typeId: number) {
public static typeToString(typeId: number) {
if (typeId === SettingItemType.Int) return 'int';
if (typeId === SettingItemType.String) return 'string';
if (typeId === SettingItemType.Bool) return 'bool';
@@ -2438,7 +2438,7 @@ class Setting extends BaseModel {
return SettingSectionSource.Default;
}
static groupMetadatasBySections(metadatas: SettingItem[]) {
public static groupMetadatasBySections(metadatas: SettingItem[]) {
const sections = [];
const generalSection: any = { name: 'general', metadatas: [] };
const nameToSections: any = {};
@@ -2472,7 +2472,7 @@ class Setting extends BaseModel {
return sections;
}
static sectionNameToLabel(name: string) {
public static sectionNameToLabel(name: string) {
if (name === 'general') return _('General');
if (name === 'sync') return _('Synchronisation');
if (name === 'appearance') return _('Appearance');
@@ -2491,7 +2491,7 @@ class Setting extends BaseModel {
return name;
}
static sectionDescription(name: string) {
public static sectionDescription(name: string) {
if (name === 'markdownPlugins') return _('These plugins enhance the Markdown renderer with additional features. Please note that, while these features might be useful, they are not standard Markdown and thus most of them will only work in Joplin. Additionally, some of them are *incompatible* with the WYSIWYG editor. If you open a note that uses one of these plugins in that editor, you will lose the plugin formatting. It is indicated below which plugins are compatible or not with the WYSIWYG editor.');
if (name === 'general') return _('Notes and settings are stored in: %s', toSystemSlashes(this.value('profileDir'), process.platform));
@@ -2500,7 +2500,7 @@ class Setting extends BaseModel {
return '';
}
static sectionNameToIcon(name: string) {
public static sectionNameToIcon(name: string) {
if (name === 'general') return 'icon-general';
if (name === 'sync') return 'icon-sync';
if (name === 'appearance') return 'icon-appearance';
@@ -2519,7 +2519,7 @@ class Setting extends BaseModel {
return 'fas fa-cog';
}
static appTypeToLabel(name: string) {
public static appTypeToLabel(name: string) {
// Not translated for now because only used on Welcome notes (which are not translated)
if (name === 'cli') return 'CLI';
return name[0].toUpperCase() + name.substr(1).toLowerCase();

View File

@@ -3,11 +3,11 @@
import BaseModel from '../BaseModel';
export default class SmartFilter extends BaseModel {
static tableName(): string {
public static tableName(): string {
throw new Error('Not using database');
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_SMART_FILTER;
}
}

View File

@@ -7,15 +7,15 @@ import Note from './Note';
import { _ } from '../locale';
export default class Tag extends BaseItem {
static tableName() {
public static tableName() {
return 'tags';
}
static modelType() {
public static modelType() {
return BaseModel.TYPE_TAG;
}
static async noteIds(tagId: string) {
public static async noteIds(tagId: string) {
const rows = await this.db().selectAll('SELECT note_id FROM note_tags WHERE tag_id = ?', [tagId]);
const output = [];
for (let i = 0; i < rows.length; i++) {
@@ -24,7 +24,7 @@ export default class Tag extends BaseItem {
return output;
}
static async notes(tagId: string, options: any = null) {
public static async notes(tagId: string, options: any = null) {
if (options === null) options = {};
const noteIds = await this.noteIds(tagId);
@@ -39,7 +39,7 @@ export default class Tag extends BaseItem {
}
// Untag all the notes and delete tag
static async untagAll(tagId: string) {
public static async untagAll(tagId: string) {
const noteTags = await NoteTag.modelSelectAll('SELECT id FROM note_tags WHERE tag_id = ?', [tagId]);
for (let i = 0; i < noteTags.length; i++) {
await NoteTag.delete(noteTags[i].id);
@@ -48,7 +48,7 @@ export default class Tag extends BaseItem {
await Tag.delete(tagId);
}
static async delete(id: string, options: any = null) {
public static async delete(id: string, options: any = null) {
if (!options) options = {};
await super.delete(id, options);
@@ -59,7 +59,7 @@ export default class Tag extends BaseItem {
});
}
static async addNote(tagId: string, noteId: string) {
public static async addNote(tagId: string, noteId: string) {
const hasIt = await this.hasNote(tagId, noteId);
if (hasIt) return;
@@ -89,7 +89,7 @@ export default class Tag extends BaseItem {
return output;
}
static async removeNote(tagId: string, noteId: string) {
public static async removeNote(tagId: string, noteId: string) {
const noteTags = await NoteTag.modelSelectAll('SELECT id FROM note_tags WHERE tag_id = ? and note_id = ?', [tagId, noteId]);
for (let i = 0; i < noteTags.length; i++) {
await NoteTag.delete(noteTags[i].id);
@@ -101,34 +101,34 @@ export default class Tag extends BaseItem {
});
}
static loadWithCount(tagId: string) {
public static loadWithCount(tagId: string) {
const sql = 'SELECT * FROM tags_with_note_count WHERE id = ?';
return this.modelSelectOne(sql, [tagId]);
}
static async hasNote(tagId: string, noteId: string) {
public static async hasNote(tagId: string, noteId: string) {
const r = await this.db().selectOne('SELECT note_id FROM note_tags WHERE tag_id = ? AND note_id = ? LIMIT 1', [tagId, noteId]);
return !!r;
}
static async allWithNotes() {
public static async allWithNotes() {
return await Tag.modelSelectAll('SELECT * FROM tags_with_note_count');
}
static async searchAllWithNotes(options: any) {
public static async searchAllWithNotes(options: any) {
if (!options) options = {};
if (!options.conditions) options.conditions = [];
options.conditions.push('id IN (SELECT distinct id FROM tags_with_note_count)');
return this.search(options);
}
static async tagsByNoteId(noteId: string) {
public static async tagsByNoteId(noteId: string) {
const tagIds = await NoteTag.tagIdsByNoteId(noteId);
if (!tagIds.length) return [];
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${tagIds.join('","')}")`);
}
static async commonTagsByNoteIds(noteIds: string[]) {
public static async commonTagsByNoteIds(noteIds: string[]) {
if (!noteIds || noteIds.length === 0) {
return [];
}
@@ -143,17 +143,17 @@ export default class Tag extends BaseItem {
return this.modelSelectAll(`SELECT * FROM tags WHERE id IN ("${commonTagIds.join('","')}")`);
}
static async loadByTitle(title: string) {
public static async loadByTitle(title: string) {
return this.loadByField('title', title, { caseInsensitive: true });
}
static async addNoteTagByTitle(noteId: string, tagTitle: string) {
public static async addNoteTagByTitle(noteId: string, tagTitle: string) {
let tag = await this.loadByTitle(tagTitle);
if (!tag) tag = await Tag.save({ title: tagTitle }, { userSideValidation: true });
return await this.addNote(tag.id, noteId);
}
static async setNoteTagsByTitles(noteId: string, tagTitles: string[]) {
public static async setNoteTagsByTitles(noteId: string, tagTitles: string[]) {
const previousTags = await this.tagsByNoteId(noteId);
const addedTitles = [];
@@ -173,7 +173,7 @@ export default class Tag extends BaseItem {
}
}
static async setNoteTagsByIds(noteId: string, tagIds: string[]) {
public static async setNoteTagsByIds(noteId: string, tagIds: string[]) {
const previousTags = await this.tagsByNoteId(noteId);
const addedIds = [];
@@ -190,7 +190,7 @@ export default class Tag extends BaseItem {
}
}
static async save(o: TagEntity, options: any = null) {
public static async save(o: TagEntity, options: any = null) {
options = Object.assign({}, {
dispatchUpdateAction: true,
userSideValidation: false,