mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	Compare commits
	
		
			24 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					21e77c83fc | ||
| 
						 | 
					b0310e34e2 | ||
| 
						 | 
					761c51069a | ||
| 
						 | 
					4dc285d84f | ||
| 
						 | 
					a1402c7c66 | ||
| 
						 | 
					6ba3e5ab7f | ||
| 
						 | 
					f740e52ebf | ||
| 
						 | 
					e9454e4db7 | ||
| 
						 | 
					bfc7570e14 | ||
| 
						 | 
					5de92171a7 | ||
| 
						 | 
					29c5e394ab | ||
| 
						 | 
					07b3d11fe5 | ||
| 
						 | 
					67663fba50 | ||
| 
						 | 
					995ebbf577 | ||
| 
						 | 
					d0e6be3e0c | ||
| 
						 | 
					01370a5968 | ||
| 
						 | 
					5b30291601 | ||
| 
						 | 
					5193f073e9 | ||
| 
						 | 
					6c7d8a9667 | ||
| 
						 | 
					5e9bedd903 | ||
| 
						 | 
					e712990c03 | ||
| 
						 | 
					91487b338a | ||
| 
						 | 
					3ff24d53e5 | ||
| 
						 | 
					94c904fb40 | 
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<dataSource name="document.db">
 | 
			
		||||
  <database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.16">
 | 
			
		||||
  <database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17">
 | 
			
		||||
    <root id="1">
 | 
			
		||||
      <ServerVersion>3.25.1</ServerVersion>
 | 
			
		||||
    </root>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "trilium",
 | 
			
		||||
  "version": "0.37.4",
 | 
			
		||||
  "version": "0.37.7",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  "name": "trilium",
 | 
			
		||||
  "productName": "Trilium Notes",
 | 
			
		||||
  "description": "Trilium Notes",
 | 
			
		||||
  "version": "0.37.5",
 | 
			
		||||
  "version": "0.37.8",
 | 
			
		||||
  "license": "AGPL-3.0-only",
 | 
			
		||||
  "main": "electron.js",
 | 
			
		||||
  "bin": {
 | 
			
		||||
 
 | 
			
		||||
@@ -334,6 +334,11 @@ class Note extends Entity {
 | 
			
		||||
        // we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter.
 | 
			
		||||
 | 
			
		||||
        const filteredAttributes = attributes.filter((attr, index) => {
 | 
			
		||||
            // if this exact attribute already appears then don't include it (can happen via cloning)
 | 
			
		||||
            if (attributes.findIndex(it => it.attributeId === attr.attributeId) !== index) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (attr.isDefinition()) {
 | 
			
		||||
                const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name);
 | 
			
		||||
 | 
			
		||||
@@ -788,6 +793,7 @@ class Note extends Entity {
 | 
			
		||||
        delete pojo.isContentAvailable;
 | 
			
		||||
        delete pojo.__attributeCache;
 | 
			
		||||
        delete pojo.content;
 | 
			
		||||
        /** zero references to contentHash, probably can be removed */
 | 
			
		||||
        delete pojo.contentHash;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
for (const appCssNoteId of window.appCssNoteIds) {
 | 
			
		||||
    cssLoader.requireCss(`/api/notes/download/${appCssNoteId}`);
 | 
			
		||||
    cssLoader.requireCss(`api/notes/download/${appCssNoteId}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/";
 | 
			
		||||
 
 | 
			
		||||
@@ -131,7 +131,7 @@ export default class ApperanceOptions {
 | 
			
		||||
            if (noteId) {
 | 
			
		||||
                // make sure the CSS is loaded
 | 
			
		||||
                // if the CSS has been loaded and then updated then the changes won't take effect though
 | 
			
		||||
                cssLoader.requireCss(`/api/notes/download/${noteId}`);
 | 
			
		||||
                cssLoader.requireCss(`api/notes/download/${noteId}`);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.$body.addClass("theme-" + newTheme);
 | 
			
		||||
 
 | 
			
		||||
@@ -93,7 +93,7 @@ export default class SidebarOptions {
 | 
			
		||||
        this.$sidebarMinWidth.val(options.sidebarMinWidth);
 | 
			
		||||
        this.$sidebarWidthPercent.val(options.sidebarWidthPercent);
 | 
			
		||||
 | 
			
		||||
        if (parseInt(options.showSidebarInNewTab)) {
 | 
			
		||||
        if (options.showSidebarInNewTab === 'true') {
 | 
			
		||||
            this.$showSidebarInNewTab.attr("checked", "checked");
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
 
 | 
			
		||||
@@ -273,7 +273,9 @@ async function filterTabs(noteId) {
 | 
			
		||||
 | 
			
		||||
async function noteDeleted(noteId) {
 | 
			
		||||
    for (const tc of tabContexts) {
 | 
			
		||||
        if (tc.notePath && tc.notePath.split("/").includes(noteId)) {
 | 
			
		||||
        // not removing active even if it contains deleted note since that one will move to another note (handled by deletion logic)
 | 
			
		||||
        // and we would lose tab context state (e.g. sidebar visibility)
 | 
			
		||||
        if (!tc.isActive() && tc.notePath && tc.notePath.split("/").includes(noteId)) {
 | 
			
		||||
            await tabRow.removeTab(tc.$tab[0]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -246,11 +246,15 @@ class TabContext {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setCurrentNotePathToHash() {
 | 
			
		||||
        if (this.$tab[0] === this.tabRow.activeTabEl) {
 | 
			
		||||
        if (this.isActive()) {
 | 
			
		||||
            document.location.hash = (this.notePath || "") + "-" + this.tabId;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isActive() {
 | 
			
		||||
        return this.$tab[0] === this.tabRow.activeTabEl;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupClasses() {
 | 
			
		||||
        for (const clazz of Array.from(this.$tab[0].classList)) { // create copy to safely iterate over while removing classes
 | 
			
		||||
            if (clazz !== 'note-tab') {
 | 
			
		||||
 
 | 
			
		||||
@@ -42,7 +42,7 @@ class TreeContextMenu {
 | 
			
		||||
                || (selNodes.length === 1 && selNodes[0] === this.node);
 | 
			
		||||
 | 
			
		||||
        const notSearch = note.type !== 'search';
 | 
			
		||||
        const parentNotSearch = parentNote.type !== 'search';
 | 
			
		||||
        const parentNotSearch = !parentNote || parentNote.type !== 'search';
 | 
			
		||||
        const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
@@ -79,7 +79,7 @@ class TreeContextMenu {
 | 
			
		||||
            { title: "Paste after", cmd: "pasteAfter", uiIcon: "paste",
 | 
			
		||||
                enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
 | 
			
		||||
            { title: "Duplicate note here", cmd: "duplicateNote", uiIcon: "empty",
 | 
			
		||||
                enabled: noSelectedNotes && parentNotSearch && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) },
 | 
			
		||||
                enabled: noSelectedNotes && parentNotSearch && isNotRoot && !isHoisted && (!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) },
 | 
			
		||||
            { title: "----" },
 | 
			
		||||
            { title: "Export", cmd: "export", uiIcon: "empty",
 | 
			
		||||
                enabled: notSearch && noSelectedNotes },
 | 
			
		||||
 
 | 
			
		||||
@@ -89,6 +89,11 @@ body {
 | 
			
		||||
    font-size: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#context-menu-container {
 | 
			
		||||
    max-height: 100vh;
 | 
			
		||||
    overflow: auto; /* make it scrollable when exceeding total height of the window */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#context-menu-container, #context-menu-container .dropdown-menu {
 | 
			
		||||
    padding: 3px 0 0;
 | 
			
		||||
    z-index: 1111;
 | 
			
		||||
 
 | 
			
		||||
@@ -411,6 +411,10 @@ div.ui-tooltip {
 | 
			
		||||
    height: 150px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#sql-console-query .CodeMirror-scroll {
 | 
			
		||||
    min-height: inherit !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn {
 | 
			
		||||
    border-radius: var(--button-border-radius);
 | 
			
		||||
}
 | 
			
		||||
@@ -460,9 +464,10 @@ button.icon-button {
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    /* setting the display to block since "table" doesn't support scrolling */
 | 
			
		||||
    display: block;
 | 
			
		||||
    flex-basis: content;
 | 
			
		||||
    flex-shrink: 1;
 | 
			
		||||
    /** flex-basis: content; - use once "content" is implemented by chrome */
 | 
			
		||||
    flex-shrink: 0;
 | 
			
		||||
    flex-grow: 0;
 | 
			
		||||
    max-height: 30%;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,10 @@ async function anonymize() {
 | 
			
		||||
 | 
			
		||||
    await db.run("UPDATE notes SET title = 'title'");
 | 
			
		||||
    await db.run("UPDATE note_contents SET content = 'text'");
 | 
			
		||||
    await db.run("UPDATE note_revisions SET title = 'title', content = 'text'");
 | 
			
		||||
    await db.run("UPDATE note_revisions SET title = 'title'");
 | 
			
		||||
    await db.run("UPDATE note_revision_contents SET content = 'title'");
 | 
			
		||||
    await db.run("UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label'");
 | 
			
		||||
    await db.run("UPDATE attributes SET name = 'name' WHERE type = 'relation'");
 | 
			
		||||
    await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
 | 
			
		||||
    await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN 
 | 
			
		||||
                    ('documentSecret', 'encryptedDataKey', 'passwordVerificationHash', 
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
module.exports = { buildDate:"2019-11-25T22:46:15+01:00", buildRevision: "bf9ad976b9bf340db3d47db72ddf0857a04de178" };
 | 
			
		||||
module.exports = { buildDate:"2019-12-03T22:31:20+01:00", buildRevision: "b0310e34e2c6f023cc8190310ff63d840157f6cc" };
 | 
			
		||||
 
 | 
			
		||||
@@ -626,12 +626,31 @@ async function runAllChecks() {
 | 
			
		||||
    return !unrecoveredConsistencyErrors;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function showEntityStat(name, query) {
 | 
			
		||||
    const map = await sql.getMap(query);
 | 
			
		||||
 | 
			
		||||
    map[0] = map[0] || 0;
 | 
			
		||||
    map[1] = map[1] || 0;
 | 
			
		||||
 | 
			
		||||
    log.info(`${name} deleted: ${map[1]}, not deleted ${map[0]}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function runDbDiagnostics() {
 | 
			
		||||
    await showEntityStat("Notes", `SELECT isDeleted, count(noteId) FROM notes GROUP BY isDeleted`);
 | 
			
		||||
    await showEntityStat("Note revisions", `SELECT isErased, count(noteRevisionId) FROM note_revisions GROUP BY isErased`);
 | 
			
		||||
    await showEntityStat("Branches", `SELECT isDeleted, count(branchId) FROM branches GROUP BY isDeleted`);
 | 
			
		||||
    await showEntityStat("Attributes", `SELECT isDeleted, count(attributeId) FROM attributes GROUP BY isDeleted`);
 | 
			
		||||
    await showEntityStat("API tokens", `SELECT isDeleted, count(apiTokenId) FROM api_tokens GROUP BY isDeleted`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function runChecks() {
 | 
			
		||||
    let elapsedTimeMs;
 | 
			
		||||
 | 
			
		||||
    await syncMutexService.doExclusively(async () => {
 | 
			
		||||
        const startTime = new Date();
 | 
			
		||||
 | 
			
		||||
        await runDbDiagnostics();
 | 
			
		||||
 | 
			
		||||
        await runAllChecks();
 | 
			
		||||
 | 
			
		||||
        elapsedTimeMs = Date.now() - startTime.getTime();
 | 
			
		||||
@@ -663,7 +682,7 @@ sqlInit.dbReady.then(() => {
 | 
			
		||||
    setInterval(cls.wrap(runChecks), 60 * 60 * 1000);
 | 
			
		||||
 | 
			
		||||
    // kickoff checks soon after startup (to not block the initial load)
 | 
			
		||||
    setTimeout(cls.wrap(runChecks), 10 * 1000);
 | 
			
		||||
    setTimeout(cls.wrap(runChecks), 20 * 1000);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = {};
 | 
			
		||||
@@ -81,7 +81,7 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, async ({ parentNote, chi
 | 
			
		||||
async function processInverseRelations(entityName, entity, handler) {
 | 
			
		||||
    if (entityName === 'attributes' && entity.type === 'relation') {
 | 
			
		||||
        const note = await entity.getNote();
 | 
			
		||||
        const attributes = (await note.getAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
 | 
			
		||||
        const attributes = (await note.getOwnedAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
 | 
			
		||||
 | 
			
		||||
        for (const attribute of attributes) {
 | 
			
		||||
            const definition = attribute.value;
 | 
			
		||||
 
 | 
			
		||||
@@ -12,9 +12,7 @@ const imageminGifLossy = require('imagemin-giflossy');
 | 
			
		||||
const jimp = require('jimp');
 | 
			
		||||
const imageType = require('image-type');
 | 
			
		||||
const sanitizeFilename = require('sanitize-filename');
 | 
			
		||||
const dateUtils = require('./date_utils');
 | 
			
		||||
const noteRevisionService = require('./note_revisions.js');
 | 
			
		||||
const NoteRevision = require("../entities/note_revision");
 | 
			
		||||
 | 
			
		||||
async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
 | 
			
		||||
    const origImageFormat = imageType(uploadBuffer);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ const fileType = require('file-type');
 | 
			
		||||
const stream = require('stream');
 | 
			
		||||
const log = require("../log");
 | 
			
		||||
const utils = require("../utils");
 | 
			
		||||
const sql = require("../sql");
 | 
			
		||||
const noteService = require("../notes");
 | 
			
		||||
const imageService = require("../image");
 | 
			
		||||
const protectedSessionService = require('../protected_session');
 | 
			
		||||
@@ -11,7 +12,7 @@ const protectedSessionService = require('../protected_session');
 | 
			
		||||
function parseDate(text) {
 | 
			
		||||
    // insert - and : to make it ISO format
 | 
			
		||||
    text = text.substr(0, 4) + "-" + text.substr(4, 2) + "-" + text.substr(6, 2)
 | 
			
		||||
        + "T" + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + "Z";
 | 
			
		||||
        + " " + text.substr(9, 2) + ":" + text.substr(11, 2) + ":" + text.substr(13, 2) + ".000Z";
 | 
			
		||||
 | 
			
		||||
    return text;
 | 
			
		||||
}
 | 
			
		||||
@@ -150,7 +151,7 @@ async function importEnex(taskContext, file, parentNote) {
 | 
			
		||||
            } else if (currentTag === 'created') {
 | 
			
		||||
                note.utcDateCreated = parseDate(text);
 | 
			
		||||
            } else if (currentTag === 'updated') {
 | 
			
		||||
                // updated is currently ignored since utcDateModified is updated automatically with each save
 | 
			
		||||
                note.utcDateModified = parseDate(text);
 | 
			
		||||
            } else if (currentTag === 'tag') {
 | 
			
		||||
                note.attributes.push({
 | 
			
		||||
                    type: 'label',
 | 
			
		||||
@@ -187,9 +188,27 @@ async function importEnex(taskContext, file, parentNote) {
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    async function updateDates(noteId, utcDateCreated, utcDateModified) {
 | 
			
		||||
        // it's difficult to force custom dateCreated and dateModified to Note entity so we do it post-creation with SQL
 | 
			
		||||
        await sql.execute(`
 | 
			
		||||
                UPDATE notes 
 | 
			
		||||
                SET dateCreated = ?, 
 | 
			
		||||
                    utcDateCreated = ?,
 | 
			
		||||
                    dateModified = ?,
 | 
			
		||||
                    utcDateModified = ?
 | 
			
		||||
                WHERE noteId = ?`,
 | 
			
		||||
            [utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, noteId]);
 | 
			
		||||
 | 
			
		||||
        await sql.execute(`
 | 
			
		||||
                UPDATE note_contents
 | 
			
		||||
                SET utcDateModified = ?
 | 
			
		||||
                WHERE noteId = ?`,
 | 
			
		||||
            [utcDateModified, noteId]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async function saveNote() {
 | 
			
		||||
        // make a copy because stream continues with the next async call and note gets overwritten
 | 
			
		||||
        let {title, content, attributes, resources, utcDateCreated} = note;
 | 
			
		||||
        let {title, content, attributes, resources, utcDateCreated, utcDateModified} = note;
 | 
			
		||||
 | 
			
		||||
        content = extractContent(content);
 | 
			
		||||
 | 
			
		||||
@@ -201,6 +220,10 @@ async function importEnex(taskContext, file, parentNote) {
 | 
			
		||||
            isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
 | 
			
		||||
        })).note;
 | 
			
		||||
 | 
			
		||||
        utcDateCreated = utcDateCreated || noteEntity.utcDateCreated;
 | 
			
		||||
        // sometime date modified is not present in ENEX, then use date created
 | 
			
		||||
        utcDateModified = utcDateModified || utcDateCreated;
 | 
			
		||||
 | 
			
		||||
        taskContext.increaseProgressCount();
 | 
			
		||||
 | 
			
		||||
        let noteContent = await noteEntity.getContent();
 | 
			
		||||
@@ -224,6 +247,8 @@ async function importEnex(taskContext, file, parentNote) {
 | 
			
		||||
                    isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
 | 
			
		||||
                })).note;
 | 
			
		||||
 | 
			
		||||
                await updateDates(resourceNote.noteId, utcDateCreated, utcDateModified);
 | 
			
		||||
 | 
			
		||||
                taskContext.increaseProgressCount();
 | 
			
		||||
 | 
			
		||||
                const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
 | 
			
		||||
@@ -235,7 +260,9 @@ async function importEnex(taskContext, file, parentNote) {
 | 
			
		||||
                try {
 | 
			
		||||
                    const originalName = "image." + resource.mime.substr(6);
 | 
			
		||||
 | 
			
		||||
                    const {url} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
 | 
			
		||||
                    const {url, note: imageNote} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
 | 
			
		||||
 | 
			
		||||
                    await updateDates(imageNote.noteId, utcDateCreated, utcDateModified);
 | 
			
		||||
 | 
			
		||||
                    const imageLink = `<img src="${url}">`;
 | 
			
		||||
 | 
			
		||||
@@ -257,6 +284,10 @@ async function importEnex(taskContext, file, parentNote) {
 | 
			
		||||
 | 
			
		||||
        // save updated content with links to files/images
 | 
			
		||||
        await noteEntity.setContent(noteContent);
 | 
			
		||||
 | 
			
		||||
        await noteService.scanForLinks(noteEntity.noteId);
 | 
			
		||||
 | 
			
		||||
        await updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    saxStream.on("closetag", async tag => {
 | 
			
		||||
 
 | 
			
		||||
@@ -258,7 +258,8 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
 | 
			
		||||
            content = content.toString("UTF-8");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ((noteMeta && noteMeta.format === 'markdown') || (!noteMeta && ['text/markdown', 'text/x-markdown'].includes(mime))) {
 | 
			
		||||
        if ((noteMeta && noteMeta.format === 'markdown')
 | 
			
		||||
            || (!noteMeta && taskContext.data.textImportedAsText && ['text/markdown', 'text/x-markdown'].includes(mime))) {
 | 
			
		||||
            const parsed = mdReader.parse(content);
 | 
			
		||||
            content = mdWriter.render(parsed);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -43,9 +43,6 @@ async function migrate() {
 | 
			
		||||
        try {
 | 
			
		||||
            log.info("Attempting migration to version " + mig.dbVersion);
 | 
			
		||||
 | 
			
		||||
            // needs to happen outside of the transaction (otherwise it's a NO-OP)
 | 
			
		||||
            await sql.execute("PRAGMA foreign_keys = OFF");
 | 
			
		||||
 | 
			
		||||
            await sql.transactional(async () => {
 | 
			
		||||
                if (mig.type === 'sql') {
 | 
			
		||||
                    const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8');
 | 
			
		||||
@@ -76,10 +73,6 @@ async function migrate() {
 | 
			
		||||
 | 
			
		||||
            utils.crash();
 | 
			
		||||
        }
 | 
			
		||||
        finally {
 | 
			
		||||
            // make sure foreign keys are enabled even if migration script disables them
 | 
			
		||||
            await sql.execute("PRAGMA foreign_keys = ON");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (await sqlInit.isDbUpToDate()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,6 @@ const dateUtils = require('../services/date_utils');
 | 
			
		||||
 * @param {Note} note
 | 
			
		||||
 */
 | 
			
		||||
async function protectNoteRevisions(note) {
 | 
			
		||||
    if (await note.hasLabel('disableVersioning')) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const revision of await note.getRevisions()) {
 | 
			
		||||
        if (note.isProtected !== revision.isProtected) {
 | 
			
		||||
            const content = await revision.getContent();
 | 
			
		||||
@@ -30,6 +26,10 @@ async function protectNoteRevisions(note) {
 | 
			
		||||
 * @return {NoteRevision}
 | 
			
		||||
 */
 | 
			
		||||
async function createNoteRevision(note) {
 | 
			
		||||
    if (await note.hasLabel("disableVersioning")) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const noteRevision = await new NoteRevision({
 | 
			
		||||
        noteId: note.noteId,
 | 
			
		||||
        // title and text should be decrypted now
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@ async function createNewNote(parentNoteId, noteData) {
 | 
			
		||||
        isExpanded: !!noteData.isExpanded
 | 
			
		||||
    }).save();
 | 
			
		||||
 | 
			
		||||
    for (const attr of await parentNote.getAttributes()) {
 | 
			
		||||
    for (const attr of await parentNote.getOwnedAttributes()) {
 | 
			
		||||
        if (attr.name.startsWith("child:")) {
 | 
			
		||||
            await new Attribute({
 | 
			
		||||
               noteId: note.noteId,
 | 
			
		||||
@@ -308,8 +308,7 @@ async function saveLinks(note, content) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function saveNoteRevision(note) {
 | 
			
		||||
    // files and images are immutable, they can't be updated
 | 
			
		||||
    // but we don't even version titles which is probably not correct
 | 
			
		||||
    // files and images are versioned separately
 | 
			
		||||
    if (note.type === 'file' || note.type === 'image' || await note.hasLabel('disableVersioning')) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
@@ -480,7 +479,7 @@ async function eraseDeletedNotes() {
 | 
			
		||||
        SET content = NULL,
 | 
			
		||||
            utcDateModified = '${utcNowDateTime}'
 | 
			
		||||
        WHERE noteRevisionId IN 
 | 
			
		||||
            (SELECT noteRevisionId FROM note_revisions WHERE isErased = 0 AND noteId IN ((???)))`, noteIdsToErase);
 | 
			
		||||
            (SELECT noteRevisionId FROM note_revisions WHERE isErased = 0 AND noteId IN (???))`, noteIdsToErase);
 | 
			
		||||
 | 
			
		||||
    await sql.executeMany(`
 | 
			
		||||
        UPDATE note_revisions 
 | 
			
		||||
@@ -514,7 +513,7 @@ async function duplicateNote(noteId, parentNoteId) {
 | 
			
		||||
        notePosition: origBranch ? origBranch.notePosition + 1 : null
 | 
			
		||||
    }).save();
 | 
			
		||||
 | 
			
		||||
    for (const attribute of await origNote.getAttributes()) {
 | 
			
		||||
    for (const attribute of await origNote.getOwnedAttributes()) {
 | 
			
		||||
        const attr = new Attribute(attribute);
 | 
			
		||||
        attr.attributeId = undefined; // force creation of new attribute
 | 
			
		||||
        attr.noteId = newNote.noteId;
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ function isProtectedSessionAvailable() {
 | 
			
		||||
function decryptNotes(notes) {
 | 
			
		||||
    for (const note of notes) {
 | 
			
		||||
        if (note.isProtected) {
 | 
			
		||||
            note.title = decrypt(note.title);
 | 
			
		||||
            note.title = decryptString(note.title);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,8 +57,6 @@ async function initDbConnection() {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await sql.execute("PRAGMA foreign_keys = ON");
 | 
			
		||||
 | 
			
		||||
        const currentDbVersion = await getDbVersion();
 | 
			
		||||
 | 
			
		||||
        if (currentDbVersion > appInfo.dbVersion) {
 | 
			
		||||
@@ -175,9 +173,11 @@ async function isDbUpToDate() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function dbInitialized() {
 | 
			
		||||
    if (!await isDbInitialized()) {
 | 
			
		||||
        await optionService.setOption('initialized', 'true');
 | 
			
		||||
 | 
			
		||||
        await initDbConnection();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dbReady.then(async () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,48 +1,32 @@
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const dataDir = require('../services/data_dir');
 | 
			
		||||
 | 
			
		||||
fs.unlinkSync(dataDir.DOCUMENT_PATH);
 | 
			
		||||
/**
 | 
			
		||||
 * Usage: node src/tools/generate_document.js 1000
 | 
			
		||||
 * will create 1000 new notes and some clones into a current document.db
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
require('../entities/entity_constructor');
 | 
			
		||||
const optionService = require('../services/options');
 | 
			
		||||
const sqlInit = require('../services/sql_init');
 | 
			
		||||
const myScryptService = require('../services/my_scrypt');
 | 
			
		||||
const passwordEncryptionService = require('../services/password_encryption');
 | 
			
		||||
const utils = require('../services/utils');
 | 
			
		||||
const noteService = require('../services/notes');
 | 
			
		||||
const attributeService = require('../services/attributes');
 | 
			
		||||
const cls = require('../services/cls');
 | 
			
		||||
const cloningService = require('../services/cloning');
 | 
			
		||||
const loremIpsum = require('lorem-ipsum');
 | 
			
		||||
 | 
			
		||||
async function setUserNamePassword() {
 | 
			
		||||
    const username = "test";
 | 
			
		||||
    const password = "test";
 | 
			
		||||
 | 
			
		||||
    await optionService.setOption('username', username);
 | 
			
		||||
 | 
			
		||||
    await optionService.setOption('passwordVerificationSalt', utils.randomSecureToken(32));
 | 
			
		||||
    await optionService.setOption('passwordDerivedKeySalt', utils.randomSecureToken(32));
 | 
			
		||||
 | 
			
		||||
    const passwordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(password));
 | 
			
		||||
    await optionService.setOption('passwordVerificationHash', passwordVerificationKey);
 | 
			
		||||
 | 
			
		||||
    await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16));
 | 
			
		||||
 | 
			
		||||
    await sqlInit.initDbConnection();
 | 
			
		||||
}
 | 
			
		||||
const loremIpsum = require('lorem-ipsum').loremIpsum;
 | 
			
		||||
 | 
			
		||||
const noteCount = parseInt(process.argv[2]);
 | 
			
		||||
 | 
			
		||||
if (!noteCount) {
 | 
			
		||||
    console.error(`Please enter number of notes as program parameter.`);
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const notes = ['root'];
 | 
			
		||||
 | 
			
		||||
function getRandomParentNoteId() {
 | 
			
		||||
function getRandomNoteId() {
 | 
			
		||||
    const index = Math.floor(Math.random() * notes.length);
 | 
			
		||||
 | 
			
		||||
    return notes[index];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function start() {
 | 
			
		||||
    await setUserNamePassword();
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < noteCount; i++) {
 | 
			
		||||
        const title = loremIpsum({ count: 1, units: 'sentences', sentenceLowerBound: 1, sentenceUpperBound: 10 });
 | 
			
		||||
 | 
			
		||||
@@ -50,17 +34,17 @@ async function start() {
 | 
			
		||||
        const content = loremIpsum({ count: paragraphCount, units: 'paragraphs', sentenceLowerBound: 1, sentenceUpperBound: 15,
 | 
			
		||||
            paragraphLowerBound: 3, paragraphUpperBound: 10, format: 'html' });
 | 
			
		||||
 | 
			
		||||
        const {note} = await noteService.createNote(getRandomParentNoteId(), title, content);
 | 
			
		||||
        const {note} = await noteService.createNote(getRandomNoteId(), title, content);
 | 
			
		||||
 | 
			
		||||
        console.log(`Created note ${i}: ${title}`);
 | 
			
		||||
 | 
			
		||||
        notes.push(note.noteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // we'll create clones for 20% of notes
 | 
			
		||||
    for (let i = 0; i < (noteCount / 50); i++) {
 | 
			
		||||
        const noteIdToClone = getRandomParentNoteId();
 | 
			
		||||
        const parentNoteId = getRandomParentNoteId();
 | 
			
		||||
    // we'll create clones for 4% of notes
 | 
			
		||||
    for (let i = 0; i < (noteCount / 25); i++) {
 | 
			
		||||
        const noteIdToClone = getRandomNoteId();
 | 
			
		||||
        const parentNoteId = getRandomNoteId();
 | 
			
		||||
        const prefix = Math.random() > 0.8 ? "prefix" : null;
 | 
			
		||||
 | 
			
		||||
        const result = await cloningService.cloneNoteToParent(noteIdToClone, parentNoteId, prefix);
 | 
			
		||||
@@ -68,6 +52,30 @@ async function start() {
 | 
			
		||||
        console.log(`Cloning ${i}:`, result.success ? "succeeded" : "FAILED");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < noteCount; i++) {
 | 
			
		||||
        await attributeService.createAttribute({
 | 
			
		||||
            noteId: getRandomNoteId(),
 | 
			
		||||
            type: 'label',
 | 
			
		||||
            name: 'label',
 | 
			
		||||
            value: 'value',
 | 
			
		||||
            isInheritable: Math.random() > 0.1 // 10% are inheritable
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        console.log(`Creating label ${i}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < noteCount; i++) {
 | 
			
		||||
        await attributeService.createAttribute({
 | 
			
		||||
            noteId: getRandomNoteId(),
 | 
			
		||||
            type: 'relation',
 | 
			
		||||
            name: 'relation',
 | 
			
		||||
            value: getRandomNoteId(),
 | 
			
		||||
            isInheritable: Math.random() > 0.1 // 10% are inheritable
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        console.log(`Creating relation ${i}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    process.exit(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <link rel="shortcut icon" href="favicon.ico">
 | 
			
		||||
    <title>Trilium Notes</title>
 | 
			
		||||
</head>
 | 
			
		||||
<body class="desktop theme-<%= theme %>" style="--main-font-size: <%= mainFontSize %>%; --tree-font-size: <%= treeFontSize %>%; --detail-font-size: <%= detailFontSize %>%;">
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user