mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into m41
# Conflicts: # src/public/javascripts/mobile.js # src/public/stylesheets/style.css
This commit is contained in:
		
							
								
								
									
										3
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							| @@ -6,9 +6,6 @@ | |||||||
|       <synchronize>true</synchronize> |       <synchronize>true</synchronize> | ||||||
|       <jdbc-driver>org.sqlite.JDBC</jdbc-driver> |       <jdbc-driver>org.sqlite.JDBC</jdbc-driver> | ||||||
|       <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url> |       <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url> | ||||||
|       <driver-properties> |  | ||||||
|         <property name="enable_load_extension" value="true" /> |  | ||||||
|       </driver-properties> |  | ||||||
|     </data-source> |     </data-source> | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
| @@ -26,9 +26,9 @@ app.on('ready', async () => { | |||||||
|  |  | ||||||
|     await sqlInit.dbConnection; |     await sqlInit.dbConnection; | ||||||
|  |  | ||||||
|     // if schema doesn't exist -> setup process |     // if db is not initialized -> setup process | ||||||
|     // if schema exists, then we need to wait until the migration process is finished |     // if db is initialized, then we need to wait until the migration process is finished | ||||||
|     if (await sqlInit.schemaExists()) { |     if (await sqlInit.isDbInitialized()) { | ||||||
|         await sqlInit.dbReady; |         await sqlInit.dbReady; | ||||||
|  |  | ||||||
|         await windowService.createMainWindow(); |         await windowService.createMainWindow(); | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.40.3", |   "version": "0.40.4", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
|   | |||||||
| @@ -6,9 +6,27 @@ import branchService from "./services/branches.js"; | |||||||
| import utils from "./services/utils.js"; | import utils from "./services/utils.js"; | ||||||
| import appContext from "./services/app_context.js"; | import appContext from "./services/app_context.js"; | ||||||
| import noteCreateService from "./services/note_create.js"; | import noteCreateService from "./services/note_create.js"; | ||||||
|  | import treeUtils from "./services/tree_utils.js"; | ||||||
|  | import linkService from "./services/link.js"; | ||||||
|  | import noteContentRenderer from "./services/note_content_renderer.js"; | ||||||
|  |  | ||||||
| window.glob.isDesktop = utils.isDesktop; | window.glob.isDesktop = utils.isDesktop; | ||||||
| window.glob.isMobile = utils.isMobile; | window.glob.isMobile = utils.isMobile; | ||||||
|  | window.glob.showAddLinkDialog = () => import('./dialogs/add_link.js').then(d => d.showDialog()); | ||||||
|  | window.glob.showIncludeNoteDialog = cb => import('./dialogs/include_note.js').then(d => d.showDialog(cb)); | ||||||
|  | window.glob.loadIncludedNote = async (noteId, el) => { | ||||||
|  |     const note = await treeCache.getNote(noteId); | ||||||
|  |  | ||||||
|  |     if (note) { | ||||||
|  |         $(el).empty().append($("<h3>").append(await linkService.createNoteLink(note.noteId, { | ||||||
|  |             showTooltip: false | ||||||
|  |         }))); | ||||||
|  |  | ||||||
|  |         const {renderedContent} = await noteContentRenderer.getRenderedContent(note); | ||||||
|  |  | ||||||
|  |         $(el).append(renderedContent); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
| const $leftPane = $("#left-pane"); | const $leftPane = $("#left-pane"); | ||||||
| const $tree = $("#tree"); | const $tree = $("#tree"); | ||||||
|   | |||||||
| @@ -7,31 +7,20 @@ import protectedSessionHolder from "./protected_session_holder.js"; | |||||||
| async function getRenderedContent(note) { | async function getRenderedContent(note) { | ||||||
|     const type = getRenderingType(note); |     const type = getRenderingType(note); | ||||||
|  |  | ||||||
|     let rendered; |     let $rendered; | ||||||
|  |  | ||||||
|     if (type === 'text') { |     if (type === 'text') { | ||||||
|         const fullNote = await server.get('notes/' + note.noteId); |         const fullNote = await server.get('notes/' + note.noteId); | ||||||
|  |  | ||||||
|         const $content = $("<div>").html(fullNote.content); |         $rendered = $("<div>").html(fullNote.content); | ||||||
|  |  | ||||||
|         if (utils.isHtmlEmpty(fullNote.content)) { |  | ||||||
|             rendered = ""; |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             rendered = $content; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     else if (type === 'code') { |     else if (type === 'code') { | ||||||
|         const fullNote = await server.get('notes/' + note.noteId); |         const fullNote = await server.get('notes/' + note.noteId); | ||||||
|  |  | ||||||
|         if (fullNote.content.trim() === "") { |         $rendered = $("<pre>").text(fullNote.content); | ||||||
|             rendered = ""; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         rendered = $("<pre>").text(fullNote.content); |  | ||||||
|     } |     } | ||||||
|     else if (type === 'image') { |     else if (type === 'image') { | ||||||
|         rendered = $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`); |         $rendered = $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`); | ||||||
|     } |     } | ||||||
|     else if (type === 'file') { |     else if (type === 'file') { | ||||||
|         function getFileUrl() { |         function getFileUrl() { | ||||||
| @@ -56,33 +45,35 @@ async function getRenderedContent(note) { | |||||||
|         // open doesn't work for protected notes since it works through browser which isn't in protected session |         // open doesn't work for protected notes since it works through browser which isn't in protected session | ||||||
|         $openButton.toggle(!note.isProtected); |         $openButton.toggle(!note.isProtected); | ||||||
|  |  | ||||||
|         rendered = $('<div>') |         $rendered = $('<div>') | ||||||
|             .append($downloadButton) |             .append($downloadButton) | ||||||
|             .append('   ') |             .append('   ') | ||||||
|             .append($openButton); |             .append($openButton); | ||||||
|     } |     } | ||||||
|     else if (type === 'render') { |     else if (type === 'render') { | ||||||
|         const $el = $('<div>'); |         $rendered = $('<div>'); | ||||||
|  |  | ||||||
|         await renderService.render(note, $el, this.ctx); |         await renderService.render(note, $rendered, this.ctx); | ||||||
|  |  | ||||||
|         rendered = $el; |  | ||||||
|     } |     } | ||||||
|     else if (type === 'protected-session') { |     else if (type === 'protected-session') { | ||||||
|         const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`) |         const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`) | ||||||
|             .on('click', protectedSessionService.enterProtectedSession); |             .on('click', protectedSessionService.enterProtectedSession); | ||||||
|  |  | ||||||
|         rendered = $("<div>") |         $rendered = $("<div>") | ||||||
|             .append("<div>This note is protected and to access it you need to enter password.</div>") |             .append("<div>This note is protected and to access it you need to enter password.</div>") | ||||||
|             .append("<br/>") |             .append("<br/>") | ||||||
|             .append($button); |             .append($button); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         rendered = "<em>Content of this note cannot be displayed in the book format</em>"; |         $rendered = $("<em>Content of this note cannot be displayed in the book format</em>"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (note.cssClass) { | ||||||
|  |         $rendered.addClass(note.cssClass); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         renderedContent: rendered, |         renderedContent: $rendered, | ||||||
|         type |         type | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2020-02-09T10:48:23+01:00", buildRevision: "88bd65c6798609a39206722305fab4f8d91d618b" }; | module.exports = { buildDate:"2020-02-24T22:59:22+01:00", buildRevision: "fb55cdaea6b1367129e11118b8b6fd2eadebad5f" }; | ||||||
|   | |||||||
| @@ -323,14 +323,25 @@ class ConsistencyChecks { | |||||||
|                     WHERE isErased = 1 |                     WHERE isErased = 1 | ||||||
|                       AND content IS NOT NULL`, |                       AND content IS NOT NULL`, | ||||||
|             async ({noteId}) => { |             async ({noteId}) => { | ||||||
|             if (this.autoFix) { |  | ||||||
|                 await sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]); |  | ||||||
|  |  | ||||||
|                 logFix(`Note ${noteId} content has been set to null since the note is erased`); |             // we always fix this issue because there does not seem to be a good way to prevent it. | ||||||
|             } |             // Scenario in which this can happen: | ||||||
|             else { |             // 1. user on instance A deletes the note (sync for notes is created, but not for note_contents) and is later erased | ||||||
|                 logError(`Note ${noteId} content is not null even though the note is erased`); |             // 2. instance B gets synced from instance A, note is updated because of sync row for notes, | ||||||
|             } |             //    but note_contents is not because erasion does not create sync rows | ||||||
|  |             // 3. therefore note.isErased = true, but note_contents.content remains not updated and not erased. | ||||||
|  |             // | ||||||
|  |             // Considered solutions: | ||||||
|  |             // - don't sync erased notes - this might prevent syncing also of the isDeleted flag and note would continue | ||||||
|  |             //   to exist on the other instance | ||||||
|  |             // - create sync rows for erased event - this would be a problem for undeletion since erasion might happen | ||||||
|  |             //   on one instance after undelete and thus would win even though there's no user action behind it | ||||||
|  |             // | ||||||
|  |             // So instead we just fix such cases afterwards here. | ||||||
|  |  | ||||||
|  |             await sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]); | ||||||
|  |  | ||||||
|  |             logFix(`Note ${noteId} content has been set to null since the note is erased`); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         await this.findAndFixIssues(` |         await this.findAndFixIssues(` | ||||||
| @@ -547,23 +558,23 @@ class ConsistencyChecks { | |||||||
|             }); |             }); | ||||||
|  |  | ||||||
|         await this.findAndFixIssues(` |         await this.findAndFixIssues(` | ||||||
|         SELECT  |             SELECT  | ||||||
|           id, entityId |               id, entityId | ||||||
|         FROM  |             FROM  | ||||||
|           sync  |               sync  | ||||||
|           LEFT JOIN ${entityName} ON entityId = ${key}  |               LEFT JOIN ${entityName} ON entityId = ${key}  | ||||||
|         WHERE  |             WHERE  | ||||||
|           sync.entityName = '${entityName}'  |               sync.entityName = '${entityName}'  | ||||||
|           AND ${key} IS NULL`, |               AND ${key} IS NULL`, | ||||||
|             async ({id, entityId}) => { |                 async ({id, entityId}) => { | ||||||
|                 if (this.autoFix) { |                     if (this.autoFix) { | ||||||
|                     await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]); |                         await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]); | ||||||
|  |  | ||||||
|                     logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); |                         logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); | ||||||
|                 } else { |                     } else { | ||||||
|                     logError(`Unrecognized sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); |                         logError(`Unrecognized sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); | ||||||
|                 } |                     } | ||||||
|             }); |                 }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async findSyncRowsIssues() { |     async findSyncRowsIssues() { | ||||||
|   | |||||||
| @@ -599,6 +599,7 @@ async function eraseDeletedNotes() { | |||||||
|         UPDATE notes  |         UPDATE notes  | ||||||
|         SET title = '[deleted]', |         SET title = '[deleted]', | ||||||
|             contentLength = 0, |             contentLength = 0, | ||||||
|  |             isProtected = 0, | ||||||
|             isErased = 1 |             isErased = 1 | ||||||
|         WHERE noteId IN (???)`, noteIdsToErase); |         WHERE noteId IN (???)`, noteIdsToErase); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -231,9 +231,10 @@ async function syncFinished(syncContext) { | |||||||
|  |  | ||||||
| async function checkContentHash(syncContext) { | async function checkContentHash(syncContext) { | ||||||
|     const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); |     const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); | ||||||
|  |     const lastSyncedPullId = await getLastSyncedPull(); | ||||||
|  |  | ||||||
|     if (await getLastSyncedPull() < resp.maxSyncId) { |     if (lastSyncedPullId < resp.maxSyncId) { | ||||||
|         log.info("There are some outstanding pulls, skipping content check."); |         log.info(`There are some outstanding pulls (${lastSyncedPullId} vs. ${resp.maxSyncId}), skipping content check.`); | ||||||
|  |  | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -71,6 +71,8 @@ | |||||||
|                         <% include details/relation_map.ejs %> |                         <% include details/relation_map.ejs %> | ||||||
|  |  | ||||||
|                         <% include details/protected_session_password.ejs %> |                         <% include details/protected_session_password.ejs %> | ||||||
|  |  | ||||||
|  |                         <% include details/book.ejs %> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|             </div> |             </div> | ||||||
|   | |||||||
| @@ -19,13 +19,13 @@ | |||||||
|                 <label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType"> |                 <label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType"> | ||||||
|                     I'm a new user and I want to create new Trilium document for my notes</label> |                     I'm a new user and I want to create new Trilium document for my notes</label> | ||||||
|             </div> |             </div> | ||||||
|             <div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;"> |             <div class="radio" style="margin-bottom: 15px;"> | ||||||
|                 <label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType"> |                 <label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType"> | ||||||
|                     I have desktop instance already and I want to setup sync with it</label> |                     I have desktop instance already and I want to setup sync with it</label> | ||||||
|             </div> |             </div> | ||||||
|             <div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;"> |             <div class="radio" style="margin-bottom: 15px;"> | ||||||
|                 <label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType"> |                 <label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType"> | ||||||
|                     I have server instance up and I want to setup sync with it</label> |                     I have server instance already and I want to setup sync with it</label> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|             <button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button> |             <button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user