bookmarks use cloning

This commit is contained in:
zadam 2022-12-04 13:16:05 +01:00
parent cd60ad4267
commit 27ce273d29
12 changed files with 832 additions and 200 deletions

894
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

View File

@ -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

View File

@ -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();
}

View File

@ -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();
}
}
}

View File

@ -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");
}
}

View File

@ -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() {

View File

@ -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() {}
}

View File

@ -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
};

View File

@ -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);

View File

@ -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 = {

View File

@ -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.'