mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	deleting attributes, closes #34
This commit is contained in:
		
							
								
								
									
										1
									
								
								db/migrations/0073__add_isDeleted_to_attributes.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								db/migrations/0073__add_isDeleted_to_attributes.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | ALTER TABLE attributes ADD COLUMN isDeleted INT NOT NULL DEFAULT 0; | ||||||
| @@ -86,7 +86,8 @@ CREATE TABLE IF NOT EXISTS "attributes" | |||||||
|   name TEXT NOT NULL, |   name TEXT NOT NULL, | ||||||
|   value TEXT, |   value TEXT, | ||||||
|   dateCreated TEXT NOT NULL, |   dateCreated TEXT NOT NULL, | ||||||
|   dateModified TEXT NOT NULL |   dateModified TEXT NOT NULL, | ||||||
|  |   isDeleted INT NOT NULL | ||||||
| ); | ); | ||||||
| CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` ( | CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` ( | ||||||
|   `entityName`, |   `entityName`, | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ class Note extends Entity { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getAttributes() { |     async getAttributes() { | ||||||
|         return this.repository.getEntities("SELECT * FROM attributes WHERE noteId = ?", [this.noteId]); |         return this.repository.getEntities("SELECT * FROM attributes WHERE noteId = ? AND isDeleted = 0", [this.noteId]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getAttribute(name) { |     async getAttribute(name) { | ||||||
|   | |||||||
| @@ -26,6 +26,19 @@ const attributesDialog = (function() { | |||||||
|             setTimeout(() => $(".attribute-name:last").focus(), 100); |             setTimeout(() => $(".attribute-name:last").focus(), 100); | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         this.deleteAttribute = function(data, event) { | ||||||
|  |             const attr = self.getTargetAttribute(event); | ||||||
|  |             const attrData = attr(); | ||||||
|  |  | ||||||
|  |             if (attrData) { | ||||||
|  |                 attrData.isDeleted = 1; | ||||||
|  |  | ||||||
|  |                 attr(attrData); | ||||||
|  |  | ||||||
|  |                 addLastEmptyRow(); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         function isValid() { |         function isValid() { | ||||||
|             for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) { |             for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) { | ||||||
|                 if (self.isEmptyName(i)) { |                 if (self.isEmptyName(i)) { | ||||||
| @@ -65,26 +78,25 @@ const attributesDialog = (function() { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         function addLastEmptyRow() { |         function addLastEmptyRow() { | ||||||
|             const attrs = self.attributes(); |             const attrs = self.attributes().filter(attr => attr().isDeleted === 0); | ||||||
|             const last = attrs.length === 0 ? null : attrs[attrs.length - 1](); |             const last = attrs.length === 0 ? null : attrs[attrs.length - 1](); | ||||||
|  |  | ||||||
|             if (!last || last.name.trim() !== "" || last.value !== "") { |             if (!last || last.name.trim() !== "" || last.value !== "") { | ||||||
|                 self.attributes.push(ko.observable({ |                 self.attributes.push(ko.observable({ | ||||||
|                     attributeId: '', |                     attributeId: '', | ||||||
|                     name: '', |                     name: '', | ||||||
|                     value: '' |                     value: '', | ||||||
|  |                     isDeleted: 0 | ||||||
|                 })); |                 })); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.attributeChanged = function (row) { |         this.attributeChanged = function (data, event) { | ||||||
|             addLastEmptyRow(); |             addLastEmptyRow(); | ||||||
|  |  | ||||||
|             for (const attr of self.attributes()) { |             const attr = self.getTargetAttribute(event); | ||||||
|                 if (row.attributeId === attr().attributeId) { |  | ||||||
|             attr.valueHasMutated(); |             attr.valueHasMutated(); | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         this.isNotUnique = function(index) { |         this.isNotUnique = function(index) { | ||||||
| @@ -109,6 +121,13 @@ const attributesDialog = (function() { | |||||||
|             const cur = self.attributes()[index](); |             const cur = self.attributes()[index](); | ||||||
|  |  | ||||||
|             return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); |             return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         this.getTargetAttribute = function(event) { | ||||||
|  |             const context = ko.contextFor(event.target); | ||||||
|  |             const index = context.$index(); | ||||||
|  |  | ||||||
|  |             return self.attributes()[index]; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ const attributes = require('../../services/attributes'); | |||||||
| router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const noteId = req.params.noteId; |     const noteId = req.params.noteId; | ||||||
|  |  | ||||||
|     res.send(await sql.getRows("SELECT * FROM attributes WHERE noteId = ? ORDER BY dateCreated", [noteId])); |     res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY dateCreated", [noteId])); | ||||||
| })); | })); | ||||||
|  |  | ||||||
| router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
| @@ -23,10 +23,15 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, | |||||||
|     await sql.doInTransaction(async () => { |     await sql.doInTransaction(async () => { | ||||||
|         for (const attr of attributes) { |         for (const attr of attributes) { | ||||||
|             if (attr.attributeId) { |             if (attr.attributeId) { | ||||||
|                 await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ? WHERE attributeId = ?", |                 await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ? WHERE attributeId = ?", | ||||||
|                     [attr.name, attr.value, now, attr.attributeId]); |                     [attr.name, attr.value, now, attr.isDeleted, attr.attributeId]); | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|  |                 // if it was "created" and then immediatelly deleted, we just don't create it at all | ||||||
|  |                 if (attr.isDeleted) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 attr.attributeId = utils.newAttributeId(); |                 attr.attributeId = utils.newAttributeId(); | ||||||
|  |  | ||||||
|                 await sql.insert("attributes", { |                 await sql.insert("attributes", { | ||||||
| @@ -35,7 +40,8 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, | |||||||
|                    name: attr.name, |                    name: attr.name, | ||||||
|                    value: attr.value, |                    value: attr.value, | ||||||
|                    dateCreated: now, |                    dateCreated: now, | ||||||
|                    dateModified: now |                    dateModified: now, | ||||||
|  |                    isDeleted: false | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -43,11 +49,11 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, | |||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     res.send(await sql.getRows("SELECT * FROM attributes WHERE noteId = ? ORDER BY dateCreated", [noteId])); |     res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY dateCreated", [noteId])); | ||||||
| })); | })); | ||||||
|  |  | ||||||
| router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const names = await sql.getColumn("SELECT DISTINCT name FROM attributes"); |     const names = await sql.getColumn("SELECT DISTINCT name FROM attributes WHERE isDeleted = 0"); | ||||||
|  |  | ||||||
|     for (const attr of attributes.BUILTIN_ATTRIBUTES) { |     for (const attr of attributes.BUILTIN_ATTRIBUTES) { | ||||||
|         if (!names.includes(attr)) { |         if (!names.includes(attr)) { | ||||||
| @@ -63,7 +69,7 @@ router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) = | |||||||
| router.get('/attributes/values/:attributeName', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/attributes/values/:attributeName', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const attributeName = req.params.attributeName; |     const attributeName = req.params.attributeName; | ||||||
|  |  | ||||||
|     const values = await sql.getColumn("SELECT DISTINCT value FROM attributes WHERE name = ? AND value != '' ORDER BY value", [attributeName]); |     const values = await sql.getColumn("SELECT DISTINCT value FROM attributes WHERE isDeleted = 0 AND name = ? AND value != '' ORDER BY value", [attributeName]); | ||||||
|  |  | ||||||
|     res.send(values); |     res.send(values); | ||||||
| })); | })); | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 72; | const APP_DB_VERSION = 73; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     app_version: packageJson.version, |     app_version: packageJson.version, | ||||||
|   | |||||||
| @@ -13,7 +13,10 @@ async function getNoteAttributeMap(noteId) { | |||||||
|  |  | ||||||
| async function getNoteIdWithAttribute(name, value) { | async function getNoteIdWithAttribute(name, value) { | ||||||
|     return await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)  |     return await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)  | ||||||
|           WHERE notes.isDeleted = 0 AND attributes.name = ? AND attributes.value = ?`, [name, value]); |           WHERE notes.isDeleted = 0 | ||||||
|  |                 AND attributes.isDeleted = 0 | ||||||
|  |                 AND attributes.name = ?  | ||||||
|  |                 AND attributes.value = ?`, [name, value]); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function getNotesWithAttribute(dataKey, name, value) { | async function getNotesWithAttribute(dataKey, name, value) { | ||||||
| @@ -23,11 +26,11 @@ async function getNotesWithAttribute(dataKey, name, value) { | |||||||
|  |  | ||||||
|     if (value !== undefined) { |     if (value !== undefined) { | ||||||
|         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)  |         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)  | ||||||
|           WHERE notes.isDeleted = 0 AND attributes.name = ? AND attributes.value = ?`, [name, value]); |           WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? AND attributes.value = ?`, [name, value]); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)  |         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)  | ||||||
|           WHERE notes.isDeleted = 0 AND attributes.name = ?`, [name]); |           WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ?`, [name]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return notes; |     return notes; | ||||||
| @@ -41,7 +44,7 @@ async function getNoteWithAttribute(dataKey, name, value) { | |||||||
|  |  | ||||||
| async function getNoteIdsWithAttribute(name) { | async function getNoteIdsWithAttribute(name) { | ||||||
|     return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN attributes USING(noteId)  |     return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN attributes USING(noteId)  | ||||||
|           WHERE notes.isDeleted = 0 AND attributes.name = ?`, [name]); |           WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? AND attributes.isDeleted = 0`, [name]); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function createAttribute(noteId, name, value = null, sourceId = null) { | async function createAttribute(noteId, name, value = null, sourceId = null) { | ||||||
| @@ -54,7 +57,8 @@ async function createAttribute(noteId, name, value = null, sourceId = null) { | |||||||
|         name: name, |         name: name, | ||||||
|         value: value, |         value: value, | ||||||
|         dateModified: now, |         dateModified: now, | ||||||
|         dateCreated: now |         dateCreated: now, | ||||||
|  |         isDeleted: false | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     await sync_table.addAttributeSync(attributeId, sourceId); |     await sync_table.addAttributeSync(attributeId, sourceId); | ||||||
|   | |||||||
| @@ -376,7 +376,7 @@ | |||||||
|     <div id="attributes-dialog" title="Note attributes" style="display: none; padding: 20px;"> |     <div id="attributes-dialog" title="Note attributes" style="display: none; padding: 20px;"> | ||||||
|       <form data-bind="submit: save"> |       <form data-bind="submit: save"> | ||||||
|       <div style="text-align: center"> |       <div style="text-align: center"> | ||||||
|         <button class="btn btn-large" style="width: 200px;" id="save-attributes-button" type="submit">Save <kbd>enter</kbd></button> |         <button class="btn btn-large" style="width: 200px;" id="save-attributes-button" type="submit">Save changes <kbd>enter</kbd></button> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div style="height: 97%; overflow: auto"> |       <div style="height: 97%; overflow: auto"> | ||||||
| @@ -386,10 +386,11 @@ | |||||||
|               <th>ID</th> |               <th>ID</th> | ||||||
|               <th>Name</th> |               <th>Name</th> | ||||||
|               <th>Value</th> |               <th>Value</th> | ||||||
|  |               <th></th> | ||||||
|             </tr> |             </tr> | ||||||
|           </thead> |           </thead> | ||||||
|           <tbody data-bind="foreach: attributes"> |           <tbody data-bind="foreach: attributes"> | ||||||
|             <tr> |            <tr data-bind="if: isDeleted == 0"> | ||||||
|               <td data-bind="text: attributeId"></td> |               <td data-bind="text: attributeId"></td> | ||||||
|               <td> |               <td> | ||||||
|                 <!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event --> |                 <!-- Change to valueUpdate: blur is necessary because jQuery UI autocomplete hijacks change event --> | ||||||
| @@ -400,6 +401,9 @@ | |||||||
|               <td> |               <td> | ||||||
|                 <input type="text" class="attribute-value" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }" style="width: 300px"/> |                 <input type="text" class="attribute-value" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.attributeChanged }" style="width: 300px"/> | ||||||
|               </td> |               </td> | ||||||
|  |               <td title="Delete" style="padding: 13px;"> | ||||||
|  |                 <span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteAttribute"></span> | ||||||
|  |               </td> | ||||||
|             </tr> |             </tr> | ||||||
|           </tbody> |           </tbody> | ||||||
|         </table> |         </table> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user