| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  | import noteDetailService from '../services/note_detail.js'; | 
					
						
							|  |  |  | import server from '../services/server.js'; | 
					
						
							|  |  |  | import infoService from "../services/info.js"; | 
					
						
							| 
									
										
										
										
											2018-07-28 17:59:55 +02:00
										 |  |  | import linkService from "../services/link.js"; | 
					
						
							|  |  |  | import treeUtils from "../services/tree_utils.js"; | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | const $dialog = $("#relations-dialog"); | 
					
						
							|  |  |  | const $saveRelationsButton = $("#save-relations-button"); | 
					
						
							|  |  |  | const $relationsBody = $('#relations-table tbody'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const relationsModel = new RelationsModel(); | 
					
						
							|  |  |  | let relationNames = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function RelationsModel() { | 
					
						
							|  |  |  |     const self = this; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.relations = ko.observableArray(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.updateRelationPositions = function() { | 
					
						
							|  |  |  |         let position = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // we need to update positions by searching in the DOM, because order of the
 | 
					
						
							|  |  |  |         // relations in the viewmodel (self.relations()) stays the same
 | 
					
						
							|  |  |  |         $relationsBody.find('input[name="position"]').each(function() { | 
					
						
							|  |  |  |             const relation = self.getTargetRelation(this); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             relation().position = position++; | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-28 17:59:55 +02:00
										 |  |  |     async function showRelations(relations) { | 
					
						
							|  |  |  |         for (const relation of relations) { | 
					
						
							|  |  |  |             relation.targetNoteId = await treeUtils.getNoteTitle(relation.targetNoteId) + " (" + relation.targetNoteId + ")"; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.relations(relations.map(ko.observable)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |     this.loadRelations = async function() { | 
					
						
							|  |  |  |         const noteId = noteDetailService.getCurrentNoteId(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const relations = await server.get('notes/' + noteId + '/relations'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-28 17:59:55 +02:00
										 |  |  |         await showRelations(relations); | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         addLastEmptyRow(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         relationNames = await server.get('relations/names'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // relation might not be rendered immediatelly so could not focus
 | 
					
						
							|  |  |  |         setTimeout(() => $(".relation-name:last").focus(), 100); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $relationsBody.sortable({ | 
					
						
							|  |  |  |             handle: '.handle', | 
					
						
							|  |  |  |             containment: $relationsBody, | 
					
						
							|  |  |  |             update: this.updateRelationPositions | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.deleteRelation = function(data, event) { | 
					
						
							|  |  |  |         const relation = self.getTargetRelation(event.target); | 
					
						
							|  |  |  |         const relationData = relation(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (relationData) { | 
					
						
							|  |  |  |             relationData.isDeleted = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             relation(relationData); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             addLastEmptyRow(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function isValid() { | 
					
						
							|  |  |  |         for (let relations = self.relations(), i = 0; i < relations.length; i++) { | 
					
						
							|  |  |  |             if (self.isEmptyName(i)) { | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.save = async function() { | 
					
						
							|  |  |  |         // 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
 | 
					
						
							|  |  |  |         // stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
 | 
					
						
							|  |  |  |         $saveRelationsButton.focus(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!isValid()) { | 
					
						
							|  |  |  |             alert("Please fix all validation errors and try saving again."); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.updateRelationPositions(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const noteId = noteDetailService.getCurrentNoteId(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const relationsToSave = self.relations() | 
					
						
							|  |  |  |             .map(relation => relation()) | 
					
						
							|  |  |  |             .filter(relation => relation.relationId !== "" || relation.name !== ""); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-28 17:59:55 +02:00
										 |  |  |         relationsToSave.forEach(relation => relation.targetNoteId = treeUtils.getNoteIdFromNotePath(linkService.getNotePathFromLabel(relation.targetNoteId))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         console.log(relationsToSave); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |         const relations = await server.put('notes/' + noteId + '/relations', relationsToSave); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-28 17:59:55 +02:00
										 |  |  |         await showRelations(relations); | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         addLastEmptyRow(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         infoService.showMessage("Relations have been saved."); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         noteDetailService.loadRelationList(); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function addLastEmptyRow() { | 
					
						
							|  |  |  |         const relations = self.relations().filter(attr => attr().isDeleted === 0); | 
					
						
							|  |  |  |         const last = relations.length === 0 ? null : relations[relations.length - 1](); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 11:28:24 +02:00
										 |  |  |         if (!last || last.name.trim() !== "" || last.targetNoteId !== "") { | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |             self.relations.push(ko.observable({ | 
					
						
							|  |  |  |                 relationId: '', | 
					
						
							|  |  |  |                 name: '', | 
					
						
							| 
									
										
										
										
											2018-07-27 11:28:24 +02:00
										 |  |  |                 targetNoteId: '', | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |                 isDeleted: 0, | 
					
						
							|  |  |  |                 position: 0 | 
					
						
							|  |  |  |             })); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.relationChanged = function (data, event) { | 
					
						
							|  |  |  |         addLastEmptyRow(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const relation = self.getTargetRelation(event.target); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         relation.valueHasMutated(); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.isNotUnique = function(index) { | 
					
						
							|  |  |  |         const cur = self.relations()[index](); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (cur.name.trim() === "") { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (let relations = self.relations(), i = 0; i < relations.length; i++) { | 
					
						
							|  |  |  |             const relation = relations[i](); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (index !== i && cur.name === relation.name) { | 
					
						
							|  |  |  |                 return true; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.isEmptyName = function(index) { | 
					
						
							|  |  |  |         const cur = self.relations()[index](); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 11:28:24 +02:00
										 |  |  |         return cur.name.trim() === "" && (cur.relationId !== "" || cur.targetNoteId !== ""); | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.getTargetRelation = function(target) { | 
					
						
							|  |  |  |         const context = ko.contextFor(target); | 
					
						
							|  |  |  |         const index = context.$index(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return self.relations()[index]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function showDialog() { | 
					
						
							|  |  |  |     glob.activeDialog = $dialog; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await relationsModel.loadRelations(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $dialog.dialog({ | 
					
						
							|  |  |  |         modal: true, | 
					
						
							|  |  |  |         width: 800, | 
					
						
							|  |  |  |         height: 500 | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ko.applyBindings(relationsModel, document.getElementById('relations-dialog')); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-28 17:02:48 +02:00
										 |  |  | $dialog.on('focus', '.relation-name', function (e) { | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |     if (!$(this).hasClass("ui-autocomplete-input")) { | 
					
						
							|  |  |  |         $(this).autocomplete({ | 
					
						
							|  |  |  |             // shouldn't be required and autocomplete should just accept array of strings, but that fails
 | 
					
						
							|  |  |  |             // because we have overriden filter() function in autocomplete.js
 | 
					
						
							|  |  |  |             source: relationNames.map(relation => { | 
					
						
							|  |  |  |                 return { | 
					
						
							| 
									
										
										
										
											2018-07-27 11:28:24 +02:00
										 |  |  |                     label: relation, | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |                     value: relation | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }), | 
					
						
							|  |  |  |             minLength: 0 | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     $(this).autocomplete("search", $(this).val()); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-29 12:34:40 +02:00
										 |  |  | async function initNoteAutocomplete($el) { | 
					
						
							| 
									
										
										
										
											2018-07-28 17:02:48 +02:00
										 |  |  |     if (!$el.hasClass("ui-autocomplete-input")) { | 
					
						
							|  |  |  |         await $el.autocomplete({ | 
					
						
							|  |  |  |             source: async function (request, response) { | 
					
						
							| 
									
										
										
										
											2018-07-27 11:28:24 +02:00
										 |  |  |                 const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (result.length > 0) { | 
					
						
							|  |  |  |                     response(result.map(row => { | 
					
						
							|  |  |  |                         return { | 
					
						
							|  |  |  |                             label: row.label, | 
					
						
							|  |  |  |                             value: row.label + ' (' + row.value + ')' | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     })); | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2018-07-27 11:28:24 +02:00
										 |  |  |                 else { | 
					
						
							|  |  |  |                     response([{ | 
					
						
							|  |  |  |                         label: "No results", | 
					
						
							|  |  |  |                         value: "No results" | 
					
						
							|  |  |  |                     }]); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             minLength: 0, | 
					
						
							|  |  |  |             select: function (event, ui) { | 
					
						
							|  |  |  |                 if (ui.item.value === 'No results') { | 
					
						
							|  |  |  |                     return false; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2018-07-28 17:59:55 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-07-28 17:02:48 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-29 12:34:40 +02:00
										 |  |  | $dialog.on('focus', '.relation-target-note-id', async function () { | 
					
						
							|  |  |  |     await initNoteAutocomplete($(this)); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-28 17:02:48 +02:00
										 |  |  | $dialog.on('click', '.relations-show-recent-notes', async function () { | 
					
						
							|  |  |  |     const $autocomplete = $(this).parent().find('.relation-target-note-id'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-29 12:34:40 +02:00
										 |  |  |     await initNoteAutocomplete($autocomplete); | 
					
						
							| 
									
										
										
										
											2018-07-27 11:28:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-28 17:02:48 +02:00
										 |  |  |     $autocomplete.autocomplete("search", ""); | 
					
						
							| 
									
										
										
										
											2018-07-27 11:28:24 +02:00
										 |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-27 10:52:48 +02:00
										 |  |  | export default { | 
					
						
							|  |  |  |     showDialog | 
					
						
							|  |  |  | }; |