mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	redesign of createNote APIs, WIP
This commit is contained in:
		| @@ -79,6 +79,10 @@ class Attribute extends Entity { | |||||||
|  |  | ||||||
|     async beforeSaving() { |     async beforeSaving() { | ||||||
|         if (!this.value) { |         if (!this.value) { | ||||||
|  |             if (this.type === 'relation') { | ||||||
|  |                 throw new Error(`Cannot save relation ${this.name} since it does not target any note.`); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             // null value isn't allowed |             // null value isn't allowed | ||||||
|             this.value = ""; |             this.value = ""; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -472,6 +472,32 @@ class Note extends Entity { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @return {Promise<Attribute>} | ||||||
|  |      */ | ||||||
|  |     async createAttribute(type, name, value = "") { | ||||||
|  |         const attr = new Attribute({ | ||||||
|  |             noteId: this.noteId, | ||||||
|  |             type: type, | ||||||
|  |             name: name, | ||||||
|  |             value: value | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         await attr.save(); | ||||||
|  |  | ||||||
|  |         this.invalidateAttributeCache(); | ||||||
|  |  | ||||||
|  |         return attr; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async createLabel(name, value = "") { | ||||||
|  |         return await this.createAttribute(LABEL, name, value); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async createRelation(name, targetNoteId) { | ||||||
|  |         return await this.createAttribute(RELATION, name, targetNoteId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param {string} name - label name |      * @param {string} name - label name | ||||||
|      * @returns {Promise<boolean>} true if label exists (including inherited) |      * @returns {Promise<boolean>} true if label exists (including inherited) | ||||||
|   | |||||||
| @@ -16,29 +16,14 @@ const protectedSessionService = require('../services/protected_session'); | |||||||
| const log = require('../services/log'); | const log = require('../services/log'); | ||||||
| const noteRevisionService = require('../services/note_revisions'); | const noteRevisionService = require('../services/note_revisions'); | ||||||
|  |  | ||||||
| async function getNewNotePosition(parentNoteId, noteData) { | async function getNewNotePosition(parentNoteId) { | ||||||
|     let newNotePos = 0; |     const maxNotePos = await sql.getValue(` | ||||||
|  |             SELECT MAX(notePosition)  | ||||||
|  |             FROM branches  | ||||||
|  |             WHERE parentNoteId = ?  | ||||||
|  |               AND isDeleted = 0`, [parentNoteId]); | ||||||
|  |  | ||||||
|     if (noteData.target === 'into') { |     return maxNotePos === null ? 0 : maxNotePos + 10; | ||||||
|         const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentNoteId]); |  | ||||||
|  |  | ||||||
|         newNotePos = maxNotePos === null ? 0 : maxNotePos + 10; |  | ||||||
|     } |  | ||||||
|     else if (noteData.target === 'after') { |  | ||||||
|         const afterNote = await sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [noteData.target_branchId]); |  | ||||||
|  |  | ||||||
|         newNotePos = afterNote.notePosition + 10; |  | ||||||
|  |  | ||||||
|         // not updating utcDateModified to avoig having to sync whole rows |  | ||||||
|         await sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0', |  | ||||||
|             [parentNoteId, afterNote.notePosition]); |  | ||||||
|  |  | ||||||
|         await syncTableService.addNoteReorderingSync(parentNoteId); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         throw new Error('Unknown target: ' + noteData.target); |  | ||||||
|     } |  | ||||||
|     return newNotePos; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| async function triggerChildNoteCreated(childNote, parentNote) { | async function triggerChildNoteCreated(childNote, parentNote) { | ||||||
| @@ -49,31 +34,12 @@ async function triggerNoteTitleChanged(note) { | |||||||
|     await eventService.emit(eventService.NOTE_TITLE_CHANGED, note); |     await eventService.emit(eventService.NOTE_TITLE_CHANGED, note); | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | function deriveTypeAndMime(noteData, parentNote) { | ||||||
|  * FIXME: noteData has mandatory property "target", it might be better to add it as parameter to reflect this |  | ||||||
|  */ |  | ||||||
| async function createNewNote(parentNoteId, noteData) { |  | ||||||
|     let newNotePos; |  | ||||||
|  |  | ||||||
|     if (noteData.notePosition !== undefined) { |  | ||||||
|         newNotePos = noteData.notePosition; |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         newNotePos = await getNewNotePosition(parentNoteId, noteData); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const parentNote = await repository.getNote(parentNoteId); |  | ||||||
|  |  | ||||||
|     if (!parentNote) { |  | ||||||
|         throw new Error(`Parent note ${parentNoteId} not found.`); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (!noteData.type) { |     if (!noteData.type) { | ||||||
|         if (parentNote.type === 'text' || parentNote.type === 'code') { |         if (parentNote.type === 'text' || parentNote.type === 'code') { | ||||||
|             noteData.type = parentNote.type; |             noteData.type = parentNote.type; | ||||||
|             noteData.mime = parentNote.mime; |             noteData.mime = parentNote.mime; | ||||||
|         } |         } else { | ||||||
|         else { |  | ||||||
|             // inheriting note type makes sense only for text and code |             // inheriting note type makes sense only for text and code | ||||||
|             noteData.type = 'text'; |             noteData.type = 'text'; | ||||||
|             noteData.mime = 'text/html'; |             noteData.mime = 'text/html'; | ||||||
| @@ -83,54 +49,83 @@ async function createNewNote(parentNoteId, noteData) { | |||||||
|     if (!noteData.mime) { |     if (!noteData.mime) { | ||||||
|         if (noteData.type === 'text') { |         if (noteData.type === 'text') { | ||||||
|             noteData.mime = 'text/html'; |             noteData.mime = 'text/html'; | ||||||
|         } |         } else if (noteData.type === 'code') { | ||||||
|         else if (noteData.type === 'code') { |  | ||||||
|             noteData.mime = 'text/plain'; |             noteData.mime = 'text/plain'; | ||||||
|         } |         } else if (noteData.type === 'relation-map' || noteData.type === 'search') { | ||||||
|         else if (noteData.type === 'relation-map' || noteData.type === 'search') { |  | ||||||
|             noteData.mime = 'application/json'; |             noteData.mime = 'application/json'; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     noteData.type = noteData.type || parentNote.type; |     noteData.type = noteData.type || parentNote.type; | ||||||
|     noteData.mime = noteData.mime || parentNote.mime; |     noteData.mime = noteData.mime || parentNote.mime; | ||||||
|  | } | ||||||
|  |  | ||||||
|     const note = await new Note({ | async function copyChildAttributes(parentNote, childNote) { | ||||||
|         noteId: noteData.noteId, // optionally can force specific noteId |  | ||||||
|         title: noteData.title, |  | ||||||
|         isProtected: noteData.isProtected, |  | ||||||
|         type: noteData.type || 'text', |  | ||||||
|         mime: noteData.mime || 'text/html' |  | ||||||
|     }).save(); |  | ||||||
|  |  | ||||||
|     if (note.isStringNote() || this.type === 'render') { // render to just make sure it's not null |  | ||||||
|         noteData.content = noteData.content || ""; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     await note.setContent(noteData.content); |  | ||||||
|  |  | ||||||
|     const branch = await new Branch({ |  | ||||||
|         noteId: note.noteId, |  | ||||||
|         parentNoteId: parentNoteId, |  | ||||||
|         notePosition: newNotePos, |  | ||||||
|         prefix: noteData.prefix, |  | ||||||
|         isExpanded: !!noteData.isExpanded |  | ||||||
|     }).save(); |  | ||||||
|  |  | ||||||
|     for (const attr of await parentNote.getAttributes()) { |     for (const attr of await parentNote.getAttributes()) { | ||||||
|         if (attr.name.startsWith("child:")) { |         if (attr.name.startsWith("child:")) { | ||||||
|             await new Attribute({ |             await new Attribute({ | ||||||
|                noteId: note.noteId, |                 noteId: childNote.noteId, | ||||||
|                type: attr.type, |                 type: attr.type, | ||||||
|                name: attr.name.substr(6), |                 name: attr.name.substr(6), | ||||||
|                value: attr.value, |                 value: attr.value, | ||||||
|                position: attr.position, |                 position: attr.position, | ||||||
|                isInheritable: attr.isInheritable |                 isInheritable: attr.isInheritable | ||||||
|             }).save(); |             }).save(); | ||||||
|  |  | ||||||
|             note.invalidateAttributeCache(); |             childNote.invalidateAttributeCache(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Following object properties are mandatory: | ||||||
|  |  * - {string} parentNoteId | ||||||
|  |  * - {string} title | ||||||
|  |  * - {*} content | ||||||
|  |  * - {string} type | ||||||
|  |  * | ||||||
|  |  * Following are optional (have defaults) | ||||||
|  |  * - {string} mime - value is derived from default mimes for type | ||||||
|  |  * - {boolean} isProtected - default is false | ||||||
|  |  * - {boolean} isExpanded - default is false | ||||||
|  |  * - {string} prefix - default is empty string | ||||||
|  |  * - {integer} notePosition - default is last existing notePosition in a parent + 10 | ||||||
|  |  * | ||||||
|  |  * @param params | ||||||
|  |  * @return {Promise<{note: Entity, branch: Entity}>} | ||||||
|  |  */ | ||||||
|  | async function createNewNote(params) { | ||||||
|  |     const parentNote = await repository.getNote(params.parentNoteId); | ||||||
|  |  | ||||||
|  |     if (!parentNote) { | ||||||
|  |         throw new Error(`Parent note ${params.parentNoteId} not found.`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!params.title || params.title.trim().length === 0) { | ||||||
|  |         throw new Error(`Note title must not be empty`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     deriveTypeAndMime(params, parentNote); | ||||||
|  |  | ||||||
|  |     const note = await new Note({ | ||||||
|  |         noteId: params.noteId, // optionally can force specific noteId | ||||||
|  |         title: params.title, | ||||||
|  |         isProtected: !!params.isProtected, | ||||||
|  |         type: params.type, | ||||||
|  |         mime: params.mime | ||||||
|  |     }).save(); | ||||||
|  |  | ||||||
|  |     await note.setContent(params.content); | ||||||
|  |  | ||||||
|  |     const branch = await new Branch({ | ||||||
|  |         noteId: note.noteId, | ||||||
|  |         parentNoteId: params.parentNoteId, | ||||||
|  |         notePosition: params.notePosition !== undefined ? params.notePosition : await getNewNotePosition(params.parentNoteId), | ||||||
|  |         prefix: params.prefix, | ||||||
|  |         isExpanded: !!params.isExpanded | ||||||
|  |     }).save(); | ||||||
|  |  | ||||||
|  |     await copyChildAttributes(parentNote, note); | ||||||
|  |  | ||||||
|     await triggerNoteTitleChanged(note); |     await triggerNoteTitleChanged(note); | ||||||
|     await triggerChildNoteCreated(note, parentNote); |     await triggerChildNoteCreated(note, parentNote); | ||||||
| @@ -141,13 +136,41 @@ async function createNewNote(parentNoteId, noteData) { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // methods below should be probably just backend API methods | ||||||
|  | async function createJsonNote(parentNoteId, title, content = {}, params = {}) { | ||||||
|  |     params.parentNoteId = parentNoteId; | ||||||
|  |     params.title = title; | ||||||
|  |  | ||||||
|  |     params.type = "code"; | ||||||
|  |     params.mime = "application/json"; | ||||||
|  |  | ||||||
|  |     params.content = JSON.stringify(content, null, '\t'); | ||||||
|  |  | ||||||
|  |     return await createNewNote(params); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function createTextNote(parentNoteId, title, content = "", params = {}) { | ||||||
|  |     params.parentNoteId = parentNoteId; | ||||||
|  |     params.title = title; | ||||||
|  |  | ||||||
|  |     params.type = "text"; | ||||||
|  |     params.mime = "text/html"; | ||||||
|  |  | ||||||
|  |     params.content = content; | ||||||
|  |  | ||||||
|  |     return await createNewNote(params); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @deprecated | ||||||
|  |  */ | ||||||
| async function createNote(parentNoteId, title, content = "", extraOptions = {}) { | async function createNote(parentNoteId, title, content = "", extraOptions = {}) { | ||||||
|     if (!parentNoteId) throw new Error("Empty parentNoteId"); |     if (!parentNoteId) throw new Error("Empty parentNoteId"); | ||||||
|     if (!title) throw new Error("Empty title"); |     if (!title) throw new Error("Empty title"); | ||||||
|  |  | ||||||
|     const noteData = { |     const noteData = { | ||||||
|         title: title, |         title: title, | ||||||
|         content: extraOptions.json ? JSON.stringify(content, null, '\t') : content, |         content: content, | ||||||
|         target: 'into', |         target: 'into', | ||||||
|         noteId: extraOptions.noteId, |         noteId: extraOptions.noteId, | ||||||
|         isProtected: !!extraOptions.isProtected, |         isProtected: !!extraOptions.isProtected, | ||||||
| @@ -158,12 +181,7 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) | |||||||
|         notePosition: extraOptions.notePosition |         notePosition: extraOptions.notePosition | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     if (extraOptions.json && !noteData.type) { |     const {note, branch} = await createNewNote(parentNoteId, title, noteData); | ||||||
|         noteData.type = "code"; |  | ||||||
|         noteData.mime = "application/json"; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const {note, branch} = await createNewNote(parentNoteId, noteData); |  | ||||||
|  |  | ||||||
|     for (const attr of extraOptions.attributes || []) { |     for (const attr of extraOptions.attributes || []) { | ||||||
|         await attributeService.createAttribute({ |         await attributeService.createAttribute({ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user