mirror of
https://github.com/zadam/trilium.git
synced 2026-01-09 00:42:16 +01:00
Compare commits
25 Commits
lightweigh
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3ccf89094 | ||
|
|
d31c6b1627 | ||
|
|
1481356d1f | ||
|
|
a54661fd0a | ||
|
|
ae4a3f10ae | ||
|
|
fe3160e7a1 | ||
|
|
66659d4786 | ||
|
|
0b25b09040 | ||
|
|
0d41cc2660 | ||
|
|
f5e8822718 | ||
|
|
bdc220ec12 | ||
|
|
3eb68e5271 | ||
|
|
521952ebcc | ||
|
|
034091a696 | ||
|
|
ae881101d8 | ||
|
|
b11a30c49c | ||
|
|
4625efda7f | ||
|
|
3c168d750d | ||
|
|
5cc7b259ce | ||
|
|
f7ae046b20 | ||
|
|
02f43d6239 | ||
|
|
53e1fa1047 | ||
|
|
b1dc0e234f | ||
|
|
9d380dd828 | ||
|
|
1f77540dbb |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -11,6 +11,14 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
sanity-check:
|
||||
name: Sanity Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Check version consistency
|
||||
run: pnpm tsx ${{ github.workspace }}/scripts/check-version-consistency.ts ${{ github.ref_name }}
|
||||
make-electron:
|
||||
name: Make Electron
|
||||
strategy:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/client",
|
||||
"version": "0.101.1",
|
||||
"version": "0.101.3",
|
||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
|
||||
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
|
||||
<title>Trilium Notes</title>
|
||||
</head>
|
||||
|
||||
<body id="trilium-app">
|
||||
<noscript>Trilium requires JavaScript to be enabled.</noscript>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
|
||||
|
||||
<!-- Required to match the PWA's top bar color with the theme -->
|
||||
<!-- This works even when the user directly changes --root-background in CSS -->
|
||||
<div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div>
|
||||
|
||||
<script src="./index.ts" type="module"></script>
|
||||
|
||||
<!-- Required for correct loading of scripts in Electron -->
|
||||
<script>
|
||||
if (typeof module === 'object') {window.module = module; module = undefined;}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,110 +0,0 @@
|
||||
async function bootstrap() {
|
||||
showSplash();
|
||||
await setupGlob();
|
||||
await Promise.all([
|
||||
initJQuery(),
|
||||
loadBootstrapCss()
|
||||
]);
|
||||
loadStylesheets();
|
||||
loadIcons();
|
||||
setBodyAttributes();
|
||||
await loadScripts();
|
||||
hideSplash();
|
||||
}
|
||||
|
||||
async function initJQuery() {
|
||||
const $ = (await import("jquery")).default;
|
||||
window.$ = $;
|
||||
window.jQuery = $;
|
||||
}
|
||||
|
||||
async function setupGlob() {
|
||||
const response = await fetch(`/bootstrap${window.location.search}`);
|
||||
const json = await response.json();
|
||||
|
||||
window.global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
|
||||
window.glob = {
|
||||
...json,
|
||||
activeDialog: null
|
||||
};
|
||||
}
|
||||
|
||||
async function loadBootstrapCss() {
|
||||
// We have to selectively import Bootstrap CSS based on text direction.
|
||||
if (glob.isRtl) {
|
||||
await import("bootstrap/dist/css/bootstrap.rtl.min.css");
|
||||
} else {
|
||||
await import("bootstrap/dist/css/bootstrap.min.css");
|
||||
}
|
||||
}
|
||||
|
||||
function loadStylesheets() {
|
||||
const { assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
|
||||
const cssToLoad: string[] = [];
|
||||
cssToLoad.push(`${assetPath}/stylesheets/ckeditor-theme.css`);
|
||||
cssToLoad.push(`api/fonts`);
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
|
||||
if (themeCssUrl) {
|
||||
cssToLoad.push(themeCssUrl);
|
||||
}
|
||||
if (themeUseNextAsBase === "next") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`);
|
||||
} else if (themeUseNextAsBase === "next-dark") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`);
|
||||
} else if (themeUseNextAsBase === "next-light") {
|
||||
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`);
|
||||
}
|
||||
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
|
||||
|
||||
for (const href of cssToLoad) {
|
||||
const linkEl = document.createElement("link");
|
||||
linkEl.href = href;
|
||||
linkEl.rel = "stylesheet";
|
||||
document.head.appendChild(linkEl);
|
||||
}
|
||||
}
|
||||
|
||||
function loadIcons() {
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.innerText = window.glob.iconPackCss;
|
||||
document.head.appendChild(styleEl);
|
||||
}
|
||||
|
||||
function setBodyAttributes() {
|
||||
const { device, headingStyle, layoutOrientation, platform, isElectron, hasNativeTitleBar, hasBackgroundEffects, currentLocale } = window.glob;
|
||||
const classesToSet = [
|
||||
device,
|
||||
`heading-style-${headingStyle}`,
|
||||
`layout-${layoutOrientation}`,
|
||||
`platform-${platform}`,
|
||||
isElectron && "electron",
|
||||
hasNativeTitleBar && "native-titlebar",
|
||||
hasBackgroundEffects && "background-effects"
|
||||
].filter(Boolean) as string[];
|
||||
|
||||
for (const classToSet of classesToSet) {
|
||||
document.body.classList.add(classToSet);
|
||||
}
|
||||
|
||||
document.body.lang = currentLocale.id;
|
||||
document.body.dir = currentLocale.rtl ? "rtl" : "ltr";
|
||||
}
|
||||
|
||||
async function loadScripts() {
|
||||
if (glob.device === "mobile") {
|
||||
await import("./mobile.js");
|
||||
} else {
|
||||
await import("./desktop.js");
|
||||
}
|
||||
}
|
||||
|
||||
function showSplash() {
|
||||
// hide body to reduce flickering on the startup. This is done through JS and not CSS to not hide <noscript>
|
||||
document.body.style.display = "none";
|
||||
}
|
||||
|
||||
function hideSplash() {
|
||||
document.body.style.display = "block";
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
|
||||
// @ts-ignore - module = undefined
|
||||
// Required for correct loading of scripts in Electron
|
||||
if (typeof module === 'object') {window.module = module; module = undefined;}
|
||||
|
||||
document.body.style.display = "block";
|
||||
|
||||
15
apps/client/src/runtime.ts
Normal file
15
apps/client/src/runtime.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import $ from "jquery";
|
||||
|
||||
async function loadBootstrap() {
|
||||
if (document.body.dir === "rtl") {
|
||||
await import("bootstrap/dist/css/bootstrap.rtl.min.css");
|
||||
} else {
|
||||
await import("bootstrap/dist/css/bootstrap.min.css");
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).$ = $;
|
||||
(window as any).jQuery = $;
|
||||
await loadBootstrap();
|
||||
|
||||
$("body").show();
|
||||
@@ -23,6 +23,12 @@ export interface RenderOptions {
|
||||
imageHasZoom?: boolean;
|
||||
/** If enabled, it will prevent the default behavior in which an empty note would display a list of children. */
|
||||
noChildrenList?: boolean;
|
||||
/** If enabled, it will prevent rendering of included notes. */
|
||||
noIncludedNotes?: boolean;
|
||||
/** If enabled, it will include archived notes when rendering children list. */
|
||||
includeArchivedNotes?: boolean;
|
||||
/** Set of note IDs that have already been seen during rendering to prevent infinite recursion. */
|
||||
seenNoteIds?: Set<string>;
|
||||
}
|
||||
|
||||
const CODE_MIME_TYPES = new Set(["application/json"]);
|
||||
|
||||
132
apps/client/src/services/content_renderer_text.spec.ts
Normal file
132
apps/client/src/services/content_renderer_text.spec.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { trimIndentation } from "@triliumnext/commons";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { buildNote } from "../test/easy-froca";
|
||||
import renderText from "./content_renderer_text";
|
||||
|
||||
describe("Text content renderer", () => {
|
||||
it("renders included note", async () => {
|
||||
const contentEl = document.createElement("div");
|
||||
const includedNote = buildNote({
|
||||
title: "Included note",
|
||||
content: "<p>This is the included note.</p>"
|
||||
});
|
||||
const note = buildNote({
|
||||
title: "New note",
|
||||
content: trimIndentation`
|
||||
<p>
|
||||
Hi there
|
||||
</p>
|
||||
<section class="include-note" data-note-id="${includedNote.noteId}" data-box-size="medium">
|
||||
|
||||
</section>
|
||||
`
|
||||
});
|
||||
await renderText(note, $(contentEl));
|
||||
expect(contentEl.querySelectorAll("section.include-note").length).toBe(1);
|
||||
expect(contentEl.querySelectorAll("section.include-note p").length).toBe(1);
|
||||
});
|
||||
|
||||
it("skips rendering included note", async () => {
|
||||
const contentEl = document.createElement("div");
|
||||
const includedNote = buildNote({
|
||||
title: "Included note",
|
||||
content: "<p>This is the included note.</p>"
|
||||
});
|
||||
const note = buildNote({
|
||||
title: "New note",
|
||||
content: trimIndentation`
|
||||
<p>
|
||||
Hi there
|
||||
</p>
|
||||
<section class="include-note" data-note-id="${includedNote.noteId}" data-box-size="medium">
|
||||
|
||||
</section>
|
||||
`
|
||||
});
|
||||
await renderText(note, $(contentEl), { noIncludedNotes: true });
|
||||
expect(contentEl.querySelectorAll("section.include-note").length).toBe(0);
|
||||
});
|
||||
|
||||
it("doesn't enter infinite loop on direct recursion", async () => {
|
||||
const contentEl = document.createElement("div");
|
||||
const note = buildNote({
|
||||
title: "New note",
|
||||
id: "Y7mBwmRjQyb4",
|
||||
content: trimIndentation`
|
||||
<p>
|
||||
Hi there
|
||||
</p>
|
||||
<section class="include-note" data-note-id="Y7mBwmRjQyb4" data-box-size="medium">
|
||||
|
||||
</section>
|
||||
<section class="include-note" data-note-id="Y7mBwmRjQyb4" data-box-size="medium">
|
||||
|
||||
</section>
|
||||
`
|
||||
});
|
||||
await renderText(note, $(contentEl));
|
||||
expect(contentEl.querySelectorAll("section.include-note").length).toBe(0);
|
||||
});
|
||||
|
||||
it("doesn't enter infinite loop on indirect recursion", async () => {
|
||||
const contentEl = document.createElement("div");
|
||||
buildNote({
|
||||
id: "first",
|
||||
title: "Included note",
|
||||
content: trimIndentation`\
|
||||
<p>This is the included note.</p>
|
||||
<section class="include-note" data-note-id="second" data-box-size="medium">
|
||||
|
||||
</section>
|
||||
`
|
||||
});
|
||||
const note = buildNote({
|
||||
id: "second",
|
||||
title: "New note",
|
||||
content: trimIndentation`
|
||||
<p>
|
||||
Hi there
|
||||
</p>
|
||||
<section class="include-note" data-note-id="first" data-box-size="medium">
|
||||
|
||||
</section>
|
||||
`
|
||||
});
|
||||
await renderText(note, $(contentEl));
|
||||
expect(contentEl.querySelectorAll("section.include-note").length).toBe(1);
|
||||
});
|
||||
|
||||
it("renders children list when note is empty", async () => {
|
||||
const contentEl = document.createElement("div");
|
||||
const parentNote = buildNote({
|
||||
title: "Parent note",
|
||||
children: [
|
||||
{ title: "Child note 1" },
|
||||
{ title: "Child note 2" }
|
||||
]
|
||||
});
|
||||
await renderText(parentNote, $(contentEl));
|
||||
const items = contentEl.querySelectorAll("a");
|
||||
expect(items.length).toBe(2);
|
||||
expect(items[0].textContent).toBe("Child note 1");
|
||||
expect(items[1].textContent).toBe("Child note 2");
|
||||
});
|
||||
|
||||
it("skips archived notes in children list", async () => {
|
||||
const contentEl = document.createElement("div");
|
||||
const parentNote = buildNote({
|
||||
title: "Parent note",
|
||||
children: [
|
||||
{ title: "Child note 1" },
|
||||
{ title: "Child note 2", "#archived": "" },
|
||||
{ title: "Child note 3" }
|
||||
]
|
||||
});
|
||||
await renderText(parentNote, $(contentEl));
|
||||
const items = contentEl.querySelectorAll("a");
|
||||
expect(items.length).toBe(2);
|
||||
expect(items[0].textContent).toBe("Child note 1");
|
||||
expect(items[1].textContent).toBe("Child note 3");
|
||||
});
|
||||
});
|
||||
@@ -15,7 +15,14 @@ export default async function renderText(note: FNote | FAttachment, $renderedCon
|
||||
|
||||
if (blob && !isHtmlEmpty(blob.content)) {
|
||||
$renderedContent.append($('<div class="ck-content">').html(blob.content));
|
||||
await renderIncludedNotes($renderedContent[0]);
|
||||
|
||||
const seenNoteIds = options.seenNoteIds ?? new Set<string>();
|
||||
seenNoteIds.add("noteId" in note ? note.noteId : note.attachmentId);
|
||||
if (!options.noIncludedNotes) {
|
||||
await renderIncludedNotes($renderedContent[0], seenNoteIds);
|
||||
} else {
|
||||
$renderedContent.find("section.include-note").remove();
|
||||
}
|
||||
|
||||
if ($renderedContent.find("span.math-tex").length > 0) {
|
||||
renderMathInElement($renderedContent[0], { trust: true });
|
||||
@@ -35,11 +42,11 @@ export default async function renderText(note: FNote | FAttachment, $renderedCon
|
||||
await rewriteMermaidDiagramsInContainer($renderedContent[0] as HTMLDivElement);
|
||||
await formatCodeBlocks($renderedContent);
|
||||
} else if (note instanceof FNote && !options.noChildrenList) {
|
||||
await renderChildrenList($renderedContent, note);
|
||||
await renderChildrenList($renderedContent, note, options.includeArchivedNotes ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
async function renderIncludedNotes(contentEl: HTMLElement) {
|
||||
async function renderIncludedNotes(contentEl: HTMLElement, seenNoteIds: Set<string>) {
|
||||
// TODO: Consider duplicating with server's share/content_renderer.ts.
|
||||
const includeNoteEls = contentEl.querySelectorAll("section.include-note");
|
||||
|
||||
@@ -66,8 +73,18 @@ async function renderIncludedNotes(contentEl: HTMLElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const renderedContent = (await content_renderer.getRenderedContent(note)).$renderedContent;
|
||||
if (seenNoteIds.has(noteId)) {
|
||||
console.warn(`Skipping inclusion of ${noteId} to avoid circular reference.`);
|
||||
includeNoteEl.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
const renderedContent = (await content_renderer.getRenderedContent(note, {
|
||||
seenNoteIds
|
||||
})).$renderedContent;
|
||||
includeNoteEl.replaceChildren(...renderedContent);
|
||||
|
||||
seenNoteIds.add(noteId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +115,7 @@ export async function applyInlineMermaid(container: HTMLDivElement) {
|
||||
}
|
||||
}
|
||||
|
||||
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: FNote) {
|
||||
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: FNote, includeArchivedNotes: boolean) {
|
||||
let childNoteIds = note.getChildNoteIds();
|
||||
|
||||
if (!childNoteIds.length) {
|
||||
@@ -108,14 +125,16 @@ async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: F
|
||||
$renderedContent.css("padding", "10px");
|
||||
$renderedContent.addClass("text-with-ellipsis");
|
||||
|
||||
// just load the first 10 child notes
|
||||
if (childNoteIds.length > 10) {
|
||||
childNoteIds = childNoteIds.slice(0, 10);
|
||||
}
|
||||
|
||||
// just load the first 10 child notes
|
||||
const childNotes = await froca.getNotes(childNoteIds);
|
||||
|
||||
for (const childNote of childNotes) {
|
||||
if (childNote.isArchived && !includeArchivedNotes) continue;
|
||||
|
||||
$renderedContent.append(
|
||||
await link.createLink(`${note.noteId}/${childNote.noteId}`, {
|
||||
showTooltip: false,
|
||||
|
||||
@@ -21,7 +21,13 @@
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Hubo un fallo al cargar un script personalizado",
|
||||
"message": "El script de la nota con ID \"{{id}}\", titulado \"{{title}}\" no pudo ser ejecutado debido a:\n\n{{message}}"
|
||||
"message": "El script no pudo ser ejecutado debido a:\n\n{{message}}"
|
||||
},
|
||||
"widget-list-error": {
|
||||
"title": "Hubo un fallo al obtener la lista de widgets del servidor"
|
||||
},
|
||||
"widget-render-error": {
|
||||
"title": "Hubo un fallo al renderizar un widget personalizado de React"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
@@ -691,7 +697,7 @@
|
||||
"convert_into_attachment_successful": "La nota '{{title}}' ha sido convertida a un archivo adjunto.",
|
||||
"convert_into_attachment_prompt": "¿Está seguro que desea convertir la nota '{{title}}' en un archivo adjunto de la nota padre?",
|
||||
"print_pdf": "Exportar como PDF...",
|
||||
"open_note_on_server": "Abrir nota en el servidor"
|
||||
"open_note_on_server": "Abrir nota en servidor"
|
||||
},
|
||||
"onclick_button": {
|
||||
"no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido"
|
||||
@@ -737,7 +743,7 @@
|
||||
"zpetne_odkazy": {
|
||||
"relation": "relación",
|
||||
"backlink_one": "{{count}} Vínculo de retroceso",
|
||||
"backlink_many": "",
|
||||
"backlink_many": "{{count}} Vínculos de retroceso",
|
||||
"backlink_other": "{{count}} vínculos de retroceso"
|
||||
},
|
||||
"mobile_detail_menu": {
|
||||
@@ -750,7 +756,10 @@
|
||||
"note_icon": {
|
||||
"change_note_icon": "Cambiar icono de nota",
|
||||
"search": "Búsqueda:",
|
||||
"reset-default": "Restablecer a icono por defecto"
|
||||
"reset-default": "Restablecer a icono por defecto",
|
||||
"search_placeholder_one": "Buscar {{number}} icono a través de {{count}} paquetes",
|
||||
"search_placeholder_many": "Buscar {{number}} iconos a través de {{count}} paquetes",
|
||||
"search_placeholder_other": "Buscar {{number}} iconos a través de {{count}} paquetes"
|
||||
},
|
||||
"basic_properties": {
|
||||
"note_type": "Tipo de nota",
|
||||
@@ -790,7 +799,7 @@
|
||||
"file_type": "Tipo de archivo",
|
||||
"file_size": "Tamaño del archivo",
|
||||
"download": "Descargar",
|
||||
"open": "Abrir",
|
||||
"open": "Abrir externamente",
|
||||
"upload_new_revision": "Subir nueva revisión",
|
||||
"upload_success": "Se ha subido una nueva revisión de archivo.",
|
||||
"upload_failed": "Error al cargar una nueva revisión de archivo.",
|
||||
@@ -1303,11 +1312,11 @@
|
||||
"code_mime_types": {
|
||||
"title": "Tipos MIME disponibles en el menú desplegable",
|
||||
"tooltip_syntax_highlighting": "Resaltado de sintaxis",
|
||||
"tooltip_code_block_syntax": "Bloques de código en notas de texto",
|
||||
"tooltip_code_note_syntax": "Notas de código"
|
||||
"tooltip_code_block_syntax": "Bloques de Código en notas de Texto",
|
||||
"tooltip_code_note_syntax": "Notas de Código"
|
||||
},
|
||||
"vim_key_bindings": {
|
||||
"use_vim_keybindings_in_code_notes": "Atajos de teclas de Vim",
|
||||
"use_vim_keybindings_in_code_notes": "Combinaciones de teclas Vim",
|
||||
"enable_vim_keybindings": "Habilitar los atajos de teclas de Vim en la notas de código (no es modo ex)"
|
||||
},
|
||||
"wrap_lines": {
|
||||
@@ -1572,7 +1581,7 @@
|
||||
"will_be_deleted_in": "Este archivo adjunto se eliminará automáticamente en {{time}}",
|
||||
"will_be_deleted_soon": "Este archivo adjunto se eliminará automáticamente pronto",
|
||||
"deletion_reason": ", porque el archivo adjunto no está vinculado en el contenido de la nota. Para evitar la eliminación, vuelva a agregar el enlace del archivo adjunto al contenido o convierta el archivo adjunto en una nota.",
|
||||
"role_and_size": "Rol: {{role}}, Tamaño: {{size}}",
|
||||
"role_and_size": "Rol: {{role}}, tamaño: {{size}}, MIME: {{- mimeType}}",
|
||||
"link_copied": "Enlace del archivo adjunto copiado al portapapeles.",
|
||||
"unrecognized_role": "Rol de archivo adjunto no reconocido '{{role}}'."
|
||||
},
|
||||
@@ -1623,7 +1632,7 @@
|
||||
"import-into-note": "Importar a nota",
|
||||
"apply-bulk-actions": "Aplicar acciones en lote",
|
||||
"converted-to-attachments": "{{count}} notas han sido convertidas en archivos adjuntos.",
|
||||
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres?",
|
||||
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres? Esta operación solo aplica a notas de Imagen, otras notas serán omitidas.",
|
||||
"open-in-popup": "Edición rápida",
|
||||
"archive": "Archivar",
|
||||
"unarchive": "Desarchivar"
|
||||
@@ -1718,7 +1727,10 @@
|
||||
"note_detail": {
|
||||
"could_not_find_typewidget": "No se pudo encontrar typeWidget para el tipo '{{type}}'",
|
||||
"printing": "Impresión en curso...",
|
||||
"printing_pdf": "Exportando a PDF en curso.."
|
||||
"printing_pdf": "Exportando a PDF en curso..",
|
||||
"print_report_collection_content_one": "{{count}} nota en la colección no se puede imprimir porque no son compatibles o está protegida.",
|
||||
"print_report_collection_content_many": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.",
|
||||
"print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "escriba el título de la nota aquí..."
|
||||
@@ -1930,7 +1942,7 @@
|
||||
"unknown_widget": "Widget desconocido para \"{{id}}\"."
|
||||
},
|
||||
"note_language": {
|
||||
"not_set": "No establecido",
|
||||
"not_set": "Idioma no establecido",
|
||||
"configure-languages": "Configurar idiomas..."
|
||||
},
|
||||
"content_language": {
|
||||
@@ -1969,7 +1981,7 @@
|
||||
"hide-weekends": "Ocultar fines de semana",
|
||||
"show-scale": "Mostrar escala",
|
||||
"display-week-numbers": "Mostrar números de semana",
|
||||
"map-style": "Estilo de mapa:",
|
||||
"map-style": "Estilo de mapa",
|
||||
"max-nesting-depth": "Máxima profundidad de anidamiento:",
|
||||
"vector_light": "Vector (claro)",
|
||||
"vector_dark": "Vector (oscuro)",
|
||||
@@ -2098,5 +2110,36 @@
|
||||
"clear-color": "Borrar color de nota",
|
||||
"set-color": "Asignar color de nota",
|
||||
"set-custom-color": "Asignar color de nota personalizado"
|
||||
},
|
||||
"status_bar": {
|
||||
"backlinks_one": "{{count}} vínculo de retroceso",
|
||||
"backlinks_many": "{{count}} vínculos de retroceso",
|
||||
"backlinks_other": "{{count}} vínculos de retroceso",
|
||||
"backlinks_title_one": "Ver vínculo de retroceso",
|
||||
"backlinks_title_many": "Ver vínculos de retroceso",
|
||||
"backlinks_title_other": "Ver vínculos de retroceso",
|
||||
"attachments_one": "{{count}} adjunto",
|
||||
"attachments_many": "{{count}} adjuntos",
|
||||
"attachments_other": "{{count}} adjuntos",
|
||||
"attachments_title_one": "Ver adjunto en una nueva pestaña",
|
||||
"attachments_title_many": "Ver adjuntos en una nueva pestaña",
|
||||
"attachments_title_other": "Ver adjuntos en una nueva pestaña",
|
||||
"attributes_one": "{{count}} atributo",
|
||||
"attributes_many": "{{count}} atributos",
|
||||
"attributes_other": "{{count}} atributos",
|
||||
"note_paths_one": "{{count}} ruta",
|
||||
"note_paths_many": "{{count}} rutas",
|
||||
"note_paths_other": "{{count}} rutas"
|
||||
},
|
||||
"pdf": {
|
||||
"attachments_one": "{{count}} adjunto",
|
||||
"attachments_many": "{{count}} adjuntos",
|
||||
"attachments_other": "{{count}} adjuntos",
|
||||
"layers_one": "{{count}} capa",
|
||||
"layers_many": "{{count}} capas",
|
||||
"layers_other": "{{count}} capas",
|
||||
"pages_one": "{{count}} página",
|
||||
"pages_many": "{{count}} páginas",
|
||||
"pages_other": "{{count}} páginas"
|
||||
}
|
||||
}
|
||||
|
||||
13
apps/client/src/types.d.ts
vendored
13
apps/client/src/types.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { IconRegistry, Locale } from "@triliumnext/commons";
|
||||
import { IconRegistry } from "@triliumnext/commons";
|
||||
|
||||
import appContext, { AppContext } from "./components/app_context";
|
||||
import type FNote from "./entities/fnote";
|
||||
@@ -47,25 +47,14 @@ interface CustomGlobals {
|
||||
platform?: typeof process.platform;
|
||||
linter: typeof lint;
|
||||
hasNativeTitleBar: boolean;
|
||||
hasBackgroundEffects: boolean;
|
||||
isElectron: boolean;
|
||||
isRtl: boolean;
|
||||
iconRegistry: IconRegistry;
|
||||
themeCssUrl: string;
|
||||
themeUseNextAsBase?: "next" | "next-light" | "next-dark";
|
||||
iconPackCss: string;
|
||||
headingStyle: "plain" | "underline" | "markdown";
|
||||
layoutOrientation: "vertical" | "horizontal";
|
||||
currentLocale: Locale;
|
||||
}
|
||||
|
||||
type RequireMethod = (moduleName: string) => any;
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
$: JQueryStatic;
|
||||
jQuery: JQueryStatic;
|
||||
|
||||
logError(message: string);
|
||||
logInfo(message: string);
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ export default function NoteDetail() {
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
class={`note-detail ${isFullHeight ? "full-height" : ""}`}
|
||||
class={`component note-detail ${isFullHeight ? "full-height" : ""}`}
|
||||
>
|
||||
{Object.entries(noteTypesToRender).map(([ itemType, Element ]) => {
|
||||
return <NoteDetailWrapper
|
||||
|
||||
@@ -44,6 +44,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
|
||||
export function GridView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
|
||||
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
|
||||
const { pageNotes, ...pagination } = usePagination(note, noteIds);
|
||||
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
|
||||
|
||||
return (
|
||||
<div class="note-list grid-view">
|
||||
@@ -52,7 +53,7 @@ export function GridView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
|
||||
|
||||
<div class="note-list-container use-tn-links">
|
||||
{pageNotes?.map(childNote => (
|
||||
<GridNoteCard note={childNote} parentNote={note} highlightedTokens={highlightedTokens} />
|
||||
<GridNoteCard note={childNote} parentNote={note} highlightedTokens={highlightedTokens} includeArchived={includeArchived} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -94,14 +95,16 @@ function ListNoteCard({ note, parentNote, highlightedTokens, currentLevel, expan
|
||||
</h5>
|
||||
|
||||
{isExpanded && <>
|
||||
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList />
|
||||
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList includeArchivedNotes={includeArchived} />
|
||||
<NoteChildren note={note} parentNote={parentNote} highlightedTokens={highlightedTokens} currentLevel={currentLevel} expandDepth={expandDepth} includeArchived={includeArchived} />
|
||||
</>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined }) {
|
||||
function GridNoteCard({ note, parentNote, highlightedTokens, includeArchived }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined, includeArchived: boolean }) {
|
||||
const titleRef = useRef<HTMLSpanElement>(null);
|
||||
const [ noteTitle, setNoteTitle ] = useState<string>();
|
||||
const notePath = getNotePath(parentNote, note);
|
||||
|
||||
return (
|
||||
@@ -120,6 +123,7 @@ function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, pa
|
||||
note={note}
|
||||
trim
|
||||
highlightedTokens={highlightedTokens}
|
||||
includeArchivedNotes={includeArchived}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -136,14 +140,22 @@ function NoteAttributes({ note }: { note: FNote }) {
|
||||
return <span className="note-list-attributes" ref={ref} />;
|
||||
}
|
||||
|
||||
function NoteContent({ note, trim, noChildrenList, highlightedTokens }: { note: FNote, trim?: boolean, noChildrenList?: boolean, highlightedTokens: string[] | null | undefined }) {
|
||||
function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: {
|
||||
note: FNote;
|
||||
trim?: boolean;
|
||||
noChildrenList?: boolean;
|
||||
highlightedTokens: string[] | null | undefined;
|
||||
includeArchivedNotes: boolean;
|
||||
}) {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
|
||||
|
||||
useEffect(() => {
|
||||
content_renderer.getRenderedContent(note, {
|
||||
trim,
|
||||
noChildrenList
|
||||
noChildrenList,
|
||||
noIncludedNotes: true,
|
||||
includeArchivedNotes
|
||||
})
|
||||
.then(({ $renderedContent, type }) => {
|
||||
if (!contentRef.current) return;
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { useCallback, useLayoutEffect, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import froca from "../../services/froca";
|
||||
import { isDesktop, isMobile } from "../../services/utils";
|
||||
import CalendarWidget from "./CalendarWidget";
|
||||
import SpacerWidget from "./SpacerWidget";
|
||||
import BookmarkButtons from "./BookmarkButtons";
|
||||
import ProtectedSessionStatusWidget from "./ProtectedSessionStatusWidget";
|
||||
import SyncStatus from "./SyncStatus";
|
||||
import HistoryNavigationButton from "./HistoryNavigation";
|
||||
import { AiChatButton, CommandButton, CustomWidget, NoteLauncher, QuickSearchLauncherWidget, ScriptLauncher, TodayLauncher } from "./LauncherDefinitions";
|
||||
import { useTriliumEvent } from "../react/hooks";
|
||||
import { onWheelHorizontalScroll } from "../widget_utils";
|
||||
import BookmarkButtons from "./BookmarkButtons";
|
||||
import CalendarWidget from "./CalendarWidget";
|
||||
import HistoryNavigationButton from "./HistoryNavigation";
|
||||
import { LaunchBarContext } from "./launch_bar_widgets";
|
||||
import { AiChatButton, CommandButton, CustomWidget, NoteLauncher, QuickSearchLauncherWidget, ScriptLauncher, TodayLauncher } from "./LauncherDefinitions";
|
||||
import ProtectedSessionStatusWidget from "./ProtectedSessionStatusWidget";
|
||||
import SpacerWidget from "./SpacerWidget";
|
||||
import SyncStatus from "./SyncStatus";
|
||||
|
||||
export default function LauncherContainer({ isHorizontalLayout }: { isHorizontalLayout: boolean }) {
|
||||
const childNotes = useLauncherChildNotes();
|
||||
@@ -34,18 +35,19 @@ export default function LauncherContainer({ isHorizontalLayout }: { isHorizontal
|
||||
}}>
|
||||
{childNotes?.map(childNote => {
|
||||
if (childNote.type !== "launcher") {
|
||||
throw new Error(`Note '${childNote.noteId}' '${childNote.title}' is not a launcher even though it's in the launcher subtree`);
|
||||
console.warn(`Note '${childNote.noteId}' '${childNote.title}' is not a launcher even though it's in the launcher subtree`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isDesktop() && childNote.isLabelTruthy("desktopOnly")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return <Launcher key={childNote.noteId} note={childNote} isHorizontalLayout={isHorizontalLayout} />
|
||||
return <Launcher key={childNote.noteId} note={childNote} isHorizontalLayout={isHorizontalLayout} />;
|
||||
})}
|
||||
</LaunchBarContext.Provider>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function Launcher({ note, isHorizontalLayout }: { note: FNote, isHorizontalLayout: boolean }) {
|
||||
@@ -72,7 +74,7 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) {
|
||||
const builtinWidget = note.getLabelValue("builtinWidget");
|
||||
switch (builtinWidget) {
|
||||
case "calendar":
|
||||
return <CalendarWidget launcherNote={note} />
|
||||
return <CalendarWidget launcherNote={note} />;
|
||||
case "spacer":
|
||||
// || has to be inside since 0 is a valid value
|
||||
const baseSize = parseInt(note.getLabelValue("baseSize") || "40");
|
||||
@@ -86,15 +88,15 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) {
|
||||
case "syncStatus":
|
||||
return <SyncStatus />;
|
||||
case "backInHistoryButton":
|
||||
return <HistoryNavigationButton launcherNote={note} command="backInNoteHistory" />
|
||||
return <HistoryNavigationButton launcherNote={note} command="backInNoteHistory" />;
|
||||
case "forwardInHistoryButton":
|
||||
return <HistoryNavigationButton launcherNote={note} command="forwardInNoteHistory" />
|
||||
return <HistoryNavigationButton launcherNote={note} command="forwardInNoteHistory" />;
|
||||
case "todayInJournal":
|
||||
return <TodayLauncher launcherNote={note} />
|
||||
return <TodayLauncher launcherNote={note} />;
|
||||
case "quickSearch":
|
||||
return <QuickSearchLauncherWidget />
|
||||
return <QuickSearchLauncherWidget />;
|
||||
case "aiChatLauncher":
|
||||
return <AiChatButton launcherNote={note} />
|
||||
return <AiChatButton launcherNote={note} />;
|
||||
default:
|
||||
throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`);
|
||||
}
|
||||
|
||||
@@ -338,19 +338,19 @@ interface AttributesProps extends StatusBarContext {
|
||||
function AttributesButton({ note, attributesShown, setAttributesShown }: AttributesProps) {
|
||||
const [ count, setCount ] = useState(note.attributes.length);
|
||||
|
||||
const getAttributeCount = useCallback((note: FNote) => {
|
||||
const refreshCount = useCallback((note: FNote) => {
|
||||
return note.getAttributes().filter(a => !a.isAutoLink).length;
|
||||
}, []);
|
||||
|
||||
// React to note changes.
|
||||
useEffect(() => {
|
||||
setCount(getAttributeCount(note));
|
||||
}, [ note, getAttributeCount ]);
|
||||
setCount(refreshCount(note));
|
||||
}, [ note, refreshCount ]);
|
||||
|
||||
// React to changes in count.
|
||||
useTriliumEvent("entitiesReloaded", (({loadResults}) => {
|
||||
if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) {
|
||||
setCount(getAttributeCount(note));
|
||||
setCount(refreshCount(note));
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,12 +2,14 @@ import "./TableOfContents.css";
|
||||
|
||||
import { CKTextEditor, ModelElement } from "@triliumnext/ckeditor5";
|
||||
import clsx from "clsx";
|
||||
import { useCallback, useEffect, useState } from "preact/hooks";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { t } from "../../services/i18n";
|
||||
import math from "../../services/math";
|
||||
import { randomString } from "../../services/utils";
|
||||
import { useActiveNoteContext, useContentElement, useGetContextData, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks";
|
||||
import Icon from "../react/Icon";
|
||||
import RawHtml from "../react/RawHtml";
|
||||
import RightPanelWidget from "./RightPanelWidget";
|
||||
|
||||
//#region Generic impl.
|
||||
@@ -80,6 +82,22 @@ function TableOfContentsHeading({ heading, scrollToHeading, activeHeadingId }: {
|
||||
}) {
|
||||
const [ collapsed, setCollapsed ] = useState(false);
|
||||
const isActive = heading.id === activeHeadingId;
|
||||
const contentRef = useRef<HTMLElement>(null);
|
||||
|
||||
// Render math equations after component mounts/updates
|
||||
useEffect(() => {
|
||||
if (!contentRef.current) return;
|
||||
const mathElements = contentRef.current.querySelectorAll(".ck-math-tex");
|
||||
|
||||
for (const mathEl of mathElements ?? []) {
|
||||
try {
|
||||
math.render(mathEl.textContent || "", mathEl as HTMLElement);
|
||||
} catch (e) {
|
||||
console.warn("Failed to render math in TOC:", e);
|
||||
}
|
||||
}
|
||||
}, [heading.text]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<li className={clsx(collapsed && "collapsed", isActive && "active")}>
|
||||
@@ -90,10 +108,12 @@ function TableOfContentsHeading({ heading, scrollToHeading, activeHeadingId }: {
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
<RawHtml
|
||||
containerRef={contentRef}
|
||||
className="item-content"
|
||||
onClick={() => scrollToHeading(heading)}
|
||||
>{heading.text}</span>
|
||||
html={heading.text}
|
||||
/>
|
||||
</li>
|
||||
{heading.children && (
|
||||
<ol>
|
||||
@@ -189,9 +209,23 @@ function extractTocFromTextEditor(editor: CKTextEditor) {
|
||||
if (type !== "elementStart" || !item.is('element') || !item.name.startsWith('heading')) continue;
|
||||
|
||||
const level = Number(item.name.replace( 'heading', '' ));
|
||||
const text = Array.from( item.getChildren() )
|
||||
.map( c => c.is( '$text' ) ? c.data : '' )
|
||||
.join( '' );
|
||||
|
||||
// Convert model element to view, then to DOM to get HTML
|
||||
const viewEl = editor.editing.mapper.toViewElement(item);
|
||||
let text = '';
|
||||
if (viewEl) {
|
||||
const domEl = editor.editing.view.domConverter.mapViewToDom(viewEl);
|
||||
if (domEl instanceof HTMLElement) {
|
||||
text = domEl.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to plain text if conversion fails
|
||||
if (!text) {
|
||||
text = Array.from( item.getChildren() )
|
||||
.map( c => c.is( '$text' ) ? c.data : '' )
|
||||
.join( '' );
|
||||
}
|
||||
|
||||
// Assign a unique ID
|
||||
let tocId = item.getAttribute(TOC_ID) as string | undefined;
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
.note-detail-split .note-detail-split-editor {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.note-detail-split .note-detail-split-editor .note-detail-code {
|
||||
@@ -30,6 +32,7 @@
|
||||
margin: 5px;
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.85em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.note-detail-split .note-detail-split-preview {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/// <reference types='vitest' />
|
||||
import preact from "@preact/preset-vite";
|
||||
import { join } from 'path';
|
||||
import webpackStatsPlugin from 'rollup-plugin-webpack-stats';
|
||||
import { defineConfig } from 'vite';
|
||||
import { join, resolve } from 'path';
|
||||
import { defineConfig, type Plugin } from 'vite';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||
import webpackStatsPlugin from 'rollup-plugin-webpack-stats';
|
||||
import preact from "@preact/preset-vite";
|
||||
|
||||
const assets = [ "assets", "stylesheets", "fonts", "translations" ];
|
||||
|
||||
@@ -70,15 +70,21 @@ export default defineConfig(() => ({
|
||||
sourcemap: false,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: join(__dirname, "src", "index.html"),
|
||||
desktop: join(__dirname, "src", "desktop.ts"),
|
||||
mobile: join(__dirname, "src", "mobile.ts"),
|
||||
login: join(__dirname, "src", "login.ts"),
|
||||
setup: join(__dirname, "src", "setup.ts"),
|
||||
set_password: join(__dirname, "src", "set_password.ts"),
|
||||
runtime: join(__dirname, "src", "runtime.ts"),
|
||||
print: join(__dirname, "src", "print.tsx")
|
||||
},
|
||||
output: {
|
||||
entryFileNames: "src/[name].js",
|
||||
chunkFileNames: "src/[name].js",
|
||||
assetFileNames: "src/[name].[ext]",
|
||||
manualChunks: {
|
||||
"ckeditor5": [ "@triliumnext/ckeditor5" ]
|
||||
"ckeditor5": [ "@triliumnext/ckeditor5" ],
|
||||
"boxicons": [ "../../node_modules/boxicons/css/boxicons.min.css" ]
|
||||
},
|
||||
},
|
||||
onwarn(warning, rollupWarn) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/desktop",
|
||||
"version": "0.101.1",
|
||||
"version": "0.101.3",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"private": true,
|
||||
"main": "src/main.ts",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import test, { BrowserContext, expect, Page } from "@playwright/test";
|
||||
import test, { expect, Page } from "@playwright/test";
|
||||
|
||||
import App from "../support/app";
|
||||
|
||||
test.beforeEach(async ({ page, context }) => {
|
||||
const app = await setLayout({ page, context }, true);
|
||||
const app = new App(page, context);
|
||||
await app.goto();
|
||||
await app.setOption("rightPaneCollapsedItems", "[]");
|
||||
});
|
||||
test.afterEach(async ({ page, context }) => await setLayout({ page, context }, false));
|
||||
|
||||
test("Table of contents works", async ({ page, context }) => {
|
||||
const app = new App(page, context);
|
||||
@@ -73,13 +73,15 @@ test("Attachments listing works", async ({ page, context }) => {
|
||||
test("Download original PDF works", async ({ page, context }) => {
|
||||
const app = new App(page, context);
|
||||
await app.goto();
|
||||
await app.goToNoteInNewTab("Dacia Logan.pdf");
|
||||
await app.goToNoteInNewTab("Layers test.pdf");
|
||||
const pdfHelper = new PdfHelper(app);
|
||||
await pdfHelper.toBeInitialized();
|
||||
|
||||
const downloadButton = app.currentNoteSplit.locator(".icon-action.bx.bx-download");
|
||||
await expect(downloadButton).toBeVisible();
|
||||
const [ download ] = await Promise.all([
|
||||
page.waitForEvent("download"),
|
||||
app.currentNoteSplit.locator(".icon-action.bx.bx-download").click()
|
||||
downloadButton.click()
|
||||
]);
|
||||
expect(download).toBeDefined();
|
||||
});
|
||||
@@ -105,13 +107,6 @@ test("Layers listing works", async ({ page, context }) => {
|
||||
await expect(layersList.locator(".pdf-layer-item")).toHaveCount(0);
|
||||
});
|
||||
|
||||
async function setLayout({ page, context}: { page: Page; context: BrowserContext }, newLayout: boolean) {
|
||||
const app = new App(page, context);
|
||||
await app.goto();
|
||||
await app.setOption("newLayout", newLayout ? "true" : "false");
|
||||
return app;
|
||||
}
|
||||
|
||||
class PdfHelper {
|
||||
private contentFrame: ReturnType<Page["frameLocator"]>;
|
||||
|
||||
@@ -125,5 +120,6 @@ class PdfHelper {
|
||||
|
||||
async toBeInitialized() {
|
||||
await expect(this.contentFrame.locator("#pageNumber")).toBeVisible();
|
||||
await expect(this.contentFrame.locator(".page")).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import App from "../support/app";
|
||||
|
||||
test("Table of contents is displayed", async ({ page, context }) => {
|
||||
@@ -8,7 +9,7 @@ test("Table of contents is displayed", async ({ page, context }) => {
|
||||
await app.goToNoteInNewTab("Table of contents");
|
||||
|
||||
await expect(app.sidebar).toContainText("Table of Contents");
|
||||
const rootList = app.sidebar.locator(".toc-widget > span > ol");
|
||||
const rootList = app.sidebar.locator(".toc > ol");
|
||||
|
||||
// Heading 1.1
|
||||
// Heading 1.1
|
||||
@@ -42,7 +43,7 @@ test("Highlights list is displayed", async ({ page, context }) => {
|
||||
await app.closeAllTabs();
|
||||
await app.goToNoteInNewTab("Highlights list");
|
||||
|
||||
await expect(app.sidebar).toContainText("Highlights List");
|
||||
await expect(app.sidebar).toContainText("10 highlights");
|
||||
const rootList = app.sidebar.locator(".highlights-list ol");
|
||||
let index = 0;
|
||||
for (const highlightedEl of ["Bold 1", "Italic 1", "Underline 1", "Colored text 1", "Background text 1", "Bold 2", "Italic 2", "Underline 2", "Colored text 2", "Background text 2"]) {
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class App {
|
||||
this.noteTreeHoistedNote = this.noteTree.locator(".fancytree-node", { has: page.locator(".unhoist-button") });
|
||||
this.launcherBar = page.locator("#launcher-container");
|
||||
this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)");
|
||||
this.currentNoteSplitTitle = this.currentNoteSplit.locator(".note-title");
|
||||
this.currentNoteSplitTitle = this.currentNoteSplit.locator(".note-title").first();
|
||||
this.currentNoteSplitContent = this.currentNoteSplit.locator(".note-detail-printable.visible");
|
||||
this.sidebar = page.locator("#right-pane");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/server",
|
||||
"version": "0.101.1",
|
||||
"version": "0.101.3",
|
||||
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
||||
"private": true,
|
||||
"main": "./src/main.ts",
|
||||
|
||||
Binary file not shown.
@@ -220,6 +220,7 @@
|
||||
"password-confirmation": "Password confirmation",
|
||||
"button": "Set password"
|
||||
},
|
||||
"javascript-required": "Trilium requires JavaScript to be enabled.",
|
||||
"setup": {
|
||||
"heading": "Trilium Notes setup",
|
||||
"new-document": "I'm a new user, and I want to create a new Trilium document for my notes",
|
||||
|
||||
60
apps/server/src/assets/views/desktop.ejs
Normal file
60
apps/server/src/assets/views/desktop.ejs
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
|
||||
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
|
||||
<title>Trilium Notes</title>
|
||||
<style id="trilium-icon-packs">
|
||||
<%- iconPackCss %>
|
||||
</style>
|
||||
<script src="<%= appPath %>/runtime.js" crossorigin type="module"></script>
|
||||
</head>
|
||||
<body
|
||||
id="trilium-app"
|
||||
class="desktop heading-style-<%= headingStyle %> layout-<%= layoutOrientation %> platform-<%= platform %> <%= isElectron ? 'electron' : '' %> <%= hasNativeTitleBar ? 'native-titlebar' : '' %> <%= hasBackgroundEffects ? 'background-effects' : '' %>"
|
||||
lang="<%= currentLocale.id %>" dir="<%= currentLocale.rtl ? 'rtl' : 'ltr' %>"
|
||||
>
|
||||
<noscript><%= t("javascript-required") %></noscript>
|
||||
|
||||
<script>
|
||||
// hide body to reduce flickering on the startup. This is done through JS and not CSS to not hide <noscript>
|
||||
document.getElementsByTagName("body")[0].style.display = "none";
|
||||
</script>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
|
||||
|
||||
<%- include("./partials/windowGlobal.ejs", locals) %>
|
||||
|
||||
<!-- Required for match the PWA's top bar color with the theme -->
|
||||
<!-- This works even when the user directly changes --root-background in CSS -->
|
||||
<div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div>
|
||||
|
||||
<!-- Required for correct loading of scripts in Electron -->
|
||||
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
|
||||
|
||||
<link href="<%= assetPath %>/stylesheets/ckeditor-theme.css" rel="stylesheet">
|
||||
<link href="api/fonts" rel="stylesheet">
|
||||
<link href="<%= assetPath %>/stylesheets/theme-light.css" rel="stylesheet">
|
||||
|
||||
<% if (themeCssUrl) { %>
|
||||
<link href="<%= themeCssUrl %>" rel="stylesheet">
|
||||
<% } %>
|
||||
|
||||
<% if (themeUseNextAsBase === "next") { %>
|
||||
<link href="<%= assetPath %>/stylesheets/theme-next.css" rel="stylesheet">
|
||||
<% } else if (themeUseNextAsBase === "next-dark") { %>
|
||||
<link href="<%= assetPath %>/stylesheets/theme-next-dark.css" rel="stylesheet">
|
||||
<% } else if (themeUseNextAsBase === "next-light") { %>
|
||||
<link href="<%= assetPath %>/stylesheets/theme-next-light.css" rel="stylesheet">
|
||||
<% } %>
|
||||
|
||||
<link href="<%= assetPath %>/stylesheets/style.css" rel="stylesheet">
|
||||
|
||||
<script src="<%= appPath %>/desktop.js" crossorigin type="module"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,6 +15,7 @@
|
||||
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/theme-light.css">
|
||||
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/theme-next.css">
|
||||
<link rel="stylesheet" href="<%= assetPath %>/stylesheets/style.css">
|
||||
<script src="<%= appPath %>/runtime.js" crossorigin type="module"></script>
|
||||
</head>
|
||||
<body lang="<%= currentLocale.id %>" dir="<%= currentLocale.rtl ? 'rtl' : 'ltr' %>">
|
||||
<div class="container login-page">
|
||||
|
||||
137
apps/server/src/assets/views/mobile.ejs
Normal file
137
apps/server/src/assets/views/mobile.ejs
Normal file
@@ -0,0 +1,137 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
|
||||
<meta name="theme-color" content="#fff">
|
||||
<title>Trilium Notes</title>
|
||||
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
|
||||
|
||||
<style>
|
||||
.lds-roller {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.lds-roller div {
|
||||
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
transform-origin: 40px 40px;
|
||||
}
|
||||
.lds-roller div:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: #000;
|
||||
margin: -4px 0 0 -4px;
|
||||
}
|
||||
.lds-roller div:nth-child(1) {
|
||||
animation-delay: -0.036s;
|
||||
}
|
||||
.lds-roller div:nth-child(1):after {
|
||||
top: 63px;
|
||||
left: 63px;
|
||||
}
|
||||
.lds-roller div:nth-child(2) {
|
||||
animation-delay: -0.072s;
|
||||
}
|
||||
.lds-roller div:nth-child(2):after {
|
||||
top: 68px;
|
||||
left: 56px;
|
||||
}
|
||||
.lds-roller div:nth-child(3) {
|
||||
animation-delay: -0.108s;
|
||||
}
|
||||
.lds-roller div:nth-child(3):after {
|
||||
top: 71px;
|
||||
left: 48px;
|
||||
}
|
||||
.lds-roller div:nth-child(4) {
|
||||
animation-delay: -0.144s;
|
||||
}
|
||||
.lds-roller div:nth-child(4):after {
|
||||
top: 72px;
|
||||
left: 40px;
|
||||
}
|
||||
.lds-roller div:nth-child(5) {
|
||||
animation-delay: -0.18s;
|
||||
}
|
||||
.lds-roller div:nth-child(5):after {
|
||||
top: 71px;
|
||||
left: 32px;
|
||||
}
|
||||
.lds-roller div:nth-child(6) {
|
||||
animation-delay: -0.216s;
|
||||
}
|
||||
.lds-roller div:nth-child(6):after {
|
||||
top: 68px;
|
||||
left: 24px;
|
||||
}
|
||||
.lds-roller div:nth-child(7) {
|
||||
animation-delay: -0.252s;
|
||||
}
|
||||
.lds-roller div:nth-child(7):after {
|
||||
top: 63px;
|
||||
left: 17px;
|
||||
}
|
||||
.lds-roller div:nth-child(8) {
|
||||
animation-delay: -0.288s;
|
||||
}
|
||||
.lds-roller div:nth-child(8):after {
|
||||
top: 56px;
|
||||
left: 12px;
|
||||
}
|
||||
@keyframes lds-roller {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style id="trilium-icon-packs">
|
||||
<%- iconPackCss %>
|
||||
</style>
|
||||
<script src="<%= appPath %>/runtime.js" crossorigin type="module"></script>
|
||||
</head>
|
||||
<body
|
||||
class="mobile heading-style-<%= headingStyle %>"
|
||||
lang="<%= currentLocale.id %>" dir="<%= currentLocale.rtl ? 'rtl' : 'ltr' %>"
|
||||
>
|
||||
<noscript><%= t("javascript-required") %></noscript>
|
||||
|
||||
<div id="context-menu-cover"></div>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
||||
|
||||
<%- include("./partials/windowGlobal.ejs", locals) %>
|
||||
|
||||
<script src="<%= appPath %>/mobile.js" crossorigin type="module"></script>
|
||||
|
||||
<link href="api/fonts" rel="stylesheet">
|
||||
<link href="<%= assetPath %>/stylesheets/ckeditor-theme.css" rel="stylesheet">
|
||||
<link href="<%= assetPath %>/stylesheets/theme-light.css" rel="stylesheet">
|
||||
<% if (themeCssUrl) { %>
|
||||
<link href="<%= themeCssUrl %>" rel="stylesheet">
|
||||
<% } %>
|
||||
|
||||
<% if (themeUseNextAsBase === "next") { %>
|
||||
<link href="<%= assetPath %>/stylesheets/theme-next.css" rel="stylesheet">
|
||||
<% } else if (themeUseNextAsBase === "next-dark") { %>
|
||||
<link href="<%= assetPath %>/stylesheets/theme-next-dark.css" rel="stylesheet">
|
||||
<% } else if (themeUseNextAsBase === "next-light") { %>
|
||||
<link href="<%= assetPath %>/stylesheets/theme-next-light.css" rel="stylesheet">
|
||||
<% } %>
|
||||
|
||||
<link href="<%= assetPath %>/stylesheets/style.css" rel="stylesheet">
|
||||
|
||||
</body>
|
||||
</html>
|
||||
25
apps/server/src/assets/views/partials/windowGlobal.ejs
Normal file
25
apps/server/src/assets/views/partials/windowGlobal.ejs
Normal file
@@ -0,0 +1,25 @@
|
||||
<script type="text/javascript">
|
||||
global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
|
||||
|
||||
window.glob = {
|
||||
device: "<%= device %>",
|
||||
baseApiUrl: "<%= baseApiUrl %>",
|
||||
activeDialog: null,
|
||||
maxEntityChangeIdAtLoad: <%= maxEntityChangeIdAtLoad %>,
|
||||
maxEntityChangeSyncIdAtLoad: <%= maxEntityChangeSyncIdAtLoad %>,
|
||||
instanceName: '<%= instanceName %>',
|
||||
csrfToken: '<%= csrfToken %>',
|
||||
isDev: <%= isDev %>,
|
||||
appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>,
|
||||
isMainWindow: <%= isMainWindow %>,
|
||||
isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>,
|
||||
triliumVersion: "<%= triliumVersion %>",
|
||||
assetPath: "<%= assetPath %>",
|
||||
appPath: "<%= appPath %>",
|
||||
platform: "<%= platform %>",
|
||||
hasNativeTitleBar: <%= hasNativeTitleBar %>,
|
||||
TRILIUM_SAFE_MODE: <%= !!process.env.TRILIUM_SAFE_MODE %>,
|
||||
isRtl: <%= !!currentLocale.rtl %>,
|
||||
iconRegistry: <%- JSON.stringify(iconRegistry) %>
|
||||
};
|
||||
</script>
|
||||
@@ -1,13 +1,10 @@
|
||||
import express from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import { existsSync } from "fs";
|
||||
import path from "path";
|
||||
import type serveStatic from "serve-static";
|
||||
|
||||
import { assetUrlFragment } from "../services/asset_path.js";
|
||||
import auth from "../services/auth.js";
|
||||
import { getResourceDir, isDev } from "../services/utils.js";
|
||||
import { doubleCsrfProtection as csrfMiddleware } from "./csrf_protection.js";
|
||||
|
||||
const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOptions<express.Response<unknown, Record<string, unknown>>>) => {
|
||||
if (!isDev) {
|
||||
@@ -23,29 +20,19 @@ async function register(app: express.Application) {
|
||||
const srcRoot = path.join(__dirname, "..", "..");
|
||||
const resourceDir = getResourceDir();
|
||||
|
||||
const rootLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 100 // limit each IP to 100 requests per windowMs
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const { createServer: createViteServer } = await import("vite");
|
||||
const clientDir = path.join(srcRoot, "../client");
|
||||
const vite = await createViteServer({
|
||||
server: { middlewareMode: true },
|
||||
appType: "spa",
|
||||
appType: "custom",
|
||||
cacheDir: path.join(srcRoot, "../../.cache/vite"),
|
||||
base: `/${assetUrlFragment}/`,
|
||||
root: clientDir,
|
||||
css: { devSourcemap: true }
|
||||
});
|
||||
app.use(`/${assetUrlFragment}/`, vite.middlewares);
|
||||
app.get(`/`, [ rootLimiter, auth.checkAuth, csrfMiddleware ], (req, res, next) => {
|
||||
req.url = `/${assetUrlFragment}/src/index.html`;
|
||||
vite.middlewares(req, res, next);
|
||||
});
|
||||
app.get(`/index.ts`, (req, res, next) => {
|
||||
req.url = `/${assetUrlFragment}/src/index.ts`;
|
||||
app.use(`/${assetUrlFragment}/`, (req, res, next) => {
|
||||
req.url = `/${assetUrlFragment}${req.url}`;
|
||||
vite.middlewares(req, res, next);
|
||||
});
|
||||
app.use(`/node_modules/@excalidraw/excalidraw/dist/prod`, persistentCacheStatic(path.join(srcRoot, "../../node_modules/@excalidraw/excalidraw/dist/prod")));
|
||||
@@ -55,14 +42,7 @@ async function register(app: express.Application) {
|
||||
throw new Error(`Public directory is missing at: ${path.resolve(publicDir)}`);
|
||||
}
|
||||
|
||||
app.get(`/`, [ rootLimiter, auth.checkAuth, csrfMiddleware ], (_, res) => {
|
||||
// We force the page to not be cached since on mobile the CSRF token can be
|
||||
// broken when closing the browser and coming back in to the page.
|
||||
// The page is restored from cache, but the API call fail.
|
||||
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
res.sendFile(path.join(publicDir, "src", "index.html"));
|
||||
});
|
||||
app.use("/assets", persistentCacheStatic(path.join(publicDir, "assets")));
|
||||
app.use(`/${assetUrlFragment}/src`, persistentCacheStatic(path.join(publicDir, "src")));
|
||||
app.use(`/${assetUrlFragment}/stylesheets`, persistentCacheStatic(path.join(publicDir, "stylesheets")));
|
||||
app.use(`/${assetUrlFragment}/fonts`, persistentCacheStatic(path.join(publicDir, "fonts")));
|
||||
app.use(`/${assetUrlFragment}/translations/`, persistentCacheStatic(path.join(publicDir, "translations")));
|
||||
|
||||
@@ -15,10 +15,10 @@ import sql from "../services/sql.js";
|
||||
import { isDev, isElectron, isWindows11 } from "../services/utils.js";
|
||||
import { generateToken as generateCsrfToken } from "./csrf_protection.js";
|
||||
|
||||
|
||||
type View = "desktop" | "mobile" | "print";
|
||||
|
||||
export function bootstrap(req: Request, res: Response) {
|
||||
function index(req: Request, res: Response) {
|
||||
const view = getView(req);
|
||||
const options = optionService.getOptionMap();
|
||||
|
||||
//'overwrite' set to false (default) => the existing token will be re-used and validated
|
||||
@@ -26,14 +26,17 @@ export function bootstrap(req: Request, res: Response) {
|
||||
const csrfToken = generateCsrfToken(req, res, false, false);
|
||||
log.info(`CSRF token generation: ${csrfToken ? "Successful" : "Failed"}`);
|
||||
|
||||
const view = getView(req);
|
||||
// We force the page to not be cached since on mobile the CSRF token can be
|
||||
// broken when closing the browser and coming back in to the page.
|
||||
// The page is restored from cache, but the API call fail.
|
||||
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
|
||||
const theme = options.theme;
|
||||
const themeNote = attributeService.getNoteWithLabel("appTheme", theme);
|
||||
const nativeTitleBarVisible = options.nativeTitleBarVisible === "true";
|
||||
const iconPacks = getIconPacks();
|
||||
const currentLocale = getCurrentLocale();
|
||||
|
||||
res.send({
|
||||
res.render(view, {
|
||||
device: view,
|
||||
csrfToken,
|
||||
themeCssUrl: getThemeCssUrl(theme, themeNote),
|
||||
@@ -44,6 +47,9 @@ export function bootstrap(req: Request, res: Response) {
|
||||
isElectron,
|
||||
hasNativeTitleBar: isElectron && nativeTitleBarVisible,
|
||||
hasBackgroundEffects: isElectron && isWindows11 && !nativeTitleBarVisible && options.backgroundEffects === "true",
|
||||
mainFontSize: parseInt(options.mainFontSize),
|
||||
treeFontSize: parseInt(options.treeFontSize),
|
||||
detailFontSize: parseInt(options.detailFontSize),
|
||||
maxEntityChangeIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes"),
|
||||
maxEntityChangeSyncIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1"),
|
||||
instanceName: config.General ? config.General.instanceName : null,
|
||||
@@ -55,16 +61,14 @@ export function bootstrap(req: Request, res: Response) {
|
||||
assetPath,
|
||||
appPath,
|
||||
baseApiUrl: 'api/',
|
||||
currentLocale,
|
||||
isRtl: !!currentLocale.rtl,
|
||||
currentLocale: getCurrentLocale(),
|
||||
iconPackCss: iconPacks
|
||||
.map(p => generateCss(p, p.builtin
|
||||
? `${assetPath}/fonts/${p.fontAttachmentId}.${MIME_TO_EXTENSION_MAPPINGS[p.fontMime]}`
|
||||
: `api/attachments/download/${p.fontAttachmentId}`))
|
||||
.filter(Boolean)
|
||||
.join("\n\n"),
|
||||
iconRegistry: generateIconRegistry(iconPacks),
|
||||
TRILIUM_SAFE_MODE: !!process.env.TRILIUM_SAFE_MODE
|
||||
iconRegistry: generateIconRegistry(iconPacks)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -129,3 +133,7 @@ function getThemeCssUrl(theme: string, themeNote: BNote | null) {
|
||||
function getAppCssNoteIds() {
|
||||
return attributeService.getNotesWithLabel("appCss").map((note) => note.noteId);
|
||||
}
|
||||
|
||||
export default {
|
||||
index
|
||||
};
|
||||
|
||||
@@ -1,73 +1,76 @@
|
||||
import { createPartialContentHandler } from "@triliumnext/express-partial-content";
|
||||
import { isElectron } from "../services/utils.js";
|
||||
import express from "express";
|
||||
|
||||
import auth from "../services/auth.js";
|
||||
import openID from '../services/open_id.js';
|
||||
import totp from './api/totp.js';
|
||||
import recoveryCodes from './api/recovery_codes.js';
|
||||
import { doubleCsrfProtection as csrfMiddleware } from "./csrf_protection.js";
|
||||
import { createPartialContentHandler } from "@triliumnext/express-partial-content";
|
||||
import rateLimit from "express-rate-limit";
|
||||
|
||||
// page routes
|
||||
import setupRoute from "./setup.js";
|
||||
import loginRoute from "./login.js";
|
||||
import indexRoute from "./index.js";
|
||||
|
||||
// API routes
|
||||
import treeApiRoute from "./api/tree.js";
|
||||
import notesApiRoute from "./api/notes.js";
|
||||
import branchesApiRoute from "./api/branches.js";
|
||||
import attachmentsApiRoute from "./api/attachments.js";
|
||||
import autocompleteApiRoute from "./api/autocomplete.js";
|
||||
import cloningApiRoute from "./api/cloning.js";
|
||||
import revisionsApiRoute from "./api/revisions.js";
|
||||
import recentChangesApiRoute from "./api/recent_changes.js";
|
||||
import optionsApiRoute from "./api/options.js";
|
||||
import passwordApiRoute from "./api/password.js";
|
||||
import syncApiRoute from "./api/sync.js";
|
||||
import loginApiRoute from "./api/login.js";
|
||||
import recentNotesRoute from "./api/recent_notes.js";
|
||||
import appInfoRoute from "./api/app_info.js";
|
||||
import exportRoute from "./api/export.js";
|
||||
import importRoute from "./api/import.js";
|
||||
import setupApiRoute from "./api/setup.js";
|
||||
import sqlRoute from "./api/sql.js";
|
||||
import databaseRoute from "./api/database.js";
|
||||
import imageRoute from "./api/image.js";
|
||||
import attributesRoute from "./api/attributes.js";
|
||||
import scriptRoute from "./api/script.js";
|
||||
import senderRoute from "./api/sender.js";
|
||||
import filesRoute from "./api/files.js";
|
||||
import searchRoute from "./api/search.js";
|
||||
import bulkActionRoute from "./api/bulk_action.js";
|
||||
import specialNotesRoute from "./api/special_notes.js";
|
||||
import noteMapRoute from "./api/note_map.js";
|
||||
import clipperRoute from "./api/clipper.js";
|
||||
import similarNotesRoute from "./api/similar_notes.js";
|
||||
import keysRoute from "./api/keys.js";
|
||||
import backendLogRoute from "./api/backend_log.js";
|
||||
import statsRoute from "./api/stats.js";
|
||||
import fontsRoute from "./api/fonts.js";
|
||||
import etapiTokensApiRoutes from "./api/etapi_tokens.js";
|
||||
import relationMapApiRoute from "./api/relation-map.js";
|
||||
import otherRoute from "./api/other.js";
|
||||
import metricsRoute from "./api/metrics.js";
|
||||
import shareRoutes from "../share/routes.js";
|
||||
import ollamaRoute from "./api/ollama.js";
|
||||
import openaiRoute from "./api/openai.js";
|
||||
import anthropicRoute from "./api/anthropic.js";
|
||||
import llmRoute from "./api/llm.js";
|
||||
import systemInfoRoute from "./api/system_info.js";
|
||||
|
||||
import etapiAuthRoutes from "../etapi/auth.js";
|
||||
import etapiAppInfoRoutes from "../etapi/app_info.js";
|
||||
import etapiAttachmentRoutes from "../etapi/attachments.js";
|
||||
import etapiAttributeRoutes from "../etapi/attributes.js";
|
||||
import etapiAuthRoutes from "../etapi/auth.js";
|
||||
import etapiBackupRoute from "../etapi/backup.js";
|
||||
import etapiBranchRoutes from "../etapi/branches.js";
|
||||
import etapiMetricsRoute from "../etapi/metrics.js";
|
||||
import etapiNoteRoutes from "../etapi/notes.js";
|
||||
import etapiSpecRoute from "../etapi/spec.js";
|
||||
import etapiSpecialNoteRoutes from "../etapi/special_notes.js";
|
||||
import auth from "../services/auth.js";
|
||||
import openID from '../services/open_id.js';
|
||||
import { isElectron } from "../services/utils.js";
|
||||
import shareRoutes from "../share/routes.js";
|
||||
import anthropicRoute from "./api/anthropic.js";
|
||||
import appInfoRoute from "./api/app_info.js";
|
||||
import attachmentsApiRoute from "./api/attachments.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";
|
||||
import databaseRoute from "./api/database.js";
|
||||
import etapiTokensApiRoutes from "./api/etapi_tokens.js";
|
||||
import exportRoute from "./api/export.js";
|
||||
import filesRoute from "./api/files.js";
|
||||
import fontsRoute from "./api/fonts.js";
|
||||
import imageRoute from "./api/image.js";
|
||||
import importRoute from "./api/import.js";
|
||||
import keysRoute from "./api/keys.js";
|
||||
import llmRoute from "./api/llm.js";
|
||||
import loginApiRoute from "./api/login.js";
|
||||
import metricsRoute from "./api/metrics.js";
|
||||
import noteMapRoute from "./api/note_map.js";
|
||||
import notesApiRoute from "./api/notes.js";
|
||||
import ollamaRoute from "./api/ollama.js";
|
||||
import openaiRoute from "./api/openai.js";
|
||||
import optionsApiRoute from "./api/options.js";
|
||||
import otherRoute from "./api/other.js";
|
||||
import passwordApiRoute from "./api/password.js";
|
||||
import recentChangesApiRoute from "./api/recent_changes.js";
|
||||
import recentNotesRoute from "./api/recent_notes.js";
|
||||
import recoveryCodes from './api/recovery_codes.js';
|
||||
import relationMapApiRoute from "./api/relation-map.js";
|
||||
import revisionsApiRoute from "./api/revisions.js";
|
||||
import scriptRoute from "./api/script.js";
|
||||
import searchRoute from "./api/search.js";
|
||||
import senderRoute from "./api/sender.js";
|
||||
import setupApiRoute from "./api/setup.js";
|
||||
import similarNotesRoute from "./api/similar_notes.js";
|
||||
import specialNotesRoute from "./api/special_notes.js";
|
||||
import sqlRoute from "./api/sql.js";
|
||||
import statsRoute from "./api/stats.js";
|
||||
import syncApiRoute from "./api/sync.js";
|
||||
import systemInfoRoute from "./api/system_info.js";
|
||||
import totp from './api/totp.js';
|
||||
// API routes
|
||||
import treeApiRoute from "./api/tree.js";
|
||||
import { doubleCsrfProtection as csrfMiddleware } from "./csrf_protection.js";
|
||||
import * as indexRoute from "./index.js";
|
||||
import loginRoute from "./login.js";
|
||||
import etapiSpecRoute from "../etapi/spec.js";
|
||||
import etapiBackupRoute from "../etapi/backup.js";
|
||||
import etapiMetricsRoute from "../etapi/metrics.js";
|
||||
import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js";
|
||||
// page routes
|
||||
import setupRoute from "./setup.js";
|
||||
|
||||
const GET = "get",
|
||||
PST = "post",
|
||||
@@ -76,6 +79,7 @@ const GET = "get",
|
||||
DEL = "delete";
|
||||
|
||||
function register(app: express.Application) {
|
||||
route(GET, "/", [auth.checkAuth, csrfMiddleware], indexRoute.index);
|
||||
route(GET, "/login", [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage);
|
||||
route(GET, "/set-password", [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage);
|
||||
|
||||
@@ -85,7 +89,6 @@ function register(app: express.Application) {
|
||||
skipSuccessfulRequests: true // successful auth to rate-limited ETAPI routes isn't counted. However, successful auth to /login is still counted!
|
||||
});
|
||||
|
||||
route(GET, "/bootstrap", [ auth.checkAuth ], indexRoute.bootstrap);
|
||||
route(PST, "/login", [loginRateLimiter], loginRoute.login);
|
||||
route(PST, "/logout", [csrfMiddleware, auth.checkAuth], loginRoute.logout);
|
||||
route(PST, "/set-password", [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPassword);
|
||||
|
||||
@@ -115,7 +115,7 @@
|
||||
},
|
||||
"social_buttons": {
|
||||
"github": "GitHub",
|
||||
"github_discussions": "GitHub Discussions",
|
||||
"github_discussions": "Discusiones de GitHub",
|
||||
"matrix": "Matrix",
|
||||
"reddit": "Reddit"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
},
|
||||
"hero_section": {
|
||||
"title": "अपने विचारों को व्यवस्थित करें। अपना व्यक्तिगत नॉलेज बेस बनाएं।",
|
||||
"screenshot_alt": "ट्रिलियम नोट्स डेस्कटॉप एप्लिकेशन का स्क्रीनशॉट"
|
||||
"screenshot_alt": "ट्रिलियम नोट्स डेस्कटॉप एप्लिकेशन का स्क्रीनशॉट",
|
||||
"get_started": "शुरू करें",
|
||||
"github": "गिटहब"
|
||||
},
|
||||
"organization_benefits": {
|
||||
"note_structure_title": "नोट संरचना",
|
||||
@@ -26,5 +28,67 @@
|
||||
"collections": {
|
||||
"calendar_title": "कैलेंडर",
|
||||
"table_title": "टेबल"
|
||||
},
|
||||
"download_now": {
|
||||
"linux_small": "लिनक्स के लिए",
|
||||
"more_platforms": "अधिक प्लेटफॉर्म और सर्वर सेटअप"
|
||||
},
|
||||
"header": {
|
||||
"get-started": "शुरू करें",
|
||||
"support-us": "हमें सपोर्ट करें"
|
||||
},
|
||||
"social_buttons": {
|
||||
"github": "गिटहब",
|
||||
"matrix": "मैट्रिक्स",
|
||||
"reddit": "रेडिट"
|
||||
},
|
||||
"support_us": {
|
||||
"title": "हमें सपोर्ट करें"
|
||||
},
|
||||
"404": {
|
||||
"description": "आप जो पेज खोज रहे थे वह नहीं मिल पाया। शायद वह डिलीट हो चुका है या यूआरएल (URL) गलत है।"
|
||||
},
|
||||
"download_helper_desktop_windows": {
|
||||
"title_x64": "Windows 64-bit",
|
||||
"title_arm64": "ARM पर Windows",
|
||||
"quick_start": "Winget द्वारा इंस्टॉल करने के लिए:",
|
||||
"download_exe": "इंस्टॉलर (.exe) डाउनलोड करें",
|
||||
"download_zip": "पोर्टेबल (.zip)"
|
||||
},
|
||||
"download_helper_desktop_linux": {
|
||||
"title_x64": "Linux 64-bit",
|
||||
"title_arm64": "ARM पर लिनक्स",
|
||||
"download_deb": ".deb",
|
||||
"download_rpm": ".rpm",
|
||||
"download_flatpak": ".flatpak",
|
||||
"download_zip": "पोर्टेबल (.zip)",
|
||||
"download_nixpkgs": "nixpkgs"
|
||||
},
|
||||
"download_helper_desktop_macos": {
|
||||
"title_x64": "Intel के लिए macOS",
|
||||
"title_arm64": "Apple Silicon के लिए macOS",
|
||||
"description_x64": "macOS Monterey या उसके बाद के वर्ज़न पर चलने वाले Intel-आधारित Macs के लिए।",
|
||||
"description_arm64": "Apple Silicon Macs के लिए, जैसे कि M1 और M2 चिप्स वाले मॉडल।",
|
||||
"quick_start": "Homebrew द्वारा इंस्टॉल करने के लिए:",
|
||||
"download_dmg": "इंस्टॉलर (.dmg) डाउनलोड करें",
|
||||
"download_homebrew_cask": "Homebrew Cask",
|
||||
"download_zip": "पोर्टेबल (.zip)"
|
||||
},
|
||||
"download_helper_server_docker": {
|
||||
"title": "Docker द्वारा सेल्फ-होस्टेड",
|
||||
"description": "Docker कंटेनर का उपयोग करके Windows, Linux या macOS पर आसानी से डिप्लॉय करें।",
|
||||
"download_ghcr": "ghcr.io"
|
||||
},
|
||||
"download_helper_server_linux": {
|
||||
"title": "Linux पर सेल्फ-होस्टेड",
|
||||
"description": "ट्रिलियम नोट्स को अपने खुद के सर्वर या VPS पर डिप्लॉय करें, जो अधिकांश डिस्ट्रीब्यूशनो के साथ कम्पेटिबल है।",
|
||||
"download_tar_x64": "x64 (.tar.xz)",
|
||||
"download_tar_arm64": "ARM (.tar.xz)",
|
||||
"download_nixos": "NixOS मॉड्यूल"
|
||||
},
|
||||
"download_helper_server_hosted": {
|
||||
"title": "पेड होस्टिंग",
|
||||
"download_pikapod": "PikaPods पर सेटअप करें",
|
||||
"download_triliumcc": "वैकल्पिक रूप से trilium.cc देखें"
|
||||
}
|
||||
}
|
||||
|
||||
150
docs/Release Notes/!!!meta.json
vendored
150
docs/Release Notes/!!!meta.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"formatVersion": 2,
|
||||
"appVersion": "0.101.1",
|
||||
"appVersion": "0.101.3",
|
||||
"files": [
|
||||
{
|
||||
"isClone": false,
|
||||
@@ -61,6 +61,58 @@
|
||||
"attachments": [],
|
||||
"dirFileName": "Release Notes",
|
||||
"children": [
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "IlBzLeN3MJhw",
|
||||
"notePath": [
|
||||
"hD3V4hiu2VW4",
|
||||
"IlBzLeN3MJhw"
|
||||
],
|
||||
"title": "v0.101.3",
|
||||
"notePosition": 10,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "template",
|
||||
"value": "wyurrlcDl416",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "v0.101.3.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "vcBthaXcwAm6",
|
||||
"notePath": [
|
||||
"hD3V4hiu2VW4",
|
||||
"vcBthaXcwAm6"
|
||||
],
|
||||
"title": "v0.101.2",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "template",
|
||||
"value": "wyurrlcDl416",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "v0.101.2.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "AgUcrU9nFXuW",
|
||||
@@ -69,7 +121,7 @@
|
||||
"AgUcrU9nFXuW"
|
||||
],
|
||||
"title": "v0.101.1",
|
||||
"notePosition": 10,
|
||||
"notePosition": 30,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -95,7 +147,7 @@
|
||||
"uYwlZ594eyJu"
|
||||
],
|
||||
"title": "v0.101.0",
|
||||
"notePosition": 20,
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -121,7 +173,7 @@
|
||||
"iPGKEk7pwJXK"
|
||||
],
|
||||
"title": "v0.100.0",
|
||||
"notePosition": 30,
|
||||
"notePosition": 50,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -147,7 +199,7 @@
|
||||
"7HKMTjmopLcM"
|
||||
],
|
||||
"title": "v0.99.5",
|
||||
"notePosition": 40,
|
||||
"notePosition": 60,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -173,7 +225,7 @@
|
||||
"RMBaNYPsRpIr"
|
||||
],
|
||||
"title": "v0.99.4",
|
||||
"notePosition": 50,
|
||||
"notePosition": 70,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -199,7 +251,7 @@
|
||||
"yuroLztFfpu5"
|
||||
],
|
||||
"title": "v0.99.3",
|
||||
"notePosition": 60,
|
||||
"notePosition": 80,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -225,7 +277,7 @@
|
||||
"z207sehwMJ6C"
|
||||
],
|
||||
"title": "v0.99.2",
|
||||
"notePosition": 70,
|
||||
"notePosition": 90,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -251,7 +303,7 @@
|
||||
"WGQsXq2jNyTi"
|
||||
],
|
||||
"title": "v0.99.1",
|
||||
"notePosition": 80,
|
||||
"notePosition": 100,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -277,7 +329,7 @@
|
||||
"cyw2Yue9vXf3"
|
||||
],
|
||||
"title": "v0.99.0",
|
||||
"notePosition": 90,
|
||||
"notePosition": 110,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -303,7 +355,7 @@
|
||||
"QOJwjruOUr4k"
|
||||
],
|
||||
"title": "v0.98.1",
|
||||
"notePosition": 100,
|
||||
"notePosition": 120,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -329,7 +381,7 @@
|
||||
"PLUoryywi0BC"
|
||||
],
|
||||
"title": "v0.98.0",
|
||||
"notePosition": 110,
|
||||
"notePosition": 130,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -355,7 +407,7 @@
|
||||
"lvOuiWsLDv8F"
|
||||
],
|
||||
"title": "v0.97.2",
|
||||
"notePosition": 120,
|
||||
"notePosition": 140,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -381,7 +433,7 @@
|
||||
"OtFZ6Nd9vM3n"
|
||||
],
|
||||
"title": "v0.97.1",
|
||||
"notePosition": 130,
|
||||
"notePosition": 150,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -407,7 +459,7 @@
|
||||
"SJZ5PwfzHSQ1"
|
||||
],
|
||||
"title": "v0.97.0",
|
||||
"notePosition": 140,
|
||||
"notePosition": 160,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -433,7 +485,7 @@
|
||||
"mYXFde3LuNR7"
|
||||
],
|
||||
"title": "v0.96.0",
|
||||
"notePosition": 150,
|
||||
"notePosition": 170,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -459,7 +511,7 @@
|
||||
"jthwbL0FdaeU"
|
||||
],
|
||||
"title": "v0.95.0",
|
||||
"notePosition": 160,
|
||||
"notePosition": 180,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -485,7 +537,7 @@
|
||||
"7HGYsJbLuhnv"
|
||||
],
|
||||
"title": "v0.94.1",
|
||||
"notePosition": 170,
|
||||
"notePosition": 190,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -511,7 +563,7 @@
|
||||
"Neq53ujRGBqv"
|
||||
],
|
||||
"title": "v0.94.0",
|
||||
"notePosition": 180,
|
||||
"notePosition": 200,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -537,7 +589,7 @@
|
||||
"VN3xnce1vLkX"
|
||||
],
|
||||
"title": "v0.93.0",
|
||||
"notePosition": 190,
|
||||
"notePosition": 210,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -555,7 +607,7 @@
|
||||
"WRaBfQqPr6qo"
|
||||
],
|
||||
"title": "v0.92.7",
|
||||
"notePosition": 200,
|
||||
"notePosition": 220,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -581,7 +633,7 @@
|
||||
"a2rwfKNmUFU1"
|
||||
],
|
||||
"title": "v0.92.6",
|
||||
"notePosition": 210,
|
||||
"notePosition": 230,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -599,7 +651,7 @@
|
||||
"fEJ8qErr0BKL"
|
||||
],
|
||||
"title": "v0.92.5-beta",
|
||||
"notePosition": 220,
|
||||
"notePosition": 240,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -617,7 +669,7 @@
|
||||
"kkkZQQGSXjwy"
|
||||
],
|
||||
"title": "v0.92.4",
|
||||
"notePosition": 230,
|
||||
"notePosition": 250,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -635,7 +687,7 @@
|
||||
"vAroNixiezaH"
|
||||
],
|
||||
"title": "v0.92.3-beta",
|
||||
"notePosition": 240,
|
||||
"notePosition": 260,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -653,7 +705,7 @@
|
||||
"mHEq1wxAKNZd"
|
||||
],
|
||||
"title": "v0.92.2-beta",
|
||||
"notePosition": 250,
|
||||
"notePosition": 270,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -671,7 +723,7 @@
|
||||
"IykjoAmBpc61"
|
||||
],
|
||||
"title": "v0.92.1-beta",
|
||||
"notePosition": 260,
|
||||
"notePosition": 280,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -689,7 +741,7 @@
|
||||
"dq2AJ9vSBX4Y"
|
||||
],
|
||||
"title": "v0.92.0-beta",
|
||||
"notePosition": 270,
|
||||
"notePosition": 290,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -707,7 +759,7 @@
|
||||
"3a8aMe4jz4yM"
|
||||
],
|
||||
"title": "v0.91.6",
|
||||
"notePosition": 280,
|
||||
"notePosition": 300,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -725,7 +777,7 @@
|
||||
"8djQjkiDGESe"
|
||||
],
|
||||
"title": "v0.91.5",
|
||||
"notePosition": 290,
|
||||
"notePosition": 310,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -743,7 +795,7 @@
|
||||
"OylxVoVJqNmr"
|
||||
],
|
||||
"title": "v0.91.4-beta",
|
||||
"notePosition": 300,
|
||||
"notePosition": 320,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -761,7 +813,7 @@
|
||||
"tANGQDvnyhrj"
|
||||
],
|
||||
"title": "v0.91.3-beta",
|
||||
"notePosition": 310,
|
||||
"notePosition": 330,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -779,7 +831,7 @@
|
||||
"hMoBfwSoj1SC"
|
||||
],
|
||||
"title": "v0.91.2-beta",
|
||||
"notePosition": 320,
|
||||
"notePosition": 340,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -797,7 +849,7 @@
|
||||
"a2XMSKROCl9z"
|
||||
],
|
||||
"title": "v0.91.1-beta",
|
||||
"notePosition": 330,
|
||||
"notePosition": 350,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -815,7 +867,7 @@
|
||||
"yqXFvWbLkuMD"
|
||||
],
|
||||
"title": "v0.90.12",
|
||||
"notePosition": 340,
|
||||
"notePosition": 360,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -833,7 +885,7 @@
|
||||
"veS7pg311yJP"
|
||||
],
|
||||
"title": "v0.90.11-beta",
|
||||
"notePosition": 350,
|
||||
"notePosition": 370,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -851,7 +903,7 @@
|
||||
"sq5W9TQxRqMq"
|
||||
],
|
||||
"title": "v0.90.10-beta",
|
||||
"notePosition": 360,
|
||||
"notePosition": 380,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -869,7 +921,7 @@
|
||||
"yFEGVCUM9tPx"
|
||||
],
|
||||
"title": "v0.90.9-beta",
|
||||
"notePosition": 370,
|
||||
"notePosition": 390,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -887,7 +939,7 @@
|
||||
"o4wAGqOQuJtV"
|
||||
],
|
||||
"title": "v0.90.8",
|
||||
"notePosition": 380,
|
||||
"notePosition": 400,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -920,7 +972,7 @@
|
||||
"i4A5g9iOg9I0"
|
||||
],
|
||||
"title": "v0.90.7-beta",
|
||||
"notePosition": 390,
|
||||
"notePosition": 410,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -938,7 +990,7 @@
|
||||
"ThNf2GaKgXUs"
|
||||
],
|
||||
"title": "v0.90.6-beta",
|
||||
"notePosition": 400,
|
||||
"notePosition": 420,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -956,7 +1008,7 @@
|
||||
"G4PAi554kQUr"
|
||||
],
|
||||
"title": "v0.90.5-beta",
|
||||
"notePosition": 410,
|
||||
"notePosition": 430,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -983,7 +1035,7 @@
|
||||
"zATRobGRCmBn"
|
||||
],
|
||||
"title": "v0.90.4",
|
||||
"notePosition": 420,
|
||||
"notePosition": 440,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -1001,7 +1053,7 @@
|
||||
"sCDLf8IKn3Iz"
|
||||
],
|
||||
"title": "v0.90.3",
|
||||
"notePosition": 430,
|
||||
"notePosition": 450,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -1019,7 +1071,7 @@
|
||||
"VqqyBu4AuTjC"
|
||||
],
|
||||
"title": "v0.90.2-beta",
|
||||
"notePosition": 440,
|
||||
"notePosition": 460,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -1037,7 +1089,7 @@
|
||||
"RX3Nl7wInLsA"
|
||||
],
|
||||
"title": "v0.90.1-beta",
|
||||
"notePosition": 450,
|
||||
"notePosition": 470,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -1055,7 +1107,7 @@
|
||||
"GyueACukPWjk"
|
||||
],
|
||||
"title": "v0.90.0-beta",
|
||||
"notePosition": 460,
|
||||
"notePosition": 480,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -1073,7 +1125,7 @@
|
||||
"kzjHexDTTeVB"
|
||||
],
|
||||
"title": "v0.48",
|
||||
"notePosition": 470,
|
||||
"notePosition": 490,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
@@ -1140,7 +1192,7 @@
|
||||
"wyurrlcDl416"
|
||||
],
|
||||
"title": "Release Template",
|
||||
"notePosition": 480,
|
||||
"notePosition": 500,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
|
||||
22
docs/Release Notes/Release Notes/v0.101.2.md
vendored
Normal file
22
docs/Release Notes/Release Notes/v0.101.2.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# v0.101.2
|
||||
> [!NOTE]
|
||||
> If you are interested in an [official mobile application](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/7447) ([#7447](https://github.com/TriliumNext/Trilium/issues/7447)) or [multi-user support](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/4956) ([#4956](https://github.com/TriliumNext/Trilium/issues/4956)), consider offering financial support via IssueHunt (see links).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you enjoyed this release, consider showing a token of appreciation by:
|
||||
>
|
||||
> * Pressing the “Star” button on [GitHub](https://github.com/TriliumNext/Trilium) (top-right).
|
||||
> * Considering a one-time or recurrent donation to the [lead developer](https://github.com/eliandoran) via [GitHub Sponsors](https://github.com/sponsors/eliandoran) or [PayPal](https://paypal.me/eliandoran).
|
||||
|
||||
## 🐞 Bugfixes
|
||||
|
||||
* [SQL Console: cannot copy table data](https://github.com/TriliumNext/Trilium/pull/8268) by @SiriusXT
|
||||
* [Title is not selected when creating a note via the launcher](https://github.com/TriliumNext/Trilium/pull/8292) by @SiriusXT
|
||||
* [Popup editor closing after inserting a note link](https://github.com/TriliumNext/Trilium/pull/8224) by @SiriusXT
|
||||
* [New Mermaid diagrams do not save content](https://github.com/TriliumNext/Trilium/pull/8220) by @lzinga
|
||||
* [Can't scroll mermaid diagram code](https://github.com/TriliumNext/Trilium/issues/8299)
|
||||
* [Max content width is not respected when switching between note types in the same tab](https://github.com/TriliumNext/Trilium/issues/8065)
|
||||
* [Crash When a Note Includes Itself](https://github.com/TriliumNext/Trilium/issues/8294)
|
||||
* [Severe Performance Degradation and Crash Issues Due to Recursive Inclusion in Included Notes](https://github.com/TriliumNext/Trilium/issues/8017)
|
||||
* [<note> is not a launcher even though it's in the launcher subtree](https://github.com/TriliumNext/Trilium/issues/8218)
|
||||
* [Archived subnotes of direct children appear in grid view without #includeArchived](https://github.com/TriliumNext/Trilium/issues/8184)
|
||||
24
docs/Release Notes/Release Notes/v0.101.3.md
vendored
Normal file
24
docs/Release Notes/Release Notes/v0.101.3.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# v0.101.3
|
||||
> [!NOTE]
|
||||
> If you are interested in an [official mobile application](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/7447) ([#7447](https://github.com/TriliumNext/Trilium/issues/7447)) or [multi-user support](https://oss.issuehunt.io/r/TriliumNext/Trilium/issues/4956) ([#4956](https://github.com/TriliumNext/Trilium/issues/4956)), consider offering financial support via IssueHunt (see links).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you enjoyed this release, consider showing a token of appreciation by:
|
||||
>
|
||||
> * Pressing the “Star” button on [GitHub](https://github.com/TriliumNext/Trilium) (top-right).
|
||||
> * Considering a one-time or recurrent donation to the [lead developer](https://github.com/eliandoran) via [GitHub Sponsors](https://github.com/sponsors/eliandoran) or [PayPal](https://paypal.me/eliandoran).
|
||||
|
||||
This is a re-release of v0.101.2, which had a cache invalidation issue.
|
||||
|
||||
## 🐞 Bugfixes
|
||||
|
||||
* [SQL Console: cannot copy table data](https://github.com/TriliumNext/Trilium/pull/8268) by @SiriusXT
|
||||
* [Title is not selected when creating a note via the launcher](https://github.com/TriliumNext/Trilium/pull/8292) by @SiriusXT
|
||||
* [Popup editor closing after inserting a note link](https://github.com/TriliumNext/Trilium/pull/8224) by @SiriusXT
|
||||
* [New Mermaid diagrams do not save content](https://github.com/TriliumNext/Trilium/pull/8220) by @lzinga
|
||||
* [Can't scroll mermaid diagram code](https://github.com/TriliumNext/Trilium/issues/8299)
|
||||
* [Max content width is not respected when switching between note types in the same tab](https://github.com/TriliumNext/Trilium/issues/8065)
|
||||
* [Crash When a Note Includes Itself](https://github.com/TriliumNext/Trilium/issues/8294)
|
||||
* [Severe Performance Degradation and Crash Issues Due to Recursive Inclusion in Included Notes](https://github.com/TriliumNext/Trilium/issues/8017)
|
||||
* [is not a launcher even though it's in the launcher subtree](https://github.com/TriliumNext/Trilium/issues/8218)
|
||||
* [Archived subnotes of direct children appear in grid view without #includeArchived](https://github.com/TriliumNext/Trilium/issues/8184)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/source",
|
||||
"version": "0.101.1",
|
||||
"version": "0.101.3",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/commons",
|
||||
"version": "0.101.1",
|
||||
"version": "0.101.3",
|
||||
"description": "Shared library between the clients (e.g. browser, Electron) and the server, mostly for type definitions and utility methods.",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
33
scripts/check-version-consistency.ts
Normal file
33
scripts/check-version-consistency.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
const projectRoot = join(__dirname, '..');
|
||||
const filesToCheck = [
|
||||
'package.json',
|
||||
'apps/server/package.json',
|
||||
'apps/client/package.json',
|
||||
'apps/desktop/package.json',
|
||||
'packages/commons/package.json',
|
||||
]
|
||||
|
||||
function main() {
|
||||
const expectedVersion = process.argv[2];
|
||||
if (!expectedVersion) {
|
||||
console.error('Expected version argument is missing.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const fileToCheck of filesToCheck) {
|
||||
const packageJsonPath = join(projectRoot, fileToCheck);
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
const version = packageJson.version;
|
||||
if (version !== expectedVersion) {
|
||||
console.error(`Version mismatch in ${fileToCheck}: expected ${expectedVersion}, found ${version}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('All versions are consistent:', expectedVersion);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user