Merge branch 'main' into feat/note-map-filter

This commit is contained in:
Elian Doran
2025-07-02 23:37:56 +03:00
committed by GitHub
44 changed files with 1117 additions and 761 deletions

View File

@@ -42,5 +42,5 @@
This will export the notes in an unencrypted form, so if you reimport into
Trilium, make sure to re-protect these notes.</p>
<h2>Supported syntax</h2>
<p>See the dedicated page:&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/mHbBMPDPkVV5/Oau6X9rCuegd/_help_rJ9grSgoExl9">Supported syntax</a>
<p>See the dedicated page:&nbsp;<a class="reference-link" href="#root/_help_rJ9grSgoExl9">Supported syntax</a>
</p>

View File

@@ -41,7 +41,7 @@
Trilium-compatible syntax, but it will not export Trilium Notes into Markdown
files with this syntax.</p>
<aside class="admonition important">
<p>The path to pages in wikilinks is resolved relatively to the <em>import root </em>and
<p>The path to pages in wikilinks is resolved relatively to the <em>import root</em> and
not the current directory of the note. This is to be inline with other
platforms that use wikilinks such as SilverBullet.</p>
<p>The root path of the import is determined as follows:</p>

View File

@@ -62,4 +62,4 @@ class="image image-style-align-center">
are currently no plans for adjusting it or allowing the user to customize
them.</p>
<h3>Markdown support</h3>
<p>See&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/mHbBMPDPkVV5/Oau6X9rCuegd/_help_rJ9grSgoExl9">Supported syntax</a>.</p>
<p>See&nbsp;<a class="reference-link" href="#root/_help_rJ9grSgoExl9">Supported syntax</a>.</p>

View File

@@ -8,7 +8,7 @@ import appInfo from "../../services/app_info.js";
* operationId: app-info
* externalDocs:
* description: Server implementation
* url: https://github.com/TriliumNext/Notes/blob/v0.91.6/src/services/app_info.ts
* url: https://github.com/TriliumNext/Trilium/blob/v0.91.6/src/services/app_info.ts
* responses:
* '200':
* description: Installation info

View File

@@ -25,7 +25,7 @@ import type { Request } from "express";
* operationId: login-sync
* externalDocs:
* description: HMAC calculation
* url: https://github.com/TriliumNext/Notes/blob/v0.91.6/src/services/utils.ts#L62-L66
* url: https://github.com/TriliumNext/Trilium/blob/v0.91.6/src/services/utils.ts#L62-L66
* requestBody:
* content:
* application/json:

View File

@@ -95,7 +95,7 @@ function forceFullSync() {
* operationId: sync-changed
* externalDocs:
* description: Server implementation
* url: https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/api/sync.ts
* url: https://github.com/TriliumNext/Trilium/blob/v0.91.6/src/routes/api/sync.ts
* parameters:
* - in: query
* name: instanceId
@@ -214,7 +214,7 @@ const partialRequests: Record<
* operationId: sync-update
* externalDocs:
* description: Server implementation
* url: https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/api/sync.ts
* url: https://github.com/TriliumNext/Trilium/blob/v0.91.6/src/routes/api/sync.ts
* parameters:
* - in: header
* name: pageCount

View File

@@ -136,7 +136,7 @@ function getNotesAndBranchesAndAttributes(_noteIds: string[] | Set<string>) {
* operationId: tree
* externalDocs:
* description: Server implementation
* url: https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/api/tree.ts
* url: https://github.com/TriliumNext/Trilium/blob/v0.91.6/src/routes/api/tree.ts
* parameters:
* - in: query
* name: subTreeNoteId

View File

@@ -8,7 +8,7 @@ const doubleCsrfUtilities = doubleCsrf({
path: "/",
secure: false,
sameSite: "strict",
httpOnly: !isElectron // set to false for Electron, see https://github.com/TriliumNext/Notes/pull/966
httpOnly: !isElectron // set to false for Electron, see https://github.com/TriliumNext/Trilium/pull/966
},
cookieName: "_csrf"
});

View File

@@ -77,7 +77,7 @@ function setPassword(req: Request, res: Response) {
* operationId: login-normal
* externalDocs:
* description: HMAC calculation
* url: https://github.com/TriliumNext/Notes/blob/v0.91.6/src/services/utils.ts#L62-L66
* url: https://github.com/TriliumNext/Trilium/blob/v0.91.6/src/services/utils.ts#L62-L66
* requestBody:
* content:
* application/x-www-form-urlencoded:

View File

@@ -255,8 +255,12 @@ export interface Api {
/**
* Returns week note for given date. If such a note doesn't exist, it is created.
*
* <p>
* If the calendar does not support week notes, this method will return `null`.
*
* @param date in YYYY-MM-DD format
* @param rootNote - specify calendar root note, normally leave empty to use the default calendar
* @return an existing or newly created week note, or `null` if the calendar does not support week notes.
*/
getWeekNote(date: string, rootNote: BNote): BNote | null;

View File

@@ -293,25 +293,25 @@ describe("Markdown export", () => {
const html = trimIndentation`\
<ul>
<li><a href="https://github.com/JYC333">@JYC333</a> made their first contribution
in <a href="https://github.com/TriliumNext/Notes/pull/294">#294</a>
in <a href="https://github.com/TriliumNext/Trilium/pull/294">#294</a>
</li>
<li>
<p><a href="https://github.com/TriliumNext/Notes/issues/375">Note Tooltip isn't removed when clicking on internal trilium link in read-only mode</a>
<p><a href="https://github.com/TriliumNext/Trilium/issues/375">Note Tooltip isn't removed when clicking on internal trilium link in read-only mode</a>
</p>
</li>
<li>
<p><a href="https://github.com/TriliumNext/Notes/issues/384">Calendar dropdown won't close if click/right-click other button that open notes from launcher bar</a>
<p><a href="https://github.com/TriliumNext/Trilium/issues/384">Calendar dropdown won't close if click/right-click other button that open notes from launcher bar</a>
</p>
</li>
</ul>
`;
const expected = trimIndentation`\
* [@JYC333](https://github.com/JYC333) made their first contribution in [#294](https://github.com/TriliumNext/Notes/pull/294)
* [Note Tooltip isn't removed when clicking on internal trilium link in read-only mode](https://github.com/TriliumNext/Notes/issues/375)
* [Calendar dropdown won't close if click/right-click other button that open notes from launcher bar](https://github.com/TriliumNext/Notes/issues/384)`;
* [@JYC333](https://github.com/JYC333) made their first contribution in [#294](https://github.com/TriliumNext/Trilium/pull/294)
* [Note Tooltip isn't removed when clicking on internal trilium link in read-only mode](https://github.com/TriliumNext/Trilium/issues/375)
* [Calendar dropdown won't close if click/right-click other button that open notes from launcher bar](https://github.com/TriliumNext/Trilium/issues/384)`;
expect(markdownExportService.toMarkdown(html)).toBe(expected);
});

View File

@@ -75,6 +75,9 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
function getDataFileName(type: string | null, mime: string, baseFileName: string, existingFileNames: Record<string, number>): string {
let fileName = baseFileName.trim();
if (!fileName) {
fileName = "note";
}
// Crop fileName to avoid its length exceeding 30 and prevent cutting into the extension.
if (fileName.length > 30) {
@@ -366,7 +369,7 @@ ${markdownContent}`;
function saveNote(noteMeta: NoteMeta, filePathPrefix: string) {
log.info(`Exporting note '${noteMeta.noteId}'`);
if (!noteMeta.noteId || !noteMeta.title) {
if (!noteMeta.noteId || noteMeta.title === undefined) {
throw new Error("Missing note meta.");
}
@@ -515,97 +518,108 @@ ${markdownContent}`;
archive.append(cssContent, { name: cssMeta.dataFileName });
}
const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {};
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
if (!rootMeta) {
throw new Error("Unable to create root meta.");
}
try {
const existingFileNames: Record<string, number> = format === "html" ? { navigation: 0, index: 1 } : {};
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
if (!rootMeta) {
throw new Error("Unable to create root meta.");
}
const metaFile: NoteMetaFile = {
formatVersion: 2,
appVersion: packageInfo.version,
files: [rootMeta]
};
let navigationMeta: NoteMeta | null = null;
let indexMeta: NoteMeta | null = null;
let cssMeta: NoteMeta | null = null;
if (format === "html") {
navigationMeta = {
noImport: true,
dataFileName: "navigation.html"
const metaFile: NoteMetaFile = {
formatVersion: 2,
appVersion: packageInfo.version,
files: [rootMeta]
};
metaFile.files.push(navigationMeta);
let navigationMeta: NoteMeta | null = null;
let indexMeta: NoteMeta | null = null;
let cssMeta: NoteMeta | null = null;
indexMeta = {
noImport: true,
dataFileName: "index.html"
};
if (format === "html") {
navigationMeta = {
noImport: true,
dataFileName: "navigation.html"
};
metaFile.files.push(indexMeta);
metaFile.files.push(navigationMeta);
cssMeta = {
noImport: true,
dataFileName: "style.css"
};
indexMeta = {
noImport: true,
dataFileName: "index.html"
};
metaFile.files.push(cssMeta);
}
metaFile.files.push(indexMeta);
for (const noteMeta of Object.values(noteIdToMeta)) {
// filter out relations which are not inside this export
noteMeta.attributes = (noteMeta.attributes || []).filter((attr) => {
if (attr.type !== "relation") {
return true;
} else if (attr.value in noteIdToMeta) {
return true;
} else if (attr.value === "root" || attr.value?.startsWith("_")) {
// relations to "named" noteIds can be preserved
return true;
} else {
return false;
cssMeta = {
noImport: true,
dataFileName: "style.css"
};
metaFile.files.push(cssMeta);
}
for (const noteMeta of Object.values(noteIdToMeta)) {
// filter out relations which are not inside this export
noteMeta.attributes = (noteMeta.attributes || []).filter((attr) => {
if (attr.type !== "relation") {
return true;
} else if (attr.value in noteIdToMeta) {
return true;
} else if (attr.value === "root" || attr.value?.startsWith("_")) {
// relations to "named" noteIds can be preserved
return true;
} else {
return false;
}
});
}
if (!rootMeta) {
// corner case of disabled export for exported note
if ("sendStatus" in res) {
res.sendStatus(400);
}
});
}
return;
}
const metaFileJson = JSON.stringify(metaFile, null, "\t");
archive.append(metaFileJson, { name: "!!!meta.json" });
saveNote(rootMeta, "");
if (format === "html") {
if (!navigationMeta || !indexMeta || !cssMeta) {
throw new Error("Missing meta.");
}
saveNavigation(rootMeta, navigationMeta);
saveIndex(rootMeta, indexMeta);
saveCss(rootMeta, cssMeta);
}
const note = branch.getNote();
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected() || "note"}.zip`;
if (setHeaders && "setHeader" in res) {
res.setHeader("Content-Disposition", getContentDisposition(zipFileName));
res.setHeader("Content-Type", "application/zip");
}
archive.pipe(res);
await archive.finalize();
taskContext.taskSucceeded();
} catch (e: unknown) {
const message = `Export failed with error: ${e instanceof Error ? e.message : String(e)}`;
log.error(message);
taskContext.reportError(message);
if (!rootMeta) {
// corner case of disabled export for exported note
if ("sendStatus" in res) {
res.sendStatus(400);
res.removeHeader("Content-Disposition");
res.removeHeader("Content-Type");
res.status(500).send(message);
}
return;
}
const metaFileJson = JSON.stringify(metaFile, null, "\t");
archive.append(metaFileJson, { name: "!!!meta.json" });
saveNote(rootMeta, "");
if (format === "html") {
if (!navigationMeta || !indexMeta || !cssMeta) {
throw new Error("Missing meta.");
}
saveNavigation(rootMeta, navigationMeta);
saveIndex(rootMeta, indexMeta);
saveCss(rootMeta, cssMeta);
}
const note = branch.getNote();
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected()}.zip`;
if (setHeaders && "setHeader" in res) {
res.setHeader("Content-Disposition", getContentDisposition(zipFileName));
res.setHeader("Content-Type", "application/zip");
}
archive.pipe(res);
await archive.finalize();
taskContext.taskSucceeded();
}
async function exportToZipFile(noteId: string, format: "markdown" | "html", zipFilePath: string, zipExportOptions?: AdvancedExportOptions) {

View File

@@ -84,7 +84,7 @@ export const DEFAULT_ALLOWED_TAGS = [
"del",
"ins",
"en-media", // for ENEX import
// Additional tags (https://github.com/TriliumNext/Notes/issues/567)
// Additional tags (https://github.com/TriliumNext/Trilium/issues/567)
"acronym",
"article",
"big",

View File

@@ -259,15 +259,15 @@ $$`;
const input = trimIndentation`\
### 🐞 Bugfixes
* [v0.90.4 docker does not read USER\_UID and USER\_GID from environment](https://github.com/TriliumNext/Notes/issues/331)
* [Invalid CSRF token on Android phone](https://github.com/TriliumNext/Notes/issues/318)
* [Excess spacing in lists](https://github.com/TriliumNext/Notes/issues/341)`;
* [v0.90.4 docker does not read USER\_UID and USER\_GID from environment](https://github.com/TriliumNext/Trilium/issues/331)
* [Invalid CSRF token on Android phone](https://github.com/TriliumNext/Trilium/issues/318)
* [Excess spacing in lists](https://github.com/TriliumNext/Trilium/issues/341)`;
const expected = [
/*html*/`<h3>🐞 Bugfixes</h3>`,
/*html*/`<ul>`,
/*html*/`<li><a href="https://github.com/TriliumNext/Notes/issues/331">v0.90.4 docker does not read USER_UID and USER_GID from environment</a></li>`,
/*html*/`<li><a href="https://github.com/TriliumNext/Notes/issues/318">Invalid CSRF token on Android phone</a></li>`,
/*html*/`<li><a href="https://github.com/TriliumNext/Notes/issues/341">Excess spacing in lists</a></li>`,
/*html*/`<li><a href="https://github.com/TriliumNext/Trilium/issues/331">v0.90.4 docker does not read USER_UID and USER_GID from environment</a></li>`,
/*html*/`<li><a href="https://github.com/TriliumNext/Trilium/issues/318">Invalid CSRF token on Android phone</a></li>`,
/*html*/`<li><a href="https://github.com/TriliumNext/Trilium/issues/341">Excess spacing in lists</a></li>`,
/*html*/`</ul>`
].join("");
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);