mirror of
https://github.com/TriliumNext/Trilium.git
synced 2025-12-09 19:43:01 -06:00
Compare commits
20 Commits
08f96a91f3
...
9ad4b725ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ad4b725ac | ||
|
|
0182c61aec | ||
|
|
78362535c7 | ||
|
|
ae7b31f343 | ||
|
|
f16441bba4 | ||
|
|
d3f9bb6def | ||
|
|
e02440aa59 | ||
|
|
f1d87c29d3 | ||
|
|
21335b1b00 | ||
|
|
7463570e76 | ||
|
|
da17a63ef5 | ||
|
|
eb8f2021cb | ||
|
|
888ff33be1 | ||
|
|
b46850e86e | ||
|
|
f053587f09 | ||
|
|
7a3092a23b | ||
|
|
d95450ae07 | ||
|
|
230def10fe | ||
|
|
036f8e49a4 | ||
|
|
4eca8a5640 |
@ -1,6 +1,6 @@
|
||||
root = true
|
||||
|
||||
[*.{js,ts,tsx}]
|
||||
[*.{js,ts,tsx,css}]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -36,5 +36,8 @@
|
||||
"docs/**/*.png": true,
|
||||
"apps/server/src/assets/doc_notes/**": true,
|
||||
"apps/edit-docs/demo/**": true
|
||||
}
|
||||
},
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "*", "severity": "warn" }
|
||||
]
|
||||
}
|
||||
@ -27,6 +27,7 @@
|
||||
"@mermaid-js/layout-elk": "0.2.0",
|
||||
"@mind-elixir/node-menu": "5.0.1",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@preact/signals": "2.5.1",
|
||||
"@triliumnext/ckeditor5": "workspace:*",
|
||||
"@triliumnext/codemirror": "workspace:*",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
|
||||
@ -22,6 +22,7 @@ bundleService.getWidgetBundlesByParent().then(async (widgetBundles) => {
|
||||
appContext.setLayout(new DesktopLayout(widgetBundles));
|
||||
appContext.start().catch((e) => {
|
||||
toastService.showPersistent({
|
||||
id: "critical-error",
|
||||
title: t("toast.critical-error.title"),
|
||||
icon: "alert",
|
||||
message: t("toast.critical-error.message", { message: e.message })
|
||||
|
||||
@ -24,6 +24,7 @@ import InfoDialog from "../widgets/dialogs/info.js";
|
||||
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
||||
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
||||
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
|
||||
import ToastContainer from "../widgets/Toast.jsx";
|
||||
|
||||
export function applyModals(rootContainer: RootContainer) {
|
||||
rootContainer
|
||||
@ -50,5 +51,6 @@ export function applyModals(rootContainer: RootContainer) {
|
||||
.child(<PromptDialog />)
|
||||
.child(<IncorrectCpuArchDialog />)
|
||||
.child(<PopupEditorDialog />)
|
||||
.child(<CallToActionDialog />);
|
||||
.child(<CallToActionDialog />)
|
||||
.child(<ToastContainer />)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import utils from "./utils.js";
|
||||
import server from "./server.js";
|
||||
import toastService, { type ToastOptions } from "./toast.js";
|
||||
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
|
||||
import froca from "./froca.js";
|
||||
import hoistedNoteService from "./hoisted_note.js";
|
||||
import ws from "./ws.js";
|
||||
@ -195,11 +195,11 @@ function filterRootNote(branchIds: string[]) {
|
||||
});
|
||||
}
|
||||
|
||||
function makeToast(id: string, message: string): ToastOptions {
|
||||
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
|
||||
return {
|
||||
id: id,
|
||||
id,
|
||||
title: t("branches.delete-status"),
|
||||
message: message,
|
||||
message,
|
||||
icon: "trash"
|
||||
};
|
||||
}
|
||||
@ -216,7 +216,7 @@ ws.subscribeToMessages(async (message) => {
|
||||
toastService.showPersistent(makeToast(message.taskId, t("branches.delete-notes-in-progress", { count: message.progressCount })));
|
||||
} else if (message.type === "taskSucceeded") {
|
||||
const toast = makeToast(message.taskId, t("branches.delete-finished-successfully"));
|
||||
toast.closeAfter = 5000;
|
||||
toast.timeout = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
}
|
||||
@ -234,7 +234,7 @@ ws.subscribeToMessages(async (message) => {
|
||||
toastService.showPersistent(makeToast(message.taskId, t("branches.undeleting-notes-in-progress", { count: message.progressCount })));
|
||||
} else if (message.type === "taskSucceeded") {
|
||||
const toast = makeToast(message.taskId, t("branches.undeleting-notes-finished-successfully"));
|
||||
toast.closeAfter = 5000;
|
||||
toast.timeout = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
}
|
||||
@ -242,7 +242,7 @@ ws.subscribeToMessages(async (message) => {
|
||||
|
||||
async function cloneNoteToBranch(childNoteId: string, parentBranchId: string, prefix?: string) {
|
||||
const resp = await server.put<Response>(`notes/${childNoteId}/clone-to-branch/${parentBranchId}`, {
|
||||
prefix: prefix
|
||||
prefix
|
||||
});
|
||||
|
||||
if (!resp.success) {
|
||||
@ -252,7 +252,7 @@ async function cloneNoteToBranch(childNoteId: string, parentBranchId: string, pr
|
||||
|
||||
async function cloneNoteToParentNote(childNoteId: string, parentNoteId: string, prefix?: string) {
|
||||
const resp = await server.put<Response>(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, {
|
||||
prefix: prefix
|
||||
prefix
|
||||
});
|
||||
|
||||
if (!resp.success) {
|
||||
|
||||
@ -37,6 +37,7 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
|
||||
} catch (e: any) {
|
||||
const note = await froca.getNote(bundle.noteId);
|
||||
toastService.showPersistent({
|
||||
id: `custom-script-failure-${note?.noteId}`,
|
||||
title: t("toast.bundle-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.bundle-error.message", {
|
||||
@ -108,6 +109,7 @@ async function getWidgetBundlesByParent() {
|
||||
const noteId = bundle.noteId;
|
||||
const note = await froca.getNote(noteId);
|
||||
toastService.showPersistent({
|
||||
id: `custom-script-failure-${noteId}`,
|
||||
title: t("toast.bundle-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.bundle-error.message", {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import toastService, { type ToastOptions } from "./toast.js";
|
||||
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
|
||||
import server from "./server.js";
|
||||
import ws from "./ws.js";
|
||||
import utils from "./utils.js";
|
||||
@ -57,11 +57,11 @@ export async function uploadFiles(entityType: string, parentNoteId: string, file
|
||||
}
|
||||
}
|
||||
|
||||
function makeToast(id: string, message: string): ToastOptions {
|
||||
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
|
||||
return {
|
||||
id: id,
|
||||
id,
|
||||
title: t("import.import-status"),
|
||||
message: message,
|
||||
message,
|
||||
icon: "plus"
|
||||
};
|
||||
}
|
||||
@ -78,7 +78,7 @@ ws.subscribeToMessages(async (message) => {
|
||||
toastService.showPersistent(makeToast(message.taskId, t("import.in-progress", { progress: message.progressCount })));
|
||||
} else if (message.type === "taskSucceeded") {
|
||||
const toast = makeToast(message.taskId, t("import.successful"));
|
||||
toast.closeAfter = 5000;
|
||||
toast.timeout = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
|
||||
@ -100,7 +100,7 @@ ws.subscribeToMessages(async (message: WebSocketMessage) => {
|
||||
toastService.showPersistent(makeToast(message.taskId, t("import.in-progress", { progress: message.progressCount })));
|
||||
} else if (message.type === "taskSucceeded") {
|
||||
const toast = makeToast(message.taskId, t("import.successful"));
|
||||
toast.closeAfter = 5000;
|
||||
toast.timeout = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import server from "./server.js";
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import toastService from "./toast.js";
|
||||
import type { ToastOptions } from "./toast.js";
|
||||
import type { ToastOptionsWithRequiredId } from "./toast.js";
|
||||
import ws from "./ws.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import froca from "./froca.js";
|
||||
@ -97,7 +97,7 @@ async function protectNote(noteId: string, protect: boolean, includingSubtree: b
|
||||
await server.put(`notes/${noteId}/protect/${protect ? 1 : 0}?subtree=${includingSubtree ? 1 : 0}`);
|
||||
}
|
||||
|
||||
function makeToast(message: Message, title: string, text: string): ToastOptions {
|
||||
function makeToast(message: Message, title: string, text: string): ToastOptionsWithRequiredId {
|
||||
return {
|
||||
id: message.taskId,
|
||||
title,
|
||||
@ -124,7 +124,7 @@ ws.subscribeToMessages(async (message) => {
|
||||
} else if (message.type === "taskSucceeded") {
|
||||
const text = isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully");
|
||||
const toast = makeToast(message, title, text);
|
||||
toast.closeAfter = 3000;
|
||||
toast.timeout = 3000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
}
|
||||
|
||||
@ -263,7 +263,7 @@ async function reportError(method: string, url: string, statusCode: number, resp
|
||||
|
||||
const toastService = (await import("./toast.js")).default;
|
||||
|
||||
const messageStr = typeof message === "string" ? message : JSON.stringify(message);
|
||||
const messageStr = (typeof message === "string" ? message : JSON.stringify(message)) || "-";
|
||||
|
||||
if ([400, 404].includes(statusCode) && response && typeof response === "object") {
|
||||
toastService.showError(messageStr);
|
||||
@ -274,10 +274,22 @@ async function reportError(method: string, url: string, statusCode: number, resp
|
||||
...response
|
||||
});
|
||||
} else {
|
||||
const title = `${statusCode} ${method} ${url}`;
|
||||
toastService.showErrorTitleAndMessage(title, messageStr);
|
||||
const { t } = await import("./i18n.js");
|
||||
if (statusCode === 400 && (url.includes("%23") || url.includes("%2F"))) {
|
||||
toastService.showPersistent({
|
||||
id: "trafik-blocked",
|
||||
icon: "bx bx-unlink",
|
||||
title: t("server.unknown_http_error_title"),
|
||||
message: t("server.traefik_blocks_requests")
|
||||
});
|
||||
} else {
|
||||
toastService.showErrorTitleAndMessage(
|
||||
t("server.unknown_http_error_title"),
|
||||
t("server.unknown_http_error_content", { statusCode, method, url, message: messageStr }),
|
||||
15_000);
|
||||
}
|
||||
const { throwError } = await import("./ws.js");
|
||||
throwError(`${title} - ${message}`);
|
||||
throwError(`${statusCode} ${method} ${url} - ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { signal } from "@preact/signals";
|
||||
|
||||
import utils from "./utils.js";
|
||||
|
||||
export interface ToastOptions {
|
||||
@ -5,117 +7,80 @@ export interface ToastOptions {
|
||||
icon: string;
|
||||
title?: string;
|
||||
message: string;
|
||||
delay?: number;
|
||||
autohide?: boolean;
|
||||
closeAfter?: number;
|
||||
timeout?: number;
|
||||
progress?: number;
|
||||
}
|
||||
|
||||
function toast({ title, icon, message, id, delay, autohide, progress }: ToastOptions) {
|
||||
const $toast = $(title
|
||||
? `\
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">
|
||||
<span class="bx bx-${icon}"></span>
|
||||
<span class="toast-title"></span>
|
||||
</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
<div class="toast-progress"></div>
|
||||
</div>`
|
||||
: `
|
||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-icon">
|
||||
<span class="bx bx-${icon}"></span>
|
||||
</div>
|
||||
<div class="toast-body"></div>
|
||||
<div class="toast-header">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-progress"></div>
|
||||
</div>`
|
||||
);
|
||||
export type ToastOptionsWithRequiredId = Omit<ToastOptions, "id"> & Required<Pick<ToastOptions, "id">>;
|
||||
|
||||
$toast.toggleClass("no-title", !title);
|
||||
$toast.find(".toast-title").text(title ?? "");
|
||||
$toast.find(".toast-body").html(message);
|
||||
$toast.find(".toast-progress").css("width", `${(progress ?? 0) * 100}%`);
|
||||
|
||||
if (id) {
|
||||
$toast.attr("id", `toast-${id}`);
|
||||
}
|
||||
|
||||
$("#toast-container").append($toast);
|
||||
|
||||
$toast.toast({
|
||||
delay: delay || 3000,
|
||||
autohide: !!autohide
|
||||
});
|
||||
|
||||
$toast.on("hidden.bs.toast", (e) => e.target.remove());
|
||||
|
||||
$toast.toast("show");
|
||||
|
||||
return $toast;
|
||||
}
|
||||
|
||||
function showPersistent(options: ToastOptions) {
|
||||
let $toast = $(`#toast-${options.id}`);
|
||||
|
||||
if ($toast.length > 0) {
|
||||
$toast.find(".toast-body").html(options.message);
|
||||
$toast.find(".toast-progress").css("width", `${(options.progress ?? 0) * 100}%`);
|
||||
function showPersistent(options: ToastOptionsWithRequiredId) {
|
||||
const existingToast = toasts.value.find(toast => toast.id === options.id);
|
||||
if (existingToast) {
|
||||
updateToast(options.id, options);
|
||||
} else {
|
||||
options.autohide = false;
|
||||
|
||||
$toast = toast(options);
|
||||
}
|
||||
|
||||
if (options.closeAfter) {
|
||||
setTimeout(() => $toast.remove(), options.closeAfter);
|
||||
addToast(options);
|
||||
}
|
||||
}
|
||||
|
||||
function closePersistent(id: string) {
|
||||
$(`#toast-${id}`).remove();
|
||||
removeToastFromStore(id);
|
||||
}
|
||||
|
||||
function showMessage(message: string, delay = 2000, icon = "check") {
|
||||
function showMessage(message: string, timeout = 2000, icon = "bx bx-check") {
|
||||
console.debug(utils.now(), "message:", message);
|
||||
|
||||
toast({
|
||||
addToast({
|
||||
icon,
|
||||
message: message,
|
||||
autohide: true,
|
||||
delay
|
||||
message,
|
||||
timeout
|
||||
});
|
||||
}
|
||||
|
||||
export function showError(message: string, delay = 10000) {
|
||||
export function showError(message: string, timeout = 10000) {
|
||||
console.log(utils.now(), "error: ", message);
|
||||
|
||||
toast({
|
||||
addToast({
|
||||
icon: "bx bx-error-circle",
|
||||
message: message,
|
||||
autohide: true,
|
||||
delay
|
||||
message,
|
||||
timeout
|
||||
});
|
||||
}
|
||||
|
||||
function showErrorTitleAndMessage(title: string, message: string, delay = 10000) {
|
||||
function showErrorTitleAndMessage(title: string, message: string, timeout = 10000) {
|
||||
console.log(utils.now(), "error: ", message);
|
||||
|
||||
toast({
|
||||
title: title,
|
||||
addToast({
|
||||
title,
|
||||
icon: "bx bx-error-circle",
|
||||
message: message,
|
||||
autohide: true,
|
||||
delay
|
||||
message,
|
||||
timeout
|
||||
});
|
||||
}
|
||||
|
||||
//#region Toast store
|
||||
export const toasts = signal<ToastOptionsWithRequiredId[]>([]);
|
||||
|
||||
function addToast(opts: ToastOptions) {
|
||||
const id = opts.id ?? crypto.randomUUID();
|
||||
const toast = { ...opts, id };
|
||||
toasts.value = [ ...toasts.value, toast ];
|
||||
return id;
|
||||
}
|
||||
|
||||
function updateToast(id: string, partial: Partial<ToastOptions>) {
|
||||
toasts.value = toasts.value.map(toast => {
|
||||
if (toast.id === id) {
|
||||
return { ...toast, ...partial }
|
||||
}
|
||||
return toast;
|
||||
});
|
||||
}
|
||||
|
||||
export function removeToastFromStore(id: string) {
|
||||
toasts.value = toasts.value.filter(toast => toast.id !== id);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
export default {
|
||||
showMessage,
|
||||
showError,
|
||||
|
||||
@ -1135,61 +1135,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
#toast-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 20px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
--bs-toast-bg: var(--accented-background-color);
|
||||
--bs-toast-color: var(--main-text-color);
|
||||
z-index: 9999999999 !important;
|
||||
pointer-events: all;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toast-header {
|
||||
background-color: var(--more-accented-background-color) !important;
|
||||
color: var(--main-text-color) !important;
|
||||
}
|
||||
|
||||
.toast-body {
|
||||
white-space: preserve-breaks;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toast.no-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.toast.no-title .toast-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);
|
||||
}
|
||||
|
||||
.toast.no-title .toast-body {
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
.toast.no-title .toast-header {
|
||||
background-color: unset !important;
|
||||
}
|
||||
|
||||
.toast .toast-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
background-color: var(--toast-text-color) !important;
|
||||
height: 4px;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
.ck-mentions .ck-button {
|
||||
font-size: var(--detail-font-size) !important;
|
||||
padding: 5px;
|
||||
|
||||
@ -2107,5 +2107,10 @@
|
||||
},
|
||||
"popup-editor": {
|
||||
"maximize": "Switch to full editor"
|
||||
},
|
||||
"server": {
|
||||
"unknown_http_error_title": "Communication error with the server",
|
||||
"unknown_http_error_content": "Status code: {{statusCode}}\nURL: {{method}} {{url}}\nMessage: {{message}}",
|
||||
"traefik_blocks_requests": "If you are using the Traefik reverse proxy, it introduced a breaking change which affects the communication with the server."
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,4 +16,5 @@ body.zen div.read-only-note-info-bar-widget {
|
||||
:root div.read-only-note-info-bar-widget button {
|
||||
white-space: nowrap;
|
||||
padding: 2px 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
59
apps/client/src/widgets/Toast.css
Normal file
59
apps/client/src/widgets/Toast.css
Normal file
@ -0,0 +1,59 @@
|
||||
#toast-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 20px;
|
||||
pointer-events: none;
|
||||
contain: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
--bs-toast-bg: var(--accented-background-color);
|
||||
--bs-toast-color: var(--main-text-color);
|
||||
z-index: 9999999999 !important;
|
||||
pointer-events: all;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toast-header {
|
||||
background-color: var(--more-accented-background-color) !important;
|
||||
color: var(--main-text-color) !important;
|
||||
}
|
||||
|
||||
.toast-body {
|
||||
white-space: preserve-breaks;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.toast.no-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.toast.no-title .toast-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);
|
||||
}
|
||||
|
||||
.toast.no-title .toast-body {
|
||||
padding-inline-start: 0;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
.toast.no-title .toast-header {
|
||||
background-color: unset !important;
|
||||
}
|
||||
|
||||
.toast .toast-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
inset-inline-end: 0;
|
||||
background-color: var(--toast-text-color) !important;
|
||||
height: 4px;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
61
apps/client/src/widgets/Toast.tsx
Normal file
61
apps/client/src/widgets/Toast.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import "./Toast.css";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { useEffect } from "preact/hooks";
|
||||
|
||||
import { removeToastFromStore, ToastOptionsWithRequiredId, toasts } from "../services/toast";
|
||||
import Icon from "./react/Icon";
|
||||
import { RawHtmlBlock } from "./react/RawHtml";
|
||||
|
||||
export default function ToastContainer() {
|
||||
return (
|
||||
<div id="toast-container">
|
||||
{toasts.value.map(toast => <Toast key={toast.id} {...toast} />)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Toast({ id, title, timeout, progress, message, icon }: ToastOptionsWithRequiredId) {
|
||||
// Autohide.
|
||||
useEffect(() => {
|
||||
if (!timeout || timeout <= 0) return;
|
||||
const timerId = setTimeout(() => removeToastFromStore(id), timeout);
|
||||
return () => clearTimeout(timerId);
|
||||
}, [ id, timeout ]);
|
||||
|
||||
const closeButton = (
|
||||
<button
|
||||
type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"
|
||||
onClick={() => removeToastFromStore(id)}
|
||||
/>
|
||||
);
|
||||
const toastIcon = <Icon icon={icon.startsWith("bx ") ? icon : `bx bx-${icon}`} />;
|
||||
|
||||
return (
|
||||
<div
|
||||
class={clsx("toast", !title && "no-title")}
|
||||
role="alert" aria-live="assertive" aria-atomic="true"
|
||||
id={`toast-${id}`}
|
||||
>
|
||||
{title ? (
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">
|
||||
{toastIcon}
|
||||
<span class="toast-title">{title}</span>
|
||||
</strong>
|
||||
{closeButton}
|
||||
</div>
|
||||
) : (
|
||||
<div class="toast-icon">{toastIcon}</div>
|
||||
)}
|
||||
|
||||
<RawHtmlBlock className="toast-body" html={message} />
|
||||
|
||||
{!title && <div class="toast-header">{closeButton}</div>}
|
||||
<div
|
||||
class="toast-progress"
|
||||
style={{ width: `${(progress ?? 0) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -169,10 +169,11 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
console.log("Got issue in widget ", this);
|
||||
console.error(e);
|
||||
|
||||
let noteId = this._noteId;
|
||||
const noteId = this._noteId;
|
||||
if (this._noteId) {
|
||||
froca.getNote(noteId, true).then((note) => {
|
||||
toastService.showPersistent({
|
||||
id: `custom-widget-failure-${noteId}`,
|
||||
title: t("toast.widget-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.widget-error.message-custom", {
|
||||
@ -182,16 +183,16 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
||||
})
|
||||
});
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
toastService.showPersistent({
|
||||
id: `custom-widget-failure-unknown-${crypto.randomUUID()}`,
|
||||
title: t("toast.widget-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.widget-error.message-unknown", {
|
||||
message: e.message || e.toString()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
toastService.showPersistent({
|
||||
title: t("toast.widget-error.title"),
|
||||
icon: "bx bx-error-circle",
|
||||
message: t("toast.widget-error.message-unknown", {
|
||||
message: e.message || e.toString()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -6,7 +6,7 @@ import FormRadioGroup from "../react/FormRadioGroup";
|
||||
import Modal from "../react/Modal";
|
||||
import "./export.css";
|
||||
import ws from "../../services/ws";
|
||||
import toastService, { ToastOptions } from "../../services/toast";
|
||||
import toastService, { type ToastOptionsWithRequiredId } from "../../services/toast";
|
||||
import utils from "../../services/utils";
|
||||
import open from "../../services/open";
|
||||
import froca from "../../services/froca";
|
||||
@ -132,11 +132,11 @@ function exportBranch(branchId: string, type: string, format: string, version: s
|
||||
}
|
||||
|
||||
ws.subscribeToMessages(async (message) => {
|
||||
function makeToast(id: string, message: string): ToastOptions {
|
||||
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
|
||||
return {
|
||||
id: id,
|
||||
id,
|
||||
title: t("export.export_status"),
|
||||
message: message,
|
||||
message,
|
||||
icon: "export"
|
||||
};
|
||||
}
|
||||
@ -152,7 +152,7 @@ ws.subscribeToMessages(async (message) => {
|
||||
toastService.showPersistent(makeToast(message.taskId, t("export.export_in_progress", { progressCount: message.progressCount })));
|
||||
} else if (message.type === "taskSucceeded") {
|
||||
const toast = makeToast(message.taskId, t("export.export_finished_successfully"));
|
||||
toast.closeAfter = 5000;
|
||||
toast.timeout = 5000;
|
||||
|
||||
toastService.showPersistent(toast);
|
||||
}
|
||||
|
||||
@ -725,7 +725,7 @@ export function useImperativeSearchHighlighlighting(highlightedTokens: string[]
|
||||
|
||||
export function useNoteTreeDrag(containerRef: MutableRef<HTMLElement | null | undefined>, { dragEnabled, dragNotEnabledMessage, callback }: {
|
||||
dragEnabled: boolean,
|
||||
dragNotEnabledMessage: Omit<ToastOptions, "id" | "closeAfter">;
|
||||
dragNotEnabledMessage: Omit<ToastOptions, "id">;
|
||||
callback: (data: DragData[], e: DragEvent) => void
|
||||
}) {
|
||||
useEffect(() => {
|
||||
@ -737,7 +737,7 @@ export function useNoteTreeDrag(containerRef: MutableRef<HTMLElement | null | un
|
||||
toast.showPersistent({
|
||||
...dragNotEnabledMessage,
|
||||
id: "drag-not-enabled",
|
||||
closeAfter: 5000
|
||||
timeout: 5000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
12
apps/server/docker/traefik/dynamic/trilium.yml
Normal file
12
apps/server/docker/traefik/dynamic/trilium.yml
Normal file
@ -0,0 +1,12 @@
|
||||
http:
|
||||
routers:
|
||||
trilliumnext:
|
||||
rule: "PathPrefix(`/`)" # accept everything for dev purposes
|
||||
entryPoints: ["web"]
|
||||
service: trilliumnext
|
||||
|
||||
services:
|
||||
trilliumnext:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://127.0.0.1:8080/"
|
||||
15
apps/server/docker/traefik/traefik.yml
Normal file
15
apps/server/docker/traefik/traefik.yml
Normal file
@ -0,0 +1,15 @@
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":8090"
|
||||
http:
|
||||
encodedCharacters:
|
||||
allowEncodedSlash: true
|
||||
allowEncodedHash: true
|
||||
|
||||
providers:
|
||||
file:
|
||||
directory: "/etc/traefik/dynamic"
|
||||
watch: true
|
||||
|
||||
log:
|
||||
level: INFO
|
||||
@ -24,7 +24,8 @@
|
||||
"docker-start-alpine": "pnpm docker-build-alpine && docker run -p 8081:8080 triliumnext-alpine",
|
||||
"docker-start-rootless-debian": "pnpm docker-build-rootless-debian && docker run -p 8081:8080 triliumnext-rootless-debian",
|
||||
"docker-start-rootless-alpine": "pnpm docker-build-rootless-alpine && docker run -p 8081:8080 triliumnext-rootless-alpine",
|
||||
"generate-document": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=src tsx ./scripts/generate_document.ts"
|
||||
"generate-document": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=src tsx ./scripts/generate_document.ts",
|
||||
"proxy-traefik": "docker run --name trilium-traefik --rm --network=host -v ./docker/traefik/traefik.yml:/etc/traefik/traefik.yml -v ./docker/traefik/dynamic:/etc/traefik/dynamic traefik:latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "12.5.0",
|
||||
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Calendar.html
generated
vendored
2
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Calendar.html
generated
vendored
@ -216,7 +216,7 @@
|
||||
<tr>
|
||||
<td><code>#calendar:color</code>
|
||||
</td>
|
||||
<td><strong>❌️ Removed since v0.100.0. Use</strong> <code>**#color**</code> <strong>instead.</strong>
|
||||
<td><strong>❌️ Removed since v0.100.0. Use</strong> <code>**#color**</code> <strong>instead.</strong>
|
||||
<br>
|
||||
<br>Similar to <code>#color</code>, but applies the color only for the event
|
||||
in the calendar and not for other places such as the note tree.</td>
|
||||
|
||||
@ -1,5 +1,24 @@
|
||||
<p>Configure Traefik proxy and HTTPS. See <a href="https://github.com/TriliumNext/Trilium/issues/7768#issuecomment-3539165814">#7768</a> for
|
||||
reference</p>
|
||||
<p>The goal of this article is to configure Traefik proxy and HTTPS. See
|
||||
<a
|
||||
href="https://github.com/TriliumNext/Trilium/issues/7768#issuecomment-3539165814">#7768</a>for reference.</p>
|
||||
<h2>Breaking change in Traefik 3.6.4</h2>
|
||||
<p>Traefik 3.6.4 introduced a <a href="https://doc.traefik.io/traefik/migrate/v3/#encoded-characters-in-request-path">breaking change</a> regarding
|
||||
how percent-encoded characters are handled in URLs. More specifically some
|
||||
URLs used by Trilium (such as <code spellcheck="false">search/%23workspace%20%23!template</code>)
|
||||
are automatically rejected by Traefik, resulting in HTTP 400 errors.</p>
|
||||
<p>To solve this, the Traefik <a href="https://doc.traefik.io/traefik/getting-started/configuration-overview/#the-install-configuration"><strong>static</strong> configuration</a> must
|
||||
be modified in order to allow those characters:</p><pre><code class="language-text-x-yaml">entryPoints:
|
||||
web:
|
||||
http:
|
||||
encodedCharacters:
|
||||
allowEncodedSlash: true
|
||||
allowEncodedHash: true</code></pre>
|
||||
<aside class="admonition tip">
|
||||
<p>If you still have issues, depending on how Trilium is used (especially
|
||||
regarding search), you might need to enable more encoded character groups.
|
||||
For more information, see <a href="https://github.com/TriliumNext/Trilium/issues/7968">the relevant GitHub issue</a>;
|
||||
feel free to report your findings.</p>
|
||||
</aside>
|
||||
<h3>Build the docker-compose file</h3>
|
||||
<p>Setting up Traefik as reverse proxy requires setting the following labels:</p><pre><code class="language-text-x-yaml"> labels:
|
||||
- traefik.enable=true
|
||||
@ -16,8 +35,8 @@
|
||||
- traefik.http.middlewares.trilium-headers.headers.customrequestheaders.X-Forwarded-Proto=https</code></pre>
|
||||
<h3>Setup needed environment variables</h3>
|
||||
<p>After setting up a reverse proxy, make sure to configure the <a class="reference-link"
|
||||
href="#root/_help_LLzSMXACKhUs">[missing note]</a>.</p>
|
||||
<h3>Example <code>docker-compose.yaml</code></h3><pre><code class="language-text-x-yaml">services:
|
||||
href="#root/_help_LLzSMXACKhUs">Trusted proxy</a>.</p>
|
||||
<h3>Example <code spellcheck="false">docker-compose.yaml</code></h3><pre><code class="language-text-x-yaml">services:
|
||||
trilium:
|
||||
image: triliumnext/trilium
|
||||
container_name: trilium
|
||||
|
||||
@ -1,33 +1,28 @@
|
||||
<p>Launch bar widgets are a subset of <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_MgibgPcfeuGz">Custom Widgets</a> that
|
||||
<p>Launch bar widgets are a subset of <a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a> that
|
||||
can be used to render custom buttons and widgets inside the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>.</p>
|
||||
href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>.</p>
|
||||
<h2>Creating a launch bar widget</h2>
|
||||
<p>Unlike <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_MgibgPcfeuGz">Custom Widgets</a>,
|
||||
<p>Unlike <a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>,
|
||||
the process of setting up a launch bar widget is slightly different:</p>
|
||||
<ol>
|
||||
<li data-list-item-id="e3111af31ab8707d93fb9e7feb1ac804d">Create a Code note of type <em>JavaScript (front-end)</em>.
|
||||
<li>Create a Code note of type <em>JavaScript (front-end)</em>.
|
||||
<ul>
|
||||
<li data-list-item-id="ea71ac173fc302483b5f571fd8bbd4142">The script itself uses the same concepts as <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_MgibgPcfeuGz">Custom Widgets</a>,
|
||||
including the use of a <code spellcheck="false">NoteContextAwareWidget</code> or
|
||||
a <code spellcheck="false">BasicWidget</code> (according to needs).</li>
|
||||
<li
|
||||
data-list-item-id="e06c05a8bdfaa11ad4214ccc5405f50cc">As examples, see <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/4Gn3psZKsfSm/_help_IPArqVfDQ4We">Note Title Widget</a> and
|
||||
<li>The script itself uses the same concepts as <a class="reference-link"
|
||||
href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>, including the use of a <code>NoteContextAwareWidget</code> or
|
||||
a <code>BasicWidget</code> (according to needs).</li>
|
||||
<li>As examples, see <a class="reference-link" href="#root/_help_IPArqVfDQ4We">Note Title Widget</a> and
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/4Gn3psZKsfSm/_help_gcI7RPbaNSh3">Analog Watch</a>.</li>
|
||||
</ul>
|
||||
class="reference-link" href="#root/_help_gcI7RPbaNSh3">Analog Watch</a>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li data-list-item-id="e31368cfb0655cfc527347b9dcbfa7d17">Don't set <code spellcheck="false">#widget</code>, as that attribute is
|
||||
reserved for <a class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/_help_MgibgPcfeuGz">Custom Widgets</a>.</li>
|
||||
<li
|
||||
data-list-item-id="e26f51e3ad87cebfa6c72504dab691804">In the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a>,
|
||||
<li>Don't set <code>#widget</code>, as that attribute is reserved for
|
||||
<a
|
||||
class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>.</li>
|
||||
<li>In the <a class="reference-link" href="#root/_help_x3i7MxGccDuM">Global menu</a>,
|
||||
select <em>Configure launchbar</em>.</li>
|
||||
<li data-list-item-id="ebd6a4fab8be5557cb958d6bf87b65d84">In the <em>Visible Launchers</em> section, select <em>Add a custom widget</em>.</li>
|
||||
<li
|
||||
data-list-item-id="ef1cb61670f561ad918be4f072d325bc7">Give the newly created launcher a name (and optionally a name).</li>
|
||||
<li
|
||||
data-list-item-id="e0b141f895a6a9973f31a71ba99471a49">In the <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> section,
|
||||
modify the <em>widget</em> field to point to the newly created note.</li>
|
||||
<li
|
||||
data-list-item-id="e5218927546ad96070b3028534e93131b">Refresh the UI. </li>
|
||||
<li>In the <em>Visible Launchers</em> section, select <em>Add a custom widget</em>.</li>
|
||||
<li>Give the newly created launcher a name (and optionally a name).</li>
|
||||
<li>In the <a class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> section,
|
||||
modify the <em>widget</em> field to point to the newly created note.</li>
|
||||
<li>Refresh the UI.</li>
|
||||
</ol>
|
||||
@ -4,7 +4,7 @@
|
||||
</figure>
|
||||
<p>This is a more intricate example of a basic widget, which displays an
|
||||
analog watch in the launch bar. Unlike note-context aware widgets, basic
|
||||
widgets don't react to note navigation.</p><pre><code class="language-application-javascript-env-frontend">const TPL = `
|
||||
widgets don't react to note navigation.</p><pre><code class="language-application-javascript-env-backend">const TPL = `
|
||||
<div class="analog-watch" style="
|
||||
position: relative;
|
||||
height: 38px;
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
currently opened note and refreshes automatically as the user navigates
|
||||
through the notes.</p>
|
||||
<p>In this example, the title of the note is displayed. It works best on
|
||||
the <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x0JgW8UqGXvq">horizontal layout</a>.</p><pre><code class="language-application-javascript-env-frontend">const TPL = `\
|
||||
the <a href="#root/_help_x0JgW8UqGXvq">horizontal layout</a>.</p><pre><code class="language-application-javascript-env-backend">const TPL = `\
|
||||
<div style="
|
||||
display: flex;
|
||||
height: 53px;
|
||||
|
||||
@ -22,8 +22,6 @@
|
||||
document.getElementsByTagName("body")[0].style.display = "none";
|
||||
</script>
|
||||
|
||||
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
|
||||
|
||||
<%- include("./partials/windowGlobal.ejs", locals) %>
|
||||
|
||||
@ -105,8 +105,6 @@
|
||||
>
|
||||
<noscript><%= t("javascript-required") %></noscript>
|
||||
|
||||
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
||||
|
||||
<div id="context-menu-cover"></div>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# Documentation
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/hOX4EFIkAwyJ/Documentation_image.png" width="205" height="162">
|
||||
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/tZh9T30Ojdsq/Documentation_image.png" width="205" height="162">
|
||||
|
||||
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
|
||||
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
|
||||
|
||||
98
docs/User Guide/!!!meta.json
vendored
98
docs/User Guide/!!!meta.json
vendored
@ -15662,6 +15662,48 @@
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "MgibgPcfeuGz",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "xYmIYSP6wE3F",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "IPArqVfDQ4We",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "gcI7RPbaNSh3",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "x3i7MxGccDuM",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "OFXdgB2nNk1F",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
@ -15675,48 +15717,6 @@
|
||||
"value": "launch-bar-widgets",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "MgibgPcfeuGz",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "xYmIYSP6wE3F",
|
||||
"isInheritable": false,
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "x3i7MxGccDuM",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "OFXdgB2nNk1F",
|
||||
"isInheritable": false,
|
||||
"position": 80
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "IPArqVfDQ4We",
|
||||
"isInheritable": false,
|
||||
"position": 90
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "gcI7RPbaNSh3",
|
||||
"isInheritable": false,
|
||||
"position": 100
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
@ -15741,6 +15741,13 @@
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "x0JgW8UqGXvq",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "shareAlias",
|
||||
@ -15754,13 +15761,6 @@
|
||||
"value": "note-title-widget",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "x0JgW8UqGXvq",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
|
||||
@ -1,5 +1,23 @@
|
||||
# Traefik
|
||||
Configure Traefik proxy and HTTPS. See [#7768](https://github.com/TriliumNext/Trilium/issues/7768#issuecomment-3539165814) for reference
|
||||
The goal of this article is to configure Traefik proxy and HTTPS. See [#7768](https://github.com/TriliumNext/Trilium/issues/7768#issuecomment-3539165814) for reference.
|
||||
|
||||
## Breaking change in Traefik 3.6.4
|
||||
|
||||
Traefik 3.6.4 introduced a [breaking change](https://doc.traefik.io/traefik/migrate/v3/#encoded-characters-in-request-path) regarding how percent-encoded characters are handled in URLs. More specifically some URLs used by Trilium (such as `search/%23workspace%20%23!template`) are automatically rejected by Traefik, resulting in HTTP 400 errors.
|
||||
|
||||
To solve this, the Traefik [**static** configuration](https://doc.traefik.io/traefik/getting-started/configuration-overview/#the-install-configuration) must be modified in order to allow those characters:
|
||||
|
||||
```yaml
|
||||
entryPoints:
|
||||
web:
|
||||
http:
|
||||
encodedCharacters:
|
||||
allowEncodedSlash: true
|
||||
allowEncodedHash: true
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> If you still have issues, depending on how Trilium is used (especially regarding search), you might need to enable more encoded character groups. For more information, see [the relevant GitHub issue](https://github.com/TriliumNext/Trilium/issues/7968); feel free to report your findings.
|
||||
|
||||
### Build the docker-compose file
|
||||
|
||||
@ -23,7 +41,7 @@ Setting up Traefik as reverse proxy requires setting the following labels:
|
||||
|
||||
### Setup needed environment variables
|
||||
|
||||
After setting up a reverse proxy, make sure to configure the <a class="reference-link" href="Trusted%20proxy.md">[missing note]</a>.
|
||||
After setting up a reverse proxy, make sure to configure the <a class="reference-link" href="Trusted%20proxy.md">Trusted proxy</a>.
|
||||
|
||||
### Example `docker-compose.yaml`
|
||||
|
||||
|
||||
@ -7,6 +7,21 @@ import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||
import playwright from "eslint-plugin-playwright";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import preact from "eslint-config-preact";
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
|
||||
// Go to https://eslint.style/rules/default/${rule_without_prefix} to check the rule details
|
||||
export const stylisticRules = {
|
||||
"@stylistic/indent": ["error", 4],
|
||||
// "@stylistic/quotes": ["error", "double", { avoidEscape: true, allowTemplateLiterals: "always" }],
|
||||
"@stylistic/semi": ["error", "always"],
|
||||
// "@stylistic/quote-props": ["error", "consistent-as-needed"],
|
||||
// "@stylistic/max-len": ["error", { code: 100 }],
|
||||
// "@stylistic/comma-dangle": ["error", "never"],
|
||||
// "@stylistic/linebreak-style": ["error", "unix"],
|
||||
// "@stylistic/array-bracket-spacing": ["error", "always"],
|
||||
// "@stylistic/object-curly-spacing": ["error", "always"],
|
||||
// "@stylistic/padded-blocks": ["error", { classes: "always" }]
|
||||
};
|
||||
|
||||
const mainConfig = [
|
||||
...preact,
|
||||
@ -43,8 +58,23 @@ const mainConfig = [
|
||||
},
|
||||
|
||||
rules: {
|
||||
"simple-import-sort/imports": "error",
|
||||
"simple-import-sort/exports": "error"
|
||||
"simple-import-sort/imports": "warn",
|
||||
"simple-import-sort/exports": "warn"
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["**/*.{js,ts,mjs,cjs}"],
|
||||
|
||||
languageOptions: {
|
||||
parser: tsParser
|
||||
},
|
||||
|
||||
plugins: {
|
||||
"@stylistic": stylistic
|
||||
},
|
||||
|
||||
rules: {
|
||||
...stylisticRules
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -190,6 +190,9 @@ importers:
|
||||
'@popperjs/core':
|
||||
specifier: 2.11.8
|
||||
version: 2.11.8
|
||||
'@preact/signals':
|
||||
specifier: 2.5.1
|
||||
version: 2.5.1(preact@10.28.0)
|
||||
'@triliumnext/ckeditor5':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ckeditor5
|
||||
@ -3897,6 +3900,14 @@ packages:
|
||||
'@babel/core': 7.x
|
||||
vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x
|
||||
|
||||
'@preact/signals-core@1.12.1':
|
||||
resolution: {integrity: sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==}
|
||||
|
||||
'@preact/signals@2.5.1':
|
||||
resolution: {integrity: sha512-VPjk5YFt7i11Fi4UK0tzaEe5xLwfhUxXL3l89ocxQ5aPz7bRo8M5+N73LjBMPklyXKYKz6YsNo4Smp8n6nplng==}
|
||||
peerDependencies:
|
||||
preact: 10.28.0
|
||||
|
||||
'@prefresh/babel-plugin@0.5.2':
|
||||
resolution: {integrity: sha512-AOl4HG6dAxWkJ5ndPHBgBa49oo/9bOiJuRDKHLSTyH+Fd9x00shTXpdiTj1W41l6oQIwUOAgJeHMn4QwIDpHkA==}
|
||||
|
||||
@ -15268,8 +15279,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||
dependencies:
|
||||
@ -15488,8 +15497,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-editor-multi-root@47.3.0':
|
||||
dependencies:
|
||||
@ -15512,8 +15519,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-table': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-emoji@47.3.0':
|
||||
dependencies:
|
||||
@ -18466,6 +18471,13 @@ snapshots:
|
||||
- preact
|
||||
- supports-color
|
||||
|
||||
'@preact/signals-core@1.12.1': {}
|
||||
|
||||
'@preact/signals@2.5.1(preact@10.28.0)':
|
||||
dependencies:
|
||||
'@preact/signals-core': 1.12.1
|
||||
preact: 10.28.0
|
||||
|
||||
'@prefresh/babel-plugin@0.5.2': {}
|
||||
|
||||
'@prefresh/core@1.5.5(preact@10.28.0)':
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user