mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +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:
commit
36eac6badd
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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user