mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	Merge branch 'master' into dev
# Conflicts: # package-lock.json # package.json # src/public/app/widgets/dialogs/note_revisions.js # src/services/handlers.js # src/services/hidden_subtree.js # src/services/search/services/parse.js
This commit is contained in:
		
						commit
						6a6ae359b6
					
				
							
								
								
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@ -12,5 +12,10 @@ module.exports = () => {
 | 
			
		||||
 | 
			
		||||
            attr.markAsDeleted("0204__migrate_bookmarks_to_clones");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // bookmarkFolder used to work in 0.57 without the bookmarked label
 | 
			
		||||
        for (const attr of becca.findAttributes('label','bookmarkFolder')) {
 | 
			
		||||
            cloningService.toggleNoteInParent(true, attr.noteId, '_lbBookmarks');
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -82,10 +82,8 @@ function getNoteTitleArrayForPath(notePathArray) {
 | 
			
		||||
        throw new Error(`${notePathArray} is not an array.`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const hoistedNoteId = cls.getHoistedNoteId();
 | 
			
		||||
 | 
			
		||||
    if (notePathArray.length === 1 && notePathArray[0] === hoistedNoteId) {
 | 
			
		||||
        return [getNoteTitle(hoistedNoteId)];
 | 
			
		||||
    if (notePathArray.length === 1) {
 | 
			
		||||
        return [getNoteTitle(notePathArray[0])];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const titles = [];
 | 
			
		||||
@ -94,6 +92,7 @@ function getNoteTitleArrayForPath(notePathArray) {
 | 
			
		||||
    let hoistedNotePassed = false;
 | 
			
		||||
 | 
			
		||||
    // this is a notePath from outside of hoisted subtree so full title path needs to be returned
 | 
			
		||||
    const hoistedNoteId = cls.getHoistedNoteId();
 | 
			
		||||
    const outsideOfHoistedSubtree = !notePathArray.includes(hoistedNoteId);
 | 
			
		||||
 | 
			
		||||
    for (const noteId of notePathArray) {
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ const BNoteRevision = require("./bnote_revision");
 | 
			
		||||
const TaskContext = require("../../services/task_context");
 | 
			
		||||
const dayjs = require("dayjs");
 | 
			
		||||
const utc = require('dayjs/plugin/utc');
 | 
			
		||||
const eventService = require("../../services/events");
 | 
			
		||||
dayjs.extend(utc)
 | 
			
		||||
 | 
			
		||||
const LABEL = 'label';
 | 
			
		||||
@ -314,6 +315,11 @@ class BNote extends AbstractBeccaEntity {
 | 
			
		||||
            utcDateChanged: pojo.utcDateModified,
 | 
			
		||||
            isSynced: true
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        eventService.emit(eventService.ENTITY_CHANGED, {
 | 
			
		||||
            entityName: 'note_contents',
 | 
			
		||||
            entity: this
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setJsonContent(content) {
 | 
			
		||||
@ -1124,6 +1130,13 @@ class BNote extends AbstractBeccaEntity {
 | 
			
		||||
        return notePaths;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
 | 
			
		||||
     */
 | 
			
		||||
    isHiddenCompletely() {
 | 
			
		||||
        return !this.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ancestorNoteId
 | 
			
		||||
     * @returns {boolean} - true if ancestorNoteId occurs in at least one of the note's paths
 | 
			
		||||
@ -1368,7 +1381,7 @@ class BNote extends AbstractBeccaEntity {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isOptions() {
 | 
			
		||||
        return this.noteId.startsWith("options");
 | 
			
		||||
        return this.noteId.startsWith("_options");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get isDeleted() {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
<p>Back and Forward buttons allow you to move in the navigation history.</p>
 | 
			
		||||
 | 
			
		||||
<p>These launchers are active only in the desktop build and will be ignored in the server edition where you can use the native browser navigation buttons instead.</p>
 | 
			
		||||
@ -360,6 +360,13 @@ class FNote {
 | 
			
		||||
        return notePaths;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
 | 
			
		||||
     */
 | 
			
		||||
    isHiddenCompletely() {
 | 
			
		||||
        return !this.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    __filterAttrs(attributes, type, name) {
 | 
			
		||||
        this.__validateTypeName(type, name);
 | 
			
		||||
 | 
			
		||||
@ -852,7 +859,7 @@ class FNote {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isOptions() {
 | 
			
		||||
        return this.noteId.startsWith("options");
 | 
			
		||||
        return this.noteId.startsWith("_options");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -230,6 +230,10 @@ function init() {
 | 
			
		||||
 | 
			
		||||
    $.fn.getSelectedNoteId = function () {
 | 
			
		||||
        const notePath = $(this).getSelectedNotePath();
 | 
			
		||||
        if (!notePath) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const chunks = notePath.split('/');
 | 
			
		||||
 | 
			
		||||
        return chunks.length >= 1 ? chunks[chunks.length - 1] : null;
 | 
			
		||||
 | 
			
		||||
@ -247,7 +247,7 @@ const ATTR_HELP = {
 | 
			
		||||
        "runOnNoteCreation": "executes when note is created on backend. Use this relation if you want to run the script for all notes created under a specific subtree. In that case, create it on the subtree root note and make it inheritable. A new note created within the subtree (any depth) will trigger the script.",
 | 
			
		||||
        "runOnChildNoteCreation": "executes when new note is created under the note where this relation is defined",
 | 
			
		||||
        "runOnNoteTitleChange": "executes when note title is changed (includes note creation as well)",
 | 
			
		||||
        "runOnNoteContentChange": "executes when note content is changed  (includes note creation as well).",
 | 
			
		||||
        "runOnNoteContentChange": "executes when note content is changed (includes note creation as well).",
 | 
			
		||||
        "runOnNoteChange": "executes when note is changed (includes note creation as well). Does not include content changes",
 | 
			
		||||
        "runOnNoteDeletion": "executes when note is being deleted",
 | 
			
		||||
        "runOnBranchCreation": "executes when a branch is created. Branch is a link between parent note and child note and is created e.g. when cloning or moving note.",
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {function|string} command
 | 
			
		||||
     * @returns {CommandButtonWidget}
 | 
			
		||||
     * @returns {this}
 | 
			
		||||
     */
 | 
			
		||||
    command(command) {
 | 
			
		||||
        this.settings.command = command;
 | 
			
		||||
 | 
			
		||||
@ -303,7 +303,7 @@ export default class GlobalMenuWidget extends BasicWidget {
 | 
			
		||||
        const resp = await fetch(RELEASES_API_URL);
 | 
			
		||||
        const data = await resp.json();
 | 
			
		||||
 | 
			
		||||
        return data.tag_name.substring(1);
 | 
			
		||||
        return data?.tag_name?.substring(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    downloadLatestVersionCommand() {
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,10 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
 | 
			
		||||
    doRender() {
 | 
			
		||||
        super.doRender();
 | 
			
		||||
 | 
			
		||||
        if (!utils.isElectron()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents();
 | 
			
		||||
 | 
			
		||||
        // without this the history is preserved across frontend reloads
 | 
			
		||||
 | 
			
		||||
@ -234,14 +234,14 @@ export default class NoteRevisionsDialog extends BasicWidget {
 | 
			
		||||
                renderMathInElement($content[0], {trust: true});
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (revisionItem.type === 'code') {
 | 
			
		||||
        else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') {
 | 
			
		||||
            this.$content.html($("<pre>").text(fullNoteRevision.content));
 | 
			
		||||
        }
 | 
			
		||||
        else if (revisionItem.type === 'image') {
 | 
			
		||||
            this.$content.html($("<img>")
 | 
			
		||||
                // reason why we put this inline as base64 is that we do not want to let user copy this
 | 
			
		||||
                // as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be an uploaded as a new note
 | 
			
		||||
                .attr("src", `data:${note.mime};base64,${fullNoteRevision.content}`)
 | 
			
		||||
                .attr("src", `data:${fullNoteRevision.mime};base64,${fullNoteRevision.content}`)
 | 
			
		||||
                .css("max-width", "100%")
 | 
			
		||||
                .css("max-height", "100%"));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -76,7 +76,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget {
 | 
			
		||||
 | 
			
		||||
        this.$saveToNoteButton.toggle(
 | 
			
		||||
            note.mime === 'text/x-sqlite;schema=trilium'
 | 
			
		||||
            && !note.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden'))
 | 
			
		||||
            && note.isHiddenCompletely()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env='));
 | 
			
		||||
 | 
			
		||||
@ -1309,6 +1309,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
 | 
			
		||||
            await this.tree.reload([rootNode]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await this.filterHoistedBranch();
 | 
			
		||||
 | 
			
		||||
        if (activeNotePath) {
 | 
			
		||||
            const node = await this.getNodeFromPath(activeNotePath, true);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -270,7 +270,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
 | 
			
		||||
    async refreshWithNote(note) {
 | 
			
		||||
        this.$component.show();
 | 
			
		||||
 | 
			
		||||
        this.$saveToNoteButton.toggle(!note.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden')));
 | 
			
		||||
        this.$saveToNoteButton.toggle(note.isHiddenCompletely());
 | 
			
		||||
 | 
			
		||||
        this.$searchOptions.empty();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -112,7 +112,7 @@ function update(name, value) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getUserThemes() {
 | 
			
		||||
    const notes = searchService.searchNotes("#appTheme");
 | 
			
		||||
    const notes = searchService.searchNotes("#appTheme", {ignoreHoistedNote: true});
 | 
			
		||||
    const ret = [];
 | 
			
		||||
 | 
			
		||||
    for (const note of notes) {
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
module.exports = { buildDate:"2023-01-11T23:44:33+01:00", buildRevision: "bdfdc0402ddb23e9af002580f368bc52e4268b3a" };
 | 
			
		||||
module.exports = { buildDate:"2023-01-16T22:39:28+01:00", buildRevision: "9fd0b85ff2be264be35ec2052c956b654f0dac9e" };
 | 
			
		||||
 | 
			
		||||
@ -71,6 +71,7 @@ module.exports = [
 | 
			
		||||
    { type: 'relation', name: 'runOnNoteCreation', isDangerous: true },
 | 
			
		||||
    { type: 'relation', name: 'runOnNoteTitleChange', isDangerous: true },
 | 
			
		||||
    { type: 'relation', name: 'runOnNoteChange', isDangerous: true },
 | 
			
		||||
    { type: 'relation', name: 'runOnNoteContentChange', isDangerous: true },
 | 
			
		||||
    { type: 'relation', name: 'runOnNoteDeletion', isDangerous: true },
 | 
			
		||||
    { type: 'relation', name: 'runOnBranchCreation', isDangerous: true },
 | 
			
		||||
    { type: 'relation', name: 'runOnBranchDeletion', isDangerous: true },
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,8 @@ const treeService = require('./tree');
 | 
			
		||||
const noteService = require('./notes');
 | 
			
		||||
const becca = require('../becca/becca');
 | 
			
		||||
const BAttribute = require('../becca/entities/battribute');
 | 
			
		||||
const hiddenSubtreeService = require("./hidden_subtree");
 | 
			
		||||
const oneTimeTimer = require("./one_time_timer");
 | 
			
		||||
 | 
			
		||||
function runAttachedRelations(note, relationName, originEntity) {
 | 
			
		||||
    if (!note) {
 | 
			
		||||
@ -206,6 +208,16 @@ eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) =>
 | 
			
		||||
    if (entityName === 'branches') {
 | 
			
		||||
        runAttachedRelations(entity.getNote(), 'runOnBranchDeletion', entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (entityName === 'notes' && entity.noteId.startsWith("_")) {
 | 
			
		||||
        // "named" note has been deleted, we will probably need to rebuild the hidden subtree
 | 
			
		||||
        // scheduling so that bulk deletes won't trigger so many checks
 | 
			
		||||
        oneTimeTimer.scheduleExecution('hidden-subtree-check', 1000, () => {
 | 
			
		||||
            console.log("Checking hidden subtree");
 | 
			
		||||
 | 
			
		||||
            hiddenSubtreeService.checkHiddenSubtree();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
const becca = require("../becca/becca");
 | 
			
		||||
const noteService = require("./notes");
 | 
			
		||||
const BAttribute = require("../becca/entities/battribute");
 | 
			
		||||
const log = require("./log");
 | 
			
		||||
const migrationService = require("./migration");
 | 
			
		||||
 | 
			
		||||
const LBTPL_ROOT = "_lbTplRoot";
 | 
			
		||||
const LBTPL_BASE = "_lbTplBase";
 | 
			
		||||
@ -179,8 +181,10 @@ const HIDDEN_SUBTREE_DEFINITION = {
 | 
			
		||||
                    isExpanded: true,
 | 
			
		||||
                    attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ],
 | 
			
		||||
                    children: [
 | 
			
		||||
                        { id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-arrow-square' },
 | 
			
		||||
                        { id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-right-arrow-square' },
 | 
			
		||||
                        { id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-arrow-square',
 | 
			
		||||
                            attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]},
 | 
			
		||||
                        { id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-right-arrow-square',
 | 
			
		||||
                            attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]},
 | 
			
		||||
                        { id: '_lbBackendLog', title: 'Backend Log', type: 'launcher', targetNoteId: '_backendLog', icon: 'bx bx-terminal' },
 | 
			
		||||
                    ]
 | 
			
		||||
                },
 | 
			
		||||
@ -237,6 +241,12 @@ const HIDDEN_SUBTREE_DEFINITION = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function checkHiddenSubtree() {
 | 
			
		||||
    if (!migrationService.isDbUpToDate()) {
 | 
			
		||||
        // on-delete hook might get triggered during some future migration and cause havoc
 | 
			
		||||
        log.info("Will not check hidden subtree until migration is finished.");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    checkHiddenSubtreeRecursively('root', HIDDEN_SUBTREE_DEFINITION);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -76,8 +76,8 @@ function importEnex(taskContext, file, parentNote) {
 | 
			
		||||
        content = content.replace(/<\/ol>\s*<li>/g, "</ol></li><li>");
 | 
			
		||||
 | 
			
		||||
        // Replace en-todo with unicode ballot box
 | 
			
		||||
        content = content.replace(/<en-todo\s+checked="true"\/>/g, "\u2611 ");
 | 
			
		||||
        content = content.replace(/<en-todo(\s+checked="false")?\/>/g, "\u2610 ");
 | 
			
		||||
        content = content.replace(/<en-todo\s+checked="true"\s*\/>/g, "\u2611 ");
 | 
			
		||||
        content = content.replace(/<en-todo(\s+checked="false")?\s*\/>/g, "\u2610 ");
 | 
			
		||||
 | 
			
		||||
        // Replace OneNote converted checkboxes with unicode ballot box based
 | 
			
		||||
        // on known hash of checkboxes for regular, p1, and p2 checkboxes
 | 
			
		||||
 | 
			
		||||
@ -119,5 +119,6 @@ async function migrateIfNecessary() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    migrateIfNecessary
 | 
			
		||||
    migrateIfNecessary,
 | 
			
		||||
    isDbUpToDate
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -108,8 +108,13 @@ function getAndValidateParent(params) {
 | 
			
		||||
        throw new ValidationError(`Only 'launcher' notes can be created in parent '${params.parentNoteId}'`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!params.ignoreForbiddenParents && (['_lbRoot', '_hidden'].includes(parentNote.noteId) || parentNote.isOptions())) {
 | 
			
		||||
        throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`);
 | 
			
		||||
    if (!params.ignoreForbiddenParents) {
 | 
			
		||||
        if (['_lbRoot', '_hidden'].includes(parentNote.noteId)
 | 
			
		||||
            || parentNote.noteId.startsWith("_lbTpl")
 | 
			
		||||
            || parentNote.isOptions()) {
 | 
			
		||||
 | 
			
		||||
            throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return parentNote;
 | 
			
		||||
@ -283,8 +288,12 @@ function protectNote(note, protect) {
 | 
			
		||||
 | 
			
		||||
            note.isProtected = protect;
 | 
			
		||||
 | 
			
		||||
            // this will force de/encryption
 | 
			
		||||
            note.setContent(content);
 | 
			
		||||
            // see https://github.com/zadam/trilium/issues/3523
 | 
			
		||||
            // IIRC a zero-sized buffer can be returned as null from the database
 | 
			
		||||
            if (content !== null) {
 | 
			
		||||
                // this will force de/encryption
 | 
			
		||||
                note.setContent(content);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            note.save();
 | 
			
		||||
        }
 | 
			
		||||
@ -592,11 +601,6 @@ function updateNoteContent(noteId, content) {
 | 
			
		||||
    content = saveLinks(note, content);
 | 
			
		||||
 | 
			
		||||
    note.setContent(content);
 | 
			
		||||
 | 
			
		||||
    eventService.emit(eventService.ENTITY_CHANGED, {
 | 
			
		||||
        entityName: 'note_contents',
 | 
			
		||||
        entity: note
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								src/services/one_time_timer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/services/one_time_timer.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
const scheduledExecutions = {};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Subsequent calls will not move the timer to future. The first caller determines the time of execution.
 | 
			
		||||
 *
 | 
			
		||||
 * The good thing about synchronous better-sqlite3 is that this cannot interrupt transaction. The execution will be called
 | 
			
		||||
 * only outside of a transaction.
 | 
			
		||||
 */
 | 
			
		||||
function scheduleExecution(name, milliseconds, cb) {
 | 
			
		||||
    if (name in scheduledExecutions) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    scheduledExecutions[name] = true;
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
        delete scheduledExecutions[name];
 | 
			
		||||
 | 
			
		||||
        cb();
 | 
			
		||||
    }, milliseconds);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    scheduleExecution
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										23
									
								
								src/services/search/expressions/is_hidden.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/services/search/expressions/is_hidden.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
const Expression = require('./expression');
 | 
			
		||||
const NoteSet = require('../note_set');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Note is hidden when all its note paths start in hidden subtree (i.e. the note is not cloned into visible tree)
 | 
			
		||||
 */
 | 
			
		||||
class IsHiddenExp extends Expression {
 | 
			
		||||
    execute(inputNoteSet, executionContext, searchContext) {
 | 
			
		||||
        const resultNoteSet = new NoteSet();
 | 
			
		||||
 | 
			
		||||
        for (const note of inputNoteSet.notes) {
 | 
			
		||||
            if (note.isHiddenCompletely()) {
 | 
			
		||||
                resultNoteSet.add(note);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return resultNoteSet;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = IsHiddenExp;
 | 
			
		||||
@ -6,10 +6,11 @@ class SearchContext {
 | 
			
		||||
    constructor(params = {}) {
 | 
			
		||||
        this.fastSearch = !!params.fastSearch;
 | 
			
		||||
        this.includeArchivedNotes = !!params.includeArchivedNotes;
 | 
			
		||||
        this.includeHiddenNotes = !!params.includeHiddenNotes;
 | 
			
		||||
        this.ignoreHoistedNote = !!params.ignoreHoistedNote;
 | 
			
		||||
        this.ancestorNoteId = params.ancestorNoteId;
 | 
			
		||||
 | 
			
		||||
        if (!this.ancestorNoteId && !this.ignoreHoistedNote && !hoistedNoteService.isHoistedInHiddenSubtree()) {
 | 
			
		||||
        if (!this.ancestorNoteId && !this.ignoreHoistedNote) {
 | 
			
		||||
            // hoisting in hidden subtree should not limit autocomplete
 | 
			
		||||
            // since we want to link (create relations) to the normal non-hidden notes
 | 
			
		||||
            this.ancestorNoteId = hoistedNoteService.getHoistedNoteId();
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ const buildComparator = require('./build_comparator');
 | 
			
		||||
const ValueExtractor = require('../value_extractor');
 | 
			
		||||
const utils = require("../../utils");
 | 
			
		||||
const TrueExp = require("../expressions/true");
 | 
			
		||||
const IsHiddenExp = require("../expressions/is_hidden");
 | 
			
		||||
 | 
			
		||||
function getFulltext(tokens, searchContext) {
 | 
			
		||||
    tokens = tokens.map(t => utils.removeDiacritic(t.token));
 | 
			
		||||
@ -429,7 +430,7 @@ function parse({fulltextTokens, expressionTokens, searchContext}) {
 | 
			
		||||
 | 
			
		||||
    let exp = AndExp.of([
 | 
			
		||||
        searchContext.includeArchivedNotes ? null : new PropertyComparisonExp(searchContext, "isarchived", "=", "false"),
 | 
			
		||||
        (searchContext.ancestorNoteId && searchContext.ancestorNoteId !== 'root') ? new AncestorExp(searchContext.ancestorNoteId, searchContext.ancestorDepth) : null,
 | 
			
		||||
        getAncestorExp(searchContext),
 | 
			
		||||
        getFulltext(fulltextTokens, searchContext),
 | 
			
		||||
        expression
 | 
			
		||||
    ]);
 | 
			
		||||
@ -448,4 +449,14 @@ function parse({fulltextTokens, expressionTokens, searchContext}) {
 | 
			
		||||
    return exp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getAncestorExp({ancestorNoteId, ancestorDepth, includeHiddenNotes}) {
 | 
			
		||||
    if (ancestorNoteId && ancestorNoteId !== 'root') {
 | 
			
		||||
        return new AncestorExp(ancestorNoteId, ancestorDepth);
 | 
			
		||||
    } else if (!includeHiddenNotes) {
 | 
			
		||||
        return new NotExp(new IsHiddenExp());
 | 
			
		||||
    } else {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = parse;
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ const beccaService = require('../../../becca/becca_service');
 | 
			
		||||
const utils = require('../../utils');
 | 
			
		||||
const log = require('../../log');
 | 
			
		||||
const scriptService = require("../../script");
 | 
			
		||||
const hoistedNoteService = require("../../hoisted_note");
 | 
			
		||||
 | 
			
		||||
function searchFromNote(note) {
 | 
			
		||||
    let searchResultNoteIds, highlightedTokens;
 | 
			
		||||
@ -272,7 +273,11 @@ function searchNotesForAutocomplete(query) {
 | 
			
		||||
    const searchContext = new SearchContext({
 | 
			
		||||
        fastSearch: true,
 | 
			
		||||
        includeArchivedNotes: false,
 | 
			
		||||
        fuzzyAttributeSearch: true
 | 
			
		||||
        includeHiddenNotes: true,
 | 
			
		||||
        fuzzyAttributeSearch: true,
 | 
			
		||||
        ancestorNoteId: hoistedNoteService.isHoistedInHiddenSubtree()
 | 
			
		||||
            ? 'root'
 | 
			
		||||
            : hoistedNoteService.getHoistedNoteId()
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const allSearchResults = findResultsWithQuery(query, searchContext);
 | 
			
		||||
 | 
			
		||||
@ -218,7 +218,7 @@ function resetLauncher(noteId) {
 | 
			
		||||
        log.info(`Note ${noteId} is not a resettable launcher note.`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hiddenSubtreeService.checkHiddenSubtree();
 | 
			
		||||
    // the re-building deleted launchers will be done in handlers
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ const {JSDOM} = require("jsdom");
 | 
			
		||||
const shaca = require("./shaca/shaca");
 | 
			
		||||
const assetPath = require("../services/asset_path");
 | 
			
		||||
const shareRoot = require('./share_root');
 | 
			
		||||
const escapeHtml = require('escape-html');
 | 
			
		||||
 | 
			
		||||
function getContent(note) {
 | 
			
		||||
    if (note.isProtected) {
 | 
			
		||||
@ -112,17 +113,17 @@ function renderCode(result) {
 | 
			
		||||
 | 
			
		||||
function renderMermaid(result) {
 | 
			
		||||
    result.content = `
 | 
			
		||||
<div class="mermaid">${result.content}</div>
 | 
			
		||||
<div class="mermaid">${escapeHtml(result.content)}</div>
 | 
			
		||||
<hr>
 | 
			
		||||
<details>
 | 
			
		||||
    <summary>Chart source</summary>
 | 
			
		||||
    <pre>${result.content}</pre>
 | 
			
		||||
    <pre>${escapeHtml(result.content)}</pre>
 | 
			
		||||
</details>`
 | 
			
		||||
    result.header += `<script src="../../${assetPath}/libraries/mermaid.min.js"></script>`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renderImage(result, note) {
 | 
			
		||||
    result.content = `<img src="api/images/${note.noteId}/${note.title}?${note.utcDateModified}">`;
 | 
			
		||||
    result.content = `<img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}">`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function renderFile(note, result) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user