mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	convert more note details to new style
This commit is contained in:
		| @@ -121,12 +121,12 @@ async function loadNoteDetail(noteId, newTab = false) { | |||||||
|     let ctx; |     let ctx; | ||||||
|  |  | ||||||
|     if (noteContexts.length === 0 || newTab) { |     if (noteContexts.length === 0 || newTab) { | ||||||
|         const tabContent = $("#note-tab-content-template").clone(); |         const $tabContent = $(".note-tab-content-template").clone(); | ||||||
|  |  | ||||||
|         tabContent.removeAttr('id'); |         $tabContent.removeClass('note-tab-content-template'); | ||||||
|         tabContent.attr('data-note-id', noteId); |         $tabContent.attr('data-note-id', noteId); | ||||||
|  |  | ||||||
|         $noteTabContentsContainer.append(tabContent); |         $noteTabContentsContainer.append($tabContent); | ||||||
|  |  | ||||||
|         // if it's a new tab explicitly by user then it's in background |         // if it's a new tab explicitly by user then it's in background | ||||||
|         ctx = new NoteContext(loadedNote, newTab); |         ctx = new NoteContext(loadedNote, newTab); | ||||||
|   | |||||||
| @@ -5,15 +5,26 @@ import server from "./server.js"; | |||||||
| import noteDetailService from "./note_detail.js"; | import noteDetailService from "./note_detail.js"; | ||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
|  |  | ||||||
| let codeEditor = null; | class NoteDetailCode { | ||||||
|  |  | ||||||
| const $component = $('#note-detail-code'); |     /** | ||||||
| const $executeScriptButton = $("#execute-script-button"); |      * @param {NoteContext} ctx | ||||||
|  |      */ | ||||||
|  |     constructor(ctx) { | ||||||
|  |         this.ctx = ctx; | ||||||
|  |         this.codeEditor = null; | ||||||
|  |         this.$component = ctx.$noteTabContent.find('.note-detail-code'); | ||||||
|  |         this.$executeScriptButton = ctx.$noteTabContent.find(".execute-script-button"); | ||||||
|  |  | ||||||
| async function show() { |         utils.bindShortcut("ctrl+return", this.executeCurrentNote); | ||||||
|  |  | ||||||
|  |         this.$executeScriptButton.click(this.executeCurrentNote); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async show() { | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); |         await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR); | ||||||
|  |  | ||||||
|     if (!codeEditor) { |         if (!this.codeEditor) { | ||||||
|             CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; |             CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; | ||||||
|             CodeMirror.keyMap.default["Tab"] = "indentMore"; |             CodeMirror.keyMap.default["Tab"] = "indentMore"; | ||||||
|  |  | ||||||
| @@ -23,7 +34,7 @@ async function show() { | |||||||
|  |  | ||||||
|             CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; |             CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; | ||||||
|  |  | ||||||
|         codeEditor = CodeMirror($component[0], { |             this.codeEditor = CodeMirror(this.$component[0], { | ||||||
|                 value: "", |                 value: "", | ||||||
|                 viewportMargin: Infinity, |                 viewportMargin: Infinity, | ||||||
|                 indentUnit: 4, |                 indentUnit: 4, | ||||||
| @@ -39,75 +50,67 @@ async function show() { | |||||||
|                 lineWrapping: true |                 lineWrapping: true | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|         onNoteChange(noteDetailService.noteChanged); |             this.onNoteChange(noteDetailService.noteChanged); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     $component.show(); |         this.$component.show(); | ||||||
|  |  | ||||||
|     const activeNote = noteDetailService.getActiveNote(); |  | ||||||
|  |  | ||||||
|         // this needs to happen after the element is shown, otherwise the editor won't be refreshed |         // this needs to happen after the element is shown, otherwise the editor won't be refreshed | ||||||
|         // CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check) |         // CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check) | ||||||
|         // we provide fallback |         // we provide fallback | ||||||
|     codeEditor.setValue(activeNote.content || ""); |         this.codeEditor.setValue(this.ctx.note.content || ""); | ||||||
|  |  | ||||||
|     const info = CodeMirror.findModeByMIME(activeNote.mime); |         const info = CodeMirror.findModeByMIME(this.ctx.note.mime); | ||||||
|  |  | ||||||
|         if (info) { |         if (info) { | ||||||
|         codeEditor.setOption("mode", info.mime); |             this.codeEditor.setOption("mode", info.mime); | ||||||
|         CodeMirror.autoLoadMode(codeEditor, info.mode); |             CodeMirror.autoLoadMode(this.codeEditor, info.mode); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     codeEditor.refresh(); |         this.codeEditor.refresh(); | ||||||
| } |     } | ||||||
|  |  | ||||||
| function getContent() { |     getContent() { | ||||||
|     return codeEditor.getValue(); |         return this.codeEditor.getValue(); | ||||||
| } |     } | ||||||
|  |  | ||||||
| function focus() { |     focus() { | ||||||
|     codeEditor.focus(); |         this.codeEditor.focus(); | ||||||
| } |     } | ||||||
|  |  | ||||||
| async function executeCurrentNote() { |     async executeCurrentNote() { | ||||||
|         // ctrl+enter is also used elsewhere so make sure we're running only when appropriate |         // ctrl+enter is also used elsewhere so make sure we're running only when appropriate | ||||||
|     if (noteDetailService.getActiveNoteType() !== 'code') { |         if (this.ctx.note.type !== 'code') { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // make sure note is saved so we load latest changes |         // make sure note is saved so we load latest changes | ||||||
|         await noteDetailService.saveNotesIfChanged(); |         await noteDetailService.saveNotesIfChanged(); | ||||||
|  |  | ||||||
|     const activeNote = noteDetailService.getActiveNote(); |         if (this.ctx.note.mime.endsWith("env=frontend")) { | ||||||
|  |             await bundleService.getAndExecuteBundle(this.ctx.note.noteId); | ||||||
|     if (activeNote.mime.endsWith("env=frontend")) { |  | ||||||
|         await bundleService.getAndExecuteBundle(noteDetailService.getActiveNoteId()); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     if (activeNote.mime.endsWith("env=backend")) { |         if (this.ctx.note.mime.endsWith("env=backend")) { | ||||||
|         await server.post('script/run/' + noteDetailService.getActiveNoteId()); |             await server.post('script/run/' + this.ctx.note.noteId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         infoService.showMessage("Note executed"); |         infoService.showMessage("Note executed"); | ||||||
| } |  | ||||||
|  |  | ||||||
| function onNoteChange(func) { |  | ||||||
|     codeEditor.on('change', func); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| utils.bindShortcut("ctrl+return", executeCurrentNote); |  | ||||||
|  |  | ||||||
| $executeScriptButton.click(executeCurrentNote); |  | ||||||
|  |  | ||||||
| export default { |  | ||||||
|     show, |  | ||||||
|     getContent, |  | ||||||
|     focus, |  | ||||||
|     onNoteChange, |  | ||||||
|     cleanup: () => { |  | ||||||
|         if (codeEditor) { |  | ||||||
|             codeEditor.setValue(''); |  | ||||||
|     } |     } | ||||||
|     }, |  | ||||||
|     scrollToTop: () => $component.scrollTop(0) |     onNoteChange(func) { | ||||||
|  |         this.codeEditor.on('change', func); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     cleanup() { | ||||||
|  |         if (this.codeEditor) { | ||||||
|  |             this.codeEditor.setValue(''); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     scrollToTop() { | ||||||
|  |         this.$component.scrollTop(0); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export default NoteDetailCode; | ||||||
| @@ -10,19 +10,6 @@ import promptDialog from "../dialogs/prompt.js"; | |||||||
| import infoDialog from "../dialogs/info.js"; | import infoDialog from "../dialogs/info.js"; | ||||||
| import confirmDialog from "../dialogs/confirm.js"; | import confirmDialog from "../dialogs/confirm.js"; | ||||||
|  |  | ||||||
| const $component = $("#note-detail-relation-map"); |  | ||||||
| const $relationMapContainer = $("#relation-map-container"); |  | ||||||
| const $createChildNote = $("#relation-map-create-child-note"); |  | ||||||
| const $zoomInButton = $("#relation-map-zoom-in"); |  | ||||||
| const $zoomOutButton = $("#relation-map-zoom-out"); |  | ||||||
| const $resetPanZoomButton = $("#relation-map-reset-pan-zoom"); |  | ||||||
|  |  | ||||||
| let mapData; |  | ||||||
| let jsPlumbInstance; |  | ||||||
| // outside of mapData because they are not persisted in the note content |  | ||||||
| let relations; |  | ||||||
| let pzInstance; |  | ||||||
|  |  | ||||||
| const uniDirectionalOverlays = [ | const uniDirectionalOverlays = [ | ||||||
|     [ "Arrow", { |     [ "Arrow", { | ||||||
|         location: 1, |         location: 1, | ||||||
| @@ -77,9 +64,120 @@ const linkOverlays = [ | |||||||
|     } ] |     } ] | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| function loadMapData() { | let containerCounter = 1; | ||||||
|     const activeNote = noteDetailService.getActiveNote(); |  | ||||||
|     mapData = { | class NoteDetailRelationMap { | ||||||
|  |     /** | ||||||
|  |      * @param {NoteContext} ctx | ||||||
|  |      */ | ||||||
|  |     constructor(ctx) { | ||||||
|  |         this.ctx = ctx; | ||||||
|  |         this.$component = ctx.$noteTabContent.find(".note-detail-relation-map"); | ||||||
|  |         this.$relationMapContainer = ctx.$noteTabContent.find(".relation-map-container"); | ||||||
|  |         this.$createChildNote = ctx.$noteTabContent.find(".relation-map-create-child-note"); | ||||||
|  |         this.$zoomInButton = ctx.$noteTabContent.find(".relation-map-zoom-in"); | ||||||
|  |         this.$zoomOutButton = ctx.$noteTabContent.find(".relation-map-zoom-out"); | ||||||
|  |         this.$resetPanZoomButton = ctx.$noteTabContent.find(".relation-map-reset-pan-zoom"); | ||||||
|  |  | ||||||
|  |         this.mapData = null; | ||||||
|  |         this.jsPlumbInstance = null; | ||||||
|  |         // outside of mapData because they are not persisted in the note content | ||||||
|  |         this.relations = null; | ||||||
|  |         this.pzInstance = null; | ||||||
|  |  | ||||||
|  |         this.$relationMapContainer.attr("id", "relation-map-container-" + (containerCounter++)); | ||||||
|  |         this.$relationMapContainer.on("contextmenu", ".note-box", e => { | ||||||
|  |             contextMenuWidget.initContextMenu(e, { | ||||||
|  |                 getContextMenuItems: () => { | ||||||
|  |                     return [ | ||||||
|  |                         {title: "Remove note", cmd: "remove", uiIcon: "trash"}, | ||||||
|  |                         {title: "Edit title", cmd: "edit-title", uiIcon: "pencil"}, | ||||||
|  |                     ]; | ||||||
|  |                 }, | ||||||
|  |                 selectContextMenuItem: (event, cmd) => this.noteContextMenuHandler(event, cmd) | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.clipboard = null; | ||||||
|  |  | ||||||
|  |         this.$createChildNote.click(async () => { | ||||||
|  |             const title = await promptDialog.ask({ message: "Enter title of new note",  defaultValue: "new note" }); | ||||||
|  |  | ||||||
|  |             if (!title.trim()) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             const {note} = await server.post(`notes/${this.ctx.note.noteId}/children`, { | ||||||
|  |                 title, | ||||||
|  |                 target: 'into' | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             infoService.showMessage("Click on canvas to place new note"); | ||||||
|  |  | ||||||
|  |             // reloading tree so that the new note appears there | ||||||
|  |             // no need to wait for it to finish | ||||||
|  |             treeService.reload(); | ||||||
|  |  | ||||||
|  |             this.clipboard = { noteId: note.noteId, title }; | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.$resetPanZoomButton.click(() => { | ||||||
|  |             // reset to initial pan & zoom state | ||||||
|  |             this.pzInstance.zoomTo(0, 0, 1 / getZoom()); | ||||||
|  |             this.pzInstance.moveTo(0, 0); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         this.$component.on("drop", this.dropNoteOntoRelationMapHandler); | ||||||
|  |         this.$component.on("dragover", ev => ev.preventDefault()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async noteContextMenuHandler(event, cmd) { | ||||||
|  |         const $noteBox = $(event.originalTarget).closest(".note-box"); | ||||||
|  |         const $title = $noteBox.find(".title a"); | ||||||
|  |         const noteId = this.idToNoteId($noteBox.prop("id")); | ||||||
|  |  | ||||||
|  |         if (cmd === "remove") { | ||||||
|  |             if (!await confirmDialog.confirmDeleteNoteBoxWithNote($title.text())) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.jsPlumbInstance.remove(this.noteIdToId(noteId)); | ||||||
|  |  | ||||||
|  |             if (confirmDialog.isDeleteNoteChecked()) { | ||||||
|  |                 await server.remove("notes/" + noteId); | ||||||
|  |  | ||||||
|  |                 // to force it to disappear from the tree | ||||||
|  |                 treeService.reload(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.mapData.notes = this.mapData.notes.filter(note => note.noteId !== noteId); | ||||||
|  |  | ||||||
|  |             this.relations = this.relations.filter(relation => relation.sourceNoteId !== noteId && relation.targetNoteId !== noteId); | ||||||
|  |  | ||||||
|  |             this.saveData(); | ||||||
|  |         } | ||||||
|  |         else if (cmd === "edit-title") { | ||||||
|  |             const title = await promptDialog.ask({ | ||||||
|  |                 message: "Enter new note title:", | ||||||
|  |                 defaultValue: $title.text() | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             if (!title) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             await server.put(`notes/${noteId}/change-title`, { title }); | ||||||
|  |  | ||||||
|  |             treeService.setNoteTitle(noteId, title); | ||||||
|  |  | ||||||
|  |             $title.text(title); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     loadMapData() { | ||||||
|  |         this.mapData = { | ||||||
|             notes: [], |             notes: [], | ||||||
|             // it is important to have this exact value here so that initial transform is same as this |             // it is important to have this exact value here so that initial transform is same as this | ||||||
|             // which will guarantee note won't be saved on first conversion to relation map note type |             // which will guarantee note won't be saved on first conversion to relation map note type | ||||||
| @@ -92,57 +190,57 @@ function loadMapData() { | |||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|     if (activeNote.content) { |         if (this.ctx.note.content) { | ||||||
|             try { |             try { | ||||||
|             mapData = JSON.parse(activeNote.content); |                 this.mapData = JSON.parse(this.ctx.note.content); | ||||||
|             } catch (e) { |             } catch (e) { | ||||||
|                 console.log("Could not parse content: ", e); |                 console.log("Could not parse content: ", e); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| } |     } | ||||||
|  |  | ||||||
| function noteIdToId(noteId) { |     noteIdToId(noteId) { | ||||||
|         return "rel-map-note-" + noteId; |         return "rel-map-note-" + noteId; | ||||||
| } |     } | ||||||
|  |  | ||||||
| function idToNoteId(id) { |     idToNoteId(id) { | ||||||
|         return id.substr(13); |         return id.substr(13); | ||||||
| } |     } | ||||||
|  |  | ||||||
| async function show() { |     async show() { | ||||||
|     $component.show(); |         this.$component.show(); | ||||||
|  |  | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); |         await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP); | ||||||
|  |  | ||||||
|     loadMapData(); |         this.loadMapData(); | ||||||
|  |  | ||||||
|         jsPlumb.ready(() => { |         jsPlumb.ready(() => { | ||||||
|         initJsPlumbInstance(); |             this.initJsPlumbInstance(); | ||||||
|  |  | ||||||
|         initPanZoom(); |             this.initPanZoom(); | ||||||
|  |  | ||||||
|         loadNotesAndRelations(); |             this.loadNotesAndRelations(); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| } |     } | ||||||
|  |  | ||||||
| function clearMap() { |     clearMap() { | ||||||
|         // delete all endpoints and connections |         // delete all endpoints and connections | ||||||
|         // this is done at this point (after async operations) to reduce flicker to the minimum |         // this is done at this point (after async operations) to reduce flicker to the minimum | ||||||
|     jsPlumbInstance.deleteEveryEndpoint(); |         this.jsPlumbInstance.deleteEveryEndpoint(); | ||||||
|  |  | ||||||
|         // without this we still end up with note boxes remaining in the canvas |         // without this we still end up with note boxes remaining in the canvas | ||||||
|     $relationMapContainer.empty(); |         this.$relationMapContainer.empty(); | ||||||
| } |     } | ||||||
|  |  | ||||||
| async function loadNotesAndRelations() { |     async loadNotesAndRelations() { | ||||||
|     const noteIds = mapData.notes.map(note => note.noteId); |         const noteIds = this.mapData.notes.map(note => note.noteId); | ||||||
|         const data = await server.post("notes/relation-map", {noteIds}); |         const data = await server.post("notes/relation-map", {noteIds}); | ||||||
|  |  | ||||||
|     relations = []; |         this.relations = []; | ||||||
|  |  | ||||||
|         for (const relation of data.relations) { |         for (const relation of data.relations) { | ||||||
|         const match = relations.find(rel => |             const match = this.relations.find(rel => | ||||||
|                 rel.name === data.inverseRelations[relation.name] |                 rel.name === data.inverseRelations[relation.name] | ||||||
|                 && ((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId) |                 && ((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId) | ||||||
|                 || (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId))); |                 || (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId))); | ||||||
| @@ -155,28 +253,28 @@ async function loadNotesAndRelations() { | |||||||
|                 relation.render = true; |                 relation.render = true; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         relations.push(relation); |             this.relations.push(relation); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     mapData.notes = mapData.notes.filter(note => note.noteId in data.noteTitles); |         this.mapData.notes = this.mapData.notes.filter(note => note.noteId in data.noteTitles); | ||||||
|  |  | ||||||
|     jsPlumbInstance.batch(async function () { |         this.jsPlumbInstance.batch(async () => { | ||||||
|         clearMap(); |             this.clearMap(); | ||||||
|  |  | ||||||
|         for (const note of mapData.notes) { |             for (const note of this.mapData.notes) { | ||||||
|                 const title = data.noteTitles[note.noteId]; |                 const title = data.noteTitles[note.noteId]; | ||||||
|  |  | ||||||
|             await createNoteBox(note.noteId, title, note.x, note.y); |                 await this.createNoteBox(note.noteId, title, note.x, note.y); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         for (const relation of relations) { |             for (const relation of this.relations) { | ||||||
|                 if (!relation.render) { |                 if (!relation.render) { | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|             const connection = jsPlumbInstance.connect({ |                 const connection = this.jsPlumbInstance.connect({ | ||||||
|                 source: noteIdToId(relation.sourceNoteId), |                     source: this.noteIdToId(relation.sourceNoteId), | ||||||
|                 target: noteIdToId(relation.targetNoteId), |                     target: this.noteIdToId(relation.targetNoteId), | ||||||
|                     type: relation.type |                     type: relation.type | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
| @@ -194,39 +292,39 @@ async function loadNotesAndRelations() { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             for (const link of data.links) { |             for (const link of data.links) { | ||||||
|             jsPlumbInstance.connect({ |                 this.jsPlumbInstance.connect({ | ||||||
|                 source: noteIdToId(link.sourceNoteId), |                     source: this.noteIdToId(link.sourceNoteId), | ||||||
|                 target: noteIdToId(link.targetNoteId), |                     target: this.noteIdToId(link.targetNoteId), | ||||||
|                     type: 'link' |                     type: 'link' | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| } |     } | ||||||
|  |  | ||||||
| function initPanZoom() { |     initPanZoom() { | ||||||
|     if (pzInstance) { |         if (this.pzInstance) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     pzInstance = panzoom($relationMapContainer[0], { |         this.pzInstance = panzoom(this.$relationMapContainer[0], { | ||||||
|             maxZoom: 2, |             maxZoom: 2, | ||||||
|             minZoom: 0.3, |             minZoom: 0.3, | ||||||
|             smoothScroll: false, |             smoothScroll: false, | ||||||
|             onMouseDown: function(event) { |             onMouseDown: function(event) { | ||||||
|             if (clipboard) { |                 if (this.clipboard) { | ||||||
|                 let {x, y} = getMousePosition(event); |                     let {x, y} = this.getMousePosition(event); | ||||||
|  |  | ||||||
|                     // modifying position so that cursor is on the top-center of the box |                     // modifying position so that cursor is on the top-center of the box | ||||||
|                     x -= 80; |                     x -= 80; | ||||||
|                     y -= 15; |                     y -= 15; | ||||||
|  |  | ||||||
|                 createNoteBox(clipboard.noteId, clipboard.title, x, y); |                     this.createNoteBox(this.clipboard.noteId, this.clipboard.title, x, y); | ||||||
|  |  | ||||||
|                 mapData.notes.push({ noteId: clipboard.noteId, x, y }); |                     this.mapData.notes.push({ noteId: this.clipboard.noteId, x, y }); | ||||||
|  |  | ||||||
|                 saveData(); |                     this.saveData(); | ||||||
|  |  | ||||||
|                 clipboard = null; |                     this.clipboard = null; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 return true; |                 return true; | ||||||
| @@ -238,101 +336,78 @@ function initPanZoom() { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|     pzInstance.on('transform', () => { // gets triggered on any transform change |         this.pzInstance.on('transform', () => { // gets triggered on any transform change | ||||||
|         jsPlumbInstance.setZoom(getZoom()); |             this.jsPlumbInstance.setZoom(this.getZoom()); | ||||||
|  |  | ||||||
|         saveCurrentTransform(); |             this.saveCurrentTransform(); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|     if (mapData.transform) { |         if (this.mapData.transform) { | ||||||
|         pzInstance.zoomTo(0, 0, mapData.transform.scale); |             this.pzInstance.zoomTo(0, 0, this.mapData.transform.scale); | ||||||
|  |  | ||||||
|         pzInstance.moveTo(mapData.transform.x, mapData.transform.y); |             this.pzInstance.moveTo(this.mapData.transform.x, this.mapData.transform.y); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             // set to initial coordinates |             // set to initial coordinates | ||||||
|         pzInstance.moveTo(0, 0); |             this.pzInstance.moveTo(0, 0); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     $zoomInButton.click(() => pzInstance.zoomTo(0, 0, 1.2)); |         this.$zoomInButton.click(() => this.pzInstance.zoomTo(0, 0, 1.2)); | ||||||
|     $zoomOutButton.click(() => pzInstance.zoomTo(0, 0, 0.8)); |         this.$zoomOutButton.click(() => this.pzInstance.zoomTo(0, 0, 0.8)); | ||||||
| } |     } | ||||||
|  |  | ||||||
| function saveCurrentTransform() { |     saveCurrentTransform() { | ||||||
|     const newTransform = pzInstance.getTransform(); |         const newTransform = this.pzInstance.getTransform(); | ||||||
|  |  | ||||||
|     if (JSON.stringify(newTransform) !== JSON.stringify(mapData.transform)) { |         if (JSON.stringify(newTransform) !== JSON.stringify(this.mapData.transform)) { | ||||||
|             // clone transform object |             // clone transform object | ||||||
|         mapData.transform = JSON.parse(JSON.stringify(newTransform)); |             this.mapData.transform = JSON.parse(JSON.stringify(newTransform)); | ||||||
|  |  | ||||||
|         saveData(); |             this.saveData(); | ||||||
|         } |         } | ||||||
| } |  | ||||||
|  |  | ||||||
| function cleanup() { |  | ||||||
|     if (jsPlumbInstance) { |  | ||||||
|         clearMap(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (pzInstance) { |     cleanup() { | ||||||
|         pzInstance.dispose(); |         if (this.jsPlumbInstance) { | ||||||
|         pzInstance = null; |             this.clearMap(); | ||||||
|         } |         } | ||||||
| } |  | ||||||
|  |  | ||||||
| function initJsPlumbInstance () { |         if (this.pzInstance) { | ||||||
|     if (jsPlumbInstance) { |             this.pzInstance.dispose(); | ||||||
|         cleanup(); |             this.pzInstance = null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     initJsPlumbInstance () { | ||||||
|  |         if (this.jsPlumbInstance) { | ||||||
|  |             this.cleanup(); | ||||||
|  |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     jsPlumbInstance = jsPlumb.getInstance({ |         this.jsPlumbInstance = jsPlumb.getInstance({ | ||||||
|             Endpoint: ["Dot", {radius: 2}], |             Endpoint: ["Dot", {radius: 2}], | ||||||
|             Connector: "StateMachine", |             Connector: "StateMachine", | ||||||
|             ConnectionOverlays: uniDirectionalOverlays, |             ConnectionOverlays: uniDirectionalOverlays, | ||||||
|             HoverPaintStyle: { stroke: "#777", strokeWidth: 1 }, |             HoverPaintStyle: { stroke: "#777", strokeWidth: 1 }, | ||||||
|         Container: "relation-map-container" |             Container: this.$relationMapContainer.attr("id") | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|     jsPlumbInstance.registerConnectionType("uniDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: uniDirectionalOverlays }); |         this.jsPlumbInstance.registerConnectionType("uniDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: uniDirectionalOverlays }); | ||||||
|  |  | ||||||
|     jsPlumbInstance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays }); |         this.jsPlumbInstance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays }); | ||||||
|  |  | ||||||
|     jsPlumbInstance.registerConnectionType("inverse", { anchor:"Continuous", connector:"StateMachine", overlays: inverseRelationsOverlays }); |         this.jsPlumbInstance.registerConnectionType("inverse", { anchor:"Continuous", connector:"StateMachine", overlays: inverseRelationsOverlays }); | ||||||
|  |  | ||||||
|     jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays }); |         this.jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays }); | ||||||
|  |  | ||||||
|     jsPlumbInstance.bind("connection", connectionCreatedHandler); |         this.jsPlumbInstance.bind("connection", (info, originalEvent) => this.connectionCreatedHandler(info, originalEvent)); | ||||||
|  |  | ||||||
|         // so that canvas is not panned when clicking/dragging note box |         // so that canvas is not panned when clicking/dragging note box | ||||||
|     $relationMapContainer.on('mousedown touchstart', '.note-box, .connection-label', e => e.stopPropagation()); |         this.$relationMapContainer.on('mousedown touchstart', '.note-box, .connection-label', e => e.stopPropagation()); | ||||||
| } |  | ||||||
|  |  | ||||||
| function connectionContextMenuHandler(connection, event) { |  | ||||||
|     event.preventDefault(); |  | ||||||
|     event.stopPropagation(); |  | ||||||
|  |  | ||||||
|     const items = [ {title: "Remove relation", cmd: "remove", uiIcon: "trash"} ]; |  | ||||||
|  |  | ||||||
|     contextMenuWidget.initContextMenu(event, items, async (event, cmd) => { |  | ||||||
|         if (cmd === 'remove') { |  | ||||||
|             if (!await confirmDialog.confirm("Are you sure you want to remove the relation?")) { |  | ||||||
|                 return; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|             const relation = relations.find(rel => rel.attributeId === connection.id); |     async connectionCreatedHandler(info, originalEvent) { | ||||||
|  |  | ||||||
|             await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`); |  | ||||||
|  |  | ||||||
|             jsPlumbInstance.deleteConnection(connection); |  | ||||||
|  |  | ||||||
|             relations = relations.filter(relation => relation.attributeId !== connection.id); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function connectionCreatedHandler(info, originalEvent) { |  | ||||||
|         const connection = info.connection; |         const connection = info.connection; | ||||||
|  |  | ||||||
|         connection.bind("contextmenu", (obj, event) => { |         connection.bind("contextmenu", (obj, event) => { | ||||||
| @@ -342,7 +417,29 @@ async function connectionCreatedHandler(info, originalEvent) { | |||||||
|                 event.preventDefault(); |                 event.preventDefault(); | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|             connectionContextMenuHandler(connection, event); |                 event.preventDefault(); | ||||||
|  |                 event.stopPropagation(); | ||||||
|  |  | ||||||
|  |                 contextMenuWidget.initContextMenu(event, { | ||||||
|  |                     getContextMenuItems: () => { | ||||||
|  |                         return [ {title: "Remove relation", cmd: "remove", uiIcon: "trash"} ]; | ||||||
|  |                     }, | ||||||
|  |                     selectContextMenuItem: async (event, cmd) => { | ||||||
|  |                         if (cmd === 'remove') { | ||||||
|  |                             if (!await confirmDialog.confirm("Are you sure you want to remove the relation?")) { | ||||||
|  |                                 return; | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             const relation = this.relations.find(rel => rel.attributeId === connection.id); | ||||||
|  |  | ||||||
|  |                             await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`); | ||||||
|  |  | ||||||
|  |                             this.jsPlumbInstance.deleteConnection(connection); | ||||||
|  |  | ||||||
|  |                             this.relations = this.relations.filter(relation => relation.attributeId !== connection.id); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| @@ -362,15 +459,15 @@ async function connectionCreatedHandler(info, originalEvent) { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         if (!name || !name.trim()) { |         if (!name || !name.trim()) { | ||||||
|         jsPlumbInstance.deleteConnection(connection); |             this.jsPlumbInstance.deleteConnection(connection); | ||||||
|  |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     const targetNoteId = idToNoteId(connection.target.id); |         const targetNoteId = this.idToNoteId(connection.target.id); | ||||||
|     const sourceNoteId = idToNoteId(connection.source.id); |         const sourceNoteId = this.idToNoteId(connection.source.id); | ||||||
|  |  | ||||||
|     const relationExists = relations.some(rel => |         const relationExists = this.relations.some(rel => | ||||||
|             rel.targetNoteId === targetNoteId |             rel.targetNoteId === targetNoteId | ||||||
|             && rel.sourceNoteId === sourceNoteId |             && rel.sourceNoteId === sourceNoteId | ||||||
|             && rel.name === name); |             && rel.name === name); | ||||||
| @@ -378,92 +475,38 @@ async function connectionCreatedHandler(info, originalEvent) { | |||||||
|         if (relationExists) { |         if (relationExists) { | ||||||
|             await infoDialog.info("Connection '" + name + "' between these notes already exists."); |             await infoDialog.info("Connection '" + name + "' between these notes already exists."); | ||||||
|  |  | ||||||
|         jsPlumbInstance.deleteConnection(connection); |             this.jsPlumbInstance.deleteConnection(connection); | ||||||
|  |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         await server.put(`notes/${sourceNoteId}/relations/${name}/to/${targetNoteId}`); |         await server.put(`notes/${sourceNoteId}/relations/${name}/to/${targetNoteId}`); | ||||||
|  |  | ||||||
|     await refresh(); |         await this.refresh(); | ||||||
| } |  | ||||||
|  |  | ||||||
| $relationMapContainer.on("contextmenu", ".note-box", e => { |  | ||||||
|     const items = [ |  | ||||||
|         {title: "Remove note", cmd: "remove", uiIcon: "trash"}, |  | ||||||
|         {title: "Edit title", cmd: "edit-title", uiIcon: "pencil"}, |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     contextMenuWidget.initContextMenu(e, items, noteContextMenuHandler); |  | ||||||
|  |  | ||||||
|     return false; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| async function noteContextMenuHandler(event, cmd) { |  | ||||||
|     const $noteBox = $(event.originalTarget).closest(".note-box"); |  | ||||||
|     const $title = $noteBox.find(".title a"); |  | ||||||
|     const noteId = idToNoteId($noteBox.prop("id")); |  | ||||||
|  |  | ||||||
|     if (cmd === "remove") { |  | ||||||
|         if (!await confirmDialog.confirmDeleteNoteBoxWithNote($title.text())) { |  | ||||||
|             return; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         jsPlumbInstance.remove(noteIdToId(noteId)); |     saveData() { | ||||||
|  |         this.ctx.noteChanged(); | ||||||
|         if (confirmDialog.isDeleteNoteChecked()) { |  | ||||||
|             await server.remove("notes/" + noteId); |  | ||||||
|  |  | ||||||
|             // to force it to disappear from the tree |  | ||||||
|             treeService.reload(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         mapData.notes = mapData.notes.filter(note => note.noteId !== noteId); |     async createNoteBox(noteId, title, x, y) { | ||||||
|  |  | ||||||
|         relations = relations.filter(relation => relation.sourceNoteId !== noteId && relation.targetNoteId !== noteId); |  | ||||||
|  |  | ||||||
|         saveData(); |  | ||||||
|     } |  | ||||||
|     else if (cmd === "edit-title") { |  | ||||||
|         const title = await promptDialog.ask({ |  | ||||||
|             message: "Enter new note title:", |  | ||||||
|             defaultValue: $title.text() |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         if (!title) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         await server.put(`notes/${noteId}/change-title`, { title }); |  | ||||||
|  |  | ||||||
|         treeService.setNoteTitle(noteId, title); |  | ||||||
|  |  | ||||||
|         $title.text(title); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function saveData() { |  | ||||||
|     noteDetailService.noteChanged(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function createNoteBox(noteId, title, x, y) { |  | ||||||
|         const $noteBox = $("<div>") |         const $noteBox = $("<div>") | ||||||
|             .addClass("note-box") |             .addClass("note-box") | ||||||
|         .prop("id", noteIdToId(noteId)) |             .prop("id", this.noteIdToId(noteId)) | ||||||
|             .append($("<span>").addClass("title").html(await linkService.createNoteLink(noteId, title))) |             .append($("<span>").addClass("title").html(await linkService.createNoteLink(noteId, title))) | ||||||
|             .append($("<div>").addClass("endpoint").attr("title", "Start dragging relations from here and drop them on another note.")) |             .append($("<div>").addClass("endpoint").attr("title", "Start dragging relations from here and drop them on another note.")) | ||||||
|             .css("left", x + "px") |             .css("left", x + "px") | ||||||
|             .css("top", y + "px"); |             .css("top", y + "px"); | ||||||
|  |  | ||||||
|     jsPlumbInstance.getContainer().appendChild($noteBox[0]); |         this.jsPlumbInstance.getContainer().appendChild($noteBox[0]); | ||||||
|  |  | ||||||
|     jsPlumbInstance.draggable($noteBox[0], { |         this.jsPlumbInstance.draggable($noteBox[0], { | ||||||
|             start: params => {}, |             start: params => {}, | ||||||
|             drag: params => {}, |             drag: params => {}, | ||||||
|             stop: params => { |             stop: params => { | ||||||
|             const noteId = idToNoteId(params.el.id); |                 const noteId = this.idToNoteId(params.el.id); | ||||||
|  |  | ||||||
|             const note = mapData.notes.find(note => note.noteId === noteId); |                 const note = this.mapData.notes.find(note => note.noteId === noteId); | ||||||
|  |  | ||||||
|                 if (!note) { |                 if (!note) { | ||||||
|                     console.error(`Note ${noteId} not found!`); |                     console.error(`Note ${noteId} not found!`); | ||||||
| @@ -472,11 +515,11 @@ async function createNoteBox(noteId, title, x, y) { | |||||||
|  |  | ||||||
|                 [note.x, note.y] = params.finalPos; |                 [note.x, note.y] = params.finalPos; | ||||||
|  |  | ||||||
|             saveData(); |                 this.saveData(); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|     jsPlumbInstance.makeSource($noteBox[0], { |         this.jsPlumbInstance.makeSource($noteBox[0], { | ||||||
|             filter: ".endpoint", |             filter: ".endpoint", | ||||||
|             anchor: "Continuous", |             anchor: "Continuous", | ||||||
|             connectorStyle: { stroke: "#000", strokeWidth: 1 }, |             connectorStyle: { stroke: "#000", strokeWidth: 1 }, | ||||||
| @@ -486,56 +529,33 @@ async function createNoteBox(noteId, title, x, y) { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|     jsPlumbInstance.makeTarget($noteBox[0], { |         this.jsPlumbInstance.makeTarget($noteBox[0], { | ||||||
|             dropOptions: { hoverClass: "dragHover" }, |             dropOptions: { hoverClass: "dragHover" }, | ||||||
|             anchor: "Continuous", |             anchor: "Continuous", | ||||||
|             allowLoopback: true |             allowLoopback: true | ||||||
|         }); |         }); | ||||||
| } |  | ||||||
|  |  | ||||||
| async function refresh() { |  | ||||||
|     await loadNotesAndRelations(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| let clipboard = null; |  | ||||||
|  |  | ||||||
| $createChildNote.click(async () => { |  | ||||||
|     const title = await promptDialog.ask({ message: "Enter title of new note",  defaultValue: "new note" }); |  | ||||||
|  |  | ||||||
|     if (!title.trim()) { |  | ||||||
|         return; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const {note} = await server.post(`notes/${noteDetailService.getActiveNoteId()}/children`, { |     async refresh() { | ||||||
|         title, |         await this.loadNotesAndRelations(); | ||||||
|         target: 'into' |     } | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     infoService.showMessage("Click on canvas to place new note"); |     getZoom() { | ||||||
|  |  | ||||||
|     // reloading tree so that the new note appears there |  | ||||||
|     // no need to wait for it to finish |  | ||||||
|     treeService.reload(); |  | ||||||
|  |  | ||||||
|     clipboard = { noteId: note.noteId, title }; |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| function getZoom() { |  | ||||||
|         const matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*-?\d*\.?\d+,\s*-?\d*\.?\d+,\s*-?\d*\.?\d+\)/; |         const matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*-?\d*\.?\d+,\s*-?\d*\.?\d+,\s*-?\d*\.?\d+\)/; | ||||||
|  |  | ||||||
|     const matches = $relationMapContainer.css('transform').match(matrixRegex); |         const matches = this.$relationMapContainer.css('transform').match(matrixRegex); | ||||||
|  |  | ||||||
|         return matches[1]; |         return matches[1]; | ||||||
| } |     } | ||||||
|  |  | ||||||
| async function dropNoteOntoRelationMapHandler(ev) { |     async dropNoteOntoRelationMapHandler(ev) { | ||||||
|         ev.preventDefault(); |         ev.preventDefault(); | ||||||
|  |  | ||||||
|         const note = JSON.parse(ev.originalEvent.dataTransfer.getData("text")); |         const note = JSON.parse(ev.originalEvent.dataTransfer.getData("text")); | ||||||
|  |  | ||||||
|     let {x, y} = getMousePosition(ev); |         let {x, y} = this.getMousePosition(ev); | ||||||
|  |  | ||||||
|     const exists = mapData.notes.some(n => n.noteId === note.noteId); |         const exists = this.mapData.notes.some(n => n.noteId === note.noteId); | ||||||
|  |  | ||||||
|         if (exists) { |         if (exists) { | ||||||
|             await infoDialog.info(`Note "${note.title}" is already placed into the diagram`); |             await infoDialog.info(`Note "${note.title}" is already placed into the diagram`); | ||||||
| @@ -543,38 +563,33 @@ async function dropNoteOntoRelationMapHandler(ev) { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     mapData.notes.push({noteId: note.noteId, x, y}); |         this.mapData.notes.push({noteId: note.noteId, x, y}); | ||||||
|  |  | ||||||
|     saveData(); |         this.saveData(); | ||||||
|  |  | ||||||
|     await refresh(); |         await this.refresh(); | ||||||
| } |     } | ||||||
|  |  | ||||||
| function getMousePosition(evt) { |     getMousePosition(evt) { | ||||||
|     const rect = $relationMapContainer[0].getBoundingClientRect(); |         const rect = this.$relationMapContainer[0].getBoundingClientRect(); | ||||||
|  |  | ||||||
|     const zoom = getZoom(); |         const zoom = this.getZoom(); | ||||||
|  |  | ||||||
|         return { |         return { | ||||||
|             x: (evt.clientX - rect.left) / zoom, |             x: (evt.clientX - rect.left) / zoom, | ||||||
|             y: (evt.clientY - rect.top) / zoom |             y: (evt.clientY - rect.top) / zoom | ||||||
|         }; |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getContent() { | ||||||
|  |         return JSON.stringify(this.mapData); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     focus() {} | ||||||
|  |  | ||||||
|  |     onNoteChange() {} | ||||||
|  |  | ||||||
|  |     scrollToTop() {} | ||||||
| } | } | ||||||
|  |  | ||||||
| $resetPanZoomButton.click(() => { | export default NoteDetailRelationMap; | ||||||
|     // reset to initial pan & zoom state |  | ||||||
|     pzInstance.zoomTo(0, 0, 1 / getZoom()); |  | ||||||
|     pzInstance.moveTo(0, 0); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| $component.on("drop", dropNoteOntoRelationMapHandler); |  | ||||||
| $component.on("dragover", ev => ev.preventDefault()); |  | ||||||
|  |  | ||||||
| export default { |  | ||||||
|     show, |  | ||||||
|     getContent: () => JSON.stringify(mapData), |  | ||||||
|     focus: () => null, |  | ||||||
|     onNoteChange: () => null, |  | ||||||
|     cleanup, |  | ||||||
|     scrollToTop: () => null |  | ||||||
| } |  | ||||||
| @@ -3,40 +3,55 @@ import server from "./server.js"; | |||||||
| import noteDetailService from "./note_detail.js"; | import noteDetailService from "./note_detail.js"; | ||||||
| import attributeService from "./attributes.js"; | import attributeService from "./attributes.js"; | ||||||
|  |  | ||||||
| const $component = $('#note-detail-render'); | class NoteDetailRender { | ||||||
| const $noteDetailRenderHelp = $('#note-detail-render-help'); |     /** | ||||||
| const $noteDetailRenderContent = $('#note-detail-render-content'); |      * @param {NoteContext} ctx | ||||||
| const $renderButton = $('#render-button'); |      */ | ||||||
|  |     constructor(ctx) { | ||||||
|  |         this.ctx = ctx; | ||||||
|  |         this.$component = ctx.$noteTabContent.find('.note-detail-render'); | ||||||
|  |         this.$noteDetailRenderHelp = ctx.$noteTabContent.find('.note-detail-render-help'); | ||||||
|  |         this.$noteDetailRenderContent = ctx.$noteTabContent.find('.note-detail-render-content'); | ||||||
|  |         this.$renderButton = ctx.$noteTabContent.find('.render-button'); | ||||||
|  |  | ||||||
| async function render() { |         this.$renderButton.click(this.show); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async show() { | ||||||
|         const attributes = await attributeService.getAttributes(); |         const attributes = await attributeService.getAttributes(); | ||||||
|         const renderNotes = attributes.filter(attr => |         const renderNotes = attributes.filter(attr => | ||||||
|             attr.type === 'relation' |             attr.type === 'relation' | ||||||
|             && attr.name === 'renderNote' |             && attr.name === 'renderNote' | ||||||
|             && !!attr.value); |             && !!attr.value); | ||||||
|  |  | ||||||
|     $component.show(); |         this.$component.show(); | ||||||
|  |  | ||||||
|     $noteDetailRenderContent.empty(); |         this.$noteDetailRenderContent.empty(); | ||||||
|     $noteDetailRenderContent.toggle(renderNotes.length > 0); |         this.$noteDetailRenderContent.toggle(renderNotes.length > 0); | ||||||
|     $noteDetailRenderHelp.toggle(renderNotes.length === 0); |         this.$noteDetailRenderHelp.toggle(renderNotes.length === 0); | ||||||
|  |  | ||||||
|         for (const renderNote of renderNotes) { |         for (const renderNote of renderNotes) { | ||||||
|             const bundle = await server.get('script/bundle/' + renderNote.value); |             const bundle = await server.get('script/bundle/' + renderNote.value); | ||||||
|  |  | ||||||
|         $noteDetailRenderContent.append(bundle.html); |             this.$noteDetailRenderContent.append(bundle.html); | ||||||
|  |  | ||||||
|             await bundleService.executeBundle(bundle, noteDetailService.getActiveNote()); |             await bundleService.executeBundle(bundle, noteDetailService.getActiveNote()); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getContent() {} | ||||||
|  |  | ||||||
|  |     focus() {} | ||||||
|  |  | ||||||
|  |     onNoteChange() {} | ||||||
|  |  | ||||||
|  |     cleanup() { | ||||||
|  |         this.$noteDetailRenderContent.empty(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     scrollToTop() { | ||||||
|  |         this.$component.scrollTop(0); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| $renderButton.click(render); | export default NoteDetailRender; | ||||||
|  |  | ||||||
| export default { |  | ||||||
|     show: render, |  | ||||||
|     getContent: () => "", |  | ||||||
|     focus: () => null, |  | ||||||
|     onNoteChange: () => null, |  | ||||||
|     cleanup: () => $noteDetailRenderContent.empty(), |  | ||||||
|     scrollToTop: () => $component.scrollTop(0) |  | ||||||
| } |  | ||||||
| @@ -1,46 +1,55 @@ | |||||||
| import noteDetailService from "./note_detail.js"; | import noteDetailService from "./note_detail.js"; | ||||||
| import searchNotesService from "./search_notes.js"; | import searchNotesService from "./search_notes.js"; | ||||||
|  |  | ||||||
| const $searchString = $("#search-string"); | class NoteDetailSearch { | ||||||
| const $component = $('#note-detail-search'); |     /** | ||||||
| const $refreshButton = $('#note-detail-search-refresh-results-button'); |      * @param {NoteContext} ctx | ||||||
| const $help = $("#note-detail-search-help"); |      */ | ||||||
|  |     constructor(ctx) { | ||||||
|  |         this.ctx = ctx; | ||||||
|  |         this.$searchString = ctx.$noteTabContent.find(".search-string"); | ||||||
|  |         this.$component = ctx.$noteTabContent.find('.note-detail-search'); | ||||||
|  |         this.$help = ctx.$noteTabContent.find(".note-detail-search-help"); | ||||||
|  |         this.$refreshButton = ctx.$noteTabContent.find('.note-detail-search-refresh-results-button'); | ||||||
|  |  | ||||||
| function show() { |         this.$refreshButton.click(async () => { | ||||||
|     $help.html(searchNotesService.getHelpText()); |  | ||||||
|  |  | ||||||
|     $component.show(); |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|         const json = JSON.parse(noteDetailService.getActiveNote().content); |  | ||||||
|  |  | ||||||
|         $searchString.val(json.searchString); |  | ||||||
|     } |  | ||||||
|     catch (e) { |  | ||||||
|         console.log(e); |  | ||||||
|         $searchString.val(''); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     $searchString.on('input', noteDetailService.noteChanged); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getContent() { |  | ||||||
|     return JSON.stringify({ |  | ||||||
|         searchString: $searchString.val() |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| $refreshButton.click(async () => { |  | ||||||
|             await noteDetailService.saveNotesIfChanged(); |             await noteDetailService.saveNotesIfChanged(); | ||||||
|  |  | ||||||
|             await searchNotesService.refreshSearch(); |             await searchNotesService.refreshSearch(); | ||||||
| }); |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
| export default { |     show() { | ||||||
|     getContent, |         this.$help.html(searchNotesService.getHelpText()); | ||||||
|     show, |  | ||||||
|     focus: () => null, |         this.$component.show(); | ||||||
|     onNoteChange: () => null, |  | ||||||
|     cleanup: () => null, |         try { | ||||||
|     scrollToTop: () => null |             const json = JSON.parse(this.ctx.note.content); | ||||||
|  |  | ||||||
|  |             this.$searchString.val(json.searchString); | ||||||
|  |         } | ||||||
|  |         catch (e) { | ||||||
|  |             console.log(e); | ||||||
|  |             this.$searchString.val(''); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.$searchString.on('input', noteDetailService.noteChanged); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getContent() { | ||||||
|  |         return JSON.stringify({ | ||||||
|  |             searchString: this.$searchString.val() | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     focus() {} | ||||||
|  |  | ||||||
|  |     onNoteChange() {} | ||||||
|  |  | ||||||
|  |     cleanup() {} | ||||||
|  |  | ||||||
|  |     scrollToTop() {} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export default NoteDetailSearch; | ||||||
| @@ -53,7 +53,7 @@ class NoteDetailText { | |||||||
|  |  | ||||||
|         this.$component.show(); |         this.$component.show(); | ||||||
|  |  | ||||||
| //        this.textEditor.setData(this.ctx.note.content); |         this.textEditor.setData(this.ctx.note.content); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getContent() { |     getContent() { | ||||||
|   | |||||||
| @@ -103,8 +103,8 @@ ul.fancytree-container { | |||||||
|     display: none; |     display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-tab-content-template { | .note-tab-content-template { | ||||||
|     display: none; |     display: none !important; | ||||||
| } | } | ||||||
|  |  | ||||||
| .note-tab-content { | .note-tab-content { | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div id="note-tab-container"> | <div id="note-tab-container"> | ||||||
|     <div id="note-tab-content-template" class="note-tab-content"> |     <div class="note-tab-content note-tab-content-template"> | ||||||
|         <% include title.ejs %> |         <% include title.ejs %> | ||||||
|  |  | ||||||
|         <div class="note-detail-script-area"></div> |         <div class="note-detail-script-area"></div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user