Compare commits

...

44 Commits

Author SHA1 Message Date
Meinzzzz
e124a3ef28
Merge f8d84814e079bdca892c1c2cac75c53baa96f20b into 2fb78275f7a7211a2614b5e0902c57ed8294ab13 2025-11-27 21:52:22 +02:00
Elian Doran
2fb78275f7
Translations update from Hosted Weblate (#7871)
Some checks failed
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Build Docker image (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
/ Check Docker build (Dockerfile) (push) Waiting to run
/ Check Docker build (Dockerfile.alpine) (push) Waiting to run
/ Build Docker images (Dockerfile, ubuntu-24.04-arm, linux/arm64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.alpine, ubuntu-latest, linux/amd64) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v7) (push) Blocked by required conditions
/ Build Docker images (Dockerfile.legacy, ubuntu-24.04-arm, linux/arm/v8) (push) Blocked by required conditions
/ Merge manifest lists (push) Blocked by required conditions
playwright / E2E tests on linux-arm64 (push) Waiting to run
playwright / E2E tests on linux-x64 (push) Waiting to run
Deploy website / Build & deploy website (push) Has been cancelled
2025-11-27 21:52:13 +02:00
Elian Doran
98f421c697
Apply suggestions from code review
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-11-27 21:49:17 +02:00
Francis C.
a5572b7d45
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1635 of 1635 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-11-27 20:42:07 +01:00
Manfred Manni
fdecbaaa6a
Translated using Weblate (German)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/de/
2025-11-27 20:42:06 +01:00
green
c6afd7fa24
Translated using Weblate (Japanese)
Currently translated at 100.0% (1635 of 1635 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-11-27 20:42:06 +01:00
Tomas Adamek
5cad522a60
Translated using Weblate (Czech)
Currently translated at 5.3% (88 of 1635 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/cs/
2025-11-27 20:42:05 +01:00
Tomas Adamek
82f64677cb
Translated using Weblate (Czech)
Currently translated at 27.2% (106 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/cs/
2025-11-27 20:42:04 +01:00
Manfred Manni
3ee086a063
Translated using Weblate (German)
Currently translated at 99.7% (1631 of 1635 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2025-11-27 20:42:03 +01:00
Francis C.
13da444a69
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1635 of 1635 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-11-27 20:42:02 +01:00
Tomas Adamek
b51ceaaadc
Translated using Weblate (Czech)
Currently translated at 65.1% (99 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/cs/
2025-11-27 20:42:01 +01:00
Elian Doran
2024c72209
i18n(client): two missing translations for dialogs 2025-11-27 21:41:24 +02:00
Elian Doran
b5959c55e1
chore(client): remove redundant log 2025-11-27 21:12:47 +02:00
Elian Doran
16f0ac97f4
fix(ckeditor): revert workaround which breaks shift-selection 2025-11-27 20:39:59 +02:00
Elian Doran
073c02ee0c
fix(ckeditor): Cmd+Up/Down not working properly 2025-11-27 20:00:15 +02:00
Elian Doran
786f0db4bb
fix(ckeditor): move block interfering with normal shortcut (closes #6964) 2025-11-27 19:37:21 +02:00
Elian Doran
6958e4b74f
feat(desktop): collapse spacing in full screen 2025-11-27 18:24:00 +02:00
meinzzzz
f8d84814e0 Fix differential d problems 2025-11-26 23:02:34 +01:00
meinzzzz
c46cf41842 Small improvements 2025-11-26 22:48:57 +01:00
meinzzzz
64ab1c4116 Imrovement for Latex 2025-11-26 22:29:29 +01:00
meinzzzz
a6de1041c7 Fix bug in math rendering where old content was not cleared 2025-11-26 21:59:33 +01:00
meinzzzz
c8d34e65ea Improve max window size 2025-11-26 21:49:09 +01:00
meinzzzz
51db729546 Improve and simplify Mathfield integration 2025-11-25 23:27:06 +01:00
meinzzzz
d2052ad236 Disable mathlive sound effects 2025-11-24 21:51:59 +01:00
meinzzzz
9c4301467f Remove unused icons from ckeditor5-math package 2025-11-24 19:46:04 +01:00
meinzzzz
e7355dc0e4 remove gitignore unneccesary changes 2025-11-24 18:43:52 +01:00
meinzzzz
4110fec94f Removed unnecessary declare keyboard 2025-11-24 18:28:59 +01:00
meinzzzz
d5e601eae9 Simpliyfied resize logic for math input form and improved css 2025-11-24 17:56:18 +01:00
meinzzzz
4f044c4a57 Use icons form CKEditor5 icons, instead of testing icons. 2025-11-23 22:43:07 +01:00
meinzzzz
5821c350e1 Fixing class property initialization order 2025-11-23 17:58:51 +01:00
meinzzzz
edba8188fe Fix dark selection colors in MathLive math-field 2025-11-23 13:44:28 +01:00
meinzzzz
1471a72633 refactor: avoid recursive updates in mathLiveInput by normalizing value before updateing 2025-11-23 13:34:22 +01:00
meinzzzz
56834cb88a Improve MathLive and Raw LaTeX input views to propagate mousedown events 2025-11-23 13:29:26 +01:00
meinzzzz
a0f16f9184 Fix typos in mathform.css 2025-11-23 13:09:56 +01:00
meinzzzz
de80eb4806 Improve mathform.css styling for better visual integration 2025-11-22 22:42:34 +01:00
meinzzzz
48a4b81fbe remove automated screenshot files 2025-11-22 21:40:55 +01:00
meinzzzz
e225794f72 Better window focus handling in MathFormView 2025-11-22 21:35:37 +01:00
meinzzzz
4eef30f8b5 Fix names 2025-11-22 00:20:20 +01:00
meinzzzz
569b09609d Remove mathlive dependency and chunking 2025-11-22 00:01:14 +01:00
meinzzzz
39838c25c2 Fixed chaching problems 2025-11-21 23:50:49 +01:00
meinzzzz
49e90c08a9 Better Names for Math UI Components 2025-11-20 22:45:21 +01:00
meinzzzz
e777b06fb8 Math 2025-11-20 18:53:39 +01:00
meinzzzz
497ec2ac74 Merge branch 'main' of https://github.com/Meinzzzz/Trilium-Mathlive 2025-11-20 18:00:18 +01:00
meinzzzz
c5d282d203 Mathlive 2025-11-20 00:09:10 +01:00
26 changed files with 848 additions and 346 deletions

2
.gitignore vendored
View File

@ -48,4 +48,4 @@ upload
.svelte-kit .svelte-kit
# docs # docs
site/ site/

View File

@ -58,6 +58,7 @@ function initOnElectron() {
initDarkOrLightMode(style); initDarkOrLightMode(style);
initTransparencyEffects(style, currentWindow); initTransparencyEffects(style, currentWindow);
initFullScreenDetection(currentWindow);
if (options.get("nativeTitleBarVisible") !== "true") { if (options.get("nativeTitleBarVisible") !== "true") {
initTitleBarButtons(style, currentWindow); initTitleBarButtons(style, currentWindow);
@ -87,6 +88,11 @@ function initTitleBarButtons(style: CSSStyleDeclaration, currentWindow: Electron
} }
} }
function initFullScreenDetection(currentWindow: Electron.BrowserWindow) {
currentWindow.on("enter-full-screen", () => document.body.classList.add("full-screen"));
currentWindow.on("leave-full-screen", () => document.body.classList.remove("full-screen"));
}
function initTransparencyEffects(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) { function initTransparencyEffects(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) {
if (window.glob.platform === "win32") { if (window.glob.platform === "win32") {
const material = style.getPropertyValue("--background-material"); const material = style.getPropertyValue("--background-material");

View File

@ -75,10 +75,7 @@ class ContextMenu {
if (this.isMobile) { if (this.isMobile) {
this.$cover.on("click", () => this.hide()); this.$cover.on("click", () => this.hide());
} else { } else {
$(document).on("click", (e) => { $(document).on("click", (e) => this.hide());
console.log("Hide due to clickus")
this.hide()
});
} }
} }

View File

@ -2009,7 +2009,7 @@ body.electron.platform-darwin:not(.native-titlebar) .tab-row-container {
-webkit-app-region: drag; -webkit-app-region: drag;
} }
body.electron.platform-darwin:not(.native-titlebar) #tab-row-left-spacer { body.electron.platform-darwin:not(.native-titlebar):not(.full-screen) #tab-row-left-spacer {
width: 80px; width: 80px;
} }

View File

@ -1702,7 +1702,7 @@
"paste": "粘贴", "paste": "粘贴",
"paste-as-plain-text": "以纯文本粘贴", "paste-as-plain-text": "以纯文本粘贴",
"search_online": "用 {{searchEngine}} 搜索 \"{{term}}\"", "search_online": "用 {{searchEngine}} 搜索 \"{{term}}\"",
"search_in_trilium": "在 Trilium 中查找搜索 \"{{term}}\"" "search_in_trilium": "在 Trilium 中搜索「{{term}}」"
}, },
"image_context_menu": { "image_context_menu": {
"copy_reference_to_clipboard": "复制引用到剪贴板", "copy_reference_to_clipboard": "复制引用到剪贴板",

View File

@ -43,7 +43,7 @@
"link_title_arbitrary": "titulek odkazu může být změněn libovolně" "link_title_arbitrary": "titulek odkazu může být změněn libovolně"
}, },
"branch_prefix": { "branch_prefix": {
"prefix": "Prefix: ", "prefix": "Předpona: ",
"save": "Uložit", "save": "Uložit",
"edit_branch_prefix": "Upravit prefix větve", "edit_branch_prefix": "Upravit prefix větve",
"edit_branch_prefix_multiple": "Upravit prefix větve pro {{count}} větví", "edit_branch_prefix_multiple": "Upravit prefix větve pro {{count}} větví",
@ -68,20 +68,54 @@
}, },
"confirm": { "confirm": {
"cancel": "Zrušit", "cancel": "Zrušit",
"ok": "OK" "ok": "OK",
"confirmation": "Potvrzení",
"are_you_sure_remove_note": "Opravdu chcete odstranit poznámku „{{title}}“ z mapy vztahů?",
"if_you_dont_check": "Pokud tuto možnost nezaškrtnete, poznámka bude odstraněna pouze z mapy vztahů.",
"also_delete_note": "Odstraňte také poznámku"
}, },
"delete_notes": { "delete_notes": {
"cancel": "Zrušit", "cancel": "Zrušit",
"ok": "OK", "ok": "OK",
"close": "Zavřít" "close": "Zavřít",
"delete_notes_preview": "Odstranit náhled poznámek",
"delete_all_clones_description": "Odstraňte také všechny klony (lze vrátit zpět v nedávných změnách)",
"erase_notes_description": "Normální (měkké) smazání pouze označí poznámky jako smazané a lze je během určité doby obnovit (v dialogovém okně posledních změn). Zaškrtnutím této možnosti se poznámky okamžitě vymažou a nebude možné je obnovit.",
"erase_notes_warning": "Trvale smažte poznámky (nelze vrátit zpět), včetně všech klonů. Tím se vynutí opětovné načtení aplikace.",
"notes_to_be_deleted": "Následující poznámky budou smazány ({{notesCount}})",
"no_note_to_delete": "Žádná poznámka nebude smazána (pouze klony).",
"broken_relations_to_be_deleted": "Následující vazby budou přerušeny a smazány ({{relationCount}})",
"deleted_relation_text": "Poznámka {{- note}} (bude smazána) je odkazována vazbou {{- relation}} pocházející z {{- source}}."
}, },
"export": { "export": {
"close": "Zavřít" "close": "Zavřít",
"export_note_title": "Exportovat poznámku",
"export_type_subtree": "Tato poznámka a všechny její odvozené poznámky",
"format_html": "HTML doporučeno, protože zachovává veškeré formátování",
"format_html_zip": "HTML v archivu ZIP toto se doporučuje, protože se tak zachová veškeré formátování.",
"format_markdown": "Markdown zachovává většinu formátování.",
"format_opml": "OPML formát pro výměnu osnov pouze pro text. Formátování, obrázky a soubory nejsou zahrnuty.",
"opml_version_1": "OPML v1.0 pouze prostý text",
"opml_version_2": "OPML v2.0 umožňuje také HTML",
"export_type_single": "Pouze tato poznámka bez jejích potomků",
"export": "Exportovat",
"choose_export_type": "Nejprve vyberte typ exportu",
"export_status": "Stav exportu",
"export_in_progress": "Export probíhá: {{progressCount}}",
"export_finished_successfully": "Export byl úspěšně dokončen.",
"format_pdf": "PDF pro tisk nebo sdílení.",
"share-format": "HTML pro publikování na webu používá stejný motiv jako sdílené poznámky, ale lze jej publikovat jako statický web."
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Klonovat poznámky do...", "clone_notes_to": "Klonovat poznámky do...",
"help_on_links": "Nápověda k odkazům", "help_on_links": "Nápověda k odkazům",
"notes_to_clone": "Poznámky na klonování", "notes_to_clone": "Poznámky na klonování",
"search_for_note_by_its_name": "hledat poznámku dle jejího názvu" "search_for_note_by_its_name": "hledat poznámku dle jejího názvu",
"prefix_optional": "Předpona (volitelná)",
"target_parent_note": "Zaměřit rodičovskou poznámku",
"cloned_note_prefix_title": "Klonovaná poznámka se zobrazí ve stromu poznámek s danou předponou",
"clone_to_selected_note": "Klonovat vybranou poznámku",
"no_path_to_clone_to": "Žádná cest pro klonování.",
"note_cloned": "Poznámka: „{{clonedTitle}}“ bylo naklonováno do „{{targetTitle}}“"
} }
} }

View File

@ -769,7 +769,8 @@
"geo-map": "Weltkarte", "geo-map": "Weltkarte",
"board": "Tafel", "board": "Tafel",
"include_archived_notes": "Zeige archivierte Notizen", "include_archived_notes": "Zeige archivierte Notizen",
"presentation": "Präsentation" "presentation": "Präsentation",
"expand_all_levels": "Alle Ebenen erweitern"
}, },
"edited_notes": { "edited_notes": {
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...", "no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",

View File

@ -1727,7 +1727,8 @@
"refresh-saved-search-results": "Refresh saved search results", "refresh-saved-search-results": "Refresh saved search results",
"create-child-note": "Create child note", "create-child-note": "Create child note",
"unhoist": "Unhoist", "unhoist": "Unhoist",
"toggle-sidebar": "Toggle sidebar" "toggle-sidebar": "Toggle sidebar",
"dropping-not-allowed": "Dropping notes into this location is not allowed."
}, },
"title_bar_buttons": { "title_bar_buttons": {
"window-on-top": "Keep Window on Top" "window-on-top": "Keep Window on Top"
@ -1830,7 +1831,8 @@
"duplicate-launcher": "Duplicate launcher <kbd data-command=\"duplicateSubtree\">" "duplicate-launcher": "Duplicate launcher <kbd data-command=\"duplicateSubtree\">"
}, },
"editable-text": { "editable-text": {
"auto-detect-language": "Auto-detected" "auto-detect-language": "Auto-detected",
"keeps-crashing": "Editing component keeps crashing. Please try restarting Trilium. If problem persists, consider creating a bug report."
}, },
"highlighting": { "highlighting": {
"title": "Code Blocks", "title": "Code Blocks",

View File

@ -541,7 +541,11 @@
"geo-map": "ジオマップ", "geo-map": "ジオマップ",
"board": "ボード", "board": "ボード",
"include_archived_notes": "アーカイブされたノートを表示", "include_archived_notes": "アーカイブされたノートを表示",
"presentation": "プレゼンテーション" "presentation": "プレゼンテーション",
"expand_tooltip": "このコレクションの直下の子1階層下を展開します。その他のオプションについては、右側の矢印を押してください。",
"expand_first_level": "直下の子を展開",
"expand_nth_level": "{{depth}} 階層下まで展開",
"expand_all_levels": "すべての階層を展開"
}, },
"note_types": { "note_types": {
"geo-map": "ジオマップ", "geo-map": "ジオマップ",

View File

@ -1661,7 +1661,7 @@
"paste": "貼上", "paste": "貼上",
"paste-as-plain-text": "以純文字貼上", "paste-as-plain-text": "以純文字貼上",
"search_online": "用 {{searchEngine}} 搜尋 \"{{term}}\"", "search_online": "用 {{searchEngine}} 搜尋 \"{{term}}\"",
"search_in_trilium": "在 Trilium 中搜尋 \"{{term}}\"" "search_in_trilium": "在 Trilium 中搜尋「{{term}}」"
}, },
"image_context_menu": { "image_context_menu": {
"copy_reference_to_clipboard": "複製引用到剪貼簿", "copy_reference_to_clipboard": "複製引用到剪貼簿",

View File

@ -508,7 +508,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
(data.hitMode === "over" && node.data.noteType === "search") || (data.hitMode === "over" && node.data.noteType === "search") ||
(["after", "before"].includes(data.hitMode) && (node.data.noteId === hoistedNoteService.getHoistedNoteId() || node.getParent().data.noteType === "search")) (["after", "before"].includes(data.hitMode) && (node.data.noteId === hoistedNoteService.getHoistedNoteId() || node.getParent().data.noteType === "search"))
) { ) {
await dialogService.info("Dropping notes into this location is not allowed."); await dialogService.info(t("note_tree.dropping-not-allowed"));
return; return;
} }

View File

@ -17,6 +17,7 @@ import TouchBar, { TouchBarButton, TouchBarGroup, TouchBarSegmentedControl } fro
import { RefObject } from "preact"; import { RefObject } from "preact";
import { buildSelectedBackgroundColor } from "../../../components/touch_bar"; import { buildSelectedBackgroundColor } from "../../../components/touch_bar";
import { deferred } from "@triliumnext/commons"; import { deferred } from "@triliumnext/commons";
import { t } from "../../../services/i18n";
/** /**
* The editor can operate into two distinct modes: * The editor can operate into two distinct modes:
@ -279,7 +280,7 @@ function onWatchdogStateChange(watchdog: EditorWatchdog) {
logError(`CKEditor crash logs: ${JSON.stringify(watchdog.crashes, null, 4)}`); logError(`CKEditor crash logs: ${JSON.stringify(watchdog.crashes, null, 4)}`);
if (currentState === "crashedPermanently") { if (currentState === "crashedPermanently") {
dialog.info(`Editing component keeps crashing. Please try restarting Trilium. If problem persists, consider creating a bug report.`); dialog.info(t("editable-text.keeps-crashing"));
watchdog.editor?.enableReadOnlyMode("crashed-editor"); watchdog.editor?.enableReadOnlyMode("crashed-editor");
} }
} }

View File

@ -61,7 +61,32 @@
"run-active-note": "Spustit aktivní kód JavaScript (frontend/backend) poznámky", "run-active-note": "Spustit aktivní kód JavaScript (frontend/backend) poznámky",
"open-dev-tools": "Otevřít vývojářské nástroje", "open-dev-tools": "Otevřít vývojářské nástroje",
"zoom-out": "Oddálit", "zoom-out": "Oddálit",
"zoom-in": "Přiblížit" "zoom-in": "Přiblížit",
"text-note-operations": "Operace textových poznámek",
"show-cheatsheet": "Zobrazit modal s běžnými operacemi klávesnice",
"add-new-label": "Vytvořít nový štítek",
"insert-date-and-time-to-text": "Vložit do textu současné datum a čas",
"dialogs": "Dialogy",
"show-revisions": "Ukázat dialog „Revize poznámky“",
"add-link-to-text": "Otevřít dialog pro přidání odkazu do textu",
"follow-link-under-cursor": "Následujte odkaz, na kterém je umístěna kurzorová šipka",
"paste-markdown-into-text": "Vložit Markdown ze schránky do poznámky",
"cut-into-note": "Vyjmout výběr z aktuální poznámky a vytvořit podpoznámku s vybraným textem",
"add-include-note-to-text": "Otevřít dialog pro vložení poznámky",
"attributes-labels-and-relations": "Atributy (štítky a vazby)",
"create-new-relation": "Vytvořit novou vazbu",
"ribbon-tabs": "Karty pásu záložek",
"print-active-note": "Vytiskonout aktiivní poznámku",
"render-active-note": "Vykreslit (znovu vykreslit) aktivní poznámku",
"open-note-externally": "Otevřít poznámku jako soubor ve výchozí aplikaci",
"reload-frontend-app": "Znovu načíst frontend",
"unhoist": "Odpojit všude",
"note-navigation": "Navigace v poznámce",
"reset-zoom-level": "Resetovat úroveň přiblížení",
"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)"
}, },
"keyboard_action_names": { "keyboard_action_names": {
"jump-to-note": "Přejít na...", "jump-to-note": "Přejít na...",
@ -73,6 +98,15 @@
"edit-note-title": "Upravit nadpis poznámky", "edit-note-title": "Upravit nadpis poznámky",
"clone-notes-to": "Klonovat poznámku do", "clone-notes-to": "Klonovat poznámku do",
"move-notes-to": "Přemístit poznámku do", "move-notes-to": "Přemístit poznámku do",
"copy-notes-to-clipboard": "Kopírovat poznámky do schránky" "copy-notes-to-clipboard": "Kopírovat poznámky do schránky",
"back-in-note-history": "Zpět v historii poznámky",
"forward-in-note-history": "Vpřed v historii poznámky",
"command-palette": "Paleta příkazů",
"scroll-to-active-note": "Posunout na aktivní poznámku",
"search-in-subtree": "Hledat v podstromu",
"expand-subtree": "Otevřít podstrom",
"collapse-tree": "Zavřít strom",
"collapse-subtree": "Zavřít podstrom",
"sort-child-notes": "Seřadit dceřiné poznámky"
} }
} }

View File

@ -257,7 +257,8 @@
"ai-llm-title": "AI/LLM", "ai-llm-title": "AI/LLM",
"localization": "Sprache & Region", "localization": "Sprache & Region",
"inbox-title": "Posteingang", "inbox-title": "Posteingang",
"zen-mode": "Zen-Modus" "zen-mode": "Zen-Modus",
"command-palette": "Befehlspalette öffnen"
}, },
"notes": { "notes": {
"new-note": "Neue Notiz", "new-note": "Neue Notiz",

View File

@ -50,6 +50,90 @@
"canvas_description": "Uspořádejte tvary, obrázky a text na nekonečném plátně pomocí stejné technologie, jaká se používá na webu excalidraw.com. Ideální pro diagramy, náčrtky a vizuální plánování.", "canvas_description": "Uspořádejte tvary, obrázky a text na nekonečném plátně pomocí stejné technologie, jaká se používá na webu excalidraw.com. Ideální pro diagramy, náčrtky a vizuální plánování.",
"mermaid_title": "Mermaid diagramy", "mermaid_title": "Mermaid diagramy",
"mermaid_description": "Vytvářejte diagramy, jako jsou vývojové diagramy, diagramy tříd a sekvencí, Ganttovy diagramy a mnoho dalších, pomocí syntaxe Mermaid.", "mermaid_description": "Vytvářejte diagramy, jako jsou vývojové diagramy, diagramy tříd a sekvencí, Ganttovy diagramy a mnoho dalších, pomocí syntaxe Mermaid.",
"mindmap_title": "Myšlenková mapa" "mindmap_title": "Myšlenková mapa",
"mindmap_description": "Zorganizujte si myšlenky vizuálně nebo uspořádejte brainstorming.",
"others_list": "a další: <0>mapa poznámek</0>, <1>mapa vztahů</1>, <2>uložená vyhledávání</2>, <3>zobrazení poznámky</3> a <4>webové zobrazení</4>."
},
"extensibility_benefits": {
"title": "Sdílení a rozšiřitelnost",
"import_export_title": "Import/export",
"import_export_description": "Snadná interakce s jinými aplikacemi pomocí formátů Markdown, ENEX a OML.",
"share_title": "Sdílet poznámky na webu",
"share_description": "Pokud máte server, můžete jej použít ke sdílení části svých poznámek s ostatními lidmi.",
"scripting_title": "Pokročilé skriptování",
"scripting_description": "Vytvořte si vlastní integrace v rámci Trilium pomocí přizpůsobených widgetů nebo logiky na straně serveru.",
"api_title": "REST API",
"api_description": "Komunikujte s Trilium programově pomocí jeho vestavěného REST API."
},
"collections": {
"title": "Kolekce",
"calendar_title": "Kalendář",
"calendar_description": "Organizujte své osobní nebo pracovní události pomocí kalendáře, který podporuje celodenní i vícedenní události. Zobrazte si své události na první pohled v týdenním, měsíčním a ročním přehledu. Snadná interakce pro přidávání nebo přetahování událostí.",
"table_title": "Tabulka",
"table_description": "Zobrazujte a upravujte informace o poznámkách v tabulkové struktuře s různými typy sloupců, jako jsou text, čísla, zaškrtávací políčka, datum a čas, odkazy a barvy, a podporou vztahů. Volitelně můžete poznámky zobrazit v hierarchické struktuře stromu uvnitř tabulky.",
"board_title": "Kanbanová tabule",
"board_description": "Uspořádejte si úkoly nebo stav projektu do tabule Kanban, kde můžete snadno vytvářet nové položky a sloupce a jednoduše měnit jejich stav přetahováním po tabuli.",
"geomap_title": "Geomapa",
"geomap_description": "Naplánujte si dovolenou nebo si označte místa, která vás zajímají, přímo na geografické mapě pomocí přizpůsobitelných značek. Zobrazte zaznamenané trasy GPX a sledujte itineráře.",
"presentation_title": "Prezentace",
"presentation_description": "Uspořádejte informace do snímků a prezentujte je na celé obrazovce s plynulými přechody. Snímky lze také exportovat do formátu PDF pro snadné sdílení."
},
"faq": {
"title": "Často kladené otázky",
"mobile_question": "Existuje mobilní aplikace?",
"mobile_answer": "V současné době neexistuje žádná oficiální mobilní aplikace. Pokud však máte instanci serveru, můžete k ní přistupovat pomocí webového prohlížeče a dokonce ji nainstalovat jako PWA. Pro Android existuje neoficiální aplikace s názvem TriliumDroid, která funguje i offline (stejně jako desktopový klient).",
"database_question": "Kde jsou má data uložena?",
"database_answer": "Všechny vaše poznámky budou uloženy v databázi SQLite ve složce aplikace. Důvodem, proč Trilium používá databázi namísto prostých textových souborů, je jak výkon, tak i skutečnost, že některé funkce by byly mnohem obtížnější implementovat, například klony (stejná poznámka na více místech ve stromu). Chcete-li najít složku aplikace, stačí přejít do okna O aplikaci.",
"server_question": "Potřebuju server pro používání Trilium?",
"server_answer": "Ne, server umožňuje přístup přes webový prohlížeč a spravuje synchronizaci, pokud máte více zařízení. Chcete-li začít, stačí si stáhnout desktopovou aplikaci a začít ji používat.",
"scaling_question": "Jak dobře se aplikace přizpůsobuje velkému množství poznámek?",
"scaling_answer": "V závislosti na použití by aplikace měla být schopna bez problémů zpracovat alespoň 100 000 poznámek. Upozorňujeme, že proces synchronizace může někdy selhat, pokud nahráváte mnoho velkých souborů (1 GB na soubor), protože Trilium je spíše aplikací pro správu znalostí než úložištěm souborů (jako například NextCloud).",
"network_share_question": "Mohu sdílet svou databázi přes síťový disk?",
"network_share_answer": "Ne, sdílení databáze SQLite přes síťový disk obecně není dobrý nápad. I když to někdy může fungovat, existuje riziko, že se databáze poškodí kvůli nedokonalému zamykání souborů v síti.",
"security_question": "Jak jsou má data chráněna?",
"security_answer": "Ve výchozím nastavení nejsou poznámky šifrovány a lze je číst přímo z databáze. Jakmile je poznámka označena jako šifrovaná, je zašifrována pomocí AES-128-CBC."
},
"final_cta": {
"title": "Jste připraveni začít používat Trilium Notes?",
"description": "Vytvořte si svou osobní znalostní bázi s výkonnými funkcemi a plným soukromím.",
"get_started": "Začít"
},
"components": {
"link_learn_more": "Zjistit více..."
},
"download_now": {
"text": "Stáhnout nyní ",
"platform_big": "v{{version}} pro{{platform}}",
"platform_small": "pro {{platform}}",
"linux_big": "v{{version}} pro Linux",
"linux_small": "pro Linux",
"more_platforms": "Další platformy a nastavení serveru"
},
"header": {
"get-started": "Začít",
"documentation": "Dokumentace",
"support-us": "Podpořte nás"
},
"footer": {
"copyright_and_the": " a ",
"copyright_community": "komunita"
},
"social_buttons": {
"github": "GitHub",
"github_discussions": "GitHub diskuze",
"matrix": "Matrix",
"reddit": "Reddit"
},
"support_us": {
"title": "Podpořte nás",
"financial_donations_title": "Finanční dary",
"financial_donations_description": "Trilium je vyvíjeno a udržováno díky <Link>stovkám hodin práce</Link>. Vaše podpora zajišťuje, že zůstane open-source, vylepšuje jeho funkce a pokrývá náklady, jako je hosting.",
"financial_donations_cta": "Zvažte podporu hlavního vývojáře (<Link>eliandoran</Link>) aplikace prostřednictvím:",
"github_sponsors": "Sponzoři GitHubu",
"paypal": "PayPal",
"buy_me_a_coffee": "Buy Me A Coffee"
},
"contribute": {
"title": "Další způsoby, jak přispět"
} }
} }

View File

@ -71,6 +71,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@ckeditor/ckeditor5-icons": "47.2.0" "@ckeditor/ckeditor5-icons": "47.2.0",
"mathlive": "0.108.2"
} }
} }

View File

@ -1,6 +1,9 @@
import ckeditor from './../theme/icons/math.svg?raw'; import ckeditor from './../theme/icons/math.svg?raw';
import './augmentation.js'; import './augmentation.js';
import "../theme/mathform.css"; import "../theme/mathform.css";
import 'mathlive';
import 'mathlive/fonts.css';
import 'mathlive/static.css';
export { default as Math } from './math.js'; export { default as Math } from './math.js';
export { default as MathUI } from './mathui.js'; export { default as MathUI } from './mathui.js';

View File

@ -56,7 +56,7 @@ export default class MathUI extends Plugin {
this._balloon.showStack( 'main' ); this._balloon.showStack( 'main' );
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.formView?.mathInputView.fieldView.element?.focus(); this.formView?.mathLiveInputView.focus();
}); });
} }
@ -71,31 +71,38 @@ export default class MathUI extends Plugin {
throw new CKEditorError( 'math-command' ); throw new CKEditorError( 'math-command' );
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const mathConfig = editor.config.get( 'math' )!; const mathConfig = editor.config.get( 'math' )!;
const formView = new MainFormView( const formView = new MainFormView(
editor.locale, editor.locale,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion {
mathConfig.engine!, engine: mathConfig.engine!,
mathConfig.lazyLoad, lazyLoad: mathConfig.lazyLoad,
previewUid: this._previewUid,
previewClassName: mathConfig.previewClassName!,
katexRenderOptions: mathConfig.katexRenderOptions!
},
mathConfig.enablePreview, mathConfig.enablePreview,
this._previewUid, mathConfig.popupClassName!
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mathConfig.previewClassName!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mathConfig.popupClassName!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
mathConfig.katexRenderOptions!
); );
formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' ); formView.mathLiveInputView.bind( 'value' ).to( mathCommand, 'value' );
formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' ); formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' );
// Form elements should be read-only when corresponding commands are disabled. // Form elements should be read-only when corresponding commands are disabled.
formView.mathInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', value => !value ); formView.mathLiveInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', value => !value );
formView.saveButtonView.bind( 'isEnabled' ).to( mathCommand ); formView.saveButtonView.bind( 'isEnabled' ).to(
formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand ); mathCommand,
'isEnabled',
formView.mathLiveInputView,
'value',
( commandEnabled, equation ) => {
const normalizedEquation = ( equation ?? '' ).trim();
return commandEnabled && normalizedEquation.length > 0;
}
);
formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand, 'isEnabled' );
// Listen to submit button click // Listen to submit button click
this.listenTo( formView, 'submit', () => { this.listenTo( formView, 'submit', () => {
@ -122,18 +129,6 @@ export default class MathUI extends Plugin {
} }
}); });
// Allow the textarea to be resizable
formView.mathInputView.fieldView.once('render', () => {
const textarea = formView.mathInputView.fieldView.element;
if (!textarea) return;
Object.assign(textarea.style, {
resize: 'both',
height: '100px',
width: '400px',
minWidth: '100%',
});
});
return formView; return formView;
} }
@ -162,14 +157,14 @@ export default class MathUI extends Plugin {
} ); } );
if ( this._balloon.visibleView === this.formView ) { if ( this._balloon.visibleView === this.formView ) {
this.formView.mathInputView.fieldView.element?.select(); this.formView.mathLiveInputView.focus();
} }
// Show preview element // Show preview element
const previewEl = document.getElementById( this._previewUid ); const previewEl = document.getElementById( this._previewUid );
if ( previewEl && this.formView.previewEnabled ) { if ( previewEl && this.formView.mathView ) {
// Force refresh preview // Force refresh preview
this.formView.mathView?.updateMath(); this.formView.mathView.updateMath();
} }
this.formView.equation = mathCommand.value ?? ''; this.formView.equation = mathCommand.value ?? '';

View File

@ -1,270 +1,219 @@
import { ButtonView, createLabeledTextarea, FocusCycler, LabelView, LabeledFieldView, submitHandler, SwitchButtonView, View, ViewCollection, type TextareaView, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5'; import {
import IconCheck from "@ckeditor/ckeditor5-icons/theme/icons/check.svg?raw"; ButtonView,
import IconCancel from "@ckeditor/ckeditor5-icons/theme/icons/cancel.svg?raw"; FocusCycler,
LabelView,
submitHandler,
SwitchButtonView,
View,
ViewCollection,
type FocusableView,
Locale,
FocusTracker,
KeystrokeHandler
} from 'ckeditor5';
import IconCheck from '@ckeditor/ckeditor5-icons/theme/icons/check.svg?raw';
import IconCancel from '@ckeditor/ckeditor5-icons/theme/icons/cancel.svg?raw';
import { extractDelimiters, hasDelimiters } from '../utils.js'; import { extractDelimiters, hasDelimiters } from '../utils.js';
import MathView from './mathview.js'; import MathView, { type MathViewOptions } from './mathview.js';
import MathLiveInputView from './mathliveinputview.js';
import RawLatexInputView from './rawlatexinputview.js';
import '../../theme/mathform.css'; import '../../theme/mathform.css';
import type { KatexOptions } from '../typings-external.js';
class MathInputView extends LabeledFieldView<TextareaView> {
public value: null | string = null;
public isReadOnly = false;
constructor( locale: Locale ) {
super( locale, createLabeledTextarea );
}
}
export default class MainFormView extends View { export default class MainFormView extends View {
public saveButtonView: ButtonView; public saveButtonView: ButtonView;
public mathInputView: MathInputView;
public displayButtonView: SwitchButtonView;
public cancelButtonView: ButtonView; public cancelButtonView: ButtonView;
public previewEnabled: boolean; public displayButtonView: SwitchButtonView;
public previewLabel?: LabelView;
public mathLiveInputView: MathLiveInputView;
public rawLatexInputView: RawLatexInputView;
public mathView?: MathView; public mathView?: MathView;
public override locale: Locale = new Locale();
public lazyLoad: undefined | ( () => Promise<void> ); public focusTracker = new FocusTracker();
public keystrokes = new KeystrokeHandler();
private _focusables = new ViewCollection<FocusableView>();
private _focusCycler: FocusCycler;
constructor( constructor(
locale: Locale, locale: Locale,
engine: mathViewOptions: MathViewOptions,
| 'mathjax'
| 'katex'
| ( (
equation: string,
element: HTMLElement,
display: boolean,
) => void ),
lazyLoad: undefined | ( () => Promise<void> ),
previewEnabled = false, previewEnabled = false,
previewUid: string, popupClassName: string[] = []
previewClassName: Array<string>,
popupClassName: Array<string>,
katexRenderOptions: KatexOptions
) { ) {
super( locale ); super( locale );
const t = locale.t; const t = locale.t;
// Submit button // --- 1. View Initialization ---
this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save', null );
this.mathLiveInputView = new MathLiveInputView( locale );
this.rawLatexInputView = new RawLatexInputView( locale );
this.rawLatexInputView.label = t( 'LaTeX' );
this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save' );
this.saveButtonView.type = 'submit'; this.saveButtonView.type = 'submit';
// Equation input this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel' );
this.mathInputView = this._createMathInput(); this.cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
// Display button this.displayButtonView = this._createDisplayButton( t );
this.displayButtonView = this._createDisplayButton();
// Cancel button // --- 2. Construct Children & Preview ---
this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel', 'cancel' );
this.previewEnabled = previewEnabled; const children: View[] = [
this.mathLiveInputView,
this.rawLatexInputView,
this.displayButtonView
];
let children = []; if ( previewEnabled ) {
if ( this.previewEnabled ) { const previewLabel = new LabelView( locale );
// Preview label previewLabel.text = t( 'Equation preview' );
this.previewLabel = new LabelView( locale );
this.previewLabel.text = t( 'Equation preview' );
// Math element // Clean instantiation using the options object
this.mathView = new MathView( engine, lazyLoad, locale, previewUid, previewClassName, katexRenderOptions ); this.mathView = new MathView( locale, mathViewOptions );
// Bind display mode: When button flips, preview updates automatically
this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' ); this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' );
children = [ children.push( previewLabel, this.mathView );
this.mathInputView,
this.displayButtonView,
this.previewLabel,
this.mathView
];
} else {
children = [
this.mathInputView,
this.displayButtonView
];
} }
// Add UI elements to template // --- 3. Sync Logic ---
this._setupInputSync( previewEnabled );
// --- 4. Template Setup ---
this.setTemplate( { this.setTemplate( {
tag: 'form', tag: 'form',
attributes: { attributes: {
class: [ class: [ 'ck', 'ck-math-form', ...popupClassName ],
'ck',
'ck-math-form',
...popupClassName
],
tabindex: '-1', tabindex: '-1',
spellcheck: 'false' spellcheck: 'false'
}, },
children: [ children: [
{ {
tag: 'div', tag: 'div',
attributes: { attributes: { class: [ 'ck-math-scroll' ] },
class: [ children: [ { tag: 'div', attributes: { class: [ 'ck-math-view' ] }, children } ]
'ck-math-view'
]
},
children
}, },
this.saveButtonView, {
this.cancelButtonView tag: 'div',
attributes: { class: [ 'ck-math-button-row' ] },
children: [ this.saveButtonView, this.cancelButtonView ]
}
] ]
} ); } );
// --- 5. Accessibility ---
this._focusCycler = new FocusCycler( {
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: { focusPrevious: 'shift + tab', focusNext: 'tab' }
} );
} }
public override render(): void { public override render(): void {
super.render(); super.render();
// Prevent default form submit event & trigger custom 'submit' submitHandler( { view: this } );
submitHandler( {
view: this
} );
// Register form elements to focusable elements // Register focusables
const childViews = [ [
this.mathInputView, this.mathLiveInputView,
this.rawLatexInputView,
this.displayButtonView, this.displayButtonView,
this.saveButtonView, this.saveButtonView,
this.cancelButtonView this.cancelButtonView
]; ].forEach( v => {
childViews.forEach( v => {
if ( v.element ) { if ( v.element ) {
this._focusables.add( v ); this._focusables.add( v );
this.focusTracker.add( v.element ); this.focusTracker.add( v.element );
} }
} ); } );
// Listen to keypresses inside form element if ( this.element ) this.keystrokes.listenTo( this.element );
if ( this.element ) { }
this.keystrokes.listenTo( this.element );
} public get equation(): string {
return this.mathLiveInputView.value ?? '';
}
public set equation( equation: string ) {
const norm = equation.trim();
// Direct updates to the "source of truth"
this.mathLiveInputView.value = norm.length ? norm : null;
this.rawLatexInputView.value = norm;
if ( this.mathView ) this.mathView.value = norm;
} }
public focus(): void { public focus(): void {
this._focusCycler.focusFirst(); this._focusCycler.focusFirst();
} }
public get equation(): string { /**
return this.mathInputView.fieldView.element?.value ?? ''; * Sets up split handlers for synchronization.
} */
private _setupInputSync( previewEnabled: boolean ): void {
// Handler 1: MathLive -> Raw LaTeX
this.mathLiveInputView.on( 'change:value', () => {
let eq = ( this.mathLiveInputView.value ?? '' ).trim();
public set equation( equation: string ) { // Delimiter Normalization
if ( this.mathInputView.fieldView.element ) { if ( hasDelimiters( eq ) ) {
this.mathInputView.fieldView.element.value = equation; const params = extractDelimiters( eq );
} eq = params.equation;
if ( this.previewEnabled && this.mathView ) { this.displayButtonView.isOn = params.display;
this.mathView.value = equation;
}
}
public focusTracker: FocusTracker = new FocusTracker(); // UX Fix: If we stripped delimiters, update the source
public keystrokes: KeystrokeHandler = new KeystrokeHandler(); // so the visual editor doesn't show them.
private _focusables = new ViewCollection<FocusableView>(); if ( this.mathLiveInputView.value !== eq ) {
private _focusCycler: FocusCycler = new FocusCycler( { this.mathLiveInputView.value = eq;
focusables: this._focusables,
focusTracker: this.focusTracker,
keystrokeHandler: this.keystrokes,
actions: {
focusPrevious: 'shift + tab',
focusNext: 'tab'
}
} );
private _createMathInput() {
const t = this.locale.t;
// Create equation input
const mathInput = new MathInputView( this.locale );
const fieldView = mathInput.fieldView;
mathInput.infoText = t( 'Insert equation in TeX format.' );
const onInput = () => {
if ( fieldView.element != null ) {
let equationInput = fieldView.element.value.trim();
// If input has delimiters
if ( hasDelimiters( equationInput ) ) {
// Get equation without delimiters
const params = extractDelimiters( equationInput );
// Remove delimiters from input field
fieldView.element.value = params.equation;
equationInput = params.equation;
// update display button and preview
this.displayButtonView.isOn = params.display;
} }
if ( this.previewEnabled && this.mathView ) {
// Update preview view
this.mathView.value = equationInput;
}
this.saveButtonView.isEnabled = !!equationInput;
} }
};
fieldView.on( 'render', onInput ); // Sync to Raw LaTeX
fieldView.on( 'input', onInput ); if ( this.rawLatexInputView.value !== eq ) {
this.rawLatexInputView.value = eq;
}
return mathInput; // Sync to Preview
if ( previewEnabled && this.mathView && this.mathView.value !== eq ) {
this.mathView.value = eq;
}
} );
// Handler 2: Raw LaTeX -> MathLive
this.rawLatexInputView.on( 'change:value', () => {
const eq = ( this.rawLatexInputView.value ?? '' ).trim();
const normalized = eq.length ? eq : null;
// Sync to MathLive
if ( this.mathLiveInputView.value !== normalized ) {
this.mathLiveInputView.value = normalized;
}
// Sync to Preview
if ( previewEnabled && this.mathView && this.mathView.value !== eq ) {
this.mathView.value = eq;
}
} );
} }
private _createButton( private _createButton( label: string, icon: string, className: string ): ButtonView {
label: string, const btn = new ButtonView( this.locale );
icon: string, btn.set( { label, icon, tooltip: true } );
className: string, btn.extendTemplate( { attributes: { class: className } } );
eventName: string | null return btn;
) {
const button = new ButtonView( this.locale );
button.set( {
label,
icon,
tooltip: true
} );
button.extendTemplate( {
attributes: {
class: className
}
} );
if ( eventName ) {
button.delegate( 'execute' ).to( this, eventName );
}
return button;
} }
private _createDisplayButton() { private _createDisplayButton( t: ( str: string ) => string ): SwitchButtonView {
const t = this.locale.t; const btn = new SwitchButtonView( this.locale );
btn.set( { label: t( 'Display mode' ), withText: true } );
btn.extendTemplate( { attributes: { class: 'ck-button-display-toggle' } } );
const switchButton = new SwitchButtonView( this.locale ); btn.on( 'execute', () => {
btn.isOn = !btn.isOn;
switchButton.set( { // mathView updates automatically via bind()
label: t( 'Display mode' ),
withText: true
} ); } );
return btn;
switchButton.extendTemplate( {
attributes: {
class: 'ck-button-display-toggle'
}
} );
switchButton.on( 'execute', () => {
// Toggle state
switchButton.isOn = !switchButton.isOn;
if ( this.previewEnabled && this.mathView ) {
// Update preview view
this.mathView.display = switchButton.isOn;
}
} );
return switchButton;
} }
} }

View File

@ -0,0 +1,116 @@
import { View, type Locale } from 'ckeditor5';
import 'mathlive'; // Import side-effects only (registers the <math-field> tag)
/**
* Interface describing the custom <math-field> element.
*/
interface MathFieldElement extends HTMLElement {
value: string;
readOnly: boolean;
mathVirtualKeyboardPolicy: string;
// Interface includes the shortcuts property
inlineShortcuts: Record<string, string>;
}
/**
* A wrapper for the MathLive <math-field> component.
*/
export default class MathLiveInputView extends View {
/**
* The current LaTeX value.
* @observable
*/
public declare value: string | null;
/**
* Read-only state.
* @observable
*/
public declare isReadOnly: boolean;
/**
* Reference to the DOM element.
* Typed as MathFieldElement | null for proper TS support.
*/
public mathfield: MathFieldElement | null = null;
constructor( locale: Locale ) {
super( locale );
this.set( 'value', null );
this.set( 'isReadOnly', false );
this.setTemplate( {
tag: 'div',
attributes: {
class: [ 'ck', 'ck-mathlive-input' ]
}
} );
}
public override render(): void {
super.render();
// 1. Create element with the specific type
const mathfield = document.createElement( 'math-field' ) as MathFieldElement;
// 2. Configure Options
mathfield.mathVirtualKeyboardPolicy = 'manual';
//Disable differential D
mathfield.addEventListener( 'mount', () => {
mathfield.inlineShortcuts = {
...mathfield.inlineShortcuts, // Safe to read now
dx: 'dx',
dy: 'dy',
dt: 'dt'
};
} );
// Disable sounds safely
const MathfieldConstructor = customElements.get( 'math-field' );
if ( MathfieldConstructor ) {
const proto = MathfieldConstructor as any;
if ( proto.soundsDirectory !== null ) proto.soundsDirectory = null;
if ( proto.plonkSound !== null ) proto.plonkSound = null;
}
// 3. Set Initial State
mathfield.value = this.value ?? '';
mathfield.readOnly = this.isReadOnly;
// 4. Bind Events (DOM -> Observable)
mathfield.addEventListener( 'input', () => {
const val = mathfield.value;
this.value = val.length ? val : null;
} );
// 5. Bind Events (Observable -> DOM)
this.on( 'change:value', ( _evt, _name, nextValue ) => {
if ( mathfield.value !== nextValue ) {
mathfield.value = nextValue ?? '';
}
} );
this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => {
mathfield.readOnly = nextValue;
} );
// 6. Mount to the wrapper view
this.element?.appendChild( mathfield );
this.mathfield = mathfield;
}
public focus(): void {
this.mathfield?.focus();
}
public override destroy(): void {
if ( this.mathfield ) {
this.mathfield.remove();
this.mathfield = null;
}
super.destroy();
}
}

View File

@ -2,44 +2,44 @@ import { View, type Locale } from 'ckeditor5';
import type { KatexOptions } from '../typings-external.js'; import type { KatexOptions } from '../typings-external.js';
import { renderEquation } from '../utils.js'; import { renderEquation } from '../utils.js';
/**
* Configuration options for the MathView.
*/
export interface MathViewOptions {
engine: 'mathjax' | 'katex' | ( ( equation: string, element: HTMLElement, display: boolean ) => void );
lazyLoad: undefined | ( () => Promise<void> );
previewUid: string;
previewClassName: Array<string>;
katexRenderOptions: KatexOptions;
}
export default class MathView extends View { export default class MathView extends View {
/**
* The LaTeX equation value to render.
* @observable
*/
public declare value: string; public declare value: string;
/**
* Whether to render in display mode (centered) or inline.
* @observable
*/
public declare display: boolean; public declare display: boolean;
public previewUid: string;
public previewClassName: Array<string>;
public katexRenderOptions: KatexOptions;
public engine:
| 'mathjax'
| 'katex'
| ( ( equation: string, element: HTMLElement, display: boolean ) => void );
public lazyLoad: undefined | ( () => Promise<void> );
constructor( /**
engine: * Configuration options passed during initialization.
| 'mathjax' */
| 'katex' private options: MathViewOptions;
| ( (
equation: string, constructor( locale: Locale, options: MathViewOptions ) {
element: HTMLElement,
display: boolean,
) => void ),
lazyLoad: undefined | ( () => Promise<void> ),
locale: Locale,
previewUid: string,
previewClassName: Array<string>,
katexRenderOptions: KatexOptions
) {
super( locale ); super( locale );
this.options = options;
this.engine = engine;
this.lazyLoad = lazyLoad;
this.previewUid = previewUid;
this.katexRenderOptions = katexRenderOptions;
this.previewClassName = previewClassName;
this.set( 'value', '' ); this.set( 'value', '' );
this.set( 'display', false ); this.set( 'display', false );
// Update rendering when state changes.
// Checking isRendered prevents errors during initialization.
this.on( 'change', () => { this.on( 'change', () => {
if ( this.isRendered ) { if ( this.isRendered ) {
this.updateMath(); this.updateMath();
@ -56,16 +56,20 @@ export default class MathView extends View {
public updateMath(): void { public updateMath(): void {
if ( this.element ) { if ( this.element ) {
// This prevents the new render from appending to the old one.
this.element.textContent = '';
void renderEquation( void renderEquation(
this.value, this.value,
this.element, this.element,
this.engine, this.options.engine,
this.lazyLoad, this.options.lazyLoad,
this.display, this.display,
true, true, // isPreview
this.previewUid, this.options.previewUid,
this.previewClassName, this.options.previewClassName,
this.katexRenderOptions this.options.katexRenderOptions
); );
} }
} }

View File

@ -0,0 +1,54 @@
import { LabeledFieldView, createLabeledTextarea, type Locale, type TextareaView } from 'ckeditor5';
/**
* A labeled textarea view for direct LaTeX code editing.
*/
export default class RawLatexInputView extends LabeledFieldView<TextareaView> {
/**
* The current LaTeX value.
* @observable
*/
public declare value: string;
/**
* Whether the input is in read-only mode.
* @observable
*/
public declare isReadOnly: boolean;
constructor( locale: Locale ) {
super( locale, createLabeledTextarea );
this.set( 'value', '' );
this.set( 'isReadOnly', false );
const fieldView = this.fieldView;
// 1. Sync: DOM (Textarea) -> Observable
fieldView.on( 'input', () => {
// We cast strictly to HTMLTextAreaElement to access '.value' safely
const textarea = fieldView.element as HTMLTextAreaElement;
if ( textarea ) {
this.value = textarea.value;
}
} );
// 2. Sync: Observable -> DOM (Textarea)
this.on( 'change:value', () => {
const textarea = fieldView.element as HTMLTextAreaElement;
// Check for difference to avoid cursor jumping
if ( textarea && textarea.value !== this.value ) {
textarea.value = this.value;
}
} );
// 3. Sync: ReadOnly State
this.on( 'change:isReadOnly', ( _evt, _name, nextValue ) => {
fieldView.isReadOnly = nextValue;
} );
}
public override render(): void {
super.render();
}
}

View File

@ -168,13 +168,13 @@ describe( 'MathUI', () => {
command.isEnabled = true; command.isEnabled = true;
expect( formView!.mathInputView.isReadOnly ).to.be.false; expect( formView!.mathLiveInputView.isReadOnly ).to.be.false;
expect( formView!.saveButtonView.isEnabled ).to.be.false; expect( formView!.saveButtonView.isEnabled ).to.be.false;
expect( formView!.cancelButtonView.isEnabled ).to.be.true; expect( formView!.cancelButtonView.isEnabled ).to.be.true;
command.isEnabled = false; command.isEnabled = false;
expect( formView!.mathInputView.isReadOnly ).to.be.true; expect( formView!.mathLiveInputView.isReadOnly ).to.be.true;
expect( formView!.saveButtonView.isEnabled ).to.be.false; expect( formView!.saveButtonView.isEnabled ).to.be.false;
expect( formView!.cancelButtonView.isEnabled ).to.be.true; expect( formView!.cancelButtonView.isEnabled ).to.be.true;
} ); } );
@ -407,22 +407,30 @@ describe( 'MathUI', () => {
setModelData( editor.model, '<paragraph>f[o]o</paragraph>' ); setModelData( editor.model, '<paragraph>f[o]o</paragraph>' );
} ); } );
it( 'should bind mainFormView.mathInputView#value to math command value', () => { it( 'should bind mainFormView.mathLiveInputView#value to math command value', () => {
const command = editor.commands.get( 'math' ); const command = editor.commands.get( 'math' );
expect( formView!.mathInputView.value ).to.null; expect( formView!.mathLiveInputView.value ).to.be.null;
command!.value = 'x^2'; command!.value = 'x^2';
expect( formView!.mathInputView.value ).to.equal( 'x^2' ); expect( formView!.mathLiveInputView.value ).to.equal( 'x^2' );
} ); } );
it( 'should execute math command on mainFormView#submit event', () => { it( 'should execute math command on mainFormView#submit event', () => {
const executeSpy = vi.spyOn( editor, 'execute' ); const executeSpy = vi.spyOn( editor, 'execute' );
formView!.mathInputView.fieldView.element!.value = 'x^2'; formView!.mathLiveInputView.value = 'x^2';
formView!.fire( 'submit' ); formView!.fire( 'submit' );
expect(executeSpy.mock.lastCall?.slice(0, 2)).toMatchObject(['math', 'x^2']); expect( executeSpy.mock.lastCall?.slice( 0, 2 ) ).toMatchObject( [ 'math', 'x^2' ] );
} );
it( 'should sync mathLiveInputView and rawLatexInputView', () => {
formView!.mathLiveInputView.value = 'x^2';
expect( formView!.rawLatexInputView.value ).to.equal( 'x^2' );
formView!.rawLatexInputView.value = '\\frac{1}{2}';
expect( formView!.mathLiveInputView.value ).to.equal( '\\frac{1}{2}' );
} ); } );
it( 'should hide the balloon on mainFormView#cancel if math command does not have a value', () => { it( 'should hide the balloon on mainFormView#cancel if math command does not have a value', () => {

View File

@ -1,35 +1,214 @@
/**
* Math equation editor dialog styles
* Supports MathLive input, raw LaTeX textarea, and equation preview
*/
/* ============================================================================
Main Dialog Container
========================================================================= */
.ck.ck-math-form { .ck.ck-math-form {
display: flex; display: flex;
align-items: flex-start; flex-direction: column;
flex-direction: row; padding: var(--ck-spacing-standard);
flex-wrap: nowrap; box-sizing: border-box;
padding: var(--ck-spacing-standard); max-width: 80vw;
max-height: 80vh;
@media screen and (max-width: 600px) { height: 100%;
flex-wrap: wrap; overflow-x: hidden;
& .ck-math-view {
flex-basis: 100%;
& .ck-labeled-view {
flex-basis: 100%;
}
& .ck-label {
flex-basis: 100%;
}
}
& .ck-button {
flex-basis: 50%;
}
}
} }
.ck-math-tex.ck-placeholder::before { /* Mobile responsiveness */
display: none !important; @media screen and (max-width: 600px) {
.ck.ck-math-form {
flex-wrap: wrap;
}
} }
.ck.ck-toolbar-container { /* ============================================================================
z-index: calc(var(--ck-z-panel) + 2); Content Layout
========================================================================= */
.ck-math-view {
display: flex;
flex-direction: column;
flex: 1 1 auto;
gap: var(--ck-spacing-standard);
min-height: fit-content;
min-width: 0;
width: 100%;
}
/* LaTeX section heading */
.ck-math-view > .ck-labeled-field-view::before {
content: "LaTeX";
display: block;
font-size: 12px;
font-weight: 600;
color: var(--ck-color-text, #333);
margin-bottom: 4px;
padding-left: 2px;
opacity: 0.8;
}
/* Equation preview section heading */
.ck-math-view > math-field::before {
content: "Equation preview";
display: block;
font-size: 12px;
font-weight: 600;
color: var(--ck-color-text, #333);
margin-bottom: 4px;
padding-left: 2px;
opacity: 0.8;
}
/* Add spacing between preview and action buttons */
.ck-math-view > math-field {
margin-bottom: var(--ck-spacing-large, 16px);
}
/* Action buttons row (Save/Cancel) */
.ck-math-button-row {
display: flex;
flex-shrink: 0;
gap: var(--ck-spacing-standard);
margin-top: var(--ck-spacing-standard);
width: fit-content;
max-width: 100%;
flex-wrap: wrap;
}
/* ============================================================================
Shared Styles for Input Fields
========================================================================= */
/* Base styling for both MathLive fields and textareas */
.ck.ck-math-form math-field,
.ck.ck-math-form textarea {
box-sizing: border-box;
padding: var(--ck-spacing-small);
background: var(--ck-color-input-background) !important;
color: var(--ck-color-input-text, inherit);
font-size: var(--ck-font-size-base);
border: none !important;
border-radius: var(--ck-border-radius, 6px);
outline: 3px solid transparent;
outline-offset: 6px;
}
/* MathLive-specific configuration */
.ck.ck-math-form math-field {
display: block !important;
width: 100%;
max-width: 100%;
overflow-x: auto !important;
/* MathLive theme customization */
--selection-background-color: rgba(33, 150, 243, 0.2);
--selection-color: inherit;
--contains-highlight-background-color: rgba(0, 0, 0, 0.05);
}
/* ============================================================================
MathLive Visual Editor (Top Input)
========================================================================= */
.ck.ck-mathlive-input {
display: inline-block;
flex: 0 0 auto;
width: 100%;
max-width: 100%;
min-height: fit-content;
max-height: 80vh;
overflow: auto;
padding-bottom: var(--ck-spacing-small);
resize: none;
}
/* Configure MathLive shadow DOM layout */
.ck.ck-math-form math-field::part(container),
.ck.ck-math-form math-field::part(content),
.ck.ck-math-form math-field::part(field) {
display: flex;
flex-direction: column;
flex: 1 1 auto;
height: 100%;
align-items: flex-start;
justify-content: flex-start;
}
/* Position MathLive UI controls */
.ck.ck-math-form math-field::part(virtual-keyboard-toggle),
.ck.ck-math-form math-field::part(menu-toggle) {
position: absolute;
top: 8px;
}
.ck.ck-math-form math-field::part(virtual-keyboard-toggle) {
right: 40px;
}
.ck.ck-math-form math-field::part(menu-toggle) {
right: 8px;
display: flex !important;
visibility: visible !important;
}
/* ============================================================================
Raw LaTeX Textarea (Middle Input)
========================================================================= */
.ck-math-view .ck-labeled-field-view {
display: flex;
flex-direction: column;
flex: 0 0 auto;
min-width: 100%;
width: 100%;
max-width: 100%;
min-height: 60px;
max-height: 65vh;
resize: both;
overflow: auto;
background: transparent;
}
/* Hide the default label (we use ::before for custom heading) */
.ck-math-view .ck-labeled-field-view .ck-label {
display: none !important;
}
/* Textarea wrapper */
.ck-math-view .ck-labeled-field-view .ck-labeled-field-view__input-wrapper {
display: flex;
flex-direction: column;
flex: 1 1 auto;
width: 100%;
min-height: 100px;
height: auto;
padding: 0;
border: none;
background: transparent;
box-shadow: none;
}
/* Raw LaTeX textarea styling */
.ck-math-view .ck-labeled-field-view textarea {
display: block;
flex: 1 1 auto;
width: 100% !important;
height: 100%;
min-height: 140px;
resize: none !important;
border-radius: 0 !important;
box-shadow: none !important;
transition: none !important;
}
/* Textarea hover and focus states */
.ck-math-view .ck-labeled-field-view textarea:hover,
.ck-math-view .ck-labeled-field-view textarea:focus {
background: var(--ck-color-input-background) !important;
outline: none !important;
box-shadow: none !important;
} }

View File

@ -2,9 +2,14 @@
* https://github.com/TriliumNext/Trilium/issues/1002 * https://github.com/TriliumNext/Trilium/issues/1002
*/ */
import { Command, ModelDocumentSelection, ModelElement, ModelNode, Plugin, ModelRange } from 'ckeditor5'; import { Command, ModelDocumentSelection, ModelElement, ModelNode, Plugin, ModelRange, _isMac, Editor } from 'ckeditor5';
export default class MoveBlockUpDownPlugin extends Plugin {
const keyMap = {
ArrowUp: 'moveBlockUp',
ArrowDown: 'moveBlockDown'
};
export default class MoveBlockUpDownPlugin extends Plugin {
init() { init() {
const editor = this.editor; const editor = this.editor;
@ -21,17 +26,14 @@ export default class MoveBlockUpDownPlugin extends Plugin {
const domRoot = editor.editing.view.getDomRoot(); const domRoot = editor.editing.view.getDomRoot();
if (!domRoot) return; if (!domRoot) return;
const isMac = _isMac(navigator.userAgent.toLowerCase());
const handleKeydown = (e: KeyboardEvent) => { const handleKeydown = (e: KeyboardEvent) => {
const keyMap = {
ArrowUp: 'moveBlockUp',
ArrowDown: 'moveBlockDown'
};
const command = keyMap[e.key]; const command = keyMap[e.key];
const isCtrl = e.ctrlKey || e.metaKey; if (!command) return;
const hasModifier = (isCtrl || e.altKey) && !(isCtrl && e.altKey); const isOnlyMeta = (!e.ctrlKey && !e.altKey && e.metaKey);
const isOnlyAlt = (!e.ctrlKey && e.altKey && !e.metaKey);
if (command && hasModifier) { if ((!isMac && isOnlyMeta) || isOnlyAlt) {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
editor.execute(command); editor.execute(command);
@ -100,8 +102,7 @@ abstract class MoveBlockUpDownCommand extends Command {
} }
writer.setSelection(range); writer.setSelection(range);
this.editor.editing.view.focus(); this.editor.editing.view.focus();
scrollToSelection(this.editor);
this.scrollToSelection();
}); });
} }
@ -129,13 +130,6 @@ abstract class MoveBlockUpDownCommand extends Command {
// Deduplicate adjacent duplicates (e.g., nested selections resolving to same block) // Deduplicate adjacent duplicates (e.g., nested selections resolving to same block)
return resolved.filter((blk, idx) => idx === 0 || blk !== resolved[idx - 1]); return resolved.filter((blk, idx) => idx === 0 || blk !== resolved[idx - 1]);
} }
scrollToSelection() {
// Ensure scroll happens in sync with DOM updates
requestAnimationFrame(() => {
this.editor.editing.view.scrollToTheSelection();
});
};
} }
class MoveBlockUpCommand extends MoveBlockUpDownCommand { class MoveBlockUpCommand extends MoveBlockUpDownCommand {
@ -162,3 +156,10 @@ class MoveBlockDownCommand extends MoveBlockUpDownCommand {
return "after" as const; return "after" as const;
} }
} }
function scrollToSelection(editor: Editor) {
// Ensure scroll happens in sync with DOM updates
requestAnimationFrame(() => {
editor.editing.view.scrollToTheSelection();
});
};

36
pnpm-lock.yaml generated
View File

@ -1061,6 +1061,9 @@ importers:
'@ckeditor/ckeditor5-icons': '@ckeditor/ckeditor5-icons':
specifier: 47.2.0 specifier: 47.2.0
version: 47.2.0 version: 47.2.0
mathlive:
specifier: 0.108.2
version: 0.108.2
devDependencies: devDependencies:
'@ckeditor/ckeditor5-dev-build-tools': '@ckeditor/ckeditor5-dev-build-tools':
specifier: 43.1.0 specifier: 43.1.0
@ -2123,6 +2126,10 @@ packages:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'} engines: {node: '>=0.1.90'}
'@cortex-js/compute-engine@0.30.2':
resolution: {integrity: sha512-Zx+iisk9WWdbxjm8EYsneIBszvjfUs7BHNwf1jBtSINIgfWGpHrTTq9vW0J59iGCFt6bOFxbmWyxNMRSmksHMA==}
engines: {node: '>=21.7.3', npm: '>=10.5.0'}
'@cspotcode/source-map-support@0.8.1': '@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -6879,6 +6886,10 @@ packages:
compare-versions@6.1.1: compare-versions@6.1.1:
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
complex-esm@2.1.1-esm1:
resolution: {integrity: sha512-IShBEWHILB9s7MnfyevqNGxV0A1cfcSnewL/4uPFiSxkcQL4Mm3FxJ0pXMtCXuWLjYz3lRRyk6OfkeDZcjD6nw==}
engines: {node: '>=16.14.2', npm: '>=8.5.0'}
component-emitter@1.3.1: component-emitter@1.3.1:
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
@ -10192,6 +10203,9 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
mathlive@0.108.2:
resolution: {integrity: sha512-GIZkfprGTxrbHckOvwo92ZmOOxdD018BHDzlrEwYUU+pzR5KabhqI1s43lxe/vqXdF5RLiQKgDcuk5jxEjhkYg==}
mathml-tag-names@2.1.3: mathml-tag-names@2.1.3:
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
@ -15623,6 +15637,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.2.0 '@ckeditor/ckeditor5-core': 47.2.0
'@ckeditor/ckeditor5-upload': 47.2.0 '@ckeditor/ckeditor5-upload': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-ai@47.2.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)': '@ckeditor/ckeditor5-ai@47.2.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies: dependencies:
@ -15769,6 +15785,8 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.2.0 '@ckeditor/ckeditor5-core': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-code-block@47.2.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': '@ckeditor/ckeditor5-code-block@47.2.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies: dependencies:
@ -15833,8 +15851,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0
'@ckeditor/ckeditor5-watchdog': 47.2.0 '@ckeditor/ckeditor5-watchdog': 47.2.0
es-toolkit: 1.39.5 es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': '@ckeditor/ckeditor5-dev-build-tools@43.1.0(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies: dependencies:
@ -16029,6 +16045,8 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0 '@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5 es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-editor-multi-root@47.2.0': '@ckeditor/ckeditor5-editor-multi-root@47.2.0':
dependencies: dependencies:
@ -16941,6 +16959,11 @@ snapshots:
'@colors/colors@1.5.0': {} '@colors/colors@1.5.0': {}
'@cortex-js/compute-engine@0.30.2':
dependencies:
complex-esm: 2.1.1-esm1
decimal.js: 10.6.0
'@cspotcode/source-map-support@0.8.1': '@cspotcode/source-map-support@0.8.1':
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.9 '@jridgewell/trace-mapping': 0.3.9
@ -22577,6 +22600,8 @@ snapshots:
compare-versions@6.1.1: {} compare-versions@6.1.1: {}
complex-esm@2.1.1-esm1: {}
component-emitter@1.3.1: {} component-emitter@1.3.1: {}
compress-commons@6.0.2: compress-commons@6.0.2:
@ -23360,8 +23385,7 @@ snapshots:
decimal.js@10.5.0: {} decimal.js@10.5.0: {}
decimal.js@10.6.0: decimal.js@10.6.0: {}
optional: true
decko@1.2.0: {} decko@1.2.0: {}
@ -26780,6 +26804,10 @@ snapshots:
math-intrinsics@1.1.0: {} math-intrinsics@1.1.0: {}
mathlive@0.108.2:
dependencies:
'@cortex-js/compute-engine': 0.30.2
mathml-tag-names@2.1.3: {} mathml-tag-names@2.1.3: {}
mdast-util-find-and-replace@3.0.2: mdast-util-find-and-replace@3.0.2: