diff --git a/app.js b/app.js
index 40e9d5957..77165068a 100644
--- a/app.js
+++ b/app.js
@@ -9,6 +9,7 @@ const session = require('express-session');
const FileStore = require('session-file-store')(session);
const os = require('os');
const sessionSecret = require('./services/session_secret');
+const utils = require('./services/utils');
require('./services/ping_job');
@@ -71,4 +72,30 @@ require('./services/sync');
// triggers backup timer
require('./services/backup');
+if (utils.isElectron()) {
+ const ipcMain = require('electron').ipcMain;
+
+ ipcMain.on('server-request', (event, arg) => {
+ const req = {};
+ req.url = arg.url;
+ req.method = arg.method;
+ req.body = arg.data;
+ req.headers = {};
+
+ const res = {};
+ res.setHeader = function() {
+
+ };
+
+ res.send = function(obj) {
+ event.sender.send('server-response', {
+ requestId: arg.requestId,
+ body: obj
+ });
+ };
+
+ return app._router.handle(req, res, () => {});
+ });
+}
+
module.exports = app;
\ No newline at end of file
diff --git a/public/javascripts/dialogs/edit_tree_prefix.js b/public/javascripts/dialogs/edit_tree_prefix.js
index 5ae40decf..9ec3c41e4 100644
--- a/public/javascripts/dialogs/edit_tree_prefix.js
+++ b/public/javascripts/dialogs/edit_tree_prefix.js
@@ -30,16 +30,9 @@ const editTreePrefix = (function() {
formEl.submit(() => {
const prefix = treePrefixInputEl.val();
- $.ajax({
- url: baseApiUrl + 'tree/' + noteTreeId + '/setPrefix',
- type: 'PUT',
- contentType: 'application/json',
- data: JSON.stringify({
- prefix: prefix
- }),
- success: () => noteTree.setPrefix(noteTreeId, prefix),
- error: () => showError("Error setting prefix.")
- });
+ server.put('tree/' + noteTreeId + '/setPrefix', {
+ prefix: prefix
+ }).then(() => noteTree.setPrefix(noteTreeId, prefix));
dialogEl.dialog("close");
diff --git a/public/javascripts/dialogs/event_log.js b/public/javascripts/dialogs/event_log.js
index bd650eb05..428bf1a41 100644
--- a/public/javascripts/dialogs/event_log.js
+++ b/public/javascripts/dialogs/event_log.js
@@ -13,11 +13,7 @@ const eventLog = (function() {
height: 700
});
- const result = await $.ajax({
- url: baseApiUrl + 'event-log',
- type: 'GET',
- error: () => showError("Error getting event log.")
- });
+ const result = await server.get('event-log');
listEl.html('');
diff --git a/public/javascripts/dialogs/note_history.js b/public/javascripts/dialogs/note_history.js
index 7254e6f58..a6da3321b 100644
--- a/public/javascripts/dialogs/note_history.js
+++ b/public/javascripts/dialogs/note_history.js
@@ -24,11 +24,7 @@ const noteHistory = (function() {
listEl.empty();
contentEl.empty();
- historyItems = await $.ajax({
- url: baseApiUrl + 'notes-history/' + noteId,
- type: 'GET',
- error: () => showError("Error getting note history.")
- });
+ historyItems = await server.get('notes-history/' + noteId);
for (const item of historyItems) {
const dateModified = getDateFromTS(item.date_modified_to);
diff --git a/public/javascripts/dialogs/recent_changes.js b/public/javascripts/dialogs/recent_changes.js
index a1ab34bce..d2547dddb 100644
--- a/public/javascripts/dialogs/recent_changes.js
+++ b/public/javascripts/dialogs/recent_changes.js
@@ -12,11 +12,7 @@ const recentChanges = (function() {
height: 700
});
- const result = await $.ajax({
- url: baseApiUrl + 'recent-changes/',
- type: 'GET',
- error: () => showError("Error getting recent changes.")
- });
+ const result = await server.get('recent-changes/');
dialogEl.html('');
diff --git a/public/javascripts/dialogs/recent_notes.js b/public/javascripts/dialogs/recent_notes.js
index eaa80d95f..a1efcbbff 100644
--- a/public/javascripts/dialogs/recent_notes.js
+++ b/public/javascripts/dialogs/recent_notes.js
@@ -10,38 +10,26 @@ const recentNotes = (function() {
const noteDetailEl = $('#note-detail');
let list = [];
- $.ajax({
- url: baseApiUrl + 'recent-notes',
- type: 'GET',
- error: () => showError("Error getting recent notes.")
- }).then(result => {
+ server.get('recent-notes').then(result => {
list = result.map(r => r.note_tree_id);
});
function addRecentNote(notePath) {
- setTimeout(() => {
+ setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (notePath && notePath === noteTree.getCurrentNotePath()) {
- $.ajax({
- url: baseApiUrl + 'recent-notes/' + encodeURIComponent(notePath),
- type: 'PUT',
- error: () => showError("Error setting recent notes.")
- }).then(result => {
- list = result.map(r => r.note_path);
- });
+ const result = await server.put('recent-notes/' + encodeURIComponent(notePath));
+
+ list = result.map(r => r.note_path);
}
}, 1500);
}
// FIXME: this should be probably just refresh upon deletion, not explicit delete
- function removeRecentNote(notePathIdToRemove) {
- $.ajax({
- url: baseApiUrl + 'recent-notes/' + encodeURIComponent(notePathIdToRemove),
- type: 'DELETE',
- error: () => showError("Error removing note from recent notes.")
- }).then(result => {
- list = result.map(r => r.note_path);
- });
+ async function removeRecentNote(notePathIdToRemove) {
+ const result = server.remove('recent-notes/' + encodeURIComponent(notePathIdToRemove));
+
+ list = result.map(r => r.note_path);
}
function showDialog() {
diff --git a/public/javascripts/dialogs/settings.js b/public/javascripts/dialogs/settings.js
index 95bd15bfe..b5b18fb3e 100644
--- a/public/javascripts/dialogs/settings.js
+++ b/public/javascripts/dialogs/settings.js
@@ -13,11 +13,7 @@ const settings = (function() {
async function showDialog() {
glob.activeDialog = dialogEl;
- const settings = await $.ajax({
- url: baseApiUrl + 'settings',
- type: 'GET',
- error: () => showError("Error getting settings.")
- });
+ const settings = await server.get('settings');
dialogEl.dialog({
modal: true,
@@ -33,20 +29,13 @@ const settings = (function() {
}
}
- function saveSettings(settingName, settingValue) {
- return $.ajax({
- url: baseApiUrl + 'settings',
- type: 'POST',
- data: JSON.stringify({
- name: settingName,
- value: settingValue
- }),
- contentType: "application/json",
- success: () => {
- showMessage("Settings change have been saved.");
- },
- error: () => alert("Error occurred during saving settings change.")
+ async function saveSettings(settingName, settingValue) {
+ await server.post('settings', {
+ name: settingName,
+ value: settingValue
});
+
+ showMessage("Settings change have been saved.");
}
return {
@@ -79,26 +68,19 @@ settings.addModule((function() {
return false;
}
- $.ajax({
- url: baseApiUrl + 'password/change',
- type: 'POST',
- data: JSON.stringify({
- 'current_password': oldPassword,
- 'new_password': newPassword1
- }),
- contentType: "application/json",
- success: result => {
- if (result.success) {
- alert("Password has been changed. Trilium will be reloaded after you press OK.");
+ server.post('password/change', {
+ 'current_password': oldPassword,
+ 'new_password': newPassword1
+ }).then(result => {
+ if (result.success) {
+ alert("Password has been changed. Trilium will be reloaded after you press OK.");
- // password changed so current protected session is invalid and needs to be cleared
- protected_session.resetProtectedSession();
- }
- else {
- showError(result.message);
- }
- },
- error: () => showError("Error occurred during changing password.")
+ // password changed so current protected session is invalid and needs to be cleared
+ protected_session.resetProtectedSession();
+ }
+ else {
+ showError(result.message);
+ }
});
return false;
@@ -159,7 +141,7 @@ settings.addModule((async function () {
const buildDateEl = $("#build-date");
const buildRevisionEl = $("#build-revision");
- const appInfo = await $.get(baseApiUrl + 'app-info');
+ const appInfo = await server.get('app-info');
appVersionEl.html(appInfo.app_version);
dbVersionEl.html(appInfo.db_version);
diff --git a/public/javascripts/init.js b/public/javascripts/init.js
index 1cb4b3eb9..b51872028 100644
--- a/public/javascripts/init.js
+++ b/public/javascripts/init.js
@@ -129,14 +129,4 @@ function showAppIfHidden() {
// Kick off the CSS transition
loaderDiv.style.opacity = 0.0;
}
-}
-
-function initAjax() {
- $.ajaxSetup({
- headers: {
- 'x-protected-session-id': typeof protected_session !== 'undefined' ? protected_session.getProtectedSessionId() : null
- }
- });
-}
-
-initAjax();
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/public/javascripts/migration.js b/public/javascripts/migration.js
index ca10779e3..0d2456248 100644
--- a/public/javascripts/migration.js
+++ b/public/javascripts/migration.js
@@ -17,29 +17,28 @@ $(document).ready(() => {
});
});
-$("#run-migration").click(() => {
+$("#run-migration").click(async () => {
$("#run-migration").prop("disabled", true);
$("#migration-result").show();
- $.ajax({
+ const result = await $.ajax({
url: baseApiUrl + 'migration',
type: 'POST',
- success: result => {
- for (const migration of result.migrations) {
- const row = $('
')
- .append($('').html(migration.db_version))
- .append($(' | ').html(migration.name))
- .append($(' | ').html(migration.success ? 'Yes' : 'No'))
- .append($(' | ').html(migration.success ? 'N/A' : migration.error));
-
- if (!migration.success) {
- row.addClass("danger");
- }
-
- $("#migration-table").append(row);
- }
- },
error: () => showError("Migration failed with unknown error")
});
+
+ for (const migration of result.migrations) {
+ const row = $(' |
')
+ .append($('').html(migration.db_version))
+ .append($(' | ').html(migration.name))
+ .append($(' | ').html(migration.success ? 'Yes' : 'No'))
+ .append($(' | ').html(migration.success ? 'N/A' : migration.error));
+
+ if (!migration.success) {
+ row.addClass("danger");
+ }
+
+ $("#migration-table").append(row);
+ }
});
\ No newline at end of file
diff --git a/public/javascripts/note_editor.js b/public/javascripts/note_editor.js
index c0015843a..6053e3ea5 100644
--- a/public/javascripts/note_editor.js
+++ b/public/javascripts/note_editor.js
@@ -93,15 +93,7 @@ const noteEditor = (function() {
}
async function saveNoteToServer(note) {
- await $.ajax({
- url: baseApiUrl + 'notes/' + note.detail.note_id,
- type: 'PUT',
- data: JSON.stringify(note),
- contentType: "application/json",
- error: () => {
- showError("Error saving the note!");
- }
- });
+ await server.put('notes/' + note.detail.note_id, note);
isNoteChanged = false;
@@ -130,7 +122,7 @@ const noteEditor = (function() {
}
async function loadNoteToEditor(noteId) {
- currentNote = await $.get(baseApiUrl + 'notes/' + noteId);
+ currentNote = await server.get('notes/' + noteId);
if (isNewNoteCreated) {
isNewNoteCreated = false;
@@ -167,7 +159,7 @@ const noteEditor = (function() {
}
async function loadNote(noteId) {
- return await $.get(baseApiUrl + 'notes/' + noteId);
+ return await server.get('notes/' + noteId);
}
$(document).ready(() => {
diff --git a/public/javascripts/note_tree.js b/public/javascripts/note_tree.js
index c52af4a12..d193f8f13 100644
--- a/public/javascripts/note_tree.js
+++ b/public/javascripts/note_tree.js
@@ -307,15 +307,10 @@ const noteTree = (function() {
return path.reverse().join('/');
}
- function setExpandedToServer(noteTreeId, isExpanded) {
+ async function setExpandedToServer(noteTreeId, isExpanded) {
const expandedNum = isExpanded ? 1 : 0;
- $.ajax({
- url: baseApiUrl + 'notes/' + noteTreeId + '/expanded/' + expandedNum,
- type: 'PUT',
- contentType: "application/json",
- success: result => {}
- });
+ await server.put('notes/' + noteTreeId + '/expanded/' + expandedNum);
}
function setCurrentNotePathToHash(node) {
@@ -479,7 +474,7 @@ const noteTree = (function() {
}
function loadTree() {
- return $.get(baseApiUrl + 'tree').then(resp => {
+ return server.get('tree').then(resp => {
startNoteTreeId = resp.start_note_tree_id;
treeLoadTime = resp.tree_load_time;
@@ -574,16 +569,11 @@ const noteTree = (function() {
const newNoteName = "new note";
- const result = await $.ajax({
- url: baseApiUrl + 'notes/' + parentNoteId + '/children' ,
- type: 'POST',
- data: JSON.stringify({
- note_title: newNoteName,
- target: target,
- target_note_tree_id: node.data.note_tree_id,
- is_protected: isProtected
- }),
- contentType: "application/json"
+ const result = await server.post('notes/' + parentNoteId + '/children', {
+ note_title: newNoteName,
+ target: target,
+ target_note_tree_id: node.data.note_tree_id,
+ is_protected: isProtected
});
const newNode = {
diff --git a/public/javascripts/protected_session.js b/public/javascripts/protected_session.js
index 58aef9d39..a00a677a9 100644
--- a/public/javascripts/protected_session.js
+++ b/public/javascripts/protected_session.js
@@ -10,11 +10,7 @@ const protected_session = (function() {
let protectedSessionTimeout = null;
let protectedSessionId = null;
- $.ajax({
- url: baseApiUrl + 'settings/all',
- type: 'GET',
- error: () => showError("Error getting protected session settings.")
- }).then(settings => {
+ server.get('settings/all').then(settings => {
protectedSessionTimeout = settings.protected_session_timeout;
});
@@ -61,7 +57,7 @@ const protected_session = (function() {
}
protectedSessionId = response.protectedSessionId;
- initAjax();
+ server.initAjax();
dialogEl.dialog("close");
@@ -88,14 +84,8 @@ const protected_session = (function() {
}
async function enterProtectedSession(password) {
- return await $.ajax({
- url: baseApiUrl + 'login/protected',
- type: 'POST',
- contentType: 'application/json',
- data: JSON.stringify({
- password: password
- }),
- error: () => showError("Error entering protected session.")
+ return await server.post('login/protected', {
+ password: password
});
}
@@ -106,7 +96,7 @@ const protected_session = (function() {
function resetProtectedSession() {
protectedSessionId = null;
- initAjax();
+ server.initAjax();
// most secure solution - guarantees nothing remained in memory
// since this expires because user doesn't use the app, it shouldn't be disruptive
@@ -154,12 +144,7 @@ const protected_session = (function() {
async function protectSubTree(noteId, protect) {
await ensureProtectedSession(true, true);
- await $.ajax({
- url: baseApiUrl + 'tree/' + noteId + "/protectSubTree/" + (protect ? 1 : 0),
- type: 'PUT',
- contentType: 'application/json',
- error: () => showError("Request to un/protect sub tree has failed.")
- });
+ await server.put('tree/' + noteId + "/protectSubTree/" + (protect ? 1 : 0));
showMessage("Request to un/protect sub tree has finished successfully");
diff --git a/public/javascripts/search_tree.js b/public/javascripts/search_tree.js
index 60279776e..38e85d39c 100644
--- a/public/javascripts/search_tree.js
+++ b/public/javascripts/search_tree.js
@@ -43,7 +43,7 @@ const searchTree = (function() {
}
if (e && e.which === $.ui.keyCode.ENTER) {
- $.get(baseApiUrl + 'notes?search=' + searchText).then(resp => {
+ server.get('notes?search=' + searchText).then(resp => {
// Pass a string to perform case insensitive matching
getTree().filterBranches(node => {
return resp.includes(node.data.note_id);
diff --git a/public/javascripts/server.js b/public/javascripts/server.js
new file mode 100644
index 000000000..e8f53bf27
--- /dev/null
+++ b/public/javascripts/server.js
@@ -0,0 +1,84 @@
+const server = (function() {
+ function initAjax() {
+ $.ajaxSetup({
+ headers: {
+ 'x-protected-session-id': typeof protected_session !== 'undefined' ? protected_session.getProtectedSessionId() : null
+ }
+ });
+ }
+
+ async function get(url) {
+ return await call('GET', url);
+ }
+
+ async function post(url, data) {
+ return await call('POST', url, data);
+ }
+
+ async function put(url, data) {
+ return await call('PUT', url, data);
+ }
+
+ async function remove(url) {
+ return await call('DELETE', url);
+ }
+
+ let i = 1;
+ const reqResolves = {};
+
+ async function call(method, url, data) {
+ if (isElectron()) {
+ const ipc = require('electron').ipcRenderer;
+ const requestId = i++;
+
+ return new Promise((resolve, reject) => {
+ reqResolves[requestId] = resolve;
+
+ ipc.send('server-request', {
+ requestId: requestId,
+ method: method,
+ url: "/" + baseApiUrl + url,
+ data: data
+ });
+ });
+ }
+ else {
+ return await ajax(url, method, data);
+ }
+ }
+
+ if (isElectron()) {
+ const ipc = require('electron').ipcRenderer;
+
+ ipc.on('server-response', (event, arg) => {
+ reqResolves[arg.requestId](arg.body);
+ });
+ }
+
+ async function ajax(url, method, data) {
+ const options = {
+ url: baseApiUrl + url,
+ type: method
+ };
+
+ if (data) {
+ options.data = JSON.stringify(data);
+ options.contentType = "application/json";
+ }
+
+ return await $.ajax(options).catch(e => {
+ showError("Error when calling " + method + " " + url + ": " + e);
+ });
+ }
+
+
+ initAjax();
+
+ return {
+ get,
+ post,
+ put,
+ remove,
+ initAjax
+ }
+})();
\ No newline at end of file
diff --git a/public/javascripts/sync.js b/public/javascripts/sync.js
index 38aa7f6ad..49f2b33f1 100644
--- a/public/javascripts/sync.js
+++ b/public/javascripts/sync.js
@@ -1,21 +1,16 @@
"use strict";
-function syncNow() {
- $.ajax({
- url: baseApiUrl + 'sync/now',
- type: 'POST',
- success: result => {
- if (result.success) {
- showMessage("Sync finished successfully.");
- }
- else {
- if (result.message.length > 50) {
- result.message = result.message.substr(0, 50);
- }
+async function syncNow() {
+ const result = await server.post('sync/now');
- showError("Sync failed: " + result.message);
- }
- },
- error: () => showError("Sync failed for unknown reason.")
- });
+ if (result.success) {
+ showMessage("Sync finished successfully.");
+ }
+ else {
+ if (result.message.length > 50) {
+ result.message = result.message.substr(0, 50);
+ }
+
+ showError("Sync failed: " + result.message);
+ }
}
\ No newline at end of file
diff --git a/public/javascripts/tree_changes.js b/public/javascripts/tree_changes.js
index 0dc4cac35..26f6ea5f4 100644
--- a/public/javascripts/tree_changes.js
+++ b/public/javascripts/tree_changes.js
@@ -2,11 +2,7 @@
const treeChanges = (function() {
async function moveBeforeNode(node, beforeNode, changeInPath = true) {
- await $.ajax({
- url: baseApiUrl + 'notes/' + node.data.note_tree_id + '/moveBefore/' + beforeNode.data.note_tree_id,
- type: 'PUT',
- contentType: "application/json"
- });
+ await server.put('notes/' + node.data.note_tree_id + '/moveBefore/' + beforeNode.data.note_tree_id);
node.moveTo(beforeNode, 'before');
@@ -18,11 +14,7 @@ const treeChanges = (function() {
}
async function moveAfterNode(node, afterNode, changeInPath = true) {
- await $.ajax({
- url: baseApiUrl + 'notes/' + node.data.note_tree_id + '/moveAfter/' + afterNode.data.note_tree_id,
- type: 'PUT',
- contentType: "application/json"
- });
+ await server.put('notes/' + node.data.note_tree_id + '/moveAfter/' + afterNode.data.note_tree_id);
node.moveTo(afterNode, 'after');
@@ -35,11 +27,7 @@ const treeChanges = (function() {
// beware that first arg is noteId and second is noteTreeId!
async function cloneNoteAfter(noteId, afterNoteTreeId) {
- const resp = await $.ajax({
- url: baseApiUrl + 'notes/' + noteId + '/cloneAfter/' + afterNoteTreeId,
- type: 'PUT',
- error: () => showError("Error cloning note.")
- });
+ const resp = await server.put('notes/' + noteId + '/cloneAfter/' + afterNoteTreeId);
if (!resp.success) {
alert(resp.message);
@@ -50,11 +38,7 @@ const treeChanges = (function() {
}
async function moveToNode(node, toNode) {
- await $.ajax({
- url: baseApiUrl + 'notes/' + node.data.note_tree_id + '/moveTo/' + toNode.data.note_id,
- type: 'PUT',
- contentType: "application/json"
- });
+ await server.put('notes/' + node.data.note_tree_id + '/moveTo/' + toNode.data.note_id);
node.moveTo(toNode);
@@ -69,11 +53,7 @@ const treeChanges = (function() {
}
async function cloneNoteTo(childNoteId, parentNoteId) {
- const resp = await $.ajax({
- url: baseApiUrl + 'notes/' + childNoteId + '/cloneTo/' + parentNoteId,
- type: 'PUT',
- error: () => showError("Error cloning note.")
- });
+ const resp = await server.put('notes/' + childNoteId + '/cloneTo/' + parentNoteId);
if (!resp.success) {
alert(resp.message);
@@ -88,10 +68,7 @@ const treeChanges = (function() {
return;
}
- await $.ajax({
- url: baseApiUrl + 'notes/' + node.data.note_tree_id,
- type: 'DELETE'
- });
+ await server.remove('notes/' + node.data.note_tree_id);
if (node.getParent() !== null && node.getParent().getChildren().length <= 1) {
node.getParent().folder = false;
@@ -118,11 +95,7 @@ const treeChanges = (function() {
return;
}
- await $.ajax({
- url: baseApiUrl + 'notes/' + node.data.note_tree_id + '/moveAfter/' + node.getParent().data.note_tree_id,
- type: 'PUT',
- contentType: "application/json",
- });
+ await server.put('notes/' + node.data.note_tree_id + '/moveAfter/' + node.getParent().data.note_tree_id);
if (node.getParent() !== null && node.getParent().getChildren().length <= 1) {
node.getParent().folder = false;
diff --git a/services/sync.js b/services/sync.js
index ca53d7a6b..9277a4ba6 100644
--- a/services/sync.js
+++ b/services/sync.js
@@ -105,6 +105,12 @@ async function getLastSyncedPull() {
return parseInt(await options.getOption('last_synced_pull'));
}
+async function setLastSyncedPull(syncId) {
+ await sql.doInTransaction(async () => {
+ await options.setOption('last_synced_pull', syncId);
+ });
+}
+
async function pullSync(syncContext) {
const lastSyncedPull = await getLastSyncedPull();
@@ -118,6 +124,8 @@ async function pullSync(syncContext) {
if (source_id.isLocalSourceId(sync.source_id)) {
log.info("Skipping " + sync.entity_name + " " + sync.entity_id + " because it has local source id.");
+ await setLastSyncedPull(sync.id);
+
continue;
}
@@ -149,9 +157,7 @@ async function pullSync(syncContext) {
throw new Error("Unrecognized entity type " + sync.entity_name);
}
- await sql.doInTransaction(async () => {
- await options.setOption('last_synced_pull', sync.id);
- });
+ await setLastSyncedPull(sync.id);
}
log.info("Finished pull");
diff --git a/views/index.ejs b/views/index.ejs
index ff03ad98d..5e304aa20 100644
--- a/views/index.ejs
+++ b/views/index.ejs
@@ -302,6 +302,7 @@
+
|