├── LICENSE ├── README.md ├── tje.css ├── tje.html ├── tje.js ├── x-spreadsheet.css └── x-spreadsheet.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Sean Ottey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trilium JSON Editor (tje) 2 | 3 | The Trilium JSON Editor is a table (spreadsheet) type editor for single depth JSON notes. It allows for JSON editing of any Code→JSON note type. It provides auto save functionality, formatting, formulas and more. The heart of this is the amazing x-spreadsheet javascript repo located here. 4 | 5 | The original js and css files from the repo are used here untouched (mostly, see comments at the top of each file to see what has been changed) and additional functionality is located in the TJE js note. Minimal css is included in TJE css, including the inline inclusion of the toolbar svg images to allow for use offline. 6 | 7 | There are no external dependancies so this should all work identically both online and offline. 8 | 9 | image 10 | 11 | 12 | 13 | ## Installation 14 | 15 | To install, import the release zip file with safe import unselected. If you wish, you can do a manual review and install of the files. 16 | 17 | Notes: 18 | 19 | - The Root should be a note of type RenderNote. It should have a relation attribute (~renderNote) pointing to TJE html. 20 | - The CSS files as well as the x-spreadsheet min js notes are actually HTML notes, wrapped in 26 | -------------------------------------------------------------------------------- /tje.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 | 13 | No Note Selected 14 | 15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /tje.js: -------------------------------------------------------------------------------- 1 | /* ******************************** */ 2 | /* Custom tje functions and loading */ 3 | /* NOTE TYPE SHOULD BE JS Frontend. */ 4 | /* ******************************** */ 5 | 6 | var currSpreadsheet = undefined; 7 | var jsonNotes = undefined; 8 | const jsonNotesData = document.getElementById("tjeNotes"); 9 | const sheetDiv = document.getElementById("tje_container"); 10 | const sheet = document.getElementsByClassName("x-spreadsheet-sheet"); 11 | const autosaveCheckbox = document.getElementById("tjeAutosaveCheckbox"); 12 | const noteName = document.getElementById("tjeNoteName"); 13 | 14 | async function init() { 15 | var saveIcon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNTc3MTc3MDkyOTg4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI2NzgiIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PC9zdHlsZT48L2RlZnM+PHBhdGggZD0iTTIxMy4zMzMzMzMgMTI4aDU5Ny4zMzMzMzRhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMSA4NS4zMzMzMzMgODUuMzMzMzMzdjU5Ny4zMzMzMzRhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMS04NS4zMzMzMzMgODUuMzMzMzMzSDIxMy4zMzMzMzNhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMS04NS4zMzMzMzMtODUuMzMzMzMzVjIxMy4zMzMzMzNhODUuMzMzMzMzIDg1LjMzMzMzMyAwIDAgMSA4NS4zMzMzMzMtODUuMzMzMzMzeiBtMzY2LjkzMzMzNCAxMjhoMzQuMTMzMzMzYTI1LjYgMjUuNiAwIDAgMSAyNS42IDI1LjZ2MTE5LjQ2NjY2N2EyNS42IDI1LjYgMCAwIDEtMjUuNiAyNS42aC0zNC4xMzMzMzNhMjUuNiAyNS42IDAgMCAxLTI1LjYtMjUuNlYyODEuNmEyNS42IDI1LjYgMCAwIDEgMjUuNi0yNS42ek0yMTMuMzMzMzMzIDIxMy4zMzMzMzN2NTk3LjMzMzMzNGg1OTcuMzMzMzM0VjIxMy4zMzMzMzNIMjEzLjMzMzMzM3ogbTEyOCAwdjI1NmgzNDEuMzMzMzM0VjIxMy4zMzMzMzNoODUuMzMzMzMzdjI5OC42NjY2NjdhNDIuNjY2NjY3IDQyLjY2NjY2NyAwIDAgMS00Mi42NjY2NjcgNDIuNjY2NjY3SDI5OC42NjY2NjdhNDIuNjY2NjY3IDQyLjY2NjY2NyAwIDAgMS00Mi42NjY2NjctNDIuNjY2NjY3VjIxMy4zMzMzMzNoODUuMzMzMzMzek0yNTYgMjEzLjMzMzMzM2g4NS4zMzMzMzMtODUuMzMzMzMzeiBtNDI2LjY2NjY2NyAwaDg1LjMzMzMzMy04NS4zMzMzMzN6IG0wIDU5Ny4zMzMzMzR2LTEyOEgzNDEuMzMzMzMzdjEyOEgyNTZ2LTE3MC42NjY2NjdhNDIuNjY2NjY3IDQyLjY2NjY2NyAwIDAgMSA0Mi42NjY2NjctNDIuNjY2NjY3aDQyNi42NjY2NjZhNDIuNjY2NjY3IDQyLjY2NjY2NyAwIDAgMSA0Mi42NjY2NjcgNDIuNjY2NjY3djE3MC42NjY2NjdoLTg1LjMzMzMzM3ogbTg1LjMzMzMzMyAwaC04NS4zMzMzMzMgODUuMzMzMzMzek0zNDEuMzMzMzMzIDgxMC42NjY2NjdIMjU2aDg1LjMzMzMzM3oiIHAtaWQ9IjI2NzkiIGZpbGw9IiMyYzJjMmMiPjwvcGF0aD48L3N2Zz4='; 16 | currSpreadsheet = x_spreadsheet('#tje_container', { showTopBar: true, showBottomBar: true,extendToolbar: {left: [{ tip: 'Save current spreadsheet', icon: saveIcon, onClick: onSaveContents}]}}); 17 | 18 | jsonNotes = await api.runOnBackend(getJSONNotes); 19 | addJSONNotesToList(jsonNotes); 20 | 21 | jsonNotesData.addEventListener("change", onJSONNoteSelect); 22 | autosaveCheckbox.addEventListener("change", onAutosaveChange); 23 | showSpreadsheet(false); 24 | } 25 | 26 | init(); 27 | 28 | /* *************** */ 29 | /* UTILITY METHODS */ 30 | /* *************** */ 31 | 32 | 33 | function onSheetChange(event) { 34 | if( autosaveCheckbox.checked) { 35 | onSaveContents(); 36 | } 37 | } 38 | 39 | function onAutosaveChange(event) { 40 | 41 | } 42 | 43 | /* ************************************* */ 44 | /* Get all notes that are of type 'json' */ 45 | /* ************************************* */ 46 | function getJSONNotes() { 47 | const jsonNotes = api.searchForNotes("note.type = code AND note.mime = 'application/json' AND #!tjeExclude"); 48 | return jsonNotes.map(n => ({ 49 | id: n.noteId, 50 | title: n.title, 51 | })); 52 | } 53 | /* ************************** */ 54 | /* Get contents of note by id */ 55 | /* ************************** */ 56 | async function getNoteContent(noteId) { 57 | var jsonData = await api.runOnBackend((id) => api.getNote(id).getContent(), [noteId]); 58 | if (jsonData == "") { 59 | jsonData = "[{\"name\":\"sheet1\"}]"; 60 | } 61 | return JSON.parse(jsonData); 62 | } 63 | 64 | /* ************************** */ 65 | /* Get path of note by id */ 66 | /* ************************** */ 67 | function getNoteLink(noteId) { 68 | var notePath = api.runOnBackend((id) => api.getNote(id).getBestNotePath(), [noteId]); 69 | return notePath; 70 | } 71 | 72 | /* ************************** */ 73 | /* Set contents of note by id */ 74 | /* ************************** */ 75 | function setNoteContent(noteId, content) { 76 | var content = JSON.stringify(currSpreadsheet.getData()); 77 | api.runOnBackend((noteId, content) => api.getNote(noteId).setContent(content), [noteId, content]); 78 | } 79 | 80 | /* **************************** */ 81 | /* When a json note is selected */ 82 | /* **************************** */ 83 | function onJSONNoteSelect(e) { 84 | const noteId = jsonNotesData.value; 85 | 86 | resetTable(); 87 | 88 | if (noteId != "") { 89 | loadContents(noteId); 90 | } 91 | } 92 | 93 | /* ********************** */ 94 | /* When save is requested */ 95 | /* ********************** */ 96 | function onSaveContents(event) { 97 | const showMessage = (event != undefined); 98 | 99 | var content = JSON.stringify(currSpreadsheet.getData(), null, 4); 100 | const noteId = jsonNotesData.value; 101 | 102 | if (noteId) { 103 | setNoteContent(noteId, content); 104 | if (showMessage) { api.showMessage("Data saved."); } 105 | console.log("Data saved."); 106 | } else { 107 | if (showMessage) { api.showWarning("Note not found. Data NOT saved."); } 108 | console.log("Note not found. Data NOT saved."); 109 | } 110 | } 111 | 112 | /* ************************************* */ 113 | /* Add json notes to the select control */ 114 | /* ************************************* */ 115 | function addJSONNotesToList(jsonObjs) { 116 | resetJSONNotesList(jsonNotesData); 117 | 118 | if (jsonObjs.length > 0) { 119 | //addOptionToSelect(jsonNotesData, "Select a note...", ""); 120 | $(jsonObjs).each(function(){ addOptionToSelect(jsonNotesData, this.title, this.id); }); 121 | onJSONNoteSelect(); 122 | } else 123 | { 124 | addOptionToSelect(jsonNotesData, "No JSON Notes Found...", ""); 125 | } 126 | } 127 | 128 | /* ******************************************** */ 129 | /* Utility to add an option to the Notes Select */ 130 | /* ******************************************** */ 131 | function addOptionToSelect(selectElem, text, value) { 132 | var elem = document.createElement("option"); 133 | elem.innerText = text; 134 | elem.value = value; 135 | selectElem.appendChild(elem); 136 | } 137 | 138 | /* ************************************** */ 139 | /* Zero out json note list and add prompt */ 140 | /* ************************************** */ 141 | function resetJSONNotesList(list) { 142 | while (list.options.length > 0) { 143 | list.options[0].remove(); 144 | } 145 | } 146 | 147 | /* *********************************************** */ 148 | /* Create table with contents from provided noteId */ 149 | /* *********************************************** */ 150 | async function loadContents(noteId) { 151 | var jsonString = await getNoteContent(noteId); 152 | var link = await getNoteLink(noteId); 153 | link = "#" + link.join(); 154 | link = link.replaceAll(",", "/"); 155 | tjeNoteName.innerHTML = "" + jsonNotesData.options[jsonNotesData.selectedIndex].text + ""; 156 | currSpreadsheet.loadData(jsonString); 157 | showSpreadsheet(true); 158 | 159 | 160 | } 161 | 162 | /* ******************** */ 163 | /* Clear existing table */ 164 | /* ******************** */ 165 | function resetTable() { 166 | var newSheet = JSON.parse('[{"name":"New"}]'); 167 | currSpreadsheet.loadData(newSheet); 168 | showSpreadsheet(false); 169 | } 170 | 171 | function showSpreadsheet(show) { 172 | if (show) { 173 | sheetDiv.style.display = "block"; 174 | sheet[0].addEventListener("keydown", onSheetChange); 175 | } else { 176 | sheetDiv.style.display = "none"; 177 | sheet[0].removeEventListener("keydown", onSheetChange); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /x-spreadsheet.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | --------------------------------------------------------------------------------