mirror of
https://github.com/zadam/trilium.git
synced 2026-01-23 06:54:28 +01:00
Merge branch 'TriliumNext:main' into main
This commit is contained in:
commit
64015ae2bd
@ -78,7 +78,7 @@
|
||||
"@types/reveal.js": "5.2.2",
|
||||
"@types/tabulator-tables": "6.3.1",
|
||||
"copy-webpack-plugin": "13.0.1",
|
||||
"happy-dom": "20.1.0",
|
||||
"happy-dom": "20.3.0",
|
||||
"lightningcss": "1.30.2",
|
||||
"script-loader": "0.7.2",
|
||||
"vite-plugin-static-copy": "3.1.4"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MIME_TYPES_DICT } from "@triliumnext/commons";
|
||||
import { getNoteIcon } from "@triliumnext/commons";
|
||||
|
||||
import cssClassManager from "../services/css_class_manager.js";
|
||||
import type { Froca } from "../services/froca-interface.js";
|
||||
@ -13,25 +13,6 @@ import type { AttributeType, default as FAttribute } from "./fattribute.js";
|
||||
const LABEL = "label";
|
||||
const RELATION = "relation";
|
||||
|
||||
export const NOTE_TYPE_ICONS = {
|
||||
file: "bx bx-file",
|
||||
image: "bx bx-image",
|
||||
code: "bx bx-code",
|
||||
render: "bx bx-extension",
|
||||
search: "bx bx-file-find",
|
||||
relationMap: "bx bxs-network-chart",
|
||||
book: "bx bx-book",
|
||||
noteMap: "bx bxs-network-chart",
|
||||
mermaid: "bx bx-selection",
|
||||
canvas: "bx bx-pen",
|
||||
webView: "bx bx-globe-alt",
|
||||
launcher: "bx bx-link",
|
||||
doc: "bx bxs-file-doc",
|
||||
contentWidget: "bx bxs-widget",
|
||||
mindMap: "bx bx-sitemap",
|
||||
aiChat: "bx bx-bot"
|
||||
};
|
||||
|
||||
/**
|
||||
* There are many different Note types, some of which are entirely opaque to the
|
||||
* end user. Those types should be used only for checking against, they are
|
||||
@ -582,32 +563,18 @@ export default class FNote {
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
return `tn-icon ${this.#getIconInternal()}`;
|
||||
}
|
||||
|
||||
#getIconInternal() {
|
||||
const iconClassLabels = this.getLabels("iconClass");
|
||||
const workspaceIconClass = this.getWorkspaceIconClass();
|
||||
|
||||
if (iconClassLabels && iconClassLabels.length > 0) {
|
||||
return iconClassLabels[0].value;
|
||||
} else if (workspaceIconClass) {
|
||||
return workspaceIconClass;
|
||||
} else if (this.noteId === "root") {
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (this.noteId === "_share") {
|
||||
return "bx bx-share-alt";
|
||||
} else if (this.type === "text") {
|
||||
if (this.isFolder()) {
|
||||
return "bx bx-folder";
|
||||
}
|
||||
return "bx bx-note";
|
||||
} else if (this.type === "code") {
|
||||
const correspondingMimeType = MIME_TYPES_DICT.find(m => m.mime === this.mime);
|
||||
return correspondingMimeType?.icon ?? NOTE_TYPE_ICONS.code;
|
||||
}
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
const icon = getNoteIcon({
|
||||
noteId: this.noteId,
|
||||
type: this.type,
|
||||
mime: this.mime,
|
||||
iconClass: iconClassLabels.length > 0 ? iconClassLabels[0].value : undefined,
|
||||
workspaceIconClass,
|
||||
isFolder: this.isFolder.bind(this)
|
||||
});
|
||||
return `tn-icon ${icon}`;
|
||||
}
|
||||
|
||||
getColorClass() {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import "./StatusBar.css";
|
||||
|
||||
import { Locale, NoteType } from "@triliumnext/commons";
|
||||
import { Locale, NOTE_TYPE_ICONS, NoteType } from "@triliumnext/commons";
|
||||
import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { type ComponentChildren, RefObject } from "preact";
|
||||
@ -9,7 +9,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "p
|
||||
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote, { NOTE_TYPE_ICONS } from "../../entities/fnote";
|
||||
import FNote from "../../entities/fnote";
|
||||
import attributes from "../../services/attributes";
|
||||
import { t } from "../../services/i18n";
|
||||
import { ViewScope } from "../../services/link";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:24.12.0-bullseye-slim AS builder
|
||||
FROM node:24.13.0-bullseye-slim AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:24.12.0-bullseye-slim
|
||||
FROM node:24.13.0-bullseye-slim
|
||||
# Install only runtime dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:24.12.0-alpine AS builder
|
||||
FROM node:24.13.0-alpine AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:24.12.0-alpine
|
||||
FROM node:24.13.0-alpine
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache su-exec shadow
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:24.12.0-alpine AS builder
|
||||
FROM node:24.13.0-alpine AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:24.12.0-alpine
|
||||
FROM node:24.13.0-alpine
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
FROM node:24.12.0-bullseye-slim AS builder
|
||||
FROM node:24.13.0-bullseye-slim AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:24.12.0-bullseye-slim
|
||||
FROM node:24.13.0-bullseye-slim
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
"move-note-up": "Notu bir üste taşı",
|
||||
"collapse-tree": "Tüm not ağacını daraltır",
|
||||
"collapse-subtree": "Geçerli notun alt ağacını daraltır",
|
||||
"sort-child-notes": "Alt notları sırala"
|
||||
"sort-child-notes": "Alt notları sırala",
|
||||
"creating-and-moving-notes": "Notları oluşturma ve yerlerini değiştirme",
|
||||
"create-note-into": "Aktif nota bağlı alt not oluştur"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { AttachmentRow, AttributeType, CloneResponse, NoteRow, NoteType, RevisionRow } from "@triliumnext/commons";
|
||||
import { dayjs } from "@triliumnext/commons";
|
||||
import { dayjs, getNoteIcon } from "@triliumnext/commons";
|
||||
|
||||
import cloningService from "../../services/cloning.js";
|
||||
import dateUtils from "../../services/date_utils.js";
|
||||
@ -24,26 +24,6 @@ import BRevision from "./brevision.js";
|
||||
const LABEL = "label";
|
||||
const RELATION = "relation";
|
||||
|
||||
// TODO: Deduplicate with fnote
|
||||
export const NOTE_TYPE_ICONS = {
|
||||
file: "bx bx-file",
|
||||
image: "bx bx-image",
|
||||
code: "bx bx-code",
|
||||
render: "bx bx-extension",
|
||||
search: "bx bx-file-find",
|
||||
relationMap: "bx bxs-network-chart",
|
||||
book: "bx bx-book",
|
||||
noteMap: "bx bxs-network-chart",
|
||||
mermaid: "bx bx-selection",
|
||||
canvas: "bx bx-pen",
|
||||
webView: "bx bx-globe-alt",
|
||||
launcher: "bx bx-link",
|
||||
doc: "bx bxs-file-doc",
|
||||
contentWidget: "bx bxs-widget",
|
||||
mindMap: "bx bx-sitemap",
|
||||
geoMap: "bx bx-map-alt"
|
||||
};
|
||||
|
||||
interface NotePathRecord {
|
||||
isArchived: boolean;
|
||||
isInHoistedSubTree: boolean;
|
||||
@ -1698,30 +1678,17 @@ class BNote extends AbstractBeccaEntity<BNote> {
|
||||
}
|
||||
|
||||
getIcon() {
|
||||
return `tn-icon ${this.#getIconInternal()}`;
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with fnote
|
||||
#getIconInternal() {
|
||||
const iconClassLabels = this.getLabels("iconClass");
|
||||
const icon = getNoteIcon({
|
||||
noteId: this.noteId,
|
||||
type: this.type,
|
||||
mime: this.mime,
|
||||
iconClass: iconClassLabels.length > 0 ? iconClassLabels[0].value : undefined,
|
||||
workspaceIconClass: undefined,
|
||||
isFolder: this.isFolder.bind(this)
|
||||
});
|
||||
|
||||
if (iconClassLabels && iconClassLabels.length > 0) {
|
||||
return iconClassLabels[0].value;
|
||||
} else if (this.noteId === "root") {
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (this.noteId === "_share") {
|
||||
return "bx bx-share-alt";
|
||||
} else if (this.type === "text") {
|
||||
if (this.isFolder()) {
|
||||
return "bx bx-folder";
|
||||
}
|
||||
return "bx bx-note";
|
||||
|
||||
} else if (this.type === "code" && this.mime.startsWith("text/x-sql")) {
|
||||
return "bx bx-data";
|
||||
}
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
return `tn-icon ${icon}`;
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with fnote
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
"use strict";
|
||||
import type { NoteType } from "@triliumnext/commons";
|
||||
|
||||
import type BNote from "../../becca/entities/bnote.js";
|
||||
import type TaskContext from "../task_context.js";
|
||||
|
||||
import noteService from "../../services/notes.js";
|
||||
import imageService from "../../services/image.js";
|
||||
import noteService from "../../services/notes.js";
|
||||
import { getNoteTitle, processStringOrBuffer } from "../../services/utils.js";
|
||||
import htmlSanitizer from "../html_sanitizer.js";
|
||||
import protectedSessionService from "../protected_session.js";
|
||||
import type TaskContext from "../task_context.js";
|
||||
import type { File } from "./common.js";
|
||||
import markdownService from "./markdown.js";
|
||||
import mimeService from "./mime.js";
|
||||
import { getNoteTitle, processStringOrBuffer } from "../../services/utils.js";
|
||||
import importUtils from "./utils.js";
|
||||
import htmlSanitizer from "../html_sanitizer.js";
|
||||
import type { File } from "./common.js";
|
||||
import type { NoteType } from "@triliumnext/commons";
|
||||
|
||||
function importSingleFile(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) {
|
||||
const mime = mimeService.getMime(file.originalname) || file.mimetype;
|
||||
@ -56,13 +54,14 @@ function importImage(file: File, parentNote: BNote, taskContext: TaskContext<"im
|
||||
function importFile(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) {
|
||||
const originalName = file.originalname;
|
||||
|
||||
const mime = mimeService.getMime(originalName) || file.mimetype;
|
||||
const { note } = noteService.createNewNote({
|
||||
parentNoteId: parentNote.noteId,
|
||||
title: originalName,
|
||||
title: getNoteTitle(originalName, mime === "application/pdf"),
|
||||
content: file.buffer,
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||
type: "file",
|
||||
mime: mimeService.getMime(originalName) || file.mimetype
|
||||
mime
|
||||
});
|
||||
|
||||
note.addLabel("originalFileName", originalName);
|
||||
@ -88,7 +87,7 @@ function importCodeNote(taskContext: TaskContext<"importNotes">, file: File, par
|
||||
title,
|
||||
content,
|
||||
type,
|
||||
mime: mime,
|
||||
mime,
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
|
||||
});
|
||||
|
||||
@ -106,7 +105,7 @@ function importCustomType(taskContext: TaskContext<"importNotes">, file: File, p
|
||||
title,
|
||||
content,
|
||||
type,
|
||||
mime: mime,
|
||||
mime,
|
||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
|
||||
});
|
||||
|
||||
@ -214,7 +213,7 @@ function importAttachment(taskContext: TaskContext<"importNotes">, file: File, p
|
||||
title: file.originalname,
|
||||
content: file.buffer,
|
||||
role: "file",
|
||||
mime: mime
|
||||
mime
|
||||
});
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
@ -1,26 +1,27 @@
|
||||
"use strict";
|
||||
|
||||
import BAttribute from "../../becca/entities/battribute.js";
|
||||
import { removeTextFileExtension, newEntityId, getNoteTitle, processStringOrBuffer, unescapeHtml } from "../../services/utils.js";
|
||||
import log from "../../services/log.js";
|
||||
import noteService from "../../services/notes.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import BBranch from "../../becca/entities/bbranch.js";
|
||||
|
||||
import { ALLOWED_NOTE_TYPES, type NoteType } from "@triliumnext/commons";
|
||||
import path from "path";
|
||||
import protectedSessionService from "../protected_session.js";
|
||||
import mimeService from "./mime.js";
|
||||
import treeService from "../tree.js";
|
||||
import type { Stream } from "stream";
|
||||
import yauzl from "yauzl";
|
||||
import htmlSanitizer from "../html_sanitizer.js";
|
||||
|
||||
import becca from "../../becca/becca.js";
|
||||
import BAttachment from "../../becca/entities/battachment.js";
|
||||
import markdownService from "./markdown.js";
|
||||
import type TaskContext from "../task_context.js";
|
||||
import BAttribute from "../../becca/entities/battribute.js";
|
||||
import BBranch from "../../becca/entities/bbranch.js";
|
||||
import type BNote from "../../becca/entities/bnote.js";
|
||||
import type NoteMeta from "../meta/note_meta.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import log from "../../services/log.js";
|
||||
import noteService from "../../services/notes.js";
|
||||
import { getNoteTitle, newEntityId, processStringOrBuffer, removeFileExtension, unescapeHtml } from "../../services/utils.js";
|
||||
import htmlSanitizer from "../html_sanitizer.js";
|
||||
import type AttributeMeta from "../meta/attribute_meta.js";
|
||||
import type { Stream } from "stream";
|
||||
import { ALLOWED_NOTE_TYPES, type NoteType } from "@triliumnext/commons";
|
||||
import type NoteMeta from "../meta/note_meta.js";
|
||||
import protectedSessionService from "../protected_session.js";
|
||||
import type TaskContext from "../task_context.js";
|
||||
import treeService from "../tree.js";
|
||||
import markdownService from "./markdown.js";
|
||||
import mimeService from "./mime.js";
|
||||
|
||||
interface MetaFile {
|
||||
files: NoteMeta[];
|
||||
@ -108,7 +109,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
dataFileName: ""
|
||||
};
|
||||
|
||||
let parent: NoteMeta | undefined = undefined;
|
||||
let parent: NoteMeta | undefined;
|
||||
|
||||
for (let segment of pathSegments) {
|
||||
if (!cursor?.children?.length) {
|
||||
@ -161,7 +162,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
|
||||
// in case we lack metadata, we treat e.g. "Programming.html" and "Programming" as the same note
|
||||
// (one data file, the other directory for children)
|
||||
const filePathNoExt = removeTextFileExtension(filePath);
|
||||
const filePathNoExt = removeFileExtension(filePath);
|
||||
|
||||
if (filePathNoExt in createdPaths) {
|
||||
return createdPaths[filePathNoExt];
|
||||
@ -241,10 +242,10 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
}
|
||||
|
||||
const { note } = noteService.createNewNote({
|
||||
parentNoteId: parentNoteId,
|
||||
parentNoteId,
|
||||
title: noteTitle || "",
|
||||
content: "",
|
||||
noteId: noteId,
|
||||
noteId,
|
||||
type: resolveNoteType(noteMeta?.type),
|
||||
mime: noteMeta ? noteMeta.mime : "text/html",
|
||||
prefix: noteMeta?.prefix || "",
|
||||
@ -294,12 +295,12 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
attachmentId: getNewAttachmentId(attachmentMeta.attachmentId),
|
||||
noteId: getNewNoteId(noteMeta.noteId)
|
||||
};
|
||||
} else {
|
||||
// don't check for noteMeta since it's not mandatory for notes
|
||||
return {
|
||||
noteId: getNoteId(noteMeta, absUrl)
|
||||
};
|
||||
}
|
||||
}
|
||||
// don't check for noteMeta since it's not mandatory for notes
|
||||
return {
|
||||
noteId: getNoteId(noteMeta, absUrl)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function processTextNoteContent(content: string, noteTitle: string, filePath: string, noteMeta?: NoteMeta) {
|
||||
@ -312,9 +313,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
|
||||
if (noteTitle.trim() === text.trim()) {
|
||||
return ""; // remove whole H1 tag
|
||||
} else {
|
||||
return `<h2>${text}</h2>`;
|
||||
}
|
||||
}
|
||||
return `<h2>${text}</h2>`;
|
||||
|
||||
});
|
||||
|
||||
if (taskContext.data?.safeImport) {
|
||||
@ -347,9 +348,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
return `src="api/attachments/${target.attachmentId}/image/${path.basename(url)}"`;
|
||||
} else if (target.noteId) {
|
||||
return `src="api/images/${target.noteId}/${path.basename(url)}"`;
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
|
||||
});
|
||||
|
||||
content = content.replace(/href="([^"]*)"/g, (match, url) => {
|
||||
@ -373,9 +374,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
return `href="#root/${target.noteId}?viewMode=attachments&attachmentId=${target.attachmentId}"`;
|
||||
} else if (target.noteId) {
|
||||
return `href="#root/${target.noteId}"`;
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
|
||||
});
|
||||
|
||||
if (noteMeta) {
|
||||
@ -525,9 +526,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
}
|
||||
|
||||
({ note } = noteService.createNewNote({
|
||||
parentNoteId: parentNoteId,
|
||||
parentNoteId,
|
||||
title: noteTitle || "",
|
||||
content: content,
|
||||
content,
|
||||
noteId,
|
||||
type,
|
||||
mime,
|
||||
@ -536,7 +537,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
|
||||
// root notePosition should be ignored since it relates to the original document
|
||||
// now import root should be placed after existing notes into new parent
|
||||
notePosition: noteMeta && firstNote ? noteMeta.notePosition : undefined,
|
||||
isProtected: isProtected
|
||||
isProtected
|
||||
}));
|
||||
|
||||
createdNoteIds.add(note.noteId);
|
||||
@ -648,7 +649,7 @@ function streamToBuffer(stream: Stream): Promise<Buffer> {
|
||||
|
||||
export function readContent(zipfile: yauzl.ZipFile, entry: yauzl.Entry): Promise<Buffer> {
|
||||
return new Promise((res, rej) => {
|
||||
zipfile.openReadStream(entry, function (err, readStream) {
|
||||
zipfile.openReadStream(entry, (err, readStream) => {
|
||||
if (err) rej(err);
|
||||
if (!readStream) throw new Error("Unable to read content.");
|
||||
|
||||
@ -659,7 +660,7 @@ export function readContent(zipfile: yauzl.ZipFile, entry: yauzl.Entry): Promise
|
||||
|
||||
export function readZipFile(buffer: Buffer, processEntryCallback: (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => Promise<void>) {
|
||||
return new Promise<void>((res, rej) => {
|
||||
yauzl.fromBuffer(buffer, { lazyEntries: true, validateEntrySizes: false }, function (err, zipfile) {
|
||||
yauzl.fromBuffer(buffer, { lazyEntries: true, validateEntrySizes: false }, (err, zipfile) => {
|
||||
if (err) rej(err);
|
||||
if (!zipfile) throw new Error("Unable to read zip file.");
|
||||
|
||||
@ -691,9 +692,9 @@ function resolveNoteType(type: string | undefined): NoteType {
|
||||
|
||||
if (type && (ALLOWED_NOTE_TYPES as readonly string[]).includes(type)) {
|
||||
return type as NoteType;
|
||||
} else {
|
||||
return "text";
|
||||
}
|
||||
}
|
||||
return "text";
|
||||
|
||||
}
|
||||
|
||||
export function removeTriliumTags(content: string) {
|
||||
@ -702,7 +703,7 @@ export function removeTriliumTags(content: string) {
|
||||
"<title data-trilium-title>([^<]*)<\/title>"
|
||||
];
|
||||
for (const tag of tagsToRemove) {
|
||||
let re = new RegExp(tag, "gi");
|
||||
const re = new RegExp(tag, "gi");
|
||||
content = content.replace(re, "");
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { describe, expect,it } from "vitest";
|
||||
|
||||
import utils from "./utils.js";
|
||||
|
||||
type TestCase<T extends (...args: any) => any> = [desc: string, fnParams: Parameters<T>, expected: ReturnType<T>];
|
||||
@ -120,7 +121,7 @@ describe("#toObject", () => {
|
||||
{ testPropA: "keyA", testPropB: "valueA" },
|
||||
{ testPropA: "keyB", testPropB: "valueB" }
|
||||
];
|
||||
const fn: TestListFn = (testListEntry: TestListEntry) => [ testListEntry.testPropA + "_fn", testListEntry.testPropB + "_fn" ];
|
||||
const fn: TestListFn = (testListEntry: TestListEntry) => [ `${testListEntry.testPropA }_fn`, `${testListEntry.testPropB }_fn` ];
|
||||
|
||||
const result = utils.toObject(testList, fn);
|
||||
expect(result).toStrictEqual({
|
||||
@ -240,8 +241,8 @@ describe.todo("#quoteRegex", () => {});
|
||||
|
||||
describe.todo("#replaceAll", () => {});
|
||||
|
||||
describe("#removeTextFileExtension", () => {
|
||||
const testCases: TestCase<typeof utils.removeTextFileExtension>[] = [
|
||||
describe("#removeFileExtension", () => {
|
||||
const testCases: TestCase<typeof utils.removeFileExtension>[] = [
|
||||
[ "w/ 'test.md' it should strip '.md'", [ "test.md" ], "test" ],
|
||||
[ "w/ 'test.markdown' it should strip '.markdown'", [ "test.markdown" ], "test" ],
|
||||
[ "w/ 'test.html' it should strip '.html'", [ "test.html" ], "test" ],
|
||||
@ -252,7 +253,7 @@ describe("#removeTextFileExtension", () => {
|
||||
testCases.forEach((testCase) => {
|
||||
const [ desc, fnParams, expected ] = testCase;
|
||||
it(desc, () => {
|
||||
const result = utils.removeTextFileExtension(...fnParams);
|
||||
const result = utils.removeFileExtension(...fnParams);
|
||||
expect(result).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
import chardet from "chardet";
|
||||
import stripBom from "strip-bom";
|
||||
import crypto from "crypto";
|
||||
import { generator } from "rand-token";
|
||||
import unescape from "unescape";
|
||||
import escape from "escape-html";
|
||||
import sanitize from "sanitize-filename";
|
||||
import mimeTypes from "mime-types";
|
||||
import path from "path";
|
||||
import type NoteMeta from "./meta/note_meta.js";
|
||||
import log from "./log.js";
|
||||
import { t } from "i18next";
|
||||
import mimeTypes from "mime-types";
|
||||
import { release as osRelease } from "os";
|
||||
import path from "path";
|
||||
import { generator } from "rand-token";
|
||||
import sanitize from "sanitize-filename";
|
||||
import stripBom from "strip-bom";
|
||||
import unescape from "unescape";
|
||||
|
||||
import log from "./log.js";
|
||||
import type NoteMeta from "./meta/note_meta.js";
|
||||
|
||||
const osVersion = osRelease().split('.').map(Number);
|
||||
|
||||
@ -204,7 +205,7 @@ export function formatDownloadTitle(fileName: string, type: string | null, mime:
|
||||
return `${fileNameBase}${getExtension()}`;
|
||||
}
|
||||
|
||||
export function removeTextFileExtension(filePath: string) {
|
||||
export function removeFileExtension(filePath: string) {
|
||||
const extension = path.extname(filePath).toLowerCase();
|
||||
|
||||
switch (extension) {
|
||||
@ -216,6 +217,7 @@ export function removeTextFileExtension(filePath: string) {
|
||||
case ".excalidraw":
|
||||
case ".mermaid":
|
||||
case ".mmd":
|
||||
case ".pdf":
|
||||
return filePath.substring(0, filePath.length - extension.length);
|
||||
default:
|
||||
return filePath;
|
||||
@ -226,7 +228,7 @@ export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boo
|
||||
const trimmedNoteMeta = noteMeta?.title?.trim();
|
||||
if (trimmedNoteMeta) return trimmedNoteMeta;
|
||||
|
||||
const basename = path.basename(removeTextFileExtension(filePath));
|
||||
const basename = path.basename(removeFileExtension(filePath));
|
||||
return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename;
|
||||
}
|
||||
|
||||
@ -467,28 +469,28 @@ export function normalizeCustomHandlerPattern(pattern: string | null | undefined
|
||||
|
||||
// If already ends with slash, create both versions
|
||||
if (basePattern.endsWith('/')) {
|
||||
const withoutSlash = basePattern.slice(0, -1) + '$';
|
||||
const withoutSlash = `${basePattern.slice(0, -1) }$`;
|
||||
const withSlash = pattern;
|
||||
return [withoutSlash, withSlash];
|
||||
} else {
|
||||
// Add optional trailing slash
|
||||
const withSlash = basePattern + '/?$';
|
||||
return [withSlash];
|
||||
}
|
||||
// Add optional trailing slash
|
||||
const withSlash = `${basePattern }/?$`;
|
||||
return [withSlash];
|
||||
|
||||
}
|
||||
|
||||
// For patterns without $, add both versions
|
||||
if (pattern.endsWith('/')) {
|
||||
const withoutSlash = pattern.slice(0, -1);
|
||||
return [withoutSlash, pattern];
|
||||
} else {
|
||||
const withSlash = pattern + '/';
|
||||
return [pattern, withSlash];
|
||||
}
|
||||
const withSlash = `${pattern }/`;
|
||||
return [pattern, withSlash];
|
||||
|
||||
}
|
||||
|
||||
export function formatUtcTime(time: string) {
|
||||
return time.replace("T", " ").substring(0, 19)
|
||||
return time.replace("T", " ").substring(0, 19);
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with client utils
|
||||
@ -501,9 +503,9 @@ export function formatSize(size: number | null | undefined) {
|
||||
|
||||
if (size < 1024) {
|
||||
return `${size} KiB`;
|
||||
} else {
|
||||
return `${Math.round(size / 102.4) / 10} MiB`;
|
||||
}
|
||||
return `${Math.round(size / 102.4) / 10} MiB`;
|
||||
|
||||
}
|
||||
|
||||
function slugify(text: string) {
|
||||
@ -544,7 +546,7 @@ export default {
|
||||
randomSecureToken,
|
||||
randomString,
|
||||
removeDiacritic,
|
||||
removeTextFileExtension,
|
||||
removeFileExtension,
|
||||
replaceAll,
|
||||
safeExtractMessageAndStackFromError,
|
||||
sanitizeSqlIdentifier,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { getNoteIcon, NoteType } from "@triliumnext/commons";
|
||||
import escape from "escape-html";
|
||||
|
||||
import { NOTE_TYPE_ICONS } from "../../../becca/entities/bnote.js";
|
||||
import type { Blob } from "../../../services/blob-interface.js";
|
||||
import utils from "../../../services/utils.js";
|
||||
import sql from "../../sql.js";
|
||||
@ -19,7 +19,7 @@ const isCredentials = (attr: SAttribute) => attr.type === "label" && attr.name =
|
||||
class SNote extends AbstractShacaEntity {
|
||||
noteId: string;
|
||||
title: string;
|
||||
type: string;
|
||||
type: NoteType;
|
||||
mime: string;
|
||||
private blobId: string;
|
||||
utcDateModified: string;
|
||||
@ -38,7 +38,7 @@ class SNote extends AbstractShacaEntity {
|
||||
|
||||
this.noteId = noteId;
|
||||
this.title = isProtected ? "[protected]" : title;
|
||||
this.type = type;
|
||||
this.type = type as NoteType;
|
||||
this.mime = mime;
|
||||
this.blobId = blobId;
|
||||
this.utcDateModified = utcDateModified; // used for caching of images
|
||||
@ -528,33 +528,22 @@ class SNote extends AbstractShacaEntity {
|
||||
}
|
||||
|
||||
getIcon(filterByPrefix: string[] = []) {
|
||||
return `tn-icon ${this.#getIconInternal(filterByPrefix)}`;
|
||||
}
|
||||
|
||||
#getIconInternal(filterByPrefix: string[] = []) {
|
||||
const iconClassLabels = this.getLabels("iconClass").filter(label => {
|
||||
if (filterByPrefix.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return filterByPrefix.some(prefix => label.value.startsWith(prefix));
|
||||
});
|
||||
const icon = getNoteIcon({
|
||||
noteId: this.noteId,
|
||||
type: this.type,
|
||||
mime: this.mime,
|
||||
workspaceIconClass: undefined,
|
||||
iconClass: iconClassLabels.length > 0 ? iconClassLabels[0].value : undefined,
|
||||
isFolder: this.isFolder.bind(this)
|
||||
});
|
||||
|
||||
if (iconClassLabels && iconClassLabels.length > 0) {
|
||||
return iconClassLabels[0].value;
|
||||
} else if (this.noteId === "root") {
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (this.noteId === "_share") {
|
||||
return "bx bx-share-alt";
|
||||
} else if (this.type === "text") {
|
||||
if (this.isFolder()) {
|
||||
return "bx bx-folder";
|
||||
}
|
||||
return "bx bx-note";
|
||||
} else if (this.type === "code" && this.mime.startsWith("text/x-sql")) {
|
||||
return "bx bx-data";
|
||||
}
|
||||
return NOTE_TYPE_ICONS[this.type];
|
||||
return `tn-icon ${icon}`;
|
||||
}
|
||||
|
||||
isFolder() {
|
||||
|
||||
@ -13,5 +13,10 @@
|
||||
"github": "GitHub",
|
||||
"dockerhub": "Docker Hub",
|
||||
"screenshot_alt": "Trilium Notes masaüstü uygulamasının ekran görüntüsü"
|
||||
},
|
||||
"organization_benefits": {
|
||||
"title": "Organizasyon",
|
||||
"note_structure_title": "Not yapısı",
|
||||
"note_structure_description": "Notlar hiyerarşik olarak düzenlenebilir. Her not 'alt notlar' içerebildiği için klasörlere ihtiyaç duyulmaz. Tek bir not, hiyerarşinin birden fazla noktasına eklenebilir."
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"@types/express": "5.0.6",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "24.10.8",
|
||||
"@types/node": "24.10.9",
|
||||
"@vitest/browser-webdriverio": "4.0.17",
|
||||
"@vitest/coverage-v8": "4.0.17",
|
||||
"@vitest/ui": "4.0.17",
|
||||
@ -63,7 +63,7 @@
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-playwright": "2.5.0",
|
||||
"eslint-plugin-simple-import-sort": "12.1.1",
|
||||
"happy-dom": "20.1.0",
|
||||
"happy-dom": "20.3.0",
|
||||
"http-server": "14.1.1",
|
||||
"jiti": "2.6.1",
|
||||
"js-yaml": "4.1.1",
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.26.1",
|
||||
"stylelint": "17.0.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.26.1",
|
||||
"stylelint": "17.0.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.26.1",
|
||||
"stylelint": "17.0.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.26.1",
|
||||
"stylelint": "17.0.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
"eslint-config-ckeditor5": ">=9.1.0",
|
||||
"http-server": "14.1.1",
|
||||
"lint-staged": "16.2.7",
|
||||
"stylelint": "16.26.1",
|
||||
"stylelint": "17.0.0",
|
||||
"stylelint-config-ckeditor5": ">=9.1.0",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "5.9.3",
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
"ckeditor5-premium-features": "47.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@smithy/middleware-retry": "4.4.22",
|
||||
"@smithy/middleware-retry": "4.4.23",
|
||||
"@types/jquery": "3.5.33"
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
"@codemirror/lang-xml": "6.1.0",
|
||||
"@codemirror/legacy-modes": "6.5.2",
|
||||
"@codemirror/search": "6.6.0",
|
||||
"@codemirror/view": "6.39.10",
|
||||
"@codemirror/view": "6.39.11",
|
||||
"@fsegurai/codemirror-theme-abcdef": "6.2.3",
|
||||
"@fsegurai/codemirror-theme-abyss": "6.2.3",
|
||||
"@fsegurai/codemirror-theme-android-studio": "6.2.3",
|
||||
|
||||
@ -12,3 +12,4 @@ export * from "./lib/ws_api.js";
|
||||
export * from "./lib/attribute_names.js";
|
||||
export * from "./lib/utils.js";
|
||||
export * from "./lib/dayjs.js";
|
||||
export * from "./lib/notes.js";
|
||||
|
||||
60
packages/commons/src/lib/notes.ts
Normal file
60
packages/commons/src/lib/notes.ts
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @module notes Common logic for notes (across front-end and back-end)
|
||||
*/
|
||||
|
||||
import { MIME_TYPES_DICT } from "./mime_type.js";
|
||||
import { NoteType } from "./rows.js";
|
||||
|
||||
export const NOTE_TYPE_ICONS = {
|
||||
file: "bx bx-file",
|
||||
image: "bx bx-image",
|
||||
code: "bx bx-code",
|
||||
render: "bx bx-extension",
|
||||
search: "bx bx-file-find",
|
||||
relationMap: "bx bxs-network-chart",
|
||||
book: "bx bx-book",
|
||||
noteMap: "bx bxs-network-chart",
|
||||
mermaid: "bx bx-selection",
|
||||
canvas: "bx bx-pen",
|
||||
webView: "bx bx-globe-alt",
|
||||
launcher: "bx bx-link",
|
||||
doc: "bx bxs-file-doc",
|
||||
contentWidget: "bx bxs-widget",
|
||||
mindMap: "bx bx-sitemap",
|
||||
aiChat: "bx bx-bot"
|
||||
};
|
||||
|
||||
const FILE_MIME_MAPPINGS = {
|
||||
"application/pdf": "bx bxs-file-pdf",
|
||||
};
|
||||
|
||||
export function getNoteIcon({ noteId, type, mime, iconClass, workspaceIconClass, isFolder }: {
|
||||
noteId: string;
|
||||
type: NoteType;
|
||||
mime: string;
|
||||
iconClass: string | undefined;
|
||||
workspaceIconClass: string | undefined;
|
||||
isFolder: () => boolean;
|
||||
}) {
|
||||
if (iconClass) {
|
||||
return iconClass;
|
||||
} else if (workspaceIconClass) {
|
||||
return workspaceIconClass;
|
||||
} else if (noteId === "root") {
|
||||
return "bx bx-home-alt-2";
|
||||
}
|
||||
if (noteId === "_share") {
|
||||
return "bx bx-share-alt";
|
||||
} else if (type === "text") {
|
||||
if (isFolder()) {
|
||||
return "bx bx-folder";
|
||||
}
|
||||
return "bx bx-note";
|
||||
} else if (type === "code") {
|
||||
const correspondingMimeType = MIME_TYPES_DICT.find(m => m.mime === mime);
|
||||
return correspondingMimeType?.icon ?? NOTE_TYPE_ICONS.code;
|
||||
} else if (type === "file") {
|
||||
return FILE_MIME_MAPPINGS[mime] ?? NOTE_TYPE_ICONS.file;
|
||||
}
|
||||
return NOTE_TYPE_ICONS[type];
|
||||
}
|
||||
1523
pnpm-lock.yaml
generated
1523
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user