mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +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