mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	bookmarks use cloning
This commit is contained in:
		
							parent
							
								
									cd60ad4267
								
							
						
					
					
						commit
						27ce273d29
					
				
							
								
								
									
										894
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										894
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -119,6 +119,19 @@ class Branch extends AbstractEntity {
 | 
			
		||||
        return !(this.branchId in this.becca.branches);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Branch is weak when its existence should not hinder deletion of its note.
 | 
			
		||||
     * As a result, note with only weak branches should be immediately deleted.
 | 
			
		||||
     * An example is shared or bookmarked clones - they are created automatically and exist for technical reasons,
 | 
			
		||||
     * not as user-intended actions. From user perspective, they don't count as real clones and for the purpose
 | 
			
		||||
     * of deletion should not act as a clone.
 | 
			
		||||
     *
 | 
			
		||||
     * @returns {boolean}
 | 
			
		||||
     */
 | 
			
		||||
    get isWeak() {
 | 
			
		||||
        return ['share', 'lb_bookmarks'].includes(this.parentNoteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a branch. If this is a last note's branch, delete the note as well.
 | 
			
		||||
     *
 | 
			
		||||
@ -159,9 +172,13 @@ class Branch extends AbstractEntity {
 | 
			
		||||
 | 
			
		||||
        this.markAsDeleted(deleteId);
 | 
			
		||||
 | 
			
		||||
        const notDeletedBranches = note.getParentBranches();
 | 
			
		||||
        const notDeletedBranches = note.getStrongParentBranches();
 | 
			
		||||
 | 
			
		||||
        if (notDeletedBranches.length === 0) {
 | 
			
		||||
            for (const weakBranch of note.getParentBranches()) {
 | 
			
		||||
                weakBranch.markAsDeleted(deleteId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (const childBranch of note.getChildBranches()) {
 | 
			
		||||
                childBranch.deleteBranch(deleteId, taskContext);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,6 @@ const NoteRevision = require("./note_revision");
 | 
			
		||||
const TaskContext = require("../../services/task_context");
 | 
			
		||||
const dayjs = require("dayjs");
 | 
			
		||||
const utc = require('dayjs/plugin/utc');
 | 
			
		||||
const searchService = require("../../services/search/services/search.js");
 | 
			
		||||
dayjs.extend(utc)
 | 
			
		||||
 | 
			
		||||
const LABEL = 'label';
 | 
			
		||||
@ -155,6 +154,15 @@ class Note extends AbstractEntity {
 | 
			
		||||
        return this.parentBranches;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns <i>strong</i> (as opposed to <i>weak</i>) parent branches. See isWeak for details.
 | 
			
		||||
     *
 | 
			
		||||
     * @returns {Branch[]}
 | 
			
		||||
     */
 | 
			
		||||
    getStrongParentBranches() {
 | 
			
		||||
        return this.getParentBranches().filter(branch => !branch.isWeak);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @returns {Branch[]}
 | 
			
		||||
     * @deprecated use getParentBranches() instead
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import FlexContainer from "./containers/flex_container.js";
 | 
			
		||||
import searchService from "../services/search.js";
 | 
			
		||||
import OpenNoteButtonWidget from "./buttons/open_note_button_widget.js";
 | 
			
		||||
import BookmarkFolderWidget from "./buttons/bookmark_folder.js";
 | 
			
		||||
import froca from "../services/froca.js";
 | 
			
		||||
 | 
			
		||||
export default class BookmarkButtons extends FlexContainer {
 | 
			
		||||
    constructor() {
 | 
			
		||||
@ -11,13 +11,13 @@ export default class BookmarkButtons extends FlexContainer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async refresh() {
 | 
			
		||||
        const bookmarkedNotes = await searchService.searchForNotes("#bookmarked or #bookmarkFolder");
 | 
			
		||||
 | 
			
		||||
        this.$widget.empty();
 | 
			
		||||
        this.children = [];
 | 
			
		||||
        this.noteIds = [];
 | 
			
		||||
 | 
			
		||||
        for (const note of bookmarkedNotes) {
 | 
			
		||||
        const bookmarkParentNote = await froca.getNote('lb_bookmarks');
 | 
			
		||||
 | 
			
		||||
        for (const note of await bookmarkParentNote.getChildNotes()) {
 | 
			
		||||
            this.noteIds.push(note.noteId);
 | 
			
		||||
 | 
			
		||||
            const buttonWidget = note.hasLabel("bookmarkFolder")
 | 
			
		||||
@ -37,11 +37,7 @@ export default class BookmarkButtons extends FlexContainer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    entitiesReloadedEvent({loadResults}) {
 | 
			
		||||
        if (loadResults.getAttributes().find(attr => attr.type === 'label' && ['bookmarked', 'bookmarkFolder'].includes(attr.name))) {
 | 
			
		||||
            this.refresh();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (loadResults.getNoteIds().find(noteId => this.noteIds.includes(noteId))) {
 | 
			
		||||
        if (loadResults.getBranches().find(branch => branch.parentNoteId === 'lb_bookmarks')) {
 | 
			
		||||
            this.refresh();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,14 @@
 | 
			
		||||
import attributeService from "../services/attributes.js";
 | 
			
		||||
import SwitchWidget from "./switch.js";
 | 
			
		||||
import server from "../services/server.js";
 | 
			
		||||
import toastService from "../services/toast.js";
 | 
			
		||||
 | 
			
		||||
export default class BookmarkSwitchWidget extends SwitchWidget {
 | 
			
		||||
    isEnabled() {
 | 
			
		||||
        return super.isEnabled()
 | 
			
		||||
            // it's not possible to bookmark root because that would clone it under bookmarks and thus create a cycle
 | 
			
		||||
            && !['root', 'hidden'].includes(this.noteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    doRender() {
 | 
			
		||||
        super.doRender();
 | 
			
		||||
 | 
			
		||||
@ -12,32 +19,24 @@ export default class BookmarkSwitchWidget extends SwitchWidget {
 | 
			
		||||
        this.$switchOffButton.attr("title", "Remove bookmark");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async switchOff() {
 | 
			
		||||
        for (const label of this.note.getLabels('bookmarked')) {
 | 
			
		||||
            await attributeService.removeAttributeById(this.noteId, label.attributeId);
 | 
			
		||||
    async toggle(state) {
 | 
			
		||||
        const resp = await server.put(`notes/${this.noteId}/toggle-in-parent/lb_bookmarks/` + !!state);
 | 
			
		||||
 | 
			
		||||
        if (!resp.success) {
 | 
			
		||||
            toastService.showError(resp.message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switchOn() {
 | 
			
		||||
        return attributeService.setLabel(this.noteId, 'bookmarked');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    refreshWithNote(note) {
 | 
			
		||||
        const isBookmarked = note.hasLabel('bookmarked');
 | 
			
		||||
        const isBookmarked = !!note.getParentBranches().find(b => b.parentNoteId === 'lb_bookmarks');
 | 
			
		||||
 | 
			
		||||
        this.$switchOn.toggle(!isBookmarked);
 | 
			
		||||
        this.$switchOff.toggle(isBookmarked);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    entitiesReloadedEvent({loadResults}) {
 | 
			
		||||
        for (const attr of loadResults.getAttributes()) {
 | 
			
		||||
            if (attr.type === 'label'
 | 
			
		||||
                && attr.name === 'bookmarked'
 | 
			
		||||
                && attributeService.isAffecting(attr, this.note)) {
 | 
			
		||||
 | 
			
		||||
                this.refresh();
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        if (loadResults.getBranches().find(b => b.noteId === this.noteId)) {
 | 
			
		||||
            this.refresh();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -740,12 +740,14 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
 | 
			
		||||
            extraClasses.push("shared");
 | 
			
		||||
        }
 | 
			
		||||
        else if (note.getParentNoteIds().length > 1) {
 | 
			
		||||
            const notSearchParents = note.getParentNoteIds()
 | 
			
		||||
            const realClones = note.getParentNoteIds()
 | 
			
		||||
                .map(noteId => froca.notes[noteId])
 | 
			
		||||
                .filter(note => !!note)
 | 
			
		||||
                .filter(note => note.type !== 'search');
 | 
			
		||||
                .filter(note =>
 | 
			
		||||
                    !['share', 'lb_bookmarks'].includes(note.noteId)
 | 
			
		||||
                    && note.type !== 'search');
 | 
			
		||||
 | 
			
		||||
            if (notSearchParents.length > 1) {
 | 
			
		||||
            if (realClones.length > 1) {
 | 
			
		||||
                extraClasses.push("multiple-parents");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,8 @@ import dialogService from "../services/dialog.js";
 | 
			
		||||
 | 
			
		||||
export default class SharedSwitchWidget extends SwitchWidget {
 | 
			
		||||
    isEnabled() {
 | 
			
		||||
        return super.isEnabled() && this.noteId !== 'root' && this.noteId !== 'share';
 | 
			
		||||
        return super.isEnabled()
 | 
			
		||||
            && !['root', 'share', 'hidden'].includes(this.noteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    doRender() {
 | 
			
		||||
 | 
			
		||||
@ -101,18 +101,26 @@ export default class SwitchWidget extends NoteContextAwareWidget {
 | 
			
		||||
        this.$switchOnName = this.$widget.find(".switch-on-name");
 | 
			
		||||
        this.$switchOnButton = this.$widget.find(".switch-on-button");
 | 
			
		||||
 | 
			
		||||
        this.$switchOnButton.on('click', () => this.switchOn());
 | 
			
		||||
        this.$switchOnButton.on('click', () => this.toggle(true));
 | 
			
		||||
 | 
			
		||||
        this.$switchOff = this.$widget.find(".switch-off");
 | 
			
		||||
        this.$switchOffName = this.$widget.find(".switch-off-name");
 | 
			
		||||
        this.$switchOffButton = this.$widget.find(".switch-off-button");
 | 
			
		||||
 | 
			
		||||
        this.$switchOffButton.on('click', () => this.switchOff());
 | 
			
		||||
        this.$switchOffButton.on('click', () => this.toggle(false));
 | 
			
		||||
 | 
			
		||||
        this.$helpButton = this.$widget.find(".switch-help-button");
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toggle(state) {
 | 
			
		||||
        if (state) {
 | 
			
		||||
            this.switchOn();
 | 
			
		||||
        } else {
 | 
			
		||||
            this.switchOff();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switchOff() {}
 | 
			
		||||
    switchOn() {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -22,8 +22,15 @@ function cloneNoteAfter(req) {
 | 
			
		||||
    return cloningService.cloneNoteAfter(noteId, afterBranchId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toggleNoteInParent(req) {
 | 
			
		||||
    const {noteId, parentNoteId, present} = req.params;
 | 
			
		||||
 | 
			
		||||
    return cloningService.toggleNoteInParent(present === 'true', noteId, parentNoteId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    cloneNoteToBranch,
 | 
			
		||||
    cloneNoteToNote,
 | 
			
		||||
    cloneNoteAfter
 | 
			
		||||
    cloneNoteAfter,
 | 
			
		||||
    toggleNoteInParent
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -281,6 +281,7 @@ function register(app) {
 | 
			
		||||
    apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate);
 | 
			
		||||
 | 
			
		||||
    apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch);
 | 
			
		||||
    apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent);
 | 
			
		||||
    apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToNote);
 | 
			
		||||
    apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -66,8 +66,10 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
 | 
			
		||||
    if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) {
 | 
			
		||||
        return { success: false, message: 'Note is deleted.' };
 | 
			
		||||
    if (isNoteDeleted(noteId)) {
 | 
			
		||||
        return { success: false, message: `Note '${noteId}' is deleted.` };
 | 
			
		||||
    } else if (isNoteDeleted(parentNoteId)) {
 | 
			
		||||
        return { success: false, message: `Note '${parentNoteId}' is deleted.` };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const parentNote = becca.getNote(parentNoteId);
 | 
			
		||||
@ -89,7 +91,7 @@ function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
 | 
			
		||||
        isExpanded: 0
 | 
			
		||||
    }).save();
 | 
			
		||||
 | 
			
		||||
    log.info(`Ensured note ${noteId} is in parent note ${parentNoteId} with prefix ${prefix}`);
 | 
			
		||||
    log.info(`Ensured note '${noteId}' is in parent note '${parentNoteId}' with prefix '${prefix}'`);
 | 
			
		||||
 | 
			
		||||
    return { success: true };
 | 
			
		||||
}
 | 
			
		||||
@ -99,22 +101,27 @@ function ensureNoteIsAbsentFromParent(noteId, parentNoteId) {
 | 
			
		||||
    const branch = becca.getBranch(branchId);
 | 
			
		||||
 | 
			
		||||
    if (branch) {
 | 
			
		||||
        if (branch.getNote().getParentBranches().length <= 1) {
 | 
			
		||||
            throw new Error(`Cannot remove branch ${branch.branchId} between child ${noteId} and parent ${parentNoteId} because this would delete the note as well.`);
 | 
			
		||||
        if (!branch.isWeak && branch.getNote().getStrongParentBranches().length <= 1) {
 | 
			
		||||
            return {
 | 
			
		||||
                success: false,
 | 
			
		||||
                message: `Cannot remove branch '${branch.branchId}' between child '${noteId}' and parent '${parentNoteId}' because this would delete the note as well.`
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        branch.deleteBranch();
 | 
			
		||||
 | 
			
		||||
        log.info(`Ensured note ${noteId} is NOT in parent note ${parentNoteId}`);
 | 
			
		||||
        log.info(`Ensured note '${noteId}' is NOT in parent note '${parentNoteId}'`);
 | 
			
		||||
 | 
			
		||||
        return { success: true };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toggleNoteInParent(present, noteId, parentNoteId, prefix) {
 | 
			
		||||
    if (present) {
 | 
			
		||||
        ensureNoteIsPresentInParent(noteId, parentNoteId, prefix);
 | 
			
		||||
        return ensureNoteIsPresentInParent(noteId, parentNoteId, prefix);
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        ensureNoteIsAbsentFromParent(noteId, parentNoteId);
 | 
			
		||||
        return ensureNoteIsAbsentFromParent(noteId, parentNoteId);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -160,7 +167,7 @@ function cloneNoteAfter(noteId, afterBranchId) {
 | 
			
		||||
        isExpanded: 0
 | 
			
		||||
    }).save();
 | 
			
		||||
 | 
			
		||||
    log.info(`Cloned note ${noteId} into parent note ${afterNote.parentNoteId} after note ${afterNote.noteId}, branch ${afterBranchId}`);
 | 
			
		||||
    log.info(`Cloned note '${noteId}' into parent note '${afterNote.parentNoteId}' after note '${afterNote.noteId}', branch ${afterBranchId}`);
 | 
			
		||||
 | 
			
		||||
    return { success: true, branchId: branch.branchId };
 | 
			
		||||
}
 | 
			
		||||
@ -168,7 +175,7 @@ function cloneNoteAfter(noteId, afterBranchId) {
 | 
			
		||||
function isNoteDeleted(noteId) {
 | 
			
		||||
    const note = becca.getNote(noteId);
 | 
			
		||||
 | 
			
		||||
    return note.isDeleted;
 | 
			
		||||
    return !note || note.isDeleted;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
 | 
			
		||||
@ -58,7 +58,7 @@ function validateParentChild(parentNoteId, childNoteId, branchId = null) {
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (becca.getNote(parentNoteId).type === 'launcher') {
 | 
			
		||||
    if (parentNoteId !== 'lb_bookmarks' && becca.getNote(parentNoteId).type === 'launcher') {
 | 
			
		||||
        return {
 | 
			
		||||
            success: false,
 | 
			
		||||
            message: 'Launcher note cannot have any children.'
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user