mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 03:46:37 +01:00 
			
		
		
		
	Compare commits
	
		
			23 Commits
		
	
	
		
			v0.27.0-be
			...
			v0.27.2-be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					6ce401f260 | ||
| 
						 | 
					5d74dcd256 | ||
| 
						 | 
					5a9fc1697b | ||
| 
						 | 
					927415838c | ||
| 
						 | 
					d72fcefdc7 | ||
| 
						 | 
					0be173a8f7 | ||
| 
						 | 
					c3913a8735 | ||
| 
						 | 
					e2dfe1b6de | ||
| 
						 | 
					fec3e47eb8 | ||
| 
						 | 
					d72efd2450 | ||
| 
						 | 
					ef1c840aa7 | ||
| 
						 | 
					1581464d8c | ||
| 
						 | 
					9de29584a4 | ||
| 
						 | 
					9e2e6fb50c | ||
| 
						 | 
					c85979b66b | ||
| 
						 | 
					ecdc5865a6 | ||
| 
						 | 
					1771ddb787 | ||
| 
						 | 
					3ab657fe46 | ||
| 
						 | 
					8785dae753 | ||
| 
						 | 
					2f1c5b29d4 | ||
| 
						 | 
					7135349a10 | ||
| 
						 | 
					66c639d5e3 | ||
| 
						 | 
					6704b755d8 | 
							
								
								
									
										16
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "trilium",
 | 
			
		||||
  "version": "0.26.1",
 | 
			
		||||
  "version": "0.27.1-beta",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
@@ -399,9 +399,9 @@
 | 
			
		||||
      "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
 | 
			
		||||
    },
 | 
			
		||||
    "@types/node": {
 | 
			
		||||
      "version": "8.10.39",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.39.tgz",
 | 
			
		||||
      "integrity": "sha512-rE7fktr02J8ybFf6eysife+WF+L4sAHWzw09DgdCebEu+qDwMvv4zl6Bc+825ttGZP73kCKxa3dhJOoGJ8+5mA==",
 | 
			
		||||
      "version": "10.12.18",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
 | 
			
		||||
      "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "abab": {
 | 
			
		||||
@@ -2375,12 +2375,12 @@
 | 
			
		||||
      "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "electron": {
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/electron/-/electron-4.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-3XPG/3IXlvnT1oe1K6zEushoD0SKbP8xwdrL10EWGe6k2iOV4hSHqJ8vWnR8yZ7VbSXmBRfomEFDNAo/q/cwKw==",
 | 
			
		||||
      "version": "4.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/electron/-/electron-4.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-kBWDLn1Vq8Tm6+/HpQc8gkjX7wJyQI8v/lf2kAirfi0Q4cXh6vBjozFvV1U/9gGCbyKnIDM+m8/wpyJIjg4w7g==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@types/node": "^8.0.24",
 | 
			
		||||
        "@types/node": "^10.12.18",
 | 
			
		||||
        "electron-download": "^4.1.0",
 | 
			
		||||
        "extract-zip": "^1.0.3"
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  "name": "trilium",
 | 
			
		||||
  "productName": "Trilium Notes",
 | 
			
		||||
  "description": "Trilium Notes",
 | 
			
		||||
  "version": "0.27.0-beta",
 | 
			
		||||
  "version": "0.27.2-beta",
 | 
			
		||||
  "license": "AGPL-3.0-only",
 | 
			
		||||
  "main": "electron.js",
 | 
			
		||||
  "bin": {
 | 
			
		||||
@@ -66,7 +66,7 @@
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "devtron": "1.4.0",
 | 
			
		||||
    "electron": "4.0.0",
 | 
			
		||||
    "electron": "4.0.1",
 | 
			
		||||
    "electron-compile": "6.4.3",
 | 
			
		||||
    "electron-packager": "13.0.1",
 | 
			
		||||
    "electron-rebuild": "1.8.2",
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,8 @@ const ENTITY_NAME_TO_ENTITY = {
 | 
			
		||||
    "note_revisions": NoteRevision,
 | 
			
		||||
    "recent_notes": RecentNote,
 | 
			
		||||
    "options": Option,
 | 
			
		||||
    "api_tokens": ApiToken
 | 
			
		||||
    "api_tokens": ApiToken,
 | 
			
		||||
    "links": Link
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function getEntityFromEntityName(entityName) {
 | 
			
		||||
 
 | 
			
		||||
@@ -198,15 +198,17 @@ addTabHandler((async function () {
 | 
			
		||||
    const $syncVersion = $("#sync-version");
 | 
			
		||||
    const $buildDate = $("#build-date");
 | 
			
		||||
    const $buildRevision = $("#build-revision");
 | 
			
		||||
    const $dataDirectory = $("#data-directory");
 | 
			
		||||
 | 
			
		||||
    const appInfo = await server.get('app-info');
 | 
			
		||||
 | 
			
		||||
    $appVersion.html(appInfo.appVersion);
 | 
			
		||||
    $dbVersion.html(appInfo.dbVersion);
 | 
			
		||||
    $syncVersion.html(appInfo.syncVersion);
 | 
			
		||||
    $buildDate.html(appInfo.buildDate);
 | 
			
		||||
    $buildRevision.html(appInfo.buildRevision);
 | 
			
		||||
    $appVersion.text(appInfo.appVersion);
 | 
			
		||||
    $dbVersion.text(appInfo.dbVersion);
 | 
			
		||||
    $syncVersion.text(appInfo.syncVersion);
 | 
			
		||||
    $buildDate.text(appInfo.buildDate);
 | 
			
		||||
    $buildRevision.text(appInfo.buildRevision);
 | 
			
		||||
    $buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
 | 
			
		||||
    $dataDirectory.text(appInfo.dataDirectory);
 | 
			
		||||
 | 
			
		||||
    return {};
 | 
			
		||||
})());
 | 
			
		||||
 
 | 
			
		||||
@@ -85,8 +85,11 @@ async function showAttributes() {
 | 
			
		||||
        $promotedAttributesContainer.empty().append($tbody);
 | 
			
		||||
    }
 | 
			
		||||
    else if (note.type !== 'relation-map') {
 | 
			
		||||
        if (attributes.length > 0) {
 | 
			
		||||
            for (const attribute of attributes) {
 | 
			
		||||
        // display only "own" notes
 | 
			
		||||
        const ownedAttributes = attributes.filter(attr => attr.noteId === note.noteId);
 | 
			
		||||
 | 
			
		||||
        if (ownedAttributes.length > 0) {
 | 
			
		||||
            for (const attribute of ownedAttributes) {
 | 
			
		||||
                if (attribute.type === 'label') {
 | 
			
		||||
                    $attributeListInner.append(utils.formatLabel(attribute) + " ");
 | 
			
		||||
                }
 | 
			
		||||
@@ -132,7 +135,9 @@ async function createPromotedAttributeRow(definitionAttr, valueAttr) {
 | 
			
		||||
    const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input));
 | 
			
		||||
 | 
			
		||||
    const $actionCell = $("<td>");
 | 
			
		||||
    const $multiplicityCell = $("<td>").addClass("multiplicity");
 | 
			
		||||
    const $multiplicityCell = $("<td>")
 | 
			
		||||
        .addClass("multiplicity")
 | 
			
		||||
        .attr("nowrap", true);
 | 
			
		||||
 | 
			
		||||
    $tr
 | 
			
		||||
        .append($labelCell)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import treeCache from './tree_cache.js';
 | 
			
		||||
import noteDetailService from './note_detail.js';
 | 
			
		||||
import noteTypeService from './note_type.js';
 | 
			
		||||
import noteTooltipService from './note_tooltip.js';
 | 
			
		||||
import protectedSessionService from'./protected_session.js';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is the main frontend API interface for scripts. It's published in the local "api" object.
 | 
			
		||||
@@ -42,7 +43,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
 | 
			
		||||
    this.activateNewNote = async notePath => {
 | 
			
		||||
        await treeService.reload();
 | 
			
		||||
 | 
			
		||||
        await treeService.activateNote(notePath, true);
 | 
			
		||||
        await treeService.activateNote(notePath, noteDetailService.focusOnTitle);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -244,7 +245,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
 | 
			
		||||
     * @method
 | 
			
		||||
     * @param {object} $el - jquery object on which to setup the tooltip
 | 
			
		||||
     */
 | 
			
		||||
    this.setupElementTooltip = noteTooltipService.setupElementTooltip
 | 
			
		||||
    this.setupElementTooltip = noteTooltipService.setupElementTooltip;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @method
 | 
			
		||||
     */
 | 
			
		||||
    this.protectCurrentNote = protectedSessionService.protectNoteAndSendToServer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default FrontendScriptApi;
 | 
			
		||||
@@ -37,6 +37,8 @@ let noteChangeDisabled = false;
 | 
			
		||||
 | 
			
		||||
let isNoteChanged = false;
 | 
			
		||||
 | 
			
		||||
let detailLoadedListeners = [];
 | 
			
		||||
 | 
			
		||||
const components = {
 | 
			
		||||
    'code': noteDetailCode,
 | 
			
		||||
    'text': noteDetailText,
 | 
			
		||||
@@ -147,12 +149,6 @@ function setNoteBackgroundIfProtected(note) {
 | 
			
		||||
    $unprotectButton.prop("disabled", !protectedSessionHolder.isProtectedSessionAvailable());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let isNewNoteCreated = false;
 | 
			
		||||
 | 
			
		||||
function newNoteCreated() {
 | 
			
		||||
    isNewNoteCreated = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleProtectedSession() {
 | 
			
		||||
    const newSessionCreated = await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false);
 | 
			
		||||
 | 
			
		||||
@@ -191,12 +187,6 @@ async function loadNoteDetail(noteId) {
 | 
			
		||||
        attributeService.invalidateAttributes();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (isNewNoteCreated) {
 | 
			
		||||
        isNewNoteCreated = false;
 | 
			
		||||
 | 
			
		||||
        $noteTitle.focus().select();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $noteIdDisplay.html(noteId);
 | 
			
		||||
 | 
			
		||||
    setNoteBackgroundIfProtected(currentNote);
 | 
			
		||||
@@ -240,11 +230,13 @@ async function loadNoteDetail(noteId) {
 | 
			
		||||
    // after loading new note make sure editor is scrolled to the top
 | 
			
		||||
    getComponent(currentNote.type).scrollToTop();
 | 
			
		||||
 | 
			
		||||
    if (utils.isDesktop()) {
 | 
			
		||||
    fireDetailLoaded();
 | 
			
		||||
 | 
			
		||||
    $scriptArea.empty();
 | 
			
		||||
 | 
			
		||||
    await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView');
 | 
			
		||||
 | 
			
		||||
    if (utils.isDesktop()) {
 | 
			
		||||
        await attributeService.showAttributes();
 | 
			
		||||
 | 
			
		||||
        await showChildrenOverview();
 | 
			
		||||
@@ -291,6 +283,30 @@ function focusOnTitle() {
 | 
			
		||||
    $noteTitle.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Since detail loading may take some time and user might just browse through the notes using UP-DOWN keys,
 | 
			
		||||
 * we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on
 | 
			
		||||
 * fancytree's activate() won't wait for the full load.
 | 
			
		||||
 *
 | 
			
		||||
 * This causes an issue where in some cases you want to do some action after detail is loaded. For this reason
 | 
			
		||||
 * we provide the listeners here which will be triggered after the detail is loaded and if the loaded note
 | 
			
		||||
 * is the one registered in the listener.
 | 
			
		||||
 */
 | 
			
		||||
function addDetailLoadedListener(noteId, callback) {
 | 
			
		||||
    detailLoadedListeners.push({ noteId, callback });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function fireDetailLoaded() {
 | 
			
		||||
    for (const {noteId, callback} of detailLoadedListeners) {
 | 
			
		||||
        if (noteId === currentNote.noteId) {
 | 
			
		||||
            callback();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // all the listeners are one time only
 | 
			
		||||
    detailLoadedListeners = [];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
messagingService.subscribeToSyncMessages(syncData => {
 | 
			
		||||
    if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) {
 | 
			
		||||
        infoService.showMessage('Reloading note because of background changes');
 | 
			
		||||
@@ -325,11 +341,11 @@ export default {
 | 
			
		||||
    getCurrentNote,
 | 
			
		||||
    getCurrentNoteType,
 | 
			
		||||
    getCurrentNoteId,
 | 
			
		||||
    newNoteCreated,
 | 
			
		||||
    focusOnTitle,
 | 
			
		||||
    saveNote,
 | 
			
		||||
    saveNoteIfChanged,
 | 
			
		||||
    noteChanged,
 | 
			
		||||
    getCurrentNoteContent,
 | 
			
		||||
    onNoteChange
 | 
			
		||||
    onNoteChange,
 | 
			
		||||
    addDetailLoadedListener
 | 
			
		||||
};
 | 
			
		||||
@@ -184,5 +184,6 @@ export default {
 | 
			
		||||
    protectSubtree,
 | 
			
		||||
    ensureDialogIsClosed,
 | 
			
		||||
    enterProtectedSession,
 | 
			
		||||
    leaveProtectedSession
 | 
			
		||||
    leaveProtectedSession,
 | 
			
		||||
    protectNoteAndSendToServer
 | 
			
		||||
};
 | 
			
		||||
@@ -83,6 +83,10 @@ async function setNodeTitleWithPrefix(node) {
 | 
			
		||||
    node.setTitle(utils.escapeHtml(title));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getNode(childNoteId, parentNoteId) {
 | 
			
		||||
    return getNodesByNoteId(childNoteId).find(node => !parentNoteId || node.data.parentNoteId === parentNoteId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function expandToNote(notePath, expandOpts) {
 | 
			
		||||
    utils.assertArguments(notePath);
 | 
			
		||||
 | 
			
		||||
@@ -94,7 +98,18 @@ async function expandToNote(notePath, expandOpts) {
 | 
			
		||||
 | 
			
		||||
    for (const childNoteId of runPath) {
 | 
			
		||||
        // for first node (!parentNoteId) it doesn't matter which node is found
 | 
			
		||||
        const node = getNodesByNoteId(childNoteId).find(node => !parentNoteId || node.data.parentNoteId === parentNoteId);
 | 
			
		||||
        let node = getNode(childNoteId, parentNoteId);
 | 
			
		||||
 | 
			
		||||
        if (!node && parentNoteId) {
 | 
			
		||||
            const parents = getNodesByNoteId(parentNoteId);
 | 
			
		||||
 | 
			
		||||
            for (const parent of parents) {
 | 
			
		||||
                // force load parents. This is useful when fancytree doesn't contain recently created notes yet.
 | 
			
		||||
                await parent.load(true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            node = getNode(childNoteId, parentNoteId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!node) {
 | 
			
		||||
            console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
 | 
			
		||||
@@ -111,7 +126,7 @@ async function expandToNote(notePath, expandOpts) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function activateNote(notePath, newNote) {
 | 
			
		||||
async function activateNote(notePath, noteLoadedListener) {
 | 
			
		||||
    utils.assertArguments(notePath);
 | 
			
		||||
 | 
			
		||||
    const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
 | 
			
		||||
@@ -131,8 +146,8 @@ async function activateNote(notePath, newNote) {
 | 
			
		||||
 | 
			
		||||
    const node = await expandToNote(notePath);
 | 
			
		||||
 | 
			
		||||
    if (newNote) {
 | 
			
		||||
        noteDetailService.newNoteCreated();
 | 
			
		||||
    if (noteLoadedListener) {
 | 
			
		||||
        noteDetailService.addDetailLoadedListener(node.data.noteId, noteLoadedListener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // we use noFocus because when we reload the tree because of background changes
 | 
			
		||||
@@ -411,13 +426,17 @@ function initFancyTree(tree) {
 | 
			
		||||
        clones: {
 | 
			
		||||
            highlightActiveClones: true
 | 
			
		||||
        },
 | 
			
		||||
        renderNode: async function (event, data) {
 | 
			
		||||
        enhanceTitle: async function (event, data) {
 | 
			
		||||
            const node = data.node;
 | 
			
		||||
            const $span = $(node.span);
 | 
			
		||||
 | 
			
		||||
            if (node.data.noteId !== 'root'
 | 
			
		||||
                && node.data.noteId === await hoistedNoteService.getHoistedNoteId()
 | 
			
		||||
                && $span.find('.unhoist-button').length === 0) {
 | 
			
		||||
 | 
			
		||||
            if (node.data.noteId !== 'root' && node.data.noteId === await hoistedNoteService.getHoistedNoteId()) {
 | 
			
		||||
                const unhoistButton = $('<span>  (<a class="unhoist-button">unhoist</a>)</span>');
 | 
			
		||||
 | 
			
		||||
                $(node.span).append(unhoistButton);
 | 
			
		||||
                $span.append(unhoistButton);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
@@ -547,7 +566,7 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
 | 
			
		||||
 | 
			
		||||
    await noteDetailService.saveNoteIfChanged();
 | 
			
		||||
 | 
			
		||||
    noteDetailService.newNoteCreated();
 | 
			
		||||
    noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusOnTitle);
 | 
			
		||||
 | 
			
		||||
    const noteEntity = new NoteShort(treeCache, note);
 | 
			
		||||
    const branchEntity = new Branch(treeCache, branch);
 | 
			
		||||
@@ -634,11 +653,15 @@ messagingService.subscribeToSyncMessages(syncData => {
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
utils.bindShortcut('ctrl+o', () => {
 | 
			
		||||
utils.bindShortcut('ctrl+o', async () => {
 | 
			
		||||
    const node = getCurrentNode();
 | 
			
		||||
    const parentNoteId = node.data.parentNoteId;
 | 
			
		||||
    const isProtected = treeUtils.getParentProtectedStatus(node);
 | 
			
		||||
 | 
			
		||||
    if (node.data.noteId === 'root' || node.data.noteId === await hoistedNoteService.getHoistedNoteId()) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createNote(node, parentNoteId, 'after', isProtected, true);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,6 @@ import syncService from "./sync.js";
 | 
			
		||||
import hoistedNoteService from './hoisted_note.js';
 | 
			
		||||
import ContextMenuItemsContainer from './context_menu_items_container.js';
 | 
			
		||||
 | 
			
		||||
const $tree = $("#tree");
 | 
			
		||||
 | 
			
		||||
let clipboardIds = [];
 | 
			
		||||
let clipboardMode = null;
 | 
			
		||||
 | 
			
		||||
@@ -110,11 +108,12 @@ async function getContextMenuItems(event) {
 | 
			
		||||
    const note = await treeCache.getNote(node.data.noteId);
 | 
			
		||||
    const parentNote = await treeCache.getNote(branch.parentNoteId);
 | 
			
		||||
    const isNotRoot = note.noteId !== 'root';
 | 
			
		||||
    const isHoisted = note.noteId === await hoistedNoteService.getHoistedNoteId();
 | 
			
		||||
 | 
			
		||||
    const itemsContainer = new ContextMenuItemsContainer(contextMenuItems);
 | 
			
		||||
 | 
			
		||||
    // Modify menu entries depending on node status
 | 
			
		||||
    itemsContainer.enableItem("insertNoteAfter", isNotRoot && parentNote.type !== 'search');
 | 
			
		||||
    itemsContainer.enableItem("insertNoteAfter", isNotRoot && !isHoisted && parentNote.type !== 'search');
 | 
			
		||||
    itemsContainer.enableItem("insertChildNote", note.type !== 'search');
 | 
			
		||||
    itemsContainer.enableItem("delete", isNotRoot && parentNote.type !== 'search');
 | 
			
		||||
    itemsContainer.enableItem("copy", isNotRoot);
 | 
			
		||||
@@ -125,10 +124,8 @@ async function getContextMenuItems(event) {
 | 
			
		||||
    itemsContainer.enableItem("export", note.type !== 'search');
 | 
			
		||||
    itemsContainer.enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search');
 | 
			
		||||
 | 
			
		||||
    const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
 | 
			
		||||
 | 
			
		||||
    itemsContainer.hideItem("hoist", note.noteId === hoistedNoteId);
 | 
			
		||||
    itemsContainer.hideItem("unhoist", note.noteId !== hoistedNoteId || !isNotRoot);
 | 
			
		||||
    itemsContainer.hideItem("hoist", isHoisted);
 | 
			
		||||
    itemsContainer.hideItem("unhoist", !isHoisted || !isNotRoot);
 | 
			
		||||
 | 
			
		||||
    // Activate node on right-click
 | 
			
		||||
    node.setActive();
 | 
			
		||||
 
 | 
			
		||||
@@ -25,26 +25,20 @@ function SetupModel() {
 | 
			
		||||
 | 
			
		||||
    this.instanceType = utils.isElectron() ? "desktop" : "server";
 | 
			
		||||
 | 
			
		||||
    this.setupTypeSelected = this.getSetupType = () =>
 | 
			
		||||
        this.setupNewDocument()
 | 
			
		||||
        || this.setupSyncFromDesktop()
 | 
			
		||||
        || this.setupSyncFromServer();
 | 
			
		||||
    this.setupTypeSelected = () => !!this.setupType();
 | 
			
		||||
 | 
			
		||||
    this.selectSetupType = () => {
 | 
			
		||||
        this.step(this.getSetupType());
 | 
			
		||||
        this.setupType(this.getSetupType());
 | 
			
		||||
        this.step(this.setupType());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.back = () => {
 | 
			
		||||
        this.step("setup-type");
 | 
			
		||||
 | 
			
		||||
        this.setupNewDocument(false);
 | 
			
		||||
        this.setupSyncFromServer(false);
 | 
			
		||||
        this.setupSyncFromDesktop(false);
 | 
			
		||||
        this.setupType("");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.finish = async () => {
 | 
			
		||||
        if (this.setupNewDocument()) {
 | 
			
		||||
        if (this.setupType() === 'new-document') {
 | 
			
		||||
            const username = this.username();
 | 
			
		||||
            const password1 = this.password1();
 | 
			
		||||
            const password2 = this.password2();
 | 
			
		||||
@@ -72,7 +66,7 @@ function SetupModel() {
 | 
			
		||||
                window.location.replace("/");
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        else if (this.setupSyncFromServer()) {
 | 
			
		||||
        else if (this.setupType() === 'sync-from-server') {
 | 
			
		||||
            const syncServerHost = this.syncServerHost();
 | 
			
		||||
            const syncProxy = this.syncProxy();
 | 
			
		||||
            const username = this.username();
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -53,6 +53,7 @@
 | 
			
		||||
    padding: 10px 0 10px 0;
 | 
			
		||||
    margin: 0 10px 0 16px;
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#context-menu-container {
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,7 @@ body {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    min-height: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.note-detail-component {
 | 
			
		||||
@@ -311,6 +312,12 @@ div.ui-tooltip {
 | 
			
		||||
 | 
			
		||||
.cm-matchhighlight {background-color: #eeeeee}
 | 
			
		||||
 | 
			
		||||
#attribute-list {
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    /* limiting the size since actual note content is more important */
 | 
			
		||||
    max-height: 30%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#label-list, #relation-list, #attribute-list {
 | 
			
		||||
    color: #777777;
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
@@ -434,8 +441,13 @@ html.theme-dark body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#note-detail-promoted-attributes {
 | 
			
		||||
    max-width: 70%;
 | 
			
		||||
    margin: auto;
 | 
			
		||||
    /* setting the display to block since "table" doesn't support scrolling */
 | 
			
		||||
    display: block;
 | 
			
		||||
    flex-basis: content;
 | 
			
		||||
    flex-shrink: 1;
 | 
			
		||||
    flex-grow: 0;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#note-detail-promoted-attributes td, #note-detail-promoted-attributes th {
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ const appInfo = require('../../services/app_info');
 | 
			
		||||
const eventService = require('../../services/events');
 | 
			
		||||
const cls = require('../../services/cls');
 | 
			
		||||
const sqlInit = require('../../services/sql_init');
 | 
			
		||||
const sql = require('../../services/sql');
 | 
			
		||||
 | 
			
		||||
async function loginSync(req) {
 | 
			
		||||
    if (!await sqlInit.schemaExists()) {
 | 
			
		||||
@@ -44,7 +45,8 @@ async function loginSync(req) {
 | 
			
		||||
    req.session.loggedIn = true;
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        sourceId: sourceIdService.getCurrentSourceId()
 | 
			
		||||
        sourceId: sourceIdService.getCurrentSourceId(),
 | 
			
		||||
        maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync")
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,16 @@
 | 
			
		||||
 | 
			
		||||
const build = require('./build');
 | 
			
		||||
const packageJson = require('../../package');
 | 
			
		||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
 | 
			
		||||
 | 
			
		||||
const APP_DB_VERSION = 121;
 | 
			
		||||
const SYNC_VERSION = 2;
 | 
			
		||||
const SYNC_VERSION = 3;
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    appVersion: packageJson.version,
 | 
			
		||||
    dbVersion: APP_DB_VERSION,
 | 
			
		||||
    syncVersion: SYNC_VERSION,
 | 
			
		||||
    buildDate: build.buildDate,
 | 
			
		||||
    buildRevision: build.buildRevision
 | 
			
		||||
    buildRevision: build.buildRevision,
 | 
			
		||||
    dataDirectory: TRILIUM_DATA_DIR
 | 
			
		||||
};
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
module.exports = { buildDate:"2018-12-30T22:38:11+01:00", buildRevision: "32220476aa6795bab036b7dd9057ea3357d7dd51" };
 | 
			
		||||
module.exports = { buildDate:"2019-01-04T23:33:32+01:00", buildRevision: "5d74dcd2564ff1341550ade1250aa9d790abc056" };
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ const Attribute = require('../entities/attribute');
 | 
			
		||||
const NoteRevision = require('../entities/note_revision');
 | 
			
		||||
const RecentNote = require('../entities/recent_note');
 | 
			
		||||
const Option = require('../entities/option');
 | 
			
		||||
const Link = require('../entities/link');
 | 
			
		||||
 | 
			
		||||
async function getHash(entityConstructor, whereBranch) {
 | 
			
		||||
    // subselect is necessary to have correct ordering in GROUP_CONCAT
 | 
			
		||||
@@ -37,7 +38,8 @@ async function getHashes() {
 | 
			
		||||
        recent_notes: await getHash(RecentNote),
 | 
			
		||||
        options: await getHash(Option, "isSynced = 1"),
 | 
			
		||||
        attributes: await getHash(Attribute),
 | 
			
		||||
        api_tokens: await getHash(ApiToken)
 | 
			
		||||
        api_tokens: await getHash(ApiToken),
 | 
			
		||||
        links: await getHash(Link)
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const elapseTimeMs = new Date().getTime() - startTime.getTime();
 | 
			
		||||
 
 | 
			
		||||
@@ -55,10 +55,6 @@ function getTriliumDataDir() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const TRILIUM_DATA_DIR =  getTriliumDataDir();
 | 
			
		||||
 | 
			
		||||
// not necessary to log this since if we have logs we already know where data dir is.
 | 
			
		||||
console.log("Using data dir:", TRILIUM_DATA_DIR);
 | 
			
		||||
 | 
			
		||||
const DOCUMENT_PATH = TRILIUM_DATA_DIR + "/document.db";
 | 
			
		||||
const BACKUP_DIR = TRILIUM_DATA_DIR + "/backup";
 | 
			
		||||
const LOG_DIR = TRILIUM_DATA_DIR + "/log";
 | 
			
		||||
 
 | 
			
		||||
@@ -5,13 +5,24 @@ const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
 | 
			
		||||
const ENTITY_CREATED = "ENTITY_CREATED";
 | 
			
		||||
const ENTITY_CHANGED = "ENTITY_CHANGED";
 | 
			
		||||
const ENTITY_DELETED = "ENTITY_DELETED";
 | 
			
		||||
const ENTITY_SYNCED = "ENTITY_SYNCED";
 | 
			
		||||
const CHILD_NOTE_CREATED = "CHILD_NOTE_CREATED";
 | 
			
		||||
 | 
			
		||||
const eventListeners = {};
 | 
			
		||||
 | 
			
		||||
function subscribe(eventType, listener) {
 | 
			
		||||
/**
 | 
			
		||||
 * @param eventTypes - can be either single event or an array of events
 | 
			
		||||
 * @param listener
 | 
			
		||||
 */
 | 
			
		||||
function subscribe(eventTypes, listener) {
 | 
			
		||||
    if (!Array.isArray(eventTypes)) {
 | 
			
		||||
        eventTypes = [ eventTypes ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const eventType of eventTypes) {
 | 
			
		||||
        eventListeners[eventType] = eventListeners[eventType] || [];
 | 
			
		||||
        eventListeners[eventType].push(listener);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function emit(eventType, data) {
 | 
			
		||||
@@ -39,5 +50,6 @@ module.exports = {
 | 
			
		||||
    ENTITY_CREATED,
 | 
			
		||||
    ENTITY_CHANGED,
 | 
			
		||||
    ENTITY_DELETED,
 | 
			
		||||
    ENTITY_SYNCED,
 | 
			
		||||
    CHILD_NOTE_CREATED
 | 
			
		||||
};
 | 
			
		||||
@@ -45,8 +45,6 @@ function request(req) {
 | 
			
		||||
    logger.info(req.method + " " + req.url);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
info("Using data dir: " + dataDir.TRILIUM_DATA_DIR);
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    info,
 | 
			
		||||
    error,
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@ async function sendMessage(client, message) {
 | 
			
		||||
async function sendMessageToAllClients(message) {
 | 
			
		||||
    const jsonStr = JSON.stringify(message);
 | 
			
		||||
 | 
			
		||||
    if (webSocketServer) {
 | 
			
		||||
        log.info("Sending message to all clients: " + jsonStr);
 | 
			
		||||
 | 
			
		||||
        webSocketServer.clients.forEach(function each(client) {
 | 
			
		||||
@@ -59,6 +60,7 @@ async function sendMessageToAllClients(message) {
 | 
			
		||||
                client.send(jsonStr);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function sendPing(client, lastSentSyncId) {
 | 
			
		||||
 
 | 
			
		||||
@@ -299,7 +299,9 @@ function getNotePath(noteId) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity}) => {
 | 
			
		||||
eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_SYNCED], async ({entityName, entity}) => {
 | 
			
		||||
    // note that entity can also be just POJO without methods if coming from sync
 | 
			
		||||
 | 
			
		||||
    if (!loaded) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -153,7 +153,8 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {})
 | 
			
		||||
            noteId: note.noteId,
 | 
			
		||||
            type: attr.type,
 | 
			
		||||
            name: attr.name,
 | 
			
		||||
            value: attr.value
 | 
			
		||||
            value: attr.value,
 | 
			
		||||
            isInheritable: !!attr.isInheritable
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -357,10 +358,14 @@ async function deleteNote(branch) {
 | 
			
		||||
    const notDeletedBranches = await note.getBranches();
 | 
			
		||||
 | 
			
		||||
    if (notDeletedBranches.length === 0) {
 | 
			
		||||
        note.isDeleted = true;
 | 
			
		||||
        // we don't reset content here, that's postponed and done later to give the user
 | 
			
		||||
        // a chance to correct a mistake
 | 
			
		||||
        await note.save();
 | 
			
		||||
        // maybe a bit counter-intuitively, protected notes can be deleted also outside of protected session
 | 
			
		||||
        // this is because protected notes offer only confidentiality which makes some things simpler - e.g. deletion UI
 | 
			
		||||
        // to allow this, we just set the isDeleted flag, otherwise saving would fail because of attempt to encrypt
 | 
			
		||||
        // content with non-existent protected session key
 | 
			
		||||
        // we don't reset content here, that's postponed and done later to give the user a chance to correct a mistake
 | 
			
		||||
        await sql.execute("UPDATE notes SET isDeleted = 1 WHERE noteId = ?", [note.noteId]);
 | 
			
		||||
        // need to manually trigger sync since it's not taken care of by note save
 | 
			
		||||
        await syncTableService.addNoteSync(note.noteId);
 | 
			
		||||
 | 
			
		||||
        for (const noteRevision of await note.getRevisions()) {
 | 
			
		||||
            await noteRevision.save();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
const url = require('url');
 | 
			
		||||
const log = require('./log');
 | 
			
		||||
const sql = require('./sql');
 | 
			
		||||
const sqlInit = require('./sql_init');
 | 
			
		||||
@@ -99,6 +98,16 @@ async function doLogin() {
 | 
			
		||||
 | 
			
		||||
    syncContext.sourceId = resp.sourceId;
 | 
			
		||||
 | 
			
		||||
    const lastSyncedPull = await getLastSyncedPull();
 | 
			
		||||
 | 
			
		||||
    // this is important in a scenario where we setup the sync by manually copying the document
 | 
			
		||||
    // lastSyncedPull then could be pretty off for the newly cloned client
 | 
			
		||||
    if (lastSyncedPull > resp.maxSyncId) {
 | 
			
		||||
        log.info(`Lowering last synced pull from ${lastSyncedPull} to ${resp.maxSyncId}`);
 | 
			
		||||
 | 
			
		||||
        await setLastSyncedPull(resp.maxSyncId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return syncContext;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -256,7 +265,7 @@ async function getEntityRow(entityName, entityId) {
 | 
			
		||||
            && entity.content !== null
 | 
			
		||||
            && (entity.type === 'file' || entity.type === 'image')) {
 | 
			
		||||
 | 
			
		||||
            entity.content = entity.content.toString("binary");
 | 
			
		||||
            entity.content = entity.content.toString("base64");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return entity;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ const sql = require('./sql');
 | 
			
		||||
const log = require('./log');
 | 
			
		||||
const eventLogService = require('./event_log');
 | 
			
		||||
const syncTableService = require('./sync_table');
 | 
			
		||||
const eventService = require('./events');
 | 
			
		||||
 | 
			
		||||
async function updateEntity(sync, entity, sourceId) {
 | 
			
		||||
    const {entityName} = sync;
 | 
			
		||||
@@ -36,11 +37,20 @@ async function updateEntity(sync, entity, sourceId) {
 | 
			
		||||
    else {
 | 
			
		||||
        throw new Error(`Unrecognized entity type ${entityName}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // currently making exception for protected notes and note revisions because here
 | 
			
		||||
    // the title and content are not available decrypted as listeners would expect
 | 
			
		||||
    if ((entityName !== 'notes' && entityName !== 'note_revisions') || !entity.isProtected) {
 | 
			
		||||
        await eventService.emit(eventService.ENTITY_SYNCED, {
 | 
			
		||||
            entityName,
 | 
			
		||||
            entity
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function deserializeNoteContentBuffer(note) {
 | 
			
		||||
    if (note.content !== null && (note.type === 'file' || note.type === 'image')) {
 | 
			
		||||
        note.content = new Buffer(note.content, 'binary');
 | 
			
		||||
        note.content = Buffer.from(note.content, 'base64');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -216,6 +216,11 @@
 | 
			
		||||
                                    <th>Build revision:</th>
 | 
			
		||||
                                    <td><a href="" target="_blank" id="build-revision"></a></td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
 | 
			
		||||
                                <tr>
 | 
			
		||||
                                    <th>Data directory:</th>
 | 
			
		||||
                                    <td id="data-directory"></td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            </table>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,15 +13,15 @@
 | 
			
		||||
 | 
			
		||||
    <div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;">
 | 
			
		||||
        <div class="radio" style="margin-bottom: 15px;">
 | 
			
		||||
            <label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupNewDocument">
 | 
			
		||||
            <label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType">
 | 
			
		||||
                I'm a new user and I want to create new Trilium document for my notes</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;">
 | 
			
		||||
            <label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupSyncFromDesktop">
 | 
			
		||||
            <label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType">
 | 
			
		||||
                I have desktop instance already and I want to setup sync with it</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;">
 | 
			
		||||
            <label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupSyncFromServer">
 | 
			
		||||
            <label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType">
 | 
			
		||||
                I have server instance up and I want to setup sync with it</label>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user