mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 21:11:30 +08: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); |         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. |      * 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); |         this.markAsDeleted(deleteId); | ||||||
| 
 | 
 | ||||||
|         const notDeletedBranches = note.getParentBranches(); |         const notDeletedBranches = note.getStrongParentBranches(); | ||||||
| 
 | 
 | ||||||
|         if (notDeletedBranches.length === 0) { |         if (notDeletedBranches.length === 0) { | ||||||
|  |             for (const weakBranch of note.getParentBranches()) { | ||||||
|  |                 weakBranch.markAsDeleted(deleteId); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             for (const childBranch of note.getChildBranches()) { |             for (const childBranch of note.getChildBranches()) { | ||||||
|                 childBranch.deleteBranch(deleteId, taskContext); |                 childBranch.deleteBranch(deleteId, taskContext); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -11,7 +11,6 @@ const NoteRevision = require("./note_revision"); | |||||||
| const TaskContext = require("../../services/task_context"); | const TaskContext = require("../../services/task_context"); | ||||||
| const dayjs = require("dayjs"); | const dayjs = require("dayjs"); | ||||||
| const utc = require('dayjs/plugin/utc'); | const utc = require('dayjs/plugin/utc'); | ||||||
| const searchService = require("../../services/search/services/search.js"); |  | ||||||
| dayjs.extend(utc) | dayjs.extend(utc) | ||||||
| 
 | 
 | ||||||
| const LABEL = 'label'; | const LABEL = 'label'; | ||||||
| @ -155,6 +154,15 @@ class Note extends AbstractEntity { | |||||||
|         return this.parentBranches; |         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[]} |      * @returns {Branch[]} | ||||||
|      * @deprecated use getParentBranches() instead |      * @deprecated use getParentBranches() instead | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import FlexContainer from "./containers/flex_container.js"; | import FlexContainer from "./containers/flex_container.js"; | ||||||
| import searchService from "../services/search.js"; |  | ||||||
| import OpenNoteButtonWidget from "./buttons/open_note_button_widget.js"; | import OpenNoteButtonWidget from "./buttons/open_note_button_widget.js"; | ||||||
| import BookmarkFolderWidget from "./buttons/bookmark_folder.js"; | import BookmarkFolderWidget from "./buttons/bookmark_folder.js"; | ||||||
|  | import froca from "../services/froca.js"; | ||||||
| 
 | 
 | ||||||
| export default class BookmarkButtons extends FlexContainer { | export default class BookmarkButtons extends FlexContainer { | ||||||
|     constructor() { |     constructor() { | ||||||
| @ -11,13 +11,13 @@ export default class BookmarkButtons extends FlexContainer { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async refresh() { |     async refresh() { | ||||||
|         const bookmarkedNotes = await searchService.searchForNotes("#bookmarked or #bookmarkFolder"); |  | ||||||
| 
 |  | ||||||
|         this.$widget.empty(); |         this.$widget.empty(); | ||||||
|         this.children = []; |         this.children = []; | ||||||
|         this.noteIds = []; |         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); |             this.noteIds.push(note.noteId); | ||||||
| 
 | 
 | ||||||
|             const buttonWidget = note.hasLabel("bookmarkFolder") |             const buttonWidget = note.hasLabel("bookmarkFolder") | ||||||
| @ -37,11 +37,7 @@ export default class BookmarkButtons extends FlexContainer { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     entitiesReloadedEvent({loadResults}) { |     entitiesReloadedEvent({loadResults}) { | ||||||
|         if (loadResults.getAttributes().find(attr => attr.type === 'label' && ['bookmarked', 'bookmarkFolder'].includes(attr.name))) { |         if (loadResults.getBranches().find(branch => branch.parentNoteId === 'lb_bookmarks')) { | ||||||
|             this.refresh(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (loadResults.getNoteIds().find(noteId => this.noteIds.includes(noteId))) { |  | ||||||
|             this.refresh(); |             this.refresh(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,7 +1,14 @@ | |||||||
| import attributeService from "../services/attributes.js"; |  | ||||||
| import SwitchWidget from "./switch.js"; | import SwitchWidget from "./switch.js"; | ||||||
|  | import server from "../services/server.js"; | ||||||
|  | import toastService from "../services/toast.js"; | ||||||
| 
 | 
 | ||||||
| export default class BookmarkSwitchWidget extends SwitchWidget { | 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() { |     doRender() { | ||||||
|         super.doRender(); |         super.doRender(); | ||||||
| 
 | 
 | ||||||
| @ -12,32 +19,24 @@ export default class BookmarkSwitchWidget extends SwitchWidget { | |||||||
|         this.$switchOffButton.attr("title", "Remove bookmark"); |         this.$switchOffButton.attr("title", "Remove bookmark"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async switchOff() { |     async toggle(state) { | ||||||
|         for (const label of this.note.getLabels('bookmarked')) { |         const resp = await server.put(`notes/${this.noteId}/toggle-in-parent/lb_bookmarks/` + !!state); | ||||||
|             await attributeService.removeAttributeById(this.noteId, label.attributeId); | 
 | ||||||
|  |         if (!resp.success) { | ||||||
|  |             toastService.showError(resp.message); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     switchOn() { |  | ||||||
|         return attributeService.setLabel(this.noteId, 'bookmarked'); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     refreshWithNote(note) { |     refreshWithNote(note) { | ||||||
|         const isBookmarked = note.hasLabel('bookmarked'); |         const isBookmarked = !!note.getParentBranches().find(b => b.parentNoteId === 'lb_bookmarks'); | ||||||
| 
 | 
 | ||||||
|         this.$switchOn.toggle(!isBookmarked); |         this.$switchOn.toggle(!isBookmarked); | ||||||
|         this.$switchOff.toggle(isBookmarked); |         this.$switchOff.toggle(isBookmarked); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     entitiesReloadedEvent({loadResults}) { |     entitiesReloadedEvent({loadResults}) { | ||||||
|         for (const attr of loadResults.getAttributes()) { |         if (loadResults.getBranches().find(b => b.noteId === this.noteId)) { | ||||||
|             if (attr.type === 'label' |             this.refresh(); | ||||||
|                 && attr.name === 'bookmarked' |  | ||||||
|                 && attributeService.isAffecting(attr, this.note)) { |  | ||||||
| 
 |  | ||||||
|                 this.refresh(); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -740,12 +740,14 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|             extraClasses.push("shared"); |             extraClasses.push("shared"); | ||||||
|         } |         } | ||||||
|         else if (note.getParentNoteIds().length > 1) { |         else if (note.getParentNoteIds().length > 1) { | ||||||
|             const notSearchParents = note.getParentNoteIds() |             const realClones = note.getParentNoteIds() | ||||||
|                 .map(noteId => froca.notes[noteId]) |                 .map(noteId => froca.notes[noteId]) | ||||||
|                 .filter(note => !!note) |                 .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"); |                 extraClasses.push("multiple-parents"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -7,7 +7,8 @@ import dialogService from "../services/dialog.js"; | |||||||
| 
 | 
 | ||||||
| export default class SharedSwitchWidget extends SwitchWidget { | export default class SharedSwitchWidget extends SwitchWidget { | ||||||
|     isEnabled() { |     isEnabled() { | ||||||
|         return super.isEnabled() && this.noteId !== 'root' && this.noteId !== 'share'; |         return super.isEnabled() | ||||||
|  |             && !['root', 'share', 'hidden'].includes(this.noteId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     doRender() { |     doRender() { | ||||||
|  | |||||||
| @ -101,18 +101,26 @@ export default class SwitchWidget extends NoteContextAwareWidget { | |||||||
|         this.$switchOnName = this.$widget.find(".switch-on-name"); |         this.$switchOnName = this.$widget.find(".switch-on-name"); | ||||||
|         this.$switchOnButton = this.$widget.find(".switch-on-button"); |         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.$switchOff = this.$widget.find(".switch-off"); | ||||||
|         this.$switchOffName = this.$widget.find(".switch-off-name"); |         this.$switchOffName = this.$widget.find(".switch-off-name"); | ||||||
|         this.$switchOffButton = this.$widget.find(".switch-off-button"); |         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"); |         this.$helpButton = this.$widget.find(".switch-help-button"); | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     toggle(state) { | ||||||
|  |         if (state) { | ||||||
|  |             this.switchOn(); | ||||||
|  |         } else { | ||||||
|  |             this.switchOff(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     switchOff() {} |     switchOff() {} | ||||||
|     switchOn() {} |     switchOn() {} | ||||||
| } | } | ||||||
|  | |||||||
| @ -22,8 +22,15 @@ function cloneNoteAfter(req) { | |||||||
|     return cloningService.cloneNoteAfter(noteId, afterBranchId); |     return cloningService.cloneNoteAfter(noteId, afterBranchId); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function toggleNoteInParent(req) { | ||||||
|  |     const {noteId, parentNoteId, present} = req.params; | ||||||
|  | 
 | ||||||
|  |     return cloningService.toggleNoteInParent(present === 'true', noteId, parentNoteId); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToNote, | ||||||
|     cloneNoteAfter |     cloneNoteAfter, | ||||||
|  |     toggleNoteInParent | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -281,6 +281,7 @@ function register(app) { | |||||||
|     apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate); |     apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate); | ||||||
| 
 | 
 | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch); |     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-to-note/:parentNoteId', cloningApiRoute.cloneNoteToNote); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); |     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -66,8 +66,10 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) { | function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) { | ||||||
|     if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { |     if (isNoteDeleted(noteId)) { | ||||||
|         return { success: false, message: 'Note is deleted.' }; |         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); |     const parentNote = becca.getNote(parentNoteId); | ||||||
| @ -89,7 +91,7 @@ function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) { | |||||||
|         isExpanded: 0 |         isExpanded: 0 | ||||||
|     }).save(); |     }).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 }; |     return { success: true }; | ||||||
| } | } | ||||||
| @ -99,22 +101,27 @@ function ensureNoteIsAbsentFromParent(noteId, parentNoteId) { | |||||||
|     const branch = becca.getBranch(branchId); |     const branch = becca.getBranch(branchId); | ||||||
| 
 | 
 | ||||||
|     if (branch) { |     if (branch) { | ||||||
|         if (branch.getNote().getParentBranches().length <= 1) { |         if (!branch.isWeak && branch.getNote().getStrongParentBranches().length <= 1) { | ||||||
|             throw new Error(`Cannot remove branch ${branch.branchId} between child ${noteId} and parent ${parentNoteId} because this would delete the note as well.`); |             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(); |         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) { | function toggleNoteInParent(present, noteId, parentNoteId, prefix) { | ||||||
|     if (present) { |     if (present) { | ||||||
|         ensureNoteIsPresentInParent(noteId, parentNoteId, prefix); |         return ensureNoteIsPresentInParent(noteId, parentNoteId, prefix); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         ensureNoteIsAbsentFromParent(noteId, parentNoteId); |         return ensureNoteIsAbsentFromParent(noteId, parentNoteId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -160,7 +167,7 @@ function cloneNoteAfter(noteId, afterBranchId) { | |||||||
|         isExpanded: 0 |         isExpanded: 0 | ||||||
|     }).save(); |     }).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 }; |     return { success: true, branchId: branch.branchId }; | ||||||
| } | } | ||||||
| @ -168,7 +175,7 @@ function cloneNoteAfter(noteId, afterBranchId) { | |||||||
| function isNoteDeleted(noteId) { | function isNoteDeleted(noteId) { | ||||||
|     const note = becca.getNote(noteId); |     const note = becca.getNote(noteId); | ||||||
| 
 | 
 | ||||||
|     return note.isDeleted; |     return !note || note.isDeleted; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| module.exports = { | 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 { |         return { | ||||||
|             success: false, |             success: false, | ||||||
|             message: 'Launcher note cannot have any children.' |             message: 'Launcher note cannot have any children.' | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam