diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index 69c5fd8e8..caa742fa5 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -6,9 +6,6 @@ true org.sqlite.JDBC jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db - - - \ No newline at end of file diff --git a/electron.js b/electron.js index 2a20bbbb6..51649c837 100644 --- a/electron.js +++ b/electron.js @@ -26,9 +26,9 @@ app.on('ready', async () => { await sqlInit.dbConnection; - // if schema doesn't exist -> setup process - // if schema exists, then we need to wait until the migration process is finished - if (await sqlInit.schemaExists()) { + // if db is not initialized -> setup process + // if db is initialized, then we need to wait until the migration process is finished + if (await sqlInit.isDbInitialized()) { await sqlInit.dbReady; await windowService.createMainWindow(); diff --git a/package.json b/package.json index 0543e2d6b..42939d540 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.40.3", + "version": "0.40.4", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { diff --git a/src/public/javascripts/mobile.js b/src/public/javascripts/mobile.js index fb4638dcb..7e942a23b 100644 --- a/src/public/javascripts/mobile.js +++ b/src/public/javascripts/mobile.js @@ -6,9 +6,27 @@ import branchService from "./services/branches.js"; import utils from "./services/utils.js"; import appContext from "./services/app_context.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.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($("

").append(await linkService.createNoteLink(note.noteId, { + showTooltip: false + }))); + + const {renderedContent} = await noteContentRenderer.getRenderedContent(note); + + $(el).append(renderedContent); + } +}; const $leftPane = $("#left-pane"); const $tree = $("#tree"); diff --git a/src/public/javascripts/services/note_content_renderer.js b/src/public/javascripts/services/note_content_renderer.js index dafa39579..5c70e60b8 100644 --- a/src/public/javascripts/services/note_content_renderer.js +++ b/src/public/javascripts/services/note_content_renderer.js @@ -7,31 +7,20 @@ import protectedSessionHolder from "./protected_session_holder.js"; async function getRenderedContent(note) { const type = getRenderingType(note); - let rendered; + let $rendered; if (type === 'text') { const fullNote = await server.get('notes/' + note.noteId); - const $content = $("
").html(fullNote.content); - - if (utils.isHtmlEmpty(fullNote.content)) { - rendered = ""; - } - else { - rendered = $content; - } + $rendered = $("
").html(fullNote.content); } else if (type === 'code') { const fullNote = await server.get('notes/' + note.noteId); - if (fullNote.content.trim() === "") { - rendered = ""; - } - - rendered = $("
").text(fullNote.content);
+        $rendered = $("
").text(fullNote.content);
     }
     else if (type === 'image') {
-        rendered = $("").attr("src", `api/images/${note.noteId}/${note.title}`);
+        $rendered = $("").attr("src", `api/images/${note.noteId}/${note.title}`);
     }
     else if (type === 'file') {
         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
         $openButton.toggle(!note.isProtected);
 
-        rendered = $('
') + $rendered = $('
') .append($downloadButton) .append('   ') .append($openButton); } else if (type === 'render') { - const $el = $('
'); + $rendered = $('
'); - await renderService.render(note, $el, this.ctx); - - rendered = $el; + await renderService.render(note, $rendered, this.ctx); } else if (type === 'protected-session') { const $button = $(``) .on('click', protectedSessionService.enterProtectedSession); - rendered = $("
") + $rendered = $("
") .append("
This note is protected and to access it you need to enter password.
") .append("
") .append($button); } else { - rendered = "Content of this note cannot be displayed in the book format"; + $rendered = $("Content of this note cannot be displayed in the book format"); + } + + if (note.cssClass) { + $rendered.addClass(note.cssClass); } return { - renderedContent: rendered, + renderedContent: $rendered, type }; } diff --git a/src/services/build.js b/src/services/build.js index 887b8aecd..96eeb5fd3 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -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" }; diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index 046a6927a..c36aa90e6 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -323,14 +323,25 @@ class ConsistencyChecks { WHERE isErased = 1 AND content IS NOT NULL`, 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`); - } - else { - logError(`Note ${noteId} content is not null even though 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: + // 1. user on instance A deletes the note (sync for notes is created, but not for note_contents) and is later 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(` @@ -547,23 +558,23 @@ class ConsistencyChecks { }); await this.findAndFixIssues(` - SELECT - id, entityId - FROM - sync - LEFT JOIN ${entityName} ON entityId = ${key} - WHERE - sync.entityName = '${entityName}' - AND ${key} IS NULL`, - async ({id, entityId}) => { - if (this.autoFix) { - await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]); + SELECT + id, entityId + FROM + sync + LEFT JOIN ${entityName} ON entityId = ${key} + WHERE + sync.entityName = '${entityName}' + AND ${key} IS NULL`, + async ({id, entityId}) => { + if (this.autoFix) { + await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]); - logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); - } else { - logError(`Unrecognized sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); - } - }); + logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); + } else { + logError(`Unrecognized sync record id=${id}, entityName=${entityName}, entityId=${entityId}`); + } + }); } async findSyncRowsIssues() { diff --git a/src/services/notes.js b/src/services/notes.js index ffafb4bb6..068ad0536 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -599,6 +599,7 @@ async function eraseDeletedNotes() { UPDATE notes SET title = '[deleted]', contentLength = 0, + isProtected = 0, isErased = 1 WHERE noteId IN (???)`, noteIdsToErase); diff --git a/src/services/sync.js b/src/services/sync.js index 41cca5e9c..60ac346f7 100644 --- a/src/services/sync.js +++ b/src/services/sync.js @@ -231,9 +231,10 @@ async function syncFinished(syncContext) { async function checkContentHash(syncContext) { const resp = await syncRequest(syncContext, 'GET', '/api/sync/check'); + const lastSyncedPullId = await getLastSyncedPull(); - if (await getLastSyncedPull() < resp.maxSyncId) { - log.info("There are some outstanding pulls, skipping content check."); + if (lastSyncedPullId < resp.maxSyncId) { + log.info(`There are some outstanding pulls (${lastSyncedPullId} vs. ${resp.maxSyncId}), skipping content check.`); return true; } diff --git a/src/views/mobile.ejs b/src/views/mobile.ejs index ad8a84e92..76e1ba069 100644 --- a/src/views/mobile.ejs +++ b/src/views/mobile.ejs @@ -71,6 +71,8 @@ <% include details/relation_map.ejs %> <% include details/protected_session_password.ejs %> + + <% include details/book.ejs %>
diff --git a/src/views/setup.ejs b/src/views/setup.ejs index 0a2454290..896a4ec7f 100644 --- a/src/views/setup.ejs +++ b/src/views/setup.ejs @@ -19,13 +19,13 @@
-
+
-
+
+ I have server instance already and I want to setup sync with it