mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	renaming attributes to labels, fixes #79
This commit is contained in:
		
							
								
								
									
										22
									
								
								db/migrations/0080__rename_attributes_to_labels.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								db/migrations/0080__rename_attributes_to_labels.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | create table labels | ||||||
|  | ( | ||||||
|  |   labelId  TEXT not null primary key, | ||||||
|  |   noteId       TEXT not null, | ||||||
|  |   name         TEXT not null, | ||||||
|  |   value        TEXT default '' not null, | ||||||
|  |   position     INT  default 0 not null, | ||||||
|  |   dateCreated  TEXT not null, | ||||||
|  |   dateModified TEXT not null, | ||||||
|  |   isDeleted    INT  not null | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | create index IDX_labels_name_value | ||||||
|  |   on labels (name, value); | ||||||
|  |  | ||||||
|  | create index IDX_labels_noteId | ||||||
|  |   on labels (noteId); | ||||||
|  |  | ||||||
|  | INSERT INTO labels (labelId, noteId, name, "value", "position", dateCreated, dateModified, isDeleted) | ||||||
|  |   SELECT attributeId, noteId, name, "value", "position", dateCreated, dateModified, isDeleted FROM attributes; | ||||||
|  |  | ||||||
|  | DROP TABLE attributes; | ||||||
| @@ -2,13 +2,13 @@ | |||||||
| 
 | 
 | ||||||
| const Entity = require('./entity'); | const Entity = require('./entity'); | ||||||
| 
 | 
 | ||||||
| class Attribute extends Entity { | class Label extends Entity { | ||||||
|     static get tableName() { return "attributes"; } |     static get tableName() { return "labels"; } | ||||||
|     static get primaryKeyName() { return "attributeId"; } |     static get primaryKeyName() { return "labelId"; } | ||||||
| 
 | 
 | ||||||
|     async getNote() { |     async getNote() { | ||||||
|         return this.repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); |         return this.repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = Attribute; | module.exports = Label; | ||||||
| @@ -48,30 +48,30 @@ class Note extends Entity { | |||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getAttributes() { |     async getLabels() { | ||||||
|         return this.repository.getEntities("SELECT * FROM attributes WHERE noteId = ? AND isDeleted = 0", [this.noteId]); |         return this.repository.getEntities("SELECT * FROM labels WHERE noteId = ? AND isDeleted = 0", [this.noteId]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // WARNING: this doesn't take into account the possibility to have multi-valued attributes! |     // WARNING: this doesn't take into account the possibility to have multi-valued labels! | ||||||
|     async getAttributeMap() { |     async getLabelMap() { | ||||||
|         const map = {}; |         const map = {}; | ||||||
|  |  | ||||||
|         for (const attr of await this.getAttributes()) { |         for (const attr of await this.getLabels()) { | ||||||
|             map[attr.name] = attr.value; |             map[attr.name] = attr.value; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return map; |         return map; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async hasAttribute(name) { |     async hasLabel(name) { | ||||||
|         const map = await this.getAttributeMap(); |         const map = await this.getLabelMap(); | ||||||
|  |  | ||||||
|         return map.hasOwnProperty(name); |         return map.hasOwnProperty(name); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // WARNING: this doesn't take into account the possibility to have multi-valued attributes! |     // WARNING: this doesn't take into account the possibility to have multi-valued labels! | ||||||
|     async getAttribute(name) { |     async getLabel(name) { | ||||||
|         return this.repository.getEntity("SELECT * FROM attributes WHERE noteId = ? AND name = ?", [this.noteId, name]); |         return this.repository.getEntity("SELECT * FROM labels WHERE noteId = ? AND name = ?", [this.noteId, name]); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async getRevisions() { |     async getRevisions() { | ||||||
|   | |||||||
| @@ -1,43 +1,43 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| const attributesDialog = (function() { | const labelsDialog = (function() { | ||||||
|     const $showDialogButton = $(".show-attributes-button"); |     const $showDialogButton = $(".show-labels-button"); | ||||||
|     const $dialog = $("#attributes-dialog"); |     const $dialog = $("#labels-dialog"); | ||||||
|     const $saveAttributesButton = $("#save-attributes-button"); |     const $saveLabelsButton = $("#save-labels-button"); | ||||||
|     const $attributesBody = $('#attributes-table tbody'); |     const $labelsBody = $('#labels-table tbody'); | ||||||
| 
 | 
 | ||||||
|     const attributesModel = new AttributesModel(); |     const labelsModel = new LabelsModel(); | ||||||
|     let attributeNames = []; |     let labelNames = []; | ||||||
| 
 | 
 | ||||||
|     function AttributesModel() { |     function LabelsModel() { | ||||||
|         const self = this; |         const self = this; | ||||||
| 
 | 
 | ||||||
|         this.attributes = ko.observableArray(); |         this.labels = ko.observableArray(); | ||||||
| 
 | 
 | ||||||
|         this.loadAttributes = async function() { |         this.loadLabels = async function() { | ||||||
|             const noteId = noteEditor.getCurrentNoteId(); |             const noteId = noteEditor.getCurrentNoteId(); | ||||||
| 
 | 
 | ||||||
|             const attributes = await server.get('notes/' + noteId + '/attributes'); |             const labels = await server.get('notes/' + noteId + '/labels'); | ||||||
| 
 | 
 | ||||||
|             self.attributes(attributes.map(ko.observable)); |             self.labels(labels.map(ko.observable)); | ||||||
| 
 | 
 | ||||||
|             addLastEmptyRow(); |             addLastEmptyRow(); | ||||||
| 
 | 
 | ||||||
|             attributeNames = await server.get('attributes/names'); |             labelNames = await server.get('labels/names'); | ||||||
| 
 | 
 | ||||||
|             // attribute might not be rendered immediatelly so could not focus
 |             // label might not be rendered immediatelly so could not focus
 | ||||||
|             setTimeout(() => $(".attribute-name:last").focus(), 100); |             setTimeout(() => $(".label-name:last").focus(), 100); | ||||||
| 
 | 
 | ||||||
|             $attributesBody.sortable({ |             $labelsBody.sortable({ | ||||||
|                 handle: '.handle', |                 handle: '.handle', | ||||||
|                 containment: $attributesBody, |                 containment: $labelsBody, | ||||||
|                 update: function() { |                 update: function() { | ||||||
|                     let position = 0; |                     let position = 0; | ||||||
| 
 | 
 | ||||||
|                     // we need to update positions by searching in the DOM, because order of the
 |                     // we need to update positions by searching in the DOM, because order of the
 | ||||||
|                     // attributes in the viewmodel (self.attributes()) stays the same
 |                     // labels in the viewmodel (self.labels()) stays the same
 | ||||||
|                     $attributesBody.find('input[name="position"]').each(function() { |                     $labelsBody.find('input[name="position"]').each(function() { | ||||||
|                         const attr = self.getTargetAttribute(this); |                         const attr = self.getTargetLabel(this); | ||||||
| 
 | 
 | ||||||
|                         attr().position = position++; |                         attr().position = position++; | ||||||
|                     }); |                     }); | ||||||
| @@ -45,8 +45,8 @@ const attributesDialog = (function() { | |||||||
|             }); |             }); | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         this.deleteAttribute = function(data, event) { |         this.deleteLabel = function(data, event) { | ||||||
|             const attr = self.getTargetAttribute(event.target); |             const attr = self.getTargetLabel(event.target); | ||||||
|             const attrData = attr(); |             const attrData = attr(); | ||||||
| 
 | 
 | ||||||
|             if (attrData) { |             if (attrData) { | ||||||
| @@ -59,7 +59,7 @@ const attributesDialog = (function() { | |||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         function isValid() { |         function isValid() { | ||||||
|             for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) { |             for (let attrs = self.labels(), i = 0; i < attrs.length; i++) { | ||||||
|                 if (self.isEmptyName(i)) { |                 if (self.isEmptyName(i)) { | ||||||
|                     return false; |                     return false; | ||||||
|                 } |                 } | ||||||
| @@ -72,7 +72,7 @@ const attributesDialog = (function() { | |||||||
|             // we need to defocus from input (in case of enter-triggered save) because value is updated
 |             // we need to defocus from input (in case of enter-triggered save) because value is updated
 | ||||||
|             // on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
 |             // on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
 | ||||||
|             // stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
 |             // stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
 | ||||||
|             $saveAttributesButton.focus(); |             $saveLabelsButton.focus(); | ||||||
| 
 | 
 | ||||||
|             if (!isValid()) { |             if (!isValid()) { | ||||||
|                 alert("Please fix all validation errors and try saving again."); |                 alert("Please fix all validation errors and try saving again."); | ||||||
| @@ -81,28 +81,28 @@ const attributesDialog = (function() { | |||||||
| 
 | 
 | ||||||
|             const noteId = noteEditor.getCurrentNoteId(); |             const noteId = noteEditor.getCurrentNoteId(); | ||||||
| 
 | 
 | ||||||
|             const attributesToSave = self.attributes() |             const labelsToSave = self.labels() | ||||||
|                 .map(attr => attr()) |                 .map(attr => attr()) | ||||||
|                 .filter(attr => attr.attributeId !== "" || attr.name !== ""); |                 .filter(attr => attr.labelId !== "" || attr.name !== ""); | ||||||
| 
 | 
 | ||||||
|             const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave); |             const labels = await server.put('notes/' + noteId + '/labels', labelsToSave); | ||||||
| 
 | 
 | ||||||
|             self.attributes(attributes.map(ko.observable)); |             self.labels(labels.map(ko.observable)); | ||||||
| 
 | 
 | ||||||
|             addLastEmptyRow(); |             addLastEmptyRow(); | ||||||
| 
 | 
 | ||||||
|             showMessage("Attributes have been saved."); |             showMessage("Labels have been saved."); | ||||||
| 
 | 
 | ||||||
|             noteEditor.loadAttributeList(); |             noteEditor.loadLabelList(); | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         function addLastEmptyRow() { |         function addLastEmptyRow() { | ||||||
|             const attrs = self.attributes().filter(attr => attr().isDeleted === 0); |             const attrs = self.labels().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.labels.push(ko.observable({ | ||||||
|                     attributeId: '', |                     labelId: '', | ||||||
|                     name: '', |                     name: '', | ||||||
|                     value: '', |                     value: '', | ||||||
|                     isDeleted: 0, |                     isDeleted: 0, | ||||||
| @@ -111,22 +111,22 @@ const attributesDialog = (function() { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.attributeChanged = function (data, event) { |         this.labelChanged = function (data, event) { | ||||||
|             addLastEmptyRow(); |             addLastEmptyRow(); | ||||||
| 
 | 
 | ||||||
|             const attr = self.getTargetAttribute(event.target); |             const attr = self.getTargetLabel(event.target); | ||||||
| 
 | 
 | ||||||
|             attr.valueHasMutated(); |             attr.valueHasMutated(); | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         this.isNotUnique = function(index) { |         this.isNotUnique = function(index) { | ||||||
|             const cur = self.attributes()[index](); |             const cur = self.labels()[index](); | ||||||
| 
 | 
 | ||||||
|             if (cur.name.trim() === "") { |             if (cur.name.trim() === "") { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) { |             for (let attrs = self.labels(), i = 0; i < attrs.length; i++) { | ||||||
|                 const attr = attrs[i](); |                 const attr = attrs[i](); | ||||||
| 
 | 
 | ||||||
|                 if (index !== i && cur.name === attr.name) { |                 if (index !== i && cur.name === attr.name) { | ||||||
| @@ -138,23 +138,23 @@ const attributesDialog = (function() { | |||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         this.isEmptyName = function(index) { |         this.isEmptyName = function(index) { | ||||||
|             const cur = self.attributes()[index](); |             const cur = self.labels()[index](); | ||||||
| 
 | 
 | ||||||
|             return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== ""); |             return cur.name.trim() === "" && (cur.labelId !== "" || cur.value !== ""); | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         this.getTargetAttribute = function(target) { |         this.getTargetLabel = function(target) { | ||||||
|             const context = ko.contextFor(target); |             const context = ko.contextFor(target); | ||||||
|             const index = context.$index(); |             const index = context.$index(); | ||||||
| 
 | 
 | ||||||
|             return self.attributes()[index]; |             return self.labels()[index]; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async function showDialog() { |     async function showDialog() { | ||||||
|         glob.activeDialog = $dialog; |         glob.activeDialog = $dialog; | ||||||
| 
 | 
 | ||||||
|         await attributesModel.loadAttributes(); |         await labelsModel.loadLabels(); | ||||||
| 
 | 
 | ||||||
|         $dialog.dialog({ |         $dialog.dialog({ | ||||||
|             modal: true, |             modal: true, | ||||||
| @@ -169,14 +169,14 @@ const attributesDialog = (function() { | |||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     ko.applyBindings(attributesModel, document.getElementById('attributes-dialog')); |     ko.applyBindings(labelsModel, document.getElementById('labels-dialog')); | ||||||
| 
 | 
 | ||||||
|     $(document).on('focus', '.attribute-name', function (e) { |     $(document).on('focus', '.label-name', function (e) { | ||||||
|         if (!$(this).hasClass("ui-autocomplete-input")) { |         if (!$(this).hasClass("ui-autocomplete-input")) { | ||||||
|             $(this).autocomplete({ |             $(this).autocomplete({ | ||||||
|                 // shouldn't be required and autocomplete should just accept array of strings, but that fails
 |                 // shouldn't be required and autocomplete should just accept array of strings, but that fails
 | ||||||
|                 // because we have overriden filter() function in init.js
 |                 // because we have overriden filter() function in init.js
 | ||||||
|                 source: attributeNames.map(attr => { |                 source: labelNames.map(attr => { | ||||||
|                     return { |                     return { | ||||||
|                         label: attr, |                         label: attr, | ||||||
|                         value: attr |                         value: attr | ||||||
| @@ -189,24 +189,24 @@ const attributesDialog = (function() { | |||||||
|         $(this).autocomplete("search", $(this).val()); |         $(this).autocomplete("search", $(this).val()); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     $(document).on('focus', '.attribute-value', async function (e) { |     $(document).on('focus', '.label-value', async function (e) { | ||||||
|         if (!$(this).hasClass("ui-autocomplete-input")) { |         if (!$(this).hasClass("ui-autocomplete-input")) { | ||||||
|             const attributeName = $(this).parent().parent().find('.attribute-name').val(); |             const labelName = $(this).parent().parent().find('.label-name').val(); | ||||||
| 
 | 
 | ||||||
|             if (attributeName.trim() === "") { |             if (labelName.trim() === "") { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const attributeValues = await server.get('attributes/values/' + encodeURIComponent(attributeName)); |             const labelValues = await server.get('labels/values/' + encodeURIComponent(labelName)); | ||||||
| 
 | 
 | ||||||
|             if (attributeValues.length === 0) { |             if (labelValues.length === 0) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $(this).autocomplete({ |             $(this).autocomplete({ | ||||||
|                 // shouldn't be required and autocomplete should just accept array of strings, but that fails
 |                 // shouldn't be required and autocomplete should just accept array of strings, but that fails
 | ||||||
|                 // because we have overriden filter() function in init.js
 |                 // because we have overriden filter() function in init.js
 | ||||||
|                 source: attributeValues.map(attr => { |                 source: labelValues.map(attr => { | ||||||
|                     return { |                     return { | ||||||
|                         label: attr, |                         label: attr, | ||||||
|                         value: attr |                         value: attr | ||||||
| @@ -13,8 +13,8 @@ const noteEditor = (function() { | |||||||
|     const $unprotectButton = $("#unprotect-button"); |     const $unprotectButton = $("#unprotect-button"); | ||||||
|     const $noteDetailWrapper = $("#note-detail-wrapper"); |     const $noteDetailWrapper = $("#note-detail-wrapper"); | ||||||
|     const $noteIdDisplay = $("#note-id-display"); |     const $noteIdDisplay = $("#note-id-display"); | ||||||
|     const $attributeList = $("#attribute-list"); |     const $labelList = $("#label-list"); | ||||||
|     const $attributeListInner = $("#attribute-list-inner"); |     const $labelListInner = $("#label-list-inner"); | ||||||
|     const $attachmentFileName = $("#attachment-filename"); |     const $attachmentFileName = $("#attachment-filename"); | ||||||
|     const $attachmentFileType = $("#attachment-filetype"); |     const $attachmentFileType = $("#attachment-filetype"); | ||||||
|     const $attachmentFileSize = $("#attachment-filesize"); |     const $attachmentFileSize = $("#attachment-filesize"); | ||||||
| @@ -253,8 +253,8 @@ const noteEditor = (function() { | |||||||
|         else if (currentNote.detail.type === 'file') { |         else if (currentNote.detail.type === 'file') { | ||||||
|             $noteDetailAttachment.show(); |             $noteDetailAttachment.show(); | ||||||
|  |  | ||||||
|             $attachmentFileName.text(currentNote.attributes.original_file_name); |             $attachmentFileName.text(currentNote.labels.original_file_name); | ||||||
|             $attachmentFileSize.text(currentNote.attributes.file_size + " bytes"); |             $attachmentFileSize.text(currentNote.labels.file_size + " bytes"); | ||||||
|             $attachmentFileType.text(currentNote.detail.mime); |             $attachmentFileType.text(currentNote.detail.mime); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
| @@ -269,25 +269,25 @@ const noteEditor = (function() { | |||||||
|         // after loading new note make sure editor is scrolled to the top |         // after loading new note make sure editor is scrolled to the top | ||||||
|         $noteDetailWrapper.scrollTop(0); |         $noteDetailWrapper.scrollTop(0); | ||||||
|  |  | ||||||
|         loadAttributeList(); |         loadLabelList(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async function loadAttributeList() { |     async function loadLabelList() { | ||||||
|         const noteId = getCurrentNoteId(); |         const noteId = getCurrentNoteId(); | ||||||
|  |  | ||||||
|         const attributes = await server.get('notes/' + noteId + '/attributes'); |         const labels = await server.get('notes/' + noteId + '/labels'); | ||||||
|  |  | ||||||
|         $attributeListInner.html(''); |         $labelListInner.html(''); | ||||||
|  |  | ||||||
|         if (attributes.length > 0) { |         if (labels.length > 0) { | ||||||
|             for (const attr of attributes) { |             for (const attr of labels) { | ||||||
|                 $attributeListInner.append(formatAttribute(attr) + " "); |                 $labelListInner.append(formatLabel(attr) + " "); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             $attributeList.show(); |             $labelList.show(); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             $attributeList.hide(); |             $labelList.hide(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -394,7 +394,7 @@ const noteEditor = (function() { | |||||||
|         getEditor, |         getEditor, | ||||||
|         focus, |         focus, | ||||||
|         executeCurrentNote, |         executeCurrentNote, | ||||||
|         loadAttributeList, |         loadLabelList, | ||||||
|         setContent |         setContent | ||||||
|     }; |     }; | ||||||
| })(); | })(); | ||||||
| @@ -125,7 +125,7 @@ function formatValueWithWhitespace(val) { | |||||||
|     return /[^\w_-]/.test(val) ? '"' + val + '"' : val; |     return /[^\w_-]/.test(val) ? '"' + val + '"' : val; | ||||||
| } | } | ||||||
|  |  | ||||||
| function formatAttribute(attr) { | function formatLabel(attr) { | ||||||
|     let str = "@" + formatValueWithWhitespace(attr.name); |     let str = "@" + formatValueWithWhitespace(attr.name); | ||||||
|  |  | ||||||
|     if (attr.value !== "") { |     if (attr.value !== "") { | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|                          "search note-content" |                          "search note-content" | ||||||
|                          "tree note-content" |                          "tree note-content" | ||||||
|                          "parent-list note-content" |                          "parent-list note-content" | ||||||
|                          "parent-list attribute-list"; |                          "parent-list label-list"; | ||||||
|     grid-template-columns: 2fr 5fr; |     grid-template-columns: 2fr 5fr; | ||||||
|     grid-template-rows: auto |     grid-template-rows: auto | ||||||
|                         auto |                         auto | ||||||
| @@ -269,14 +269,14 @@ div.ui-tooltip { | |||||||
|  |  | ||||||
| .cm-matchhighlight {background-color: #eeeeee} | .cm-matchhighlight {background-color: #eeeeee} | ||||||
|  |  | ||||||
| #attribute-list { | #label-list { | ||||||
|     grid-area: attribute-list; |     grid-area: label-list; | ||||||
|     color: #777777; |     color: #777777; | ||||||
|     border-top: 1px solid #eee; |     border-top: 1px solid #eee; | ||||||
|     padding: 5px; display: none; |     padding: 5px; display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #attribute-list button { | #label-list button { | ||||||
|     padding: 2px; |     padding: 2px; | ||||||
|     margin-right: 5px; |     margin-right: 5px; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ const router = express.Router(); | |||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const auth = require('../../services/auth'); | const auth = require('../../services/auth'); | ||||||
| const notes = require('../../services/notes'); | const notes = require('../../services/notes'); | ||||||
| const attributes = require('../../services/attributes'); | const labels = require('../../services/labels'); | ||||||
| const protected_session = require('../../services/protected_session'); | const protected_session = require('../../services/protected_session'); | ||||||
| const multer = require('multer')(); | const multer = require('multer')(); | ||||||
| const wrap = require('express-promise-wrap').wrap; | const wrap = require('express-promise-wrap').wrap; | ||||||
| @@ -33,8 +33,8 @@ router.post('/upload/:parentNoteId', auth.checkApiAuthOrElectron, multer.single( | |||||||
|             mime: file.mimetype |             mime: file.mimetype | ||||||
|         }, req, sourceId)).noteId; |         }, req, sourceId)).noteId; | ||||||
|  |  | ||||||
|         await attributes.createAttribute(noteId, "original_file_name", originalName, sourceId); |         await labels.createLabel(noteId, "original_file_name", originalName, sourceId); | ||||||
|         await attributes.createAttribute(noteId, "file_size", size, sourceId); |         await labels.createLabel(noteId, "file_size", size, sourceId); | ||||||
|  |  | ||||||
|         res.send({ |         res.send({ | ||||||
|             noteId: noteId |             noteId: noteId | ||||||
| @@ -62,8 +62,8 @@ router.get('/download/:noteId', auth.checkApiAuthOrElectron, wrap(async (req, re | |||||||
|         protected_session.decryptNote(dataKey, note); |         protected_session.decryptNote(dataKey, note); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const attributeMap = await attributes.getNoteAttributeMap(noteId); |     const labelMap = await labels.getNoteLabelMap(noteId); | ||||||
|     const fileName = attributeMap.original_file_name ? attributeMap.original_file_name : note.title; |     const fileName = labelMap.original_file_name ? labelMap.original_file_name : note.title; | ||||||
|  |  | ||||||
|     res.setHeader('Content-Disposition', 'attachment; filename=' + fileName); |     res.setHeader('Content-Disposition', 'attachment; filename=' + fileName); | ||||||
|     res.setHeader('Content-Type', note.mime); |     res.setHeader('Content-Type', note.mime); | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ router.post('/cleanup-soft-deleted-items', auth.checkApiAuth, wrap(async (req, r | |||||||
|  |  | ||||||
|         await sql.execute(`DELETE FROM note_images WHERE noteId IN (${noteIdsSql})`); |         await sql.execute(`DELETE FROM note_images WHERE noteId IN (${noteIdsSql})`); | ||||||
|  |  | ||||||
|         await sql.execute(`DELETE FROM attributes WHERE noteId IN (${noteIdsSql})`); |         await sql.execute(`DELETE FROM labels WHERE noteId IN (${noteIdsSql})`); | ||||||
|  |  | ||||||
|         await sql.execute("DELETE FROM branches WHERE isDeleted = 1"); |         await sql.execute("DELETE FROM branches WHERE isDeleted = 1"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ async function exportNote(branchId, directory, pack, repo) { | |||||||
|  |  | ||||||
|     const metadata = await getMetadata(note); |     const metadata = await getMetadata(note); | ||||||
|  |  | ||||||
|     if (metadata.attributes.find(attr => attr.name === 'exclude_from_export')) { |     if (metadata.labels.find(attr => attr.name === 'exclude_from_export')) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -68,7 +68,7 @@ async function getMetadata(note) { | |||||||
|         title: note.title, |         title: note.title, | ||||||
|         type: note.type, |         type: note.type, | ||||||
|         mime: note.mime, |         mime: note.mime, | ||||||
|         attributes: (await note.getAttributes()).map(attr => { |         labels: (await note.getLabels()).map(attr => { | ||||||
|             return { |             return { | ||||||
|                 name: attr.name, |                 name: attr.name, | ||||||
|                 value: attr.value |                 value: attr.value | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ const express = require('express'); | |||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const auth = require('../../services/auth'); | const auth = require('../../services/auth'); | ||||||
| const attributes = require('../../services/attributes'); | const labels = require('../../services/labels'); | ||||||
| const notes = require('../../services/notes'); | const notes = require('../../services/notes'); | ||||||
| const wrap = require('express-promise-wrap').wrap; | const wrap = require('express-promise-wrap').wrap; | ||||||
| const tar = require('tar-stream'); | const tar = require('tar-stream'); | ||||||
| @@ -126,8 +126,8 @@ async function importNotes(files, parentNoteId, sourceId) { | |||||||
|             sourceId: sourceId |             sourceId: sourceId | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         for (const attr of file.meta.attributes) { |         for (const attr of file.meta.labels) { | ||||||
|             await attributes.createAttribute(noteId, attr.name, attr.value); |             await labels.createLabel(noteId, attr.name, attr.value); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (file.children.length > 0) { |         if (file.children.length > 0) { | ||||||
|   | |||||||
| @@ -7,24 +7,24 @@ const auth = require('../../services/auth'); | |||||||
| const sync_table = require('../../services/sync_table'); | const sync_table = require('../../services/sync_table'); | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
| const wrap = require('express-promise-wrap').wrap; | const wrap = require('express-promise-wrap').wrap; | ||||||
| const attributes = require('../../services/attributes'); | const labels = require('../../services/labels'); | ||||||
| 
 | 
 | ||||||
| router.get('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/notes/:noteId/labels', 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 isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId])); |     res.send(await sql.getRows("SELECT * FROM labels WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId])); | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | router.put('/notes/:noteId/labels', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const noteId = req.params.noteId; |     const noteId = req.params.noteId; | ||||||
|     const attributes = req.body; |     const labels = req.body; | ||||||
|     const now = utils.nowDate(); |     const now = utils.nowDate(); | ||||||
| 
 | 
 | ||||||
|     await sql.doInTransaction(async () => { |     await sql.doInTransaction(async () => { | ||||||
|         for (const attr of attributes) { |         for (const attr of labels) { | ||||||
|             if (attr.attributeId) { |             if (attr.labelId) { | ||||||
|                 await sql.execute("UPDATE attributes SET name = ?, value = ?, dateModified = ?, isDeleted = ?, position = ? WHERE attributeId = ?", |                 await sql.execute("UPDATE labels SET name = ?, value = ?, dateModified = ?, isDeleted = ?, position = ? WHERE labelId = ?", | ||||||
|                     [attr.name, attr.value, now, attr.isDeleted, attr.position, attr.attributeId]); |                     [attr.name, attr.value, now, attr.isDeleted, attr.position, attr.labelId]); | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|                 // if it was "created" and then immediatelly deleted, we just don't create it at all
 |                 // if it was "created" and then immediatelly deleted, we just don't create it at all
 | ||||||
| @@ -32,10 +32,10 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, | |||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 attr.attributeId = utils.newAttributeId(); |                 attr.labelId = utils.newLabelId(); | ||||||
| 
 | 
 | ||||||
|                 await sql.insert("attributes", { |                 await sql.insert("labels", { | ||||||
|                     attributeId: attr.attributeId, |                     labelId: attr.labelId, | ||||||
|                     noteId: noteId, |                     noteId: noteId, | ||||||
|                     name: attr.name, |                     name: attr.name, | ||||||
|                     value: attr.value, |                     value: attr.value, | ||||||
| @@ -46,17 +46,17 @@ router.put('/notes/:noteId/attributes', auth.checkApiAuth, wrap(async (req, res, | |||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             await sync_table.addAttributeSync(attr.attributeId); |             await sync_table.addLabelSync(attr.labelId); | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     res.send(await sql.getRows("SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId])); |     res.send(await sql.getRows("SELECT * FROM labels WHERE isDeleted = 0 AND noteId = ? ORDER BY position, dateCreated", [noteId])); | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/labels/names', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const names = await sql.getColumn("SELECT DISTINCT name FROM attributes WHERE isDeleted = 0"); |     const names = await sql.getColumn("SELECT DISTINCT name FROM labels WHERE isDeleted = 0"); | ||||||
| 
 | 
 | ||||||
|     for (const attr of attributes.BUILTIN_ATTRIBUTES) { |     for (const attr of labels.BUILTIN_LABELS) { | ||||||
|         if (!names.includes(attr)) { |         if (!names.includes(attr)) { | ||||||
|             names.push(attr); |             names.push(attr); | ||||||
|         } |         } | ||||||
| @@ -67,10 +67,10 @@ router.get('/attributes/names', auth.checkApiAuth, wrap(async (req, res, next) = | |||||||
|     res.send(names); |     res.send(names); | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| router.get('/attributes/values/:attributeName', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/labels/values/:labelName', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const attributeName = req.params.attributeName; |     const labelName = req.params.labelName; | ||||||
| 
 | 
 | ||||||
|     const values = await sql.getColumn("SELECT DISTINCT value FROM attributes WHERE isDeleted = 0 AND name = ? AND value != '' ORDER BY value", [attributeName]); |     const values = await sql.getColumn("SELECT DISTINCT value FROM labels WHERE isDeleted = 0 AND name = ? AND value != '' ORDER BY value", [labelName]); | ||||||
| 
 | 
 | ||||||
|     res.send(values); |     res.send(values); | ||||||
| })); | })); | ||||||
| @@ -5,7 +5,7 @@ const router = express.Router(); | |||||||
| const auth = require('../../services/auth'); | const auth = require('../../services/auth'); | ||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const notes = require('../../services/notes'); | const notes = require('../../services/notes'); | ||||||
| const attributes = require('../../services/attributes'); | const labels = require('../../services/labels'); | ||||||
| const log = require('../../services/log'); | const log = require('../../services/log'); | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
| const protected_session = require('../../services/protected_session'); | const protected_session = require('../../services/protected_session'); | ||||||
| @@ -26,19 +26,19 @@ router.get('/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => { | |||||||
|  |  | ||||||
|     protected_session.decryptNote(req, detail); |     protected_session.decryptNote(req, detail); | ||||||
|  |  | ||||||
|     let attributeMap = null; |     let labelMap = null; | ||||||
|  |  | ||||||
|     if (detail.type === 'file') { |     if (detail.type === 'file') { | ||||||
|         // no need to transfer attachment payload for this request |         // no need to transfer attachment payload for this request | ||||||
|         detail.content = null; |         detail.content = null; | ||||||
|  |  | ||||||
|         // attributes contain important attachment metadata - filename and size |         // labels contain important attachment metadata - filename and size | ||||||
|         attributeMap = await attributes.getNoteAttributeMap(noteId); |         labelMap = await labels.getNoteLabelMap(noteId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.send({ |     res.send({ | ||||||
|         detail: detail, |         detail: detail, | ||||||
|         attributes: attributeMap |         labels: labelMap | ||||||
|     }); |     }); | ||||||
| })); | })); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ const express = require('express'); | |||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| const auth = require('../../services/auth'); | const auth = require('../../services/auth'); | ||||||
| const wrap = require('express-promise-wrap').wrap; | const wrap = require('express-promise-wrap').wrap; | ||||||
| const attributes = require('../../services/attributes'); | const labels = require('../../services/labels'); | ||||||
| const script = require('../../services/script'); | const script = require('../../services/script'); | ||||||
| const Repository = require('../../services/repository'); | const Repository = require('../../services/repository'); | ||||||
|  |  | ||||||
| @@ -29,7 +29,7 @@ router.post('/run/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => { | |||||||
|  |  | ||||||
| router.get('/startup', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/startup', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const repository = new Repository(req); |     const repository = new Repository(req); | ||||||
|     const notes = await attributes.getNotesWithAttribute(repository, "run", "frontend_startup"); |     const notes = await labels.getNotesWithLabel(repository, "run", "frontend_startup"); | ||||||
|  |  | ||||||
|     const scripts = []; |     const scripts = []; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -144,10 +144,10 @@ router.get('/note_images/:noteImageId', auth.checkApiAuth, wrap(async (req, res, | |||||||
|     res.send(await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [noteImageId])); |     res.send(await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [noteImageId])); | ||||||
| })); | })); | ||||||
|  |  | ||||||
| router.get('/attributes/:attributeId', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/labels/:labelId', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     const attributeId = req.params.attributeId; |     const labelId = req.params.labelId; | ||||||
|  |  | ||||||
|     res.send(await sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [attributeId])); |     res.send(await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [labelId])); | ||||||
| })); | })); | ||||||
|  |  | ||||||
| router.get('/api_tokens/:apiTokenId', auth.checkApiAuth, wrap(async (req, res, next) => { | router.get('/api_tokens/:apiTokenId', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
| @@ -204,8 +204,8 @@ router.put('/note_images', auth.checkApiAuth, wrap(async (req, res, next) => { | |||||||
|     res.send({}); |     res.send({}); | ||||||
| })); | })); | ||||||
|  |  | ||||||
| router.put('/attributes', auth.checkApiAuth, wrap(async (req, res, next) => { | router.put('/labels', auth.checkApiAuth, wrap(async (req, res, next) => { | ||||||
|     await syncUpdate.updateAttribute(req.body.entity, req.body.sourceId); |     await syncUpdate.updateLabel(req.body.entity, req.body.sourceId); | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
| })); | })); | ||||||
|   | |||||||
| @@ -42,10 +42,10 @@ router.get('/', auth.checkApiAuth, wrap(async (req, res, next) => { | |||||||
|         notes.isProtected, |         notes.isProtected, | ||||||
|         notes.type, |         notes.type, | ||||||
|         notes.mime, |         notes.mime, | ||||||
|         hideInAutocomplete.attributeId AS 'hideInAutocomplete' |         hideInAutocomplete.labelId AS 'hideInAutocomplete' | ||||||
|       FROM |       FROM | ||||||
|         notes |         notes | ||||||
|         LEFT JOIN attributes AS hideInAutocomplete ON hideInAutocomplete.noteId = notes.noteId |         LEFT JOIN labels AS hideInAutocomplete ON hideInAutocomplete.noteId = notes.noteId | ||||||
|                              AND hideInAutocomplete.name = 'hide_in_autocomplete' |                              AND hideInAutocomplete.name = 'hide_in_autocomplete' | ||||||
|                              AND hideInAutocomplete.isDeleted = 0 |                              AND hideInAutocomplete.isDeleted = 0 | ||||||
|       WHERE  |       WHERE  | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ router.put('/:branchId/expanded/:expanded', auth.checkApiAuth, wrap(async (req, | |||||||
|     await sql.doInTransaction(async () => { |     await sql.doInTransaction(async () => { | ||||||
|         await sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]); |         await sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]); | ||||||
|  |  | ||||||
|         // we don't sync expanded attribute |         // we don't sync expanded label | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     res.send({}); |     res.send({}); | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ const auth = require('../services/auth'); | |||||||
| const source_id = require('../services/source_id'); | const source_id = require('../services/source_id'); | ||||||
| const sql = require('../services/sql'); | const sql = require('../services/sql'); | ||||||
| const Repository = require('../services/repository'); | const Repository = require('../services/repository'); | ||||||
| const attributes = require('../services/attributes'); | const labels = require('../services/labels'); | ||||||
| const wrap = require('express-promise-wrap').wrap; | const wrap = require('express-promise-wrap').wrap; | ||||||
|  |  | ||||||
| router.get('', auth.checkAuth, wrap(async (req, res, next) => { | router.get('', auth.checkAuth, wrap(async (req, res, next) => { | ||||||
| @@ -21,7 +21,7 @@ router.get('', auth.checkAuth, wrap(async (req, res, next) => { | |||||||
|  |  | ||||||
| async function getAppCss(repository) { | async function getAppCss(repository) { | ||||||
|     let css = ''; |     let css = ''; | ||||||
|     const notes = attributes.getNotesWithAttribute(repository, 'app_css'); |     const notes = labels.getNotesWithLabel(repository, 'app_css'); | ||||||
|  |  | ||||||
|     for (const note of await notes) { |     for (const note of await notes) { | ||||||
|         css += `/* ${note.noteId} */ |         css += `/* ${note.noteId} */ | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ const sqlRoute = require('./api/sql'); | |||||||
| const anonymizationRoute = require('./api/anonymization'); | const anonymizationRoute = require('./api/anonymization'); | ||||||
| const cleanupRoute = require('./api/cleanup'); | const cleanupRoute = require('./api/cleanup'); | ||||||
| const imageRoute = require('./api/image'); | const imageRoute = require('./api/image'); | ||||||
| const attributesRoute = require('./api/attributes'); | const labelsRoute = require('./api/labels'); | ||||||
| const scriptRoute = require('./api/script'); | const scriptRoute = require('./api/script'); | ||||||
| const senderRoute = require('./api/sender'); | const senderRoute = require('./api/sender'); | ||||||
| const attachmentsRoute = require('./api/attachments'); | const attachmentsRoute = require('./api/attachments'); | ||||||
| @@ -43,7 +43,7 @@ function register(app) { | |||||||
|     app.use('/api/notes', notesApiRoute); |     app.use('/api/notes', notesApiRoute); | ||||||
|     app.use('/api/tree', treeChangesApiRoute); |     app.use('/api/tree', treeChangesApiRoute); | ||||||
|     app.use('/api/notes', cloningApiRoute); |     app.use('/api/notes', cloningApiRoute); | ||||||
|     app.use('/api', attributesRoute); |     app.use('/api', labelsRoute); | ||||||
|     app.use('/api/notes-history', noteHistoryApiRoute); |     app.use('/api/notes-history', noteHistoryApiRoute); | ||||||
|     app.use('/api/recent-changes', recentChangesApiRoute); |     app.use('/api/recent-changes', recentChangesApiRoute); | ||||||
|     app.use('/api/settings', settingsApiRoute); |     app.use('/api/settings', settingsApiRoute); | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -3,7 +3,7 @@ | |||||||
| const build = require('./build'); | const build = require('./build'); | ||||||
| const packageJson = require('../../package'); | const packageJson = require('../../package'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 79; | const APP_DB_VERSION = 80; | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     app_version: packageJson.version, |     app_version: packageJson.version, | ||||||
|   | |||||||
| @@ -1,89 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
|  |  | ||||||
| const sql = require('./sql'); |  | ||||||
| const utils = require('./utils'); |  | ||||||
| const sync_table = require('./sync_table'); |  | ||||||
|  |  | ||||||
| const BUILTIN_ATTRIBUTES = [ |  | ||||||
|     'frontend_startup', |  | ||||||
|     'backend_startup', |  | ||||||
|     'disable_versioning', |  | ||||||
|     'calendar_root', |  | ||||||
|     'hide_in_autocomplete', |  | ||||||
|     'exclude_from_export', |  | ||||||
|     'run', |  | ||||||
|     'manual_transaction_handling', |  | ||||||
|     'disable_inclusion', |  | ||||||
|     'app_css' |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| async function getNoteAttributeMap(noteId) { |  | ||||||
|     return await sql.getMap(`SELECT name, value FROM attributes WHERE noteId = ? AND isDeleted = 0`, [noteId]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getNoteIdWithAttribute(name, value) { |  | ||||||
|     return await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)  |  | ||||||
|           WHERE notes.isDeleted = 0 |  | ||||||
|                 AND attributes.isDeleted = 0 |  | ||||||
|                 AND attributes.name = ?  |  | ||||||
|                 AND attributes.value = ?`, [name, value]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getNotesWithAttribute(repository, name, value) { |  | ||||||
|     let notes; |  | ||||||
|  |  | ||||||
|     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]); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return notes; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getNoteWithAttribute(repository, name, value) { |  | ||||||
|     const notes = getNotesWithAttribute(repository, name, value); |  | ||||||
|  |  | ||||||
|     return notes.length > 0 ? notes[0] : null; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function getNoteIdsWithAttribute(name) { |  | ||||||
|     return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN attributes USING(noteId)  |  | ||||||
|           WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? AND attributes.isDeleted = 0`, [name]); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function createAttribute(noteId, name, value = "", sourceId = null) { |  | ||||||
|     if (value === null || value === undefined) { |  | ||||||
|         value = ""; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const now = utils.nowDate(); |  | ||||||
|     const attributeId = utils.newAttributeId(); |  | ||||||
|     const position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [noteId]); |  | ||||||
|  |  | ||||||
|     await sql.insert("attributes", { |  | ||||||
|         attributeId: attributeId, |  | ||||||
|         noteId: noteId, |  | ||||||
|         name: name, |  | ||||||
|         value: value, |  | ||||||
|         position: position, |  | ||||||
|         dateModified: now, |  | ||||||
|         dateCreated: now, |  | ||||||
|         isDeleted: false |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     await sync_table.addAttributeSync(attributeId, sourceId); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = { |  | ||||||
|     getNoteAttributeMap, |  | ||||||
|     getNoteIdWithAttribute, |  | ||||||
|     getNotesWithAttribute, |  | ||||||
|     getNoteWithAttribute, |  | ||||||
|     getNoteIdsWithAttribute, |  | ||||||
|     createAttribute, |  | ||||||
|     BUILTIN_ATTRIBUTES |  | ||||||
| }; |  | ||||||
| @@ -7,16 +7,16 @@ module.exports = function(attrFilters, searchText) { | |||||||
|     let i = 1; |     let i = 1; | ||||||
|  |  | ||||||
|     for (const filter of attrFilters) { |     for (const filter of attrFilters) { | ||||||
|         joins.push(`LEFT JOIN attributes AS attr${i} ON attr${i}.noteId = notes.noteId AND attr${i}.name = ?`); |         joins.push(`LEFT JOIN labels AS attr${i} ON attr${i}.noteId = notes.noteId AND attr${i}.name = ?`); | ||||||
|         joinParams.push(filter.name); |         joinParams.push(filter.name); | ||||||
|  |  | ||||||
|         where += " " + filter.relation + " "; |         where += " " + filter.relation + " "; | ||||||
|  |  | ||||||
|         if (filter.operator === 'exists') { |         if (filter.operator === 'exists') { | ||||||
|             where += `attr${i}.attributeId IS NOT NULL`; |             where += `attr${i}.labelId IS NOT NULL`; | ||||||
|         } |         } | ||||||
|         else if (filter.operator === 'not-exists') { |         else if (filter.operator === 'not-exists') { | ||||||
|             where += `attr${i}.attributeId IS NULL`; |             where += `attr${i}.labelId IS NULL`; | ||||||
|         } |         } | ||||||
|         else if (filter.operator === '=' || filter.operator === '!=') { |         else if (filter.operator === '=' || filter.operator === '!=') { | ||||||
|             where += `attr${i}.value ${filter.operator} ?`; |             where += `attr${i}.value ${filter.operator} ?`; | ||||||
|   | |||||||
| @@ -233,7 +233,7 @@ async function runAllChecks() { | |||||||
|     await runSyncRowChecks("recent_notes", "branchId", errorList); |     await runSyncRowChecks("recent_notes", "branchId", errorList); | ||||||
|     await runSyncRowChecks("images", "imageId", errorList); |     await runSyncRowChecks("images", "imageId", errorList); | ||||||
|     await runSyncRowChecks("note_images", "noteImageId", errorList); |     await runSyncRowChecks("note_images", "noteImageId", errorList); | ||||||
|     await runSyncRowChecks("attributes", "attributeId", errorList); |     await runSyncRowChecks("labels", "labelId", errorList); | ||||||
|     await runSyncRowChecks("api_tokens", "apiTokenId", errorList); |     await runSyncRowChecks("api_tokens", "apiTokenId", errorList); | ||||||
|  |  | ||||||
|     if (errorList.length === 0) { |     if (errorList.length === 0) { | ||||||
|   | |||||||
| @@ -83,16 +83,16 @@ async function getHashes() { | |||||||
|           FROM images   |           FROM images   | ||||||
|           ORDER BY imageId`)), |           ORDER BY imageId`)), | ||||||
|  |  | ||||||
|         attributes: getHash(await sql.getRows(` |         labels: getHash(await sql.getRows(` | ||||||
|           SELECT  |           SELECT  | ||||||
|             attributeId, |             labelId, | ||||||
|             noteId |             noteId | ||||||
|             name, |             name, | ||||||
|             value |             value | ||||||
|             dateModified, |             dateModified, | ||||||
|             dateCreated |             dateCreated | ||||||
|           FROM attributes   |           FROM labels   | ||||||
|           ORDER BY attributeId`)) |           ORDER BY labelId`)) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     const elapseTimeMs = new Date().getTime() - startTime.getTime(); |     const elapseTimeMs = new Date().getTime() - startTime.getTime(); | ||||||
|   | |||||||
| @@ -2,13 +2,13 @@ | |||||||
|  |  | ||||||
| const sql = require('./sql'); | const sql = require('./sql'); | ||||||
| const notes = require('./notes'); | const notes = require('./notes'); | ||||||
| const attributes = require('./attributes'); | const labels = require('./labels'); | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
|  |  | ||||||
| const CALENDAR_ROOT_ATTRIBUTE = 'calendar_root'; | const CALENDAR_ROOT_LABEL = 'calendar_root'; | ||||||
| const YEAR_ATTRIBUTE = 'year_note'; | const YEAR_LABEL = 'year_note'; | ||||||
| const MONTH_ATTRIBUTE = 'month_note'; | const MONTH_LABEL = 'month_note'; | ||||||
| const DATE_ATTRIBUTE = 'date_note'; | const DATE_LABEL = 'date_note'; | ||||||
|  |  | ||||||
| const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; | const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; | ||||||
| const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; | const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; | ||||||
| @@ -30,8 +30,8 @@ async function getNoteStartingWith(parentNoteId, startsWith) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function getRootCalendarNoteId() { | async function getRootCalendarNoteId() { | ||||||
|     let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN attributes USING(noteId)  |     let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)  | ||||||
|               WHERE attributes.name = '${CALENDAR_ROOT_ATTRIBUTE}' AND notes.isDeleted = 0`); |               WHERE labels.name = '${CALENDAR_ROOT_LABEL}' AND notes.isDeleted = 0`); | ||||||
|  |  | ||||||
|     if (!rootNoteId) { |     if (!rootNoteId) { | ||||||
|         rootNoteId = (await notes.createNewNote('root', { |         rootNoteId = (await notes.createNewNote('root', { | ||||||
| @@ -40,7 +40,7 @@ async function getRootCalendarNoteId() { | |||||||
|             isProtected: false |             isProtected: false | ||||||
|         })).noteId; |         })).noteId; | ||||||
|  |  | ||||||
|         await attributes.createAttribute(rootNoteId, CALENDAR_ROOT_ATTRIBUTE); |         await labels.createLabel(rootNoteId, CALENDAR_ROOT_LABEL); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return rootNoteId; |     return rootNoteId; | ||||||
| @@ -49,7 +49,7 @@ async function getRootCalendarNoteId() { | |||||||
| async function getYearNoteId(dateTimeStr, rootNoteId) { | async function getYearNoteId(dateTimeStr, rootNoteId) { | ||||||
|     const yearStr = dateTimeStr.substr(0, 4); |     const yearStr = dateTimeStr.substr(0, 4); | ||||||
|  |  | ||||||
|     let yearNoteId = await attributes.getNoteIdWithAttribute(YEAR_ATTRIBUTE, yearStr); |     let yearNoteId = await labels.getNoteIdWithLabel(YEAR_LABEL, yearStr); | ||||||
|  |  | ||||||
|     if (!yearNoteId) { |     if (!yearNoteId) { | ||||||
|         yearNoteId = await getNoteStartingWith(rootNoteId, yearStr); |         yearNoteId = await getNoteStartingWith(rootNoteId, yearStr); | ||||||
| @@ -58,7 +58,7 @@ async function getYearNoteId(dateTimeStr, rootNoteId) { | |||||||
|             yearNoteId = await createNote(rootNoteId, yearStr); |             yearNoteId = await createNote(rootNoteId, yearStr); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await attributes.createAttribute(yearNoteId, YEAR_ATTRIBUTE, yearStr); |         await labels.createLabel(yearNoteId, YEAR_LABEL, yearStr); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return yearNoteId; |     return yearNoteId; | ||||||
| @@ -68,7 +68,7 @@ async function getMonthNoteId(dateTimeStr, rootNoteId) { | |||||||
|     const monthStr = dateTimeStr.substr(0, 7); |     const monthStr = dateTimeStr.substr(0, 7); | ||||||
|     const monthNumber = dateTimeStr.substr(5, 2); |     const monthNumber = dateTimeStr.substr(5, 2); | ||||||
|  |  | ||||||
|     let monthNoteId = await attributes.getNoteIdWithAttribute(MONTH_ATTRIBUTE, monthStr); |     let monthNoteId = await labels.getNoteIdWithLabel(MONTH_LABEL, monthStr); | ||||||
|  |  | ||||||
|     if (!monthNoteId) { |     if (!monthNoteId) { | ||||||
|         const yearNoteId = await getYearNoteId(dateTimeStr, rootNoteId); |         const yearNoteId = await getYearNoteId(dateTimeStr, rootNoteId); | ||||||
| @@ -83,7 +83,7 @@ async function getMonthNoteId(dateTimeStr, rootNoteId) { | |||||||
|             monthNoteId = await createNote(yearNoteId, noteTitle); |             monthNoteId = await createNote(yearNoteId, noteTitle); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await attributes.createAttribute(monthNoteId, MONTH_ATTRIBUTE, monthStr); |         await labels.createLabel(monthNoteId, MONTH_LABEL, monthStr); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return monthNoteId; |     return monthNoteId; | ||||||
| @@ -97,7 +97,7 @@ async function getDateNoteId(dateTimeStr, rootNoteId = null) { | |||||||
|     const dateStr = dateTimeStr.substr(0, 10); |     const dateStr = dateTimeStr.substr(0, 10); | ||||||
|     const dayNumber = dateTimeStr.substr(8, 2); |     const dayNumber = dateTimeStr.substr(8, 2); | ||||||
|  |  | ||||||
|     let dateNoteId = await attributes.getNoteIdWithAttribute(DATE_ATTRIBUTE, dateStr); |     let dateNoteId = await labels.getNoteIdWithLabel(DATE_LABEL, dateStr); | ||||||
|  |  | ||||||
|     if (!dateNoteId) { |     if (!dateNoteId) { | ||||||
|         const monthNoteId = await getMonthNoteId(dateTimeStr, rootNoteId); |         const monthNoteId = await getMonthNoteId(dateTimeStr, rootNoteId); | ||||||
| @@ -112,7 +112,7 @@ async function getDateNoteId(dateTimeStr, rootNoteId = null) { | |||||||
|             dateNoteId = await createNote(monthNoteId, noteTitle); |             dateNoteId = await createNote(monthNoteId, noteTitle); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await attributes.createAttribute(dateNoteId, DATE_ATTRIBUTE, dateStr); |         await labels.createLabel(dateNoteId, DATE_LABEL, dateStr); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return dateNoteId; |     return dateNoteId; | ||||||
|   | |||||||
							
								
								
									
										89
									
								
								src/services/labels.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/services/labels.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | const sql = require('./sql'); | ||||||
|  | const utils = require('./utils'); | ||||||
|  | const sync_table = require('./sync_table'); | ||||||
|  |  | ||||||
|  | const BUILTIN_LABELS = [ | ||||||
|  |     'frontend_startup', | ||||||
|  |     'backend_startup', | ||||||
|  |     'disable_versioning', | ||||||
|  |     'calendar_root', | ||||||
|  |     'hide_in_autocomplete', | ||||||
|  |     'exclude_from_export', | ||||||
|  |     'run', | ||||||
|  |     'manual_transaction_handling', | ||||||
|  |     'disable_inclusion', | ||||||
|  |     'app_css' | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | async function getNoteLabelMap(noteId) { | ||||||
|  |     return await sql.getMap(`SELECT name, value FROM labels WHERE noteId = ? AND isDeleted = 0`, [noteId]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getNoteIdWithLabel(name, value) { | ||||||
|  |     return await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)  | ||||||
|  |           WHERE notes.isDeleted = 0 | ||||||
|  |                 AND labels.isDeleted = 0 | ||||||
|  |                 AND labels.name = ?  | ||||||
|  |                 AND labels.value = ?`, [name, value]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getNotesWithLabel(repository, name, value) { | ||||||
|  |     let notes; | ||||||
|  |  | ||||||
|  |     if (value !== undefined) { | ||||||
|  |         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN labels USING(noteId)  | ||||||
|  |           WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ? AND labels.value = ?`, [name, value]); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         notes = await repository.getEntities(`SELECT notes.* FROM notes JOIN labels USING(noteId)  | ||||||
|  |           WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ?`, [name]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return notes; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getNoteWithLabel(repository, name, value) { | ||||||
|  |     const notes = getNotesWithLabel(repository, name, value); | ||||||
|  |  | ||||||
|  |     return notes.length > 0 ? notes[0] : null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getNoteIdsWithLabel(name) { | ||||||
|  |     return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN labels USING(noteId)  | ||||||
|  |           WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ? AND labels.isDeleted = 0`, [name]); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function createLabel(noteId, name, value = "", sourceId = null) { | ||||||
|  |     if (value === null || value === undefined) { | ||||||
|  |         value = ""; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const now = utils.nowDate(); | ||||||
|  |     const labelId = utils.newLabelId(); | ||||||
|  |     const position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM labels WHERE noteId = ?`, [noteId]); | ||||||
|  |  | ||||||
|  |     await sql.insert("labels", { | ||||||
|  |         labelId: labelId, | ||||||
|  |         noteId: noteId, | ||||||
|  |         name: name, | ||||||
|  |         value: value, | ||||||
|  |         position: position, | ||||||
|  |         dateModified: now, | ||||||
|  |         dateCreated: now, | ||||||
|  |         isDeleted: false | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await sync_table.addLabelSync(labelId, sourceId); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     getNoteLabelMap, | ||||||
|  |     getNoteIdWithLabel, | ||||||
|  |     getNotesWithLabel, | ||||||
|  |     getNoteWithLabel, | ||||||
|  |     getNoteIdsWithLabel, | ||||||
|  |     createLabel, | ||||||
|  |     BUILTIN_LABELS | ||||||
|  | }; | ||||||
| @@ -2,7 +2,7 @@ const sql = require('./sql'); | |||||||
| const options = require('./options'); | const options = require('./options'); | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const sync_table = require('./sync_table'); | const sync_table = require('./sync_table'); | ||||||
| const attributes = require('./attributes'); | const labels = require('./labels'); | ||||||
| const protected_session = require('./protected_session'); | const protected_session = require('./protected_session'); | ||||||
|  |  | ||||||
| async function createNewNote(parentNoteId, noteOpts, dataKey, sourceId) { | async function createNewNote(parentNoteId, noteOpts, dataKey, sourceId) { | ||||||
| @@ -108,9 +108,9 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {}) | |||||||
|  |  | ||||||
|     const {noteId} = await createNewNote(parentNoteId, note, extraOptions.dataKey, extraOptions.sourceId); |     const {noteId} = await createNewNote(parentNoteId, note, extraOptions.dataKey, extraOptions.sourceId); | ||||||
|  |  | ||||||
|     if (extraOptions.attributes) { |     if (extraOptions.labels) { | ||||||
|         for (const attrName in extraOptions.attributes) { |         for (const attrName in extraOptions.labels) { | ||||||
|             await attributes.createAttribute(noteId, attrName, extraOptions.attributes[attrName]); |             await labels.createLabel(noteId, attrName, extraOptions.labels[attrName]); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -274,7 +274,7 @@ async function updateNote(noteId, newNote, dataKey, sourceId) { | |||||||
|         await protected_session.encryptNote(dataKey, newNote.detail); |         await protected_session.encryptNote(dataKey, newNote.detail); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const attributesMap = await attributes.getNoteAttributeMap(noteId); |     const labelsMap = await labels.getNoteLabelMap(noteId); | ||||||
|  |  | ||||||
|     const now = new Date(); |     const now = new Date(); | ||||||
|     const nowStr = utils.nowDate(); |     const nowStr = utils.nowDate(); | ||||||
| @@ -289,7 +289,7 @@ async function updateNote(noteId, newNote, dataKey, sourceId) { | |||||||
|     await sql.doInTransaction(async () => { |     await sql.doInTransaction(async () => { | ||||||
|         const msSinceDateCreated = now.getTime() - utils.parseDateTime(newNote.detail.dateCreated).getTime(); |         const msSinceDateCreated = now.getTime() - utils.parseDateTime(newNote.detail.dateCreated).getTime(); | ||||||
|  |  | ||||||
|         if (attributesMap.disable_versioning !== 'true' |         if (labelsMap.disable_versioning !== 'true' | ||||||
|             && !existingnoteRevisionId |             && !existingnoteRevisionId | ||||||
|             && msSinceDateCreated >= historySnapshotTimeInterval * 1000) { |             && msSinceDateCreated >= historySnapshotTimeInterval * 1000) { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ module.exports = function(searchText) { | |||||||
|             value: match[7] !== undefined ? trimQuotes(match[7]) : null |             value: match[7] !== undefined ? trimQuotes(match[7]) : null | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         // remove attributes from further fulltext search |         // remove labels from further fulltext search | ||||||
|         searchText = searchText.split(match[0]).join(''); |         searchText = searchText.split(match[0]).join(''); | ||||||
|  |  | ||||||
|         match = attrRegex.exec(searchText); |         match = attrRegex.exec(searchText); | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ const protected_session = require('./protected_session'); | |||||||
| const Note = require('../entities/note'); | const Note = require('../entities/note'); | ||||||
| const NoteRevision = require('../entities/note_revision'); | const NoteRevision = require('../entities/note_revision'); | ||||||
| const Branch = require('../entities/branch'); | const Branch = require('../entities/branch'); | ||||||
| const Attribute = require('../entities/attribute'); | const Label = require('../entities/label'); | ||||||
| const sync_table = require('../services/sync_table'); | const sync_table = require('../services/sync_table'); | ||||||
|  |  | ||||||
| class Repository { | class Repository { | ||||||
| @@ -40,8 +40,8 @@ class Repository { | |||||||
|     createEntityFromRow(row) { |     createEntityFromRow(row) { | ||||||
|         let entity; |         let entity; | ||||||
|  |  | ||||||
|         if (row.attributeId) { |         if (row.labelId) { | ||||||
|             entity = new Attribute(this, row); |             entity = new Label(this, row); | ||||||
|         } |         } | ||||||
|         else if (row.noteRevisionId) { |         else if (row.noteRevisionId) { | ||||||
|             entity = new NoteRevision(this, row); |             entity = new NoteRevision(this, row); | ||||||
|   | |||||||
| @@ -3,14 +3,14 @@ const Repository = require('./repository'); | |||||||
|  |  | ||||||
| const repo = new Repository(); | const repo = new Repository(); | ||||||
|  |  | ||||||
| async function runNotesWithAttribute(runAttrValue) { | async function runNotesWithLabel(runAttrValue) { | ||||||
|     const notes = await repo.getEntities(` |     const notes = await repo.getEntities(` | ||||||
|         SELECT notes.*  |         SELECT notes.*  | ||||||
|         FROM notes  |         FROM notes  | ||||||
|           JOIN attributes ON attributes.noteId = notes.noteId |           JOIN labels ON labels.noteId = notes.noteId | ||||||
|                            AND attributes.isDeleted = 0 |                            AND labels.isDeleted = 0 | ||||||
|                            AND attributes.name = 'run'  |                            AND labels.name = 'run'  | ||||||
|                            AND attributes.value = ?  |                            AND labels.value = ?  | ||||||
|         WHERE |         WHERE | ||||||
|           notes.type = 'code' |           notes.type = 'code' | ||||||
|           AND notes.isDeleted = 0`, [runAttrValue]); |           AND notes.isDeleted = 0`, [runAttrValue]); | ||||||
| @@ -20,8 +20,8 @@ async function runNotesWithAttribute(runAttrValue) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| setTimeout(() => runNotesWithAttribute('backend_startup'), 10 * 1000); | setTimeout(() => runNotesWithLabel('backend_startup'), 10 * 1000); | ||||||
|  |  | ||||||
| setInterval(() => runNotesWithAttribute('hourly'), 3600 * 1000); | setInterval(() => runNotesWithLabel('hourly'), 3600 * 1000); | ||||||
|  |  | ||||||
| setInterval(() => runNotesWithAttribute('daily'), 24 * 3600 * 1000); | setInterval(() => runNotesWithLabel('daily'), 24 * 3600 * 1000); | ||||||
| @@ -23,7 +23,7 @@ async function executeBundle(dataKey, bundle, startNote) { | |||||||
|  |  | ||||||
|     const ctx = new ScriptContext(dataKey, startNote, bundle.allNotes); |     const ctx = new ScriptContext(dataKey, startNote, bundle.allNotes); | ||||||
|  |  | ||||||
|     if (await bundle.note.hasAttribute('manual_transaction_handling')) { |     if (await bundle.note.hasLabel('manual_transaction_handling')) { | ||||||
|         return await execute(ctx, script, ''); |         return await execute(ctx, script, ''); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
| @@ -73,7 +73,7 @@ async function getScriptBundle(note, root = true, scriptEnv = null, includedNote | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!root && await note.hasAttribute('disable_inclusion')) { |     if (!root && await note.hasLabel('disable_inclusion')) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ const protected_session = require('./protected_session'); | |||||||
| const notes = require('./notes'); | const notes = require('./notes'); | ||||||
| const sql = require('./sql'); | const sql = require('./sql'); | ||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const attributes = require('./attributes'); | const labels = require('./labels'); | ||||||
| const date_notes = require('./date_notes'); | const date_notes = require('./date_notes'); | ||||||
| const config = require('./config'); | const config = require('./config'); | ||||||
| const Repository = require('./repository'); | const Repository = require('./repository'); | ||||||
| @@ -48,12 +48,12 @@ function ScriptApi(dataKey, startNote, currentNote) { | |||||||
|         return repository.getNote(noteId); |         return repository.getNote(noteId); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.getNotesWithAttribute = async function (attrName, attrValue) { |     this.getNotesWithLabel = async function (attrName, attrValue) { | ||||||
|         return await attributes.getNotesWithAttribute(repository, attrName, attrValue); |         return await labels.getNotesWithLabel(repository, attrName, attrValue); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.getNoteWithAttribute = async function (attrName, attrValue) { |     this.getNoteWithLabel = async function (attrName, attrValue) { | ||||||
|         const notes = await this.getNotesWithAttribute(attrName, attrValue); |         const notes = await this.getNotesWithLabel(attrName, attrValue); | ||||||
|  |  | ||||||
|         return notes.length > 0 ? notes[0] : null; |         return notes.length > 0 ? notes[0] : null; | ||||||
|     }; |     }; | ||||||
| @@ -64,7 +64,7 @@ function ScriptApi(dataKey, startNote, currentNote) { | |||||||
|         return await notes.createNote(parentNoteId, title, content, extraOptions); |         return await notes.createNote(parentNoteId, title, content, extraOptions); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     this.createAttribute = attributes.createAttribute; |     this.createLabel = labels.createLabel; | ||||||
|  |  | ||||||
|     this.updateEntity = repository.updateEntity; |     this.updateEntity = repository.updateEntity; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -146,8 +146,8 @@ async function pullSync(syncContext) { | |||||||
|         else if (sync.entityName === 'note_images') { |         else if (sync.entityName === 'note_images') { | ||||||
|             await syncUpdate.updateNoteImage(resp, syncContext.sourceId); |             await syncUpdate.updateNoteImage(resp, syncContext.sourceId); | ||||||
|         } |         } | ||||||
|         else if (sync.entityName === 'attributes') { |         else if (sync.entityName === 'labels') { | ||||||
|             await syncUpdate.updateAttribute(resp, syncContext.sourceId); |             await syncUpdate.updateLabel(resp, syncContext.sourceId); | ||||||
|         } |         } | ||||||
|         else if (sync.entityName === 'api_tokens') { |         else if (sync.entityName === 'api_tokens') { | ||||||
|             await syncUpdate.updateApiToken(resp, syncContext.sourceId); |             await syncUpdate.updateApiToken(resp, syncContext.sourceId); | ||||||
| @@ -235,8 +235,8 @@ async function pushEntity(sync, syncContext) { | |||||||
|     else if (sync.entityName === 'note_images') { |     else if (sync.entityName === 'note_images') { | ||||||
|         entity = await sql.getRow('SELECT * FROM note_images WHERE noteImageId = ?', [sync.entityId]); |         entity = await sql.getRow('SELECT * FROM note_images WHERE noteImageId = ?', [sync.entityId]); | ||||||
|     } |     } | ||||||
|     else if (sync.entityName === 'attributes') { |     else if (sync.entityName === 'labels') { | ||||||
|         entity = await sql.getRow('SELECT * FROM attributes WHERE attributeId = ?', [sync.entityId]); |         entity = await sql.getRow('SELECT * FROM labels WHERE labelId = ?', [sync.entityId]); | ||||||
|     } |     } | ||||||
|     else if (sync.entityName === 'api_tokens') { |     else if (sync.entityName === 'api_tokens') { | ||||||
|         entity = await sql.getRow('SELECT * FROM api_tokens WHERE apiTokenId = ?', [sync.entityId]); |         entity = await sql.getRow('SELECT * FROM api_tokens WHERE apiTokenId = ?', [sync.entityId]); | ||||||
|   | |||||||
| @@ -36,8 +36,8 @@ async function addNoteImageSync(noteImageId, sourceId) { | |||||||
|     await addEntitySync("note_images", noteImageId, sourceId); |     await addEntitySync("note_images", noteImageId, sourceId); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function addAttributeSync(attributeId, sourceId) { | async function addLabelSync(labelId, sourceId) { | ||||||
|     await addEntitySync("attributes", attributeId, sourceId); |     await addEntitySync("labels", labelId, sourceId); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function addApiTokenSync(apiTokenId, sourceId) { | async function addApiTokenSync(apiTokenId, sourceId) { | ||||||
| @@ -96,7 +96,7 @@ async function fillAllSyncRows() { | |||||||
|     await fillSyncRows("recent_notes", "branchId"); |     await fillSyncRows("recent_notes", "branchId"); | ||||||
|     await fillSyncRows("images", "imageId"); |     await fillSyncRows("images", "imageId"); | ||||||
|     await fillSyncRows("note_images", "noteImageId"); |     await fillSyncRows("note_images", "noteImageId"); | ||||||
|     await fillSyncRows("attributes", "attributeId"); |     await fillSyncRows("labels", "labelId"); | ||||||
|     await fillSyncRows("api_tokens", "apiTokenId"); |     await fillSyncRows("api_tokens", "apiTokenId"); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -109,7 +109,7 @@ module.exports = { | |||||||
|     addRecentNoteSync, |     addRecentNoteSync, | ||||||
|     addImageSync, |     addImageSync, | ||||||
|     addNoteImageSync, |     addNoteImageSync, | ||||||
|     addAttributeSync, |     addLabelSync, | ||||||
|     addApiTokenSync, |     addApiTokenSync, | ||||||
|     addEntitySync, |     addEntitySync, | ||||||
|     cleanupSyncRowsForMissingEntities, |     cleanupSyncRowsForMissingEntities, | ||||||
|   | |||||||
| @@ -130,17 +130,17 @@ async function updateNoteImage(entity, sourceId) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function updateAttribute(entity, sourceId) { | async function updateLabel(entity, sourceId) { | ||||||
|     const origAttribute = await sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [entity.attributeId]); |     const origLabel = await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [entity.labelId]); | ||||||
|  |  | ||||||
|     if (!origAttribute || origAttribute.dateModified <= entity.dateModified) { |     if (!origLabel || origLabel.dateModified <= entity.dateModified) { | ||||||
|         await sql.doInTransaction(async () => { |         await sql.doInTransaction(async () => { | ||||||
|             await sql.replace("attributes", entity); |             await sql.replace("labels", entity); | ||||||
|  |  | ||||||
|             await sync_table.addAttributeSync(entity.attributeId, sourceId); |             await sync_table.addLabelSync(entity.labelId, sourceId); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         log.info("Update/sync attribute " + entity.attributeId); |         log.info("Update/sync label " + entity.labelId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -167,6 +167,6 @@ module.exports = { | |||||||
|     updateRecentNotes, |     updateRecentNotes, | ||||||
|     updateImage, |     updateImage, | ||||||
|     updateNoteImage, |     updateNoteImage, | ||||||
|     updateAttribute, |     updateLabel, | ||||||
|     updateApiToken |     updateApiToken | ||||||
| }; | }; | ||||||
| @@ -24,7 +24,7 @@ function newNoteImageId() { | |||||||
|     return randomString(12); |     return randomString(12); | ||||||
| } | } | ||||||
|  |  | ||||||
| function newAttributeId() { | function newLabelId() { | ||||||
|     return randomString(12); |     return randomString(12); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -159,7 +159,7 @@ module.exports = { | |||||||
|     newNoteRevisionId, |     newNoteRevisionId, | ||||||
|     newImageId, |     newImageId, | ||||||
|     newNoteImageId, |     newNoteImageId, | ||||||
|     newAttributeId, |     newLabelId, | ||||||
|     newApiTokenId, |     newApiTokenId, | ||||||
|     toBase64, |     toBase64, | ||||||
|     fromBase64, |     fromBase64, | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ | |||||||
|  |  | ||||||
|       <div id="search-box" class="hide-toggle" style="grid-area: search; display: none; padding: 10px; margin-top: 10px;"> |       <div id="search-box" class="hide-toggle" style="grid-area: search; display: none; padding: 10px; margin-top: 10px;"> | ||||||
|         <div style="display: flex; align-items: center;"> |         <div style="display: flex; align-items: center;"> | ||||||
|           <input name="search-text" placeholder="Search text, attributes" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off"> |           <input name="search-text" placeholder="Search text, labels" style="flex-grow: 100; margin-left: 5px; margin-right: 5px;" autocomplete="off"> | ||||||
|           <button id="do-search-button" class="btn btn-primary btn-sm" title="Search">Search</button> |           <button id="do-search-button" class="btn btn-primary btn-sm" title="Search">Search</button> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
| @@ -124,7 +124,7 @@ | |||||||
|             </button> |             </button> | ||||||
|             <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dLabel"> |             <ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dLabel"> | ||||||
|               <li><a id="show-history-button"><kbd>Alt+H</kbd> History</a></li> |               <li><a id="show-history-button"><kbd>Alt+H</kbd> History</a></li> | ||||||
|               <li><a class="show-attributes-button"><kbd>Alt+A</kbd> Attributes</a></li> |               <li><a class="show-labels-button"><kbd>Alt+A</kbd> Labels</a></li> | ||||||
|               <li><a id="show-source-button"><kbd>Ctrl+U</kbd> HTML source</a></li> |               <li><a id="show-source-button"><kbd>Ctrl+U</kbd> HTML source</a></li> | ||||||
|               <li><a id="upload-attachment-button">Upload attachment</a></li> |               <li><a id="upload-attachment-button">Upload attachment</a></li> | ||||||
|             </ul> |             </ul> | ||||||
| @@ -147,29 +147,29 @@ | |||||||
|           <p> |           <p> | ||||||
|             <ul> |             <ul> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@abc</code> - matches notes with attribute abc</li> |                 <code>@abc</code> - matches notes with label abc</li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@!abc</code> - matches notes without abc attribute (maybe not the best syntax)</li> |                 <code>@!abc</code> - matches notes without abc label (maybe not the best syntax)</li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@abc=true</code> - matches notes with attribute abc having value true</li> |                 <code>@abc=true</code> - matches notes with label abc having value true</li> | ||||||
|               <li><code>@abc!=true</code></li> |               <li><code>@abc!=true</code></li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@"weird attribute"="weird value"</code> - works also with whitespace inside names values</li> |                 <code>@"weird label"="weird value"</code> - works also with whitespace inside names values</li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@abc and @def</code> - matches notes with both abc and def</li> |                 <code>@abc and @def</code> - matches notes with both abc and def</li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@abc @def</code> - AND relation is implicit when specifying multiple attributes</li> |                 <code>@abc @def</code> - AND relation is implicit when specifying multiple labels</li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@abc or @def</code> - OR relation</li> |                 <code>@abc or @def</code> - OR relation</li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@abc<=5</code> - numerical comparison (also >, >=, <).</li> |                 <code>@abc<=5</code> - numerical comparison (also >, >=, <).</li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>some search string @abc @def</code> - combination of fulltext and attribute search - both of them need to match (OR not supported)</li> |                 <code>some search string @abc @def</code> - combination of fulltext and label search - both of them need to match (OR not supported)</li> | ||||||
|               <li> |               <li> | ||||||
|                 <code>@abc @def some search string</code> - same combination</li> |                 <code>@abc @def some search string</code> - same combination</li> | ||||||
|             </ul> |             </ul> | ||||||
|  |  | ||||||
|             <a href="https://github.com/zadam/trilium/wiki/Attributes">Complete help on search syntax</a> |             <a href="https://github.com/zadam/trilium/wiki/Labels">Complete help on search syntax</a> | ||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
| @@ -204,10 +204,10 @@ | |||||||
|         <input type="file" id="attachment-upload" style="display: none" /> |         <input type="file" id="attachment-upload" style="display: none" /> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div id="attribute-list"> |       <div id="label-list"> | ||||||
|         <button class="btn btn-sm show-attributes-button">Attributes:</button> |         <button class="btn btn-sm show-labels-button">Labels:</button> | ||||||
|  |  | ||||||
|         <span id="attribute-list-inner"></span> |         <span id="label-list-inner"></span> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
| @@ -437,14 +437,14 @@ | |||||||
|       <textarea id="note-source" readonly="readonly"></textarea> |       <textarea id="note-source" readonly="readonly"></textarea> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|     <div id="attributes-dialog" title="Note attributes" style="display: none; padding: 20px;"> |     <div id="labels-dialog" title="Note labels" 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 changes <kbd>enter</kbd></button> |         <button class="btn btn-large" style="width: 200px;" id="save-labels-button" type="submit">Save changes <kbd>enter</kbd></button> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div style="height: 97%; overflow: auto"> |       <div style="height: 97%; overflow: auto"> | ||||||
|         <table id="attributes-table" class="table"> |         <table id="labels-table" class="table"> | ||||||
|           <thead> |           <thead> | ||||||
|             <tr> |             <tr> | ||||||
|               <th></th> |               <th></th> | ||||||
| @@ -454,25 +454,25 @@ | |||||||
|               <th></th> |               <th></th> | ||||||
|             </tr> |             </tr> | ||||||
|           </thead> |           </thead> | ||||||
|           <tbody data-bind="foreach: attributes"> |           <tbody data-bind="foreach: labels"> | ||||||
|             <tr data-bind="if: isDeleted == 0"> |             <tr data-bind="if: isDeleted == 0"> | ||||||
|               <td class="handle"> |               <td class="handle"> | ||||||
|                 <span class="glyphicon glyphicon-resize-vertical"></span> |                 <span class="glyphicon glyphicon-resize-vertical"></span> | ||||||
|                 <input type="hidden" name="position" data-bind="value: position"/> |                 <input type="hidden" name="position" data-bind="value: position"/> | ||||||
|               </td> |               </td> | ||||||
|               <!-- ID column has specific width because if it's empty its size can be deformed when dragging --> |               <!-- ID column has specific width because if it's empty its size can be deformed when dragging --> | ||||||
|               <td data-bind="text: attributeId" style="width: 150px;"></td> |               <td data-bind="text: labelId" style="width: 150px;"></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 --> | ||||||
|                 <input type="text" class="attribute-name" data-bind="value: name, valueUpdate: 'blur',  event: { blur: $parent.attributeChanged }"/> |                 <input type="text" class="label-name" data-bind="value: name, valueUpdate: 'blur',  event: { blur: $parent.labelChanged }"/> | ||||||
|                 <div style="color: yellowgreen" data-bind="if: $parent.isNotUnique($index())"><span class="glyphicon glyphicon-info-sign"></span> Duplicate attribute.</div> |                 <div style="color: yellowgreen" data-bind="if: $parent.isNotUnique($index())"><span class="glyphicon glyphicon-info-sign"></span> Duplicate label.</div> | ||||||
|                 <div style="color: red" data-bind="if: $parent.isEmptyName($index())">Attribute name can't be empty.</div> |                 <div style="color: red" data-bind="if: $parent.isEmptyName($index())">Label name can't be empty.</div> | ||||||
|               </td> |               </td> | ||||||
|               <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="label-value" data-bind="value: value, valueUpdate: 'blur', event: { blur: $parent.labelChanged }" style="width: 300px"/> | ||||||
|               </td> |               </td> | ||||||
|               <td title="Delete" style="padding: 13px;"> |               <td title="Delete" style="padding: 13px;"> | ||||||
|                 <span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteAttribute"></span> |                 <span class="glyphicon glyphicon-trash" data-bind="click: $parent.deleteLabel"></span> | ||||||
|               </td> |               </td> | ||||||
|             </tr> |             </tr> | ||||||
|           </tbody> |           </tbody> | ||||||
| @@ -550,7 +550,7 @@ | |||||||
|     <script src="/javascripts/dialogs/edit_tree_prefix.js"></script> |     <script src="/javascripts/dialogs/edit_tree_prefix.js"></script> | ||||||
|     <script src="/javascripts/dialogs/sql_console.js"></script> |     <script src="/javascripts/dialogs/sql_console.js"></script> | ||||||
|     <script src="/javascripts/dialogs/note_source.js"></script> |     <script src="/javascripts/dialogs/note_source.js"></script> | ||||||
|     <script src="/javascripts/dialogs/attributes.js"></script> |     <script src="/javascripts/dialogs/labels.js"></script> | ||||||
|  |  | ||||||
|     <script src="/javascripts/link.js"></script> |     <script src="/javascripts/link.js"></script> | ||||||
|     <script src="/javascripts/sync.js"></script> |     <script src="/javascripts/sync.js"></script> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user