mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
tags
This commit is contained in:
parent
dde3ea2008
commit
b36905cb3c
@ -1,3 +1,5 @@
|
||||
"use strict"
|
||||
|
||||
require('source-map-support').install();
|
||||
require('babel-plugin-transform-runtime');
|
||||
|
||||
@ -94,12 +96,32 @@ async function clientItems(client) {
|
||||
}
|
||||
}
|
||||
|
||||
function randomTag(items) {
|
||||
let tags = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type_ != 5) continue;
|
||||
tags.push(items[i]);
|
||||
}
|
||||
|
||||
return randomElement(tags);
|
||||
}
|
||||
|
||||
function randomNote(items) {
|
||||
let notes = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type_ != 1) continue;
|
||||
notes.push(items[i]);
|
||||
}
|
||||
|
||||
return randomElement(notes);
|
||||
}
|
||||
|
||||
async function execRandomCommand(client) {
|
||||
let possibleCommands = [
|
||||
['mkbook {word}', 40], // CREATE FOLDER
|
||||
['mknote {word}', 70], // CREATE NOTE
|
||||
[async () => { // DELETE RANDOM ITEM
|
||||
let items = clientItems(client);
|
||||
let items = await clientItems(client);
|
||||
let item = randomElement(items);
|
||||
if (!item) return;
|
||||
|
||||
@ -107,6 +129,8 @@ async function execRandomCommand(client) {
|
||||
return execCommand(client, 'rm -f ' + item.title);
|
||||
} else if (item.type_ == 2) {
|
||||
return execCommand(client, 'rm -f ' + '../' + item.title);
|
||||
} else if (item.type_ == 5) {
|
||||
// tag
|
||||
} else {
|
||||
throw new Error('Unknown type: ' + item.type_);
|
||||
}
|
||||
@ -122,12 +146,22 @@ async function execRandomCommand(client) {
|
||||
return execCommand(client, 'sync --random-failures', options);
|
||||
}, 30],
|
||||
[async () => { // UPDATE RANDOM ITEM
|
||||
let items = clientItems(client);
|
||||
let items = await clientItems(client);
|
||||
let item = randomElement(items);
|
||||
if (!item) return;
|
||||
|
||||
return execCommand(client, 'set ' + item.id + ' title "' + randomWord() + '"');
|
||||
}, 50],
|
||||
[async () => { // ADD TAG
|
||||
let items = await clientItems(client);
|
||||
let note = randomNote(items);
|
||||
if (!note) return;
|
||||
|
||||
let tag = randomTag(items);
|
||||
let tagTitle = !tag || Math.random() >= 0.9 ? 'tag-' + randomWord() : tag.title;
|
||||
|
||||
return execCommand(client, 'tag add ' + tagTitle + ' "' + note.title + '"');
|
||||
}, 50],
|
||||
];
|
||||
|
||||
let cmd = null;
|
||||
@ -171,7 +205,16 @@ function compareItems(item1, item2) {
|
||||
if (n == 'sync_time') continue;
|
||||
let p1 = item1[n];
|
||||
let p2 = item2[n];
|
||||
if (p1 !== p2) output.push(n);
|
||||
|
||||
if (n == 'notes_') {
|
||||
p1.sort();
|
||||
p2.sort();
|
||||
if (JSON.stringify(p1) !== JSON.stringify(p2)) {
|
||||
output.push(n);
|
||||
}
|
||||
} else {
|
||||
if (p1 !== p2) output.push(n);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@ -235,8 +278,8 @@ async function compareClientItems(clientItems) {
|
||||
let diff = compareItems(item1, item2);
|
||||
if (diff.length) {
|
||||
differences.push({
|
||||
item1: item1,
|
||||
item2: item2,
|
||||
item1: JSON.stringify(item1),
|
||||
item2: JSON.stringify(item2),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import { Folder } from 'lib/models/folder.js';
|
||||
import { Resource } from 'lib/models/resource.js';
|
||||
import { BaseItem } from 'lib/models/base-item.js';
|
||||
import { Note } from 'lib/models/note.js';
|
||||
import { Tag } from 'lib/models/tag.js';
|
||||
import { Setting } from 'lib/models/setting.js';
|
||||
import { Synchronizer } from 'lib/synchronizer.js';
|
||||
import { Logger } from 'lib/logger.js';
|
||||
@ -59,7 +60,7 @@ commands.push({
|
||||
aliases: ['mkdir'],
|
||||
description: 'Creates a new notebook',
|
||||
action: function(args, end) {
|
||||
Folder.save({ title: args['notebook'] }).then((folder) => {
|
||||
Folder.save({ title: args['notebook'] }, { duplicateCheck: true }).then((folder) => {
|
||||
switchCurrentFolder(folder);
|
||||
}).catch((error) => {
|
||||
this.log(error);
|
||||
@ -70,7 +71,7 @@ commands.push({
|
||||
});
|
||||
|
||||
commands.push({
|
||||
usage: 'mknote <note-title>',
|
||||
usage: 'mknote <note>',
|
||||
aliases: ['touch'],
|
||||
description: 'Creates a new note',
|
||||
action: function(args, end) {
|
||||
@ -81,7 +82,7 @@ commands.push({
|
||||
}
|
||||
|
||||
let note = {
|
||||
title: args['note-title'],
|
||||
title: args['note'],
|
||||
parent_id: currentFolder.id,
|
||||
};
|
||||
Note.save(note).catch((error) => {
|
||||
@ -265,6 +266,45 @@ commands.push({
|
||||
autocomplete: autocompleteItems,
|
||||
});
|
||||
|
||||
commands.push({
|
||||
usage: 'tag <command> [tag] [note]',
|
||||
description: '<command> can be "add", "remove" or "list" to assign or remove [tag] from [note], or to list the notes associated with [tag]. The command `tag list` can be used to list all the tags.',
|
||||
action: async function(args, end) {
|
||||
try {
|
||||
let tag = null;
|
||||
if (args.tag) tag = await loadItem(BaseModel.MODEL_TYPE_TAG, args.tag);
|
||||
let note = null;
|
||||
if (args.note) note = await loadItem(BaseModel.MODEL_TYPE_NOTE, args.note);
|
||||
|
||||
if (args.command == 'remove' && !tag) throw new Error(_('Tag does not exist: "%s"', args.tag));
|
||||
|
||||
if (args.command == 'add') {
|
||||
if (!note) throw new Error(_('Note does not exist: "%s"', args.note));
|
||||
if (!tag) tag = await Tag.save({ title: args.tag });
|
||||
await Tag.addNote(tag.id, note.id);
|
||||
} else if (args.command == 'remove') {
|
||||
if (!tag) throw new Error(_('Tag does not exist: "%s"', args.tag));
|
||||
if (!note) throw new Error(_('Note does not exist: "%s"', args.note));
|
||||
await Tag.removeNote(tag.id, note.id);
|
||||
} else if (args.command == 'list') {
|
||||
if (tag) {
|
||||
let notes = await Tag.notes(tag.id);
|
||||
notes.map((note) => { this.log(note.title); });
|
||||
} else {
|
||||
let tags = await Tag.all();
|
||||
tags.map((tag) => { this.log(tag.title); });
|
||||
}
|
||||
} else {
|
||||
throw new Error(_('Invalid command: "%s"', args.command));
|
||||
}
|
||||
} catch (error) {
|
||||
this.log(error);
|
||||
}
|
||||
|
||||
end();
|
||||
}
|
||||
});
|
||||
|
||||
commands.push({
|
||||
usage: 'dump',
|
||||
description: 'Dumps the complete database as JSON.',
|
||||
@ -278,6 +318,13 @@ commands.push({
|
||||
items.push(folder);
|
||||
items = items.concat(notes);
|
||||
}
|
||||
|
||||
let tags = await Tag.all();
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
tags[i].notes_ = await Tag.tagNoteIds(tags[i].id);
|
||||
}
|
||||
|
||||
items = items.concat(tags);
|
||||
|
||||
this.log(JSON.stringify(items));
|
||||
} catch (error) {
|
||||
@ -285,8 +332,7 @@ commands.push({
|
||||
}
|
||||
|
||||
end();
|
||||
},
|
||||
autocomplete: autocompleteFolders,
|
||||
}
|
||||
});
|
||||
|
||||
commands.push({
|
||||
@ -495,6 +541,19 @@ commands.push({
|
||||
},
|
||||
});
|
||||
|
||||
async function loadItem(type, pattern) {
|
||||
let output = await loadItems(type, pattern);
|
||||
return output.length ? output[0] : null;
|
||||
}
|
||||
|
||||
async function loadItems(type, pattern) {
|
||||
let ItemClass = BaseItem.itemClass(type);
|
||||
let item = await ItemClass.loadByTitle(pattern);
|
||||
if (item) return [item];
|
||||
item = await ItemClass.load(pattern);
|
||||
return [item];
|
||||
}
|
||||
|
||||
function commandByName(name) {
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
let c = commands[i];
|
||||
@ -519,58 +578,6 @@ function execCommand(name, args) {
|
||||
});
|
||||
}
|
||||
|
||||
// async function execCommand(args) {
|
||||
// var parseArgs = require('minimist');
|
||||
|
||||
// let results = parseArgs(args);
|
||||
// //var results = vorpal.parse(args, { use: 'minimist' });
|
||||
// if (!results['_'].length) throw new Error(_('Invalid command: %s', args));
|
||||
|
||||
// console.info(results);
|
||||
|
||||
// let commandName = results['_'].splice(0, 1);
|
||||
// let cmd = commandByName(commandName);
|
||||
// if (!cmd) throw new Error(_('Unknown command: %s', args));
|
||||
|
||||
|
||||
// let usage = cmd.usage.split(' ');
|
||||
// let commandArgs = [];
|
||||
// usage.splice(0, 1);
|
||||
// for (let i = 0; i < usage.length; i++) {
|
||||
// let u = usage[i].trim();
|
||||
// if (u == '') continue;
|
||||
|
||||
// let required = false;
|
||||
|
||||
// if (u.length >= 3 && u[0] == '<' && u[u.length - 1] == '>') {
|
||||
// required = true;
|
||||
// u = u.substr(1, u.length - 2);
|
||||
// }
|
||||
|
||||
// if (u.length >= 3 && u[0] == '[' && u[u.length - 1] == ']') {
|
||||
// u = u.substr(1, u.length - 2);
|
||||
// }
|
||||
|
||||
// if (required && !results['_'].length) throw new Error(_('Missing argument: %s', args));
|
||||
|
||||
// if (!results['_'].length) break;
|
||||
|
||||
// console.info(u);
|
||||
|
||||
// commandArgs[u] = results['_'].splice(0, 1);
|
||||
// }
|
||||
|
||||
// console.info(commandArgs);
|
||||
|
||||
|
||||
// // usage: 'import-enex <file> [notebook]',
|
||||
// // description: _('Imports en Evernote notebook file (.enex file).'),
|
||||
// // options: [
|
||||
// // ['--fuzzy-matching', 'For debugging purposes. Do not use.'],
|
||||
// // ],
|
||||
|
||||
// }
|
||||
|
||||
async function synchronizer(syncTarget) {
|
||||
if (synchronizers_[syncTarget]) return synchronizers_[syncTarget];
|
||||
|
||||
|
@ -31,7 +31,7 @@ function sleep(n) {
|
||||
}
|
||||
|
||||
async function switchClient(id) {
|
||||
await time.msleep(200);
|
||||
await time.msleep(200); // Always leave a little time so that updated_time properties don't overlap
|
||||
await Setting.saveAll();
|
||||
|
||||
currentClient_ = id;
|
||||
|
@ -128,16 +128,35 @@ class BaseModel {
|
||||
if (options.orderByDir) sql += ' ' + options.orderByDir;
|
||||
}
|
||||
if (options.limit) sql += ' LIMIT ' + options.limit;
|
||||
//if (options.fields && options.fields.length) sql = sql.replace('SELECT *', 'SELECT ' + this.db().escapeFields(options.fields).join(','));
|
||||
|
||||
return { sql: sql, params: params };
|
||||
}
|
||||
|
||||
static async all(options = null) {
|
||||
static async all(options = null) {
|
||||
let q = this.applySqlOptions(options, 'SELECT * FROM `' + this.tableName() + '`');
|
||||
return this.modelSelectAll(q.sql);
|
||||
}
|
||||
|
||||
static async search(options = null) {
|
||||
if (!options) options = {};
|
||||
if (!options.fields) options.fields = '*';
|
||||
|
||||
let conditions = options.conditions ? options.conditions.slice(0) : [];
|
||||
let params = options.conditionsParams ? options.conditionsParams.slice(0) : [];
|
||||
|
||||
if (options.titlePattern) {
|
||||
let pattern = options.titlePattern.replace(/\*/g, '%');
|
||||
conditions.push('title LIKE ?');
|
||||
params.push(pattern);
|
||||
}
|
||||
|
||||
let sql = 'SELECT ' + this.db().escapeFields(options.fields) + ' FROM `' + this.tableName() + '`';
|
||||
if (conditions.length) sql += ' WHERE ' + conditions.join(' AND ');
|
||||
|
||||
let query = this.applySqlOptions(options, sql, params);
|
||||
return this.modelSelectAll(query.sql, query.params);
|
||||
}
|
||||
|
||||
static modelSelectOne(sql, params = null) {
|
||||
if (params === null) params = [];
|
||||
return this.db().selectOne(sql, params).then((model) => {
|
||||
|
@ -163,10 +163,13 @@ class Database {
|
||||
}
|
||||
|
||||
escapeField(field) {
|
||||
if (field == '*') return '*';
|
||||
return '`' + field + '`';
|
||||
}
|
||||
|
||||
escapeFields(fields) {
|
||||
if (fields == '*') return '*';
|
||||
|
||||
let output = [];
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
output.push(this.escapeField(fields[i]));
|
||||
@ -174,50 +177,46 @@ class Database {
|
||||
return output;
|
||||
}
|
||||
|
||||
selectOne(sql, params = null) {
|
||||
this.logQuery(sql, params);
|
||||
return this.driver().selectOne(sql, params).catch((error) => {
|
||||
throw this.sqliteErrorToJsError(error, sql, params);
|
||||
});
|
||||
}
|
||||
|
||||
selectAll(sql, params = null) {
|
||||
this.logQuery(sql, params);
|
||||
return this.driver().selectAll(sql, params).catch((error) => {
|
||||
throw this.sqliteErrorToJsError(error, sql, params);
|
||||
});
|
||||
}
|
||||
|
||||
async exec(sql, params = null) {
|
||||
async tryCall(callName, sql, params) {
|
||||
if (typeof sql === 'object') {
|
||||
params = sql.params;
|
||||
sql = sql.sql;
|
||||
}
|
||||
|
||||
let result = null;
|
||||
let waitTime = 50;
|
||||
let totalWaitTime = 0;
|
||||
while (true) {
|
||||
try {
|
||||
this.logQuery(sql, params);
|
||||
let result = await this.driver().exec(sql, params);
|
||||
return result;; // No exception was thrown
|
||||
let result = await this.driver()[callName](sql, params);
|
||||
return result; // No exception was thrown
|
||||
} catch (error) {
|
||||
throw error;
|
||||
if (error && error.code == 'SQLITE_IOERR') {
|
||||
if (totalWaitTime >= 20000) throw error;
|
||||
this.logger().warn(sprintf('SQLITE_IOERR: will retry in %s milliseconds', waitTime));
|
||||
if (error && (error.code == 'SQLITE_IOERR' || error.code == 'SQLITE_BUSY')) {
|
||||
if (totalWaitTime >= 20000) throw this.sqliteErrorToJsError(error, sql, params);
|
||||
this.logger().warn(sprintf('Error %s: will retry in %s milliseconds', error.code, waitTime));
|
||||
this.logger().warn('Error was: ' + error.toString());
|
||||
await time.msleep(waitTime);
|
||||
totalWaitTime += waitTime;
|
||||
waitTime *= 1.5;
|
||||
} else {
|
||||
throw this.sqliteErrorToJsError(error, sql, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async selectOne(sql, params = null) {
|
||||
return this.tryCall('selectOne', sql, params);
|
||||
}
|
||||
|
||||
async selectAll(sql, params = null) {
|
||||
return this.tryCall('selectAll', sql, params);
|
||||
}
|
||||
|
||||
async exec(sql, params = null) {
|
||||
return this.tryCall('exec', sql, params);
|
||||
}
|
||||
|
||||
transactionExecBatch(queries) {
|
||||
if (queries.length <= 0) return Promise.resolve();
|
||||
|
||||
|
@ -5,6 +5,13 @@ import { time } from 'lib/time-utils.js';
|
||||
|
||||
class FileApiDriverLocal {
|
||||
|
||||
fsErrorToJsError_(error) {
|
||||
let msg = error.toString();
|
||||
let output = new Error(msg);
|
||||
if (error.code) output.code = error.code;
|
||||
return output;
|
||||
}
|
||||
|
||||
stat(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (error, s) => {
|
||||
@ -12,7 +19,7 @@ class FileApiDriverLocal {
|
||||
if (error.code == 'ENOENT') {
|
||||
resolve(null);
|
||||
} else {
|
||||
reject(error);
|
||||
reject(this.fsErrorToJsError_(error));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -45,7 +52,7 @@ class FileApiDriverLocal {
|
||||
let t = Math.floor(timestampMs / 1000);
|
||||
fs.utimes(path, t, t, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
reject(this.fsErrorToJsError_(error));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
@ -54,20 +61,24 @@ class FileApiDriverLocal {
|
||||
}
|
||||
|
||||
async list(path, options) {
|
||||
let items = await fs.readdir(path);
|
||||
let output = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let stat = await this.stat(path + '/' + items[i]);
|
||||
if (!stat) continue; // Has been deleted between the readdir() call and now
|
||||
stat.path = items[i];
|
||||
output.push(stat);
|
||||
}
|
||||
try {
|
||||
let items = await fs.readdir(path);
|
||||
let output = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let stat = await this.stat(path + '/' + items[i]);
|
||||
if (!stat) continue; // Has been deleted between the readdir() call and now
|
||||
stat.path = items[i];
|
||||
output.push(stat);
|
||||
}
|
||||
|
||||
return {
|
||||
items: output,
|
||||
hasMore: false,
|
||||
context: null,
|
||||
};
|
||||
return {
|
||||
items: output,
|
||||
hasMore: false,
|
||||
context: null,
|
||||
};
|
||||
} catch(error) {
|
||||
throw this.fsErrorToJsError_(error);
|
||||
}
|
||||
}
|
||||
|
||||
async get(path, options) {
|
||||
@ -81,7 +92,7 @@ class FileApiDriverLocal {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code == 'ENOENT') return null;
|
||||
throw error;
|
||||
throw this.fsErrorToJsError_(error);
|
||||
}
|
||||
|
||||
return output;
|
||||
@ -99,7 +110,7 @@ class FileApiDriverLocal {
|
||||
|
||||
mkdirp(path, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
reject(this.fsErrorToJsError_(error));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
@ -112,7 +123,7 @@ class FileApiDriverLocal {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(path, content, function(error) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
reject(this.fsErrorToJsError_(error));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
@ -128,7 +139,7 @@ class FileApiDriverLocal {
|
||||
// File doesn't exist - it's fine
|
||||
resolve();
|
||||
} else {
|
||||
reject(error);
|
||||
reject(this.fsErrorToJsError_(error));
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
@ -152,7 +163,7 @@ class FileApiDriverLocal {
|
||||
await time.sleep(1);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
throw this.fsErrorToJsError_(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,12 @@ class Folder extends BaseItem {
|
||||
return Folder.save(folder, { isNew: true });
|
||||
}
|
||||
|
||||
static save(o, options = null) {
|
||||
static async save(o, options = null) {
|
||||
if (options && options.duplicateCheck === true && o.title) {
|
||||
let existingFolder = await Folder.loadByTitle(o.title);
|
||||
if (existingFolder) throw new Error(_('A notebook with this title already exists: "%s"', o.title));
|
||||
}
|
||||
|
||||
return super.save(o, options).then((folder) => {
|
||||
this.dispatch({
|
||||
type: 'FOLDERS_UPDATE_ONE',
|
||||
|
@ -56,28 +56,48 @@ class Note extends BaseItem {
|
||||
if (!options) options = {};
|
||||
if (!options.orderBy) options.orderBy = 'updated_time';
|
||||
if (!options.orderByDir) options.orderByDir = 'DESC';
|
||||
if (!options.conditions) options.conditions = [];
|
||||
if (!options.conditionsParams) options.conditionsParams = [];
|
||||
if (!options.fields) options.fields = this.previewFields();
|
||||
|
||||
options.conditions.push('is_conflict = 0');
|
||||
|
||||
options.conditions.push('parent_id = ?');
|
||||
options.conditionsParams.push(parentId);
|
||||
|
||||
let sql = 'SELECT ' + this.previewFieldsSql() + ' FROM notes WHERE is_conflict = 0 AND parent_id = ?';
|
||||
let params = [parentId];
|
||||
if (options.itemTypes && options.itemTypes.length) {
|
||||
if (options.itemTypes.indexOf('note') >= 0 && options.itemTypes.indexOf('todo') >= 0) {
|
||||
// Fetch everything
|
||||
} else if (options.itemTypes.indexOf('note') >= 0) {
|
||||
sql += ' AND is_todo = 0';
|
||||
options.conditions.push('is_todo = 0');
|
||||
} else if (options.itemTypes.indexOf('todo') >= 0) {
|
||||
sql += ' AND is_todo = 1';
|
||||
options.conditions.push('is_todo = 1');
|
||||
}
|
||||
}
|
||||
|
||||
if (options.titlePattern) {
|
||||
let pattern = options.titlePattern.replace(/\*/g, '%');
|
||||
sql += ' AND title LIKE ?';
|
||||
params.push(pattern);
|
||||
}
|
||||
return this.search(options);
|
||||
|
||||
let query = this.applySqlOptions(options, sql, params);
|
||||
// let sql = 'SELECT ' + this.previewFieldsSql() + ' FROM notes WHERE is_conflict = 0 AND parent_id = ?';
|
||||
// let params = [parentId];
|
||||
// if (options.itemTypes && options.itemTypes.length) {
|
||||
// if (options.itemTypes.indexOf('note') >= 0 && options.itemTypes.indexOf('todo') >= 0) {
|
||||
// // Fetch everything
|
||||
// } else if (options.itemTypes.indexOf('note') >= 0) {
|
||||
// sql += ' AND is_todo = 0';
|
||||
// } else if (options.itemTypes.indexOf('todo') >= 0) {
|
||||
// sql += ' AND is_todo = 1';
|
||||
// }
|
||||
// }
|
||||
|
||||
return this.modelSelectAll(query.sql, query.params);
|
||||
// if (options.titlePattern) {
|
||||
// let pattern = options.titlePattern.replace(/\*/g, '%');
|
||||
// sql += ' AND title LIKE ?';
|
||||
// params.push(pattern);
|
||||
// }
|
||||
|
||||
// let query = this.applySqlOptions(options, sql, params);
|
||||
|
||||
// return this.modelSelectAll(query.sql, query.params);
|
||||
}
|
||||
|
||||
static preview(noteId) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { BaseModel } from 'lib/base-model.js';
|
||||
import { Database } from 'lib/database.js';
|
||||
import { BaseItem } from 'lib/models/base-item.js';
|
||||
import { Note } from 'lib/models/note.js';
|
||||
import { time } from 'lib/time-utils.js';
|
||||
import lodash from 'lodash';
|
||||
|
||||
@ -37,6 +38,19 @@ class Tag extends BaseItem {
|
||||
return output;
|
||||
}
|
||||
|
||||
static async notes(tagId) {
|
||||
let noteIds = await this.tagNoteIds(tagId);
|
||||
if (!noteIds.length) return [];
|
||||
|
||||
let noteIdsSql = noteIds.join('","');
|
||||
noteIdsSql = '"' + noteIdsSql + '"';
|
||||
let options = {
|
||||
conditions: ['id IN (' + noteIdsSql + ')'],
|
||||
};
|
||||
|
||||
return Note.search(options);
|
||||
}
|
||||
|
||||
static async addNote(tagId, noteId) {
|
||||
let hasIt = await this.hasNote(tagId, noteId);
|
||||
if (hasIt) return;
|
||||
|
Loading…
Reference in New Issue
Block a user