diff --git a/package.json b/package.json index 1e6f0dae2..b0ac5adad 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "start-forge": "electron-forge start", "package-forge": "electron-forge package", "make-forge": "electron-forge make", - "publish-forge": "electron-forge publish" + "publish-forge": "electron-forge publish", + "build-docs": "jsdoc src/entities/*.js src/services/backend_script_api.js" }, "dependencies": { "async-mutex": "^0.1.3", diff --git a/src/services/attributes.js b/src/services/attributes.js index 41c15ed68..9f87ab462 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -29,18 +29,16 @@ const BUILTIN_ATTRIBUTES = [ ]; async function getNotesWithLabel(name, value) { - let notes; + let valueCondition = ""; + let params = [name]; if (value !== undefined) { - notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId) - WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? AND attributes.value = ?`, [name, value]); - } - else { - notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId) - WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ?`, [name]); + valueCondition = " AND attributes.value = ?"; + params.push(value); } - return notes; + return await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId) + WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? ${valueCondition} ORDER BY position`, params); } async function getNoteWithLabel(name, value) { diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js new file mode 100644 index 000000000..ee592cfab --- /dev/null +++ b/src/services/backend_script_api.js @@ -0,0 +1,219 @@ +const log = require('./log'); +const noteService = require('./notes'); +const sql = require('./sql'); +const utils = require('./utils'); +const dateUtils = require('./date_utils'); +const attributeService = require('./attributes'); +const dateNoteService = require('./date_notes'); +const treeService = require('./tree'); +const config = require('./config'); +const repository = require('./repository'); +const axios = require('axios'); +const cloningService = require('./cloning'); +const messagingService = require('./messaging'); + +/** + * @constructor + * @hideconstructor + */ +function BackendScriptApi(startNote, currentNote, originEntity) { + /** @param {Note} */ + this.startNote = startNote; + /** @param {Note} */ + this.currentNote = currentNote; + /** @param {Entity} */ + this.originEntity = originEntity; + + this.axios = axios; + + this.utils = { + unescapeHtml: utils.unescapeHtml, + isoDateTimeStr: dateUtils.dateStr, + isoDateStr: date => dateUtils.dateStr(date).substr(0, 10) + }; + + /** + * Instance name identifies particular Trilium instance. It can be useful for scripts + * if some action needs to happen on only one specific instance. + * + * @returns {string|null} + */ + this.getInstanceName = () => config.General ? config.General.instanceName : null; + + /** + * @method + * @param {string} noteId + * @returns {Promise} + */ + this.getNote = repository.getNote; + + /** + * @method + * @param {string} branchId + * @returns {Promise} + */ + this.getBranch = repository.getBranch; + + /** + * @method + * @param {string} attributeId + * @returns {Promise} + */ + this.getAttribute = repository.getAttribute; + + /** + * @method + * @param {string} imageId + * @returns {Promise} + */ + this.getImage = repository.getImage; + + /** + * Retrieves first entity from the SQL's result set. + * + * @method + * @param {string} SQL query + * @param {Array.<*>} array of params + * @returns {Promise} + */ + this.getEntity = repository.getEntity; + + /** + * @method + * @param {string} SQL query + * @param {Array.<*>} array of params + * @returns {Promise>} + */ + this.getEntities = repository.getEntities; + + /** + * Retrieves notes with given label name & value + * + * @method + * @param {string} name - attribute name + * @param {string} [value] - attribute value + * @returns {Promise>} + */ + this.getNotesWithLabel = attributeService.getNotesWithLabel; + + /** + * Retrieves first note with given label name & value + * + * @method + * @param {string} name - attribute name + * @param {string} [value] - attribute value + * @returns {Promise} + */ + this.getNoteWithLabel = attributeService.getNoteWithLabel; + + /** + * If there's no branch between note and parent note, create one. Otherwise do nothing. + * + * @method + * @param {string} noteId + * @param {string} parentNoteId + * @param {string} prefix - if branch will be create between note and parent note, set this prefix + * @returns {Promise} + */ + this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent; + + /** + * If there's a branch between note and parent note, remove it. Otherwise do nothing. + * + * @method + * @param {string} noteId + * @param {string} parentNoteId + * @returns {Promise} + */ + this.ensureNoteIsAbsentFromParent = cloningService.ensureNoteIsAbsentFromParent; + + /** + * Based on the value, either create or remove branch between note and parent note. + * + * @method + * @param {boolean} present - true if we want the branch to exist, false if we want it gone + * @param {string} noteId + * @param {string} parentNoteId + * @param {string} prefix - if branch will be create between note and parent note, set this prefix + * @returns {Promise} + */ + this.toggleNoteInParent = cloningService.toggleNoteInParent; + + /** + * @method + * + * @param {string} parentNoteId - create new note under this parent + * @param {string} title + * @param {string} [content] + * @params {object} [extraOptions] + * @returns {Promise} object contains attributes "note" and "branch" which contain newly created entities + */ + this.createNote = noteService.createNote; + + /** + * Log given message to trilium logs. + * + * @param message + */ + this.log = message => log.info(`Script "${currentNote.title}" (${currentNote.noteId}): ${message}`); + + /** + * Returns root note of the calendar. + * + * @method + * @returns {Promise} + */ + this.getRootCalendarNote = dateNoteService.getRootCalendarNote; + + /** + * Returns day note for given date (YYYY-MM-DD format). If such note doesn't exist, it is created. + * + * @method + * @param {string} date + * @returns {Promise} + */ + this.getDateNote = dateNoteService.getDateNote; + + /** + * @method + * @param {string} parentNoteId - this note's child notes will be sorted + * @returns Promise + */ + this.sortNotesAlphabetically = treeService.sortNotesAlphabetically; + + /** + * This method finds note by its noteId and prefix and either sets it to the given parentNoteId + * or removes the branch (if parentNoteId is not given). + * + * This method looks similar to toggleNoteInParent() but differs because we're looking up branch by prefix. + * + * @method + * @param {string} noteId + * @param {string} prefix + * @param {string} [parentNoteId] + */ + this.setNoteToParent = treeService.setNoteToParent; + + /** + * This functions wraps code which is supposed to be running in transaction. If transaction already + * exists, then we'll use that transaction. + * + * This method is required only when script has label manualTransactionHandling, all other scripts are + * transactional by default. + * + * @method + * @params {function} func + * @returns {Promise} result of func callback + */ + this.transactional = sql.transactional; + + /** + * Trigger tree refresh in all connected clients. This is required when some tree change happens in + * the backend. + * + * @returns {Promise} + */ + this.refreshTree = () => messagingService.sendMessageToAllClients({ type: 'refresh-tree' }); +} + +module.exports = BackendScriptApi; \ No newline at end of file diff --git a/src/services/script_context.js b/src/services/script_context.js index 6f8249c9e..9d88817cd 100644 --- a/src/services/script_context.js +++ b/src/services/script_context.js @@ -1,21 +1,10 @@ -const log = require('./log'); -const noteService = require('./notes'); -const sql = require('./sql'); const utils = require('./utils'); -const dateUtils = require('./date_utils'); -const attributeService = require('./attributes'); -const dateNoteService = require('./date_notes'); -const treeService = require('./tree'); -const config = require('./config'); -const repository = require('./repository'); -const axios = require('axios'); -const cloningService = require('./cloning'); -const messagingService = require('./messaging'); +const BackendScriptApi = require('./backend_script_api'); function ScriptContext(startNote, allNotes, originEntity = null) { this.modules = {}; this.notes = utils.toObject(allNotes, note => [note.noteId, note]); - this.apis = utils.toObject(allNotes, note => [note.noteId, new ScriptApi(startNote, note, originEntity)]); + this.apis = utils.toObject(allNotes, note => [note.noteId, new BackendScriptApi(startNote, note, originEntity)]); this.require = moduleNoteIds => { return moduleName => { const candidates = allNotes.filter(note => moduleNoteIds.includes(note.noteId)); @@ -30,51 +19,4 @@ function ScriptContext(startNote, allNotes, originEntity = null) { }; } -function ScriptApi(startNote, currentNote, originEntity) { - this.startNote = startNote; - this.currentNote = currentNote; - this.originEntity = originEntity; - - this.axios = axios; - - this.utils = { - unescapeHtml: utils.unescapeHtml, - isoDateTimeStr: dateUtils.dateStr, - isoDateStr: date => dateUtils.dateStr(date).substr(0, 10) - }; - - this.getInstanceName = () => config.General ? config.General.instanceName : null; - - this.getNote = repository.getNote; - this.getBranch = repository.getBranch; - this.getAttribute = repository.getAttribute; - this.getImage = repository.getImage; - this.getEntity = repository.getEntity; - this.getEntities = repository.getEntities; - - this.createAttribute = attributeService.createAttribute; - this.getNotesWithLabel = attributeService.getNotesWithLabel; - this.getNoteWithLabel = attributeService.getNoteWithLabel; - - this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent; - this.ensureNoteIsAbsentFromParent = cloningService.ensureNoteIsAbsentFromParent; - - this.toggleNoteInParent = cloningService.toggleNoteInParent; - - this.createNote = noteService.createNote; - - this.log = message => log.info(`Script ${currentNote.noteId}: ${message}`); - - this.getRootCalendarNote = dateNoteService.getRootCalendarNote; - this.getDateNote = dateNoteService.getDateNote; - - this.sortNotesAlphabetically = treeService.sortNotesAlphabetically; - - this.setNoteToParent = treeService.setNoteToParent; - - this.transactional = sql.transactional; - - this.refreshTree = () => messagingService.sendMessageToAllClients({ type: 'refresh-tree' }); -} - module.exports = ScriptContext; \ No newline at end of file diff --git a/src/services/tree.js b/src/services/tree.js index 34269a2b7..348e9bab4 100644 --- a/src/services/tree.js +++ b/src/services/tree.js @@ -111,6 +111,7 @@ async function setNoteToParent(noteId, prefix, parentNoteId) { } else { branch.parentNoteId = parentNoteId; + branch.prefix = prefix; } await branch.save();