mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Compare commits
	
		
			12 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5b30291601 | ||
|  | 5193f073e9 | ||
|  | 6c7d8a9667 | ||
|  | 5e9bedd903 | ||
|  | e712990c03 | ||
|  | 91487b338a | ||
|  | 3ff24d53e5 | ||
|  | 94c904fb40 | ||
|  | 5f258fbbbf | ||
|  | bf9ad976b9 | ||
|  | 434d8ef48c | ||
|  | c8ba07a4ae | 
| @@ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <dataSource name="document.db"> | ||||
|   <database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.16"> | ||||
|   <database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17"> | ||||
|     <root id="1"> | ||||
|       <ServerVersion>3.25.1</ServerVersion> | ||||
|     </root> | ||||
|   | ||||
| @@ -20,7 +20,7 @@ SELECT noteId, title, -1, isProtected, type, mime, hash, isDeleted, isErased, da | ||||
| DROP TABLE notes; | ||||
| ALTER TABLE notes_mig RENAME TO notes; | ||||
|  | ||||
| UPDATE notes SET contentLength = (SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId); | ||||
| UPDATE notes SET contentLength = COALESCE((SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId), -1); | ||||
|  | ||||
| CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`); | ||||
| CREATE INDEX `IDX_notes_title` ON `notes` (`title`); | ||||
|   | ||||
							
								
								
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/ckeditor/ckeditor.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -498,7 +498,7 @@ | ||||
| 				t._started = false; | ||||
| 				onRenderStop(); | ||||
| 			} else { | ||||
| 				setImmediate(step); | ||||
| 				requestIdleCallback(step, { timeout: 10 }); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "version": "0.37.3", | ||||
|   "version": "0.37.5", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.37.4", | ||||
|   "version": "0.37.6", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
|   | ||||
| @@ -788,6 +788,7 @@ class Note extends Entity { | ||||
|         delete pojo.isContentAvailable; | ||||
|         delete pojo.__attributeCache; | ||||
|         delete pojo.content; | ||||
|         /** zero references to contentHash, probably can be removed */ | ||||
|         delete pojo.contentHash; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -93,7 +93,7 @@ export default class SidebarOptions { | ||||
|         this.$sidebarMinWidth.val(options.sidebarMinWidth); | ||||
|         this.$sidebarWidthPercent.val(options.sidebarWidthPercent); | ||||
|  | ||||
|         if (parseInt(options.showSidebarInNewTab)) { | ||||
|         if (options.showSidebarInNewTab === 'true') { | ||||
|             this.$showSidebarInNewTab.attr("checked", "checked"); | ||||
|         } | ||||
|         else { | ||||
|   | ||||
| @@ -273,7 +273,9 @@ async function filterTabs(noteId) { | ||||
|  | ||||
| async function noteDeleted(noteId) { | ||||
|     for (const tc of tabContexts) { | ||||
|         if (tc.notePath && tc.notePath.split("/").includes(noteId)) { | ||||
|         // not removing active even if it contains deleted note since that one will move to another note (handled by deletion logic) | ||||
|         // and we would lose tab context state (e.g. sidebar visibility) | ||||
|         if (!tc.isActive() && tc.notePath && tc.notePath.split("/").includes(noteId)) { | ||||
|             await tabRow.removeTab(tc.$tab[0]); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -246,11 +246,15 @@ class TabContext { | ||||
|     } | ||||
|  | ||||
|     setCurrentNotePathToHash() { | ||||
|         if (this.$tab[0] === this.tabRow.activeTabEl) { | ||||
|         if (this.isActive()) { | ||||
|             document.location.hash = (this.notePath || "") + "-" + this.tabId; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     isActive() { | ||||
|         return this.$tab[0] === this.tabRow.activeTabEl; | ||||
|     } | ||||
|  | ||||
|     setupClasses() { | ||||
|         for (const clazz of Array.from(this.$tab[0].classList)) { // create copy to safely iterate over while removing classes | ||||
|             if (clazz !== 'note-tab') { | ||||
|   | ||||
| @@ -42,7 +42,7 @@ class TreeContextMenu { | ||||
|                 || (selNodes.length === 1 && selNodes[0] === this.node); | ||||
|  | ||||
|         const notSearch = note.type !== 'search'; | ||||
|         const parentNotSearch = parentNote.type !== 'search'; | ||||
|         const parentNotSearch = !parentNote || parentNote.type !== 'search'; | ||||
|         const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; | ||||
|  | ||||
|         return [ | ||||
| @@ -79,7 +79,7 @@ class TreeContextMenu { | ||||
|             { title: "Paste after", cmd: "pasteAfter", uiIcon: "paste", | ||||
|                 enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes }, | ||||
|             { title: "Duplicate note here", cmd: "duplicateNote", uiIcon: "empty", | ||||
|                 enabled: noSelectedNotes && parentNotSearch && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) }, | ||||
|                 enabled: noSelectedNotes && parentNotSearch && isNotRoot && !isHoisted && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) }, | ||||
|             { title: "----" }, | ||||
|             { title: "Export", cmd: "export", uiIcon: "empty", | ||||
|                 enabled: notSearch && noSelectedNotes }, | ||||
|   | ||||
| @@ -127,11 +127,13 @@ async function consumeSyncData() { | ||||
| } | ||||
|  | ||||
| function connectWebSocket() { | ||||
|     const protocol = document.location.protocol === 'https:' ? 'wss' : 'ws'; | ||||
|     const loc = window.location; | ||||
|     const webSocketUri = (loc.protocol === "https:" ? "wss:" : "ws:") | ||||
|                        + "//" + loc.host + loc.pathname; | ||||
|  | ||||
|     // use wss for secure messaging | ||||
|     const ws = new WebSocket(protocol + "://" + location.host); | ||||
|     ws.onopen = () => console.debug(utils.now(), "Connected to server with WebSocket"); | ||||
|     const ws = new WebSocket(webSocketUri); | ||||
|     ws.onopen = () => console.debug(utils.now(), `Connected to server ${webSocketUri} with WebSocket`); | ||||
|     ws.onmessage = handleMessage; | ||||
|     // we're not handling ws.onclose here because reconnection is done in sendPing() | ||||
|  | ||||
|   | ||||
| @@ -89,6 +89,11 @@ body { | ||||
|     font-size: inherit; | ||||
| } | ||||
|  | ||||
| #context-menu-container { | ||||
|     max-height: 100vh; | ||||
|     overflow: auto; /* make it scrollable when exceeding total height of the window */ | ||||
| } | ||||
|  | ||||
| #context-menu-container, #context-menu-container .dropdown-menu { | ||||
|     padding: 3px 0 0; | ||||
|     z-index: 1111; | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2019-11-22T22:38:03+01:00", buildRevision: "7a2c7edd7e9b975bf64f732629e711379baecf48" }; | ||||
| module.exports = { buildDate:"2019-11-26T22:50:08+01:00", buildRevision: "5193f073e9e55f5440fe2e71fbd2cdfcdb2d2c6b" }; | ||||
|   | ||||
| @@ -311,6 +311,25 @@ async function findLogicIssues() { | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     await findAndFixIssues(` | ||||
|                 SELECT notes.noteId | ||||
|                 FROM notes | ||||
|                   LEFT JOIN note_contents USING(noteId) | ||||
|                 WHERE | ||||
|                   note_contents.noteId IS NULL`, | ||||
|         async ({noteId}, autoFix) => { | ||||
|             if (autoFix) { | ||||
|                 const note = await repository.getNote(noteId); | ||||
|                 // empty string might be wrong choice for some note types (and protected notes) but it's a best guess | ||||
|                 await note.setContent(note.isErased ? null : ''); | ||||
|  | ||||
|                 logFix(`Note ${noteId} content was set to empty string since there was no corresponding row`); | ||||
|             } | ||||
|             else { | ||||
|                 logError(`Note ${noteId} content row does not exist`); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     await findAndFixIssues(` | ||||
|           SELECT noteId | ||||
|           FROM notes | ||||
| @@ -321,6 +340,7 @@ async function findLogicIssues() { | ||||
|         async ({noteId}, autoFix) => { | ||||
|             if (autoFix) { | ||||
|                 const note = await repository.getNote(noteId); | ||||
|                 // empty string might be wrong choice for some note types (and protected notes) but it's a best guess | ||||
|                 await note.setContent(''); | ||||
|  | ||||
|                 logFix(`Note ${noteId} content was set to empty string since it was null even though it is not deleted`); | ||||
| @@ -360,6 +380,25 @@ async function findLogicIssues() { | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     await findAndFixIssues(` | ||||
|                 SELECT note_revisions.noteRevisionId | ||||
|                 FROM note_revisions | ||||
|                 LEFT JOIN note_revision_contents USING(noteRevisionId) | ||||
|                 WHERE note_revision_contents.noteRevisionId IS NULL`, | ||||
|         async ({noteRevisionId}, autoFix) => { | ||||
|             if (autoFix) { | ||||
|                 const noteRevision = await repository.getNoteRevision(noteRevisionId); | ||||
|                 await noteRevision.setContent(null); | ||||
|                 noteRevision.isErased = true; | ||||
|                 await noteRevision.save(); | ||||
|  | ||||
|                 logFix(`Note revision content ${noteRevisionId} was created and set to erased since it did not exist.`); | ||||
|             } | ||||
|             else { | ||||
|                 logError(`Note revision content ${noteRevisionId} does not exist`); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     await findAndFixIssues(` | ||||
|           SELECT noteRevisionId | ||||
|           FROM note_revisions | ||||
|   | ||||
| @@ -3,6 +3,7 @@ const fileType = require('file-type'); | ||||
| const stream = require('stream'); | ||||
| const log = require("../log"); | ||||
| const utils = require("../utils"); | ||||
| const sql = require("../sql"); | ||||
| const noteService = require("../notes"); | ||||
| const imageService = require("../image"); | ||||
| const protectedSessionService = require('../protected_session'); | ||||
| @@ -11,7 +12,7 @@ const protectedSessionService = require('../protected_session'); | ||||
| function parseDate(text) { | ||||
|     // insert - and : to make it ISO format | ||||
|     text = text.substr(0, 4) + "-" + text.substr(4, 2) + "-" + text.substr(6, 2) | ||||
|         + "T" + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + "Z"; | ||||
|         + " " + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + ".000Z"; | ||||
|  | ||||
|     return text; | ||||
| } | ||||
| @@ -150,7 +151,7 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|             } else if (currentTag === 'created') { | ||||
|                 note.utcDateCreated = parseDate(text); | ||||
|             } else if (currentTag === 'updated') { | ||||
|                 // updated is currently ignored since utcDateModified is updated automatically with each save | ||||
|                 note.utcDateModified = parseDate(text); | ||||
|             } else if (currentTag === 'tag') { | ||||
|                 note.attributes.push({ | ||||
|                     type: 'label', | ||||
| @@ -187,9 +188,27 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     async function updateDates(noteId, utcDateCreated, utcDateModified) { | ||||
|         // it's difficult to force custom dateCreated and dateModified to Note entity so we do it post-creation with SQL | ||||
|         await sql.execute(` | ||||
|                 UPDATE notes  | ||||
|                 SET dateCreated = ?,  | ||||
|                     utcDateCreated = ?, | ||||
|                     dateModified = ?, | ||||
|                     utcDateModified = ? | ||||
|                 WHERE noteId = ?`, | ||||
|             [utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, noteId]); | ||||
|  | ||||
|         await sql.execute(` | ||||
|                 UPDATE note_contents | ||||
|                 SET utcDateModified = ? | ||||
|                 WHERE noteId = ?`, | ||||
|             [utcDateModified, noteId]); | ||||
|     } | ||||
|  | ||||
|     async function saveNote() { | ||||
|         // make a copy because stream continues with the next async call and note gets overwritten | ||||
|         let {title, content, attributes, resources, utcDateCreated} = note; | ||||
|         let {title, content, attributes, resources, utcDateCreated, utcDateModified} = note; | ||||
|  | ||||
|         content = extractContent(content); | ||||
|  | ||||
| @@ -201,6 +220,10 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|             isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), | ||||
|         })).note; | ||||
|  | ||||
|         utcDateCreated = utcDateCreated || noteEntity.utcDateCreated; | ||||
|         // sometime date modified is not present in ENEX, then use date created | ||||
|         utcDateModified = utcDateModified || utcDateCreated; | ||||
|  | ||||
|         taskContext.increaseProgressCount(); | ||||
|  | ||||
|         let noteContent = await noteEntity.getContent(); | ||||
| @@ -224,6 +247,8 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|                     isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), | ||||
|                 })).note; | ||||
|  | ||||
|                 await updateDates(resourceNote.noteId, utcDateCreated, utcDateModified); | ||||
|  | ||||
|                 taskContext.increaseProgressCount(); | ||||
|  | ||||
|                 const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`; | ||||
| @@ -235,7 +260,9 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|                 try { | ||||
|                     const originalName = "image." + resource.mime.substr(6); | ||||
|  | ||||
|                     const {url} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages); | ||||
|                     const {url, note: imageNote} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages); | ||||
|  | ||||
|                     await updateDates(imageNote.noteId, utcDateCreated, utcDateModified); | ||||
|  | ||||
|                     const imageLink = `<img src="${url}">`; | ||||
|  | ||||
| @@ -257,6 +284,10 @@ async function importEnex(taskContext, file, parentNote) { | ||||
|  | ||||
|         // save updated content with links to files/images | ||||
|         await noteEntity.setContent(noteContent); | ||||
|  | ||||
|         await noteService.scanForLinks(noteEntity.noteId); | ||||
|  | ||||
|         await updateDates(noteEntity.noteId, utcDateCreated, utcDateModified); | ||||
|     } | ||||
|  | ||||
|     saxStream.on("closetag", async tag => { | ||||
|   | ||||
| @@ -37,7 +37,7 @@ function isProtectedSessionAvailable() { | ||||
| function decryptNotes(notes) { | ||||
|     for (const note of notes) { | ||||
|         if (note.isProtected) { | ||||
|             note.title = decrypt(note.title); | ||||
|             note.title = decryptString(note.title); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user