mirror of
https://github.com/zadam/trilium.git
synced 2026-01-15 11:04:30 +01:00
chore(core): integrate branches service and route
This commit is contained in:
parent
f9731d9cfc
commit
0c52b56e02
@ -1,3 +1,4 @@
|
||||
import { routes } from "@triliumnext/core";
|
||||
import { createPartialContentHandler } from "@triliumnext/express-partial-content";
|
||||
import express from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
@ -21,7 +22,6 @@ import appInfoRoute from "./api/app_info.js";
|
||||
import attributesRoute from "./api/attributes.js";
|
||||
import autocompleteApiRoute from "./api/autocomplete.js";
|
||||
import backendLogRoute from "./api/backend_log.js";
|
||||
import branchesApiRoute from "./api/branches.js";
|
||||
import bulkActionRoute from "./api/bulk_action.js";
|
||||
import clipperRoute from "./api/clipper.js";
|
||||
import cloningApiRoute from "./api/cloning.js";
|
||||
@ -62,7 +62,6 @@ import loginRoute from "./login.js";
|
||||
import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js";
|
||||
// page routes
|
||||
import setupRoute from "./setup.js";
|
||||
import { routes } from "@triliumnext/core";
|
||||
|
||||
const GET = "get",
|
||||
PST = "post",
|
||||
@ -124,15 +123,6 @@ function register(app: express.Application) {
|
||||
apiRoute(PST, "/api/notes/:noteId/save-to-tmp-dir", filesRoute.saveNoteToTmpDir);
|
||||
apiRoute(PST, "/api/notes/:noteId/upload-modified-file", filesRoute.uploadModifiedFileToNote);
|
||||
|
||||
apiRoute(PUT, "/api/branches/:branchId/move-to/:parentBranchId", branchesApiRoute.moveBranchToParent);
|
||||
apiRoute(PUT, "/api/branches/:branchId/move-before/:beforeBranchId", branchesApiRoute.moveBranchBeforeNote);
|
||||
apiRoute(PUT, "/api/branches/:branchId/move-after/:afterBranchId", branchesApiRoute.moveBranchAfterNote);
|
||||
apiRoute(PUT, "/api/branches/:branchId/expanded/:expanded", branchesApiRoute.setExpanded);
|
||||
apiRoute(PUT, "/api/branches/:branchId/expanded-subtree/:expanded", branchesApiRoute.setExpandedForSubtree);
|
||||
apiRoute(DEL, "/api/branches/:branchId", branchesApiRoute.deleteBranch);
|
||||
apiRoute(PUT, "/api/branches/:branchId/set-prefix", branchesApiRoute.setPrefix);
|
||||
apiRoute(PUT, "/api/branches/set-prefix-batch", branchesApiRoute.setPrefixBatch);
|
||||
|
||||
// TODO: Bring back attachment uploading
|
||||
// route(PST, "/api/notes/:noteId/attachments/upload", [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], attachmentsApiRoute.uploadAttachment, apiResultHandler);
|
||||
route(GET, "/api/attachments/:attachmentId/image/:filename", [auth.checkApiAuthOrElectron], imageRoute.returnAttachedImage);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type AttributeRow, dayjs, formatLogMessage } from "@triliumnext/commons";
|
||||
import { type AbstractBeccaEntity, Becca, NoteParams } from "@triliumnext/core";
|
||||
import { type AbstractBeccaEntity, Becca, branches as branchService, NoteParams } from "@triliumnext/core";
|
||||
import axios from "axios";
|
||||
import * as cheerio from "cheerio";
|
||||
import xml2js from "xml2js";
|
||||
@ -16,7 +16,6 @@ import appInfo from "./app_info.js";
|
||||
import attributeService from "./attributes.js";
|
||||
import type { ApiParams } from "./backend_script_api_interface.js";
|
||||
import backupService from "./backup.js";
|
||||
import branchService from "./branches.js";
|
||||
import cloningService from "./cloning.js";
|
||||
import config from "./config.js";
|
||||
import dateNoteService from "./date_notes.js";
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { ActionHandlers, BulkAction, BulkActionData } from "@triliumnext/commons";
|
||||
import { erase as eraseService } from "@triliumnext/core";
|
||||
import { branches as branchService, erase as eraseService } from "@triliumnext/core";
|
||||
|
||||
import becca from "../becca/becca.js";
|
||||
import type BNote from "../becca/entities/bnote.js";
|
||||
import branchService from "./branches.js";
|
||||
import cloningService from "./cloning.js";
|
||||
import log from "./log.js";
|
||||
import { randomString } from "./utils.js";
|
||||
|
||||
@ -87,11 +87,6 @@ export function constantTimeCompare(a: string | null | undefined, b: string | nu
|
||||
return crypto.timingSafeEqual(bufA, bufB);
|
||||
}
|
||||
|
||||
export function isEmptyOrWhitespace(str: string | null | undefined) {
|
||||
if (!str) return true;
|
||||
return str.match(/^ *$/) !== null;
|
||||
}
|
||||
|
||||
export function sanitizeSqlIdentifier(str: string) {
|
||||
return str.replace(/[^A-Za-z0-9_]/g, "");
|
||||
}
|
||||
@ -457,6 +452,8 @@ export const unescapeHtml = coreUtils.unescapeHtml;
|
||||
export const randomSecureToken = coreUtils.randomSecureToken;
|
||||
/** @deprecated */
|
||||
export const safeExtractMessageAndStackFromError = coreUtils.safeExtractMessageAndStackFromError;
|
||||
/** @deprecated */
|
||||
export const isEmptyOrWhitespace = coreUtils.isEmptyOrWhitespace;
|
||||
|
||||
export default {
|
||||
compareVersions,
|
||||
|
||||
@ -37,6 +37,7 @@ export { default as TaskContext } from "./services/task_context";
|
||||
export { default as revisions } from "./services/revisions";
|
||||
export { default as erase } from "./services/erase";
|
||||
export { default as getSharedBootstrapItems } from "./services/bootstrap_utils";
|
||||
export { default as branches } from "./services/branches";
|
||||
|
||||
// Messaging system
|
||||
export * from "./services/messaging/index";
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import { erase as eraseService, events as eventService, ValidationError } from "@triliumnext/core";
|
||||
import branchService from "../../services/branches.js";
|
||||
import eraseService from "../../services/erase.js";
|
||||
import eventService from "../../services/events.js";
|
||||
import type { Request } from "express";
|
||||
|
||||
import becca from "../../becca/becca.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
import entityChangesService from "../../services/entity_changes.js";
|
||||
import log from "../../services/log.js";
|
||||
import sql from "../../services/sql.js";
|
||||
import { getLog } from "../../services/log.js";
|
||||
import TaskContext from "../../services/task_context.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import { isEmptyOrWhitespace, randomString } from "../../services/utils/index.js";
|
||||
import { getSql } from "../../services/sql/index.js";
|
||||
import { ValidationError } from "../../errors.js";
|
||||
|
||||
/**
|
||||
* Code in this file deals with moving and cloning branches. The relationship between note and parent note is unique
|
||||
@ -45,7 +47,7 @@ function moveBranchBeforeNote(req: Request) {
|
||||
// we don't change utcDateModified, so other changes are prioritized in case of conflict
|
||||
// also we would have to sync all those modified branches otherwise hash checks would fail
|
||||
|
||||
sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition >= ? AND isDeleted = 0", [beforeBranch.parentNoteId, originalBeforeNotePosition]);
|
||||
getSql().execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition >= ? AND isDeleted = 0", [beforeBranch.parentNoteId, originalBeforeNotePosition]);
|
||||
|
||||
// also need to update becca positions
|
||||
const parentNote = becca.getNoteOrThrow(beforeBranch.parentNoteId);
|
||||
@ -71,7 +73,7 @@ function moveBranchBeforeNote(req: Request) {
|
||||
// if sorting is not needed, then still the ordering might have changed above manually
|
||||
entityChangesService.putNoteReorderingEntityChange(parentNote.noteId);
|
||||
|
||||
log.info(`Moved note ${branchToMove.noteId}, branch ${branchId} before note ${beforeBranch.noteId}, branch ${beforeBranchId}`);
|
||||
getLog().info(`Moved note ${branchToMove.noteId}, branch ${branchId} before note ${beforeBranch.noteId}, branch ${beforeBranchId}`);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
@ -92,7 +94,7 @@ function moveBranchAfterNote(req: Request) {
|
||||
|
||||
// we don't change utcDateModified, so other changes are prioritized in case of conflict
|
||||
// also we would have to sync all those modified branches otherwise hash checks would fail
|
||||
sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0", [afterNote.parentNoteId, originalAfterNotePosition]);
|
||||
getSql().execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0", [afterNote.parentNoteId, originalAfterNotePosition]);
|
||||
|
||||
// also need to update becca positions
|
||||
const parentNote = becca.getNoteOrThrow(afterNote.parentNoteId);
|
||||
@ -120,7 +122,7 @@ function moveBranchAfterNote(req: Request) {
|
||||
// if sorting is not needed, then still the ordering might have changed above manually
|
||||
entityChangesService.putNoteReorderingEntityChange(parentNote.noteId);
|
||||
|
||||
log.info(`Moved note ${branchToMove.noteId}, branch ${branchId} after note ${afterNote.noteId}, branch ${afterBranchId}`);
|
||||
getLog().info(`Moved note ${branchToMove.noteId}, branch ${branchId} after note ${afterNote.noteId}, branch ${afterBranchId}`);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
@ -130,7 +132,7 @@ function setExpanded(req: Request) {
|
||||
const expanded = parseInt(req.params.expanded);
|
||||
|
||||
if (branchId !== "none_root") {
|
||||
sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
|
||||
getSql().execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
|
||||
// we don't sync expanded label
|
||||
// also this does not trigger updates to the frontend, this would trigger too many reloads
|
||||
|
||||
@ -150,6 +152,7 @@ function setExpanded(req: Request) {
|
||||
function setExpandedForSubtree(req: Request) {
|
||||
const { branchId } = req.params;
|
||||
const expanded = parseInt(req.params.expanded);
|
||||
const sql = getSql();
|
||||
|
||||
let branchIds = sql.getColumn<string>(
|
||||
`
|
||||
@ -236,7 +239,7 @@ function deleteBranch(req: Request) {
|
||||
|
||||
const taskContext = TaskContext.getInstance(req.query.taskId as string, "deleteNotes", null);
|
||||
|
||||
const deleteId = utils.randomString(10);
|
||||
const deleteId = randomString(10);
|
||||
let noteDeleted;
|
||||
|
||||
if (eraseNotes) {
|
||||
@ -260,7 +263,7 @@ function deleteBranch(req: Request) {
|
||||
function setPrefix(req: Request) {
|
||||
const branchId = req.params.branchId;
|
||||
//TriliumNextTODO: req.body arrives as string, so req.body.prefix will be undefined – did the code below ever even work?
|
||||
const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix;
|
||||
const prefix = isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix;
|
||||
|
||||
const branch = becca.getBranchOrThrow(branchId);
|
||||
branch.prefix = prefix;
|
||||
@ -279,7 +282,7 @@ function setPrefixBatch(req: Request) {
|
||||
throw new ValidationError("prefix must be a string or null");
|
||||
}
|
||||
|
||||
const normalizedPrefix = utils.isEmptyOrWhitespace(prefix) ? null : prefix;
|
||||
const normalizedPrefix = isEmptyOrWhitespace(prefix) ? null : prefix;
|
||||
let updatedCount = 0;
|
||||
|
||||
for (const branchId of branchIds) {
|
||||
@ -289,7 +292,7 @@ function setPrefixBatch(req: Request) {
|
||||
branch.save();
|
||||
updatedCount++;
|
||||
} else {
|
||||
log.info(`Branch ${branchId} not found, skipping prefix update`);
|
||||
getLog().info(`Branch ${branchId} not found, skipping prefix update`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import attachmentsApiRoute from "./api/attachments";
|
||||
import noteMapRoute from "./api/note_map";
|
||||
import recentNotesRoute from "./api/recent_notes";
|
||||
import otherRoute from "./api/others";
|
||||
import branchesApiRoute from "./api/branches";
|
||||
import AbstractBeccaEntity from "../becca/entities/abstract_becca_entity";
|
||||
|
||||
// TODO: Deduplicate with routes.ts
|
||||
@ -53,6 +54,15 @@ export function buildSharedApiRoutes(apiRoute: any) {
|
||||
apiRoute(PUT, "/api/attachments/:attachmentId/rename", attachmentsApiRoute.renameAttachment);
|
||||
apiRoute(GET, "/api/attachments/:attachmentId/blob", attachmentsApiRoute.getAttachmentBlob);
|
||||
|
||||
apiRoute(PUT, "/api/branches/:branchId/move-to/:parentBranchId", branchesApiRoute.moveBranchToParent);
|
||||
apiRoute(PUT, "/api/branches/:branchId/move-before/:beforeBranchId", branchesApiRoute.moveBranchBeforeNote);
|
||||
apiRoute(PUT, "/api/branches/:branchId/move-after/:afterBranchId", branchesApiRoute.moveBranchAfterNote);
|
||||
apiRoute(PUT, "/api/branches/:branchId/expanded/:expanded", branchesApiRoute.setExpanded);
|
||||
apiRoute(PUT, "/api/branches/:branchId/expanded-subtree/:expanded", branchesApiRoute.setExpandedForSubtree);
|
||||
apiRoute(DEL, "/api/branches/:branchId", branchesApiRoute.deleteBranch);
|
||||
apiRoute(PUT, "/api/branches/:branchId/set-prefix", branchesApiRoute.setPrefix);
|
||||
apiRoute(PUT, "/api/branches/set-prefix-batch", branchesApiRoute.setPrefixBatch);
|
||||
|
||||
apiRoute(GET, "/api/note-map/:noteId/backlink-count", noteMapRoute.getBacklinkCount);
|
||||
|
||||
apiRoute(PST, "/api/recent-notes", recentNotesRoute.addRecentNote);
|
||||
|
||||
50
packages/trilium-core/src/services/branches.ts
Normal file
50
packages/trilium-core/src/services/branches.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import treeService from "./tree.js";
|
||||
import type BBranch from "../becca/entities/bbranch.js";
|
||||
import { getSql } from "./sql/index.js";
|
||||
|
||||
function moveBranchToNote(branchToMove: BBranch, targetParentNoteId: string) {
|
||||
if (branchToMove.parentNoteId === targetParentNoteId) {
|
||||
return { success: true }; // no-op
|
||||
}
|
||||
|
||||
const validationResult = treeService.validateParentChild(targetParentNoteId, branchToMove.noteId, branchToMove.branchId);
|
||||
|
||||
if (!validationResult.success) {
|
||||
return [200, validationResult];
|
||||
}
|
||||
|
||||
const maxNotePos = getSql().getValue<number | null>("SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [targetParentNoteId]);
|
||||
const newNotePos = !maxNotePos ? 0 : maxNotePos + 10;
|
||||
|
||||
const newBranch = branchToMove.createClone(targetParentNoteId, newNotePos);
|
||||
newBranch.save();
|
||||
|
||||
branchToMove.markAsDeleted();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
branch: newBranch
|
||||
};
|
||||
}
|
||||
|
||||
function moveBranchToBranch(branchToMove: BBranch, targetParentBranch: BBranch, branchId: string) {
|
||||
// TODO: Unused branch ID argument.
|
||||
const res = moveBranchToNote(branchToMove, targetParentBranch.noteId);
|
||||
|
||||
if (!("success" in res) || !res.success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// expanding so that the new placement of the branch is immediately visible
|
||||
if (!targetParentBranch.isExpanded) {
|
||||
targetParentBranch.isExpanded = true;
|
||||
targetParentBranch.save();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export default {
|
||||
moveBranchToBranch,
|
||||
moveBranchToNote
|
||||
};
|
||||
@ -130,3 +130,8 @@ export function randomSecureToken(bytes = 32) {
|
||||
export function safeExtractMessageAndStackFromError(err: unknown): [errMessage: string, errStack: string | undefined] {
|
||||
return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const;
|
||||
}
|
||||
|
||||
export function isEmptyOrWhitespace(str: string | null | undefined) {
|
||||
if (!str) return true;
|
||||
return str.match(/^ *$/) !== null;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user