Compare commits

...

45 Commits

Author SHA1 Message Date
Elian Doran
d873accf3e Merge remote-tracking branch 'origin/main' into feature/cleanup_ck_modules 2026-04-06 12:41:45 +03:00
Elian Doran
94b448863c chore(deps): update dependency esbuild to v0.28.0 (#9302) 2026-04-06 12:34:15 +03:00
Elian Doran
32acc8555d fix(deps): update dependency @eslint/js to v10 (#9308) 2026-04-06 12:33:56 +03:00
Elian Doran
d68ad84155 Refactor/build warnings due to imports (#9309) 2026-04-06 12:32:32 +03:00
Elian Doran
45e82b7f33 test(ckeditor5-mermaid): fix type errors 2026-04-06 12:31:53 +03:00
Elian Doran
55ad0fe9f0 test(ckeditor5-mermaid): broken tests after change in rendering 2026-04-06 12:30:33 +03:00
Elian Doran
559815273e fix(ckeditor5-mermaid): protect against multiple init 2026-04-06 12:27:12 +03:00
Elian Doran
af76740fd9 fix(ckeditor5-mermaid): protect against stale renders 2026-04-06 12:26:17 +03:00
Elian Doran
7dadd50bfe chore(ckeditor5-mermaid): don't remove parent element on error 2026-04-06 12:25:50 +03:00
Elian Doran
dd4cab22c1 chore(client): address requested changes 2026-04-06 12:22:00 +03:00
Elian Doran
c4d3e776a1 refactor(client): the last circular dependency 2026-04-06 12:20:40 +03:00
Elian Doran
19bb7f5ddb refactor(server): remove unnecessary route 2026-04-06 12:18:36 +03:00
Elian Doran
d212120f9b refactor(client): read locales from common instead of going through the server 2026-04-06 12:16:32 +03:00
Elian Doran
42da1872e7 fix(client): crashing due to circular dependency 2026-04-06 12:10:33 +03:00
Elian Doran
a080b50c45 refactor(client): duplicate toast import 2026-04-06 12:06:27 +03:00
Elian Doran
6d31e9b028 feat(ckeditor5-mermaid): use more modern mechanism for rendering with less flicker 2026-04-06 12:03:29 +03:00
Elian Doran
b606afa858 refactor(ckeditor5-mermaid): get rid of any runtime dependencies 2026-04-06 11:59:24 +03:00
Elian Doran
f9446304b3 refactor(ckeditor5-mermaid): switch to es-toolkit 2026-04-06 11:57:31 +03:00
renovate[bot]
fbe312d580 chore(deps): update dependency esbuild to v0.28.0 2026-04-06 08:54:56 +00:00
Elian Doran
8d383caaff chore(deps): update dependency @electron/fuses to v2 (#9304) 2026-04-06 11:53:07 +03:00
Elian Doran
6caf4fa7ce chore(deps): update dependency copy-webpack-plugin to v14 (#9305) 2026-04-06 11:52:29 +03:00
Elian Doran
606d58b08c chore(ckeditor5-*): remove unnecessary publishing stack 2026-04-06 11:49:47 +03:00
Elian Doran
09258179f0 refactor(client): one more ineffective dynamic import due to appContext 2026-04-06 11:46:35 +03:00
Elian Doran
40e986b188 refactor(client): ineffective dynamic imports 2026-04-06 11:41:35 +03:00
Elian Doran
37e47041bf Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-04-06 11:41:09 +03:00
Elian Doran
543438bca0 chore(ai): mention instructions for adding new locale 2026-04-06 11:28:10 +03:00
Elian Doran
b31290c1fc fix(deps): update dependency fuse.js to v7.2.0 (#9303) 2026-04-06 11:22:33 +03:00
Elian Doran
d41111a209 docs(dev): remove unnecessary step for adding new locale 2026-04-06 11:21:17 +03:00
Elian Doran
828b523382 feat(i18n): enable Czech 2026-04-06 11:20:58 +03:00
Elian Doran
32409ecbee Translations update from Hosted Weblate (#9295) 2026-04-06 11:16:03 +03:00
renovate[bot]
3ca2cec63a chore(deps): update dependency copy-webpack-plugin to v14 2026-04-06 08:14:46 +00:00
Francis C.
1ed2db0c82 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1842 of 1842 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-04-06 08:13:48 +00:00
Francis C.
2423b74dd0 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (391 of 391 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/zh_Hant/
2026-04-06 08:13:48 +00:00
Tomas Adamek
3f781ea298 Translated using Weblate (Czech)
Currently translated at 98.9% (1822 of 1842 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/cs/
2026-04-06 08:13:47 +00:00
Tomas Adamek
30c5c49aef Translated using Weblate (Czech)
Currently translated at 100.0% (391 of 391 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/cs/
2026-04-06 08:13:47 +00:00
Tomas Adamek
9421e39c34 Translated using Weblate (Czech)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/cs/
2026-04-06 08:13:46 +00:00
Tomas Adamek
c46805cf4f Translated using Weblate (Czech)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/cs/
2026-04-06 08:13:46 +00:00
Elian Doran
f181343fca chore(deps): update dependency @redocly/cli to v2.25.4 (#9297) 2026-04-06 11:13:37 +03:00
Elian Doran
8a512e4f73 chore(deps): update dependency electron to v41 (#9306) 2026-04-06 11:13:01 +03:00
Elian Doran
06a3750168 chore(renovate): group AI SDK updates 2026-04-06 11:09:07 +03:00
renovate[bot]
35c1a5642d fix(deps): update dependency @eslint/js to v10 2026-04-06 01:05:51 +00:00
renovate[bot]
f29df2ad28 chore(deps): update dependency electron to v41 2026-04-06 01:03:55 +00:00
renovate[bot]
75a5714451 chore(deps): update dependency @electron/fuses to v2 2026-04-06 01:01:53 +00:00
renovate[bot]
2882863b5b fix(deps): update dependency fuse.js to v7.2.0 2026-04-06 01:00:57 +00:00
renovate[bot]
773b6cca14 chore(deps): update dependency @redocly/cli to v2.25.4 2026-04-06 00:54:32 +00:00
61 changed files with 3588 additions and 1656 deletions

View File

@@ -320,6 +320,7 @@ Trilium provides powerful user scripting capabilities:
- Use translation system via `t()` function
- Automatic pluralization: Add `_other` suffix to translation keys (e.g., `item` and `item_other` for singular/plural)
- When a translated string contains **interpolated components** (e.g. links, note references) whose order may vary across languages, use `<Trans>` from `react-i18next` instead of `t()`. This lets translators reorder components freely (e.g. `"<Note/> in <Parent/>"` vs `"in <Parent/>, <Note/>"`)
- When adding a new locale, follow the step-by-step guide in `docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md`
## Testing Conventions

View File

@@ -121,6 +121,7 @@ Trilium provides powerful user scripting capabilities:
- **Only add new translation keys to `en/translation.json`** — translations for other languages are managed via Weblate and will be contributed by the community
- Third-party components (e.g., mind-map context menu) should use i18next `t()` for their labels, with the English strings added to `en/translation.json` under a dedicated namespace (e.g., `"mind-map"`)
- When a translated string contains **interpolated components** (e.g. links, note references) whose order may vary across languages, use `<Trans>` from `react-i18next` instead of `t()`. This lets translators reorder components freely (e.g. `"<Note/> in <Parent/>"` vs `"in <Parent/>, <Note/>"`)
- When adding a new locale, follow the step-by-step guide in `docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md`
### Security Considerations
- Per-note encryption with granular protected sessions

View File

@@ -16,7 +16,7 @@
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.33.0",
"devDependencies": {
"@redocly/cli": "2.25.3",
"@redocly/cli": "2.25.4",
"archiver": "7.0.1",
"fs-extra": "11.3.4",
"js-yaml": "4.1.1",

View File

@@ -1,10 +1,11 @@
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror";
import { SqlExecuteResponse } from "@triliumnext/commons";
import { type LOCALE_IDS, SqlExecuteResponse } from "@triliumnext/commons";
import type { NativeImage, TouchBar } from "electron";
import { ColumnComponent } from "tabulator-tables";
import type { Attribute } from "../services/attribute_parser.js";
import bundleService from "../services/bundle.js";
import froca from "../services/froca.js";
import { initLocale, t } from "../services/i18n.js";
import keyboardActionsService from "../services/keyboard_actions.js";
@@ -563,7 +564,7 @@ export class AppContext extends Component {
*/
async earlyInit() {
await options.initializedPromise;
await initLocale();
await initLocale((options.get("locale") || "en") as LOCALE_IDS);
}
setLayout(layout: Layout) {
@@ -578,7 +579,6 @@ export class AppContext extends Component {
this.tabManager.loadTabs();
const bundleService = (await import("../services/bundle.js")).default;
setTimeout(() => bundleService.executeStartupBundles(), 2000);
}

View File

@@ -1,5 +1,6 @@
import { getNoteIcon } from "@triliumnext/commons";
import bundleService from "../services/bundle.js";
import cssClassManager from "../services/css_class_manager.js";
import type { Froca } from "../services/froca-interface.js";
import noteAttributeCache from "../services/note_attribute_cache.js";
@@ -1014,7 +1015,6 @@ export default class FNote {
const env = this.getScriptEnv();
if (env === "frontend") {
const bundleService = (await import("../services/bundle.js")).default;
return await bundleService.getAndExecuteBundle(this.noteId);
} else if (env === "backend") {
await server.post(`script/run/${this.noteId}`);

View File

@@ -1,12 +1,14 @@
import utils from "../services/utils.js";
import options from "../services/options.js";
import zoomService from "../components/zoom.js";
import contextMenu, { type MenuItem } from "./context_menu.js";
import { t } from "../services/i18n.js";
import server from "../services/server.js";
import * as clipboardExt from "../services/clipboard_ext.js";
import type { BrowserWindow } from "electron";
import type { CommandNames, AppContext } from "../components/app_context.js";
import type { CommandNames } from "../components/app_context.js";
import appContext from "../components/app_context.js";
import zoomService from "../components/zoom.js";
import * as clipboardExt from "../services/clipboard_ext.js";
import { t } from "../services/i18n.js";
import options from "../services/options.js";
import server from "../services/server.js";
import utils from "../services/utils.js";
import contextMenu, { type MenuItem } from "./context_menu.js";
function setupContextMenu() {
const electron = utils.dynamicRequire("electron");
@@ -15,8 +17,6 @@ function setupContextMenu() {
// FIXME: Remove typecast once Electron is properly integrated.
const { webContents } = remote.getCurrentWindow() as BrowserWindow;
let appContext: AppContext;
webContents.on("context-menu", (event, params) => {
const { editFlags } = params;
const hasText = params.selectionText.trim().length > 0;
@@ -141,7 +141,7 @@ function setupContextMenu() {
}
// Replace the placeholder with the real search keyword.
let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText));
const searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText));
items.push({ kind: "separator" });
@@ -155,10 +155,6 @@ function setupContextMenu() {
title: t("electron_context_menu.search_in_trilium", { term: shortenedSelection }),
uiIcon: "bx bx-search",
handler: async () => {
if (!appContext) {
appContext = (await import("../components/app_context.js")).default;
}
await appContext.triggerCommand("searchNotes", {
searchString: params.selectionText
});

View File

@@ -4,6 +4,7 @@ import { useCallback, useLayoutEffect, useRef } from "preact/hooks";
import FNote from "./entities/fnote";
import content_renderer from "./services/content_renderer";
import { applyInlineMermaid } from "./services/content_renderer_text";
import froca from "./services/froca";
import { dynamicRequire, isElectron } from "./services/utils";
import { CustomNoteList, useNoteViewType } from "./widgets/collections/NoteList";
@@ -30,7 +31,6 @@ async function main() {
if (!noteId) return;
await import("./print.css");
const froca = (await import("./services/froca")).default;
const note = await froca.getNote(noteId);
const bodyWrapper = document.createElement("div");

View File

@@ -26,7 +26,7 @@ type WithNoteId<T> = T & {
};
export type Widget = WithNoteId<(LegacyWidget | WidgetDefinitionWithType)>;
async function getAndExecuteBundle(noteId: string, originEntity = null, script = null, params = null) {
async function getAndExecuteBundle(noteId: string, originEntity: Entity | null = null, script: string | null = null, params: string | null = null) {
const bundle = await server.post<Bundle>(`script/bundle/${noteId}`, {
script,
params

View File

@@ -1,3 +1,6 @@
import { t } from "./i18n.js";
import toast from "./toast.js";
export function copyText(text: string) {
if (!text) {
return;
@@ -6,29 +9,26 @@ export function copyText(text: string) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text);
return true;
} else {
// Fallback method: https://stackoverflow.com/a/72239825
const textArea = document.createElement("textarea");
textArea.value = text;
try {
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
return document.execCommand('copy');
} finally {
document.body.removeChild(textArea);
}
}
// Fallback method: https://stackoverflow.com/a/72239825
const textArea = document.createElement("textarea");
textArea.value = text;
try {
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
return document.execCommand('copy');
} finally {
document.body.removeChild(textArea);
}
} catch (e) {
console.warn(e);
return false;
}
}
export async function copyTextWithToast(text: string) {
const t = (await import("./i18n.js")).t;
const toast = (await import("./toast.js")).default;
export function copyTextWithToast(text: string) {
if (copyText(text)) {
toast.showMessage(t("clipboard.copy_success"));
} else {

View File

@@ -1,9 +1,11 @@
import { Modal } from "bootstrap";
import appContext from "../components/app_context.js";
import type { ConfirmDialogOptions, ConfirmDialogResult, ConfirmWithMessageOptions, MessageType } from "../widgets/dialogs/confirm.js";
import { InfoExtraProps } from "../widgets/dialogs/info.jsx";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import { focusSavedElement, saveFocusedElement } from "./focus.js";
import { InfoExtraProps } from "../widgets/dialogs/info.jsx";
import keyboardActionsService from "./keyboard_actions.js";
export async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true, config?: Partial<Modal.Options>) {
if (closeActDialog) {
@@ -25,7 +27,6 @@ export async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog =
}
});
const keyboardActionsService = (await import("./keyboard_actions.js")).default;
keyboardActionsService.updateDisplayedShortcuts($dialog);
return $dialog;

View File

@@ -1,14 +1,16 @@
import LoadResults from "./load_results.js";
import froca from "./froca.js";
import utils from "./utils.js";
import options from "./options.js";
import noteAttributeCache from "./note_attribute_cache.js";
import FBranch, { type FBranchRow } from "../entities/fbranch.js";
import FAttribute, { type FAttributeRow } from "../entities/fattribute.js";
import type { OptionNames } from "@triliumnext/commons";
import appContext from "../components/app_context.js";
import FAttachment, { type FAttachmentRow } from "../entities/fattachment.js";
import FAttribute, { type FAttributeRow } from "../entities/fattribute.js";
import FBranch, { type FBranchRow } from "../entities/fbranch.js";
import type { default as FNote, FNoteRow } from "../entities/fnote.js";
import type { EntityChange } from "../server_types.js";
import type { OptionNames } from "@triliumnext/commons";
import froca from "./froca.js";
import LoadResults from "./load_results.js";
import noteAttributeCache from "./note_attribute_cache.js";
import options from "./options.js";
import utils from "./utils.js";
async function processEntityChanges(entityChanges: EntityChange[]) {
const loadResults = new LoadResults(entityChanges);
@@ -63,7 +65,7 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
if (entityName === "branches" && !((entity as FBranchRow).parentNoteId in froca.notes)) {
missingNoteIds.push((entity as FBranchRow).parentNoteId);
} else if (entityName === "attributes") {
let attributeEntity = entity as FAttributeRow;
const attributeEntity = entity as FAttributeRow;
if (attributeEntity.type === "relation" && (attributeEntity.name === "template" || attributeEntity.name === "inherit") && !(attributeEntity.value in froca.notes)) {
missingNoteIds.push(attributeEntity.value);
}
@@ -79,7 +81,6 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
noteAttributeCache.invalidate();
}
const appContext = (await import("../components/app_context.js")).default;
await appContext.triggerEvent("entitiesReloaded", { loadResults });
}
}

View File

@@ -1,21 +1,14 @@
import options from "./options.js";
import { LOCALE_IDS, LOCALES, setDayjsLocale } from "@triliumnext/commons";
import i18next from "i18next";
import i18nextHttpBackend from "i18next-http-backend";
import server from "./server.js";
import { LOCALE_IDS, setDayjsLocale, type Locale } from "@triliumnext/commons";
import { initReactI18next } from "react-i18next";
let locales: Locale[] | null;
/**
* A deferred promise that resolves when translations are initialized.
*/
export let translationsInitializedPromise = $.Deferred();
export const translationsInitializedPromise = $.Deferred();
export async function initLocale() {
const locale = ((options.get("locale") as string) || "en") as LOCALE_IDS;
locales = await server.get<Locale[]>("options/locales");
export async function initLocale(locale: LOCALE_IDS = "en") {
i18next.use(initReactI18next);
await i18next.use(i18nextHttpBackend).init({
@@ -32,11 +25,7 @@ export async function initLocale() {
}
export function getAvailableLocales() {
if (!locales) {
throw new Error("Tried to load list of locales, but localization is not yet initialized.")
}
return locales;
return LOCALES;
}
/**
@@ -47,7 +36,7 @@ export function getAvailableLocales() {
*/
export function getLocaleById(localeId: string | null | undefined) {
if (!localeId) return null;
return locales?.find((l) => l.id === localeId) ?? null;
return LOCALES.find((l) => l.id === localeId) ?? null;
}
export const t = i18next.t;

View File

@@ -1,3 +1,4 @@
import { t } from "./i18n.js";
import utils, { isShare } from "./utils.js";
import ValidationError from "./validation_error.js";
@@ -32,8 +33,7 @@ async function getHeaders(headers?: Headers) {
return {};
}
const appContext = (await import("../components/app_context.js")).default;
const activeNoteContext = appContext.tabManager ? appContext.tabManager.getActiveContext() : null;
const activeNoteContext = glob.appContext?.tabManager ? glob.appContext.tabManager.getActiveContext() : null;
// headers need to be lowercase because node.js automatically converts them to lower case
// also avoiding using underscores instead of dashes since nginx filters them out by default
@@ -344,6 +344,7 @@ async function reportError(method: string, url: string, statusCode: number, resp
} catch (e) {}
}
// Dynamic import to avoid circular dependency (toast → app_context → options → server).
const toastService = (await import("./toast.js")).default;
const messageStr = (typeof message === "string" ? message : JSON.stringify(message)) || "-";
@@ -357,7 +358,6 @@ async function reportError(method: string, url: string, statusCode: number, resp
...response
});
} else {
const { t } = await import("./i18n.js");
if (statusCode === 400 && (url.includes("%23") || url.includes("%2F"))) {
toastService.showPersistent({
id: "trafik-blocked",
@@ -371,8 +371,7 @@ async function reportError(method: string, url: string, statusCode: number, resp
t("server.unknown_http_error_content", { statusCode, method, url, message: messageStr }),
15_000);
}
const { logError } = await import("./ws.js");
logError(`${statusCode} ${method} ${url} - ${message}`);
window.logError(`${statusCode} ${method} ${url} - ${message}`);
}
}

View File

@@ -455,9 +455,7 @@ export function openInAppHelpFromUrl(inAppHelpPage: string) {
export async function openInReusableSplit(targetNoteId: string, targetViewMode: ViewMode, openOpts: {
hoistedNoteId?: string;
} = {}) {
// Dynamic import to avoid import issues in tests.
const appContext = (await import("../components/app_context.js")).default;
const activeContext = appContext.tabManager.getActiveContext();
const activeContext = glob.appContext?.tabManager?.getActiveContext();
if (!activeContext) {
return;
}
@@ -467,7 +465,7 @@ export async function openInReusableSplit(targetNoteId: string, targetViewMode:
if (!existingSubcontext) {
// The target split is not already open, open a new split with it.
const { ntxId } = subContexts[subContexts.length - 1];
appContext.triggerCommand("openNewNoteSplit", {
glob.appContext?.triggerCommand("openNewNoteSplit", {
ntxId,
notePath: targetNoteId,
hoistedNoteId: openOpts.hoistedNoteId,

View File

@@ -1,13 +1,15 @@
import utils from "./utils.js";
import toastService from "./toast.js";
import server from "./server.js";
import options from "./options.js";
import frocaUpdater from "./froca_updater.js";
import appContext from "../components/app_context.js";
import { t } from "./i18n.js";
import type { EntityChange } from "../server_types.js";
import { WebSocketMessage } from "@triliumnext/commons";
import appContext from "../components/app_context.js";
import type { EntityChange } from "../server_types.js";
import bundleService from "./bundle.js";
import froca from "./froca.js";
import frocaUpdater from "./froca_updater.js";
import { t } from "./i18n.js";
import options from "./options.js";
import server from "./server.js";
import toast from "./toast.js";
import utils from "./utils.js";
type MessageHandler = (message: WebSocketMessage) => void;
let messageHandlers: MessageHandler[] = [];
@@ -126,20 +128,14 @@ async function handleMessage(event: MessageEvent<any>) {
} else if (message.type === "frontend-update") {
await executeFrontendUpdate(message.data.entityChanges);
} else if (message.type === "sync-hash-check-failed") {
toastService.showError(t("ws.sync-check-failed"), 60000);
toast.showError(t("ws.sync-check-failed"), 60000);
} else if (message.type === "consistency-checks-failed") {
toastService.showError(t("ws.consistency-checks-failed"), 50 * 60000);
toast.showError(t("ws.consistency-checks-failed"), 50 * 60000);
} else if (message.type === "api-log-messages") {
appContext.triggerEvent("apiLogMessages", { noteId: message.noteId, messages: message.messages });
} else if (message.type === "toast") {
toastService.showMessage(message.message);
toast.showMessage(message.message);
} else if (message.type === "execute-script") {
// TODO: Remove after porting the file
// @ts-ignore
const bundleService = (await import("./bundle.js")).default as any;
// TODO: Remove after porting the file
// @ts-ignore
const froca = (await import("./froca.js")).default as any;
const originEntity = message.originEntityId ? await froca.getNote(message.originEntityId) : null;
bundleService.getAndExecuteBundle(message.currentNoteId, originEntity, message.script, message.params);
@@ -161,7 +157,7 @@ function waitForEntityChangeId(desiredEntityChangeId: number) {
return new Promise<void>((res, rej) => {
entityChangeIdReachedListeners.push({
desiredEntityChangeId: desiredEntityChangeId,
desiredEntityChangeId,
resolvePromise: res,
start: Date.now()
});
@@ -205,7 +201,7 @@ async function consumeFrontendUpdateData() {
} else {
console.log("nonProcessedEntityChanges causing the timeout", nonProcessedEntityChanges);
toastService.showError(t("ws.encountered-error", { message: e.message }));
toast.showError(t("ws.encountered-error", { message: e.message }));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -368,7 +368,7 @@
"calendar_root": "標記應用作為每日筆記的根。只應標記一個筆記。",
"archived": "含有此標籤的筆記預設在搜尋結果中不可見(也適用於跳轉至、新增連結對話方塊等)。",
"exclude_from_export": "筆記(及其子階層)不會包含在任何匯出的筆記中",
"run": "定義腳本應運行的事件。可能的值包括:\n<ul>\n<li>frontendStartup - Trilium前端啟動時或重新整理時但不會在移動端執行。</li>\n<li>mobileStartup - Trilium前端啟動時或重新整理時 在行動端會執行。</li>\n<li>backendStartup - Trilium後端啟動時</li>\n<li>hourly - 每小時運行一次。您可以使用附加標籤<code>runAtHour</code>指定小時。</li>\n<li>daily - 每天運行一次</li>\n</ul>",
"run": "定義腳本應運行的事件。可能的值包括:\n<ul>\n<li>frontendStartup - Trilium前端啟動時或重新整理時但不會在移動端執行。</li>\n<li>mobileStartup - Trilium前端啟動時或重新整理時 在行動端會執行。</li>\n<li>backendStartup - Trilium後端啟動時</li>\n<li>hourly - 每小時運行一次。您可以使用附加標籤<code>runAtHour</code>指定小時。</li>\n<li>daily - 每天運行一次</li>\n</ul>",
"run_on_instance": "定義應在哪個 Trilium 實例上運行。預設為所有實例。",
"run_at_hour": "應在哪個小時運行。應與<code>#run=hourly</code>一起使用。可以多次定義,以便一天內運行多次。",
"disable_inclusion": "含有此標籤的腳本不會包含在父腳本執行中。",
@@ -706,7 +706,8 @@
"export_as_image": "匯出為圖片",
"export_as_image_png": "PNG (點陣)",
"export_as_image_svg": "SVG (向量)",
"note_map": "筆記地圖"
"note_map": "筆記地圖",
"view_ocr_text": "顯示 OCR 文字"
},
"onclick_button": {
"no_click_handler": "按鈕元件'{{componentId}}'沒有定義點擊時的處理方式"
@@ -1196,12 +1197,28 @@
},
"images": {
"images_section_title": "圖片",
"download_images_automatically": "自動下載圖片以供離線使用。",
"download_images_description": "貼上的 HTML 可能包含線上圖片的引用Trilium 會找到這些引用並下載圖片,以便它們可以離線使用。",
"enable_image_compression": "啟用圖片壓縮",
"max_image_dimensions": "圖片的最大寬度 / 高度(超過此限制的圖片將會被縮放)。",
"jpeg_quality_description": "JPEG 質量10 - 最差質量100 最佳質量,建議為 50 - 85",
"max_image_dimensions_unit": "像素"
"download_images_automatically": "自動下載圖片",
"download_images_description": "貼上的 HTML 下載引用的線上圖片以便離線使用。",
"enable_image_compression": "圖片壓縮",
"max_image_dimensions": "最大圖片尺寸",
"jpeg_quality_description": "建議範圍為 5085。較低的數值可縮小檔案大小較高的數值則能保留更多細節。",
"max_image_dimensions_unit": "像素",
"enable_image_compression_description": "在上傳或貼上圖片時壓縮並調整圖片大小。",
"max_image_dimensions_description": "超過此尺寸的圖片將會自動調整大小。",
"jpeg_quality": "JPEG 品質",
"ocr_section_title": "文字擷取OCR",
"ocr_related_content_languages": "內容語言(用於文字擷取)",
"ocr_auto_process": "自動處理新檔案",
"ocr_auto_process_description": "自動從新上傳或貼上的檔案中擷取文字。",
"ocr_min_confidence": "最低信賴度",
"ocr_confidence_description": "僅提取高於此信賴度閾值的文字。較低的閾值雖能包含更多文字,但準確度可能較低。",
"batch_ocr_title": "處理現有檔案",
"batch_ocr_description": "從筆記中的所有現有圖片、PDF 檔案及 Office 文件中擷取文字。根據檔案數量多寡,此過程可能需要一些時間。",
"batch_ocr_start": "開始批次處理",
"batch_ocr_starting": "開始批次處理…",
"batch_ocr_progress": "正在處理 {{processed}} 個檔案,共 {{total}} 個檔案…",
"batch_ocr_completed": "批次處理完成!已處理 {{processed}} 個檔案。",
"batch_ocr_error": "批次處理期間發生錯誤:{{error}}"
},
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "附件清理超時",
@@ -1497,7 +1514,8 @@
"new-feature": "新增",
"collections": "集合",
"ai-chat": "AI 聊天",
"spreadsheet": "試算表"
"spreadsheet": "試算表",
"llm-chat": "AI 對話"
},
"protect_note": {
"toggle-on": "保護筆記",
@@ -1866,7 +1884,7 @@
},
"content_language": {
"title": "內文語言",
"description": "選擇一種或多種語言作為唯讀或可編輯文字筆記的可選基本屬性,這將支援拼寫檢查從右向左之類的功能。"
"description": "選擇一種或多種語言作為唯讀或可編輯文字筆記的可選基本屬性,這將支援拼寫檢查從右向左及文字擷取 (OCR) 等功能。"
},
"switch_layout_button": {
"title_vertical": "將編輯面板移至底部",
@@ -2046,7 +2064,9 @@
"title": "實驗性選項",
"disclaimer": "這些選項屬實驗性質,可能導致系統不穩定。請謹慎使用。",
"new_layout_name": "新版面配置",
"new_layout_description": "體驗全新版面配置,呈現更現代的外觀與更佳的使用體驗。在未來版本將進行大幅調整。"
"new_layout_description": "體驗全新版面配置,呈現更現代的外觀與更佳的使用體驗。在未來版本將進行大幅調整。",
"llm_name": "AI / LLM 對話",
"llm_description": "啟用由大語言模型驅動的 AI 聊天側邊欄及 LLM 聊天筆記。"
},
"server": {
"unknown_http_error_title": "與伺服器通訊錯誤",
@@ -2229,6 +2249,121 @@
"sample_user_journey": "使用者旅程",
"sample_xy": "XY 圖表",
"sample_venn": "韋恩圖",
"sample_ishikawa": "魚骨圖"
"sample_ishikawa": "魚骨圖",
"sample_treeview": "樹狀視圖",
"sample_wardley": "沃德利地圖"
},
"llm_chat": {
"placeholder": "輸入訊息…",
"send": "送出",
"sending": "正在送出…",
"empty_state": "請在下方輸入訊息,開啟對話。",
"searching_web": "正在搜尋網頁…",
"web_search": "網頁搜尋",
"note_tools": "筆記存取",
"sources": "來源",
"sources_summary": "來自 {{sites}} 個網站的 {{count}} 個來源",
"extended_thinking": "延伸思考",
"legacy_models": "傳統模型",
"thinking": "正在思考…",
"thought_process": "思考過程",
"tool_calls": "{{count}} 次工具調用",
"input": "輸入",
"result": "結果",
"error": "錯誤",
"tool_error": "失敗",
"total_tokens": "{{total}} 個詞元",
"tokens_detail": "{{prompt}} 提示詞 + {{completion}} 補全",
"tokens_used": "{{prompt}} 提示詞 + {{completion}} 補全 = {{total}} 個詞元",
"tokens_used_with_cost": "{{prompt}} 提示詞 + {{completion}} 補全 = {{total}} 個詞元(約 ${{cost}}",
"tokens_used_with_model": "{{model}}{{prompt}} 提示詞 + {{completion}} 補全 = {{total}} 個詞元",
"tokens_used_with_model_and_cost": "{{model}}{{prompt}} 提示詞 + {{completion}} 補全 = {{total}} 個詞元(約 ${{cost}}",
"tokens": "詞元",
"context_used": "已使用 {{percentage}}%",
"note_context_enabled": "點擊以禁用筆記上下文:{{title}}",
"note_context_disabled": "點擊將當前筆記納入上下文",
"no_provider_message": "尚未設定任何 AI 服務提供者。請新增一個以開始聊天。",
"add_provider": "新增 AI 提供者"
},
"ocr": {
"processing_complete": "OCR 處理已完成。",
"processing_failed": "無法啟動 OCR 處理",
"text_filtered_low_confidence": "OCR 偵測到的信賴度為 {{confidence}}%,但因您的最低閾值設定為 {{threshold}}%,故該結果已被捨棄。",
"open_media_settings": "開啟設定",
"view_extracted_text": "檢視擷取的文字 (OCR)",
"extracted_text": "已擷取的文字 (OCR)",
"extracted_text_title": "已擷取的文字 (OCR)",
"loading_text": "正在載入 OCR 文字…",
"no_text_available": "無 OCR 文字可用",
"no_text_explanation": "此筆記尚未經過 OCR 文字擷取處理,或未找到任何文字。",
"failed_to_load": "載入 OCR 文字失敗",
"process_now": "處理 OCR",
"processing": "正在處理…",
"processing_started": "OCR 處理已開始。請稍候片刻並重新整理頁面。"
},
"mind-map": {
"addChild": "新增子節點",
"addParent": "新增父節點",
"addSibling": "新增同級節點",
"removeNode": "刪除節點",
"focus": "專注模式",
"cancelFocus": "退出專注模式",
"moveUp": "上移",
"moveDown": "下移",
"link": "連結",
"linkBidirectional": "雙向連結",
"clickTips": "請點擊目標節點",
"summary": "摘要"
},
"llm": {
"settings_title": "AI / LLM",
"settings_description": "設定 AI 及大型語言模型整合。",
"feature_not_enabled": "請前往「設定」→「進階」→「實驗性功能」啟用 LLM 實驗性功能,即可使用 AI 整合。",
"add_provider": "新增提供者",
"add_provider_title": "新增 AI 提供者",
"configured_providers": "已設定的提供者",
"no_providers_configured": "尚未設定任何提供者。",
"provider_name": "名稱",
"provider_type": "提供者",
"actions": "動作",
"delete_provider": "刪除",
"delete_provider_confirmation": "您確定要刪除提供者 \"{{name}}\" 嗎?",
"api_key": "API 金鑰",
"api_key_placeholder": "請輸入您的 API 金鑰",
"cancel": "取消",
"mcp_title": "MCP模型上下文協定",
"mcp_enabled": "MCP 伺服器",
"mcp_enabled_description": "公開一個模型上下文協定 (MCP) 端點,以便人工智慧編程助手(例如 Claude Code、GitHub Copilot能夠讀取並修改您的筆記。此端點僅限從 localhost 存取。",
"mcp_endpoint_title": "端點網址",
"mcp_endpoint_description": "將此網址新增至您的 AI 助理的 MCP 設定中",
"tools": {
"search_notes": "搜尋筆記",
"get_note": "取得筆記",
"get_note_content": "取得筆記內容",
"update_note_content": "更新筆記內容",
"append_to_note": "追加至筆記",
"create_note": "建立筆記",
"get_attributes": "取得屬性",
"get_attribute": "取得屬性",
"set_attribute": "設定屬性",
"delete_attribute": "移除屬性",
"get_child_notes": "取得子筆記",
"get_subtree": "取得子階層",
"load_skill": "載入技能",
"web_search": "網頁搜尋",
"note_in_parent": "<Note/> 於 <Parent/>",
"get_attachment": "取得附件",
"get_attachment_content": "讀取附件內容"
}
},
"sidebar_chat": {
"title": "AI 對話",
"launcher_title": "打開 AI 對話",
"new_chat": "開始新對話",
"save_chat": "將對話保存至筆記",
"empty_state": "開始會話",
"history": "對話歷史",
"recent_chats": "最近的對話",
"no_chats": "無先前的對話記錄"
}
}

View File

@@ -82,6 +82,7 @@ export const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, (() => Promise<{ de
hi: () => import("@fullcalendar/core/locales/hi"),
ga: null,
cn: () => import("@fullcalendar/core/locales/zh-cn"),
cs: () => import("@fullcalendar/core/locales/cs"),
tw: () => import("@fullcalendar/core/locales/zh-tw"),
ro: () => import("@fullcalendar/core/locales/ro"),
ru: () => import("@fullcalendar/core/locales/ru"),

View File

@@ -4,6 +4,7 @@ import type { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
export const LANGUAGE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, Language["code"] | null> = {
ar: "ar-SA",
cn: "zh-CN",
cs: "cs-CZ",
de: "de-DE",
en: "en",
"en-GB": "en",

View File

@@ -39,12 +39,12 @@
"@electron-forge/maker-zip": "7.11.1",
"@electron-forge/plugin-auto-unpack-natives": "7.11.1",
"@electron-forge/plugin-fuses": "7.11.1",
"@electron/fuses": "1.8.0",
"@electron/fuses": "2.1.1",
"@triliumnext/commons": "workspace:*",
"@triliumnext/server": "workspace:*",
"@types/electron-squirrel-startup": "1.0.2",
"copy-webpack-plugin": "13.0.1",
"electron": "40.8.5",
"copy-webpack-plugin": "14.0.0",
"electron": "41.1.1",
"prebuild-install": "7.1.3"
}
}

View File

@@ -86,7 +86,24 @@
"copy-without-formatting": "Kopírovat vybraný text bez formátování",
"force-save-revision": "Vynutit vytvoření / uložení nové revize aktivní poznámky",
"export-as-pdf": "Exportovat současnou poznámku jako PDF",
"toggle-zen-mode": "Zapnout/vypnout režim zen (minimalistické uživatelské rozhraní pro soustředěnější úpravy)"
"toggle-zen-mode": "Zapnout/vypnout režim zen (minimalistické uživatelské rozhraní pro soustředěnější úpravy)",
"toggle-basic-properties": "Přepnout základní vlastnosti",
"toggle-file-properties": "Přepnout vlastnosti souboru",
"toggle-image-properties": "Přepnout vlastnosti obrázku",
"toggle-owned-attributes": "Přepnout vlastní atributy",
"toggle-inherited-attributes": "Přepnout zděděné atributy",
"toggle-promoted-attributes": "Přepnout propagované atributy",
"toggle-link-map": "Přepnout mapu odkazů",
"toggle-note-info": "Přepnout informace o poznámce",
"toggle-note-paths": "Přepnout cesty k poznámce",
"toggle-similar-notes": "Přepnout podobné poznámky",
"toggle-right-pane": "Přepnout zobrazení pravého panelu, který obsahuje obsah a zvýraznění",
"toggle-note-hoisting": "Přepnout zúžení zobrazení aktivní poznámky",
"find-in-text": "Přepnout panel hledání",
"toggle-left-note-tree-panel": "Přepnout levý panel (strom poznámek)",
"toggle-full-screen": "Přepnout režim celého obrazovky",
"toggle-book-properties": "Přepnout vlastnosti kolekce",
"toggle-classic-editor-toolbar": "Přepnout záložku formátování pro editor s fixní páskou"
},
"keyboard_action_names": {
"jump-to-note": "Přejít na...",
@@ -107,6 +124,322 @@
"expand-subtree": "Otevřít podstrom",
"collapse-tree": "Zavřít strom",
"collapse-subtree": "Zavřít podstrom",
"sort-child-notes": "Seřadit dceřiné poznámky"
"sort-child-notes": "Seřadit dceřiné poznámky",
"create-note-after": "Vytvořit poznámku po",
"create-note-into": "Vytvořit poznámku do",
"create-note-into-inbox": "Vytvořit poznámku v doručené poště",
"delete-notes": "Smazat poznámky",
"edit-branch-prefix": "Upravit předponu větve",
"paste-notes-from-clipboard": "Vložit poznámky ze schránky",
"cut-notes-to-clipboard": "Vyříznout poznámky do schránky",
"select-all-notes-in-parent": "Vybrat všechny poznámky v nadřazené položce",
"add-note-above-to-selection": "Přidat poznámku nad výběr",
"add-note-below-to-selection": "Přidat poznámku pod výběr",
"duplicate-subtree": "Duplikovat podstrom",
"open-new-tab": "Otevřít novou záložku",
"close-active-tab": "Zavřít aktivní záložku",
"reopen-last-tab": "Znovu otevřít poslední záložku",
"activate-next-tab": "Aktivovat další záložku",
"activate-previous-tab": "Aktivovat předchozí záložku",
"open-new-window": "Otevřít nové okno",
"toggle-system-tray-icon": "Přepínat ikonu v systémové oblasti",
"toggle-zen-mode": "Přepínat režim Zen",
"switch-to-first-tab": "Přepnout na první záložku",
"switch-to-second-tab": "Přepnout na druhou záložku",
"switch-to-third-tab": "Přepnout na třetí záložku",
"switch-to-fourth-tab": "Přepnout na čtvrtou záložku",
"switch-to-fifth-tab": "Přepnout na pátou záložku",
"switch-to-sixth-tab": "Přepnout na šestou záložku",
"switch-to-seventh-tab": "Přepnout na sedmou záložku",
"switch-to-eighth-tab": "Přepnout na osmou záložku",
"switch-to-ninth-tab": "Přepnout na devátou záložku",
"switch-to-last-tab": "Přepnout na poslední záložku",
"show-note-source": "Zobrazit zdroj poznámky",
"show-options": "Zobrazit nastavení",
"show-revisions": "Zobrazit revize",
"show-recent-changes": "Zobrazit nedávné změny",
"show-sql-console": "Zobrazit SQL konzoli",
"show-backend-log": "Zobrazit log backendu",
"show-help": "Zobrazit nápovědu",
"show-cheatsheet": "Zobrazit kısestupku",
"add-link-to-text": "Přidat odkaz do textu",
"follow-link-under-cursor": "Otevřít odkaz pod kurzorem",
"insert-date-and-time-to-text": "Vložit datum a čas do textu",
"paste-markdown-into-text": "Vložit Markdown do textu",
"cut-into-note": "Vyříznout do poznámky",
"add-include-note-to-text": "Přidat zahrnutí poznámky do textu",
"edit-read-only-note": "Upravit poznámku pouze pro čtení",
"add-new-label": "Přidat nový štítek",
"add-new-relation": "Přidat novou vazbu",
"toggle-ribbon-tab-classic-editor": "Přepínat záložku pásu karet Klasický editor",
"toggle-ribbon-tab-basic-properties": "Přepínat záložku pásu karet Základní vlastnosti",
"toggle-ribbon-tab-book-properties": "Přepínat záložku pásu karet Vlastnosti knihy",
"toggle-ribbon-tab-file-properties": "Přepínat záložku pásu karet Vlastnosti souboru",
"toggle-ribbon-tab-image-properties": "Přepínat záložku pásu karet Vlastnosti obrázku",
"toggle-ribbon-tab-owned-attributes": "Přepínat záložku pásu karet Vlastní atributy",
"toggle-ribbon-tab-inherited-attributes": "Přepínat záložku pásu karrét Zděděné atributy",
"toggle-ribbon-tab-promoted-attributes": "Přepínat záložku pásu karet Propagované atributy",
"toggle-ribbon-tab-note-map": "Přepínat záložku pásu karet Mapa poznámky",
"toggle-ribbon-tab-note-info": "Přepínat záložku pásu karet Informace o poznámce",
"toggle-ribbon-tab-note-paths": "Přepínat záložku pásu karet Cesty k poznámce",
"toggle-ribbon-tab-similar-notes": "Přepínat záložku pásu karet Podobné poznámky",
"toggle-right-pane": "Přepnout pravý panel",
"print-active-note": "Tisknout aktivní poznámku",
"export-active-note-as-pdf": "Exportovat aktivní poznámku jako PDF",
"open-note-externally": "Otevřít poznámku externě",
"render-active-note": "Zobrazit aktivní poznámku",
"run-active-note": "Spustit aktivní poznámku",
"toggle-note-hoisting": "Přepnout zúžení zobrazení poznámky",
"unhoist-note": "Zrušit zúžení zobrazení poznámky",
"reload-frontend-app": "Znovu načíst frontend aplikaci",
"open-developer-tools": "Otevřít vývojářské nástroje",
"find-in-text": "Najít v textu",
"toggle-left-pane": "Přepnout levý panel",
"toggle-full-screen": "Přepnout režim celého obrazovky",
"zoom-out": "Zoom out",
"zoom-in": "Zoom in",
"reset-zoom-level": "Resetovat úroveň zvětšení",
"copy-without-formatting": "Kopírovat bez formátování",
"force-save-revision": "Vynutit uložení revize"
},
"login": {
"title": "Přihlášení",
"heading": "Přihlášení do Trilium",
"incorrect-totp": "TOTP je nesprávné. Zkuste to prosím znovu.",
"incorrect-password": "Heslo je nesprávné. Zkuste to prosím znovu.",
"password": "Heslo",
"remember-me": "Zapamatovat si mě",
"button": "Přihlásit se",
"sign_in_with_sso": "Přihlásit se pomocí {{ ssoIssuerName }}"
},
"set_password": {
"title": "Nastavit heslo",
"heading": "Nastavit heslo",
"description": "Než budete moci začít Trilium používat z webu, musíte nejprve nastavit heslo. Toto heslo pak budete používat k přihlášení.",
"password": "Heslo",
"password-confirmation": "Potvrzení hesla",
"button": "Nastavit heslo"
},
"setup": {
"heading": "Nastavení Trilium Notes",
"new-document": "Jsem nový uživatel a chci vytvořit nový dokument Trilium pro své poznámky",
"sync-from-desktop": "Již mám instanci na počítači a chci s ní nastavit synchronizaci",
"sync-from-server": "Již mám instanci na serveru a chci s ní nastavit synchronizaci",
"next": "Další",
"init-in-progress": "Inicializace dokumentu probíhá",
"redirecting": "Budete brzy přesměrováni do aplikace.",
"title": "Nastavení"
},
"setup_sync-from-desktop": {
"heading": "Synchronizace z počítače",
"description": "Toto nastavení musí být zahájeno z instance na počítači:",
"step1": "Otevřete svou instanci Trilium Notes na počítači.",
"step2": "V menu Trilium klikněte na Nastavení.",
"step3": "Klikněte na kategorii Synchronizace.",
"step4": "Změňte adresu instance serveru na: {{- host}} a klikněte na Uložit.",
"step5": "Klikněte na tlačítko „Testovat synchronizaci“ pro ověření úspěšného připojení.",
"step6": "Jakmile tyto kroky dokončíte, klikněte na {{- link}}.",
"step6-here": "zde"
},
"setup_sync-from-server": {
"heading": "Synchronizace ze serveru",
"instructions": "Níže prosím zadejte adresu serveru Trilium a přihlašovací údaje. To stáhne celý dokument Trilium ze serveru a nastaví jeho synchronizaci. V závislosti na velikosti dokumentu a rychlosti vašeho připojení to může trvat nějakou dobu.",
"server-host": "Adresa serveru Trilium",
"server-host-placeholder": "https://<hostname>:<port>",
"proxy-server": "Proxy server (volitelné)",
"proxy-server-placeholder": "https://<hostname>:<port>",
"note": "Poznámka:",
"proxy-instruction": "Pokud ponecháte nastavení proxy prázdné, bude použita systémová proxy (platí pouze pro počítačovou aplikaci)",
"password": "Heslo",
"password-placeholder": "Heslo",
"back": "Zpět",
"finish-setup": "Dokončit nastavení"
},
"setup_sync-in-progress": {
"heading": "Synchronizace probíhá",
"successful": "Synchronizace byla správně nastavena. Prvotní synchronizace bude trvat nějaký čas. Jakmile bude hotovo, budete přesměrováni na přihlašovací stránku.",
"outstanding-items": "Neodeslané položky synchronizace:",
"outstanding-items-default": "N/A"
},
"share_404": {
"title": "Nenalezeno",
"heading": "Nenalezeno"
},
"share_page": {
"parent": "nadřazená:",
"clipped-from": "Tato poznámka byla původně uložena ze zdroje {{- url}}",
"child-notes": "Dceřiné poznámky:",
"no-content": "Tato poznámka neobsahuje žádný obsah."
},
"weekdays": {
"monday": "Pondělí",
"tuesday": "Úterý",
"wednesday": "Středa",
"thursday": "Čtvrtek",
"friday": "Pátek",
"saturday": "Sobota",
"sunday": "Neděle"
},
"weekdayNumber": "Týden {{weekNumber}}",
"months": {
"january": "Leden",
"february": "Únor",
"march": "Březen",
"april": "Duben",
"may": "Květen",
"june": "Červen",
"july": "Červenec",
"august": "Srpen",
"september": "Září",
"october": "Říjen",
"november": "Listopad",
"december": "Prosinec"
},
"quarterNumber": "Čtvrtletí {quarterNumber}",
"special_notes": {
"search_prefix": "Hledání:",
"llm_chat_prefix": "Chat:"
},
"test_sync": {
"not-configured": "Hostitel synchronizačního serveru není nakonfigurován. Nejprve prosím nakonfigurujte synchronizaci.",
"successful": "Protokol synchronizačního serveru byl úspěšný, synchronizace byla zahájena."
},
"hidden-subtree": {
"root-title": "Skryté poznámky",
"search-history-title": "Historie hledání",
"note-map-title": "Mapa poznámek",
"sql-console-history-title": "Historie SQL konzole",
"llm-chat-history-title": "Historie AI Chat",
"shared-notes-title": "Sdílené poznámky",
"bulk-action-title": "Hromadná akce",
"backend-log-title": "Log Backend",
"user-hidden-title": "Uživatel skryt",
"launch-bar-templates-title": "Šablony panelu spouštěče",
"base-abstract-launcher-title": "Základní abstraktní spouštěč",
"command-launcher-title": "Spouštěč příkazů",
"note-launcher-title": "Spouštěč poznámky",
"script-launcher-title": "Spouštěč skriptu",
"built-in-widget-title": "Vestavěný widget",
"spacer-title": "Mezera",
"custom-widget-title": "Vlastní widget",
"launch-bar-title": "Panel spouštěče",
"available-launchers-title": "Dostupné spouštěče",
"go-to-previous-note-title": "Přejít na předchozí poznámku",
"go-to-next-note-title": "Přejít na další poznámku",
"new-note-title": "Nová poznámka",
"search-notes-title": "Hledat poznámky",
"jump-to-note-title": "Skočit na...",
"calendar-title": "Kalendář",
"recent-changes-title": "Nedávné změny",
"bookmarks-title": "Záložky",
"command-palette": "Otevřít paletu příkazů",
"zen-mode": "Režim Zen",
"open-today-journal-note-title": "Otevřít dnešní deník",
"quick-search-title": "Rychlé hledání",
"protected-session-title": "Chráněná relace",
"sync-status-title": "Stav synchronizace",
"settings-title": "Nastavení",
"options-title": "Možnosti",
"appearance-title": "Vzhled",
"shortcuts-title": "Zkratky",
"text-notes": "Textové poznámky",
"code-notes-title": "Poznámky s kódem",
"images-title": "Média",
"spellcheck-title": "Kontrola pravopisu",
"password-title": "Heslo",
"multi-factor-authentication-title": "MFA",
"etapi-title": "ETAPI",
"backup-title": "Záloha",
"sync-title": "Synchronizace",
"other": "Ostatní",
"advanced-title": "Pokročilé",
"llm-title": "AI / LLM",
"visible-launchers-title": "Viditelné spouštěče",
"user-guide": "Uživatelská příručka",
"localization": "Jazyk a region",
"inbox-title": "Schránka příchozí",
"tab-switcher-title": "Přepínač záložek",
"sidebar-chat-title": "AI Chat"
},
"notes": {
"new-note": "Nová poznámka",
"duplicate-note-suffix": "(dup)",
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
},
"backend_log": {
"log-does-not-exist": "Logovací soubor Backend '{{ fileName }}' neexistuje (zatím).",
"reading-log-failed": "Čtení logovacího souboru Backend '{{ fileName }}' se nepodařilo."
},
"content_renderer": {
"note-cannot-be-displayed": "Tento typ poznámky nelze zobrazit."
},
"pdf": {
"export_filter": "PDF dokument (*.pdf)",
"unable-to-export-message": "Aktuální poznámku nebylo možné exportovat jako PDF.",
"unable-to-export-title": "Nelze exportovat jako PDF",
"unable-to-save-message": "Do vybraného souboru nebylo možné zapisovat. Zkuste to znovu nebo vyberte jiné cílové místo.",
"unable-to-print": "Nelze vytisknout poznámku"
},
"tray": {
"tooltip": "Trilium Notes",
"close": "Ukončit Trilium",
"recents": "Nedávné poznámky",
"bookmarks": "Záložky (oblíbené)",
"today": "Otevřít dnešní deníkovou poznámku",
"new-note": "Nová poznámka",
"show-windows": "Zobrazit okna",
"open_new_window": "Otevřít nové okno"
},
"migration": {
"old_version": "Přímá migrace z vaší aktuální verze není podporována. Nejprve prosím upgradujte na nejnovější v0.60.4 a až poté na tuto verzi.",
"error_message": "Chyba během migrace na verzi {{version}}: {{stack}}",
"wrong_db_version": "Verze databáze ({{version}}) je novější, než jakou aplikace očekává ({{targetVersion}}), což znamená, že byla vytvořena novější a nekompatibilní verzí Trilium. Pro vyřešení tohoto problému upgradujte na nejnovější verzi Trilium."
},
"modals": {
"error_title": "Chyba"
},
"share_theme": {
"site-theme": "Motiv webu",
"search_placeholder": "Hledat...",
"image_alt": "Obrázek článku",
"last-updated": "Poslední aktualizace dne {{ - date}}",
"subpages": "Podstránky:",
"on-this-page": "Na této stránce",
"expand": "Rozbalit"
},
"hidden_subtree_templates": {
"text-snippet": "Textový úryvek",
"description": "Popis",
"list-view": "Seznamový pohled",
"grid-view": "Mřížkový pohled",
"calendar": "Kalendář",
"table": "Tabulka",
"geo-map": "Geomapa",
"start-date": "Počáteční datum",
"end-date": "Koncové datum",
"start-time": "Počáteční čas",
"end-time": "Koncowy čas",
"geolocation": "Geolokalizace",
"built-in-templates": "Vestavěné šablony",
"board": "Kanbanová tabule",
"status": "Stav",
"board_note_first": "První poznámka",
"board_note_second": "Druhá poznámka",
"board_note_third": "Třetí poznámka",
"board_status_todo": "K dokončení",
"board_status_progress": "Probíhá",
"board_status_done": "Hotovo",
"presentation": "Prezentace",
"presentation_slide": "Prezentace snímku",
"presentation_slide_first": "První snímek",
"presentation_slide_second": "Druhý snímek",
"background": "Pozadí"
},
"sql_init": {
"db_not_initialized_desktop": "DB není inicializována, postupujte podle pokynů na obrazovce.",
"db_not_initialized_server": "DB není inicializována, navštivte prosím stránku pro nastavení - http://[your-server-host]:{{port}}, kde najdete pokyny, jak inicializovat Trilium."
},
"desktop": {
"instance_already_running": "Instance již běží, místo vytváření nové se zaměříme na tu stávající."
}
}

View File

@@ -198,7 +198,8 @@
"december": "十二月"
},
"special_notes": {
"search_prefix": "搜尋:"
"search_prefix": "搜尋:",
"llm_chat_prefix": "對話:"
},
"test_sync": {
"not-configured": "尚未設定同步伺服器主機,請先設定同步。",
@@ -340,7 +341,7 @@
"shortcuts-title": "快捷鍵",
"text-notes": "文字筆記",
"code-notes-title": "程式碼筆記",
"images-title": "圖片",
"images-title": "媒體",
"spellcheck-title": "拼寫檢查",
"password-title": "密碼",
"multi-factor-authentication-title": "多重身份驗證",
@@ -355,7 +356,10 @@
"inbox-title": "收件匣",
"command-palette": "打開命令面板",
"zen-mode": "禪模式",
"tab-switcher-title": "切換分頁"
"tab-switcher-title": "切換分頁",
"llm-chat-history-title": "AI 對話歷史",
"llm-title": "AI / LLM",
"sidebar-chat-title": "AI 對話"
},
"notes": {
"new-note": "新增筆記",

View File

@@ -5,7 +5,7 @@ import type { Request } from "express";
import ValidationError from "../../errors/validation_error.js";
import config from "../../services/config.js";
import { changeLanguage, getLocales } from "../../services/i18n.js";
import { changeLanguage } from "../../services/i18n.js";
import log from "../../services/log.js";
import optionService from "../../services/options.js";
import searchService from "../../services/search/services/search.js";
@@ -192,10 +192,6 @@ function getUserThemes() {
return ret;
}
function getSupportedLocales() {
return getLocales();
}
function isAllowed(name: string) {
return (ALLOWED_OPTIONS as Set<string>).has(name)
|| name.startsWith("keyboardShortcuts")
@@ -207,6 +203,5 @@ export default {
getOptions,
updateOption,
updateOptions,
getUserThemes,
getSupportedLocales
getUserThemes
};

View File

@@ -215,7 +215,6 @@ function register(app: express.Application) {
apiRoute(PUT, "/api/options/:name/:value", optionsApiRoute.updateOption);
apiRoute(PUT, "/api/options", optionsApiRoute.updateOptions);
apiRoute(GET, "/api/options/user-themes", optionsApiRoute.getUserThemes);
apiRoute(GET, "/api/options/locales", optionsApiRoute.getSupportedLocales);
apiRoute(PST, "/api/password/change", passwordApiRoute.changePassword);
apiRoute(PST, "/api/password/reset", passwordApiRoute.resetPassword);

View File

@@ -4,7 +4,7 @@ import sql_init from "./sql_init.js";
import { join } from "path";
import { getResourceDir } from "./utils.js";
import hidden_subtree from "./hidden_subtree.js";
import { dayjs, LOCALES, setDayjsLocale, type Dayjs, type Locale, type LOCALE_IDS } from "@triliumnext/commons";
import { dayjs, LOCALES, setDayjsLocale, type Dayjs, type LOCALE_IDS } from "@triliumnext/commons";
export async function initializeTranslations() {
const resourceDir = getResourceDir();
@@ -30,10 +30,6 @@ export function ordinal(date: Dayjs) {
.format("Do");
}
export function getLocales(): Locale[] {
return LOCALES;
}
function getCurrentLanguage(): LOCALE_IDS {
let language: string | null = null;
if (sql_init.isDbInitialized()) {

View File

@@ -112,7 +112,8 @@
"header": {
"get-started": "Začít",
"documentation": "Dokumentace",
"support-us": "Podpořte nás"
"support-us": "Podpořte nás",
"resources": "Zdroje"
},
"footer": {
"copyright_and_the": " a ",
@@ -134,6 +135,74 @@
"buy_me_a_coffee": "Buy Me A Coffee"
},
"contribute": {
"title": "Další způsoby, jak přispět"
"title": "Další způsoby, jak přispět",
"way_translate": "Přeložte aplikaci do svého rodného jazyka prostřednictvím <Link>Weblate</Link>.",
"way_community": "Kontaktujte komunitu na <Discussions>GitHub Discussions</Discussions> nebo na <Matrix>Matrix</Matrix>.",
"way_reports": "Nahlaste chyby prostřednictvím <Link>GitHub issues</Link>.",
"way_document": "Vylepšujte dokumentaci tím, že nás upozorníte na její nedostatky, nebo přispějte do příruček, FAQ či návodů.",
"way_market": "Šířte dobrou věst: Sdílejte Trilium Notes s přáteli, na blogy nebo na sociální sítě."
},
"404": {
"title": "404: Nenalezeno",
"description": "Stránka, kterou jste hleděli, nebyla nalezena. Možná byla smazána nebo je URL adresa nesprávná."
},
"download_helper_desktop_windows": {
"title_x64": "Windows 64-bit",
"title_arm64": "Windows na ARM",
"description_x64": "Kompatibilní s zařízeními Intel nebo AMD s operačním systémem Windows 10 a 11.",
"description_arm64": "Kompatibilní s ARM zařízeními (např. s Qualcomm Snapdragon).",
"quick_start": "Pro instalaci pomocí Winget:",
"download_exe": "Stáhnout instalátor (.exe)",
"download_zip": "Přenosná verze (.zip)",
"download_scoop": "Scoop"
},
"download_helper_desktop_linux": {
"title_x64": "Linux 64-bit",
"title_arm64": "Linux na ARM",
"description_x64": "Pro většinu distribucí Linuxu, kompatibilní s architekturou x86_64.",
"description_arm64": "Pro distribuce Linuxu založené na ARM, kompatibilní s architekturou aarch64.",
"quick_start": "Vyberte vhodný formát balíčku podle vaší distribuce:",
"download_deb": ".deb",
"download_rpm": ".rpm",
"download_flatpak": ".flatpak",
"download_zip": "Přenosné (.zip)",
"download_nixpkgs": "nixpkgs",
"download_aur": "AUR"
},
"download_helper_desktop_macos": {
"title_x64": "macOS pro Intel",
"title_arm64": "macOS pro Apple Silicon",
"description_x64": "Pro Mac s procesorem Intel běžící pod macOS Monterey nebo novějším.",
"description_arm64": "Pro Mac s Apple Silicon, jako jsou modely s čipy M1 a M2.",
"quick_start": "Pro instalaci pomocí Homebrew:",
"download_dmg": "Stáhnout instalátor (.dmg)",
"download_homebrew_cask": "Homebrew Cask",
"download_zip": "Přenosná verze (.zip)"
},
"download_helper_server_docker": {
"title": "Vlastní hosting pomocí Docker",
"description": "Snadné nasazení na Windows, Linux nebo macOS pomocí Docker kontejneru.",
"download_dockerhub": "Docker Hub",
"download_ghcr": "ghcr.io"
},
"download_helper_server_linux": {
"title": "Vlastní hosting na Linuxu",
"description": "Nasazujte Trilium Notes na vlastním serveru nebo VPS, kompatibilní s většinou distribucí.",
"download_tar_x64": "x64 (.tar.xz)",
"download_tar_arm64": "ARM (.tar.xz)",
"download_nixos": "Modul pro NixOS"
},
"download_helper_server_hosted": {
"title": "Placený hosting",
"description": "Trilium Notes hostováno na PikaPods, placené službě pro snadný přístup a správu. Není přímo spojen s týmem Trilium.",
"download_pikapod": "Nastaveno na PikaPods",
"download_triliumcc": "Případně se podívejte na trilium.cc"
},
"resources": {
"title": "Zdroje",
"icon_packs": "Balíčky ikon",
"icon_packs_intro": "Rozšiřte výběr dostupných ikon pro své poznámky použitím balíčku ikon. Více informací o balíčcích ikon naleznete v <DocumentationLink>oficiální dokumentaci</DocumentationLink>.",
"download": "Stáhnout",
"website": "Webová stránka"
}
}

View File

@@ -7,6 +7,5 @@ To do so:
2. In `packages/commons` look for `dayjs.ts` and add a mapping for the new language in `DAYJS_LOADER`. Sort the entire list.
3. In `apps/client`, look for `collections/calendar/index.tsx` and modify `LOCALE_MAPPINGS` to add support to the new language.
4. In `apps/client`, look for `widgets/type_widgets/canvas/i18n.ts` and modify `LANGUAGE_MAPPINGS`. A unit test ensures that the language is actually loadable.
5. In `apps/client`, look for `widgets/type_widgets/MindMap.tsx` and modify `LOCALE_MAPPINGS`. The type definitions should already validate if the new value is supported by Mind Elixir.
6. In `packages/ckeditor5`, look for `i18n.ts` and modify `LOCALE_MAPPINGS`. The import validation should already check if the new value is supported by CKEditor, and there's also a test to ensure it.
7. Locale mappings for PDF.js might need adjustment. To do so, in `packages/pdfjs-viewer/scripts/build.ts` there is `LOCALE_MAPPINGS`.
5. In `packages/ckeditor5`, look for `i18n.ts` and modify `LOCALE_MAPPINGS`. The import validation should already check if the new value is supported by CKEditor, and there's also a test to ensure it.
6. Locale mappings for PDF.js might need adjustment. To do so, in `packages/pdfjs-viewer/scripts/build.ts` there is `LOCALE_MAPPINGS`.

272
docs/README-cs.md vendored
View File

@@ -124,123 +124,127 @@ Naše dokumenatce je dostupná ve vícero formátech:
motiv](https://docs.triliumnotes.org/user-guide/concepts/themes), podpora
uživatelských motivů
* [Evernote](https://docs.triliumnotes.org/user-guide/concepts/import-export/evernote)
and [Markdown import &
export](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown)
* [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) for
easy saving of web content
* Customizable UI (sidebar buttons, user-defined widgets, ...)
* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics),
along with a Grafana Dashboard.
a [import & export
Markdown](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown)
* [Webový výstřižek](https://docs.triliumnotes.org/user-guide/setup/web-clipper)
pro snadné ukládání webového obsahu
* Přizpůsobitelné UI (tlačítka bočního panelu, uživatelsky definované widgety,
...)
* [Metriky](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics)
spolu s Grafana Dashboard.
Check out the following third-party resources/communities for more TriliumNext
related goodies:
Podívejte se na následující externí zdroje/komunity pro další vychytávky
související s TriliumNext:
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party
themes, scripts, plugins and more.
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) pro externí
motivy, skripty, pluginy a další.
- [TriliumRocks!](https://trilium.rocks/) pro návody, průvodce a mnohem více.
## ❓Why TriliumNext?
## ❓Proč TriliumNext?
The original Trilium developer ([Zadam](https://github.com/zadam)) has
graciously given the Trilium repository to the community project which resides
at https://github.com/TriliumNext
Původní vývojář Trilium ([Zadam](https://github.com/zadam)) štědře předal
repozitář Trilium komunitnímu projektu, který sídlí na
https://github.com/TriliumNext
### ⬆Migrating from Zadam/Trilium?
### ⬆Migrujete ze Zadam/Trilium?
There are no special migration steps to migrate from a zadam/Trilium instance to
a TriliumNext/Trilium instance. Simply [install
TriliumNext/Trilium](#-installation) as usual and it will use your existing
database.
Neexistují žádné speciální kroky pro migraci z instance zadam/Trilium na
instanci TriliumNext/Trilium. Jednoduše si [ nainstalujte
TriliumNext/Trilium](#-installation) jako obvykle a použije vaši stávající
databázi.
Versions up to and including
[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are
compatible with the latest zadam/trilium version of
[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later
versions of TriliumNext/Trilium have their sync versions incremented which
prevents direct migration.
Verze až do
[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) včetně
jsou kompatibilní s nejnovější verzí zadam/trilium
[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Jakékoli
pozdější verze TriliumNext/Trilium mají zvýšené verze synchronizace, což brání
přímé migraci.
## 💬 Discuss with us
## 💬 Diskutujte s námi
Feel free to join our official conversations. We would love to hear what
features, suggestions, or issues you may have!
Nebojte se připojit k našim oficiálním konverzationím. Rádi uslyšíme vaše nápady
na funkce, návrhy nebo problémy!
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous
discussions.)
- The `General` Matrix room is also bridged to
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (Pro synchron
diskuse.)
- Pokoj Matrix `General` je také propojen s
[XMPP](xmpp:discuss@trilium.thisgreat.party?join)
- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (For
asynchronous discussions.)
- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (For bug
reports and feature requests.)
- [Github Discussions](https://github.com/TriliumNext/Trilium/discussions) (Pro
asynchron diskuse.)
- [Github Issues](https://github.com/TriliumNext/Trilium/issues) (Pro hlášení
chyb a požadavky na funkce.)
## 🏗 Installation
## 🏗 Instalace
### Windows / MacOS
Download the binary release for your platform from the [latest release
page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package
and run the `trilium` executable.
Stáhněte si binární verzi pro svou platformu z [stránky s nejnovější
verzí](https://github.com/TriliumNext/Trilium/releases/latest), rozbalte balíček
a spusťte spustitelný soubor `trilium`.
### Linux
If your distribution is listed in the table below, use your distribution's
package.
Pokud je vaše distribuce uvedena v níže uvedené tabulce, použijte balíček pro
vaši distribuci.
[![Packaging
status](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions)
[![Stav
balení](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions)
You may also download the binary release for your platform from the [latest
release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the
package and run the `trilium` executable.
Můžete si také stáhnout binární verzi pro svou platformu ze [stránky s
nejnovější verzí](https://github.com/TriliumNext/Trilium/releases/latest),
rozbalit balíček a spustit spustitelný soubor `trilium`.
TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.
TriliumNext je k dispozici také jako Flatpak, ale ještě není zveřejněn na
FlatHub.
### Browser (any OS)
### Prohlížeč (jakýkoli OS)
If you use a server installation (see below), you can directly access the web
interface (which is almost identical to the desktop app).
Pokud používáte serverovou instalaci (viz níže), můžete přistupovat přímo k
webovému rozhraní (které je téměř identické s desktopovou aplikací).
Currently only the latest versions of Chrome & Firefox are supported (and
tested).
Momentálně jsou podporovány (a testovány) pouze nejnovější verze Chrome &
Firefox.
### Mobile
### Mobilní zařízení
To use TriliumNext on a mobile device, you can use a mobile web browser to
access the mobile interface of a server installation (see below).
Chcete-li používat TriliumNext na mobilním zařízení, můžete použít mobilní
webový prohlížeč k přístupu k mobilnímu rozhraní instalace serveru (viz níže).
See issue https://github.com/TriliumNext/Trilium/issues/4962 for more
information on mobile app support.
Více informací o podpoře mobilní aplikace naleznete v issue
https://github.com/TriliumNext/Trilium/issues/4962.
If you prefer a native Android app, you can use
Pokud preferujete nativní aplikaci pro Android, můžete použít
[TriliumDroid](https://apt.izzysoft.de/fdroid/index/apk/eu.fliegendewurst.triliumdroid).
Report bugs and missing features at [their
repository](https://github.com/FliegendeWurst/TriliumDroid). Note: It is best to
disable automatic updates on your server installation (see below) when using
TriliumDroid since the sync version must match between Trilium and TriliumDroid.
Chyby a chybějící funkce hlaste v [jejím
repozitáři](https://github.com/FliegendeWurst/TriliumDroid). Poznámka: Při
používání TriliumDroid je nejlepší zakázat automatické aktualizace na vaší
instalaci serveru (viz níže), protože verze pro synchronizaci musí mezi Trilium
a TriliumDroid souhlasit.
### Server
To install TriliumNext on your own server (including via Docker from
[Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server
installation docs](https://docs.triliumnotes.org/user-guide/setup/server).
Chcete-li nainstalovat TriliumNext na svůj vlastní server (včetně pomocí Docker
z [Dockerhub](https://hub.docker.com/r/triliumnext/trilium)), postupujte podle
[dokumentace k instalaci
serveru](https://docs.triliumnotes.org/user-guide/setup/server).
## 💻 Contribute
## 💻 Přispějte
### Translations
### Překlady
If you are a native speaker, help us translate Trilium by heading over to our
[Weblate page](https://hosted.weblate.org/engage/trilium/).
Pokud jste rodilý mluvčí, pomozte nám s překladem Trilium tím, že navštívíte
naši [stránku Weblate](https://hosted.weblate.org/engage/trilium/).
Here's the language coverage we have so far:
Zde je aktuální pokrytí jazyky:
[![Translation
status](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/)
[![Stav
překladu](https://hosted.weblate.org/widget/trilium/multi-auto.svg)](https://hosted.weblate.org/engage/trilium/)
### Code
### Kód
Download the repository, install dependencies using `pnpm` and then run the
server (available at http://localhost:8080):
Stáhněte si repozitář, nainstalujte závislosti pomocí `pnpm` a poté spusťte
server (dostupný na http://localhost:8080):
```shell
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
@@ -248,10 +252,10 @@ pnpm install
pnpm run server:start
```
### Documentation
### Dokumentace
Download the repository, install dependencies using `pnpm` and then run the
environment required to edit the documentation:
Stáhněte si repozitář, nainstalujte závislosti pomocí `pnpm` a poté spusťte
prostředí vyžadované pro úpravu dokumentace:
```shell
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
@@ -259,9 +263,9 @@ pnpm install
pnpm edit-docs:edit-docs
```
### Building the Executable
Download the repository, install dependencies using `pnpm` and then build the
desktop app for Windows:
### Kompilace spustitelného souboru
Stáhněte si repozitář, nainstalujte závislosti pomocí `pnpm` a poté sestavte
desktopovou aplikaci pro Windows:
```shell
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
@@ -269,71 +273,69 @@ pnpm install
pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
```
For more details, see the [development
docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
Pro více podrobností navštivte [vývojovou
dokumentaci](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/Developer%20Guide).
### Developer Documentation
### Vývojářská dokumentace
Please view the [documentation
guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
for details. If you have more questions, feel free to reach out via the links
described in the "Discuss with us" section above.
Podrobnosti naleznete v [průvodci
dokumentací](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md).
Pokud máte další dotazy, neváhejte nás kontaktovat prostřednictím odkazů
uvedených v sekci „Diskuse s námi“ výše.
## 👏 Shoutouts
## 👏 Poděkování
* [zadam](https://github.com/zadam) for the original concept and implementation
of the application.
* [Sarah Hussein](https://github.com/Sarah-Hussein) for designing the
application icon.
* [nriver](https://github.com/nriver) for his work on internationalization.
* [Thomas Frei](https://github.com/thfrei) for his original work on the Canvas.
* [antoniotejada](https://github.com/nriver) for the original syntax highlight
widget.
* [Dosu](https://dosu.dev/) for providing us with the automated responses to
GitHub issues and discussions.
* [Tabler Icons](https://tabler.io/icons) for the system tray icons.
* [zadam](https://github.com/zadam) za původní koncept a implementaci aplikace.
* [Sarah Hussein](https://github.com/Sarah-Hussein) za návrh ikony aplikace.
* [nriver](https://github.com/nriver) za jeho práci na internacionalizaci.
* [Thomas Frei](https://github.com/thfrei) za jeho původní práci na Plátně.
* [antoniotejada](https://github.com/nriver) za původní widget pro zvýrazňování
syntaxe.
* [Dosu](https://dosu.dev/) za poskytnutí automatických odpovědí na GitHub
issues a diskuse.
* [Tabler Icons](https://tabler.io/icons) za ikony v systémové oblasti.
Trilium would not be possible without the technologies behind it:
Trilium by nebyl možný bez technologií, které za ním stojí:
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - the visual editor behind
text notes. We are grateful for being offered a set of the premium features.
* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with
support for huge amount of languages.
* [Excalidraw](https://github.com/excalidraw/excalidraw) - the infinite
whiteboard used in Canvas notes.
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - providing the
mind map functionality.
* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical
maps.
* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive
table used in collections.
* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library
without real competition.
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library.
Used in [relation
maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and
[link
maps](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map)
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - vizuální editor pro
textové poznámky. Jsme vděční za nabídku sady prémiových funkcí.
* [CodeMirror](https://github.com/codemirror/CodeMirror) - editor kódu s
podporou obrovského množství jazyků.
* [Excalidraw](https://github.com/excalidraw/excalidraw) - nekonečná bílá tabule
používaná v poznámkách typu Canvas.
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - poskytuje
funkcionalitu myšlenkových map.
* [Leaflet](https://github.com/Leaflet/Leaflet) - pro vykreslování geografických
map.
* [Tabulator](https://github.com/olifolkerd/tabulator) - pro interaktiv
tabulku používanou v kolekcích.
* [FancyTree](https://github.com/mar10/fancytree) - bohatá knihovna pro stromové
struktury bez skutečné konkurence.
* [jsPlumb](https://github.com/jsplumb/jsplumb) - knihovna pro vizuální
propojení. Používá se v [mapách
vazeb](https://docs.triliumnotes.org/user-guide/note-types/relation-map) a
[mapách
odkazů](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map)
## 🤝 Support
## 🤝 Podpora
Trilium is built and maintained with [hundreds of hours of
work](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Your
support keeps it open-source, improves features, and covers costs such as
hosting.
Trilium je vyvíjen a udržován s [úsilím stovek hodin
práce](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Vaše
podpora pomáhá udržovat projekt jako open-source, vylepšuje funkce a pokrývá
náklady, jako je hosting.
Consider supporting the main developer
([eliandoran](https://github.com/eliandoran)) of the application via:
Zvažte podporu hlavního vývojáře ([eliandoran](https://github.com/eliandoran))
aplikace prostřednictvím:
- [GitHub Sponsors](https://github.com/sponsors/eliandoran)
- [PayPal](https://paypal.me/eliandoran)
- [Buy Me a Coffee](https://buymeacoffee.com/eliandoran)
## 🔑 License
## 🔑 Licence
Copyright 2017-2025 zadam, Elian Doran, and other contributors
Copyright 2017-2025 zadam, Elian Doran a ostatní přispěvatelé
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
Tento program je volný software: můžete jej redistribuovat a/nebo upravovat za
podmínek GNU Affero General Public License, jak jej vydala Free Software
Foundation, buď ve verzi 3 této licence, nebo (volitelně) jakoukoli pozdější
verzi.

View File

@@ -59,7 +59,7 @@
"chalk": "5.6.2",
"cross-env": "10.1.0",
"dpdm": "4.0.1",
"esbuild": "0.27.5",
"esbuild": "0.28.0",
"eslint": "10.1.0",
"eslint-config-preact": "2.0.0",
"eslint-config-prettier": "10.1.8",

View File

@@ -16,12 +16,7 @@
"node": ">=18.0.0",
"npm": ">=5.7.1"
},
"files": [
"dist",
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "55.3.1",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@typescript-eslint/eslint-plugin": "8.58.0",
"@typescript-eslint/parser": "8.58.0",
@@ -43,9 +38,6 @@
"author": "Elian Doran <contact@eliandoran.me>",
"license": "GPL-2.0-or-later",
"scripts": {
"build": "node ./scripts/build-dist.mjs",
"ts:build": "tsc -p ./tsconfig.release.json",
"ts:clear": "npx rimraf --glob \"src/**/*.@(js|d.ts)\"",
"lint": "eslint \"**/*.{js,ts}\" --quiet",
"stylelint": "stylelint --quiet --allow-empty-input 'theme/**/*.css'",
"test": "vitest",

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env node
/**
* @license Copyright (c) 2020-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
/* eslint-env node */
import { createRequire } from 'module';
import upath from 'upath';
import chalk from 'chalk';
import { build } from '@ckeditor/ckeditor5-dev-build-tools';
function dist( path ) {
return upath.join( 'dist', path );
}
( async () => {
const tsconfig = 'tsconfig.dist.ckeditor5.json';
/**
* Step 1
*/
console.log( chalk.cyan( '1/2: Generating NPM build...' ) );
const require = createRequire( import.meta.url );
const pkg = require( upath.resolve( process.cwd(), './package.json' ) );
await build( {
input: 'src/index.ts',
output: dist( './index.js' ),
tsconfig: 'tsconfig.dist.json',
external: [
'ckeditor5',
'ckeditor5-premium-features',
...Object.keys( {
...pkg.dependencies,
...pkg.peerDependencies
} )
],
clean: true,
sourceMap: true,
declarations: true,
translations: '**/*.po'
} );
} )();

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"rootDir": "./src",
"composite": false,
"types": [
"../typings/types.d.ts"
]
}
}

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"declaration": true
},
"exclude": [
"./tests/",
"./src",
"./sample/"
]
}

View File

@@ -17,12 +17,7 @@
"node": ">=18.0.0",
"npm": ">=5.7.1"
},
"files": [
"dist",
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "55.3.1",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@typescript-eslint/eslint-plugin": "8.58.0",
"@typescript-eslint/parser": "8.58.0",
@@ -42,9 +37,6 @@
"ckeditor5": "48.0.0"
},
"scripts": {
"build": "node ./scripts/build-dist.mjs",
"ts:build": "tsc -p ./tsconfig.release.json",
"ts:clear": "npx rimraf --glob \"src/**/*.@(js|d.ts)\"",
"lint": "eslint \"**/*.{js,ts}\" --quiet",
"stylelint": "stylelint --quiet --allow-empty-input 'theme/**/*.css'",
"test": "vitest",

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env node
/**
* @license Copyright (c) 2020-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
/* eslint-env node */
import { createRequire } from 'module';
import upath from 'upath';
import chalk from 'chalk';
import { build } from '@ckeditor/ckeditor5-dev-build-tools';
function dist( path ) {
return upath.join( 'dist', path );
}
( async () => {
const tsconfig = 'tsconfig.dist.ckeditor5.json';
/**
* Step 1
*/
console.log( chalk.cyan( '1/2: Generating NPM build...' ) );
const require = createRequire( import.meta.url );
const pkg = require( upath.resolve( process.cwd(), './package.json' ) );
await build( {
input: 'src/index.ts',
output: dist( './index.js' ),
tsconfig: 'tsconfig.dist.json',
external: [
'ckeditor5',
'ckeditor5-premium-features',
...Object.keys( {
...pkg.dependencies,
...pkg.peerDependencies
} )
],
clean: true,
sourceMap: true,
declarations: true,
translations: '**/*.po'
} );
} )();

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"rootDir": "./src",
"composite": false,
"types": [
"../typings/types.d.ts"
]
}
}

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"declaration": true
},
"exclude": [
"./tests/",
"./src",
"./sample/"
]
}

View File

@@ -19,12 +19,7 @@
"node": ">=18.0.0",
"npm": ">=5.7.1"
},
"files": [
"dist",
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "55.3.1",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@typescript-eslint/eslint-plugin": "8.58.0",
"@typescript-eslint/parser": "8.58.0",
@@ -44,9 +39,6 @@
"ckeditor5": "48.0.0"
},
"scripts": {
"build": "node ./scripts/build-dist.mjs",
"ts:build": "tsc -p ./tsconfig.release.json",
"ts:clear": "npx rimraf --glob \"src/**/*.@(js|d.ts)\"",
"lint": "eslint \"**/*.{js,ts}\" --quiet",
"stylelint": "stylelint --quiet --allow-empty-input 'theme/**/*.css'",
"test": "vitest",

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env node
/**
* @license Copyright (c) 2020-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
/* eslint-env node */
import { createRequire } from 'module';
import upath from 'upath';
import chalk from 'chalk';
import { build } from '@ckeditor/ckeditor5-dev-build-tools';
function dist( path ) {
return upath.join( 'dist', path );
}
( async () => {
const tsconfig = 'tsconfig.dist.ckeditor5.json';
/**
* Step 1
*/
console.log( chalk.cyan( '1/2: Generating NPM build...' ) );
const require = createRequire( import.meta.url );
const pkg = require( upath.resolve( process.cwd(), './package.json' ) );
await build( {
input: 'src/index.ts',
output: dist( './index.js' ),
tsconfig: 'tsconfig.dist.json',
external: [
'ckeditor5',
'ckeditor5-premium-features',
...Object.keys( {
...pkg.dependencies,
...pkg.peerDependencies
} )
],
clean: true,
sourceMap: true,
declarations: true,
translations: '**/*.po'
} );
} )();

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"rootDir": "./src",
"composite": false,
"types": [
"../typings/types.d.ts"
]
}
}

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"declaration": true
},
"exclude": [
"./tests/",
"./src",
"./sample/"
]
}

View File

@@ -19,12 +19,7 @@
"node": ">=18.0.0",
"npm": ">=5.7.1"
},
"files": [
"dist",
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "55.3.1",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@typescript-eslint/eslint-plugin": "8.58.0",
"@typescript-eslint/parser": "8.58.0",
@@ -44,9 +39,6 @@
"ckeditor5": "48.0.0"
},
"scripts": {
"build": "node ./scripts/build-dist.mjs",
"ts:build": "tsc -p ./tsconfig.release.json",
"ts:clear": "npx rimraf --glob \"src/**/*.@(js|d.ts)\"",
"lint": "eslint \"**/*.{js,ts}\" --quiet",
"stylelint": "stylelint --quiet --allow-empty-input 'theme/**/*.css'",
"test": "vitest",

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env node
/**
* @license Copyright (c) 2020-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
/* eslint-env node */
import { createRequire } from 'module';
import upath from 'upath';
import chalk from 'chalk';
import { build } from '@ckeditor/ckeditor5-dev-build-tools';
function dist( path ) {
return upath.join( 'dist', path );
}
( async () => {
const tsconfig = 'tsconfig.dist.ckeditor5.json';
/**
* Step 1
*/
console.log( chalk.cyan( '1/2: Generating NPM build...' ) );
const require = createRequire( import.meta.url );
const pkg = require( upath.resolve( process.cwd(), './package.json' ) );
await build( {
input: 'src/index.ts',
output: dist( './index.js' ),
tsconfig: 'tsconfig.dist.json',
external: [
'ckeditor5',
'ckeditor5-premium-features',
...Object.keys( {
...pkg.dependencies,
...pkg.peerDependencies
} )
],
clean: true,
sourceMap: true,
declarations: true,
translations: '**/*.po'
} );
} )();

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"rootDir": "./src",
"composite": false,
"types": [
"../typings/types.d.ts"
]
}
}

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"declaration": true
},
"exclude": [
"./tests/",
"./src",
"./sample/"
]
}

View File

@@ -8,3 +8,6 @@ sample/ckeditor.dist.js
# Ignore compiled TypeScript files.
src/**/*.js
src/**/*.d.ts
.vitest-attachments
tests/__screenshots__

View File

@@ -19,12 +19,7 @@
"node": ">=18.0.0",
"npm": ">=5.7.1"
},
"files": [
"dist",
"ckeditor5-metadata.json"
],
"devDependencies": {
"@ckeditor/ckeditor5-dev-build-tools": "55.3.1",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@typescript-eslint/eslint-plugin": "8.58.0",
"@typescript-eslint/parser": "8.58.0",
@@ -44,16 +39,9 @@
"ckeditor5": "48.0.0"
},
"scripts": {
"build": "node ./scripts/build-dist.mjs",
"ts:build": "tsc -p ./tsconfig.release.json",
"ts:clear": "npx rimraf --glob \"src/**/*.@(js|d.ts)\"",
"lint": "eslint \"**/*.{js,ts}\" --quiet",
"stylelint": "stylelint --quiet --allow-empty-input 'theme/**/*.css'",
"test": "vitest",
"test:debug": "vitest --inspect-brk --no-file-parallelism --browser.headless=false"
},
"dependencies": {
"@types/lodash-es": "4.17.12",
"lodash-es": "4.18.1"
}
}

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env node
/**
* @license Copyright (c) 2020-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md.
*/
/* eslint-env node */
import { createRequire } from 'module';
import upath from 'upath';
import chalk from 'chalk';
import { build } from '@ckeditor/ckeditor5-dev-build-tools';
function dist( path ) {
return upath.join( 'dist', path );
}
( async () => {
const tsconfig = 'tsconfig.dist.ckeditor5.json';
/**
* Step 1
*/
console.log( chalk.cyan( '1/2: Generating NPM build...' ) );
const require = createRequire( import.meta.url );
const pkg = require( upath.resolve( process.cwd(), './package.json' ) );
await build( {
input: 'src/index.ts',
output: dist( './index.js' ),
tsconfig: 'tsconfig.dist.json',
external: [
'ckeditor5',
'ckeditor5-premium-features',
...Object.keys( {
...pkg.dependencies,
...pkg.peerDependencies
} )
],
clean: true,
sourceMap: true,
declarations: true,
translations: '**/*.po'
} );
} )();

View File

@@ -5,7 +5,8 @@ import MermaidUI from './mermaidui.js';
declare global {
interface MermaidInstance {
init(config: MermaidConfig, element: HTMLElement): void;
initialize(config: MermaidConfig): void;
render(id: string, source: string): Promise<{ svg: string }>;
}
interface MermaidConfig {

View File

@@ -2,13 +2,13 @@
* @module mermaid/mermaidediting
*/
import { debounce } from 'lodash-es';
import MermaidPreviewCommand from './commands/mermaidPreviewCommand.js';
import MermaidSourceViewCommand from './commands/mermaidSourceViewCommand.js';
import MermaidSplitViewCommand from './commands/mermaidSplitViewCommand.js';
import InsertMermaidCommand from './commands/insertMermaidCommand.js';
import { DowncastAttributeEvent, DowncastConversionApi, EditorConfig, ModelElement, EventInfo, ModelItem, ModelNode, Plugin, toWidget, UpcastConversionApi, UpcastConversionData, ViewElement, ViewText, ViewUIElement } from 'ckeditor5';
import { DowncastAttributeEvent, DowncastConversionApi, EditorConfig, ModelElement, EventInfo, ModelItem, ModelNode, Plugin, toWidget, uid, UpcastConversionApi, UpcastConversionData, ViewElement, ViewText, ViewUIElement } from 'ckeditor5';
import { debounce } from './utils.js';
// Time in milliseconds.
const DEBOUNCE_TIME = 300;
@@ -20,7 +20,8 @@ type DowncastConversionData = DowncastAttributeEvent["args"][0];
export default class MermaidEditing extends Plugin {
private _config!: EditorConfig["mermaid"];
private mermaid?: MermaidInstance;
private _mermaidPromise?: Promise<MermaidInstance>;
private _renderGeneration = 0;
/**
* @inheritDoc
@@ -179,16 +180,10 @@ export default class MermaidEditing extends Plugin {
}
function createMermaidPreview(this: ViewUIElement, domDocument: Document ) {
// Taking the text from the wrapper container element for now
const mermaidSource = data.item.getAttribute( 'source' ) as string;
const domElement = this.toDomElement( domDocument );
domElement.textContent = mermaidSource;
window.setTimeout( () => {
// @todo: by the looks of it the domElement needs to be hooked to tree in order to allow for rendering.
that._renderMermaid( domElement );
}, 100 );
that._renderMermaid( domElement, mermaidSource );
return domElement;
}
@@ -219,10 +214,7 @@ export default class MermaidEditing extends Plugin {
const domPreviewWrapper = domConverter.viewToDom(child);
if ( domPreviewWrapper ) {
domPreviewWrapper.textContent = newSource;
domPreviewWrapper.removeAttribute( 'data-processed' );
this._renderMermaid( domPreviewWrapper );
this._renderMermaid( domPreviewWrapper, newSource );
}
}
}
@@ -263,14 +255,36 @@ export default class MermaidEditing extends Plugin {
}
/**
* Renders Mermaid in a given `domElement`. Expect this domElement to have mermaid
* source set as text content.
* Renders Mermaid (a parsed `source`) in a given `domElement`.
*/
async _renderMermaid( domElement: HTMLElement ) {
if (!window.mermaid && typeof this._config?.lazyLoad === "function") {
this.mermaid = await this._config.lazyLoad();
async _renderMermaid( domElement: HTMLElement, source: string ) {
if ( !this._mermaidPromise && typeof this._config?.lazyLoad === 'function' ) {
this._mermaidPromise = Promise.resolve( this._config.lazyLoad() ).then( instance => {
instance.initialize( this._config?.config ?? {} );
return instance;
} );
}
this.mermaid?.init( this._config?.config ?? {}, domElement );
const mermaid = await this._mermaidPromise;
if ( !mermaid ) {
return;
}
const generation = ++this._renderGeneration;
const id = `ck-mermaid-${ uid() }`;
try {
const { svg } = await mermaid.render( id, source );
if ( generation === this._renderGeneration ) {
domElement.innerHTML = svg;
}
} catch ( err: any ) {
if ( generation === this._renderGeneration ) {
domElement.innerText = err.message;
}
document.getElementById( id )?.remove();
}
}
}

View File

@@ -5,6 +5,16 @@
import { Editor } from "ckeditor5";
export function debounce<T extends (...args: any[]) => void>( fn: T, waitMs: number ): T {
let timeout: ReturnType<typeof setTimeout> | null = null;
return function( this: unknown, ...args: Parameters<T> ) {
if ( timeout ) {
clearTimeout( timeout );
}
timeout = setTimeout( () => fn.apply( this, args ), waitMs );
} as T;
}
/**
* Helper function for setting the `isOn` state of buttons.
*

View File

@@ -1,8 +1,6 @@
import { ClassicEditor, Essentials, Paragraph, Heading, CodeBlockEditing, _setModelData as setModelData, _getModelData as getModelData, _getViewData as getViewData } from 'ckeditor5';
import { ClassicEditor, Essentials, Paragraph, Heading, CodeBlockEditing, ViewElement, _setModelData as setModelData, _getModelData as getModelData, _getViewData as getViewData } from 'ckeditor5';
import MermaidEditing from '../src/mermaidediting.js';
import { afterEach, beforeEach, describe, it } from 'vitest';
import { expect } from 'vitest';
import { vi } from 'vitest';
import { afterEach, beforeEach, describe, it, expect, vi, type MockInstance } from 'vitest';
/* global document */
@@ -12,7 +10,7 @@ describe( 'MermaidEditing', () => {
} );
describe( 'conversion', () => {
let domElement, editor, model;
let domElement: HTMLDivElement, editor: ClassicEditor, model: ClassicEditor['model'];
beforeEach( async () => {
domElement = document.createElement( 'div' );
@@ -154,7 +152,7 @@ describe( 'MermaidEditing', () => {
} );
describe( 'textarea value', () => {
let domTextarea = null;
let domTextarea: HTMLTextAreaElement;
beforeEach( () => {
// Using editor.setData() instead of setModelData helper because of #11365.
@@ -164,8 +162,8 @@ describe( 'MermaidEditing', () => {
'</pre>'
);
const textareaView = editor.editing.view.document.getRoot().getChild( 0 ).getChild( 1 );
domTextarea = editor.editing.view.domConverter.viewToDom( textareaView );
const textareaView = editor.editing.view.document.getRoot()!.getChild( 0 )! as ViewElement;
domTextarea = editor.editing.view.domConverter.viewToDom( textareaView.getChild( 1 )! ) as HTMLTextAreaElement;
} );
it( 'is properly set during the initial conversion', () => {
@@ -175,7 +173,7 @@ describe( 'MermaidEditing', () => {
it( 'is properly updated after model\'s attribute change', () => {
const { model } = editor;
const mermaidModel = model.document.getRoot().getChild( 0 );
const mermaidModel = model.document.getRoot()!.getChild( 0 )!;
model.change( writer => {
writer.setAttribute( 'source', 'abc', mermaidModel );
@@ -187,7 +185,7 @@ describe( 'MermaidEditing', () => {
it( 'doesn\'t loop if model attribute changes to the same value', () => {
const { model } = editor;
const mermaidModel = model.document.getRoot().getChild( 0 );
const mermaidModel = model.document.getRoot()!.getChild( 0 )!;
model.change( writer => {
writer.setAttribute( 'source', 'flowchart TB\nA --> B\nB --> C', mermaidModel );
@@ -198,9 +196,12 @@ describe( 'MermaidEditing', () => {
} );
describe( 'preview div', () => {
let domPreviewContainer, renderMermaidStub;
let domPreviewContainer: HTMLElement;
let renderMermaidStub: MockInstance;
beforeEach( () => {
renderMermaidStub = vi.spyOn( editor.plugins.get( 'MermaidEditing' ) as unknown as MermaidEditing, '_renderMermaid' );
// Using editor.setData() instead of setModelData helper because of #11365.
editor.setData(
'<pre spellcheck="false">' +
@@ -208,42 +209,31 @@ describe( 'MermaidEditing', () => {
'</pre>'
);
const previewContainerView = editor.editing.view.document.getRoot().getChild( 0 ).getChild( 2 );
domPreviewContainer = editor.editing.view.domConverter.viewToDom( previewContainerView );
renderMermaidStub = vi.spyOn( editor.plugins.get( 'MermaidEditing' ), '_renderMermaid' );
const wrapperView = editor.editing.view.document.getRoot()!.getChild( 0 )! as ViewElement;
const previewContainerView = wrapperView.getChild( 2 )!;
domPreviewContainer = editor.editing.view.domConverter.viewToDom( previewContainerView ) as HTMLElement;
} );
afterEach( () => {
vi.clearAllMocks();
} );
it( 'has proper inner text set during the initial conversion', () => {
expect( domPreviewContainer.textContent ).to.equal( 'flowchart TB\nA --> B\nB --> C' );
it( 'calls render with source during the initial conversion', () => {
expect( renderMermaidStub ).toBeCalledWith( domPreviewContainer, 'flowchart TB\nA --> B\nB --> C' );
} );
it( 'has proper inner text set after a model\'s attribute change', () => {
it( 'calls render with updated source after a model\'s attribute change', () => {
const { model } = editor;
const mermaidModel = model.document.getRoot().getChild( 0 );
renderMermaidStub.mockClear();
const mermaidModel = model.document.getRoot()!.getChild( 0 )!;
model.change( writer => {
writer.setAttribute( 'source', 'abc', mermaidModel );
} );
expect( domPreviewContainer.textContent ).to.equal( 'abc' );
} );
it( 'calls mermaid render function after a model\'s attribute change', () => {
const { model } = editor;
const mermaidModel = model.document.getRoot().getChild( 0 );
model.change( writer => {
writer.setAttribute( 'source', 'abc', mermaidModel );
} );
expect(renderMermaidStub).toBeCalledWith(domPreviewContainer);
expect( renderMermaidStub ).toBeCalledWith( domPreviewContainer, 'abc' );
} );
} );
} );
@@ -251,7 +241,7 @@ describe( 'MermaidEditing', () => {
it( 'adds a editing pipeline converter that has a precedence over code block', () => {
setModelData( editor.model, '<mermaid source="foo"></mermaid>' );
const firstViewChild = editor.editing.view.document.getRoot().getChild( 0 );
const firstViewChild = editor.editing.view.document.getRoot()!.getChild( 0 ) as ViewElement;
expect( firstViewChild.name ).to.equal( 'div' );
expect( firstViewChild.hasClass( 'ck-mermaid__wrapper' ), 'has ck-mermaid__wrapper class' ).to.be.true;
@@ -260,7 +250,7 @@ describe( 'MermaidEditing', () => {
it( 'does not convert code blocks other than mermaid language', () => {
setModelData( editor.model, '<codeBlock language="javascript">foo</codeBlock>' );
const firstViewChild = editor.editing.view.document.getRoot().getChild( 0 );
const firstViewChild = editor.editing.view.document.getRoot()!.getChild( 0 ) as ViewElement;
expect( firstViewChild.name ).not.to.equal( 'div' );
expect( firstViewChild.hasClass( 'ck-mermaid__wrapper' ), 'has ck-mermaid__wrapper class' ).to.be.false;
@@ -269,7 +259,8 @@ describe( 'MermaidEditing', () => {
it( 'adds a preview element', () => {
setModelData( editor.model, '<mermaid source="foo"></mermaid>' );
const widgetChildren = [ ...editor.editing.view.document.getRoot().getChild( 0 ).getChildren() ];
const widget = editor.editing.view.document.getRoot()!.getChild( 0 ) as ViewElement;
const widgetChildren = [ ...widget.getChildren() ] as ViewElement[];
const previewView = widgetChildren.filter( item => item.name === 'div' && item.hasClass( 'ck-mermaid__preview' ) );
expect( previewView.length ).to.equal( 1 );
@@ -278,7 +269,8 @@ describe( 'MermaidEditing', () => {
it( 'adds an editing element', () => {
setModelData( editor.model, '<mermaid source="foo"></mermaid>' );
const widgetChildren = [ ...editor.editing.view.document.getRoot().getChild( 0 ).getChildren() ];
const widget = editor.editing.view.document.getRoot()!.getChild( 0 ) as ViewElement;
const widgetChildren = [ ...widget.getChildren() ] as ViewElement[];
const previewView = widgetChildren.filter(
item => item.name === 'textarea' && item.hasClass( 'ck-mermaid__editing-view' )
);

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"rootDir": "./src",
"composite": false,
"types": [
"../typings/types.d.ts"
]
}
}

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"declaration": true
},
"exclude": [
"./tests/",
"./src",
"./sample/"
]
}

View File

@@ -25,6 +25,11 @@ const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, LocaleMapping | null> = {
coreTranslation: () => import("ckeditor5/translations/zh-cn.js"),
premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/zh-cn.js"),
},
cs: {
languageCode: "cs",
coreTranslation: () => import("ckeditor5/translations/cs.js"),
premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/cs.js"),
},
de: {
languageCode: "de",
coreTranslation: () => import("ckeditor5/translations/de.js"),

View File

@@ -52,7 +52,7 @@
"codemirror-lang-elixir": "4.0.1",
"codemirror-lang-hcl": "0.1.0",
"codemirror-lang-mermaid": "0.5.0",
"@eslint/js": "9.39.4",
"@eslint/js": "10.0.1",
"eslint-linter-browserify": "10.1.0",
"globals": "17.4.0"
}

View File

@@ -34,6 +34,7 @@ dayjs.extend(utc);
export const DAYJS_LOADER: Record<LOCALE_IDS, () => Promise<typeof import("dayjs/locale/en.js")>> = {
"ar": () => import("dayjs/locale/ar.js"),
"cn": () => import("dayjs/locale/zh-cn.js"),
"cs": () => import("dayjs/locale/cs.js"),
"de": () => import("dayjs/locale/de.js"),
"en": () => import("dayjs/locale/en.js"),
"en-GB": () => import("dayjs/locale/en-gb.js"),

View File

@@ -10,12 +10,13 @@ export interface Locale {
/** The value to pass to `--lang` for the Electron instance in order to set it as a locale. Not setting it will hide it from the list of supported locales. */
electronLocale?: "en" | "de" | "es" | "fr" | "zh_CN" | "zh_TW" | "ro" | "af" | "am" | "ar" | "bg" | "bn" | "ca" | "cs" | "da" | "el" | "en_GB" | "es_419" | "et" | "fa" | "fi" | "fil" | "gu" | "he" | "hi" | "hr" | "hu" | "id" | "it" | "ja" | "kn" | "ko" | "lt" | "lv" | "ml" | "mr" | "ms" | "nb" | "nl" | "pl" | "pt_BR" | "pt_PT" | "ru" | "sk" | "sl" | "sr" | "sv" | "sw" | "ta" | "te" | "th" | "tr" | "uk" | "ur" | "vi";
/** The Tesseract OCR language code for this locale (e.g. "eng", "fra", "deu"). See https://tesseract-ocr.github.io/tessdoc/Data-Files-in-different-versions.html */
tesseractCode?: "eng" | "deu" | "spa" | "fra" | "gle" | "ita" | "hin" | "jpn" | "por" | "pol" | "ron" | "rus" | "chi_sim" | "chi_tra" | "ukr" | "ara" | "heb" | "kur" | "fas" | "kor";
tesseractCode?: "eng" | "deu" | "spa" | "fra" | "gle" | "ita" | "hin" | "jpn" | "por" | "pol" | "ron" | "rus" | "chi_sim" | "chi_tra" | "ukr" | "ara" | "heb" | "kur" | "fas" | "kor" | "ces";
}
// When adding a new locale, prefer the version with hyphen instead of underscore.
const UNSORTED_LOCALES = [
{ id: "cn", name: "简体中文", electronLocale: "zh_CN", tesseractCode: "chi_sim" },
{ id: "cs", name: "Čeština", electronLocale: "cs", tesseractCode: "ces" },
{ id: "de", name: "Deutsch", electronLocale: "de", tesseractCode: "deu" },
{ id: "en", name: "English (United States)", electronLocale: "en", tesseractCode: "eng" },
{ id: "en-GB", name: "English (United Kingdom)", electronLocale: "en_GB", tesseractCode: "eng" },

View File

@@ -24,7 +24,7 @@
],
"license": "Apache-2.0",
"dependencies": {
"fuse.js": "7.1.0",
"fuse.js": "7.2.0",
"katex": "0.16.44",
"mermaid": "11.14.0"
},
@@ -34,7 +34,7 @@
"@typescript-eslint/eslint-plugin": "8.58.0",
"@typescript-eslint/parser": "8.58.0",
"dotenv": "17.4.0",
"esbuild": "0.27.5",
"esbuild": "0.28.0",
"eslint": "10.1.0",
"highlight.js": "11.11.1",
"typescript": "6.0.2"

1413
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -50,6 +50,13 @@
{
"matchPackageNames": "@univerjs/**",
"groupName": "univer monorepo"
},
{
"matchPackageNames": [
"ai",
"@ai-sdk/**"
],
"groupName": "ai sdk"
}
]
}