chore(calendar): create note with attributes atomically

This commit is contained in:
Elian Doran
2026-01-23 12:11:06 +02:00
parent 2eae8bbb64
commit 81c85d712e
3 changed files with 85 additions and 60 deletions

View File

@@ -1,4 +1,4 @@
import { CreateChildrenResponse } from "@triliumnext/commons";
import { AttributeRow, CreateChildrenResponse } from "@triliumnext/commons";
import FNote from "../../../entities/fnote";
import { setAttribute, setLabel } from "../../../services/attributes";
@@ -21,24 +21,41 @@ interface ChangeEventOpts {
}
export async function newEvent(parentNote: FNote, { title, startDate, endDate, startTime, endTime, componentId }: NewEventOpts) {
// Create the note.
const { note } = await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
title,
content: "",
type: "text"
}, componentId);
// Set the attributes.
setLabel(note.noteId, "startDate", startDate, false, componentId);
const attributes: Omit<AttributeRow, "noteId" | "attributeId">[] = [];
attributes.push({
type: "label",
name: "startDate",
value: startDate
});
if (endDate) {
setLabel(note.noteId, "endDate", endDate, false, componentId);
attributes.push({
type: "label",
name: "endDate",
value: endDate
});
}
if (startTime) {
setLabel(note.noteId, "startTime", startTime, false, componentId);
attributes.push({
type: "label",
name: "startTime",
value: startTime
});
}
if (endTime) {
setLabel(note.noteId, "endTime", endTime, false, componentId);
attributes.push({
type: "label",
name: "endTime",
value: endTime
});
}
// Create the note.
await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
title,
content: "",
type: "text",
attributes
}, componentId);
}
export async function changeEvent(note: FNote, { startDate, endDate, startTime, endTime }: ChangeEventOpts) {

View File

@@ -1,4 +1,4 @@
import type { NoteType } from "@triliumnext/commons";
import type { AttributeRow, NoteType } from "@triliumnext/commons";
export interface NoteParams {
/** optionally can force specific noteId */
@@ -24,4 +24,6 @@ export interface NoteParams {
utcDateCreated?: string;
ignoreForbiddenParents?: boolean;
target?: "into";
/** Attributes to be set on the note. These are set atomically on note creation, so entity changes are not sent for attributes defined here. */
attributes?: Omit<AttributeRow, "noteId" | "attributeId">[];
}

View File

@@ -1,33 +1,34 @@
import sql from "./sql.js";
import optionService from "./options.js";
import type { AttachmentRow, AttributeRow, BranchRow, NoteRow } from "@triliumnext/commons";
import { dayjs } from "@triliumnext/commons";
import fs from "fs";
import html2plaintext from "html2plaintext";
import { t } from "i18next";
import path from "path";
import url from "url";
import becca from "../becca/becca.js";
import BAttachment from "../becca/entities/battachment.js";
import BAttribute from "../becca/entities/battribute.js";
import BBranch from "../becca/entities/bbranch.js";
import BNote from "../becca/entities/bnote.js";
import ValidationError from "../errors/validation_error.js";
import cls from "../services/cls.js";
import log from "../services/log.js";
import protectedSessionService from "../services/protected_session.js";
import { newEntityId, quoteRegex, toMap,unescapeHtml } from "../services/utils.js";
import dateUtils from "./date_utils.js";
import entityChangesService from "./entity_changes.js";
import eventService from "./events.js";
import cls from "../services/cls.js";
import protectedSessionService from "../services/protected_session.js";
import log from "../services/log.js";
import { newEntityId, unescapeHtml, quoteRegex, toMap } from "../services/utils.js";
import revisionService from "./revisions.js";
import request from "./request.js";
import path from "path";
import url from "url";
import becca from "../becca/becca.js";
import BBranch from "../becca/entities/bbranch.js";
import BNote from "../becca/entities/bnote.js";
import BAttribute from "../becca/entities/battribute.js";
import BAttachment from "../becca/entities/battachment.js";
import { dayjs } from "@triliumnext/commons";
import htmlSanitizer from "./html_sanitizer.js";
import ValidationError from "../errors/validation_error.js";
import noteTypesService from "./note_types.js";
import fs from "fs";
import ws from "./ws.js";
import html2plaintext from "html2plaintext";
import type { AttachmentRow, AttributeRow, BranchRow, NoteRow } from "@triliumnext/commons";
import type TaskContext from "./task_context.js";
import type { NoteParams } from "./note-interface.js";
import imageService from "./image.js";
import { t } from "i18next";
import noteTypesService from "./note_types.js";
import type { NoteParams } from "./note-interface.js";
import optionService from "./options.js";
import request from "./request.js";
import revisionService from "./revisions.js";
import sql from "./sql.js";
import type TaskContext from "./task_context.js";
import ws from "./ws.js";
interface FoundLink {
name: "imageLink" | "internalLink" | "includeNoteLink" | "relationMapLink";
@@ -47,14 +48,13 @@ function getNewNotePosition(parentNote: BNote) {
.reduce((min, note) => Math.min(min, note?.notePosition || 0), 0);
return minNotePos - 10;
} else {
const maxNotePos = parentNote
.getChildBranches()
.filter((branch) => branch?.noteId !== "_hidden") // has "always last" note position
.reduce((max, note) => Math.max(max, note?.notePosition || 0), 0);
return maxNotePos + 10;
}
const maxNotePos = parentNote
.getChildBranches()
.filter((branch) => branch?.noteId !== "_hidden") // has "always last" note position
.reduce((max, note) => Math.max(max, note?.notePosition || 0), 0);
return maxNotePos + 10;
}
function triggerNoteTitleChanged(note: BNote) {
@@ -88,7 +88,7 @@ function copyChildAttributes(parentNote: BNote, childNote: BNote) {
new BAttribute({
noteId: childNote.noteId,
type: attr.type,
name: name,
name,
value: attr.value,
position: attr.position,
isInheritable: attr.isInheritable
@@ -222,6 +222,14 @@ function createNewNote(params: NoteParams): {
utcDateCreated: params.utcDateCreated
}).save();
// Create attributes atomically.
for (const attribute of params.attributes || []) {
new BAttribute({
...attribute,
noteId: note.noteId
}).save();
}
note.setContent(params.content);
branch = new BBranch({
@@ -260,7 +268,7 @@ function createNewNote(params: NoteParams): {
eventService.emit(eventService.ENTITY_CHANGED, { entityName: "blobs", entity: note });
eventService.emit(eventService.ENTITY_CREATED, { entityName: "branches", entity: branch });
eventService.emit(eventService.ENTITY_CHANGED, { entityName: "branches", entity: branch });
eventService.emit(eventService.CHILD_NOTE_CREATED, { childNote: note, parentNote: parentNote });
eventService.emit(eventService.CHILD_NOTE_CREATED, { childNote: note, parentNote });
log.info(`Created new note '${note.noteId}', branch '${branch.branchId}' of type '${note.type}', mime '${note.mime}'`);
@@ -308,9 +316,8 @@ function createNewNoteWithTarget(target: "into" | "after" | "before", targetBran
entityChangesService.putNoteReorderingEntityChange(params.parentNoteId);
return retObject;
} else {
throw new Error(`Unknown target '${target}'`);
}
throw new Error(`Unknown target '${target}'`);
}
function protectNoteRecursively(note: BNote, protect: boolean, includingSubTree: boolean, taskContext: TaskContext<"protectNotes">) {
@@ -488,7 +495,7 @@ function findRelationMapLinks(content: string, foundLinks: FoundLink[]) {
});
}
} catch (e: any) {
log.error("Could not scan for relation map links: " + e.message);
log.error(`Could not scan for relation map links: ${e.message}`);
}
}
@@ -656,8 +663,8 @@ function saveAttachments(note: BNote, content: string) {
const attachment = note.saveAttachment({
role: "file",
mime: mime,
title: title,
mime,
title,
content: buffer
});
@@ -953,7 +960,7 @@ function duplicateSubtree(origNoteId: string, newParentNoteId: string) {
const duplicateNoteSuffix = t("notes.duplicate-note-suffix");
if (!res.note.title.endsWith(duplicateNoteSuffix) && !res.note.title.startsWith(duplicateNoteSuffix)) {
res.note.title = t("notes.duplicate-note-title", { noteTitle: res.note.title, duplicateNoteSuffix: duplicateNoteSuffix });
res.note.title = t("notes.duplicate-note-title", { noteTitle: res.note.title, duplicateNoteSuffix });
}
res.note.save();
@@ -1050,13 +1057,12 @@ function duplicateSubtreeInner(origNote: BNote, origBranch: BBranch | null | und
note: existingNote,
branch: createDuplicatedBranch()
};
} else {
return {
// order here is important, note needs to be created first to not mess up the becca
note: createDuplicatedNote(),
branch: createDuplicatedBranch()
};
}
return {
// order here is important, note needs to be created first to not mess up the becca
note: createDuplicatedNote(),
branch: createDuplicatedBranch()
};
}
function getNoteIdMapping(origNote: BNote) {