mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 18:12:29 +08:00
Merge remote-tracking branch 'origin/develop' into ai-llm-integration
This commit is contained in:
commit
0133e83d23
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -5,6 +5,7 @@
|
||||
"vitest.explorer",
|
||||
"ms-playwright.playwright",
|
||||
"tobermory.es6-string-html",
|
||||
"dbaeumer.vscode-eslint"
|
||||
"dbaeumer.vscode-eslint",
|
||||
"yzhang.markdown-all-in-one"
|
||||
]
|
||||
}
|
||||
|
@ -25,6 +25,11 @@ keyPath=
|
||||
# expressjs shortcuts are supported: loopback(127.0.0.1/8, ::1/128), linklocal(169.254.0.0/16, fe80::/10), uniquelocal(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7)
|
||||
trustedReverseProxy=false
|
||||
|
||||
# setting the CORS headers for cross-origin requests
|
||||
# corsAllowOrigin='*'
|
||||
# corsAllowMethods='GET,POST,PUT,DELETE,PATCH'
|
||||
# corsAllowHeaders='Content-Type,Authorization'
|
||||
|
||||
|
||||
[Session]
|
||||
# Use this setting to set a custom value for the "Max-Age" Attribute of the session cookie.
|
||||
|
@ -1,7 +1,4 @@
|
||||
# v0.93.0
|
||||
## 💡 Key highlights
|
||||
|
||||
* …
|
||||
|
||||
## 🐞 Bugfixes
|
||||
|
||||
@ -15,6 +12,10 @@
|
||||
* [config.Session.cookieMaxAge is ignored](https://github.com/TriliumNext/Notes/issues/1709) by @pano9000
|
||||
* [Return correct HTTP status code on failed login attempts instead of 200](https://github.com/TriliumNext/Notes/issues/1707) by @pano9000
|
||||
* [Calendar stops displaying notes after adding a Day Note](https://github.com/TriliumNext/Notes/issues/1705)
|
||||
* Full anonymization not redacting attachment titles.
|
||||
* Unable to trigger "Move to" dialog via keyboard shortcut.
|
||||
* [Note ordering doesn't load correctly, only shows up right after moving a note](https://github.com/TriliumNext/Notes/issues/1727)
|
||||
* [Note selection dialog shows icon class when selecting result with arrow button (jump to note / create link)](https://github.com/TriliumNext/Notes/issues/1721)
|
||||
|
||||
## ✨ Improvements
|
||||
|
||||
@ -23,6 +24,7 @@
|
||||
* Reduce extra whitespace between list items.
|
||||
* Preserve include note.
|
||||
* Handle note titles that contain inline code.
|
||||
* Support to-do lists.
|
||||
* In-app help:
|
||||
* Document structure is now precalculated, so start-up time should be slightly better.
|
||||
* Optimized the content in order to reduce the size on disk.
|
||||
@ -35,10 +37,8 @@
|
||||
* Basic Touch Bar support for macOS.
|
||||
* [Support Bearer Token](https://github.com/TriliumNext/Notes/issues/1701)
|
||||
* The tab bar is now scrollable when there are many tabs by @SiriusXT
|
||||
|
||||
## 🌍 Internationalization
|
||||
|
||||
* …
|
||||
* Make each part of the note path clickable by @SiriusXT
|
||||
* Allow setting CORS headers by @yiranlus
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
@ -47,6 +47,5 @@
|
||||
|
||||
## 🛠️ Technical updates
|
||||
|
||||
* upgrade to express 5.1.0 by @pano9000
|
||||
* update dependency mind-elixir to v4.5.1
|
||||
* remove non-working cookiePath option by @pano9000
|
@ -9968,6 +9968,26 @@
|
||||
"format": "markdown",
|
||||
"dataFileName": "Trilium instance.md",
|
||||
"attachments": []
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "LWtBjFej3wX3",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"tC7s2alapj8V",
|
||||
"Gzjqa934BdH4",
|
||||
"LWtBjFej3wX3"
|
||||
],
|
||||
"title": "Cross-Origin Resource Sharing (CORS)",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Cross-Origin Resource Sharing .md",
|
||||
"attachments": []
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -0,0 +1,6 @@
|
||||
# Cross-Origin Resource Sharing (CORS)
|
||||
By default, Trilium cannot be accessed in web browsers by requests coming from other domains/origins than Trilium itself.
|
||||
|
||||
However, it is possible to manually configure [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) since Trilium v0.93.0 using environment variables or `config.ini`, as follows:
|
||||
|
||||
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:26.93%;"><col style="width:32.46%;"><col style="width:40.61%;"></colgroup><thead><tr><th>CORS Header</th><th>Corresponding option in <code>config.ini</code></th><th>Corresponding option in environment variables in the <code>Network</code> section</th></tr></thead><tbody><tr><td><code>Access-Control-Allow-Origin</code></td><td><code>TRILIUM_NETWORK_CORS_ALLOW_ORIGIN</code></td><td><code>corsAllowOrigin</code> </td></tr><tr><td><code>Access-Control-Allow-Methods</code></td><td><code>TRILIUM_NETWORK_CORS_ALLOW_METHODS</code></td><td><code>corsAllowMethods</code> </td></tr><tr><td><code>Access-Control-Allow-Headers</code></td><td><code>TRILIUM_NETWORK_CORS_ALLOW_HEADERS</code></td><td><code>corsAllowHeaders</code></td></tr></tbody></table></figure>
|
20
package-lock.json
generated
20
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.92.7",
|
||||
"version": "0.93.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trilium",
|
||||
"version": "0.92.7",
|
||||
"version": "0.93.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.39.0",
|
||||
@ -50,7 +50,7 @@
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "24.2.3",
|
||||
"i18next": "25.0.0",
|
||||
"i18next-fs-backend": "2.6.0",
|
||||
"image-type": "5.2.0",
|
||||
"ini": "5.0.0",
|
||||
@ -73,7 +73,7 @@
|
||||
"rand-token": "1.0.1",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "2.15.0",
|
||||
"sanitize-html": "2.16.0",
|
||||
"sax": "1.4.1",
|
||||
"serve-favicon": "2.5.0",
|
||||
"session-file-store": "1.5.0",
|
||||
@ -12742,9 +12742,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "24.2.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz",
|
||||
"integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==",
|
||||
"version": "25.0.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.0.0.tgz",
|
||||
"integrity": "sha512-POPvwjOPR1GQvRnbikTMPEhQD+ekd186MHE6NtVxl3Lby+gPp0iq60eCqGrY6wfRnp1lejjFNu0EKs1afA322w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@ -18324,9 +18324,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sanitize-html": {
|
||||
"version": "2.15.0",
|
||||
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.15.0.tgz",
|
||||
"integrity": "sha512-wIjst57vJGpLyBP8ioUbg6ThwJie5SuSIjHxJg53v5Fg+kUK+AXlb7bK3RNXpp315MvwM+0OBGCV6h5pPHsVhA==",
|
||||
"version": "2.16.0",
|
||||
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.16.0.tgz",
|
||||
"integrity": "sha512-0s4caLuHHaZFVxFTG74oW91+j6vW7gKbGD6CD2+miP73CE6z6YtOBN0ArtLd2UGyi4IC7K47v3ENUbQX4jV3Mg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deepmerge": "^4.2.2",
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "TriliumNext Notes",
|
||||
"description": "Build your personal knowledge base with TriliumNext Notes",
|
||||
"version": "0.92.7",
|
||||
"version": "0.93.0",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "./electron-main.js",
|
||||
"author": {
|
||||
@ -110,7 +110,7 @@
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "24.2.3",
|
||||
"i18next": "25.0.0",
|
||||
"i18next-fs-backend": "2.6.0",
|
||||
"image-type": "5.2.0",
|
||||
"ini": "5.0.0",
|
||||
@ -133,7 +133,7 @@
|
||||
"rand-token": "1.0.1",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "2.15.0",
|
||||
"sanitize-html": "2.16.0",
|
||||
"sax": "1.4.1",
|
||||
"serve-favicon": "2.5.0",
|
||||
"session-file-store": "1.5.0",
|
||||
|
12
src/app.ts
12
src/app.ts
@ -7,6 +7,7 @@ import compression from "compression";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
import sessionParser from "./routes/session_parser.js";
|
||||
import config from "./services/config.js";
|
||||
import utils from "./services/utils.js";
|
||||
import assets from "./routes/assets.js";
|
||||
import routes from "./routes/routes.js";
|
||||
@ -71,6 +72,17 @@ app.set("views", path.join(scriptDir, "views"));
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
app.use((req, res, next) => {
|
||||
// set CORS header
|
||||
if (config["Network"]["corsAllowOrigin"]) {
|
||||
res.header("Access-Control-Allow-Origin", config["Network"]["corsAllowOrigin"]);
|
||||
}
|
||||
if (config["Network"]["corsAllowMethods"]) {
|
||||
res.header("Access-Control-Allow-Methods", config["Network"]["corsAllowMethods"]);
|
||||
}
|
||||
if (config["Network"]["corsAllowHeaders"]) {
|
||||
res.header("Access-Control-Allow-Headers", config["Network"]["corsAllowHeaders"]);
|
||||
}
|
||||
|
||||
res.locals.t = t;
|
||||
return next();
|
||||
});
|
||||
|
@ -53,8 +53,8 @@ export interface ContextMenuCommandData extends CommandData {
|
||||
node: Fancytree.FancytreeNode;
|
||||
notePath?: string;
|
||||
noteId?: string;
|
||||
selectedOrActiveBranchIds?: any; // TODO: Remove any once type is defined
|
||||
selectedOrActiveNoteIds: any; // TODO: Remove any once type is defined
|
||||
selectedOrActiveBranchIds: string[];
|
||||
selectedOrActiveNoteIds?: string[];
|
||||
}
|
||||
|
||||
export interface NoteCommandData extends CommandData {
|
||||
|
@ -18,10 +18,26 @@ export default class MainTreeExecutors extends Component {
|
||||
}
|
||||
|
||||
async cloneNotesToCommand({ selectedOrActiveNoteIds }: EventData<"cloneNotesTo">) {
|
||||
if (!selectedOrActiveNoteIds && this.tree) {
|
||||
selectedOrActiveNoteIds = this.tree.getSelectedOrActiveNodes().map((node) => node.data.noteId);
|
||||
}
|
||||
|
||||
if (!selectedOrActiveNoteIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerCommand("cloneNoteIdsTo", { noteIds: selectedOrActiveNoteIds });
|
||||
}
|
||||
|
||||
async moveNotesToCommand({ selectedOrActiveBranchIds }: EventData<"moveNotesTo">) {
|
||||
if (!selectedOrActiveBranchIds && this.tree) {
|
||||
selectedOrActiveBranchIds = this.tree.getSelectedOrActiveNodes().map((node) => node.data.branchId);
|
||||
}
|
||||
|
||||
if (!selectedOrActiveBranchIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.triggerCommand("moveBranchIdsTo", { branchIds: selectedOrActiveBranchIds });
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,46 @@
|
||||
<p>By default, Trilium cannot be accessed in web browsers by requests coming
|
||||
from other domains/origins than Trilium itself. </p>
|
||||
<p>However, it is possible to manually configure <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS">Cross-Origin Resource Sharing (CORS)</a> since
|
||||
Trilium v0.93.0 using environment variables or <code>config.ini</code>,
|
||||
as follows:</p>
|
||||
<figure class="table" style="width:100%;">
|
||||
<table class="ck-table-resized">
|
||||
<colgroup>
|
||||
<col style="width:26.93%;">
|
||||
<col style="width:32.46%;">
|
||||
<col style="width:40.61%;">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>CORS Header</th>
|
||||
<th>Corresponding option in <code>config.ini</code>
|
||||
</th>
|
||||
<th>Corresponding option in environment variables in the <code>Network</code> section</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>Access-Control-Allow-Origin</code>
|
||||
</td>
|
||||
<td><code>TRILIUM_NETWORK_CORS_ALLOW_ORIGIN</code>
|
||||
</td>
|
||||
<td><code>corsAllowOrigin</code> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Access-Control-Allow-Methods</code>
|
||||
</td>
|
||||
<td><code>TRILIUM_NETWORK_CORS_ALLOW_METHODS</code>
|
||||
</td>
|
||||
<td><code>corsAllowMethods</code> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Access-Control-Allow-Headers</code>
|
||||
</td>
|
||||
<td><code>TRILIUM_NETWORK_CORS_ALLOW_HEADERS</code>
|
||||
</td>
|
||||
<td><code>corsAllowHeaders</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
@ -152,7 +152,7 @@ class FNote {
|
||||
|
||||
for (const branchId of Object.values(this.childToBranch)) {
|
||||
const notePosition = this.froca.getBranch(branchId)?.notePosition;
|
||||
if (notePosition) {
|
||||
if (notePosition !== undefined) {
|
||||
branchIdPos[branchId] = notePosition;
|
||||
}
|
||||
}
|
||||
|
@ -278,15 +278,20 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
||||
const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
|
||||
|
||||
const ctrlKey = utils.isCtrlKey(evt);
|
||||
const shiftKey = evt.shiftKey;
|
||||
const isLeftClick = "which" in evt && evt.which === 1;
|
||||
const isMiddleClick = "which" in evt && evt.which === 2;
|
||||
const targetIsBlank = ($link?.attr("target") === "_blank");
|
||||
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick || targetIsBlank;
|
||||
const activate = (isLeftClick && ctrlKey && shiftKey) || (isMiddleClick && shiftKey);
|
||||
const openInNewWindow = isLeftClick && evt.shiftKey && !ctrlKey;
|
||||
|
||||
if (notePath) {
|
||||
if (openInNewTab) {
|
||||
if (openInNewWindow) {
|
||||
appContext.triggerCommand("openInWindow", { notePath, viewScope });
|
||||
} else if (openInNewTab) {
|
||||
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
|
||||
activate: targetIsBlank,
|
||||
activate: activate ? true : targetIsBlank,
|
||||
viewScope
|
||||
});
|
||||
} else if (isLeftClick) {
|
||||
|
@ -21,6 +21,7 @@ function getSearchDelay(notesCount: number): number {
|
||||
}
|
||||
let searchDelay = getSearchDelay(notesCount);
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
export interface Suggestion {
|
||||
noteTitle?: string;
|
||||
externalLink?: string;
|
||||
@ -29,6 +30,7 @@ export interface Suggestion {
|
||||
highlightedNotePathTitle?: string;
|
||||
action?: string | "create-note" | "search-notes" | "external-link";
|
||||
parentNoteId?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
@ -262,7 +264,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
},
|
||||
displayKey: "notePathTitle",
|
||||
templates: {
|
||||
suggestion: (suggestion) => suggestion.highlightedNotePathTitle
|
||||
suggestion: (suggestion) => `<span class="${suggestion.icon ?? "bx bx-note"}"></span> ${suggestion.highlightedNotePathTitle}`
|
||||
},
|
||||
// we can't cache identical searches because notes can be created / renamed, new recent notes can be added
|
||||
cache: false
|
||||
|
@ -19,15 +19,15 @@ const TPL = /*html*/`
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.note-path-list .path-current {
|
||||
.note-path-list .path-current a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.note-path-list .path-archived {
|
||||
.note-path-list .path-archived a {
|
||||
color: var(--muted-text-color) !important;
|
||||
}
|
||||
|
||||
.note-path-list .path-search {
|
||||
.note-path-list .path-search a {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
@ -72,7 +72,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
||||
this.$notePathList.empty();
|
||||
|
||||
if (!this.note || this.noteId === "root") {
|
||||
this.$notePathList.empty().append(await this.getRenderedPath("root"));
|
||||
this.$notePathList.empty().append(await this.getRenderedPath(["root"]));
|
||||
|
||||
return;
|
||||
}
|
||||
@ -88,7 +88,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
||||
const renderedPaths = [];
|
||||
|
||||
for (const notePathRecord of sortedNotePaths) {
|
||||
const notePath = notePathRecord.notePath.join("/");
|
||||
const notePath = notePathRecord.notePath;
|
||||
|
||||
renderedPaths.push(await this.getRenderedPath(notePath, notePathRecord));
|
||||
}
|
||||
@ -96,42 +96,54 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
||||
this.$notePathList.empty().append(...renderedPaths);
|
||||
}
|
||||
|
||||
async getRenderedPath(notePath: string, notePathRecord: NotePathRecord | null = null) {
|
||||
const title = await treeService.getNotePathTitle(notePath);
|
||||
async getRenderedPath(notePath: string[], notePathRecord: NotePathRecord | null = null) {
|
||||
const $pathItem = $("<li>");
|
||||
const pathSegments: string[] = [];
|
||||
const lastIndex = notePath.length - 1;
|
||||
|
||||
for (let i = 0; i < notePath.length; i++) {
|
||||
const noteId = notePath[i];
|
||||
pathSegments.push(noteId);
|
||||
const title = await treeService.getNoteTitle(noteId);
|
||||
const $noteLink = await linkService.createLink(pathSegments.join("/"), { title });
|
||||
|
||||
const $noteLink = await linkService.createLink(notePath, { title });
|
||||
|
||||
$noteLink.find("a").addClass("no-tooltip-preview tn-link");
|
||||
$noteLink.find("a").addClass("no-tooltip-preview tn-link");
|
||||
$pathItem.append($noteLink);
|
||||
|
||||
if (i != lastIndex) {
|
||||
$pathItem.append(" / ");
|
||||
}
|
||||
}
|
||||
|
||||
const icons = [];
|
||||
|
||||
if (this.notePath === notePath) {
|
||||
$noteLink.addClass("path-current");
|
||||
if (this.notePath === notePath.join("/")) {
|
||||
$pathItem.addClass("path-current");
|
||||
}
|
||||
|
||||
if (!notePathRecord || notePathRecord.isInHoistedSubTree) {
|
||||
$noteLink.addClass("path-in-hoisted-subtree");
|
||||
$pathItem.addClass("path-in-hoisted-subtree");
|
||||
} else {
|
||||
icons.push(`<span class="bx bx-trending-up" title="${t("note_paths.outside_hoisted")}"></span>`);
|
||||
}
|
||||
|
||||
if (notePathRecord?.isArchived) {
|
||||
$noteLink.addClass("path-archived");
|
||||
$pathItem.addClass("path-archived");
|
||||
|
||||
icons.push(`<span class="bx bx-archive" title="${t("note_paths.archived")}"></span>`);
|
||||
}
|
||||
|
||||
if (notePathRecord?.isSearch) {
|
||||
$noteLink.addClass("path-search");
|
||||
$pathItem.addClass("path-search");
|
||||
|
||||
icons.push(`<span class="bx bx-search" title="${t("note_paths.search")}"></span>`);
|
||||
}
|
||||
|
||||
if (icons.length > 0) {
|
||||
$noteLink.append(` ${icons.join(" ")}`);
|
||||
$pathItem.append(` ${icons.join(" ")}`);
|
||||
}
|
||||
|
||||
return $("<li>").append($noteLink);
|
||||
return $pathItem;
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
|
@ -75,7 +75,8 @@ function getRecentNotes(activeNoteId: string) {
|
||||
notePath: rn.notePath,
|
||||
noteTitle: title,
|
||||
notePathTitle,
|
||||
highlightedNotePathTitle: `<span class="${icon ?? "bx bx-note"}"></span> ${notePathTitle}`
|
||||
highlightedNotePathTitle: utils.escapeHtml(notePathTitle),
|
||||
icon: icon ?? "bx bx-note"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -13,11 +13,12 @@ function getFullAnonymizationScript() {
|
||||
.map((attr) => `'${attr.name}'`)
|
||||
.join(", ");
|
||||
|
||||
const anonymizeScript = `
|
||||
const anonymizeScript = /*sql*/`\
|
||||
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
|
||||
UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
|
||||
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
|
||||
UPDATE revisions SET title = 'title';
|
||||
UPDATE attachments SET title = 'title';
|
||||
|
||||
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrNames});
|
||||
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrNames});
|
||||
|
@ -29,6 +29,9 @@ export interface TriliumConfig {
|
||||
certPath: string;
|
||||
keyPath: string;
|
||||
trustedReverseProxy: boolean | string;
|
||||
corsAllowOrigin: string;
|
||||
corsAllowMethods: string;
|
||||
corsAllowHeaders: string;
|
||||
};
|
||||
Session: {
|
||||
cookieMaxAge: number;
|
||||
@ -79,7 +82,16 @@ const config: TriliumConfig = {
|
||||
process.env.TRILIUM_NETWORK_KEYPATH || iniConfig.Network.keyPath || "",
|
||||
|
||||
trustedReverseProxy:
|
||||
process.env.TRILIUM_NETWORK_TRUSTEDREVERSEPROXY || iniConfig.Network.trustedReverseProxy || false
|
||||
process.env.TRILIUM_NETWORK_TRUSTEDREVERSEPROXY || iniConfig.Network.trustedReverseProxy || false,
|
||||
|
||||
corsAllowOrigin:
|
||||
process.env.TRILIUM_NETWORK_CORS_ALLOW_ORIGIN || iniConfig.Network.corsAllowOrigin || "",
|
||||
|
||||
corsAllowMethods:
|
||||
process.env.TRILIUM_NETWORK_CORS_ALLOW_METHODS || iniConfig.Network.corsAllowMethods || "",
|
||||
|
||||
corsAllowHeaders:
|
||||
process.env.TRILIUM_NETWORK_CORS_ALLOW_HEADERS || iniConfig.Network.corsAllowHeaders || ""
|
||||
},
|
||||
|
||||
Session: {
|
||||
|
@ -321,4 +321,25 @@ describe("Markdown export", () => {
|
||||
expect(markdownExportService.toMarkdown(html)).toBe(expected);
|
||||
});
|
||||
|
||||
it("exports todo lists properly", () => {
|
||||
const html = trimIndentation/*html*/`\
|
||||
<ul class="todo-list">
|
||||
<li>
|
||||
<label class="todo-list__label">
|
||||
<input type="checkbox" checked="checked" disabled="disabled"><span class="todo-list__label__description">Hello</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label class="todo-list__label">
|
||||
<input type="checkbox" disabled="disabled"><span class="todo-list__label__description">World</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
`;
|
||||
const expected = trimIndentation`\
|
||||
- [x] Hello
|
||||
- [ ] World`;
|
||||
expect(markdownExportService.toMarkdown(html)).toBe(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -230,7 +230,11 @@ function buildListItemFilter(): Rule {
|
||||
var start = parent.getAttribute('start')
|
||||
var index = Array.prototype.indexOf.call(parent.children, node)
|
||||
prefix = (start ? Number(start) + index : index + 1) + '. '
|
||||
} else if (parent.classList.contains("todo-list")) {
|
||||
const isChecked = node.querySelector("input[type=checkbox]:checked");
|
||||
prefix = (isChecked ? "- [x] " : "- [ ] ");
|
||||
}
|
||||
|
||||
const result = prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '');
|
||||
return result;
|
||||
}
|
||||
|
@ -233,4 +233,12 @@ second line 2</code></pre><ul><li>Hello</li><li>world</li></ul><ol><li>Hello</li
|
||||
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it("imports todo lists properly", () => {
|
||||
const input = trimIndentation`\
|
||||
- [x] Hello
|
||||
- [ ] World`;
|
||||
const expected = `<ul class="todo-list"><li><label class="todo-list__label"><input type="checkbox" checked="checked" disabled="disabled"><span class="todo-list__label__description">Hello</span></label></li><li><label class="todo-list__label"><input type="checkbox" disabled="disabled"><span class="todo-list__label__description">World</span></label></li></ul>`;
|
||||
expect(markdownService.renderToHtml(input, "Title")).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -48,12 +48,52 @@ class CustomMarkdownRenderer extends Renderer {
|
||||
}
|
||||
|
||||
list(token: Tokens.List): string {
|
||||
return super.list(token)
|
||||
let result = super.list(token)
|
||||
.replace("\n", "") // we replace the first one only.
|
||||
.trimEnd();
|
||||
|
||||
// Handle todo-list in the CKEditor format.
|
||||
if (token.items.some(item => item.task)) {
|
||||
result = result.replace(/^<ul>/, "<ul class=\"todo-list\">");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
checkbox({ checked }: Tokens.Checkbox): string {
|
||||
return '<input type="checkbox"'
|
||||
+ (checked ? 'checked="checked" ' : '')
|
||||
+ 'disabled="disabled">';
|
||||
}
|
||||
|
||||
listitem(item: Tokens.ListItem): string {
|
||||
// Handle todo-list in the CKEditor format.
|
||||
if (item.task) {
|
||||
let itemBody = '';
|
||||
const checkbox = this.checkbox({ checked: !!item.checked });
|
||||
if (item.loose) {
|
||||
if (item.tokens[0]?.type === 'paragraph') {
|
||||
item.tokens[0].text = checkbox + item.tokens[0].text;
|
||||
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
|
||||
item.tokens[0].tokens[0].text = checkbox + escape(item.tokens[0].tokens[0].text);
|
||||
item.tokens[0].tokens[0].escaped = true;
|
||||
}
|
||||
} else {
|
||||
item.tokens.unshift({
|
||||
type: 'text',
|
||||
raw: checkbox,
|
||||
text: checkbox,
|
||||
escaped: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
itemBody += checkbox;
|
||||
}
|
||||
|
||||
itemBody += `<span class="todo-list__label__description">${this.parser.parse(item.tokens, !!item.loose)}</span>`;
|
||||
return `<li><label class="todo-list__label">${itemBody}</label></li>`;
|
||||
}
|
||||
|
||||
return super.listitem(item).trimEnd();
|
||||
}
|
||||
|
||||
|
@ -358,8 +358,9 @@ function searchNotesForAutocomplete(query: string, fastSearch: boolean = true) {
|
||||
return {
|
||||
notePath: result.notePath,
|
||||
noteTitle: title,
|
||||
notePathTitle: `${icon} ${result.notePathTitle}`,
|
||||
highlightedNotePathTitle: `<span class="${icon ?? "bx bx-note"}"></span> ${result.highlightedNotePathTitle}`
|
||||
notePathTitle: result.notePathTitle,
|
||||
highlightedNotePathTitle: result.highlightedNotePathTitle,
|
||||
icon: icon ?? "bx bx-note"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user