mirror of
https://github.com/zadam/trilium.git
synced 2025-10-30 09:56:36 +01:00
Compare commits
36 Commits
v0.10.0-be
...
v0.11.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dc16dd29f | ||
|
|
d8924c536b | ||
|
|
3ebbf2cc46 | ||
|
|
f4079604c9 | ||
|
|
1f96a6beab | ||
|
|
b277a250e5 | ||
|
|
5b0e1a644d | ||
|
|
6bb3cfa9a3 | ||
|
|
9720868f5a | ||
|
|
8d8ee2a87a | ||
|
|
542e82ee5d | ||
|
|
0104b19502 | ||
|
|
120888b53e | ||
|
|
d2e2caed62 | ||
|
|
63066802a8 | ||
|
|
6128bb4ff3 | ||
|
|
982796255d | ||
|
|
36b15f474d | ||
|
|
13f71f8967 | ||
|
|
64336ffbee | ||
|
|
b09463d1b2 | ||
|
|
b5e6f46b9c | ||
|
|
08af4a0465 | ||
|
|
8c5df6321f | ||
|
|
d19f044961 | ||
|
|
e378d9f645 | ||
|
|
39dc0f71b4 | ||
|
|
0cef5c6b8c | ||
|
|
9b5a44cef4 | ||
|
|
29769ed91d | ||
|
|
867d794e17 | ||
|
|
fdd8458336 | ||
|
|
a0bec22e96 | ||
|
|
5aeb5cd214 | ||
|
|
e827ddffb9 | ||
|
|
98f80998b9 |
@@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json
|
|||||||
|
|
||||||
git add package.json
|
git add package.json
|
||||||
|
|
||||||
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > services/build.js
|
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js
|
||||||
|
|
||||||
git add services/build.js
|
git add src/services/build.js
|
||||||
|
|
||||||
TAG=v$VERSION
|
TAG=v$VERSION
|
||||||
|
|
||||||
|
|||||||
5
db/migrations/0087__add_type_mime_to_note_revision.sql
Normal file
5
db/migrations/0087__add_type_mime_to_note_revision.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE note_revisions ADD type TEXT DEFAULT '' NOT NULL;
|
||||||
|
ALTER TABLE note_revisions ADD mime TEXT DEFAULT '' NOT NULL;
|
||||||
|
|
||||||
|
UPDATE note_revisions SET type = (SELECT type FROM notes WHERE notes.noteId = note_revisions.noteId);
|
||||||
|
UPDATE note_revisions SET mime = (SELECT mime FROM notes WHERE notes.noteId = note_revisions.noteId);
|
||||||
@@ -76,12 +76,12 @@ app.on('ready', () => {
|
|||||||
const dateNoteService = require('./src/services/date_notes');
|
const dateNoteService = require('./src/services/date_notes');
|
||||||
const dateUtils = require('./src/services/date_utils');
|
const dateUtils = require('./src/services/date_utils');
|
||||||
|
|
||||||
const parentNoteId = await dateNoteService.getDateNoteId(dateUtils.nowDate());
|
const parentNote = await dateNoteService.getDateNote(dateUtils.nowDate());
|
||||||
|
|
||||||
// window may be hidden / not in focus
|
// window may be hidden / not in focus
|
||||||
mainWindow.focus();
|
mainWindow.focus();
|
||||||
|
|
||||||
mainWindow.webContents.send('create-day-sub-note', parentNoteId);
|
mainWindow.webContents.send('create-day-sub-note', parentNote.noteId);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.10.0-beta",
|
"version": "0.11.0-beta",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class Note extends Entity {
|
|||||||
constructor(row) {
|
constructor(row) {
|
||||||
super(row);
|
super(row);
|
||||||
|
|
||||||
if (this.isProtected) {
|
// check if there's noteId, otherwise this is a new entity which wasn't encrypted yet
|
||||||
|
if (this.isProtected && this.noteId) {
|
||||||
protected_session.decryptNote(this);
|
protected_session.decryptNote(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,6 +22,14 @@ class Note extends Entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setContent(content) {
|
||||||
|
this.content = content;
|
||||||
|
|
||||||
|
if (this.isJson()) {
|
||||||
|
this.jsonContent = JSON.parse(this.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isJson() {
|
isJson() {
|
||||||
return this.mime === "application/json";
|
return this.mime === "application/json";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,13 @@ $list.on('change', () => {
|
|||||||
const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal);
|
const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal);
|
||||||
|
|
||||||
$title.html(revisionItem.title);
|
$title.html(revisionItem.title);
|
||||||
$content.html(revisionItem.content);
|
|
||||||
|
if (revisionItem.type === 'text') {
|
||||||
|
$content.html(revisionItem.content);
|
||||||
|
}
|
||||||
|
else if (revisionItem.type === 'code') {
|
||||||
|
$content.html($("<pre>").text(revisionItem.content));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', "a[action='note-revision']", event => {
|
$(document).on('click', "a[action='note-revision']", event => {
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ class Branch {
|
|||||||
return await this.treeCache.getNote(this.noteId);
|
return await this.treeCache.getNote(this.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTopLevel() {
|
||||||
|
return this.parentNoteId === 'root';
|
||||||
|
}
|
||||||
|
|
||||||
get toString() {
|
get toString() {
|
||||||
return `Branch(branchId=${this.branchId})`;
|
return `Branch(branchId=${this.branchId})`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ class NoteShort {
|
|||||||
get toString() {
|
get toString() {
|
||||||
return `Note(noteId=${this.noteId}, title=${this.title})`;
|
return `Note(noteId=${this.noteId}, title=${this.title})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dto() {
|
||||||
|
const dto = Object.assign({}, this);
|
||||||
|
delete dto.treeCache;
|
||||||
|
delete dto.hideInAutocomplete;
|
||||||
|
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NoteShort;
|
export default NoteShort;
|
||||||
@@ -1,20 +1,19 @@
|
|||||||
import server from './services/server.js';
|
import server from './services/server.js';
|
||||||
|
|
||||||
$(document).ready(() => {
|
$(document).ready(async () => {
|
||||||
server.get('migration').then(result => {
|
const {appDbVersion, dbVersion} = await server.get('migration');
|
||||||
const appDbVersion = result.app_dbVersion;
|
|
||||||
const dbVersion = result.dbVersion;
|
|
||||||
|
|
||||||
if (appDbVersion === dbVersion) {
|
console.log("HI", {appDbVersion, dbVersion});
|
||||||
$("#up-to-date").show();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$("#need-to-migrate").show();
|
|
||||||
|
|
||||||
$("#app-db-version").html(appDbVersion);
|
if (appDbVersion === dbVersion) {
|
||||||
$("#db-version").html(dbVersion);
|
$("#up-to-date").show();
|
||||||
}
|
}
|
||||||
});
|
else {
|
||||||
|
$("#need-to-migrate").show();
|
||||||
|
|
||||||
|
$("#app-db-version").html(appDbVersion);
|
||||||
|
$("#db-version").html(dbVersion);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#run-migration").click(async () => {
|
$("#run-migration").click(async () => {
|
||||||
@@ -37,4 +36,11 @@ $("#run-migration").click(async () => {
|
|||||||
|
|
||||||
$("#migration-table").append(row);
|
$("#migration-table").append(row);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// copy of this shortcut to be able to debug migration problems
|
||||||
|
$(document).bind('keydown', 'ctrl+shift+i', () => {
|
||||||
|
require('electron').remote.getCurrentWindow().toggleDevTools();
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import treeCache from "./tree_cache.js";
|
import treeCache from "./tree_cache.js";
|
||||||
import treeUtils from "./tree_utils.js";
|
import treeUtils from "./tree_utils.js";
|
||||||
|
import protectedSessionHolder from './protected_session_holder.js';
|
||||||
|
|
||||||
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
||||||
if (!parentNoteId) {
|
if (!parentNoteId) {
|
||||||
@@ -21,9 +22,6 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
|||||||
titlePath = '';
|
titlePath = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/zadam/trilium/issues/46
|
|
||||||
// unfortunately not easy to implement because we don't have an easy access to note's isProtected property
|
|
||||||
|
|
||||||
const autocompleteItems = [];
|
const autocompleteItems = [];
|
||||||
|
|
||||||
for (const childNote of childNotes) {
|
for (const childNote of childNotes) {
|
||||||
@@ -34,10 +32,12 @@ async function getAutocompleteItems(parentNoteId, notePath, titlePath) {
|
|||||||
const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
|
const childNotePath = (notePath ? (notePath + '/') : '') + childNote.noteId;
|
||||||
const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
|
const childTitlePath = (titlePath ? (titlePath + ' / ') : '') + await treeUtils.getNoteTitle(childNote.noteId, parentNoteId);
|
||||||
|
|
||||||
autocompleteItems.push({
|
if (!childNote.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||||
value: childTitlePath + ' (' + childNotePath + ')',
|
autocompleteItems.push({
|
||||||
label: childTitlePath
|
value: childTitlePath + ' (' + childNotePath + ')',
|
||||||
});
|
label: childTitlePath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
|
const childItems = await getAutocompleteItems(childNote.noteId, childNotePath, childTitlePath);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import treeUtils from './tree_utils.js';
|
|||||||
import branchPrefixDialog from '../dialogs/branch_prefix.js';
|
import branchPrefixDialog from '../dialogs/branch_prefix.js';
|
||||||
import infoService from "./info.js";
|
import infoService from "./info.js";
|
||||||
import treeCache from "./tree_cache.js";
|
import treeCache from "./tree_cache.js";
|
||||||
|
import syncService from "./sync.js";
|
||||||
|
|
||||||
const $tree = $("#tree");
|
const $tree = $("#tree");
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ const contextMenuOptions = {
|
|||||||
],
|
],
|
||||||
beforeOpen: async (event, ui) => {
|
beforeOpen: async (event, ui) => {
|
||||||
const node = $.ui.fancytree.getNode(ui.target);
|
const node = $.ui.fancytree.getNode(ui.target);
|
||||||
const branch = await treeCache.getBranch(branchId);
|
const branch = await treeCache.getBranch(node.data.branchId);
|
||||||
const note = await treeCache.getNote(node.data.noteId);
|
const note = await treeCache.getNote(node.data.noteId);
|
||||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||||
|
|
||||||
|
|||||||
@@ -32,18 +32,19 @@ async function requireLibrary(library) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dynamicallyLoadedScripts = [];
|
// we save the promises in case of the same script being required concurrently multiple times
|
||||||
|
const loadedScriptPromises = {};
|
||||||
|
|
||||||
async function requireScript(url) {
|
async function requireScript(url) {
|
||||||
if (!dynamicallyLoadedScripts.includes(url)) {
|
if (!loadedScriptPromises[url]) {
|
||||||
dynamicallyLoadedScripts.push(url);
|
loadedScriptPromises[url] = $.ajax({
|
||||||
|
|
||||||
return await $.ajax({
|
|
||||||
url: url,
|
url: url,
|
||||||
dataType: "script",
|
dataType: "script",
|
||||||
cache: true
|
cache: true
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await loadedScriptPromises[url];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requireCss(url) {
|
async function requireCss(url) {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ setTimeout(() => {
|
|||||||
lastSyncId: lastSyncId
|
lastSyncId: lastSyncId
|
||||||
}));
|
}));
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}, 1000);
|
}, 0);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
logError,
|
logError,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
|
import treeUtils from './tree_utils.js';
|
||||||
import noteTypeService from './note_type.js';
|
import noteTypeService from './note_type.js';
|
||||||
import protectedSessionService from './protected_session.js';
|
import protectedSessionService from './protected_session.js';
|
||||||
import protectedSessionHolder from './protected_session_holder.js';
|
import protectedSessionHolder from './protected_session_holder.js';
|
||||||
@@ -24,6 +25,7 @@ const $noteDetailWrapper = $("#note-detail-wrapper");
|
|||||||
const $noteIdDisplay = $("#note-id-display");
|
const $noteIdDisplay = $("#note-id-display");
|
||||||
const $labelList = $("#label-list");
|
const $labelList = $("#label-list");
|
||||||
const $labelListInner = $("#label-list-inner");
|
const $labelListInner = $("#label-list-inner");
|
||||||
|
const $childrenOverview = $("#children-overview");
|
||||||
|
|
||||||
let currentNote = null;
|
let currentNote = null;
|
||||||
|
|
||||||
@@ -73,50 +75,42 @@ function noteChanged() {
|
|||||||
async function reload() {
|
async function reload() {
|
||||||
// no saving here
|
// no saving here
|
||||||
|
|
||||||
await loadNoteToEditor(getCurrentNoteId());
|
await loadNoteDetail(getCurrentNoteId());
|
||||||
}
|
}
|
||||||
|
|
||||||
async function switchToNote(noteId) {
|
async function switchToNote(noteId) {
|
||||||
if (getCurrentNoteId() !== noteId) {
|
if (getCurrentNoteId() !== noteId) {
|
||||||
await saveNoteIfChanged();
|
await saveNoteIfChanged();
|
||||||
|
|
||||||
await loadNoteToEditor(noteId);
|
await loadNoteDetail(noteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveNote() {
|
||||||
|
const note = getCurrentNote();
|
||||||
|
|
||||||
|
note.title = $noteTitle.val();
|
||||||
|
note.content = getComponent(note.type).getContent();
|
||||||
|
|
||||||
|
treeService.setNoteTitle(note.noteId, note.title);
|
||||||
|
|
||||||
|
await server.put('notes/' + note.noteId, note.dto);
|
||||||
|
|
||||||
|
isNoteChanged = false;
|
||||||
|
|
||||||
|
if (note.isProtected) {
|
||||||
|
protectedSessionHolder.touchProtectedSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
infoService.showMessage("Saved!");
|
||||||
|
}
|
||||||
|
|
||||||
async function saveNoteIfChanged() {
|
async function saveNoteIfChanged() {
|
||||||
if (!isNoteChanged) {
|
if (!isNoteChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = getCurrentNote();
|
await saveNote();
|
||||||
|
|
||||||
updateNoteFromInputs(note);
|
|
||||||
|
|
||||||
await saveNoteToServer(note);
|
|
||||||
|
|
||||||
if (note.isProtected) {
|
|
||||||
protectedSessionHolder.touchProtectedSession();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNoteFromInputs(note) {
|
|
||||||
note.title = $noteTitle.val();
|
|
||||||
note.content = getComponent(note.type).getContent();
|
|
||||||
|
|
||||||
treeService.setNoteTitle(note.noteId, note.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveNoteToServer(note) {
|
|
||||||
const dto = Object.assign({}, note);
|
|
||||||
delete dto.treeCache;
|
|
||||||
delete dto.hideInAutocomplete;
|
|
||||||
|
|
||||||
await server.put('notes/' + dto.noteId, dto);
|
|
||||||
|
|
||||||
isNoteChanged = false;
|
|
||||||
|
|
||||||
infoService.showMessage("Saved!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNoteBackgroundIfProtected(note) {
|
function setNoteBackgroundIfProtected(note) {
|
||||||
@@ -145,7 +139,7 @@ async function handleProtectedSession() {
|
|||||||
protectedSessionService.ensureDialogIsClosed();
|
protectedSessionService.ensureDialogIsClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadNoteToEditor(noteId) {
|
async function loadNoteDetail(noteId) {
|
||||||
currentNote = await loadNote(noteId);
|
currentNote = await loadNote(noteId);
|
||||||
|
|
||||||
if (isNewNoteCreated) {
|
if (isNewNoteCreated) {
|
||||||
@@ -183,6 +177,26 @@ async function loadNoteToEditor(noteId) {
|
|||||||
$noteDetailWrapper.scrollTop(0);
|
$noteDetailWrapper.scrollTop(0);
|
||||||
|
|
||||||
await loadLabelList();
|
await loadLabelList();
|
||||||
|
|
||||||
|
await showChildrenOverview();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showChildrenOverview() {
|
||||||
|
const note = getCurrentNote();
|
||||||
|
|
||||||
|
$childrenOverview.empty();
|
||||||
|
|
||||||
|
const notePath = treeService.getCurrentNotePath();
|
||||||
|
|
||||||
|
for (const childBranch of await note.getChildBranches()) {
|
||||||
|
const link = $('<a>', {
|
||||||
|
href: 'javascript:',
|
||||||
|
text: await treeUtils.getNoteTitle(childBranch.noteId, childBranch.parentNoteId)
|
||||||
|
}).attr('action', 'note').attr('note-path', notePath + '/' + childBranch.noteId);
|
||||||
|
|
||||||
|
const childEl = $('<div class="child-overview">').html(link);
|
||||||
|
$childrenOverview.append(childEl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadLabelList() {
|
async function loadLabelList() {
|
||||||
@@ -245,8 +259,6 @@ setInterval(saveNoteIfChanged, 5000);
|
|||||||
export default {
|
export default {
|
||||||
reload,
|
reload,
|
||||||
switchToNote,
|
switchToNote,
|
||||||
updateNoteFromInputs,
|
|
||||||
saveNoteToServer,
|
|
||||||
setNoteBackgroundIfProtected,
|
setNoteBackgroundIfProtected,
|
||||||
loadNote,
|
loadNote,
|
||||||
getCurrentNote,
|
getCurrentNote,
|
||||||
@@ -255,6 +267,7 @@ export default {
|
|||||||
newNoteCreated,
|
newNoteCreated,
|
||||||
focus,
|
focus,
|
||||||
loadLabelList,
|
loadLabelList,
|
||||||
|
saveNote,
|
||||||
saveNoteIfChanged,
|
saveNoteIfChanged,
|
||||||
noteChanged
|
noteChanged
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import utils from "./utils.js";
|
|
||||||
import libraryLoader from "./library_loader.js";
|
import libraryLoader from "./library_loader.js";
|
||||||
import bundleService from "./bundle.js";
|
import bundleService from "./bundle.js";
|
||||||
import infoService from "./info.js";
|
import infoService from "./info.js";
|
||||||
@@ -11,15 +10,19 @@ const $noteDetailCode = $('#note-detail-code');
|
|||||||
const $executeScriptButton = $("#execute-script-button");
|
const $executeScriptButton = $("#execute-script-button");
|
||||||
|
|
||||||
async function show() {
|
async function show() {
|
||||||
if (!codeEditor) {
|
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
|
||||||
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
|
|
||||||
|
|
||||||
|
if (!codeEditor) {
|
||||||
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
|
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
|
||||||
CodeMirror.keyMap.default["Tab"] = "indentMore";
|
CodeMirror.keyMap.default["Tab"] = "indentMore";
|
||||||
|
|
||||||
|
// these conflict with backward/forward navigation shortcuts
|
||||||
|
delete CodeMirror.keyMap.default["Alt-Left"];
|
||||||
|
delete CodeMirror.keyMap.default["Alt-Right"];
|
||||||
|
|
||||||
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
|
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
|
||||||
|
|
||||||
codeEditor = CodeMirror($("#note-detail-code")[0], {
|
codeEditor = CodeMirror($noteDetailCode[0], {
|
||||||
value: "",
|
value: "",
|
||||||
viewportMargin: Infinity,
|
viewportMargin: Infinity,
|
||||||
indentUnit: 4,
|
indentUnit: 4,
|
||||||
@@ -38,7 +41,7 @@ async function show() {
|
|||||||
|
|
||||||
const currentNote = noteDetailService.getCurrentNote();
|
const currentNote = noteDetailService.getCurrentNote();
|
||||||
|
|
||||||
// this needs to happen after the element is shown, otherwise the editor won't be refresheds
|
// this needs to happen after the element is shown, otherwise the editor won't be refreshed
|
||||||
codeEditor.setValue(currentNote.content);
|
codeEditor.setValue(currentNote.content);
|
||||||
|
|
||||||
const info = CodeMirror.findModeByMIME(currentNote.mime);
|
const info = CodeMirror.findModeByMIME(currentNote.mime);
|
||||||
@@ -67,13 +70,13 @@ async function executeCurrentNote() {
|
|||||||
const currentNote = noteDetailService.getCurrentNote();
|
const currentNote = noteDetailService.getCurrentNote();
|
||||||
|
|
||||||
if (currentNote.mime.endsWith("env=frontend")) {
|
if (currentNote.mime.endsWith("env=frontend")) {
|
||||||
const bundle = await server.get('script/bundle/' + getCurrentNoteId());
|
const bundle = await server.get('script/bundle/' + noteDetailService.getCurrentNoteId());
|
||||||
|
|
||||||
bundleService.executeBundle(bundle);
|
bundleService.executeBundle(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentNote.mime.endsWith("env=backend")) {
|
if (currentNote.mime.endsWith("env=backend")) {
|
||||||
await server.post('script/run/' + getCurrentNoteId());
|
await server.post('script/run/' + noteDetailService.getCurrentNoteId());
|
||||||
}
|
}
|
||||||
|
|
||||||
infoService.showMessage("Note executed");
|
infoService.showMessage("Note executed");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
import noteDetail from './note_detail.js';
|
import noteDetailService from './note_detail.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import infoService from "./info.js";
|
import infoService from "./info.js";
|
||||||
|
|
||||||
@@ -84,13 +84,13 @@ function NoteTypeModel() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
const note = noteDetail.getCurrentNote();
|
const note = noteDetailService.getCurrentNote();
|
||||||
|
|
||||||
await server.put('notes/' + note.noteId
|
await server.put('notes/' + note.noteId
|
||||||
+ '/type/' + encodeURIComponent(self.type())
|
+ '/type/' + encodeURIComponent(self.type())
|
||||||
+ '/mime/' + encodeURIComponent(self.mime()));
|
+ '/mime/' + encodeURIComponent(self.mime()));
|
||||||
|
|
||||||
await noteDetail.reload();
|
await noteDetailService.reload();
|
||||||
|
|
||||||
// for the note icon to be updated in the tree
|
// for the note icon to be updated in the tree
|
||||||
await treeService.reload();
|
await treeService.reload();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
import noteDetail from './note_detail.js';
|
import noteDetailService from './note_detail.js';
|
||||||
import utils from './utils.js';
|
import utils from './utils.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import protectedSessionHolder from './protected_session_holder.js';
|
import protectedSessionHolder from './protected_session_holder.js';
|
||||||
@@ -57,7 +57,7 @@ async function setupProtectedSession() {
|
|||||||
|
|
||||||
$dialog.dialog("close");
|
$dialog.dialog("close");
|
||||||
|
|
||||||
noteDetail.reload();
|
noteDetailService.reload();
|
||||||
treeService.reload();
|
treeService.reload();
|
||||||
|
|
||||||
if (protectedSessionDeferred !== null) {
|
if (protectedSessionDeferred !== null) {
|
||||||
@@ -90,33 +90,27 @@ async function enterProtectedSession(password) {
|
|||||||
async function protectNoteAndSendToServer() {
|
async function protectNoteAndSendToServer() {
|
||||||
await ensureProtectedSession(true, true);
|
await ensureProtectedSession(true, true);
|
||||||
|
|
||||||
const note = noteDetail.getCurrentNote();
|
const note = noteDetailService.getCurrentNote();
|
||||||
|
|
||||||
noteDetail.updateNoteFromInputs(note);
|
|
||||||
|
|
||||||
note.isProtected = true;
|
note.isProtected = true;
|
||||||
|
|
||||||
await noteDetail.saveNoteToServer(note);
|
await noteDetailService.saveNote(note);
|
||||||
|
|
||||||
treeService.setProtected(note.noteId, note.isProtected);
|
treeService.setProtected(note.noteId, note.isProtected);
|
||||||
|
|
||||||
noteDetail.setNoteBackgroundIfProtected(note);
|
noteDetailService.setNoteBackgroundIfProtected(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unprotectNoteAndSendToServer() {
|
async function unprotectNoteAndSendToServer() {
|
||||||
await ensureProtectedSession(true, true);
|
await ensureProtectedSession(true, true);
|
||||||
|
|
||||||
const note = noteDetail.getCurrentNote();
|
const note = noteDetailService.getCurrentNote();
|
||||||
|
|
||||||
noteDetail.updateNoteFromInputs(note);
|
|
||||||
|
|
||||||
note.isProtected = false;
|
note.isProtected = false;
|
||||||
|
|
||||||
await noteDetail.saveNoteToServer(note);
|
await noteDetailService.saveNote(note);
|
||||||
|
|
||||||
treeService.setProtected(note.noteId, note.isProtected);
|
treeService.setProtected(note.noteId, note.isProtected);
|
||||||
|
|
||||||
noteDetail.setNoteBackgroundIfProtected(note);
|
noteDetailService.setNoteBackgroundIfProtected(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function protectBranch(noteId, protect) {
|
async function protectBranch(noteId, protect) {
|
||||||
@@ -127,7 +121,7 @@ async function protectBranch(noteId, protect) {
|
|||||||
infoService.showMessage("Request to un/protect sub tree has finished successfully");
|
infoService.showMessage("Request to un/protect sub tree has finished successfully");
|
||||||
|
|
||||||
treeService.reload();
|
treeService.reload();
|
||||||
noteDetail.reload();
|
noteDetailService.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
$passwordForm.submit(() => {
|
$passwordForm.submit(() => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
import utils from './utils.js';
|
import utils from './utils.js';
|
||||||
|
import infoService from './info.js';
|
||||||
|
|
||||||
function ScriptApi(startNote, currentNote) {
|
function ScriptApi(startNote, currentNote) {
|
||||||
const $pluginButtons = $("#plugin-buttons");
|
const $pluginButtons = $("#plugin-buttons");
|
||||||
@@ -54,7 +55,11 @@ function ScriptApi(startNote, currentNote) {
|
|||||||
activateNote,
|
activateNote,
|
||||||
getInstanceName: () => window.glob.instanceName,
|
getInstanceName: () => window.glob.instanceName,
|
||||||
runOnServer,
|
runOnServer,
|
||||||
formatDateISO: utils.formatDateISO
|
formatDateISO: utils.formatDateISO,
|
||||||
|
parseDate: utils.parseDate,
|
||||||
|
showMessage: infoService.showMessage,
|
||||||
|
showError: infoService.showError,
|
||||||
|
reloadTree: treeService.reload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,19 +85,17 @@ async function ajax(url, method, data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
if (utils.isElectron()) {
|
||||||
if (utils.isElectron()) {
|
const ipc = require('electron').ipcRenderer;
|
||||||
const ipc = require('electron').ipcRenderer;
|
|
||||||
|
|
||||||
ipc.on('server-response', (event, arg) => {
|
ipc.on('server-response', (event, arg) => {
|
||||||
console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode);
|
console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode);
|
||||||
|
|
||||||
reqResolves[arg.requestId](arg.body);
|
reqResolves[arg.requestId](arg.body);
|
||||||
|
|
||||||
delete reqResolves[arg.requestId];
|
delete reqResolves[arg.requestId];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 100);
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
get,
|
get,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import utils from './utils.js';
|
import server from './server.js';
|
||||||
import infoService from "./info.js";
|
import infoService from "./info.js";
|
||||||
|
|
||||||
async function syncNow() {
|
async function syncNow() {
|
||||||
@@ -19,7 +19,7 @@ async function syncNow() {
|
|||||||
$("#sync-now-button").click(syncNow);
|
$("#sync-now-button").click(syncNow);
|
||||||
|
|
||||||
async function forceNoteSync(noteId) {
|
async function forceNoteSync(noteId) {
|
||||||
const result = await server.post('sync/force-note-sync/' + noteId);
|
await server.post('sync/force-note-sync/' + noteId);
|
||||||
|
|
||||||
infoService.showMessage("Note added to sync queue.");
|
infoService.showMessage("Note added to sync queue.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ function initFancyTree(branch) {
|
|||||||
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
keyboard: false, // we takover keyboard handling in the hotkeys plugin
|
||||||
extensions: ["hotkeys", "filter", "dnd", "clones"],
|
extensions: ["hotkeys", "filter", "dnd", "clones"],
|
||||||
source: branch,
|
source: branch,
|
||||||
scrollParent: $("#tree"),
|
scrollParent: $tree,
|
||||||
click: (event, data) => {
|
click: (event, data) => {
|
||||||
const targetType = data.targetType;
|
const targetType = data.targetType;
|
||||||
const node = data.node;
|
const node = data.node;
|
||||||
|
|||||||
799
src/public/libraries/jquery.js
vendored
799
src/public/libraries/jquery.js
vendored
File diff suppressed because it is too large
Load Diff
7
src/public/libraries/jquery.min.js
vendored
7
src/public/libraries/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,9 +5,9 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "header header"
|
grid-template-areas: "header header"
|
||||||
"tree-actions title"
|
"tree-actions title"
|
||||||
"search note-content"
|
"search note-detail"
|
||||||
"tree note-content"
|
"tree note-detail"
|
||||||
"parent-list note-content"
|
"parent-list note-detail"
|
||||||
"parent-list label-list";
|
"parent-list label-list";
|
||||||
grid-template-columns: 2fr 5fr;
|
grid-template-columns: 2fr 5fr;
|
||||||
grid-template-rows: auto
|
grid-template-rows: auto
|
||||||
@@ -288,4 +288,21 @@ div.ui-tooltip {
|
|||||||
#file-table th, #file-table td {
|
#file-table th, #file-table td {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: large;
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
#children-overview {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.child-overview {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: large;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid black;
|
||||||
|
width: 150px;
|
||||||
|
height: 95px;
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 15px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,68 @@ async function exportNote(req, res) {
|
|||||||
|
|
||||||
const pack = tar.pack();
|
const pack = tar.pack();
|
||||||
|
|
||||||
const name = await exportNoteInner(branchId, '', pack);
|
const exportedNoteIds = [];
|
||||||
|
const name = await exportNoteInner(branchId, '');
|
||||||
|
|
||||||
|
async function exportNoteInner(branchId, directory) {
|
||||||
|
const branch = await repository.getBranch(branchId);
|
||||||
|
const note = await branch.getNote();
|
||||||
|
const childFileName = directory + sanitize(note.title);
|
||||||
|
|
||||||
|
if (exportedNoteIds.includes(note.noteId)) {
|
||||||
|
saveMetadataFile(childFileName, {
|
||||||
|
version: 1,
|
||||||
|
clone: true,
|
||||||
|
noteId: note.noteId,
|
||||||
|
prefix: branch.prefix
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = {
|
||||||
|
version: 1,
|
||||||
|
clone: false,
|
||||||
|
noteId: note.noteId,
|
||||||
|
title: note.title,
|
||||||
|
prefix: branch.prefix,
|
||||||
|
type: note.type,
|
||||||
|
mime: note.mime,
|
||||||
|
labels: (await note.getLabels()).map(label => {
|
||||||
|
return {
|
||||||
|
name: label.name,
|
||||||
|
value: label.value
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if (metadata.labels.find(label => label.name === 'excludeFromExport')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveMetadataFile(childFileName, metadata);
|
||||||
|
saveDataFile(childFileName, note);
|
||||||
|
|
||||||
|
exportedNoteIds.push(note.noteId);
|
||||||
|
|
||||||
|
for (const child of await note.getChildBranches()) {
|
||||||
|
await exportNoteInner(child.branchId, childFileName + "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
return childFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveDataFile(childFileName, note) {
|
||||||
|
const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content;
|
||||||
|
|
||||||
|
pack.entry({name: childFileName + ".dat", size: content.length}, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveMetadataFile(childFileName, metadata) {
|
||||||
|
const metadataJson = JSON.stringify(metadata, null, '\t');
|
||||||
|
|
||||||
|
pack.entry({name: childFileName + ".meta", size: metadataJson.length}, metadataJson);
|
||||||
|
}
|
||||||
|
|
||||||
pack.finalize();
|
pack.finalize();
|
||||||
|
|
||||||
@@ -23,51 +84,6 @@ async function exportNote(req, res) {
|
|||||||
pack.pipe(res);
|
pack.pipe(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportNoteInner(branchId, directory, pack) {
|
|
||||||
const branch = await repository.getBranch(branchId);
|
|
||||||
const note = await branch.getNote();
|
|
||||||
|
|
||||||
if (note.isProtected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadata = await getMetadata(note);
|
|
||||||
|
|
||||||
if (metadata.labels.find(label => label.name === 'excludeFromExport')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metadataJson = JSON.stringify(metadata, null, '\t');
|
|
||||||
const childFileName = directory + sanitize(note.title);
|
|
||||||
|
|
||||||
pack.entry({ name: childFileName + ".meta", size: metadataJson.length }, metadataJson);
|
|
||||||
|
|
||||||
const content = note.type === 'text' ? html.prettyPrint(note.content, {indent_size: 2}) : note.content;
|
|
||||||
|
|
||||||
pack.entry({ name: childFileName + ".dat", size: content.length }, content);
|
|
||||||
|
|
||||||
for (const child of await note.getChildBranches()) {
|
|
||||||
await exportNoteInner(child.branchId, childFileName + "/", pack);
|
|
||||||
}
|
|
||||||
|
|
||||||
return childFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getMetadata(note) {
|
|
||||||
return {
|
|
||||||
version: 1,
|
|
||||||
title: note.title,
|
|
||||||
type: note.type,
|
|
||||||
mime: note.mime,
|
|
||||||
labels: (await note.getLabels()).map(label => {
|
|
||||||
return {
|
|
||||||
name: label.name,
|
|
||||||
value: label.value
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
exportNote
|
exportNote
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
const repository = require('../../services/repository');
|
const repository = require('../../services/repository');
|
||||||
const labelService = require('../../services/labels');
|
const labelService = require('../../services/labels');
|
||||||
const noteService = require('../../services/notes');
|
const noteService = require('../../services/notes');
|
||||||
|
const Branch = require('../../entities/branch');
|
||||||
const tar = require('tar-stream');
|
const tar = require('tar-stream');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -31,7 +32,7 @@ async function parseImportFile(file) {
|
|||||||
const extract = tar.extract();
|
const extract = tar.extract();
|
||||||
|
|
||||||
extract.on('entry', function(header, stream, next) {
|
extract.on('entry', function(header, stream, next) {
|
||||||
let {name, key} = getFileName(header.name);
|
const {name, key} = getFileName(header.name);
|
||||||
|
|
||||||
let file = fileMap[name];
|
let file = fileMap[name];
|
||||||
|
|
||||||
@@ -97,30 +98,46 @@ async function importTar(req) {
|
|||||||
|
|
||||||
const files = await parseImportFile(file);
|
const files = await parseImportFile(file);
|
||||||
|
|
||||||
await importNotes(files, parentNoteId);
|
// maps from original noteId (in tar file) to newly generated noteId
|
||||||
|
const noteIdMap = {};
|
||||||
|
|
||||||
|
await importNotes(files, parentNoteId, noteIdMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importNotes(files, parentNoteId) {
|
async function importNotes(files, parentNoteId, noteIdMap) {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.meta.version !== 1) {
|
if (file.meta.version !== 1) {
|
||||||
throw new Error("Can't read meta data version " + file.meta.version);
|
throw new Error("Can't read meta data version " + file.meta.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (file.meta.clone) {
|
||||||
|
await new Branch({
|
||||||
|
parentNoteId: parentNoteId,
|
||||||
|
noteId: noteIdMap[file.meta.noteId],
|
||||||
|
prefix: file.meta.prefix
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (file.meta.type !== 'file') {
|
if (file.meta.type !== 'file') {
|
||||||
file.data = file.data.toString("UTF-8");
|
file.data = file.data.toString("UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
const {note} = await noteService.createNote(parentNoteId, file.meta.title, file.data, {
|
const {note} = await noteService.createNote(parentNoteId, file.meta.title, file.data, {
|
||||||
type: file.meta.type,
|
type: file.meta.type,
|
||||||
mime: file.meta.mime
|
mime: file.meta.mime,
|
||||||
|
prefix: file.meta.prefix
|
||||||
});
|
});
|
||||||
|
|
||||||
|
noteIdMap[file.meta.noteId] = note.noteId;
|
||||||
|
|
||||||
for (const label of file.meta.labels) {
|
for (const label of file.meta.labels) {
|
||||||
await labelService.createLabel(note.noteId, label.name, label.value);
|
await labelService.createLabel(note.noteId, label.name, label.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.children.length > 0) {
|
if (file.children.length > 0) {
|
||||||
await importNotes(file.children, note.noteId);
|
await importNotes(file.children, note.noteId, noteIdMap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const appInfo = require('../../services/app_info');
|
|||||||
async function getMigrationInfo() {
|
async function getMigrationInfo() {
|
||||||
return {
|
return {
|
||||||
dbVersion: parseInt(await optionService.getOption('dbVersion')),
|
dbVersion: parseInt(await optionService.getOption('dbVersion')),
|
||||||
app_dbVersion: appInfo.dbVersion
|
appDbVersion: appInfo.dbVersion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ async function uploadImage(req) {
|
|||||||
return [400, "Unknown image type: " + file.mimetype];
|
return [400, "Unknown image type: " + file.mimetype];
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentNoteId = await dateNoteService.getDateNoteId(req.headers['x-local-date']);
|
const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']);
|
||||||
|
|
||||||
const {note} = await noteService.createNewNote(parentNoteId, {
|
const {note} = await noteService.createNewNote(parentNote.noteId, {
|
||||||
title: "Sender image",
|
title: "Sender image",
|
||||||
content: "",
|
content: "",
|
||||||
target: 'into',
|
target: 'into',
|
||||||
@@ -57,9 +57,9 @@ async function uploadImage(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveNote(req) {
|
async function saveNote(req) {
|
||||||
const parentNoteId = await dateNoteService.getDateNoteId(req.headers['x-local-date']);
|
const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']);
|
||||||
|
|
||||||
await noteService.createNewNote(parentNoteId, {
|
await noteService.createNewNote(parentNote.noteId, {
|
||||||
title: req.body.title,
|
title: req.body.title,
|
||||||
content: req.body.content,
|
content: req.body.content,
|
||||||
target: 'into',
|
target: 'into',
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const log = require('../../services/log');
|
|||||||
|
|
||||||
async function checkSync() {
|
async function checkSync() {
|
||||||
return {
|
return {
|
||||||
'hashes': await contentHashService.getHashes(),
|
hashes: await contentHashService.getHashes(),
|
||||||
'max_sync_id': await sql.getValue('SELECT MAX(id) FROM sync')
|
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,129 +55,21 @@ async function forceNoteSync(req) {
|
|||||||
syncService.sync();
|
syncService.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getChanged() {
|
async function getChanged(req) {
|
||||||
const lastSyncId = parseInt(req.query.lastSyncId);
|
const lastSyncId = parseInt(req.query.lastSyncId);
|
||||||
|
|
||||||
return await sql.getRows("SELECT * FROM sync WHERE id > ?", [lastSyncId]);
|
const syncs = await sql.getRows("SELECT * FROM sync WHERE id > ? LIMIT 1000", [lastSyncId]);
|
||||||
|
|
||||||
|
return await syncService.getSyncRecords(syncs);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getNote(req) {
|
async function update(req) {
|
||||||
const noteId = req.params.noteId;
|
const sourceId = req.body.sourceId;
|
||||||
const entity = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
const entities = req.body.entities;
|
||||||
|
|
||||||
syncService.serializeNoteContentBuffer(entity);
|
for (const {sync, entity} of entities) {
|
||||||
|
await syncUpdateService.updateEntity(sync.entityName, entity, sourceId);
|
||||||
return {
|
|
||||||
entity: entity
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getBranch(req) {
|
|
||||||
const branchId = req.params.branchId;
|
|
||||||
|
|
||||||
return await sql.getRow("SELECT * FROM branches WHERE branchId = ?", [branchId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNoteRevision(req) {
|
|
||||||
const noteRevisionId = req.params.noteRevisionId;
|
|
||||||
|
|
||||||
return await sql.getRow("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getOption(req) {
|
|
||||||
const name = req.params.name;
|
|
||||||
const opt = await sql.getRow("SELECT * FROM options WHERE name = ?", [name]);
|
|
||||||
|
|
||||||
if (!opt.isSynced) {
|
|
||||||
return [400, "This option can't be synced."];
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return opt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNoteReordering(req) {
|
|
||||||
const parentNoteId = req.params.parentNoteId;
|
|
||||||
|
|
||||||
return {
|
|
||||||
parentNoteId: parentNoteId,
|
|
||||||
ordering: await sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [parentNoteId])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getRecentNote(req) {
|
|
||||||
const branchId = req.params.branchId;
|
|
||||||
|
|
||||||
return await sql.getRow("SELECT * FROM recent_notes WHERE branchId = ?", [branchId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getImage(req) {
|
|
||||||
const imageId = req.params.imageId;
|
|
||||||
const entity = await sql.getRow("SELECT * FROM images WHERE imageId = ?", [imageId]);
|
|
||||||
|
|
||||||
if (entity && entity.data !== null) {
|
|
||||||
entity.data = entity.data.toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNoteImage(req) {
|
|
||||||
const noteImageId = req.params.noteImageId;
|
|
||||||
|
|
||||||
return await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [noteImageId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getLabel(req) {
|
|
||||||
const labelId = req.params.labelId;
|
|
||||||
|
|
||||||
return await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [labelId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getApiToken(req) {
|
|
||||||
const apiTokenId = req.params.apiTokenId;
|
|
||||||
|
|
||||||
return await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [apiTokenId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateNote(req) {
|
|
||||||
await syncUpdateService.updateNote(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateBranch(req) {
|
|
||||||
await syncUpdateService.updateBranch(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateNoteRevision(req) {
|
|
||||||
await syncUpdateService.updateNoteRevision(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateNoteReordering(req) {
|
|
||||||
await syncUpdateService.updateNoteReordering(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateOption(req) {
|
|
||||||
await syncUpdateService.updateOptions(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateRecentNote(req) {
|
|
||||||
await syncUpdateService.updateRecentNotes(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateImage(req) {
|
|
||||||
await syncUpdateService.updateImage(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateNoteImage(req) {
|
|
||||||
await syncUpdateService.updateNoteImage(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateLabel(req) {
|
|
||||||
await syncUpdateService.updateLabel(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateApiToken(req) {
|
|
||||||
await syncUpdateService.updateApiToken(req.body.entity, req.body.sourceId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -187,24 +79,5 @@ module.exports = {
|
|||||||
forceFullSync,
|
forceFullSync,
|
||||||
forceNoteSync,
|
forceNoteSync,
|
||||||
getChanged,
|
getChanged,
|
||||||
getNote,
|
update
|
||||||
getBranch,
|
|
||||||
getImage,
|
|
||||||
getNoteImage,
|
|
||||||
getNoteReordering,
|
|
||||||
getNoteRevision,
|
|
||||||
getRecentNote,
|
|
||||||
getOption,
|
|
||||||
getLabel,
|
|
||||||
getApiToken,
|
|
||||||
updateNote,
|
|
||||||
updateBranch,
|
|
||||||
updateImage,
|
|
||||||
updateNoteImage,
|
|
||||||
updateNoteReordering,
|
|
||||||
updateNoteRevision,
|
|
||||||
updateRecentNote,
|
|
||||||
updateOption,
|
|
||||||
updateLabel,
|
|
||||||
updateApiToken
|
|
||||||
};
|
};
|
||||||
@@ -19,6 +19,7 @@ function init(app) {
|
|||||||
|
|
||||||
res.status = function(statusCode) {
|
res.status = function(statusCode) {
|
||||||
res.statusCode = statusCode;
|
res.statusCode = statusCode;
|
||||||
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
res.send = function(obj) {
|
res.send = function(obj) {
|
||||||
|
|||||||
@@ -40,22 +40,22 @@ const cls = require('../services/cls');
|
|||||||
const sql = require('../services/sql');
|
const sql = require('../services/sql');
|
||||||
const protectedSessionService = require('../services/protected_session');
|
const protectedSessionService = require('../services/protected_session');
|
||||||
|
|
||||||
function apiResultHandler(res, result) {
|
function apiResultHandler(req, res, result) {
|
||||||
// if it's an array and first element is integer then we consider this to be [statusCode, response] format
|
// if it's an array and first element is integer then we consider this to be [statusCode, response] format
|
||||||
if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) {
|
if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) {
|
||||||
const [statusCode, response] = result;
|
const [statusCode, response] = result;
|
||||||
|
|
||||||
res.status(statusCode).send(response);
|
res.status(statusCode).send(response);
|
||||||
|
|
||||||
if (statusCode !== 200) {
|
if (statusCode !== 200 && statusCode !== 201 && statusCode !== 204) {
|
||||||
log.info(`${method} ${path} returned ${statusCode} with response ${JSON.stringify(response)}`);
|
log.info(`${req.method} ${req.originalUrl} returned ${statusCode} with response ${JSON.stringify(response)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (result === undefined) {
|
else if (result === undefined) {
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
res.status(200).send(result);
|
res.send(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,13 +70,13 @@ function route(method, path, middleware, routeHandler, resultHandler) {
|
|||||||
cls.namespace.set('sourceId', req.headers.source_id);
|
cls.namespace.set('sourceId', req.headers.source_id);
|
||||||
protectedSessionService.setProtectedSessionId(req);
|
protectedSessionService.setProtectedSessionId(req);
|
||||||
|
|
||||||
return await sql.doInTransaction(async () => {
|
return await sql.transactional(async () => {
|
||||||
return await routeHandler(req, res, next);
|
return await routeHandler(req, res, next);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resultHandler) {
|
if (resultHandler) {
|
||||||
resultHandler(res, result);
|
resultHandler(req, res, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
@@ -147,25 +147,7 @@ function register(app) {
|
|||||||
apiRoute(POST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync);
|
apiRoute(POST, '/api/sync/force-full-sync', syncApiRoute.forceFullSync);
|
||||||
apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
|
apiRoute(POST, '/api/sync/force-note-sync/:noteId', syncApiRoute.forceNoteSync);
|
||||||
apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged);
|
apiRoute(GET, '/api/sync/changed', syncApiRoute.getChanged);
|
||||||
apiRoute(GET, '/api/sync/notes/:noteId', syncApiRoute.getNote);
|
apiRoute(PUT, '/api/sync/update', syncApiRoute.update);
|
||||||
apiRoute(GET, '/api/sync/branches/:branchId', syncApiRoute.getBranch);
|
|
||||||
apiRoute(GET, '/api/sync/note_revisions/:noteRevisionId', syncApiRoute.getNoteRevision);
|
|
||||||
apiRoute(GET, '/api/sync/options/:name', syncApiRoute.getOption);
|
|
||||||
apiRoute(GET, '/api/sync/note_reordering/:parentNoteId', syncApiRoute.getNoteReordering);
|
|
||||||
apiRoute(GET, '/api/sync/recent_notes/:branchId', syncApiRoute.getRecentNote);
|
|
||||||
apiRoute(GET, '/api/sync/images/:imageId', syncApiRoute.getImage);
|
|
||||||
apiRoute(GET, '/api/sync/note_images/:noteImageId', syncApiRoute.getNoteImage);
|
|
||||||
apiRoute(GET, '/api/sync/labels/:labelId', syncApiRoute.getLabel);
|
|
||||||
apiRoute(GET, '/api/sync/api_tokens/:apiTokenId', syncApiRoute.getApiToken);
|
|
||||||
apiRoute(PUT, '/api/sync/notes', syncApiRoute.updateNote);
|
|
||||||
apiRoute(PUT, '/api/sync/note_revisions', syncApiRoute.updateNoteRevision);
|
|
||||||
apiRoute(PUT, '/api/sync/note_reordering', syncApiRoute.updateNoteReordering);
|
|
||||||
apiRoute(PUT, '/api/sync/options', syncApiRoute.updateOption);
|
|
||||||
apiRoute(PUT, '/api/sync/recent_notes', syncApiRoute.updateRecentNote);
|
|
||||||
apiRoute(PUT, '/api/sync/images', syncApiRoute.updateImage);
|
|
||||||
apiRoute(PUT, '/api/sync/note_images', syncApiRoute.updateNoteImage);
|
|
||||||
apiRoute(PUT, '/api/sync/labels', syncApiRoute.updateLabel);
|
|
||||||
apiRoute(PUT, '/api/sync/api_tokens', syncApiRoute.updateApiToken);
|
|
||||||
|
|
||||||
apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog);
|
apiRoute(GET, '/api/event-log', eventLogRoute.getEventLog);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
const build = require('./build');
|
const build = require('./build');
|
||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
|
|
||||||
const APP_DB_VERSION = 86;
|
const APP_DB_VERSION = 87;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
appVersion: packageJson.version,
|
appVersion: packageJson.version,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2018-01-17T23:59:03-05:00", buildRevision: "651a9fb3272c85d287c16d5a4978464fb7d2490d" };
|
module.exports = { buildDate:"2018-04-09T22:38:37-04:00", buildRevision: "d8924c536b70415a9e35299f62bcf978320d8fee" };
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ async function changePassword(currentPassword, newPassword) {
|
|||||||
const newPasswordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(newPassword));
|
const newPasswordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(newPassword));
|
||||||
const decryptedDataKey = await passwordEncryptionService.getDataKey(currentPassword);
|
const decryptedDataKey = await passwordEncryptionService.getDataKey(currentPassword);
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);
|
await passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);
|
||||||
|
|
||||||
await optionService.setOption('passwordVerificationHash', newPasswordVerificationKey);
|
await optionService.setOption('passwordVerificationHash', newPasswordVerificationKey);
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
|
const eventLogService = require('./event_log');
|
||||||
|
const messagingService = require('./messaging');
|
||||||
|
|
||||||
function getHash(rows) {
|
function getHash(rows) {
|
||||||
let hash = '';
|
let hash = '';
|
||||||
@@ -121,6 +125,29 @@ async function getHashes() {
|
|||||||
return hashes;
|
return hashes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkContentHashes(otherHashes) {
|
||||||
|
const hashes = await getHashes();
|
||||||
|
let allChecksPassed = true;
|
||||||
|
|
||||||
|
for (const key in hashes) {
|
||||||
|
if (hashes[key] !== otherHashes[key]) {
|
||||||
|
allChecksPassed = false;
|
||||||
|
|
||||||
|
await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`);
|
||||||
|
|
||||||
|
if (key !== 'recent_notes') {
|
||||||
|
// let's not get alarmed about recent notes which get updated often and can cause failures in race conditions
|
||||||
|
await messagingService.sendMessageToAllClients({type: 'sync-hash-check-failed'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allChecksPassed) {
|
||||||
|
log.info("Content hash checks PASSED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getHashes
|
getHashes,
|
||||||
|
checkContentHashes
|
||||||
};
|
};
|
||||||
@@ -4,6 +4,7 @@ const sql = require('./sql');
|
|||||||
const noteService = require('./notes');
|
const noteService = require('./notes');
|
||||||
const labelService = require('./labels');
|
const labelService = require('./labels');
|
||||||
const dateUtils = require('./date_utils');
|
const dateUtils = require('./date_utils');
|
||||||
|
const repository = require('./repository');
|
||||||
|
|
||||||
const CALENDAR_ROOT_LABEL = 'calendarRoot';
|
const CALENDAR_ROOT_LABEL = 'calendarRoot';
|
||||||
const YEAR_LABEL = 'yearNote';
|
const YEAR_LABEL = 'yearNote';
|
||||||
@@ -14,117 +15,112 @@ const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Satur
|
|||||||
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
||||||
|
|
||||||
async function createNote(parentNoteId, noteTitle, noteText) {
|
async function createNote(parentNoteId, noteTitle, noteText) {
|
||||||
const {note} = await noteService.createNewNote(parentNoteId, {
|
return (await noteService.createNewNote(parentNoteId, {
|
||||||
title: noteTitle,
|
title: noteTitle,
|
||||||
content: noteText,
|
content: noteText,
|
||||||
target: 'into',
|
target: 'into',
|
||||||
isProtected: false
|
isProtected: false
|
||||||
});
|
})).note;
|
||||||
|
|
||||||
return note.noteId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getNoteStartingWith(parentNoteId, startsWith) {
|
async function getNoteStartingWith(parentNoteId, startsWith) {
|
||||||
return await sql.getValue(`SELECT noteId FROM notes JOIN branches USING(noteId)
|
return await repository.getEntity(`SELECT notes.* FROM notes JOIN branches USING(noteId)
|
||||||
WHERE parentNoteId = ? AND title LIKE '${startsWith}%'
|
WHERE parentNoteId = ? AND title LIKE '${startsWith}%'
|
||||||
AND notes.isDeleted = 0 AND isProtected = 0
|
AND notes.isDeleted = 0 AND isProtected = 0
|
||||||
AND branches.isDeleted = 0`, [parentNoteId]);
|
AND branches.isDeleted = 0`, [parentNoteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getRootCalendarNoteId() {
|
async function getRootCalendarNote() {
|
||||||
let rootNoteId = await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)
|
let rootNote = await labelService.getNoteWithLabel(CALENDAR_ROOT_LABEL);
|
||||||
WHERE labels.name = '${CALENDAR_ROOT_LABEL}' AND notes.isDeleted = 0`);
|
|
||||||
|
|
||||||
if (!rootNoteId) {
|
if (!rootNote) {
|
||||||
const {rootNote} = await noteService.createNewNote('root', {
|
rootNote = (await noteService.createNewNote('root', {
|
||||||
title: 'Calendar',
|
title: 'Calendar',
|
||||||
target: 'into',
|
target: 'into',
|
||||||
isProtected: false
|
isProtected: false
|
||||||
});
|
})).note;
|
||||||
|
|
||||||
const rootNoteId = rootNote.noteId;
|
await labelService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL);
|
||||||
|
|
||||||
await labelService.createLabel(rootNoteId, CALENDAR_ROOT_LABEL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rootNoteId;
|
return rootNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getYearNoteId(dateTimeStr, rootNoteId) {
|
async function getYearNote(dateTimeStr, rootNote) {
|
||||||
const yearStr = dateTimeStr.substr(0, 4);
|
const yearStr = dateTimeStr.substr(0, 4);
|
||||||
|
|
||||||
let yearNoteId = await labelService.getNoteIdWithLabel(YEAR_LABEL, yearStr);
|
let yearNote = await labelService.getNoteWithLabel(YEAR_LABEL, yearStr);
|
||||||
|
|
||||||
if (!yearNoteId) {
|
if (!yearNote) {
|
||||||
yearNoteId = await getNoteStartingWith(rootNoteId, yearStr);
|
yearNote = await getNoteStartingWith(rootNote.noteId, yearStr);
|
||||||
|
|
||||||
if (!yearNoteId) {
|
if (!yearNote) {
|
||||||
yearNoteId = await createNote(rootNoteId, yearStr);
|
yearNote = await createNote(rootNote.noteId, yearStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
await labelService.createLabel(yearNoteId, YEAR_LABEL, yearStr);
|
await labelService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return yearNoteId;
|
return yearNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMonthNoteId(dateTimeStr, rootNoteId) {
|
async function getMonthNote(dateTimeStr, rootNote) {
|
||||||
const monthStr = dateTimeStr.substr(0, 7);
|
const monthStr = dateTimeStr.substr(0, 7);
|
||||||
const monthNumber = dateTimeStr.substr(5, 2);
|
const monthNumber = dateTimeStr.substr(5, 2);
|
||||||
|
|
||||||
let monthNoteId = await labelService.getNoteIdWithLabel(MONTH_LABEL, monthStr);
|
let monthNote = await labelService.getNoteWithLabel(MONTH_LABEL, monthStr);
|
||||||
|
|
||||||
if (!monthNoteId) {
|
if (!monthNote) {
|
||||||
const yearNoteId = await getYearNoteId(dateTimeStr, rootNoteId);
|
const yearNote = await getYearNote(dateTimeStr, rootNote);
|
||||||
|
|
||||||
monthNoteId = await getNoteStartingWith(yearNoteId, monthNumber);
|
monthNote = await getNoteStartingWith(yearNote.noteId, monthNumber);
|
||||||
|
|
||||||
if (!monthNoteId) {
|
if (!monthNote) {
|
||||||
const dateObj = dateUtils.parseDate(dateTimeStr);
|
const dateObj = dateUtils.parseDate(dateTimeStr);
|
||||||
|
|
||||||
const noteTitle = monthNumber + " - " + MONTHS[dateObj.getMonth()];
|
const noteTitle = monthNumber + " - " + MONTHS[dateObj.getMonth()];
|
||||||
|
|
||||||
monthNoteId = await createNote(yearNoteId, noteTitle);
|
monthNote = await createNote(yearNote.noteId, noteTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
await labelService.createLabel(monthNoteId, MONTH_LABEL, monthStr);
|
await labelService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return monthNoteId;
|
return monthNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDateNoteId(dateTimeStr, rootNoteId = null) {
|
async function getDateNote(dateTimeStr, rootNote = null) {
|
||||||
if (!rootNoteId) {
|
if (!rootNote) {
|
||||||
rootNoteId = await getRootCalendarNoteId();
|
rootNote = await getRootCalendarNote();
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateStr = dateTimeStr.substr(0, 10);
|
const dateStr = dateTimeStr.substr(0, 10);
|
||||||
const dayNumber = dateTimeStr.substr(8, 2);
|
const dayNumber = dateTimeStr.substr(8, 2);
|
||||||
|
|
||||||
let dateNoteId = await labelService.getNoteIdWithLabel(DATE_LABEL, dateStr);
|
let dateNote = await labelService.getNoteWithLabel(DATE_LABEL, dateStr);
|
||||||
|
|
||||||
if (!dateNoteId) {
|
if (!dateNote) {
|
||||||
const monthNoteId = await getMonthNoteId(dateTimeStr, rootNoteId);
|
const monthNote = await getMonthNote(dateTimeStr, rootNote);
|
||||||
|
|
||||||
dateNoteId = await getNoteStartingWith(monthNoteId, dayNumber);
|
dateNote = await getNoteStartingWith(monthNote.noteId, dayNumber);
|
||||||
|
|
||||||
if (!dateNoteId) {
|
if (!dateNote) {
|
||||||
const dateObj = dateUtils.parseDate(dateTimeStr);
|
const dateObj = dateUtils.parseDate(dateTimeStr);
|
||||||
|
|
||||||
const noteTitle = dayNumber + " - " + DAYS[dateObj.getDay()];
|
const noteTitle = dayNumber + " - " + DAYS[dateObj.getDay()];
|
||||||
|
|
||||||
dateNoteId = await createNote(monthNoteId, noteTitle);
|
dateNote = await createNote(monthNote.noteId, noteTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
await labelService.createLabel(dateNoteId, DATE_LABEL, dateStr);
|
await labelService.createLabel(dateNote.noteId, DATE_LABEL, dateStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return dateNoteId;
|
return dateNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getRootCalendarNoteId,
|
getRootCalendarNote,
|
||||||
getYearNoteId,
|
getYearNote,
|
||||||
getMonthNoteId,
|
getMonthNote,
|
||||||
getDateNoteId
|
getDateNote
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('./sql');
|
|
||||||
const repository = require('./repository');
|
const repository = require('./repository');
|
||||||
const Label = require('../entities/label');
|
const Label = require('../entities/label');
|
||||||
|
|
||||||
@@ -15,14 +14,6 @@ const BUILTIN_LABELS = [
|
|||||||
'appCss'
|
'appCss'
|
||||||
];
|
];
|
||||||
|
|
||||||
async function getNoteIdWithLabel(name, value) {
|
|
||||||
return await sql.getValue(`SELECT notes.noteId FROM notes JOIN labels USING(noteId)
|
|
||||||
WHERE notes.isDeleted = 0
|
|
||||||
AND labels.isDeleted = 0
|
|
||||||
AND labels.name = ?
|
|
||||||
AND labels.value = ?`, [name, value]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getNotesWithLabel(name, value) {
|
async function getNotesWithLabel(name, value) {
|
||||||
let notes;
|
let notes;
|
||||||
|
|
||||||
@@ -44,11 +35,6 @@ async function getNoteWithLabel(name, value) {
|
|||||||
return notes.length > 0 ? notes[0] : null;
|
return notes.length > 0 ? notes[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getNoteIdsWithLabel(name) {
|
|
||||||
return await sql.getColumn(`SELECT DISTINCT notes.noteId FROM notes JOIN labels USING(noteId)
|
|
||||||
WHERE notes.isDeleted = 0 AND labels.isDeleted = 0 AND labels.name = ? AND labels.isDeleted = 0`, [name]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createLabel(noteId, name, value = "") {
|
async function createLabel(noteId, name, value = "") {
|
||||||
return await new Label({
|
return await new Label({
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
@@ -58,10 +44,8 @@ async function createLabel(noteId, name, value = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getNoteIdWithLabel,
|
|
||||||
getNotesWithLabel,
|
getNotesWithLabel,
|
||||||
getNoteWithLabel,
|
getNoteWithLabel,
|
||||||
getNoteIdsWithLabel,
|
|
||||||
createLabel,
|
createLabel,
|
||||||
BUILTIN_LABELS
|
BUILTIN_LABELS
|
||||||
};
|
};
|
||||||
@@ -15,14 +15,22 @@ const logger = require('simple-node-logger').createRollingFileLogger({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function info(message) {
|
function info(message) {
|
||||||
logger.info(message);
|
// info messages are logged asynchronously
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(message);
|
||||||
|
|
||||||
console.log(message);
|
logger.info(message);
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function error(message) {
|
function error(message) {
|
||||||
|
message = "ERROR: " + message;
|
||||||
|
|
||||||
// we're using .info() instead of .error() because simple-node-logger emits weird error for showError()
|
// we're using .info() instead of .error() because simple-node-logger emits weird error for showError()
|
||||||
info("ERROR: " + message);
|
// errors are logged synchronously to make sure it doesn't get lost in case of crash
|
||||||
|
logger.info(message);
|
||||||
|
|
||||||
|
console.trace(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestBlacklist = [ "/libraries", "/javascripts", "/images", "/stylesheets" ];
|
const requestBlacklist = [ "/libraries", "/javascripts", "/images", "/stylesheets" ];
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ async function migrate() {
|
|||||||
// needs to happen outside of the transaction (otherwise it's a NO-OP)
|
// needs to happen outside of the transaction (otherwise it's a NO-OP)
|
||||||
await sql.execute("PRAGMA foreign_keys = OFF");
|
await sql.execute("PRAGMA foreign_keys = OFF");
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
if (mig.type === 'sql') {
|
if (mig.type === 'sql') {
|
||||||
const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8');
|
const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8');
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ async function createNewNote(parentNoteId, noteData) {
|
|||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
notePosition: newNotePos,
|
notePosition: newNotePos,
|
||||||
|
prefix: noteData.prefix,
|
||||||
isExpanded: 0
|
isExpanded: 0
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
@@ -180,6 +181,8 @@ async function saveNoteRevision(note) {
|
|||||||
// title and text should be decrypted now
|
// title and text should be decrypted now
|
||||||
title: note.title,
|
title: note.title,
|
||||||
content: note.content,
|
content: note.content,
|
||||||
|
type: note.type,
|
||||||
|
mime: note.mime,
|
||||||
isProtected: 0, // will be fixed in the protectNoteRevisions() call
|
isProtected: 0, // will be fixed in the protectNoteRevisions() call
|
||||||
dateModifiedFrom: note.dateModified,
|
dateModifiedFrom: note.dateModified,
|
||||||
dateModifiedTo: dateUtils.nowDate()
|
dateModifiedTo: dateUtils.nowDate()
|
||||||
@@ -198,7 +201,7 @@ async function updateNote(noteId, noteUpdates) {
|
|||||||
await saveNoteRevision(note);
|
await saveNoteRevision(note);
|
||||||
|
|
||||||
note.title = noteUpdates.title;
|
note.title = noteUpdates.title;
|
||||||
note.content = noteUpdates.content;
|
note.setContent(noteUpdates.content);
|
||||||
note.isProtected = noteUpdates.isProtected;
|
note.isProtected = noteUpdates.isProtected;
|
||||||
await note.save();
|
await note.save();
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ async function updateEntity(entity) {
|
|||||||
|
|
||||||
delete clone.jsonContent;
|
delete clone.jsonContent;
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.replace(entity.constructor.tableName, clone);
|
await sql.replace(entity.constructor.tableName, clone);
|
||||||
|
|
||||||
const primaryKey = entity[entity.constructor.primaryKeyName];
|
const primaryKey = entity[entity.constructor.primaryKeyName];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const scriptService = require('./script');
|
const scriptService = require('./script');
|
||||||
const repository = require('./repository');
|
const repository = require('./repository');
|
||||||
const cls = require('./cls');
|
const cls = require('./cls');
|
||||||
|
const sqlInit = require('./sql_init');
|
||||||
|
|
||||||
async function runNotesWithLabel(runAttrValue) {
|
async function runNotesWithLabel(runAttrValue) {
|
||||||
const notes = await repository.getEntities(`
|
const notes = await repository.getEntities(`
|
||||||
@@ -19,8 +20,10 @@ async function runNotesWithLabel(runAttrValue) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
|
sqlInit.dbReady.then(() => {
|
||||||
|
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
|
||||||
|
|
||||||
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
|
||||||
|
|
||||||
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);
|
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);
|
||||||
|
});
|
||||||
@@ -27,7 +27,7 @@ async function executeBundle(bundle, startNote) {
|
|||||||
return await execute(ctx, script, '');
|
return await execute(ctx, script, '');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return await sql.doInTransaction(async () => execute(ctx, script, ''));
|
return await sql.transactional(async () => execute(ctx, script, ''));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,10 +56,10 @@ function ScriptApi(startNote, currentNote) {
|
|||||||
|
|
||||||
this.log = message => log.info(`Script ${currentNote.noteId}: ${message}`);
|
this.log = message => log.info(`Script ${currentNote.noteId}: ${message}`);
|
||||||
|
|
||||||
this.getRootCalendarNoteId = dateNoteService.getRootCalendarNoteId;
|
this.getRootCalendarNote = dateNoteService.getRootCalendarNote;
|
||||||
this.getDateNoteId = dateNoteService.getDateNoteId;
|
this.getDateNote = dateNoteService.getDateNote;
|
||||||
|
|
||||||
this.transactional = sql.doInTransaction;
|
this.transactional = sql.transactional;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ScriptContext;
|
module.exports = ScriptContext;
|
||||||
@@ -122,7 +122,7 @@ async function wrap(func) {
|
|||||||
let transactionActive = false;
|
let transactionActive = false;
|
||||||
let transactionPromise = null;
|
let transactionPromise = null;
|
||||||
|
|
||||||
async function doInTransaction(func) {
|
async function transactional(func) {
|
||||||
if (cls.namespace.get('isInTransaction')) {
|
if (cls.namespace.get('isInTransaction')) {
|
||||||
return await func();
|
return await func();
|
||||||
}
|
}
|
||||||
@@ -181,5 +181,5 @@ module.exports = {
|
|||||||
getColumn,
|
getColumn,
|
||||||
execute,
|
execute,
|
||||||
executeScript,
|
executeScript,
|
||||||
doInTransaction
|
transactional
|
||||||
};
|
};
|
||||||
@@ -58,7 +58,7 @@ async function createInitialDatabase() {
|
|||||||
const imagesSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_images.sql', 'UTF-8');
|
const imagesSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_images.sql', 'UTF-8');
|
||||||
const notesImageSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_note_images.sql', 'UTF-8');
|
const notesImageSql = fs.readFileSync(resourceDir.DB_INIT_DIR + '/main_note_images.sql', 'UTF-8');
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.executeScript(schema);
|
await sql.executeScript(schema);
|
||||||
await sql.executeScript(notesSql);
|
await sql.executeScript(notesSql);
|
||||||
await sql.executeScript(notesTreeSql);
|
await sql.executeScript(notesTreeSql);
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ const sourceIdService = require('./source_id');
|
|||||||
const dateUtils = require('./date_utils');
|
const dateUtils = require('./date_utils');
|
||||||
const syncUpdateService = require('./sync_update');
|
const syncUpdateService = require('./sync_update');
|
||||||
const contentHashService = require('./content_hash');
|
const contentHashService = require('./content_hash');
|
||||||
const eventLogService = require('./event_log');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const appInfo = require('./app_info');
|
const appInfo = require('./app_info');
|
||||||
const messagingService = require('./messaging');
|
|
||||||
const syncSetup = require('./sync_setup');
|
const syncSetup = require('./sync_setup');
|
||||||
const syncMutexService = require('./sync_mutex');
|
const syncMutexService = require('./sync_mutex');
|
||||||
const cls = require('./cls');
|
const cls = require('./cls');
|
||||||
@@ -91,69 +89,19 @@ async function login() {
|
|||||||
return syncContext;
|
return syncContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLastSyncedPull() {
|
|
||||||
return parseInt(await optionService.getOption('lastSyncedPull'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setLastSyncedPull(syncId) {
|
|
||||||
await optionService.setOption('lastSyncedPull', syncId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pullSync(syncContext) {
|
async function pullSync(syncContext) {
|
||||||
const lastSyncedPull = await getLastSyncedPull();
|
const changesUri = '/api/sync/changed?lastSyncId=' + await getLastSyncedPull();
|
||||||
|
|
||||||
const changesUri = '/api/sync/changed?lastSyncId=' + lastSyncedPull;
|
const rows = await syncRequest(syncContext, 'GET', changesUri);
|
||||||
|
|
||||||
const syncRows = await syncRequest(syncContext, 'GET', changesUri);
|
log.info("Pulled " + rows.length + " changes from " + changesUri);
|
||||||
|
|
||||||
log.info("Pulled " + syncRows.length + " changes from " + changesUri);
|
for (const {sync, entity} of rows) {
|
||||||
|
|
||||||
for (const sync of syncRows) {
|
|
||||||
if (sourceIdService.isLocalSourceId(sync.sourceId)) {
|
if (sourceIdService.isLocalSourceId(sync.sourceId)) {
|
||||||
log.info(`Skipping pull #${sync.id} ${sync.entityName} ${sync.entityId} because ${sync.sourceId} is a local source id.`);
|
log.info(`Skipping pull #${sync.id} ${sync.entityName} ${sync.entityId} because ${sync.sourceId} is a local source id.`);
|
||||||
|
|
||||||
await setLastSyncedPull(sync.id);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resp = await syncRequest(syncContext, 'GET', "/api/sync/" + sync.entityName + "/" + encodeURIComponent(sync.entityId));
|
|
||||||
|
|
||||||
if (!resp || (sync.entityName === 'notes' && !resp.entity)) {
|
|
||||||
log.error(`Empty response to pull for sync #${sync.id} ${sync.entityName}, id=${sync.entityId}`);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'notes') {
|
|
||||||
await syncUpdateService.updateNote(resp.entity, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'branches') {
|
|
||||||
await syncUpdateService.updateBranch(resp, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'note_revisions') {
|
|
||||||
await syncUpdateService.updateNoteRevision(resp, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'note_reordering') {
|
|
||||||
await syncUpdateService.updateNoteReordering(resp, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'options') {
|
|
||||||
await syncUpdateService.updateOptions(resp, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'recent_notes') {
|
|
||||||
await syncUpdateService.updateRecentNotes(resp, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'images') {
|
|
||||||
await syncUpdateService.updateImage(resp, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'note_images') {
|
|
||||||
await syncUpdateService.updateNoteImage(resp, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'labels') {
|
|
||||||
await syncUpdateService.updateLabel(resp, syncContext.sourceId);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'api_tokens') {
|
|
||||||
await syncUpdateService.updateApiToken(resp, syncContext.sourceId);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`);
|
await syncUpdateService.updateEntity(sync.entityName, entity, syncContext.sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await setLastSyncedPull(sync.id);
|
await setLastSyncedPull(sync.id);
|
||||||
@@ -162,145 +110,69 @@ async function pullSync(syncContext) {
|
|||||||
log.info("Finished pull");
|
log.info("Finished pull");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLastSyncedPush() {
|
|
||||||
return parseInt(await optionService.getOption('lastSyncedPush'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setLastSyncedPush(lastSyncedPush) {
|
|
||||||
await optionService.setOption('lastSyncedPush', lastSyncedPush);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pushSync(syncContext) {
|
async function pushSync(syncContext) {
|
||||||
let lastSyncedPush = await getLastSyncedPush();
|
let lastSyncedPush = await getLastSyncedPush();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const sync = await sql.getRowOrNull('SELECT * FROM sync WHERE id > ? LIMIT 1', [lastSyncedPush]);
|
const syncs = await sql.getRows('SELECT * FROM sync WHERE id > ? LIMIT 1000', [lastSyncedPush]);
|
||||||
|
|
||||||
if (sync === null) {
|
const filteredSyncs = syncs.filter(sync => {
|
||||||
// nothing to sync
|
if (sync.sourceId === syncContext.sourceId) {
|
||||||
|
log.info(`Skipping push #${sync.id} ${sync.entityName} ${sync.entityId} because it originates from sync target`);
|
||||||
|
|
||||||
|
// this may set lastSyncedPush beyond what's actually sent (because of size limit)
|
||||||
|
// so this is applied to the database only if there's no actual update
|
||||||
|
// TODO: it would be better to simplify this somehow
|
||||||
|
lastSyncedPush = sync.id;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filteredSyncs.length === 0) {
|
||||||
log.info("Nothing to push");
|
log.info("Nothing to push");
|
||||||
|
|
||||||
|
await setLastSyncedPush(lastSyncedPush);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sync.sourceId === syncContext.sourceId) {
|
const syncRecords = await getSyncRecords(filteredSyncs);
|
||||||
log.info(`Skipping push #${sync.id} ${sync.entityName} ${sync.entityId} because it originates from sync target`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await pushEntity(sync, syncContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSyncedPush = sync.id;
|
log.info(`Pushing ${syncRecords.length} syncs.`);
|
||||||
|
|
||||||
|
await syncRequest(syncContext, 'PUT', '/api/sync/update', {
|
||||||
|
sourceId: sourceIdService.getCurrentSourceId(),
|
||||||
|
entities: syncRecords
|
||||||
|
});
|
||||||
|
|
||||||
|
lastSyncedPush = syncRecords[syncRecords.length - 1].sync.id;
|
||||||
|
|
||||||
await setLastSyncedPush(lastSyncedPush);
|
await setLastSyncedPush(lastSyncedPush);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function pushEntity(sync, syncContext) {
|
|
||||||
let entity;
|
|
||||||
|
|
||||||
if (sync.entityName === 'notes') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM notes WHERE noteId = ?', [sync.entityId]);
|
|
||||||
|
|
||||||
serializeNoteContentBuffer(entity);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'branches') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM branches WHERE branchId = ?', [sync.entityId]);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'note_revisions') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM note_revisions WHERE noteRevisionId = ?', [sync.entityId]);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'note_reordering') {
|
|
||||||
entity = {
|
|
||||||
parentNoteId: sync.entityId,
|
|
||||||
ordering: await sql.getMap('SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [sync.entityId])
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'options') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM options WHERE name = ?', [sync.entityId]);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'recent_notes') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM recent_notes WHERE branchId = ?', [sync.entityId]);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'images') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM images WHERE imageId = ?', [sync.entityId]);
|
|
||||||
|
|
||||||
if (entity.data !== null) {
|
|
||||||
entity.data = entity.data.toString('base64');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'note_images') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM note_images WHERE noteImageId = ?', [sync.entityId]);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'labels') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM labels WHERE labelId = ?', [sync.entityId]);
|
|
||||||
}
|
|
||||||
else if (sync.entityName === 'api_tokens') {
|
|
||||||
entity = await sql.getRow('SELECT * FROM api_tokens WHERE apiTokenId = ?', [sync.entityId]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Error(`Unrecognized entity type ${sync.entityName} in sync #${sync.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entity) {
|
|
||||||
log.info(`Sync #${sync.id} entity for ${sync.entityName} ${sync.entityId} doesn't exist. Skipping.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(`Pushing changes in sync #${sync.id} ${sync.entityName} ${sync.entityId}`);
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
sourceId: sourceIdService.getCurrentSourceId(),
|
|
||||||
entity: entity
|
|
||||||
};
|
|
||||||
|
|
||||||
await syncRequest(syncContext, 'PUT', '/api/sync/' + sync.entityName, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeNoteContentBuffer(note) {
|
|
||||||
if (note.type === 'file') {
|
|
||||||
note.content = note.content.toString("binary");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
||||||
|
|
||||||
if (await getLastSyncedPull() < resp.max_sync_id) {
|
if (await getLastSyncedPull() < resp.maxSyncId) {
|
||||||
log.info("There are some outstanding pulls, skipping content check.");
|
log.info("There are some outstanding pulls, skipping content check.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastSyncedPush = await getLastSyncedPush();
|
const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [await getLastSyncedPush()]);
|
||||||
const notPushedSyncs = await sql.getValue("SELECT COUNT(*) FROM sync WHERE id > ?", [lastSyncedPush]);
|
|
||||||
|
|
||||||
if (notPushedSyncs > 0) {
|
if (notPushedSyncs > 0) {
|
||||||
log.info("There's " + notPushedSyncs + " outstanding pushes, skipping content check.");
|
log.info(`There's ${notPushedSyncs} outstanding pushes, skipping content check.`);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashes = await contentHashService.getHashes();
|
await contentHashService.checkContentHashes(resp.hashes);
|
||||||
let allChecksPassed = true;
|
|
||||||
|
|
||||||
for (const key in hashes) {
|
|
||||||
if (hashes[key] !== resp.hashes[key]) {
|
|
||||||
allChecksPassed = false;
|
|
||||||
|
|
||||||
await eventLogService.addEvent(`Content hash check for ${key} FAILED. Local is ${hashes[key]}, remote is ${resp.hashes[key]}`);
|
|
||||||
|
|
||||||
if (key !== 'recent_notes') {
|
|
||||||
// let's not get alarmed about recent notes which get updated often and can cause failures in race conditions
|
|
||||||
await messagingService.sendMessageToAllClients({type: 'sync-hash-check-failed'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allChecksPassed) {
|
|
||||||
log.info("Content hash checks PASSED");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncRequest(syncContext, method, uri, body) {
|
async function syncRequest(syncContext, method, uri, body) {
|
||||||
@@ -331,6 +203,80 @@ async function syncRequest(syncContext, method, uri, body) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const primaryKeys = {
|
||||||
|
"notes": "noteId",
|
||||||
|
"branches": "branchId",
|
||||||
|
"note_revisions": "noteRevisionId",
|
||||||
|
"option": "name",
|
||||||
|
"recent_notes": "branchId",
|
||||||
|
"images": "imageId",
|
||||||
|
"note_images": "noteImageId",
|
||||||
|
"labels": "labelId",
|
||||||
|
"api_tokens": "apiTokenId"
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getEntityRow(entityName, entityId) {
|
||||||
|
if (entityName === 'note_reordering') {
|
||||||
|
return await sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [entityId]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const primaryKey = primaryKeys[entityName];
|
||||||
|
|
||||||
|
if (!primaryKey) {
|
||||||
|
throw new Error("Unknown entity " + entityName);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entity = await sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]);
|
||||||
|
|
||||||
|
if (entityName === 'notes' && entity.type === 'file') {
|
||||||
|
entity.content = entity.content.toString("binary");
|
||||||
|
}
|
||||||
|
else if (entityName === 'images') {
|
||||||
|
entity.data = entity.data.toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSyncRecords(syncs) {
|
||||||
|
const records = [];
|
||||||
|
let length = 0;
|
||||||
|
|
||||||
|
for (const sync of syncs) {
|
||||||
|
const record = {
|
||||||
|
sync: sync,
|
||||||
|
entity: await getEntityRow(sync.entityName, sync.entityId)
|
||||||
|
};
|
||||||
|
|
||||||
|
records.push(record);
|
||||||
|
|
||||||
|
length += JSON.stringify(record).length;
|
||||||
|
|
||||||
|
if (length > 1000000) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLastSyncedPull() {
|
||||||
|
return parseInt(await optionService.getOption('lastSyncedPull'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setLastSyncedPull(syncId) {
|
||||||
|
await optionService.setOption('lastSyncedPull', syncId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLastSyncedPush() {
|
||||||
|
return parseInt(await optionService.getOption('lastSyncedPush'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setLastSyncedPush(lastSyncedPush) {
|
||||||
|
await optionService.setOption('lastSyncedPush', lastSyncedPush);
|
||||||
|
}
|
||||||
|
|
||||||
sqlInit.dbReady.then(() => {
|
sqlInit.dbReady.then(() => {
|
||||||
if (syncSetup.isSyncSetup) {
|
if (syncSetup.isSyncSetup) {
|
||||||
log.info("Setting up sync to " + syncSetup.SYNC_SERVER + " with timeout " + syncSetup.SYNC_TIMEOUT);
|
log.info("Setting up sync to " + syncSetup.SYNC_SERVER + " with timeout " + syncSetup.SYNC_TIMEOUT);
|
||||||
@@ -357,5 +303,5 @@ sqlInit.dbReady.then(() => {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sync,
|
sync,
|
||||||
serializeNoteContentBuffer
|
getSyncRecords
|
||||||
};
|
};
|
||||||
@@ -91,6 +91,8 @@ async function fillSyncRows(entityName, entityKey) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fillAllSyncRows() {
|
async function fillAllSyncRows() {
|
||||||
|
await sql.execute("DELETE FROM sync");
|
||||||
|
|
||||||
await fillSyncRows("notes", "noteId");
|
await fillSyncRows("notes", "noteId");
|
||||||
await fillSyncRows("branches", "branchId");
|
await fillSyncRows("branches", "branchId");
|
||||||
await fillSyncRows("note_revisions", "noteRevisionId");
|
await fillSyncRows("note_revisions", "noteRevisionId");
|
||||||
|
|||||||
@@ -3,6 +3,42 @@ const log = require('./log');
|
|||||||
const eventLogService = require('./event_log');
|
const eventLogService = require('./event_log');
|
||||||
const syncTableService = require('./sync_table');
|
const syncTableService = require('./sync_table');
|
||||||
|
|
||||||
|
async function updateEntity(entityName, entity, sourceId) {
|
||||||
|
if (entityName === 'notes') {
|
||||||
|
await updateNote(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'branches') {
|
||||||
|
await updateBranch(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'note_revisions') {
|
||||||
|
await updateNoteRevision(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'note_reordering') {
|
||||||
|
await updateNoteReordering(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'options') {
|
||||||
|
await updateOptions(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'recent_notes') {
|
||||||
|
await updateRecentNotes(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'images') {
|
||||||
|
await updateImage(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'note_images') {
|
||||||
|
await updateNoteImage(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'labels') {
|
||||||
|
await updateLabel(entity, sourceId);
|
||||||
|
}
|
||||||
|
else if (entityName === 'api_tokens') {
|
||||||
|
await updateApiToken(entity, sourceId);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`Unrecognized entity type ${entityName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function deserializeNoteContentBuffer(note) {
|
function deserializeNoteContentBuffer(note) {
|
||||||
if (note.type === 'file') {
|
if (note.type === 'file') {
|
||||||
note.content = new Buffer(note.content, 'binary');
|
note.content = new Buffer(note.content, 'binary');
|
||||||
@@ -15,7 +51,7 @@ async function updateNote(entity, sourceId) {
|
|||||||
const origNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [entity.noteId]);
|
const origNote = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [entity.noteId]);
|
||||||
|
|
||||||
if (!origNote || origNote.dateModified <= entity.dateModified) {
|
if (!origNote || origNote.dateModified <= entity.dateModified) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.replace("notes", entity);
|
await sql.replace("notes", entity);
|
||||||
|
|
||||||
await syncTableService.addNoteSync(entity.noteId, sourceId);
|
await syncTableService.addNoteSync(entity.noteId, sourceId);
|
||||||
@@ -29,7 +65,7 @@ async function updateNote(entity, sourceId) {
|
|||||||
async function updateBranch(entity, sourceId) {
|
async function updateBranch(entity, sourceId) {
|
||||||
const orig = await sql.getRowOrNull("SELECT * FROM branches WHERE branchId = ?", [entity.branchId]);
|
const orig = await sql.getRowOrNull("SELECT * FROM branches WHERE branchId = ?", [entity.branchId]);
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
if (orig === null || orig.dateModified < entity.dateModified) {
|
if (orig === null || orig.dateModified < entity.dateModified) {
|
||||||
delete entity.isExpanded;
|
delete entity.isExpanded;
|
||||||
|
|
||||||
@@ -45,7 +81,7 @@ async function updateBranch(entity, sourceId) {
|
|||||||
async function updateNoteRevision(entity, sourceId) {
|
async function updateNoteRevision(entity, sourceId) {
|
||||||
const orig = await sql.getRowOrNull("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [entity.noteRevisionId]);
|
const orig = await sql.getRowOrNull("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [entity.noteRevisionId]);
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
// we update note revision even if date modified to is the same because the only thing which might have changed
|
// we update note revision even if date modified to is the same because the only thing which might have changed
|
||||||
// is the protected status (and correnspondingly title and content) which doesn't affect the dateModifiedTo
|
// is the protected status (and correnspondingly title and content) which doesn't affect the dateModifiedTo
|
||||||
if (orig === null || orig.dateModifiedTo <= entity.dateModifiedTo) {
|
if (orig === null || orig.dateModifiedTo <= entity.dateModifiedTo) {
|
||||||
@@ -59,7 +95,7 @@ async function updateNoteRevision(entity, sourceId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateNoteReordering(entity, sourceId) {
|
async function updateNoteReordering(entity, sourceId) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
Object.keys(entity.ordering).forEach(async key => {
|
Object.keys(entity.ordering).forEach(async key => {
|
||||||
await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity.ordering[key], key]);
|
await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity.ordering[key], key]);
|
||||||
});
|
});
|
||||||
@@ -75,7 +111,7 @@ async function updateOptions(entity, sourceId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
if (orig === null || orig.dateModified < entity.dateModified) {
|
if (orig === null || orig.dateModified < entity.dateModified) {
|
||||||
await sql.replace('options', entity);
|
await sql.replace('options', entity);
|
||||||
|
|
||||||
@@ -90,7 +126,7 @@ async function updateRecentNotes(entity, sourceId) {
|
|||||||
const orig = await sql.getRowOrNull("SELECT * FROM recent_notes WHERE branchId = ?", [entity.branchId]);
|
const orig = await sql.getRowOrNull("SELECT * FROM recent_notes WHERE branchId = ?", [entity.branchId]);
|
||||||
|
|
||||||
if (orig === null || orig.dateAccessed < entity.dateAccessed) {
|
if (orig === null || orig.dateAccessed < entity.dateAccessed) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.replace('recent_notes', entity);
|
await sql.replace('recent_notes', entity);
|
||||||
|
|
||||||
await syncTableService.addRecentNoteSync(entity.branchId, sourceId);
|
await syncTableService.addRecentNoteSync(entity.branchId, sourceId);
|
||||||
@@ -106,7 +142,7 @@ async function updateImage(entity, sourceId) {
|
|||||||
const origImage = await sql.getRow("SELECT * FROM images WHERE imageId = ?", [entity.imageId]);
|
const origImage = await sql.getRow("SELECT * FROM images WHERE imageId = ?", [entity.imageId]);
|
||||||
|
|
||||||
if (!origImage || origImage.dateModified <= entity.dateModified) {
|
if (!origImage || origImage.dateModified <= entity.dateModified) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.replace("images", entity);
|
await sql.replace("images", entity);
|
||||||
|
|
||||||
await syncTableService.addImageSync(entity.imageId, sourceId);
|
await syncTableService.addImageSync(entity.imageId, sourceId);
|
||||||
@@ -120,7 +156,7 @@ async function updateNoteImage(entity, sourceId) {
|
|||||||
const origNoteImage = await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [entity.noteImageId]);
|
const origNoteImage = await sql.getRow("SELECT * FROM note_images WHERE noteImageId = ?", [entity.noteImageId]);
|
||||||
|
|
||||||
if (!origNoteImage || origNoteImage.dateModified <= entity.dateModified) {
|
if (!origNoteImage || origNoteImage.dateModified <= entity.dateModified) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.replace("note_images", entity);
|
await sql.replace("note_images", entity);
|
||||||
|
|
||||||
await syncTableService.addNoteImageSync(entity.noteImageId, sourceId);
|
await syncTableService.addNoteImageSync(entity.noteImageId, sourceId);
|
||||||
@@ -134,7 +170,7 @@ async function updateLabel(entity, sourceId) {
|
|||||||
const origLabel = await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [entity.labelId]);
|
const origLabel = await sql.getRow("SELECT * FROM labels WHERE labelId = ?", [entity.labelId]);
|
||||||
|
|
||||||
if (!origLabel || origLabel.dateModified <= entity.dateModified) {
|
if (!origLabel || origLabel.dateModified <= entity.dateModified) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.replace("labels", entity);
|
await sql.replace("labels", entity);
|
||||||
|
|
||||||
await syncTableService.addLabelSync(entity.labelId, sourceId);
|
await syncTableService.addLabelSync(entity.labelId, sourceId);
|
||||||
@@ -148,7 +184,7 @@ async function updateApiToken(entity, sourceId) {
|
|||||||
const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]);
|
const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]);
|
||||||
|
|
||||||
if (!apiTokenId) {
|
if (!apiTokenId) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
await sql.replace("api_tokens", entity);
|
await sql.replace("api_tokens", entity);
|
||||||
|
|
||||||
await syncTableService.addApiTokenSync(entity.apiTokenId, sourceId);
|
await syncTableService.addApiTokenSync(entity.apiTokenId, sourceId);
|
||||||
@@ -159,14 +195,5 @@ async function updateApiToken(entity, sourceId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
updateNote,
|
updateEntity
|
||||||
updateBranch,
|
|
||||||
updateNoteRevision,
|
|
||||||
updateNoteReordering,
|
|
||||||
updateOptions,
|
|
||||||
updateRecentNotes,
|
|
||||||
updateImage,
|
|
||||||
updateNoteImage,
|
|
||||||
updateLabel,
|
|
||||||
updateApiToken
|
|
||||||
};
|
};
|
||||||
@@ -77,7 +77,7 @@ async function loadSubTreeNoteIds(parentNoteId, subTreeNoteIds) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sortNotesAlphabetically(parentNoteId) {
|
async function sortNotesAlphabetically(parentNoteId) {
|
||||||
await sql.doInTransaction(async () => {
|
await sql.transactional(async () => {
|
||||||
const notes = await sql.getRows(`SELECT branchId, noteId, title, isProtected
|
const notes = await sql.getRows(`SELECT branchId, noteId, title, isProtected
|
||||||
FROM notes JOIN branches USING(noteId)
|
FROM notes JOIN branches USING(noteId)
|
||||||
WHERE branches.isDeleted = 0 AND parentNoteId = ?`, [parentNoteId]);
|
WHERE branches.isDeleted = 0 AND parentNoteId = ?`, [parentNoteId]);
|
||||||
|
|||||||
@@ -132,76 +132,81 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="position: relative; overflow: auto; grid-area: note-content; padding-left: 10px; padding-top: 10px;" id="note-detail-wrapper">
|
<div style="position: relative; overflow: hidden; grid-area: note-detail; padding-left: 10px; padding-top: 10px; display: flex; flex-direction: column;" id="note-detail-wrapper">
|
||||||
<div id="note-detail-text" class="note-detail-component"></div>
|
<div style="flex-grow: 1; position: relative; overflow: auto; flex-basis: content;">
|
||||||
|
<div id="note-detail-text" style="height: 100%;" class="note-detail-component"></div>
|
||||||
|
|
||||||
<div id="note-detail-search" class="note-detail-component">
|
<div id="note-detail-search" class="note-detail-component">
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<strong>Search string: </strong>
|
<strong>Search string: </strong>
|
||||||
<textarea rows="4" cols="50" id="search-string"></textarea>
|
<textarea rows="4" cols="50" id="search-string"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<h4>Help</h4>
|
||||||
|
<p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>@abc</code> - matches notes with label abc</li>
|
||||||
|
<li>
|
||||||
|
<code>@!abc</code> - matches notes without abc label (maybe not the best syntax)</li>
|
||||||
|
<li>
|
||||||
|
<code>@abc=true</code> - matches notes with label abc having value true</li>
|
||||||
|
<li><code>@abc!=true</code></li>
|
||||||
|
<li>
|
||||||
|
<code>@"weird label"="weird value"</code> - works also with whitespace inside names values</li>
|
||||||
|
<li>
|
||||||
|
<code>@abc and @def</code> - matches notes with both abc and def</li>
|
||||||
|
<li>
|
||||||
|
<code>@abc @def</code> - AND relation is implicit when specifying multiple labels</li>
|
||||||
|
<li>
|
||||||
|
<code>@abc or @def</code> - OR relation</li>
|
||||||
|
<li>
|
||||||
|
<code>@abc<=5</code> - numerical comparison (also >, >=, <).</li>
|
||||||
|
<li>
|
||||||
|
<code>some search string @abc @def</code> - combination of fulltext and label search - both of them need to match (OR not supported)</li>
|
||||||
|
<li>
|
||||||
|
<code>@abc @def some search string</code> - same combination</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<a href="https://github.com/zadam/trilium/wiki/Labels">Complete help on search syntax</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
<div id="note-detail-code" class="note-detail-component"></div>
|
||||||
|
|
||||||
<h4>Help</h4>
|
<div id="note-detail-render" class="note-detail-component"></div>
|
||||||
<p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<code>@abc</code> - matches notes with label abc</li>
|
|
||||||
<li>
|
|
||||||
<code>@!abc</code> - matches notes without abc label (maybe not the best syntax)</li>
|
|
||||||
<li>
|
|
||||||
<code>@abc=true</code> - matches notes with label abc having value true</li>
|
|
||||||
<li><code>@abc!=true</code></li>
|
|
||||||
<li>
|
|
||||||
<code>@"weird label"="weird value"</code> - works also with whitespace inside names values</li>
|
|
||||||
<li>
|
|
||||||
<code>@abc and @def</code> - matches notes with both abc and def</li>
|
|
||||||
<li>
|
|
||||||
<code>@abc @def</code> - AND relation is implicit when specifying multiple labels</li>
|
|
||||||
<li>
|
|
||||||
<code>@abc or @def</code> - OR relation</li>
|
|
||||||
<li>
|
|
||||||
<code>@abc<=5</code> - numerical comparison (also >, >=, <).</li>
|
|
||||||
<li>
|
|
||||||
<code>some search string @abc @def</code> - combination of fulltext and label search - both of them need to match (OR not supported)</li>
|
|
||||||
<li>
|
|
||||||
<code>@abc @def some search string</code> - same combination</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<a href="https://github.com/zadam/trilium/wiki/Labels">Complete help on search syntax</a>
|
<div id="note-detail-file" class="note-detail-component">
|
||||||
</p>
|
<table id="file-table">
|
||||||
|
<tr>
|
||||||
|
<th>File name:</th>
|
||||||
|
<td id="file-filename"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>File type:</th>
|
||||||
|
<td id="file-filetype"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>File size:</th>
|
||||||
|
<td id="file-filesize"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<button id="file-download" class="btn btn-primary" type="button">Download</button>
|
||||||
|
|
||||||
|
<button id="file-open" class="btn btn-primary" type="button">Open</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="file" id="file-upload" style="display: none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="note-detail-code" class="note-detail-component"></div>
|
<div id="children-overview" style="flex-grow: 1000; flex-shrink: 1000; flex-basis: 1px; height: 100px; overflow: hidden; display: flex; flex-wrap: wrap">
|
||||||
|
|
||||||
<div id="note-detail-render" class="note-detail-component"></div>
|
|
||||||
|
|
||||||
<div id="note-detail-file" class="note-detail-component">
|
|
||||||
<table id="file-table">
|
|
||||||
<tr>
|
|
||||||
<th>File name:</th>
|
|
||||||
<td id="file-filename"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>File type:</th>
|
|
||||||
<td id="file-filetype"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>File size:</th>
|
|
||||||
<td id="file-filesize"></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<button id="file-download" class="btn btn-primary" type="button">Download</button>
|
|
||||||
|
|
||||||
<button id="file-open" class="btn btn-primary" type="button">Open</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="file" id="file-upload" style="display: none" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="label-list">
|
<div id="label-list">
|
||||||
|
|||||||
4
src/www
4
src/www
@@ -18,8 +18,6 @@ const log = require('./services/log');
|
|||||||
const appInfo = require('./services/app_info');
|
const appInfo = require('./services/app_info');
|
||||||
const messagingService = require('./services/messaging');
|
const messagingService = require('./services/messaging');
|
||||||
const utils = require('./services/utils');
|
const utils = require('./services/utils');
|
||||||
const sql = require('./services/sql');
|
|
||||||
const sqlInit = require('./services/sql_init');
|
|
||||||
|
|
||||||
const port = normalizePort(config['Network']['port'] || '3000');
|
const port = normalizePort(config['Network']['port'] || '3000');
|
||||||
app.set('port', port);
|
app.set('port', port);
|
||||||
@@ -56,7 +54,7 @@ httpServer.listen(port);
|
|||||||
httpServer.on('error', onError);
|
httpServer.on('error', onError);
|
||||||
httpServer.on('listening', onListening);
|
httpServer.on('listening', onListening);
|
||||||
|
|
||||||
sqlInit.dbReady.then(() => messagingService.init(httpServer, sessionParser));
|
messagingService.init(httpServer, sessionParser);
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
const electronRouting = require('./routes/electron');
|
const electronRouting = require('./routes/electron');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|||||||
Reference in New Issue
Block a user