mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	separated text and code handling out of note detail service
This commit is contained in:
		
							
								
								
									
										2
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							| @@ -13,7 +13,7 @@ import contextMenu from './context_menu.js'; | ||||
| import dragAndDropSetup from './drag_and_drop.js'; | ||||
| import exportService from './export.js'; | ||||
| import link from './link.js'; | ||||
| import messaging from './messaging.js'; | ||||
| import messagingService from './messaging.js'; | ||||
| import noteDetailService from './note_detail.js'; | ||||
| import noteType from './note_type.js'; | ||||
| import protected_session from './protected_session.js'; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import treeService from './tree.js'; | ||||
| import noteDetailService from './note_detail.js'; | ||||
| import noteDetailText from './note_detail_text.js'; | ||||
| import treeUtils from './tree_utils.js'; | ||||
|  | ||||
| function getNotePathFromLink(url) { | ||||
| @@ -75,14 +75,14 @@ function goToLink(e) { | ||||
| } | ||||
|  | ||||
| function addLinkToEditor(linkTitle, linkHref) { | ||||
|     const editor = noteDetailService.getEditor(); | ||||
|     const editor = noteDetailText.getEditor(); | ||||
|     const doc = editor.document; | ||||
|  | ||||
|     doc.enqueueChanges(() => editor.data.insertLink(linkTitle, linkHref), doc.selection); | ||||
| } | ||||
|  | ||||
| function addTextToEditor(text) { | ||||
|     const editor = noteDetailService.getEditor(); | ||||
|     const editor = noteDetailText.getEditor(); | ||||
|     const doc = editor.document; | ||||
|  | ||||
|     doc.enqueueChanges(() => editor.data.insertText(text), doc.selection); | ||||
|   | ||||
| @@ -9,11 +9,12 @@ import bundleService from "./bundle.js"; | ||||
| import infoService from "./info.js"; | ||||
| import treeCache from "./tree_cache.js"; | ||||
| import NoteFull from "../entities/note_full.js"; | ||||
| import noteDetailCode from './note_detail_code.js'; | ||||
| import noteDetailText from './note_detail_text.js'; | ||||
|  | ||||
| const $noteTitle = $("#note-title"); | ||||
|  | ||||
| const $noteDetailText = $('#note-detail-text'); | ||||
| const $noteDetailCode = $('#note-detail-code'); | ||||
| const $noteDetailComponents = $(".note-detail-component"); | ||||
| const $noteDetailSearch = $('#note-detail-search'); | ||||
| const $noteDetailRender = $('#note-detail-render'); | ||||
| const $noteDetailAttachment = $('#note-detail-attachment'); | ||||
| @@ -31,11 +32,6 @@ const $attachmentDownload = $("#attachment-download"); | ||||
| const $attachmentOpen = $("#attachment-open"); | ||||
| const $searchString = $("#search-string"); | ||||
|  | ||||
| const $executeScriptButton = $("#execute-script-button"); | ||||
|  | ||||
| let textEditor = null; | ||||
| let codeEditor = null; | ||||
|  | ||||
| let currentNote = null; | ||||
|  | ||||
| let noteChangeDisabled = false; | ||||
| @@ -50,6 +46,12 @@ function getCurrentNoteId() { | ||||
|     return currentNote ? currentNote.noteId : null; | ||||
| } | ||||
|  | ||||
| function getCurrentNoteType() { | ||||
|     const currentNote = getCurrentNote(); | ||||
|  | ||||
|     return currentNote ? currentNote.type : null; | ||||
| } | ||||
|  | ||||
| function noteChanged() { | ||||
|     if (noteChangeDisabled) { | ||||
|         return; | ||||
| @@ -90,18 +92,10 @@ async function saveNoteIfChanged() { | ||||
|  | ||||
| function updateNoteFromInputs(note) { | ||||
|     if (note.type === 'text') { | ||||
|         let content = textEditor.getData(); | ||||
|  | ||||
|         // if content is only tags/whitespace (typically <p> </p>), then just make it empty | ||||
|         // this is important when setting new note to code | ||||
|         if (jQuery(content).text().trim() === '' && !content.includes("<img")) { | ||||
|             content = ''; | ||||
|         } | ||||
|  | ||||
|         note.content = content; | ||||
|         note.content = noteDetailText.getContent(); | ||||
|     } | ||||
|     else if (note.type === 'code') { | ||||
|         note.content = codeEditor.getValue(); | ||||
|         note.content = noteDetailCode.getContent(); | ||||
|     } | ||||
|     else if (note.type === 'search') { | ||||
|         note.content = JSON.stringify({ | ||||
| @@ -151,7 +145,7 @@ async function showRenderNote() { | ||||
|  | ||||
|     $noteDetailRender.html(bundle.html); | ||||
|  | ||||
|     bundleService.executeBundle(bundle); | ||||
|     await bundleService.executeBundle(bundle); | ||||
| } | ||||
|  | ||||
| async function showFileNote() { | ||||
| @@ -165,60 +159,6 @@ async function showFileNote() { | ||||
|     $attachmentFileType.text(currentNote.mime); | ||||
| } | ||||
|  | ||||
| async function showTextNote() { | ||||
|     if (!textEditor) { | ||||
|         await utils.requireLibrary(utils.CKEDITOR); | ||||
|  | ||||
|         textEditor = await BalloonEditor.create($noteDetailText[0], {}); | ||||
|  | ||||
|         textEditor.document.on('change', noteChanged); | ||||
|     } | ||||
|  | ||||
|     // temporary workaround for https://github.com/ckeditor/ckeditor5-enter/issues/49 | ||||
|     textEditor.setData(currentNote.content || "<p></p>"); | ||||
|  | ||||
|     $noteDetailText.show(); | ||||
| } | ||||
|  | ||||
| async function showCodeNote() { | ||||
|     if (!codeEditor) { | ||||
|         await utils.requireLibrary(utils.CODE_MIRROR); | ||||
|  | ||||
|         CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; | ||||
|         CodeMirror.keyMap.default["Tab"] = "indentMore"; | ||||
|  | ||||
|         CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; | ||||
|  | ||||
|         codeEditor = CodeMirror($("#note-detail-code")[0], { | ||||
|             value: "", | ||||
|             viewportMargin: Infinity, | ||||
|             indentUnit: 4, | ||||
|             matchBrackets: true, | ||||
|             matchTags: {bothTags: true}, | ||||
|             highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}, | ||||
|             lint: true, | ||||
|             gutters: ["CodeMirror-lint-markers"], | ||||
|             lineNumbers: true | ||||
|         }); | ||||
|  | ||||
|         codeEditor.on('change', noteChanged); | ||||
|     } | ||||
|  | ||||
|     $noteDetailCode.show(); | ||||
|  | ||||
|     // this needs to happen after the element is shown, otherwise the editor won't be refresheds | ||||
|     codeEditor.setValue(currentNote.content); | ||||
|  | ||||
|     const info = CodeMirror.findModeByMIME(currentNote.mime); | ||||
|  | ||||
|     if (info) { | ||||
|         codeEditor.setOption("mode", info.mime); | ||||
|         CodeMirror.autoLoadMode(codeEditor, info.mode); | ||||
|     } | ||||
|  | ||||
|     codeEditor.refresh(); | ||||
| } | ||||
|  | ||||
| function showSearchNote() { | ||||
|     $noteDetailSearch.show(); | ||||
|  | ||||
| @@ -235,6 +175,18 @@ function showSearchNote() { | ||||
|     $searchString.on('input', noteChanged); | ||||
| } | ||||
|  | ||||
| async function handleProtectedSession() { | ||||
|     await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false); | ||||
|  | ||||
|     if (currentNote.isProtected) { | ||||
|         protectedSessionHolder.touchProtectedSession(); | ||||
|     } | ||||
|  | ||||
|     // this might be important if we focused on protected note when not in protected note and we got a dialog | ||||
|     // to login, but we chose instead to come to another node - at that point the dialog is still visible and this will close it. | ||||
|     protectedSessionService.ensureDialogIsClosed(); | ||||
| } | ||||
|  | ||||
| async function loadNoteToEditor(noteId) { | ||||
|     currentNote = await loadNote(noteId); | ||||
|  | ||||
| @@ -246,48 +198,39 @@ async function loadNoteToEditor(noteId) { | ||||
|  | ||||
|     $noteIdDisplay.html(noteId); | ||||
|  | ||||
|     await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false); | ||||
|  | ||||
|     if (currentNote.isProtected) { | ||||
|         protectedSessionHolder.touchProtectedSession(); | ||||
|     } | ||||
|  | ||||
|     // this might be important if we focused on protected note when not in protected note and we got a dialog | ||||
|     // to login, but we chose instead to come to another node - at that point the dialog is still visible and this will close it. | ||||
|     protectedSessionService.ensureDialogIsClosed(); | ||||
|     await handleProtectedSession(); | ||||
|  | ||||
|     $noteDetailWrapper.show(); | ||||
|  | ||||
|     noteChangeDisabled = true; | ||||
|  | ||||
|     $noteTitle.val(currentNote.title); | ||||
|     try { | ||||
|         $noteTitle.val(currentNote.title); | ||||
|  | ||||
|     noteTypeService.setNoteType(currentNote.type); | ||||
|     noteTypeService.setNoteMime(currentNote.mime); | ||||
|         noteTypeService.setNoteType(currentNote.type); | ||||
|         noteTypeService.setNoteMime(currentNote.mime); | ||||
|  | ||||
|     $noteDetailText.hide(); | ||||
|     $noteDetailSearch.hide(); | ||||
|     $noteDetailCode.hide(); | ||||
|     $noteDetailRender.html('').hide(); | ||||
|     $noteDetailAttachment.hide(); | ||||
|         $noteDetailComponents.hide(); | ||||
|  | ||||
|     if (currentNote.type === 'render') { | ||||
|         await showRenderNote(); | ||||
|         if (currentNote.type === 'render') { | ||||
|             await showRenderNote(); | ||||
|         } | ||||
|         else if (currentNote.type === 'file') { | ||||
|             await showFileNote(); | ||||
|         } | ||||
|         else if (currentNote.type === 'text') { | ||||
|             await noteDetailText.showTextNote(); | ||||
|         } | ||||
|         else if (currentNote.type === 'code') { | ||||
|             await noteDetailCode.showCodeNote(); | ||||
|         } | ||||
|         else if (currentNote.type === 'search') { | ||||
|             showSearchNote(); | ||||
|         } | ||||
|     } | ||||
|     else if (currentNote.type === 'file') { | ||||
|         await showFileNote(); | ||||
|     finally { | ||||
|         noteChangeDisabled = false; | ||||
|     } | ||||
|     else if (currentNote.type === 'text') { | ||||
|         await showTextNote(); | ||||
|     } | ||||
|     else if (currentNote.type === 'code') { | ||||
|         await showCodeNote(); | ||||
|     } | ||||
|     else if (currentNote.type === 'search') { | ||||
|         showSearchNote(); | ||||
|     } | ||||
|  | ||||
|     noteChangeDisabled = false; | ||||
|  | ||||
|     setNoteBackgroundIfProtected(currentNote); | ||||
|     treeService.setBranchBackgroundBasedOnProtectedStatus(noteId); | ||||
| @@ -295,7 +238,7 @@ async function loadNoteToEditor(noteId) { | ||||
|     // after loading new note make sure editor is scrolled to the top | ||||
|     $noteDetailWrapper.scrollTop(0); | ||||
|  | ||||
|     loadLabelList(); | ||||
|     await loadLabelList(); | ||||
| } | ||||
|  | ||||
| async function loadLabelList() { | ||||
| @@ -323,18 +266,14 @@ async function loadNote(noteId) { | ||||
|     return new NoteFull(treeCache, row); | ||||
| } | ||||
|  | ||||
| function getEditor() { | ||||
|     return textEditor; | ||||
| } | ||||
|  | ||||
| function focus() { | ||||
|     const note = getCurrentNote(); | ||||
|  | ||||
|     if (note.type === 'text') { | ||||
|         $noteDetailText.focus(); | ||||
|         noteDetailText.focus(); | ||||
|     } | ||||
|     else if (note.type === 'code') { | ||||
|         codeEditor.focus(); | ||||
|         noteDetailCode.focus(); | ||||
|     } | ||||
|     else if (note.type === 'render' || note.type === 'file' || note.type === 'search') { | ||||
|         // do nothing | ||||
| @@ -344,31 +283,6 @@ function focus() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function getCurrentNoteType() { | ||||
|     const currentNote = getCurrentNote(); | ||||
|  | ||||
|     return currentNote ? currentNote.type : null; | ||||
| } | ||||
|  | ||||
| async function executeCurrentNote() { | ||||
|     if (getCurrentNoteType() === 'code') { | ||||
|         // make sure note is saved so we load latest changes | ||||
|         await saveNoteIfChanged(); | ||||
|  | ||||
|         if (currentNote.mime.endsWith("env=frontend")) { | ||||
|             const bundle = await server.get('script/bundle/' + getCurrentNoteId()); | ||||
|  | ||||
|             bundleService.executeBundle(bundle); | ||||
|         } | ||||
|  | ||||
|         if (currentNote.mime.endsWith("env=backend")) { | ||||
|             await server.post('script/run/' + getCurrentNoteId()); | ||||
|         } | ||||
|  | ||||
|         infoService.showMessage("Note executed"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| $attachmentDownload.click(() => utils.download(getAttachmentUrl())); | ||||
|  | ||||
| $attachmentOpen.click(() => { | ||||
| @@ -405,18 +319,13 @@ $(document).ready(() => { | ||||
|         treeService.setNoteTitle(getCurrentNoteId(), title); | ||||
|     }); | ||||
|  | ||||
|     // so that tab jumps from note title (which has tabindex 1) | ||||
|     $noteDetailText.attr("tabindex", 2); | ||||
|     noteDetailText.focus(); | ||||
| }); | ||||
|  | ||||
| // this makes sure that when user e.g. reloads the page or navigates away from the page, the note's content is saved | ||||
| // this sends the request asynchronously and doesn't wait for result | ||||
| $(window).on('beforeunload', saveNoteIfChanged); | ||||
|  | ||||
| $(document).bind('keydown', "ctrl+return", executeCurrentNote); | ||||
|  | ||||
| $executeScriptButton.click(executeCurrentNote()); | ||||
|  | ||||
| setInterval(saveNoteIfChanged, 5000); | ||||
|  | ||||
| export default { | ||||
| @@ -430,7 +339,8 @@ export default { | ||||
|     getCurrentNoteType, | ||||
|     getCurrentNoteId, | ||||
|     newNoteCreated, | ||||
|     getEditor, | ||||
|     focus, | ||||
|     loadLabelList | ||||
|     loadLabelList, | ||||
|     saveNoteIfChanged, | ||||
|     noteChanged | ||||
| }; | ||||
							
								
								
									
										90
									
								
								src/public/javascripts/services/note_detail_code.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/public/javascripts/services/note_detail_code.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| import utils from "./utils.js"; | ||||
| import bundleService from "./bundle.js"; | ||||
| import infoService from "./info.js"; | ||||
| import server from "./server.js"; | ||||
| import noteDetailService from "./note_detail.js"; | ||||
|  | ||||
| let codeEditor = null; | ||||
|  | ||||
| const $noteDetailCode = $('#note-detail-code'); | ||||
| const $executeScriptButton = $("#execute-script-button"); | ||||
|  | ||||
| async function showCodeNote() { | ||||
|     if (!codeEditor) { | ||||
|         await utils.requireLibrary(utils.CODE_MIRROR); | ||||
|  | ||||
|         CodeMirror.keyMap.default["Shift-Tab"] = "indentLess"; | ||||
|         CodeMirror.keyMap.default["Tab"] = "indentMore"; | ||||
|  | ||||
|         CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js'; | ||||
|  | ||||
|         codeEditor = CodeMirror($("#note-detail-code")[0], { | ||||
|             value: "", | ||||
|             viewportMargin: Infinity, | ||||
|             indentUnit: 4, | ||||
|             matchBrackets: true, | ||||
|             matchTags: {bothTags: true}, | ||||
|             highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}, | ||||
|             lint: true, | ||||
|             gutters: ["CodeMirror-lint-markers"], | ||||
|             lineNumbers: true | ||||
|         }); | ||||
|  | ||||
|         codeEditor.on('change', noteDetailService.noteChanged); | ||||
|     } | ||||
|  | ||||
|     $noteDetailCode.show(); | ||||
|  | ||||
|     const currentNote = noteDetailService.getCurrentNote(); | ||||
|  | ||||
|     // this needs to happen after the element is shown, otherwise the editor won't be refresheds | ||||
|     codeEditor.setValue(currentNote.content); | ||||
|  | ||||
|     const info = CodeMirror.findModeByMIME(currentNote.mime); | ||||
|  | ||||
|     if (info) { | ||||
|         codeEditor.setOption("mode", info.mime); | ||||
|         CodeMirror.autoLoadMode(codeEditor, info.mode); | ||||
|     } | ||||
|  | ||||
|     codeEditor.refresh(); | ||||
| } | ||||
|  | ||||
| function getContent() { | ||||
|     return codeEditor.getValue(); | ||||
| } | ||||
|  | ||||
| function focus() { | ||||
|     codeEditor.focus(); | ||||
| } | ||||
|  | ||||
| async function executeCurrentNote() { | ||||
|     if (noteDetailService.getCurrentNoteType() === 'code') { | ||||
|         // make sure note is saved so we load latest changes | ||||
|         await noteDetailService.saveNoteIfChanged(); | ||||
|  | ||||
|         const currentNote = noteDetailService.getCurrentNote(); | ||||
|  | ||||
|         if (currentNote.mime.endsWith("env=frontend")) { | ||||
|             const bundle = await server.get('script/bundle/' + getCurrentNoteId()); | ||||
|  | ||||
|             bundleService.executeBundle(bundle); | ||||
|         } | ||||
|  | ||||
|         if (currentNote.mime.endsWith("env=backend")) { | ||||
|             await server.post('script/run/' + getCurrentNoteId()); | ||||
|         } | ||||
|  | ||||
|         infoService.showMessage("Note executed"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| $(document).bind('keydown', "ctrl+return", executeCurrentNote); | ||||
|  | ||||
| $executeScriptButton.click(executeCurrentNote); | ||||
|  | ||||
| export default { | ||||
|     showCodeNote, | ||||
|     getContent, | ||||
|     focus | ||||
| } | ||||
							
								
								
									
										48
									
								
								src/public/javascripts/services/note_detail_text.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/public/javascripts/services/note_detail_text.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| import utils from "./utils.js"; | ||||
| import noteDetailService from './note_detail.js'; | ||||
|  | ||||
| const $noteDetailText = $('#note-detail-text'); | ||||
|  | ||||
| let textEditor = null; | ||||
|  | ||||
| async function showTextNote() { | ||||
|     if (!textEditor) { | ||||
|         await utils.requireLibrary(utils.CKEDITOR); | ||||
|  | ||||
|         textEditor = await BalloonEditor.create($noteDetailText[0], {}); | ||||
|  | ||||
|         textEditor.document.on('change', noteDetailService.noteChanged); | ||||
|     } | ||||
|  | ||||
|     // temporary workaround for https://github.com/ckeditor/ckeditor5-enter/issues/49 | ||||
|     textEditor.setData(noteDetailService.getCurrentNote().content || "<p></p>"); | ||||
|  | ||||
|     $noteDetailText.show(); | ||||
| } | ||||
|  | ||||
| function getContent() { | ||||
|     let content = textEditor.getData(); | ||||
|  | ||||
|     // if content is only tags/whitespace (typically <p> </p>), then just make it empty | ||||
|     // this is important when setting new note to code | ||||
|     if (jQuery(content).text().trim() === '' && !content.includes("<img")) { | ||||
|         content = ''; | ||||
|     } | ||||
|  | ||||
|     return content; | ||||
| } | ||||
|  | ||||
| function focus() { | ||||
|     $noteDetailText.focus(); | ||||
| } | ||||
|  | ||||
| function getEditor() { | ||||
|     return textEditor; | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     showTextNote, | ||||
|     getEditor, | ||||
|     getContent, | ||||
|     focus | ||||
| } | ||||
| @@ -28,6 +28,10 @@ | ||||
|     align-items: center; | ||||
| } | ||||
|  | ||||
| .note-detail-component { | ||||
|     display: none; | ||||
| } | ||||
|  | ||||
| #note-detail-text { | ||||
|     border: 0 !important; | ||||
|     box-shadow: none !important; | ||||
|   | ||||
| @@ -133,9 +133,9 @@ | ||||
|       </div> | ||||
|  | ||||
|       <div style="position: relative; overflow: auto; grid-area: note-content; padding-left: 10px; padding-top: 10px;" id="note-detail-wrapper"> | ||||
|         <div id="note-detail-text" style="display: none;"></div> | ||||
|         <div id="note-detail-text" class="note-detail-component"></div> | ||||
|  | ||||
|         <div id="note-detail-search" style="display: none;"> | ||||
|         <div id="note-detail-search" class="note-detail-component"> | ||||
|           <div style="display: flex; align-items: center;"> | ||||
|             <strong>Search string:    </strong> | ||||
|             <textarea rows="4" cols="50" id="search-string"></textarea> | ||||
| @@ -173,11 +173,11 @@ | ||||
|           </p> | ||||
|         </div> | ||||
|  | ||||
|         <div id="note-detail-code" style="display: none;"></div> | ||||
|         <div id="note-detail-code" class="note-detail-component"></div> | ||||
|  | ||||
|         <div id="note-detail-render" style="display: none;"></div> | ||||
|         <div id="note-detail-render" class="note-detail-component"></div> | ||||
|  | ||||
|         <div id="note-detail-attachment" style="display: none;"> | ||||
|         <div id="note-detail-attachment" class="note-detail-component"> | ||||
|           <table id="attachment-table"> | ||||
|             <tr> | ||||
|               <th>File name:</th> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user