From 8ff108db9e82b9db3f2887c6da972d1737287360 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 14 Aug 2025 17:19:38 +0300 Subject: [PATCH 001/532] feat(react/settings): basic rendering of React content widgets --- .../widgets/type_widgets/content_widget.ts | 33 ++++++++++--------- .../type_widgets/options/appearance.tsx | 3 ++ 2 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 apps/client/src/widgets/type_widgets/options/appearance.tsx diff --git a/apps/client/src/widgets/type_widgets/content_widget.ts b/apps/client/src/widgets/type_widgets/content_widget.ts index beb9de272..7f1b675bf 100644 --- a/apps/client/src/widgets/type_widgets/content_widget.ts +++ b/apps/client/src/widgets/type_widgets/content_widget.ts @@ -47,6 +47,9 @@ import type BasicWidget from "../basic_widget.js"; import CodeTheme from "./options/code_notes/code_theme.js"; import RelatedSettings from "./options/appearance/related_settings.js"; import EditorFeaturesOptions from "./options/text_notes/features.js"; +import type { JSX } from "preact/jsx-runtime"; +import AppearanceSettings from "./options/appearance.jsx"; +import { renderReactWidget } from "../react/ReactBasicWidget.jsx"; const TPL = /*html*/`
-
-`; - -interface RelatedSettingsConfig { - items: { - title: string; - targetPage: OptionPages; - }[]; -} - -const RELATED_SETTINGS: Record = { - "_optionsAppearance": { - items: [ - { - title: "Color scheme for code blocks in text notes", - targetPage: "_optionsTextNotes" - }, - { - title: "Color scheme for code notes", - targetPage: "_optionsCodeNotes" - } - ] - } -}; - -export default class RelatedSettings extends OptionsWidget { - - doRender() { - this.$widget = $(TPL); - - const config = this.noteId && RELATED_SETTINGS[this.noteId]; - if (!config) { - return; - } - - const $relatedSettings = this.$widget.find(".related-settings"); - $relatedSettings.empty(); - for (const item of config.items) { - const $item = $("
  • "); - const $link = $("").text(item.title); - - $item.append($link); - $link.attr("href", `#root/_hidden/_options/${item.targetPage}`); - $relatedSettings.append($item); - } - } - - isEnabled() { - return (!!this.noteId && this.noteId in RELATED_SETTINGS); - } - -} diff --git a/apps/client/src/widgets/type_widgets/options/components/RelatedSettings.tsx b/apps/client/src/widgets/type_widgets/options/components/RelatedSettings.tsx new file mode 100644 index 000000000..9ba1b1064 --- /dev/null +++ b/apps/client/src/widgets/type_widgets/options/components/RelatedSettings.tsx @@ -0,0 +1,24 @@ +import OptionsSection from "./OptionsSection"; +import type { OptionPages } from "../../content_widget"; +import { t } from "../../../../services/i18n"; + +interface RelatedSettingsProps { + items: { + title: string; + targetPage: OptionPages; + }[]; +} + +export default function RelatedSettings({ items }: RelatedSettingsProps) { + return ( + + + + ); +} \ No newline at end of file From c67c3a68615e5ef5a3325cfbbc1aa74abb41ea54 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 14 Aug 2025 22:27:07 +0300 Subject: [PATCH 018/532] feat(react/settings): port images --- apps/client/src/stylesheets/style.css | 5 + apps/client/src/widgets/react/FormGroup.tsx | 5 +- .../widgets/type_widgets/content_widget.tsx | 6 +- .../widgets/type_widgets/options/images.tsx | 51 ++++++++++ .../type_widgets/options/images/images.ts | 98 ------------------- 5 files changed, 61 insertions(+), 104 deletions(-) create mode 100644 apps/client/src/widgets/type_widgets/options/images.tsx delete mode 100644 apps/client/src/widgets/type_widgets/options/images/images.ts diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index fa55b04a6..8e1a787e0 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -139,6 +139,11 @@ textarea, color: var(--muted-text-color); } +.form-group.disabled { + opacity: 0.5; + pointer-events: none; +} + /* Add a gap between consecutive radios / check boxes */ label.tn-radio + label.tn-radio, label.tn-checkbox + label.tn-checkbox { diff --git a/apps/client/src/widgets/react/FormGroup.tsx b/apps/client/src/widgets/react/FormGroup.tsx index ff972d49c..513594dfa 100644 --- a/apps/client/src/widgets/react/FormGroup.tsx +++ b/apps/client/src/widgets/react/FormGroup.tsx @@ -7,11 +7,12 @@ interface FormGroupProps { className?: string; children: ComponentChildren; description?: string | ComponentChildren; + disabled?: boolean; } -export default function FormGroup({ label, title, className, children, description, labelRef }: FormGroupProps) { +export default function FormGroup({ label, title, className, children, description, labelRef, disabled }: FormGroupProps) { return ( -
    ) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 04ae86ef2..6ea511be9 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -142,6 +142,14 @@ export function useTriliumOptionInt(name: OptionNames): [number, (newValue: numb ] } +export function useTriliumOptionJson(name: OptionNames): [ T, (newValue: T) => Promise ] { + const [ value, setValue ] = useTriliumOption(name); + return [ + (JSON.parse(value) as T), + (newValue => setValue(JSON.stringify(newValue))) + ]; +} + /** * Generates a unique name via a random alphanumeric string of a fixed length. * diff --git a/apps/client/src/widgets/type_widgets/content_widget.tsx b/apps/client/src/widgets/type_widgets/content_widget.tsx index cf50cb3f3..b847dd04d 100644 --- a/apps/client/src/widgets/type_widgets/content_widget.tsx +++ b/apps/client/src/widgets/type_widgets/content_widget.tsx @@ -24,7 +24,6 @@ import HtmlImportTagsOptions from "./options/other/html_import_tags.js"; import BackendLogWidget from "./content/backend_log.js"; import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js"; import MultiFactorAuthenticationOptions from './options/multi_factor_authentication.js'; -import LocalizationOptions from "./options/i18n/i18n.js"; import CodeBlockOptions from "./options/text_notes/code_block.js"; import EditorOptions from "./options/text_notes/editor.js"; import ShareSettingsOptions from "./options/other/share_settings.js"; @@ -32,7 +31,6 @@ import AiSettingsOptions from "./options/ai_settings.js"; import type FNote from "../../entities/fnote.js"; import type NoteContextAwareWidget from "../note_context_aware_widget.js"; import { t } from "../../services/i18n.js"; -import LanguageOptions from "./options/i18n/language.js"; import type BasicWidget from "../basic_widget.js"; import CodeTheme from "./options/code_notes/code_theme.js"; import EditorFeaturesOptions from "./options/text_notes/features.js"; diff --git a/apps/client/src/widgets/type_widgets/options/components/CheckboxList.tsx b/apps/client/src/widgets/type_widgets/options/components/CheckboxList.tsx new file mode 100644 index 000000000..fedfe1d86 --- /dev/null +++ b/apps/client/src/widgets/type_widgets/options/components/CheckboxList.tsx @@ -0,0 +1,40 @@ +import { useEffect, useState } from "preact/hooks"; + +interface CheckboxListProps { + values: T[]; + keyProperty: keyof T; + titleProperty?: keyof T; + currentValue: string[]; + onChange: (newValues: string[]) => void; +} + +export default function CheckboxList({ values, keyProperty, titleProperty, currentValue, onChange }: CheckboxListProps) { + function toggleValue(value: string) { + if (currentValue.includes(value)) { + // Already there, needs removing. + onChange(currentValue.filter(v => v !== value)); + } else { + // Not there, needs adding. + onChange([ ...currentValue, value ]); + } + } + + return ( +
      + {values.map(value => ( +
    • + +
    • + ))} +
    + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/type_widgets/options/i18n.tsx b/apps/client/src/widgets/type_widgets/options/i18n.tsx index 796e3b0f4..e424ef791 100644 --- a/apps/client/src/widgets/type_widgets/options/i18n.tsx +++ b/apps/client/src/widgets/type_widgets/options/i18n.tsx @@ -3,7 +3,7 @@ import { getAvailableLocales, t } from "../../../services/i18n"; import FormSelect from "../../react/FormSelect"; import OptionsRow from "./components/OptionsRow"; import OptionsSection from "./components/OptionsSection"; -import { useTriliumOption, useTriliumOptionInt } from "../../react/hooks"; +import { useTriliumOption, useTriliumOptionInt, useTriliumOptionJson } from "../../react/hooks"; import type { Locale } from "@triliumnext/commons"; import { isElectron, restartDesktopApp } from "../../../services/utils"; import FormRadioGroup, { FormInlineRadioGroup } from "../../react/FormRadioGroup"; @@ -11,11 +11,13 @@ import FormText from "../../react/FormText"; import RawHtml from "../../react/RawHtml"; import Admonition from "../../react/Admonition"; import Button from "../../react/Button"; +import CheckboxList from "./components/CheckboxList"; export default function InternationalizationOptions() { return ( <> + ) } @@ -115,4 +117,21 @@ function DateSettings() { ) +} + +function ContentLanguages() { + const locales = useMemo(() => getAvailableLocales(), []); + const [ languages, setLanguages ] = useTriliumOptionJson("languages"); + + return ( + + {t("content_language.description")} + + + + ); } \ No newline at end of file diff --git a/apps/client/src/widgets/type_widgets/options/i18n/language.ts b/apps/client/src/widgets/type_widgets/options/i18n/language.ts deleted file mode 100644 index 7b38067a9..000000000 --- a/apps/client/src/widgets/type_widgets/options/i18n/language.ts +++ /dev/null @@ -1,63 +0,0 @@ -import OptionsWidget from "../options_widget.js"; -import type { OptionMap } from "@triliumnext/commons"; -import { getAvailableLocales } from "../../../../services/i18n.js"; -import { t } from "../../../../services/i18n.js"; - -const TPL = /*html*/` -
    -

    ${t("content_language.title")}

    -

    ${t("content_language.description")}

    - -
      -
    - - -
    -`; - -export default class LanguageOptions extends OptionsWidget { - - private $languagesContainer!: JQuery; - - doRender() { - this.$widget = $(TPL); - this.$languagesContainer = this.$widget.find(".options-languages"); - } - - async save() { - const enabledLanguages: string[] = []; - - this.$languagesContainer.find("input:checked").each((i, el) => { - const languageId = $(el).attr("data-language-id"); - if (languageId) { - enabledLanguages.push(languageId); - } - }); - - await this.updateOption("languages", JSON.stringify(enabledLanguages)); - } - - async optionsLoaded(options: OptionMap) { - const availableLocales = getAvailableLocales(); - const enabledLanguages = (JSON.parse(options.languages) as string[]); - - this.$languagesContainer.empty(); - for (const locale of availableLocales) { - const checkbox = $('') - .attr("data-language-id", locale.id) - .prop("checked", enabledLanguages.includes(locale.id)); - const wrapper = $(`
  • ").append(wrapper)); - } - } - -} From f62078d02b1ac748a3bd51cc6d8e39bc59f6e636 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 15 Aug 2025 11:21:19 +0300 Subject: [PATCH 033/532] feat(react/settings): port sync options --- apps/client/src/services/utils.ts | 53 +++++----- apps/client/src/widgets/react/FormTextBox.tsx | 7 +- apps/client/src/widgets/react/hooks.tsx | 14 +++ .../widgets/type_widgets/content_widget.tsx | 6 +- .../src/widgets/type_widgets/options/sync.ts | 100 ------------------ .../src/widgets/type_widgets/options/sync.tsx | 62 +++++++++++ 6 files changed, 110 insertions(+), 132 deletions(-) delete mode 100644 apps/client/src/widgets/type_widgets/options/sync.ts create mode 100644 apps/client/src/widgets/type_widgets/options/sync.tsx diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index fb2d1941d..4b714bf9c 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -374,33 +374,36 @@ async function openInAppHelp($button: JQuery) { const inAppHelpPage = $button.attr("data-in-app-help"); if (inAppHelpPage) { - // Dynamic import to avoid import issues in tests. - const appContext = (await import("../components/app_context.js")).default; - const activeContext = appContext.tabManager.getActiveContext(); - if (!activeContext) { - return; - } - const subContexts = activeContext.getSubContexts(); - const targetNote = `_help_${inAppHelpPage}`; - const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help"); - const viewScope: ViewScope = { - viewMode: "contextual-help", - }; - if (!helpSubcontext) { - // The help is not already open, open a new split with it. - const { ntxId } = subContexts[subContexts.length - 1]; - appContext.triggerCommand("openNewNoteSplit", { - ntxId, - notePath: targetNote, - hoistedNoteId: "_help", - viewScope - }) - } else { - // There is already a help window open, make sure it opens on the right note. - helpSubcontext.setNote(targetNote, { viewScope }); - } + openInAppHelpFromUrl(inAppHelpPage); + } +} + +export async function openInAppHelpFromUrl(inAppHelpPage: string) { + // Dynamic import to avoid import issues in tests. + const appContext = (await import("../components/app_context.js")).default; + const activeContext = appContext.tabManager.getActiveContext(); + if (!activeContext) { return; } + const subContexts = activeContext.getSubContexts(); + const targetNote = `_help_${inAppHelpPage}`; + const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help"); + const viewScope: ViewScope = { + viewMode: "contextual-help", + }; + if (!helpSubcontext) { + // The help is not already open, open a new split with it. + const { ntxId } = subContexts[subContexts.length - 1]; + appContext.triggerCommand("openNewNoteSplit", { + ntxId, + notePath: targetNote, + hoistedNoteId: "_help", + viewScope + }) + } else { + // There is already a help window open, make sure it opens on the right note. + helpSubcontext.setNote(targetNote, { viewScope }); + } } function initHelpButtons($el: JQuery | JQuery) { diff --git a/apps/client/src/widgets/react/FormTextBox.tsx b/apps/client/src/widgets/react/FormTextBox.tsx index abf0e9c5a..01b7b56c2 100644 --- a/apps/client/src/widgets/react/FormTextBox.tsx +++ b/apps/client/src/widgets/react/FormTextBox.tsx @@ -1,5 +1,4 @@ import type { InputHTMLAttributes, RefObject } from "preact/compat"; -import FormText from "./FormText"; interface FormTextBoxProps extends Omit, "onChange" | "value"> { id?: string; @@ -11,9 +10,11 @@ interface FormTextBoxProps extends Omit, " export default function FormTextBox({ inputRef, className, type, currentValue, onChange, ...rest}: FormTextBoxProps) { if (type === "number" && currentValue) { const { min, max } = rest; - if (min && currentValue < min) { + console.log(currentValue , min, max); + const currentValueNum = parseInt(currentValue, 10); + if (min && currentValueNum < parseInt(String(min), 10)) { currentValue = String(min); - } else if (max && currentValue > max) { + } else if (max && currentValueNum > parseInt(String(max), 10)) { currentValue = String(max); } } diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 6ea511be9..846ea62fb 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -6,6 +6,7 @@ import { OptionNames } from "@triliumnext/commons"; import options, { type OptionValue } from "../../services/options"; import utils, { reloadFrontendApp } from "../../services/utils"; import Component from "../../components/component"; +import server from "../../services/server"; type TriliumEventHandler = (data: EventData) => void; const registeredHandlers: Map[]>> = new Map(); @@ -150,6 +151,19 @@ export function useTriliumOptionJson(name: OptionNames): [ T, (newValue: T) = ]; } +export function useTriliumOptions(...names: T[]) { + const values: Record = {}; + for (const name of names) { + values[name] = options.get(name); + } + + const setValue = (newValues: Record) => server.put("options", newValues); + return [ + values as Record, + setValue + ] as const; +} + /** * Generates a unique name via a random alphanumeric string of a fixed length. * diff --git a/apps/client/src/widgets/type_widgets/content_widget.tsx b/apps/client/src/widgets/type_widgets/content_widget.tsx index b847dd04d..9ec12e032 100644 --- a/apps/client/src/widgets/type_widgets/content_widget.tsx +++ b/apps/client/src/widgets/type_widgets/content_widget.tsx @@ -13,7 +13,6 @@ import PasswordOptions from "./options/password/password.js"; import ProtectedSessionTimeoutOptions from "./options/password/protected_session_timeout.js"; import EtapiOptions from "./options/etapi.js"; import BackupOptions from "./options/backup.js"; -import SyncOptions from "./options/sync.js"; import SearchEngineOptions from "./options/other/search_engine.js"; import TrayOptions from "./options/other/tray.js"; import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js"; @@ -40,6 +39,7 @@ import { renderReactWidget } from "../react/ReactBasicWidget.jsx"; import ImageSettings from "./options/images.jsx"; import AdvancedSettings from "./options/advanced.jsx"; import InternationalizationOptions from "./options/i18n.jsx"; +import SyncOptions from "./options/sync.jsx"; const TPL = /*html*/`
    `; - -// TODO: Deduplicate -interface PostTokensResponse { - authToken: string; -} - -// TODO: Deduplicate -interface Token { - name: string; - utcDateCreated: number; - etapiTokenId: string; -} - -export default class EtapiOptions extends OptionsWidget { - - doRender() { - this.$widget = $(TPL); - - this.$widget.find(".create-etapi-token").on("click", async () => { - const tokenName = await dialogService.prompt({ - title: t("etapi.new_token_title"), - message: t("etapi.new_token_message"), - defaultValue: t("etapi.default_token_name") - }); - - if (!tokenName?.trim()) { - toastService.showError(t("etapi.error_empty_name")); - return; - } - - const { authToken } = await server.post("etapi-tokens", { tokenName }); - - await dialogService.prompt({ - title: t("etapi.token_created_title"), - message: t("etapi.token_created_message"), - defaultValue: authToken - }); - - this.refreshTokens(); - }); - - this.refreshTokens(); - } - - async refreshTokens() { - const $noTokensYet = this.$widget.find(".no-tokens-yet"); - const $tokensTable = this.$widget.find(".tokens-table"); - - const tokens = await server.get("etapi-tokens"); - - $noTokensYet.toggle(tokens.length === 0); - $tokensTable.toggle(tokens.length > 0); - - const $tokensTableBody = $tokensTable.find("tbody"); - $tokensTableBody.empty(); - - for (const token of tokens) { - $tokensTableBody.append( - $("") - .append($("").text(token.name)) - .append($("").text(formatDateTime(token.utcDateCreated))) - .append( - $("").append( - $(``).on("click", () => this.renameToken(token.etapiTokenId, token.name)), - $(``).on("click", () => this.deleteToken(token.etapiTokenId, token.name)) - ) - ) - ); - } - } - - async renameToken(etapiTokenId: string, oldName: string) { - const tokenName = await dialogService.prompt({ - title: t("etapi.rename_token_title"), - message: t("etapi.rename_token_message"), - defaultValue: oldName - }); - - if (!tokenName?.trim()) { - return; - } - - await server.patch(`etapi-tokens/${etapiTokenId}`, { name: tokenName }); - - this.refreshTokens(); - } - - async deleteToken(etapiTokenId: string, name: string) { - if (!(await dialogService.confirm(t("etapi.delete_token_confirmation", { name })))) { - return; - } - - await server.remove(`etapi-tokens/${etapiTokenId}`); - - this.refreshTokens(); - } -} diff --git a/apps/client/src/widgets/type_widgets/options/etapi.tsx b/apps/client/src/widgets/type_widgets/options/etapi.tsx new file mode 100644 index 000000000..ebd497ccc --- /dev/null +++ b/apps/client/src/widgets/type_widgets/options/etapi.tsx @@ -0,0 +1,139 @@ +import { useCallback, useEffect, useState } from "preact/hooks"; +import { t } from "../../../services/i18n"; +import Button from "../../react/Button"; +import FormText from "../../react/FormText"; +import RawHtml from "../../react/RawHtml"; +import OptionsSection from "./components/OptionsSection"; +import { EtapiToken, PostTokensResponse } from "@triliumnext/commons"; +import server from "../../../services/server"; +import toast from "../../../services/toast"; +import dialog from "../../../services/dialog"; +import { formatDateTime } from "../../../utils/formatters"; +import ActionButton from "../../react/ActionButton"; + +type RenameTokenCallback = (tokenId: string, oldName: string) => Promise; +type DeleteTokenCallback = (tokenId: string, name: string ) => Promise; + +export default function EtapiSettings() { + const [ tokens, setTokens ] = useState([]); + + function refreshTokens() { + server.get("etapi-tokens").then(setTokens); + } + + useEffect(refreshTokens, []); + + const createTokenCallback = useCallback(async () => { + const tokenName = await dialog.prompt({ + title: t("etapi.new_token_title"), + message: t("etapi.new_token_message"), + defaultValue: t("etapi.default_token_name") + }); + + if (!tokenName?.trim()) { + toast.showError(t("etapi.error_empty_name")); + return; + } + + const { authToken } = await server.post("etapi-tokens", { tokenName }); + + await dialog.prompt({ + title: t("etapi.token_created_title"), + message: t("etapi.token_created_message"), + defaultValue: authToken + }); + + refreshTokens(); + }, []); + + const renameTokenCallback = useCallback(async (tokenId: string, oldName: string) => { + const tokenName = await dialog.prompt({ + title: t("etapi.rename_token_title"), + message: t("etapi.rename_token_message"), + defaultValue: oldName + }); + + if (!tokenName?.trim()) { + return; + } + + await server.patch(`etapi-tokens/${tokenId}`, { name: tokenName }); + + refreshTokens(); + }, []); + + const deleteTokenCallback = useCallback(async (tokenId: string, name: string) => { + if (!(await dialog.confirm(t("etapi.delete_token_confirmation", { name })))) { + return; + } + + await server.remove(`etapi-tokens/${tokenId}`); + refreshTokens(); + }, []); + + return ( + + + {t("etapi.description")}
    + ${t("etapi.wiki")}`, + // TODO: We use window.open src/public/app/services/link.ts -> prevents regular click behavior on "a" element here because it's a relative path + link_to_openapi_spec: `${t("etapi.openapi_spec")}`, + link_to_swagger_ui: `${t("etapi.swagger_ui")}` + })} /> +
    + + - - -
    -`; - -let globActions: KeyboardShortcut[]; - -export default class KeyboardShortcutsOptions extends OptionsWidget { - doRender() { - this.$widget = $(TPL); - - this.$widget.find(".options-keyboard-shortcuts-reload-app").on("click", () => utils.reloadFrontendApp()); - - const $table = this.$widget.find(".keyboard-shortcut-table tbody"); - - server.get("keyboard-actions").then((actions) => { - globActions = actions; - - for (const action of actions) { - const $tr = $(""); - - if ("separator" in action) { - $tr.append($('').attr("style", "background-color: var(--accented-background-color); font-weight: bold;").text(action.separator)); - } else if (action.defaultShortcuts && action.actionName) { - $tr.append($("").text(action.friendlyName)) - .append( - $("").append( - $(``) - .val((action.effectiveShortcuts ?? []).join(", ")) - .attr("data-keyboard-action-name", action.actionName) - .attr("data-default-keyboard-shortcuts", action.defaultShortcuts.join(", ")) - ) - ) - .append($("").text(action.defaultShortcuts.join(", "))) - .append($("").text(action.description ?? "")); - } - - $table.append($tr); - } - }); - - $table.on("change", "input.form-control", (e) => { - const $input = this.$widget.find(e.target); - const actionName = $input.attr("data-keyboard-action-name"); - if (!actionName) { - return; - } - - const shortcuts = ($input.val() as String) - .replace("+,", "+Comma") - .split(",") - .map((shortcut) => shortcut.replace("+Comma", "+,")) - .filter((shortcut) => !!shortcut); - - const optionName = `keyboardShortcuts${actionName.substr(0, 1).toUpperCase()}${actionName.substr(1)}`; - - this.updateOption(optionName as OptionNames, JSON.stringify(shortcuts)); - }); - - this.$widget.find(".options-keyboard-shortcuts-set-all-to-default").on("click", async () => { - if (!(await dialogService.confirm(t("shortcuts.confirm_reset")))) { - return; - } - - $table.find("input.form-control").each((_index, el) => { - const defaultShortcuts = this.$widget.find(el).attr("data-default-keyboard-shortcuts"); - - if (defaultShortcuts && this.$widget.find(el).val() !== defaultShortcuts) { - this.$widget.find(el).val(defaultShortcuts).trigger("change"); - } - }); - }); - - const $filter = this.$widget.find(".keyboard-shortcut-filter"); - - $filter.on("keyup", () => { - const filter = String($filter.val()).trim().toLowerCase(); - - $table.find("tr").each((i, el) => { - if (!filter) { - this.$widget.find(el).show(); - return; - } - - const actionName = this.$widget.find(el).find("input").attr("data-keyboard-action-name"); - - if (!actionName) { - this.$widget.find(el).hide(); - return; - } - - const action = globActions.find((act) => "actionName" in act && act.actionName === actionName) as KeyboardShortcutWithRequiredActionName; - - if (!action) { - this.$widget.find(el).hide(); - return; - } - - this.$widget - .find(el) - .toggle( - !!( - action.actionName.toLowerCase().includes(filter) || - (action.friendlyName && action.friendlyName.toLowerCase().includes(filter)) || - (action.defaultShortcuts ?? []).some((shortcut) => shortcut.toLowerCase().includes(filter)) || - (action.effectiveShortcuts ?? []).some((shortcut) => shortcut.toLowerCase().includes(filter)) || - (action.description && action.description.toLowerCase().includes(filter)) - ) - ); - }); - }); - } -} diff --git a/apps/client/src/widgets/type_widgets/options/shortcuts.tsx b/apps/client/src/widgets/type_widgets/options/shortcuts.tsx new file mode 100644 index 000000000..770ebce87 --- /dev/null +++ b/apps/client/src/widgets/type_widgets/options/shortcuts.tsx @@ -0,0 +1,140 @@ +import { ActionKeyboardShortcut, KeyboardShortcut } from "@triliumnext/commons"; +import { t } from "../../../services/i18n"; +import { reloadFrontendApp } from "../../../services/utils"; +import Button from "../../react/Button"; +import FormGroup from "../../react/FormGroup"; +import FormText from "../../react/FormText"; +import FormTextBox from "../../react/FormTextBox"; +import RawHtml from "../../react/RawHtml"; +import OptionsSection from "./components/OptionsSection"; +import { useCallback, useEffect, useState } from "preact/hooks"; +import server from "../../../services/server"; +import options from "../../../services/options"; +import dialog from "../../../services/dialog"; + +export default function ShortcutSettings() { + const [ keyboardShortcuts, setKeyboardShortcuts ] = useState([]); + const [ filter, setFilter ] = useState(); + + useEffect(() => { + server.get("keyboard-actions").then(setKeyboardShortcuts); + }, []) + + const resetShortcuts = useCallback(async () => { + if (!(await dialog.confirm(t("shortcuts.confirm_reset")))) { + return; + } + + const newKeyboardShortcuts = []; + for (const keyboardShortcut of keyboardShortcuts) { + if (!("effectiveShortcuts" in keyboardShortcut)) { + continue; + } + + } + }, [ keyboardShortcuts ]); + + return ( + + + {t("shortcuts.multiple_shortcuts")} + + + + + setFilter(value.toLowerCase())} + /> + + +
    + +
    + +
    +
    +
    + ) +} + +function filterKeyboardAction(action: ActionKeyboardShortcut, filter: string) { + return action.actionName.toLowerCase().includes(filter) || + (action.friendlyName && action.friendlyName.toLowerCase().includes(filter)) || + (action.defaultShortcuts ?? []).some((shortcut) => shortcut.toLowerCase().includes(filter)) || + (action.effectiveShortcuts ?? []).some((shortcut) => shortcut.toLowerCase().includes(filter)) || + (action.description && action.description.toLowerCase().includes(filter)); +} + +function KeyboardShortcutTable({ filter, keyboardShortcuts }: { filter?: string, keyboardShortcuts: KeyboardShortcut[] }) { + return ( + + + + + + + + + + + {keyboardShortcuts.map(action => ( + + {"separator" in action ? ( !filter && + + ) : ( (!filter || filterKeyboardAction(action, filter)) && + <> + + + + + + )} + + ))} + +
    {t("shortcuts.action_name")}{t("shortcuts.shortcuts")}{t("shortcuts.default_shortcuts")}{t("shortcuts.description")}
    + {action.separator} + {action.friendlyName} + + {action.defaultShortcuts?.join(", ")}{action.description}
    + ); +} + +function ShortcutEditor({ keyboardShortcut: action }: { keyboardShortcut: ActionKeyboardShortcut }) { + const [ shortcuts, setShortcuts ] = useState((action.effectiveShortcuts ?? []).join(", ")); + + useEffect(() => { + const { actionName } = action; + const optionName = `keyboardShortcuts${actionName.substr(0, 1).toUpperCase()}${actionName.substr(1)}`; + const newShortcuts = shortcuts + .replace("+,", "+Comma") + .split(",") + .map((shortcut) => shortcut.replace("+Comma", "+,")) + .filter((shortcut) => !!shortcut); + options.save(optionName, JSON.stringify(newShortcuts)); + }, [ shortcuts ]) + + return ( + + ) +} \ No newline at end of file From b9b4961f3ca2d899eda212e51826121941bc1830 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 16 Aug 2025 00:13:18 +0300 Subject: [PATCH 047/532] fix(react/settings): shortcuts saved upon render --- .../type_widgets/options/shortcuts.tsx | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/options/shortcuts.tsx b/apps/client/src/widgets/type_widgets/options/shortcuts.tsx index 770ebce87..e6171f679 100644 --- a/apps/client/src/widgets/type_widgets/options/shortcuts.tsx +++ b/apps/client/src/widgets/type_widgets/options/shortcuts.tsx @@ -119,22 +119,20 @@ function KeyboardShortcutTable({ filter, keyboardShortcuts }: { filter?: string, } function ShortcutEditor({ keyboardShortcut: action }: { keyboardShortcut: ActionKeyboardShortcut }) { - const [ shortcuts, setShortcuts ] = useState((action.effectiveShortcuts ?? []).join(", ")); - - useEffect(() => { - const { actionName } = action; - const optionName = `keyboardShortcuts${actionName.substr(0, 1).toUpperCase()}${actionName.substr(1)}`; - const newShortcuts = shortcuts - .replace("+,", "+Comma") - .split(",") - .map((shortcut) => shortcut.replace("+Comma", "+,")) - .filter((shortcut) => !!shortcut); - options.save(optionName, JSON.stringify(newShortcuts)); - }, [ shortcuts ]) + const originalShortcut = (action.effectiveShortcuts ?? []).join(", "); return ( { + const { actionName } = action; + const optionName = `keyboardShortcuts${actionName.substr(0, 1).toUpperCase()}${actionName.substr(1)}`; + const newShortcuts = newShortcut + .replace("+,", "+Comma") + .split(",") + .map((shortcut) => shortcut.replace("+Comma", "+,")) + .filter((shortcut) => !!shortcut); + options.save(optionName, JSON.stringify(newShortcuts)); + }} /> ) } \ No newline at end of file From 5614891d925b12a0fc9131bb2550f486fbba7078 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 16 Aug 2025 00:22:18 +0300 Subject: [PATCH 048/532] fix(react/settings): unnecessary top margin --- .../type_widgets/options/components/OptionsSection.tsx | 10 +++++----- .../src/widgets/type_widgets/options/shortcuts.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/options/components/OptionsSection.tsx b/apps/client/src/widgets/type_widgets/options/components/OptionsSection.tsx index bd3a930e0..7fb3440fa 100644 --- a/apps/client/src/widgets/type_widgets/options/components/OptionsSection.tsx +++ b/apps/client/src/widgets/type_widgets/options/components/OptionsSection.tsx @@ -2,17 +2,17 @@ import type { ComponentChildren } from "preact"; import { CSSProperties } from "preact/compat"; interface OptionsSectionProps { - title: string; + title?: string; children: ComponentChildren; noCard?: boolean; style?: CSSProperties; + className?: string; } -export default function OptionsSection({ title, children, noCard, style }: OptionsSectionProps) { +export default function OptionsSection({ title, children, noCard, ...rest }: OptionsSectionProps) { return ( -
    -

    {title}

    - +
    + {title &&

    {title}

    } {children}
    ); diff --git a/apps/client/src/widgets/type_widgets/options/shortcuts.tsx b/apps/client/src/widgets/type_widgets/options/shortcuts.tsx index e6171f679..b948902c3 100644 --- a/apps/client/src/widgets/type_widgets/options/shortcuts.tsx +++ b/apps/client/src/widgets/type_widgets/options/shortcuts.tsx @@ -36,7 +36,7 @@ export default function ShortcutSettings() { return ( From 71b627fbc7f0a4e07619e5e328cb7c358789d6f1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 18 Aug 2025 09:34:16 +0300 Subject: [PATCH 049/532] feat(react/settings): port text formatting toolbar --- .../client/src/widgets/react/FormCheckbox.tsx | 7 +- .../src/widgets/react/FormRadioGroup.tsx | 16 +++-- apps/client/src/widgets/react/hooks.tsx | 14 +++- .../widgets/type_widgets/content_widget.tsx | 12 +--- .../type_widgets/options/appearance.tsx | 6 +- .../type_widgets/options/text_notes.tsx | 46 +++++++++++++ .../type_widgets/options/text_notes/editor.ts | 64 ------------------- 7 files changed, 80 insertions(+), 85 deletions(-) create mode 100644 apps/client/src/widgets/type_widgets/options/text_notes.tsx delete mode 100644 apps/client/src/widgets/type_widgets/options/text_notes/editor.ts diff --git a/apps/client/src/widgets/react/FormCheckbox.tsx b/apps/client/src/widgets/react/FormCheckbox.tsx index 4d6c2a384..f064e2e79 100644 --- a/apps/client/src/widgets/react/FormCheckbox.tsx +++ b/apps/client/src/widgets/react/FormCheckbox.tsx @@ -2,7 +2,7 @@ import { Tooltip } from "bootstrap"; import { useEffect, useRef, useMemo, useCallback } from "preact/hooks"; import { escapeQuotes } from "../../services/utils"; import { ComponentChildren } from "preact"; -import { memo } from "preact/compat"; +import { CSSProperties, memo } from "preact/compat"; interface FormCheckboxProps { name: string; @@ -14,9 +14,10 @@ interface FormCheckboxProps { currentValue: boolean; disabled?: boolean; onChange(newValue: boolean): void; + containerStyle?: CSSProperties; } -const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint }: FormCheckboxProps) => { +const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint, containerStyle }: FormCheckboxProps) => { const labelRef = useRef(null); // Fix: Move useEffect outside conditional @@ -46,7 +47,7 @@ const FormCheckbox = memo(({ name, disabled, label, currentValue, onChange, hint const titleText = useMemo(() => hint ? escapeQuotes(hint) : undefined, [hint]); return ( -
    +
  • ) } + +export function FormDivider() { + return
    ; +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 865898041..e24bec3a8 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -1,8 +1,10 @@ import { useCallback, useMemo } from "preact/hooks"; import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; -import { FormListBadge, FormListItem } from "../react/FormList"; +import { FormDivider, FormListBadge, FormListItem } from "../react/FormList"; import { t } from "../../services/i18n"; +import { useTriliumOption } from "../react/hooks"; +import mime_types from "../../services/mime_types"; export default function BasicPropertiesTab() { return ( @@ -14,9 +16,11 @@ export default function BasicPropertiesTab() { function NoteTypeWidget() { const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); + const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes"); + const mimeTypes = useMemo(() => mime_types.getMimeTypes().filter(mimeType => mimeType.enabled), [ codeNotesMimeTypes ]); return ( - + {noteTypes.map(noteType => { const badges: FormListBadge[] = []; if (noteType.isNew) { @@ -31,12 +35,27 @@ function NoteTypeWidget() { }); } - return ( - {noteType.title} - ); + if (noteType.type !== "code") { + return ( + {noteType.title} + ); + } else { + return ( + <> + + + {noteType.title} + + + ) + } })} + + {mimeTypes.map(mimeType => ( + {mimeType.title} + ))} ) } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 93190578e..5f38712ad 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -114,4 +114,10 @@ .note-language-container { display: flex; align-items: center; +} + +.note-type-dropdown { + max-height: 500px; + overflow-y: auto; + overflow-x: hidden; } \ No newline at end of file From c0beab8a5db7574d3795adbaf565d34221308f4f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 21 Aug 2025 20:38:19 +0300 Subject: [PATCH 179/532] chore(react/ribbon): display current note type --- apps/client/src/widgets/note_type.ts | 15 +-- apps/client/src/widgets/react/Dropdown.tsx | 10 +- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 98 ++++++++++++------- .../ribbon_widgets/basic_properties.ts | 2 +- 4 files changed, 70 insertions(+), 55 deletions(-) diff --git a/apps/client/src/widgets/note_type.ts b/apps/client/src/widgets/note_type.ts index cb1d53bb3..fe45fa233 100644 --- a/apps/client/src/widgets/note_type.ts +++ b/apps/client/src/widgets/note_type.ts @@ -14,8 +14,7 @@ const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => nt.reserved || nt.st const TPL = /*html*/` `; @@ -36,7 +35,6 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown"); this.$noteTypeButton = this.$widget.find(".note-type-button"); - this.$noteTypeDesc = this.$widget.find(".note-type-desc"); this.$widget.on("click", ".dropdown-item", () => this.dropdown.toggle()); } @@ -105,18 +103,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { } } - async findTypeTitle(type: NoteType, mime: string) { - if (type === "code") { - const mimeTypes = mimeTypesService.getMimeTypes(); - const found = mimeTypes.find((mt) => mt.mime === mime); - return found ? found.title : mime; - } else { - const noteType = NOTE_TYPES.find((nt) => nt.type === type); - - return noteType ? noteType.title : type; - } - } async save(type: NoteType, mime?: string) { if (type === this.note?.type && mime === this.note?.mime) { diff --git a/apps/client/src/widgets/react/Dropdown.tsx b/apps/client/src/widgets/react/Dropdown.tsx index 49a1ac228..2578aec31 100644 --- a/apps/client/src/widgets/react/Dropdown.tsx +++ b/apps/client/src/widgets/react/Dropdown.tsx @@ -14,9 +14,10 @@ interface DropdownProps { dropdownContainerClassName?: string; hideToggleArrow?: boolean; disabled?: boolean; + text?: ComponentChildren; } -export default function Dropdown({ className, buttonClassName, isStatic, children, title, dropdownContainerStyle, dropdownContainerClassName, hideToggleArrow, disabled }: DropdownProps) { +export default function Dropdown({ className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, hideToggleArrow, disabled }: DropdownProps) { const dropdownRef = useRef(null); const triggerRef = useRef(null); @@ -56,7 +57,7 @@ export default function Dropdown({ className, buttonClassName, isStatic, childre return (
    NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes"); const mimeTypes = useMemo(() => mime_types.getMimeTypes().filter(mimeType => mimeType.enabled), [ codeNotesMimeTypes ]); + + const { note } = useNoteContext(); + const type = useNoteProperty(note, "type") ?? undefined; + const mime = useNoteProperty(note, "mime"); return ( - - {noteTypes.map(noteType => { - const badges: FormListBadge[] = []; - if (noteType.isNew) { - badges.push({ - className: "new-note-type-badge", - text: t("note_types.new-feature") - }); - } - if (noteType.isBeta) { - badges.push({ - text: t("note_types.beta-feature") - }); - } + <> + {t("basic_properties.note_type")}:   + {findTypeTitle(type, mime)}} + > + {noteTypes.map(noteType => { + const badges: FormListBadge[] = []; + if (noteType.isNew) { + badges.push({ + className: "new-note-type-badge", + text: t("note_types.new-feature") + }); + } + if (noteType.isBeta) { + badges.push({ + text: t("note_types.beta-feature") + }); + } - if (noteType.type !== "code") { - return ( - {noteType.title} - ); - } else { - return ( - <> - - - {noteType.title} - - - ) - } - })} + if (noteType.type !== "code") { + return ( + {noteType.title} + ); + } else { + return ( + <> + + + {noteType.title} + + + ) + } + })} - {mimeTypes.map(mimeType => ( - {mimeType.title} - ))} - + {mimeTypes.map(mimeType => ( + {mimeType.title} + ))} + + ) +} + +function findTypeTitle(type?: NoteType, mime?: string) { + if (type === "code") { + const mimeTypes = mime_types.getMimeTypes(); + const found = mimeTypes.find((mt) => mt.mime === mime); + + return found ? found.title : mime; + } else { + const noteType = NOTE_TYPES.find((nt) => nt.type === type); + + return noteType ? noteType.title : type; + } } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts index 603dee60f..e5f2ca8a2 100644 --- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts @@ -11,7 +11,7 @@ import NoteLanguageWidget from "../note_language.js"; const TPL = /*html*/`
    - ${t("basic_properties.note_type")}:   +
    From f45da049b9243ad80bbd1c5ccf23bfbd38e6f421 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 21 Aug 2025 20:56:37 +0300 Subject: [PATCH 180/532] chore(react/ribbon): change note type --- apps/client/src/widgets/note_type.ts | 97 ------------------- apps/client/src/widgets/react/FormList.tsx | 9 +- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 58 ++++++++--- .../ribbon_widgets/basic_properties.ts | 4 - 4 files changed, 51 insertions(+), 117 deletions(-) diff --git a/apps/client/src/widgets/note_type.ts b/apps/client/src/widgets/note_type.ts index fe45fa233..4231ac979 100644 --- a/apps/client/src/widgets/note_type.ts +++ b/apps/client/src/widgets/note_type.ts @@ -11,14 +11,6 @@ import type FNote from "../entities/fnote.js"; const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type); -const TPL = /*html*/` - -`; - export default class NoteTypeWidget extends NoteContextAwareWidget { private dropdown!: Dropdown; @@ -47,93 +39,4 @@ export default class NoteTypeWidget extends NoteContextAwareWidget { this.dropdown.hide(); } - /** the actual body is rendered lazily on note-type button click */ - async renderDropdown() { - this.$noteTypeDropdown.empty(); - - if (!this.note) { - return; - } - - for (const noteType of ) { - let $typeLink: JQuery; - - if (noteType.type !== "code") { - $typeLink = $('
    ') - .attr("data-note-type", noteType.type) - .append(' ') - .append($title) - .on("click", (e) => { - const type = $typeLink.attr("data-note-type"); - const noteType = NOTE_TYPES.find((nt) => nt.type === type); - - if (noteType) { - this.save(noteType.type, noteType.mime); - } - }); - } else { - this.$noteTypeDropdown.append(''); - $typeLink = $('').attr("data-note-type", noteType.type).append(' ').append($("").text()); - } - - if (this.note.type === noteType.type) { - $typeLink.addClass("selected"); - } - - this.$noteTypeDropdown.append($typeLink); - } - - for (const mimeType of ) { - const $mimeLink = $('') - .attr("data-mime-type", mimeType.mime) - .append(' ') - .on("click", (e) => { - const $link = $(e.target).closest(".dropdown-item"); - - this.save("code", $link.attr("data-mime-type") ?? ""); - }); - - if (this.note.type === "code" && this.note.mime === mimeType.mime) { - $mimeLink.addClass("selected"); - - this.$noteTypeDesc.text(mimeType.title); - } - - this.$noteTypeDropdown.append($mimeLink); - } - } - - - - async save(type: NoteType, mime?: string) { - if (type === this.note?.type && mime === this.note?.mime) { - return; - } - - if (type !== this.note?.type && !(await this.confirmChangeIfContent())) { - return; - } - - await server.put(`notes/${this.noteId}/type`, { type, mime }); - } - - async confirmChangeIfContent() { - if (!this.note) { - return; - } - - const blob = await this.note.getBlob(); - - if (!blob?.content || !blob.content.trim().length) { - return true; - } - - return await dialogService.confirm(t("note_types.confirm-change")); - } - - async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.isNoteReloaded(this.noteId)) { - this.refresh(); - } - } } diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 01f16264f..ff0da091a 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -76,14 +76,21 @@ interface FormListItemOpts { active?: boolean; badges?: FormListBadge[]; disabled?: boolean; + checked?: boolean; + onClick?: () => void; } -export function FormListItem({ children, icon, value, title, active, badges, disabled }: FormListItemOpts) { +export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick }: FormListItemOpts) { + if (checked) { + icon = "bx bx-check"; + } + return (   {children} diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 7e18f49ba..dc16ba17b 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "preact/hooks"; +import { useCallback, useMemo } from "preact/hooks"; import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; import { FormDivider, FormListBadge, FormListItem } from "../react/FormList"; @@ -6,6 +6,8 @@ import { t } from "../../services/i18n"; import { useNoteContext, useNoteProperty, useTriliumOption } from "../react/hooks"; import mime_types from "../../services/mime_types"; import { NoteType } from "@triliumnext/commons"; +import server from "../../services/server"; +import dialog from "../../services/dialog"; export default function BasicPropertiesTab() { return ( @@ -21,53 +23,79 @@ function NoteTypeWidget() { const mimeTypes = useMemo(() => mime_types.getMimeTypes().filter(mimeType => mimeType.enabled), [ codeNotesMimeTypes ]); const { note } = useNoteContext(); - const type = useNoteProperty(note, "type") ?? undefined; - const mime = useNoteProperty(note, "mime"); + const currentNoteType = useNoteProperty(note, "type") ?? undefined; + const currentNoteMime = useNoteProperty(note, "mime"); + + const changeNoteType = useCallback(async (type: NoteType, mime?: string) => { + if (!note || (type === currentNoteType && mime === currentNoteMime)) { + return; + } + + // Confirm change if the note already has a content. + if (type !== currentNoteType) { + const blob = await note.getBlob(); + + if (blob?.content && blob.content.trim().length && + !await (dialog.confirm(t("note_types.confirm-change")))) { + return; + } + } + + await server.put(`notes/${note.noteId}/type`, { type, mime }); + }, [ note, currentNoteType, currentNoteMime ]); return ( - <> +
    {t("basic_properties.note_type")}:   {findTypeTitle(type, mime)}} + text={{findTypeTitle(currentNoteType, currentNoteMime)}} > - {noteTypes.map(noteType => { + {noteTypes.map(({ isNew, isBeta, type, mime, title }) => { const badges: FormListBadge[] = []; - if (noteType.isNew) { + if (isNew) { badges.push({ className: "new-note-type-badge", text: t("note_types.new-feature") }); } - if (noteType.isBeta) { + if (isBeta) { badges.push({ text: t("note_types.beta-feature") }); } - if (noteType.type !== "code") { + const checked = (type === currentNoteType); + if (type !== "code") { return ( {noteType.title} + onClick={() => changeNoteType(type, mime)} + >{title} ); } else { return ( <> - - {noteType.title} + + {title} ) } })} - {mimeTypes.map(mimeType => ( - {mimeType.title} + {mimeTypes.map(({ title, mime }) => ( + changeNoteType("code", mime)}> + {title} + ))} - +
    ) } diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts index e5f2ca8a2..1a59f3d48 100644 --- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts @@ -10,10 +10,6 @@ import type FNote from "../../entities/fnote.js"; import NoteLanguageWidget from "../note_language.js"; const TPL = /*html*/` -
    - -
    -
    From 5945f2860a3a36191812206d5367972893cb95c5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 21 Aug 2025 21:01:57 +0300 Subject: [PATCH 181/532] chore(react/ribbon): finalize note type selection --- apps/client/src/widgets/note_type.ts | 42 ------------------- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 4 +- packages/commons/src/lib/rows.ts | 3 +- 3 files changed, 5 insertions(+), 44 deletions(-) delete mode 100644 apps/client/src/widgets/note_type.ts diff --git a/apps/client/src/widgets/note_type.ts b/apps/client/src/widgets/note_type.ts deleted file mode 100644 index 4231ac979..000000000 --- a/apps/client/src/widgets/note_type.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Dropdown } from "bootstrap"; -import { NOTE_TYPES } from "../services/note_types.js"; -import { t } from "../services/i18n.js"; -import dialogService from "../services/dialog.js"; -import mimeTypesService from "../services/mime_types.js"; -import NoteContextAwareWidget from "./note_context_aware_widget.js"; -import server from "../services/server.js"; -import type { EventData } from "../components/app_context.js"; -import type { NoteType } from "../entities/fnote.js"; -import type FNote from "../entities/fnote.js"; - -const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type); - -export default class NoteTypeWidget extends NoteContextAwareWidget { - - private dropdown!: Dropdown; - private $noteTypeDropdown!: JQuery; - private $noteTypeButton!: JQuery; - private $noteTypeDesc!: JQuery; - - doRender() { - this.$widget = $(TPL); - - this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]); - - this.$widget.on("show.bs.dropdown", () => this.renderDropdown()); - - this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown"); - this.$noteTypeButton = this.$widget.find(".note-type-button"); - - this.$widget.on("click", ".dropdown-item", () => this.dropdown.toggle()); - } - - async refreshWithNote(note: FNote) { - this.$noteTypeButton.prop("disabled", () => NOT_SELECTABLE_NOTE_TYPES.includes(note.type)); - - this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime)); - - this.dropdown.hide(); - } - -} diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index dc16ba17b..fe10d1fde 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -21,6 +21,7 @@ function NoteTypeWidget() { const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes"); const mimeTypes = useMemo(() => mime_types.getMimeTypes().filter(mimeType => mimeType.enabled), [ codeNotesMimeTypes ]); + const notSelectableNoteTypes = useMemo(() => NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type), []); const { note } = useNoteContext(); const currentNoteType = useNoteProperty(note, "type") ?? undefined; @@ -50,6 +51,7 @@ function NoteTypeWidget() { {findTypeTitle(currentNoteType, currentNoteMime)}} + disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")} > {noteTypes.map(({ isNew, isBeta, type, mime, title }) => { const badges: FormListBadge[] = []; @@ -99,7 +101,7 @@ function NoteTypeWidget() { ) } -function findTypeTitle(type?: NoteType, mime?: string) { +function findTypeTitle(type?: NoteType, mime?: string | null) { if (type === "code") { const mimeTypes = mime_types.getMimeTypes(); const found = mimeTypes.find((mt) => mt.mime === mime); diff --git a/packages/commons/src/lib/rows.ts b/packages/commons/src/lib/rows.ts index 8bf64b275..9bb9d11d5 100644 --- a/packages/commons/src/lib/rows.ts +++ b/packages/commons/src/lib/rows.ts @@ -119,7 +119,8 @@ export const ALLOWED_NOTE_TYPES = [ "book", "webView", "code", - "mindMap" + "mindMap", + "aiChat" ] as const; export type NoteType = (typeof ALLOWED_NOTE_TYPES)[number]; From 1964fb90d59b55e3fcb059c322304c7a7a61d61f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 21 Aug 2025 21:24:01 +0300 Subject: [PATCH 182/532] feat(react/ribbon): port toggle protected note --- .../src/widgets/protected_note_switch.ts | 39 ----- apps/client/src/widgets/react/FormToggle.css | 98 ++++++++++++ apps/client/src/widgets/react/FormToggle.tsx | 47 ++++++ .../src/widgets/ribbon/BasicPropertiesTab.tsx | 28 +++- .../ribbon_widgets/basic_properties.ts | 4 - apps/client/src/widgets/switch.ts | 149 ------------------ 6 files changed, 169 insertions(+), 196 deletions(-) delete mode 100644 apps/client/src/widgets/protected_note_switch.ts create mode 100644 apps/client/src/widgets/react/FormToggle.css create mode 100644 apps/client/src/widgets/react/FormToggle.tsx diff --git a/apps/client/src/widgets/protected_note_switch.ts b/apps/client/src/widgets/protected_note_switch.ts deleted file mode 100644 index a65f4db31..000000000 --- a/apps/client/src/widgets/protected_note_switch.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { EventData } from "../components/app_context.js"; -import type FNote from "../entities/fnote.js"; -import { t } from "../services/i18n.js"; -import protectedSessionService from "../services/protected_session.js"; -import SwitchWidget from "./switch.js"; - -export default class ProtectedNoteSwitchWidget extends SwitchWidget { - doRender() { - super.doRender(); - - this.switchOnName = t("protect_note.toggle-on"); - this.switchOnTooltip = t("protect_note.toggle-on-hint"); - - this.switchOffName = t("protect_note.toggle-off"); - this.switchOffTooltip = t("protect_note.toggle-off-hint"); - } - - switchOn() { - if (this.noteId) { - protectedSessionService.protectNote(this.noteId, true, false); - } - } - - switchOff() { - if (this.noteId) { - protectedSessionService.protectNote(this.noteId, false, false); - } - } - - async refreshWithNote(note: FNote) { - this.isToggled = note.isProtected; - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.isNoteReloaded(this.noteId)) { - this.refresh(); - } - } -} diff --git a/apps/client/src/widgets/react/FormToggle.css b/apps/client/src/widgets/react/FormToggle.css new file mode 100644 index 000000000..f727bc5ff --- /dev/null +++ b/apps/client/src/widgets/react/FormToggle.css @@ -0,0 +1,98 @@ +.switch-widget { + --switch-track-width: 50px; + --switch-track-height: 24px; + --switch-off-track-background: var(--more-accented-background-color); + --switch-on-track-background: var(--main-text-color); + + --switch-thumb-width: 16px; + --switch-thumb-height: 16px; + --switch-off-thumb-background: var(--main-background-color); + --switch-on-thumb-background: var(--main-background-color); + + display: flex; + align-items: center; +} + +/* The track of the toggle switch */ + +.switch-widget .switch-button { + display: block; + position: relative; + margin-left: 8px; + width: var(--switch-track-width); + height: var(--switch-track-height); + border-radius: 24px; + background-color: var(--switch-off-track-background); + transition: background 200ms ease-in; +} + +.switch-widget .switch-button.on { + background: var(--switch-on-track-background); + transition: background 100ms ease-out; +} + +/* The thumb of the toggle switch */ + +.switch-widget .switch-button:after { + --y: calc((var(--switch-track-height) - var(--switch-thumb-height)) / 2); + --x: var(--y); + + content: ""; + position: absolute; + top: 0; + left: 0; + width: var(--switch-thumb-width); + height: var(--switch-thumb-height); + background-color: var(--switch-off-thumb-background); + border-radius: 50%; + transform: translate(var(--x), var(--y)); + transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1), + background 200ms ease-out; +} + +.switch-widget .switch-button.on:after { + --x: calc(var(--switch-track-width) - var(--switch-thumb-width) - var(--y)); + + background: var(--switch-on-thumb-background); + transition: transform 200ms cubic-bezier(0.64, 0, 0.78, 0), + background 100ms ease-in; +} + + +.switch-widget .switch-button input[type="checkbox"] { + /* A hidden check box for accesibility purposes */ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; +} + +/* Disabled state */ +.switch-widget .switch-button:not(.disabled) input[type="checkbox"], +.switch-widget .switch-button:not(.disabled) { + cursor: pointer; +} + +.switch-widget .switch-button:has(input[type="checkbox"]:focus-visible) { + outline: 2px solid var(--button-border-color); + outline-offset: 2px; +} + +.switch-widget .switch-button.disabled { + opacity: 70%; +} + +.switch-widget .switch-help-button { + border: 0; + margin-left: 4px; + background: none; + cursor: pointer; + font-size: 1.1em; + color: var(--muted-text-color); +} + +.switch-widget .switch-help-button:hover { + color: var(--main-text-color); +} \ No newline at end of file diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx new file mode 100644 index 000000000..a6dc2fe1d --- /dev/null +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -0,0 +1,47 @@ +import { t } from "../../services/i18n"; +import { openInAppHelpFromUrl } from "../../services/utils"; +import "./FormToggle.css"; + +interface FormToggleProps { + currentValue: boolean | null; + onChange(newValue: boolean): void; + switchOnName: string; + switchOnTooltip: string; + switchOffName: string; + switchOffTooltip: string; + helpPage?: string; +} + +export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange }: FormToggleProps) { + return ( +
    + { currentValue ? switchOffName : switchOnName } + + + + { helpPage && ( +
    + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index fe10d1fde..5c7ab1937 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -8,22 +8,27 @@ import mime_types from "../../services/mime_types"; import { NoteType } from "@triliumnext/commons"; import server from "../../services/server"; import dialog from "../../services/dialog"; +import FormToggle from "../react/FormToggle"; +import FNote from "../../entities/fnote"; +import protected_session from "../../services/protected_session"; export default function BasicPropertiesTab() { + const { note } = useNoteContext(); + return ( -
    - +
    + +
    ); } -function NoteTypeWidget() { +function NoteTypeWidget({ note }: { note?: FNote | null }) { const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []); const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes"); const mimeTypes = useMemo(() => mime_types.getMimeTypes().filter(mimeType => mimeType.enabled), [ codeNotesMimeTypes ]); const notSelectableNoteTypes = useMemo(() => NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type), []); - const { note } = useNoteContext(); const currentNoteType = useNoteProperty(note, "type") ?? undefined; const currentNoteMime = useNoteProperty(note, "mime"); @@ -101,6 +106,21 @@ function NoteTypeWidget() { ) } +function ProtectedNoteSwitch({ note }: { note?: FNote | null }) { + const isProtected = useNoteProperty(note, "isProtected"); + + return ( +
    + note && protected_session.protectNote(note.noteId, shouldProtect, false)} + switchOnName={t("protect_note.toggle-on")} switchOnTooltip={t("protect_note.toggle-on-hint")} + switchOffName={t("protect_note.toggle-off")} switchOffTooltip={t("protect_note.toggle-off-hint")} + /> +
    + ) +} + function findTypeTitle(type?: NoteType, mime?: string | null) { if (type === "code") { const mimeTypes = mime_types.getMimeTypes(); diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts index 1a59f3d48..14fb7468f 100644 --- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts @@ -10,8 +10,6 @@ import type FNote from "../../entities/fnote.js"; import NoteLanguageWidget from "../note_language.js"; const TPL = /*html*/` -
    -
    ${t("basic_properties.editable")}:  
    @@ -40,8 +38,6 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget { constructor() { super(); - this.noteTypeWidget = new NoteTypeWidget().contentSized(); - this.protectedNoteSwitchWidget = new ProtectedNoteSwitchWidget().contentSized(); this.editabilitySelectWidget = new EditabilitySelectWidget().contentSized(); this.bookmarkSwitchWidget = new BookmarkSwitchWidget().contentSized(); this.sharedSwitchWidget = new SharedSwitchWidget().contentSized(); diff --git a/apps/client/src/widgets/switch.ts b/apps/client/src/widgets/switch.ts index 166165fc1..8c521fb99 100644 --- a/apps/client/src/widgets/switch.ts +++ b/apps/client/src/widgets/switch.ts @@ -4,162 +4,22 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js"; const TPL = /*html*/`
    -
    - - - - - -
    -
    `; export default class SwitchWidget extends NoteContextAwareWidget { - private $switchButton!: JQuery; - private $switchToggle!: JQuery; - private $switchName!: JQuery; - protected $helpButton!: JQuery; - - protected switchOnName = ""; - protected switchOnTooltip = ""; - - protected switchOffName = ""; - protected switchOffTooltip = ""; - - protected disabledTooltip = ""; - - private currentState = false; - doRender() { this.$widget = $(TPL); this.$switchButton = this.$widget.find(".switch-button"); this.$switchToggle = this.$widget.find(".switch-toggle"); - this.$switchToggle.on("click", (e) => { - this.toggle(!this.currentState); - - // Prevent the check box from being toggled by the click, the value of the check box - // should be set exclusively by the 'isToggled' property setter. - e.preventDefault(); - }); - this.$switchName = this.$widget.find(".switch-name"); this.$helpButton = this.$widget.find(".switch-help-button"); } - toggle(state: boolean) { - if (state) { - this.switchOn(); - } else { - this.switchOff(); - } - } - switchOff() {} switchOn() {} @@ -172,15 +32,6 @@ export default class SwitchWidget extends NoteContextAwareWidget { this.currentState = !!state; this.$switchButton.toggleClass("on", this.currentState); - this.$switchToggle.prop("checked", this.currentState); - - if (this.currentState) { - this.$switchName.text(this.switchOffName); - this.$switchButton.attr("title", this.switchOffTooltip); - } else { - this.$switchName.text(this.switchOnName); - this.$switchButton.attr("title", this.switchOnTooltip); - } } /** Gets or sets whether the switch is enabled. */ From f772f59d7c5f6aac904d416603fbf21a2c384122 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 21 Aug 2025 22:19:26 +0300 Subject: [PATCH 183/532] feat(react/ribbon): port editability select --- apps/client/src/widgets/editability_select.ts | 120 ------------------ .../src/widgets/react/FormDropdownList.tsx | 30 +++++ apps/client/src/widgets/react/FormList.css | 5 + apps/client/src/widgets/react/FormList.tsx | 21 ++- apps/client/src/widgets/react/hooks.tsx | 28 +++- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 43 ++++++- .../ribbon_widgets/basic_properties.ts | 8 -- 7 files changed, 117 insertions(+), 138 deletions(-) delete mode 100644 apps/client/src/widgets/editability_select.ts create mode 100644 apps/client/src/widgets/react/FormDropdownList.tsx create mode 100644 apps/client/src/widgets/react/FormList.css diff --git a/apps/client/src/widgets/editability_select.ts b/apps/client/src/widgets/editability_select.ts deleted file mode 100644 index e7127ca8a..000000000 --- a/apps/client/src/widgets/editability_select.ts +++ /dev/null @@ -1,120 +0,0 @@ -import attributeService from "../services/attributes.js"; -import NoteContextAwareWidget from "./note_context_aware_widget.js"; -import { t } from "../services/i18n.js"; -import type FNote from "../entities/fnote.js"; -import type { EventData } from "../components/app_context.js"; -import { Dropdown } from "bootstrap"; - -type Editability = "auto" | "readOnly" | "autoReadOnlyDisabled"; - -const TPL = /*html*/` -
    -`; - -export default class EditabilitySelectWidget extends NoteContextAwareWidget { - - private dropdown!: Dropdown; - private $editabilityActiveDesc!: JQuery; - - doRender() { - this.$widget = $(TPL); - - this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]); - - this.$editabilityActiveDesc = this.$widget.find(".editability-active-desc"); - - this.$widget.on("click", ".dropdown-item", async (e) => { - this.dropdown.toggle(); - - const editability = $(e.target).closest("[data-editability]").attr("data-editability"); - - if (!this.note || !this.noteId) { - return; - } - - for (const ownedAttr of this.note.getOwnedLabels()) { - if (["readOnly", "autoReadOnlyDisabled"].includes(ownedAttr.name)) { - await attributeService.removeAttributeById(this.noteId, ownedAttr.attributeId); - } - } - - if (editability && editability !== "auto") { - await attributeService.addLabel(this.noteId, editability); - } - }); - } - - async refreshWithNote(note: FNote) { - let editability: Editability = "auto"; - - if (this.note?.isLabelTruthy("readOnly")) { - editability = "readOnly"; - } else if (this.note?.isLabelTruthy("autoReadOnlyDisabled")) { - editability = "autoReadOnlyDisabled"; - } - - const labels = { - auto: t("editability_select.auto"), - readOnly: t("editability_select.read_only"), - autoReadOnlyDisabled: t("editability_select.always_editable") - }; - - this.$widget.find(".dropdown-item").removeClass("selected"); - this.$widget.find(`.dropdown-item[data-editability='${editability}']`).addClass("selected"); - - this.$editabilityActiveDesc.text(labels[editability]); - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.getAttributeRows().find((attr) => attr.noteId === this.noteId)) { - this.refresh(); - } - } -} diff --git a/apps/client/src/widgets/react/FormDropdownList.tsx b/apps/client/src/widgets/react/FormDropdownList.tsx new file mode 100644 index 000000000..4bab06948 --- /dev/null +++ b/apps/client/src/widgets/react/FormDropdownList.tsx @@ -0,0 +1,30 @@ +import Dropdown from "./Dropdown"; +import { FormListItem } from "./FormList"; + +interface FormDropdownList { + values: T[]; + keyProperty: keyof T; + titleProperty: keyof T; + descriptionProperty?: keyof T; + currentValue: string; + onChange(newValue: string): void; +} + +export default function FormDropdownList({ values, keyProperty, titleProperty, descriptionProperty, currentValue, onChange }: FormDropdownList) { + const currentValueData = values.find(value => value[keyProperty] === currentValue); + + return ( + + {values.map(item => ( + onChange(item[keyProperty] as string)} + checked={currentValue === item[keyProperty]} + description={descriptionProperty && item[descriptionProperty] as string} + selected={currentValue === item[keyProperty]} + > + {item[titleProperty] as string} + + ))} + + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/react/FormList.css b/apps/client/src/widgets/react/FormList.css new file mode 100644 index 000000000..62631b131 --- /dev/null +++ b/apps/client/src/widgets/react/FormList.css @@ -0,0 +1,5 @@ +.dropdown-item .description { + font-size: small; + color: var(--muted-text-color); + white-space: normal; +} \ No newline at end of file diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index ff0da091a..42aeb93d5 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -2,6 +2,7 @@ import { Dropdown as BootstrapDropdown } from "bootstrap"; import { ComponentChildren } from "preact"; import Icon from "./Icon"; import { useEffect, useMemo, useRef, type CSSProperties } from "preact/compat"; +import "./FormList.css"; interface FormListOpts { children: ComponentChildren; @@ -76,27 +77,33 @@ interface FormListItemOpts { active?: boolean; badges?: FormListBadge[]; disabled?: boolean; - checked?: boolean; + checked?: boolean | null; + selected?: boolean; onClick?: () => void; + description?: string; + className?: string; } -export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick }: FormListItemOpts) { +export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick, description, selected }: FormListItemOpts) { if (checked) { icon = "bx bx-check"; } return (   - {children} - {badges && badges.map(({ className, text }) => ( - {text} - ))} +
    + {children} + {badges && badges.map(({ className, text }) => ( + {text} + ))} + {description &&
    {description}
    } +
    ); } diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index bc520dc0e..7c8257ead 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -314,12 +314,12 @@ export function useNoteProperty(note: FNote | null | unde } export function useNoteLabel(note: FNote | undefined | null, labelName: string): [string | null | undefined, (newValue: string) => void] { - const [ labelValue, setNoteValue ] = useState(note?.getLabelValue(labelName)); + const [ labelValue, setLabelValue ] = useState(note?.getLabelValue(labelName)); useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { for (const attr of loadResults.getAttributeRows()) { if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) { - setNoteValue(attr.value ?? null); + setLabelValue(attr.value ?? null); } } }); @@ -334,4 +334,28 @@ export function useNoteLabel(note: FNote | undefined | null, labelName: string): labelValue, setter ] as const; +} + +export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: string): [ boolean | null | undefined, (newValue: boolean) => void] { + const [ labelValue, setLabelValue ] = useState(note?.hasLabel(labelName)); + + useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + for (const attr of loadResults.getAttributeRows()) { + if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) { + setLabelValue(!attr.isDeleted); + } + } + }); + + const setter = useCallback((value: boolean) => { + if (note) { + if (value) { + attributes.setLabel(note.noteId, labelName, ""); + } else { + attributes.removeOwnedLabelByName(note, labelName); + } + } + }, [note]); + + return [ labelValue, setter ] as const; } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 5c7ab1937..84de1b425 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -3,7 +3,7 @@ import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; import { FormDivider, FormListBadge, FormListItem } from "../react/FormList"; import { t } from "../../services/i18n"; -import { useNoteContext, useNoteProperty, useTriliumOption } from "../react/hooks"; +import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import mime_types from "../../services/mime_types"; import { NoteType } from "@triliumnext/commons"; import server from "../../services/server"; @@ -11,6 +11,7 @@ import dialog from "../../services/dialog"; import FormToggle from "../react/FormToggle"; import FNote from "../../entities/fnote"; import protected_session from "../../services/protected_session"; +import FormDropdownList from "../react/FormDropdownList"; export default function BasicPropertiesTab() { const { note } = useNoteContext(); @@ -19,6 +20,7 @@ export default function BasicPropertiesTab() {
    +
    ); } @@ -121,6 +123,45 @@ function ProtectedNoteSwitch({ note }: { note?: FNote | null }) { ) } +function EditabilitySelect({ note }: { note?: FNote | null }) { + const [ readOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); + const [ autoReadOnlyDisabled, setAutoReadOnlyDisabled ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled"); + + const options = useMemo(() => ([ + { + value: "auto", + label: t("editability_select.auto"), + description: t("editability_select.note_is_editable"), + }, + { + value: "readOnly", + label: t("editability_select.read_only"), + description: t("editability_select.note_is_read_only") + }, + { + value: "autoReadOnlyDisabled", + label: t("editability_select.always_editable"), + description: t("editability_select.note_is_always_editable") + } + ]), []); + + return ( +
    + {t("basic_properties.editable")}:   + + { + setReadOnly(editability === "readOnly"); + setAutoReadOnlyDisabled(editability === "autoReadOnlyDisabled"); + }} + /> +
    + ) +} + function findTypeTitle(type?: NoteType, mime?: string | null) { if (type === "code") { const mimeTypes = mime_types.getMimeTypes(); diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts index 14fb7468f..f80e163a8 100644 --- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts @@ -10,10 +10,6 @@ import type FNote from "../../entities/fnote.js"; import NoteLanguageWidget from "../note_language.js"; const TPL = /*html*/` -
    - ${t("basic_properties.editable")}:   -
    -
    @@ -27,8 +23,6 @@ const TPL = /*html*/` export default class BasicPropertiesWidget extends NoteContextAwareWidget { - private noteTypeWidget: NoteTypeWidget; - private protectedNoteSwitchWidget: ProtectedNoteSwitchWidget; private editabilitySelectWidget: EditabilitySelectWidget; private bookmarkSwitchWidget: BookmarkSwitchWidget; private sharedSwitchWidget: SharedSwitchWidget; @@ -45,8 +39,6 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget { this.noteLanguageWidget = new NoteLanguageWidget().contentSized(); this.child( - this.noteTypeWidget, - this.protectedNoteSwitchWidget, this.editabilitySelectWidget, this.bookmarkSwitchWidget, this.sharedSwitchWidget, From da4810672d2f302e8311ab7bc9c964b4e621813f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 21 Aug 2025 22:24:35 +0300 Subject: [PATCH 184/532] feat(react/ribbon): improve editability select --- apps/client/src/widgets/react/Dropdown.tsx | 2 +- apps/client/src/widgets/react/FormDropdownList.tsx | 8 ++++---- apps/client/src/widgets/react/FormList.css | 4 ++++ apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx | 1 + apps/client/src/widgets/ribbon/style.css | 4 ++++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/react/Dropdown.tsx b/apps/client/src/widgets/react/Dropdown.tsx index 2578aec31..025192d74 100644 --- a/apps/client/src/widgets/react/Dropdown.tsx +++ b/apps/client/src/widgets/react/Dropdown.tsx @@ -4,7 +4,7 @@ import { CSSProperties } from "preact/compat"; import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import { useUniqueName } from "./hooks"; -interface DropdownProps { +export interface DropdownProps { className?: string; buttonClassName?: string; isStatic?: boolean; diff --git a/apps/client/src/widgets/react/FormDropdownList.tsx b/apps/client/src/widgets/react/FormDropdownList.tsx index 4bab06948..08d607a8c 100644 --- a/apps/client/src/widgets/react/FormDropdownList.tsx +++ b/apps/client/src/widgets/react/FormDropdownList.tsx @@ -1,7 +1,7 @@ -import Dropdown from "./Dropdown"; +import Dropdown, { DropdownProps } from "./Dropdown"; import { FormListItem } from "./FormList"; -interface FormDropdownList { +interface FormDropdownList extends Omit { values: T[]; keyProperty: keyof T; titleProperty: keyof T; @@ -10,11 +10,11 @@ interface FormDropdownList { onChange(newValue: string): void; } -export default function FormDropdownList({ values, keyProperty, titleProperty, descriptionProperty, currentValue, onChange }: FormDropdownList) { +export default function FormDropdownList({ values, keyProperty, titleProperty, descriptionProperty, currentValue, onChange, ...restProps }: FormDropdownList) { const currentValueData = values.find(value => value[keyProperty] === currentValue); return ( - + {values.map(item => ( onChange(item[keyProperty] as string)} diff --git a/apps/client/src/widgets/react/FormList.css b/apps/client/src/widgets/react/FormList.css index 62631b131..ae5c3340f 100644 --- a/apps/client/src/widgets/react/FormList.css +++ b/apps/client/src/widgets/react/FormList.css @@ -2,4 +2,8 @@ font-size: small; color: var(--muted-text-color); white-space: normal; +} + +.dropdown-item span.bx { + flex-shrink: 0; } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 84de1b425..733c2dbe2 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -150,6 +150,7 @@ function EditabilitySelect({ note }: { note?: FNote | null }) { {t("basic_properties.editable")}:   Date: Fri, 22 Aug 2025 02:43:31 +0000 Subject: [PATCH 185/532] chore(deps): update ckeditor5 config packages to v12.1.1 --- pnpm-lock.yaml | 232 ++++++++----------------------------------------- 1 file changed, 35 insertions(+), 197 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c717ce7c1..ca758744c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -915,7 +915,7 @@ importers: version: 9.33.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -927,7 +927,7 @@ importers: version: 16.23.1(typescript@5.9.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(stylelint@16.23.1(typescript@5.9.2)) + version: 12.1.1(stylelint@16.23.1(typescript@5.9.2)) ts-node: specifier: ^10.9.1 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2) @@ -975,7 +975,7 @@ importers: version: 9.33.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -987,7 +987,7 @@ importers: version: 16.23.1(typescript@5.9.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(stylelint@16.23.1(typescript@5.9.2)) + version: 12.1.1(stylelint@16.23.1(typescript@5.9.2)) ts-node: specifier: ^10.9.1 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2) @@ -1035,7 +1035,7 @@ importers: version: 9.33.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -1047,7 +1047,7 @@ importers: version: 16.23.1(typescript@5.9.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(stylelint@16.23.1(typescript@5.9.2)) + version: 12.1.1(stylelint@16.23.1(typescript@5.9.2)) ts-node: specifier: ^10.9.1 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2) @@ -1102,7 +1102,7 @@ importers: version: 9.33.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -1114,7 +1114,7 @@ importers: version: 16.23.1(typescript@5.9.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(stylelint@16.23.1(typescript@5.9.2)) + version: 12.1.1(stylelint@16.23.1(typescript@5.9.2)) ts-node: specifier: ^10.9.1 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2) @@ -1169,7 +1169,7 @@ importers: version: 9.33.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -1181,7 +1181,7 @@ importers: version: 16.23.1(typescript@5.9.2) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.0(stylelint@16.23.1(typescript@5.9.2)) + version: 12.1.1(stylelint@16.23.1(typescript@5.9.2)) ts-node: specifier: ^10.9.1 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2) @@ -3020,10 +3020,6 @@ packages: resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.4': - resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.5': resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5968,14 +5964,6 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.38.0': - resolution: {integrity: sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.38.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/eslint-plugin@8.40.0': resolution: {integrity: sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5984,13 +5972,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.38.0': - resolution: {integrity: sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.40.0': resolution: {integrity: sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6004,12 +5985,6 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/project-service@8.39.1': - resolution: {integrity: sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.40.0': resolution: {integrity: sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6020,10 +5995,6 @@ packages: resolution: {integrity: sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.39.1': - resolution: {integrity: sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.40.0': resolution: {integrity: sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6034,12 +6005,6 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/tsconfig-utils@8.39.1': - resolution: {integrity: sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/tsconfig-utils@8.40.0': resolution: {integrity: sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6064,10 +6029,6 @@ packages: resolution: {integrity: sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.39.1': - resolution: {integrity: sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.40.0': resolution: {integrity: sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6078,12 +6039,6 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/typescript-estree@8.39.1': - resolution: {integrity: sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/typescript-estree@8.40.0': resolution: {integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6097,13 +6052,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.39.1': - resolution: {integrity: sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.40.0': resolution: {integrity: sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6115,10 +6063,6 @@ packages: resolution: {integrity: sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.39.1': - resolution: {integrity: sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.40.0': resolution: {integrity: sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -8562,8 +8506,8 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-config-ckeditor5@12.1.0: - resolution: {integrity: sha512-lbmgyvrIEIm7MhzlFkBkegeMPhpiJzJCHw5ZNu8zhxOxVisSron0+4+enjy2CSEXmsolWk4tl4vpB5dSwID7Fg==} + eslint-config-ckeditor5@12.1.1: + resolution: {integrity: sha512-08GaAQihDvGeTL5XVx/Ihgin11+3bVPaWi+xKzh3ewkcM5HrC9PF06WltBGFsxSJmBRmg8VFSLMwAXcuyZDq3Q==} peerDependencies: eslint: ^9.0.0 typescript: ^5.0.0 @@ -8577,8 +8521,8 @@ packages: eslint-linter-browserify@9.33.0: resolution: {integrity: sha512-ZWB7DriwQ8/zQpt4m+R5ys33bJhdKaccl6+chxJ7PD+oWAA3jtBzG+pJHBUiK7rWkqinSHxpSjJb69jbMt6hug==} - eslint-plugin-ckeditor5-rules@12.1.0: - resolution: {integrity: sha512-tu8xYJkQ9CZzS+cDsBvnaKdADJVNzGNl/4a8i7A538y/YifyAiSMCSQOeefx5Ej27bBL+Wo9WlgeuLmjiyTP+A==} + eslint-plugin-ckeditor5-rules@12.1.1: + resolution: {integrity: sha512-e0PhbA3sNWy4Djs6r+kVfWNvu6urJXucIUfqI2GKjgOfqYOhmpLNaudH6FHKAg/OM8g0ETb7TbG3Bc375ru+sg==} eslint-plugin-mocha@11.1.0: resolution: {integrity: sha512-rKntVWRsQFPbf8OkSgVNRVRrcVAPaGTyEgWCEyXaPDJkTl0v5/lwu1vTk5sWiUJU8l2sxwvGUZzSNrEKdVMeQw==} @@ -14035,8 +13979,8 @@ packages: peerDependencies: postcss: ^8.4.32 - stylelint-config-ckeditor5@12.1.0: - resolution: {integrity: sha512-6JDfp60U2XNba8xmXl4K4R5ix+YM/0ZoWUbdIhVl+WNVVJG0P21jRzQjIb1QbTVE8xjS3kdc9S7kErY3ch5zgw==} + stylelint-config-ckeditor5@12.1.1: + resolution: {integrity: sha512-bEqemEiQ8Y4emTKAlOsN4cOx/lk61pVhFwegBDewlS8OqbJ6QsWXBPccSWQCETN5DUUoqoOcZYKsfsH/IsrNEg==} peerDependencies: stylelint: '>=16.0.0' @@ -14056,8 +14000,8 @@ packages: peerDependencies: stylelint: '>=10.1.0' - stylelint-plugin-ckeditor5-rules@12.1.0: - resolution: {integrity: sha512-RKTrDvmVOH4vb1oLjcuIql7sq/JzbFCmkd6St5S9eDo8mvquzbjIi0fq9DT+oAhdb3dM4G+eNpniF12L68VLmw==} + stylelint-plugin-ckeditor5-rules@12.1.1: + resolution: {integrity: sha512-vS7uJta2itqoGo6hXgitclZH0ucwikbXP30y0I7GLhppUmYPqQhPvDBX21RuC9imbEYuIkTcaQzggU/DjSTBsA==} peerDependencies: stylelint: '>=16.0.0' @@ -14541,13 +14485,6 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.38.0: - resolution: {integrity: sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - typescript-eslint@8.40.0: resolution: {integrity: sha512-Xvd2l+ZmFDPEt4oj1QEXzA4A2uUK6opvKu3eGN9aGjB8au02lIVcLyi375w94hHyejTOmzIU77L8ol2sRg9n7Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -15447,8 +15384,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yoctocolors-cjs@2.1.2: - resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} z-schema@5.0.5: @@ -16866,6 +16803,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-bookmark@46.0.2': dependencies: @@ -17677,8 +17616,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-restricted-editing@46.0.2': dependencies: @@ -17875,8 +17812,6 @@ snapshots: '@ckeditor/ckeditor5-icons': 46.0.2 '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-upload@46.0.2': dependencies: @@ -18669,7 +18604,7 @@ snapshots: '@eslint/markdown@6.6.0': dependencies: '@eslint/core': 0.14.0 - '@eslint/plugin-kit': 0.3.4 + '@eslint/plugin-kit': 0.3.5 github-slugger: 2.0.0 mdast-util-from-markdown: 2.0.2 mdast-util-frontmatter: 2.0.1 @@ -18681,11 +18616,6 @@ snapshots: '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.3.4': - dependencies: - '@eslint/core': 0.15.2 - levn: 0.4.1 - '@eslint/plugin-kit@0.3.5': dependencies: '@eslint/core': 0.15.2 @@ -19043,7 +18973,7 @@ snapshots: mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 + yoctocolors-cjs: 2.1.3 optionalDependencies: '@types/node': 22.17.2 optional: true @@ -21293,7 +21223,7 @@ snapshots: '@stylistic/eslint-plugin@4.4.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@typescript-eslint/utils': 8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.33.0(jiti@2.5.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -22169,23 +22099,6 @@ snapshots: '@types/node': 22.17.2 optional: true - '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/type-utils': 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.38.0 - eslint: 9.33.0(jiti@2.5.1) - graphemer: 1.4.0 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -22203,18 +22116,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.38.0 - debug: 4.4.1(supports-color@6.0.0) - eslint: 9.33.0(jiti@2.5.1) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 8.40.0 @@ -22236,15 +22137,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.39.1(typescript@5.9.2)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.39.1(typescript@5.9.2) - '@typescript-eslint/types': 8.39.1 - debug: 4.4.1(supports-color@6.0.0) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/project-service@8.40.0(typescript@5.9.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.40.0(typescript@5.9.2) @@ -22259,11 +22151,6 @@ snapshots: '@typescript-eslint/types': 8.38.0 '@typescript-eslint/visitor-keys': 8.38.0 - '@typescript-eslint/scope-manager@8.39.1': - dependencies: - '@typescript-eslint/types': 8.39.1 - '@typescript-eslint/visitor-keys': 8.39.1 - '@typescript-eslint/scope-manager@8.40.0': dependencies: '@typescript-eslint/types': 8.40.0 @@ -22273,10 +22160,6 @@ snapshots: dependencies: typescript: 5.9.2 - '@typescript-eslint/tsconfig-utils@8.39.1(typescript@5.9.2)': - dependencies: - typescript: 5.9.2 - '@typescript-eslint/tsconfig-utils@8.40.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 @@ -22307,8 +22190,6 @@ snapshots: '@typescript-eslint/types@8.38.0': {} - '@typescript-eslint/types@8.39.1': {} - '@typescript-eslint/types@8.40.0': {} '@typescript-eslint/typescript-estree@8.38.0(typescript@5.9.2)': @@ -22327,22 +22208,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.39.1(typescript@5.9.2)': - dependencies: - '@typescript-eslint/project-service': 8.39.1(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.39.1(typescript@5.9.2) - '@typescript-eslint/types': 8.39.1 - '@typescript-eslint/visitor-keys': 8.39.1 - debug: 4.4.1(supports-color@6.0.0) - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/typescript-estree@8.40.0(typescript@5.9.2)': dependencies: '@typescript-eslint/project-service': 8.40.0(typescript@5.9.2) @@ -22370,17 +22235,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) - '@typescript-eslint/scope-manager': 8.39.1 - '@typescript-eslint/types': 8.39.1 - '@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.2) - eslint: 9.33.0(jiti@2.5.1) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) @@ -22397,11 +22251,6 @@ snapshots: '@typescript-eslint/types': 8.38.0 eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.39.1': - dependencies: - '@typescript-eslint/types': 8.39.1 - eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.40.0': dependencies: '@typescript-eslint/types': 8.40.0 @@ -25406,17 +25255,17 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-ckeditor5@12.1.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): + eslint-config-ckeditor5@12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): dependencies: '@eslint/js': 9.33.0 '@eslint/markdown': 6.6.0 '@stylistic/eslint-plugin': 4.4.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.33.0(jiti@2.5.1) - eslint-plugin-ckeditor5-rules: 12.1.0 + eslint-plugin-ckeditor5-rules: 12.1.1 eslint-plugin-mocha: 11.1.0(eslint@9.33.0(jiti@2.5.1)) globals: 16.3.0 typescript: 5.9.2 - typescript-eslint: 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + typescript-eslint: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) transitivePeerDependencies: - supports-color @@ -25426,15 +25275,15 @@ snapshots: eslint-linter-browserify@9.33.0: {} - eslint-plugin-ckeditor5-rules@12.1.0: + eslint-plugin-ckeditor5-rules@12.1.1: dependencies: '@es-joy/jsdoccomment': 0.50.2 - enhanced-resolve: 5.18.2 + enhanced-resolve: 5.18.3 fs-extra: 11.3.1 resolve.exports: 2.0.3 upath: 2.0.1 validate-npm-package-name: 6.0.2 - yaml: 2.8.0 + yaml: 2.8.1 eslint-plugin-mocha@11.1.0(eslint@9.33.0(jiti@2.5.1)): dependencies: @@ -31986,12 +31835,12 @@ snapshots: postcss: 8.5.6 postcss-selector-parser: 7.1.0 - stylelint-config-ckeditor5@12.1.0(stylelint@16.23.1(typescript@5.9.2)): + stylelint-config-ckeditor5@12.1.1(stylelint@16.23.1(typescript@5.9.2)): dependencies: '@stylistic/stylelint-plugin': 3.1.3(stylelint@16.23.1(typescript@5.9.2)) stylelint: 16.23.1(typescript@5.9.2) stylelint-config-recommended: 16.0.0(stylelint@16.23.1(typescript@5.9.2)) - stylelint-plugin-ckeditor5-rules: 12.1.0(stylelint@16.23.1(typescript@5.9.2)) + stylelint-plugin-ckeditor5-rules: 12.1.1(stylelint@16.23.1(typescript@5.9.2)) stylelint-config-ckeditor5@2.0.1(stylelint@16.23.1(typescript@5.9.2)): dependencies: @@ -32006,7 +31855,7 @@ snapshots: dependencies: stylelint: 16.23.1(typescript@5.9.2) - stylelint-plugin-ckeditor5-rules@12.1.0(stylelint@16.23.1(typescript@5.9.2)): + stylelint-plugin-ckeditor5-rules@12.1.1(stylelint@16.23.1(typescript@5.9.2)): dependencies: stylelint: 16.23.1(typescript@5.9.2) @@ -32739,17 +32588,6 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): - dependencies: - '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/parser': 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.33.0(jiti@2.5.1) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - typescript-eslint@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): dependencies: '@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) @@ -33770,7 +33608,7 @@ snapshots: yocto-queue@0.1.0: {} - yoctocolors-cjs@2.1.2: + yoctocolors-cjs@2.1.3: optional: true z-schema@5.0.5: From aa191e110cc4920f88844a4d28d0e7b6eca74e5d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 02:44:08 +0000 Subject: [PATCH 186/532] fix(deps): update dependency react-i18next to v15.7.1 --- apps/client/package.json | 2 +- pnpm-lock.yaml | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index cc72a6366..d6267a0b0 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -52,7 +52,7 @@ "normalize.css": "8.0.1", "panzoom": "9.4.3", "preact": "10.27.1", - "react-i18next": "15.7.0", + "react-i18next": "15.7.1", "split.js": "1.6.5", "svg-pan-zoom": "3.6.2", "tabulator-tables": "6.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c717ce7c1..4ea8a187f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -286,8 +286,8 @@ importers: specifier: 10.27.1 version: 10.27.1 react-i18next: - specifier: 15.7.0 - version: 15.7.0(i18next@25.4.0(typescript@5.9.2))(react-dom@19.1.0(react@16.14.0))(react@16.14.0)(typescript@5.9.2) + specifier: 15.7.1 + version: 15.7.1(i18next@25.4.0(typescript@5.9.2))(react-dom@19.1.0(react@16.14.0))(react@16.14.0)(typescript@5.9.2) split.js: specifier: 1.6.5 version: 1.6.5 @@ -12884,10 +12884,10 @@ packages: peerDependencies: react: ^19.1.0 - react-i18next@15.7.0: - resolution: {integrity: sha512-hogS6K+7hJnGN1k5hpxcY0x3SnQ30K2Cj9PMKSwP8lqyhfbj7DEdjFhc7tXh9+z+npDQaxvPCGnpkRmCnRNCcQ==} + react-i18next@15.7.1: + resolution: {integrity: sha512-o4VsKh30fy7p0z5ACHuyWqB6xu9WpQIQy2/ZcbCqopNnrnTVOPn/nAv9uYP4xYAWg99QMpvZ9Bu/si3eGurzGw==} peerDependencies: - i18next: '>= 23.2.3' + i18next: '>= 23.4.0' react: '>= 16.8.0' react-dom: '*' react-native: '*' @@ -16866,6 +16866,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-bookmark@46.0.2': dependencies: @@ -16928,6 +16930,8 @@ snapshots: '@ckeditor/ckeditor5-core': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-code-block@46.0.2(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -17153,6 +17157,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-classic@46.0.2': dependencies: @@ -17162,6 +17168,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-decoupled@46.0.2': dependencies: @@ -17875,8 +17883,6 @@ snapshots: '@ckeditor/ckeditor5-icons': 46.0.2 '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-upload@46.0.2': dependencies: @@ -30622,7 +30628,7 @@ snapshots: react: 16.14.0 scheduler: 0.26.0 - react-i18next@15.7.0(i18next@25.4.0(typescript@5.9.2))(react-dom@19.1.0(react@16.14.0))(react@16.14.0)(typescript@5.9.2): + react-i18next@15.7.1(i18next@25.4.0(typescript@5.9.2))(react-dom@19.1.0(react@16.14.0))(react@16.14.0)(typescript@5.9.2): dependencies: '@babel/runtime': 7.27.6 html-parse-stringify: 3.0.1 From f9b6fd6ac5fa3f59dee340da9363873ff0e9e986 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 11:24:27 +0300 Subject: [PATCH 187/532] feat(react): port bookmark switch --- apps/client/src/widgets/bookmark_switch.ts | 54 ------------------- apps/client/src/widgets/react/FormToggle.tsx | 8 +-- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 46 ++++++++++++++-- .../ribbon_widgets/basic_properties.ts | 8 --- apps/client/src/widgets/switch.ts | 3 -- packages/commons/src/lib/server_api.ts | 7 +++ 6 files changed, 53 insertions(+), 73 deletions(-) delete mode 100644 apps/client/src/widgets/bookmark_switch.ts diff --git a/apps/client/src/widgets/bookmark_switch.ts b/apps/client/src/widgets/bookmark_switch.ts deleted file mode 100644 index 93d4789aa..000000000 --- a/apps/client/src/widgets/bookmark_switch.ts +++ /dev/null @@ -1,54 +0,0 @@ -import SwitchWidget from "./switch.js"; -import server from "../services/server.js"; -import toastService from "../services/toast.js"; -import { t } from "../services/i18n.js"; -import type FNote from "../entities/fnote.js"; -import type { EventData } from "../components/app_context.js"; - -// TODO: Deduplicate -type Response = { - success: true; -} | { - success: false; - message: string; -} - -export default class BookmarkSwitchWidget extends SwitchWidget { - isEnabled() { - return ( - super.isEnabled() && - // it's not possible to bookmark root because that would clone it under bookmarks and thus create a cycle - !["root", "_hidden"].includes(this.noteId ?? "") - ); - } - - doRender() { - super.doRender(); - - this.switchOnName = t("bookmark_switch.bookmark"); - this.switchOnTooltip = t("bookmark_switch.bookmark_this_note"); - - this.switchOffName = t("bookmark_switch.bookmark"); - this.switchOffTooltip = t("bookmark_switch.remove_bookmark"); - } - - async toggle(state: boolean | null | undefined) { - const resp = await server.put(`notes/${this.noteId}/toggle-in-parent/_lbBookmarks/${!!state}`); - - if (!resp.success && "message" in resp) { - toastService.showError(resp.message); - } - } - - async refreshWithNote(note: FNote) { - const isBookmarked = !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks"); - - this.isToggled = isBookmarked; - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.getBranchRows().find((b) => b.noteId === this.noteId)) { - this.refresh(); - } - } -} diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx index a6dc2fe1d..c41ea6750 100644 --- a/apps/client/src/widgets/react/FormToggle.tsx +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -10,16 +10,17 @@ interface FormToggleProps { switchOffName: string; switchOffTooltip: string; helpPage?: string; + disabled?: boolean; } -export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange }: FormToggleProps) { +export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled }: FormToggleProps) { return (
    { currentValue ? switchOffName : switchOnName } diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 733c2dbe2..918d01760 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -1,17 +1,18 @@ -import { useCallback, useMemo } from "preact/hooks"; +import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; import { FormDivider, FormListBadge, FormListItem } from "../react/FormList"; import { t } from "../../services/i18n"; -import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; +import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEventBeta, useTriliumOption } from "../react/hooks"; import mime_types from "../../services/mime_types"; -import { NoteType } from "@triliumnext/commons"; +import { NoteType, ToggleInParentResponse } from "@triliumnext/commons"; import server from "../../services/server"; import dialog from "../../services/dialog"; import FormToggle from "../react/FormToggle"; import FNote from "../../entities/fnote"; import protected_session from "../../services/protected_session"; import FormDropdownList from "../react/FormDropdownList"; +import toast from "../../services/toast"; export default function BasicPropertiesTab() { const { note } = useNoteContext(); @@ -21,6 +22,7 @@ export default function BasicPropertiesTab() { +
    ); } @@ -114,10 +116,10 @@ function ProtectedNoteSwitch({ note }: { note?: FNote | null }) { return (
    note && protected_session.protectNote(note.noteId, shouldProtect, false)} switchOnName={t("protect_note.toggle-on")} switchOnTooltip={t("protect_note.toggle-on-hint")} switchOffName={t("protect_note.toggle-off")} switchOffTooltip={t("protect_note.toggle-off-hint")} + currentValue={isProtected} + onChange={(shouldProtect) => note && protected_session.protectNote(note.noteId, shouldProtect, false)} />
    ) @@ -163,6 +165,40 @@ function EditabilitySelect({ note }: { note?: FNote | null }) { ) } +function BookmarkSwitch({ note }: { note?: FNote | null }) { + const [ isBookmarked, setIsBookmarked ] = useState(false); + const refreshState = useCallback(() => { + const isBookmarked = note && !!note.getParentBranches().find((b) => b.parentNoteId === "_lbBookmarks"); + setIsBookmarked(!!isBookmarked); + }, [ note ]); + + useEffect(() => refreshState(), [ note ]); + useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { + refreshState(); + } + }); + + return ( +
    + { + if (!note) return; + const resp = await server.put(`notes/${note.noteId}/toggle-in-parent/_lbBookmarks/${shouldBookmark}`); + + if (!resp.success && "message" in resp) { + toast.showError(resp.message); + } + }} + disabled={["root", "_hidden"].includes(note?.noteId ?? "")} + /> +
    + ) +} + function findTypeTitle(type?: NoteType, mime?: string | null) { if (type === "code") { const mimeTypes = mime_types.getMimeTypes(); diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts index f80e163a8..e4127e0bc 100644 --- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts @@ -10,8 +10,6 @@ import type FNote from "../../entities/fnote.js"; import NoteLanguageWidget from "../note_language.js"; const TPL = /*html*/` -
    -
    @@ -23,8 +21,6 @@ const TPL = /*html*/` export default class BasicPropertiesWidget extends NoteContextAwareWidget { - private editabilitySelectWidget: EditabilitySelectWidget; - private bookmarkSwitchWidget: BookmarkSwitchWidget; private sharedSwitchWidget: SharedSwitchWidget; private templateSwitchWidget: TemplateSwitchWidget; private noteLanguageWidget: NoteLanguageWidget; @@ -32,15 +28,11 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget { constructor() { super(); - this.editabilitySelectWidget = new EditabilitySelectWidget().contentSized(); - this.bookmarkSwitchWidget = new BookmarkSwitchWidget().contentSized(); this.sharedSwitchWidget = new SharedSwitchWidget().contentSized(); this.templateSwitchWidget = new TemplateSwitchWidget().contentSized(); this.noteLanguageWidget = new NoteLanguageWidget().contentSized(); this.child( - this.editabilitySelectWidget, - this.bookmarkSwitchWidget, this.sharedSwitchWidget, this.templateSwitchWidget, this.noteLanguageWidget); diff --git a/apps/client/src/widgets/switch.ts b/apps/client/src/widgets/switch.ts index 8c521fb99..1f5956a6e 100644 --- a/apps/client/src/widgets/switch.ts +++ b/apps/client/src/widgets/switch.ts @@ -40,9 +40,6 @@ export default class SwitchWidget extends NoteContextAwareWidget { } set canToggle(isEnabled) { - this.$switchButton.toggleClass("disabled", !isEnabled); - this.$switchToggle.attr("disabled", !isEnabled ? "disabled" : null); - if (isEnabled) { this.isToggled = this.currentState; // Reapply the correct tooltip } else { diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index e660e049c..311e45fb0 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -155,3 +155,10 @@ export interface OpenAiOrAnthropicModelResponse { type: string; }>; } + +export type ToggleInParentResponse = { + success: true; +} | { + success: false; + message: string; +} From e873cdab7e728fa1757da6f250974432bf61e819 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 11:42:07 +0300 Subject: [PATCH 188/532] chore(react/ribbon): fix width of editability select --- apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 918d01760..2b10599bd 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -152,7 +152,7 @@ function EditabilitySelect({ note }: { note?: FNote | null }) { {t("basic_properties.editable")}:   Date: Fri, 22 Aug 2025 11:57:45 +0300 Subject: [PATCH 189/532] feat(react/ribbon): port shared switch --- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 48 +++++++++++ .../ribbon_widgets/basic_properties.ts | 3 - apps/client/src/widgets/shared_switch.ts | 81 ------------------- 3 files changed, 48 insertions(+), 84 deletions(-) delete mode 100644 apps/client/src/widgets/shared_switch.ts diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 2b10599bd..33f28a277 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -13,6 +13,8 @@ import FNote from "../../entities/fnote"; import protected_session from "../../services/protected_session"; import FormDropdownList from "../react/FormDropdownList"; import toast from "../../services/toast"; +import branches from "../../services/branches"; +import sync from "../../services/sync"; export default function BasicPropertiesTab() { const { note } = useNoteContext(); @@ -23,6 +25,7 @@ export default function BasicPropertiesTab() { +
    ); } @@ -199,6 +202,51 @@ function BookmarkSwitch({ note }: { note?: FNote | null }) { ) } +function SharedSwitch({ note }: { note?: FNote | null }) { + const [ isShared, setIsShared ] = useState(false); + const refreshState = useCallback(() => { + setIsShared(!!note?.hasAncestor("_share")); + }, [ note ]); + + useEffect(() => refreshState(), [ note ]); + useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + if (note && loadResults.getBranchRows().find((b) => b.noteId === note.noteId)) { + refreshState(); + } + }); + + const switchShareState = useCallback(async (shouldShare: boolean) => { + if (!note) return; + + if (shouldShare) { + await branches.cloneNoteToParentNote(note.noteId, "_share"); + } else { + if (note?.getParentBranches().length === 1 && !(await dialog.confirm(t("shared_switch.shared-branch")))) { + return; + } + + const shareBranch = note?.getParentBranches().find((b) => b.parentNoteId === "_share"); + if (!shareBranch?.branchId) return; + await server.remove(`branches/${shareBranch.branchId}?taskId=no-progress-reporting`); + } + + sync.syncNow(true); + }, [ note ]); + + return ( +
    + +
    + ) +} + function findTypeTitle(type?: NoteType, mime?: string | null) { if (type === "code") { const mimeTypes = mime_types.getMimeTypes(); diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts index e4127e0bc..8a762f96c 100644 --- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts @@ -10,8 +10,6 @@ import type FNote from "../../entities/fnote.js"; import NoteLanguageWidget from "../note_language.js"; const TPL = /*html*/` -
    -
    @@ -28,7 +26,6 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget { constructor() { super(); - this.sharedSwitchWidget = new SharedSwitchWidget().contentSized(); this.templateSwitchWidget = new TemplateSwitchWidget().contentSized(); this.noteLanguageWidget = new NoteLanguageWidget().contentSized(); diff --git a/apps/client/src/widgets/shared_switch.ts b/apps/client/src/widgets/shared_switch.ts deleted file mode 100644 index 20e954efa..000000000 --- a/apps/client/src/widgets/shared_switch.ts +++ /dev/null @@ -1,81 +0,0 @@ -import SwitchWidget from "./switch.js"; -import branchService from "../services/branches.js"; -import server from "../services/server.js"; -import utils from "../services/utils.js"; -import syncService from "../services/sync.js"; -import dialogService from "../services/dialog.js"; -import { t } from "../services/i18n.js"; -import type FNote from "../entities/fnote.js"; -import type { EventData } from "../components/app_context.js"; - -export default class SharedSwitchWidget extends SwitchWidget { - - isEnabled() { - return super.isEnabled() - && !["root", "_share", "_hidden"].includes(this.noteId ?? "") - && !this.noteId?.startsWith("_options"); - } - - doRender() { - super.doRender(); - - this.switchOnName = t("shared_switch.shared"); - this.switchOnTooltip = t("shared_switch.toggle-on-title"); - - this.switchOffName = t("shared_switch.shared"); - this.switchOffTooltip = t("shared_switch.toggle-off-title"); - - this.$helpButton.attr("data-help-page", "sharing.html").show(); - this.$helpButton.on("click", (e) => utils.openHelp($(e.target))); - } - - async switchOn() { - if (!this.noteId) { - return; - } - - await branchService.cloneNoteToParentNote(this.noteId, "_share"); - - syncService.syncNow(true); - } - - async switchOff() { - const shareBranch = this.note?.getParentBranches().find((b) => b.parentNoteId === "_share"); - - if (!shareBranch) { - return; - } - - if (this.note?.getParentBranches().length === 1) { - if (!(await dialogService.confirm(t("shared_switch.shared-branch")))) { - return; - } - } - - await server.remove(`branches/${shareBranch.branchId}?taskId=no-progress-reporting`); - - syncService.syncNow(true); - } - - async refreshWithNote(note: FNote) { - const isShared = note.hasAncestor("_share"); - const canBeUnshared = isShared && note.getParentBranches().find((b) => b.parentNoteId === "_share"); - const switchDisabled = isShared && !canBeUnshared; - - this.isToggled = isShared; - - if (switchDisabled) { - this.disabledTooltip = t("shared_switch.inherited"); - this.canToggle = false; - } else { - this.disabledTooltip = ""; - this.canToggle = true; - } - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.getBranchRows().find((b) => b.noteId === this.noteId)) { - this.refresh(); - } - } -} From c91748da15ad7078a1ac125695ff6b6a399af1f3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 12:14:53 +0300 Subject: [PATCH 190/532] feat(react/ribbon): port template switch --- apps/client/src/widgets/react/hooks.tsx | 7 ++- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 18 +++++++ .../ribbon_widgets/basic_properties.ts | 2 - apps/client/src/widgets/switch.ts | 8 --- apps/client/src/widgets/template_switch.ts | 52 ------------------- 5 files changed, 23 insertions(+), 64 deletions(-) delete mode 100644 apps/client/src/widgets/template_switch.ts diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 7c8257ead..1e91c475a 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -316,6 +316,7 @@ export function useNoteProperty(note: FNote | null | unde export function useNoteLabel(note: FNote | undefined | null, labelName: string): [string | null | undefined, (newValue: string) => void] { const [ labelValue, setLabelValue ] = useState(note?.getLabelValue(labelName)); + useEffect(() => setLabelValue(note?.getLabelValue(labelName) ?? null), [ note ]); useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { for (const attr of loadResults.getAttributeRows()) { if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) { @@ -336,8 +337,10 @@ export function useNoteLabel(note: FNote | undefined | null, labelName: string): ] as const; } -export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: string): [ boolean | null | undefined, (newValue: boolean) => void] { - const [ labelValue, setLabelValue ] = useState(note?.hasLabel(labelName)); +export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: string): [ boolean, (newValue: boolean) => void] { + const [ labelValue, setLabelValue ] = useState(!!note?.hasLabel(labelName)); + + useEffect(() => setLabelValue(!!note?.hasLabel(labelName)), [ note ]); useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { for (const attr of loadResults.getAttributeRows()) { diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 33f28a277..8ed0526b7 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -15,6 +15,7 @@ import FormDropdownList from "../react/FormDropdownList"; import toast from "../../services/toast"; import branches from "../../services/branches"; import sync from "../../services/sync"; +import TemplateSwitchWidget from "../template_switch"; export default function BasicPropertiesTab() { const { note } = useNoteContext(); @@ -26,6 +27,7 @@ export default function BasicPropertiesTab() { +
    ); } @@ -202,6 +204,22 @@ function BookmarkSwitch({ note }: { note?: FNote | null }) { ) } +function TemplateSwitch({ note }: { note?: FNote | null }) { + const [ isTemplate, setIsTemplate ] = useNoteLabelBoolean(note, "template"); + + return ( +
    + +
    + ) +} + function SharedSwitch({ note }: { note?: FNote | null }) { const [ isShared, setIsShared ] = useState(false); const refreshState = useCallback(() => { diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts index 8a762f96c..a60b999e4 100644 --- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts @@ -10,8 +10,6 @@ import type FNote from "../../entities/fnote.js"; import NoteLanguageWidget from "../note_language.js"; const TPL = /*html*/` -
    -
    ${t("basic_properties.language")}:  
    diff --git a/apps/client/src/widgets/switch.ts b/apps/client/src/widgets/switch.ts index 1f5956a6e..c31be8646 100644 --- a/apps/client/src/widgets/switch.ts +++ b/apps/client/src/widgets/switch.ts @@ -1,14 +1,6 @@ import { t } from "../services/i18n.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js"; -const TPL = /*html*/` -
    - - -
    `; - export default class SwitchWidget extends NoteContextAwareWidget { doRender() { diff --git a/apps/client/src/widgets/template_switch.ts b/apps/client/src/widgets/template_switch.ts deleted file mode 100644 index 50c11f657..000000000 --- a/apps/client/src/widgets/template_switch.ts +++ /dev/null @@ -1,52 +0,0 @@ -import SwitchWidget from "./switch.js"; -import attributeService from "../services/attributes.js"; -import { t } from "../services/i18n.js"; -import type { EventData } from "../components/app_context.js"; -import type FNote from "../entities/fnote.js"; - -/** - * Switch for the basic properties widget which allows the user to select whether the note is a template or not, which toggles the `#template` attribute. - */ -export default class TemplateSwitchWidget extends SwitchWidget { - - isEnabled() { - return super.isEnabled() && !this.noteId?.startsWith("_options"); - } - - doRender() { - super.doRender(); - - this.switchOnName = t("template_switch.template"); - this.switchOnTooltip = t("template_switch.toggle-on-hint"); - - this.switchOffName = t("template_switch.template"); - this.switchOffTooltip = t("template_switch.toggle-off-hint"); - - this.$helpButton.attr("data-help-page", "template.html").show(); - } - - async switchOn() { - if (this.noteId) { - await attributeService.setLabel(this.noteId, "template"); - } - } - - async switchOff() { - if (this.note && this.noteId) { - for (const templateAttr of this.note.getOwnedLabels("template")) { - await attributeService.removeAttributeById(this.noteId, templateAttr.attributeId); - } - } - } - - async refreshWithNote(note: FNote) { - const isTemplate = note.hasLabel("template"); - this.isToggled = isTemplate; - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.getAttributeRows().find((attr) => attr.type === "label" && attr.name === "template" && attr.noteId === this.noteId)) { - this.refresh(); - } - } -} From 8e29b5eed65b6f60b3883c69a29f6875f3ec4b11 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 12:34:21 +0300 Subject: [PATCH 191/532] feat(react/ribbon): port note language --- apps/client/src/widgets/note_language.ts | 65 +--------------- apps/client/src/widgets/react/FormList.tsx | 6 +- .../src/widgets/ribbon/BasicPropertiesTab.tsx | 77 +++++++++++++++++-- .../ribbon_widgets/basic_properties.ts | 2 +- 4 files changed, 77 insertions(+), 73 deletions(-) diff --git a/apps/client/src/widgets/note_language.ts b/apps/client/src/widgets/note_language.ts index 398951e20..b6be77230 100644 --- a/apps/client/src/widgets/note_language.ts +++ b/apps/client/src/widgets/note_language.ts @@ -38,11 +38,6 @@ const TPL = /*html*/`\
    `; -const DEFAULT_LOCALE: Locale = { - id: "", - name: t("note_language.not_set") -}; - export default class NoteLanguageWidget extends NoteContextAwareWidget { private dropdown!: Dropdown; @@ -59,7 +54,6 @@ export default class NoteLanguageWidget extends NoteContextAwareWidget { doRender() { this.$widget = $(TPL); this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]); - this.$widget.on("show.bs.dropdown", () => this.renderDropdown()); this.$noteLanguageDropdown = this.$widget.find(".note-language-dropdown") this.$noteLanguageDesc = this.$widget.find(".note-language-desc"); @@ -72,36 +66,8 @@ export default class NoteLanguageWidget extends NoteContextAwareWidget { return; } - for (const locale of this.locales) { - if (typeof locale === "object") { - const $title = $("").text(locale.name); - - const $link = $('') - .attr("data-language", locale.id) - .append(' ') - .append($title) - .on("click", () => { - const languageId = $link.attr("data-language") ?? ""; - this.save(languageId); - }); - - if (locale.rtl) { - $link.attr("dir", "rtl"); - } - - if (locale.id === this.currentLanguageId) { - $link.addClass("selected"); - } - - this.$noteLanguageDropdown.append($link); - } else { - this.$noteLanguageDropdown.append(''); - } - } - const $configureLink = $('') - .append(`${t("note_language.configure-languages")}`) - .on("click", () => appContext.tabManager.openContextWithNote("_optionsLocalization", { activate: true })); + .on("click", () => )); this.$noteLanguageDropdown.append($configureLink); } @@ -114,7 +80,6 @@ export default class NoteLanguageWidget extends NoteContextAwareWidget { } async refreshWithNote(note: FNote) { - const currentLanguageId = note.getLabelValue("language") ?? ""; const language = getLocaleById(currentLanguageId) ?? DEFAULT_LOCALE; this.currentLanguageId = currentLanguageId; this.$noteLanguageDesc.text(language.name); @@ -132,35 +97,7 @@ export default class NoteLanguageWidget extends NoteContextAwareWidget { } static #buildLocales() { - const enabledLanguages = JSON.parse(options.get("languages") ?? "[]") as string[]; - const filteredLanguages = getAvailableLocales().filter((l) => typeof l !== "object" || enabledLanguages.includes(l.id)); - const leftToRightLanguages = filteredLanguages.filter((l) => !l.rtl); - const rightToLeftLanguages = filteredLanguages.filter((l) => l.rtl); - let locales: ("---" | Locale)[] = [ - DEFAULT_LOCALE - ]; - - if (leftToRightLanguages.length > 0) { - locales = [ - ...locales, - "---", - ...leftToRightLanguages - ]; - } - - if (rightToLeftLanguages.length > 0) { - locales = [ - ...locales, - "---", - ...rightToLeftLanguages - ]; - } - - // This will separate the list of languages from the "Configure languages" button. - // If there is at least one language. - locales.push("---"); - return locales; } } diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx index 42aeb93d5..6d16e9d0f 100644 --- a/apps/client/src/widgets/react/FormList.tsx +++ b/apps/client/src/widgets/react/FormList.tsx @@ -82,9 +82,10 @@ interface FormListItemOpts { onClick?: () => void; description?: string; className?: string; + rtl?: boolean; } -export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick, description, selected }: FormListItemOpts) { +export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick, description, selected, rtl }: FormListItemOpts) { if (checked) { icon = "bx bx-check"; } @@ -95,6 +96,7 @@ export function FormListItem({ children, icon, value, title, active, badges, dis data-value={value} title={title} tabIndex={0} onClick={onClick} + dir={rtl ? "rtl" : undefined} >  
    @@ -120,6 +122,6 @@ export function FormListHeader({ text }: FormListHeaderOpts) { ) } -export function FormDivider() { +export function FormDropdownDivider() { return
    ; } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index 8ed0526b7..ab5c35011 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -1,11 +1,11 @@ import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; -import { FormDivider, FormListBadge, FormListItem } from "../react/FormList"; -import { t } from "../../services/i18n"; -import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEventBeta, useTriliumOption } from "../react/hooks"; +import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; +import { getAvailableLocales, t } from "../../services/i18n"; +import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEventBeta, useTriliumOption, useTriliumOptionJson } from "../react/hooks"; import mime_types from "../../services/mime_types"; -import { NoteType, ToggleInParentResponse } from "@triliumnext/commons"; +import { Locale, NoteType, ToggleInParentResponse } from "@triliumnext/commons"; import server from "../../services/server"; import dialog from "../../services/dialog"; import FormToggle from "../react/FormToggle"; @@ -15,7 +15,7 @@ import FormDropdownList from "../react/FormDropdownList"; import toast from "../../services/toast"; import branches from "../../services/branches"; import sync from "../../services/sync"; -import TemplateSwitchWidget from "../template_switch"; +import appContext from "../../components/app_context"; export default function BasicPropertiesTab() { const { note } = useNoteContext(); @@ -28,6 +28,7 @@ export default function BasicPropertiesTab() { +
    ); } @@ -93,7 +94,7 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) { } else { return ( <> - + { + const enabledLanguages = JSON.parse(languages ?? "[]") as string[]; + const filteredLanguages = getAvailableLocales().filter((l) => typeof l !== "object" || enabledLanguages.includes(l.id)); + const leftToRightLanguages = filteredLanguages.filter((l) => !l.rtl); + const rightToLeftLanguages = filteredLanguages.filter((l) => l.rtl); + + let locales: ("---" | Locale)[] = [ + DEFAULT_LOCALE + ]; + + if (leftToRightLanguages.length > 0) { + locales = [ + ...locales, + "---", + ...leftToRightLanguages + ]; + } + + if (rightToLeftLanguages.length > 0) { + locales = [ + ...locales, + "---", + ...rightToLeftLanguages + ]; + } + + // This will separate the list of languages from the "Configure languages" button. + // If there is at least one language. + locales.push("---"); + return locales; + }, [ languages ]); + + return ( +
    + + {locales.map(locale => { + if (typeof locale === "object") { + return setCurrentNoteLanguage(locale.id)} + >{locale.name} + } else { + return + } + })} + + appContext.tabManager.openContextWithNote("_optionsLocalization", { activate: true })} + >{t("note_language.configure-languages")} + +
    + ) +} + function findTypeTitle(type?: NoteType, mime?: string | null) { if (type === "code") { const mimeTypes = mime_types.getMimeTypes(); diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts index a60b999e4..5f0fc9ffa 100644 --- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts +++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts @@ -57,7 +57,7 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget { this.$widget.find(".bookmark-switch-container").append(this.bookmarkSwitchWidget.render()); this.$widget.find(".shared-switch-container").append(this.sharedSwitchWidget.render()); this.$widget.find(".template-switch-container").append(this.templateSwitchWidget.render()); - this.$widget.find(".note-language-container").append(this.noteLanguageWidget.render()); + this.$widget.find(".").append(this.noteLanguageWidget.render()); } async refreshWithNote(note: FNote) { From eff5b6459d2dfb412fd3357e65ef523e728258a7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 15:11:12 +0300 Subject: [PATCH 192/532] chore(react/ribbon): fix event --- apps/client/src/widgets/react/hooks.tsx | 27 +++++++++++++++++++ .../src/widgets/ribbon/BasicPropertiesTab.tsx | 10 ++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 1e91c475a..66100105f 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -166,6 +166,33 @@ export function useTriliumOption(name: OptionNames, needsRefresh?: boolean): [st ] } +export function useTriliumOptionBeta(name: OptionNames, needsRefresh?: boolean): [string, (newValue: OptionValue) => Promise] { + const initialValue = options.get(name); + const [ value, setValue ] = useState(initialValue); + + const wrappedSetValue = useMemo(() => { + return async (newValue: OptionValue) => { + await options.save(name, newValue); + + if (needsRefresh) { + reloadFrontendApp(`option change: ${name}`); + } + } + }, [ name, needsRefresh ]); + + useTriliumEventBeta("entitiesReloaded", useCallback(({ loadResults }) => { + if (loadResults.getOptionNames().includes(name)) { + const newValue = options.get(name); + setValue(newValue); + } + }, [ name ])); + + return [ + value, + wrappedSetValue + ] +} + /** * Similar to {@link useTriliumOption}, but the value is converted to and from a boolean instead of a string. * diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index ab5c35011..f82223c0a 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -3,7 +3,7 @@ import Dropdown from "../react/Dropdown"; import { NOTE_TYPES } from "../../services/note_types"; import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; import { getAvailableLocales, t } from "../../services/i18n"; -import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEventBeta, useTriliumOption, useTriliumOptionJson } from "../react/hooks"; +import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEventBeta, useTriliumOption, useTriliumOptionBeta, useTriliumOptionJson } from "../react/hooks"; import mime_types from "../../services/mime_types"; import { Locale, NoteType, ToggleInParentResponse } from "@triliumnext/commons"; import server from "../../services/server"; @@ -267,13 +267,13 @@ function SharedSwitch({ note }: { note?: FNote | null }) { } function NoteLanguageSwitch({ note }: { note?: FNote | null }) { - const [ languages ] = useTriliumOption("languages"); + const [ languages ] = useTriliumOptionBeta("languages"); const DEFAULT_LOCALE = { id: "", name: t("note_language.not_set") }; - const [ currentNoteLanguage, setCurrentNoteLanguage ] = useNoteLabel(note, "language") ?? ""; + const [ currentNoteLanguage, setCurrentNoteLanguage ] = useNoteLabel(note, "language"); const locales = useMemo(() => { const enabledLanguages = JSON.parse(languages ?? "[]") as string[]; @@ -307,14 +307,16 @@ function NoteLanguageSwitch({ note }: { note?: FNote | null }) { return locales; }, [ languages ]); + return (
    {locales.map(locale => { if (typeof locale === "object") { + const checked = locale.id === (currentNoteLanguage ?? ""); return setCurrentNoteLanguage(locale.id)} >{locale.name} } else { From bf0213907e5e1ac4f8b775884693c5c48e9ca1a8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 15:40:15 +0300 Subject: [PATCH 193/532] chore(react/ribbon): finalize language switcher --- apps/client/src/widgets/note_language.ts | 103 ------------------ apps/client/src/widgets/react/FormToggle.tsx | 10 +- apps/client/src/widgets/react/HelpButton.tsx | 21 ++++ .../src/widgets/ribbon/BasicPropertiesTab.tsx | 14 ++- .../ribbon_widgets/basic_properties.ts | 72 ------------ 5 files changed, 34 insertions(+), 186 deletions(-) delete mode 100644 apps/client/src/widgets/note_language.ts create mode 100644 apps/client/src/widgets/react/HelpButton.tsx delete mode 100644 apps/client/src/widgets/ribbon_widgets/basic_properties.ts diff --git a/apps/client/src/widgets/note_language.ts b/apps/client/src/widgets/note_language.ts deleted file mode 100644 index b6be77230..000000000 --- a/apps/client/src/widgets/note_language.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Dropdown } from "bootstrap"; -import NoteContextAwareWidget from "./note_context_aware_widget.js"; -import { getAvailableLocales, getLocaleById, t } from "../services/i18n.js"; -import type { EventData } from "../components/app_context.js"; -import type FNote from "../entities/fnote.js"; -import attributes from "../services/attributes.js"; -import type { Locale } from "@triliumnext/commons"; -import options from "../services/options.js"; -import appContext from "../components/app_context.js"; - -const TPL = /*html*/`\ - -`; - -export default class NoteLanguageWidget extends NoteContextAwareWidget { - - private dropdown!: Dropdown; - private $noteLanguageDropdown!: JQuery; - private $noteLanguageDesc!: JQuery; - private locales: (Locale | "---")[]; - private currentLanguageId?: string; - - constructor() { - super(); - this.locales = NoteLanguageWidget.#buildLocales(); - } - - doRender() { - this.$widget = $(TPL); - this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]); - - this.$noteLanguageDropdown = this.$widget.find(".note-language-dropdown") - this.$noteLanguageDesc = this.$widget.find(".note-language-desc"); - } - - renderDropdown() { - this.$noteLanguageDropdown.empty(); - - if (!this.note) { - return; - } - - const $configureLink = $('') - .on("click", () => )); - this.$noteLanguageDropdown.append($configureLink); - } - - async save(languageId: string) { - if (!this.note) { - return; - } - - attributes.setAttribute(this.note, "label", "language", languageId); - } - - async refreshWithNote(note: FNote) { - const language = getLocaleById(currentLanguageId) ?? DEFAULT_LOCALE; - this.currentLanguageId = currentLanguageId; - this.$noteLanguageDesc.text(language.name); - this.dropdown.hide(); - } - - async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.isOptionReloaded("languages")) { - this.locales = NoteLanguageWidget.#buildLocales(); - } - - if (loadResults.getAttributeRows().find((a) => a.noteId === this.noteId && a.name === "language")) { - this.refresh(); - } - } - - static #buildLocales() { - - } - -} diff --git a/apps/client/src/widgets/react/FormToggle.tsx b/apps/client/src/widgets/react/FormToggle.tsx index c41ea6750..813947ac6 100644 --- a/apps/client/src/widgets/react/FormToggle.tsx +++ b/apps/client/src/widgets/react/FormToggle.tsx @@ -1,6 +1,7 @@ import { t } from "../../services/i18n"; import { openInAppHelpFromUrl } from "../../services/utils"; import "./FormToggle.css"; +import HelpButton from "./HelpButton"; interface FormToggleProps { currentValue: boolean | null; @@ -36,14 +37,7 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc
    - { helpPage && ( -
    ) } \ No newline at end of file diff --git a/apps/client/src/widgets/react/HelpButton.tsx b/apps/client/src/widgets/react/HelpButton.tsx new file mode 100644 index 000000000..065252264 --- /dev/null +++ b/apps/client/src/widgets/react/HelpButton.tsx @@ -0,0 +1,21 @@ +import { CSSProperties } from "preact/compat"; +import { t } from "../../services/i18n"; +import { openInAppHelpFromUrl } from "../../services/utils"; + +interface HelpButtonProps { + className?: string; + helpPage: string; + style?: CSSProperties; +} + +export default function HelpButton({ className, helpPage, style }: HelpButtonProps) { + return ( +
    +
    + ); +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 594a3e6e2..514ec98a4 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -147,4 +147,15 @@ opacity: 0.3; } +/* #endregion */ + +/* #region Script Tab */ +.script-runner-widget { + padding: 12px; + color: var(--muted-text-color); +} + +.execute-description { + margin-bottom: 10px; +} /* #endregion */ \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/script_executor.ts b/apps/client/src/widgets/ribbon_widgets/script_executor.ts deleted file mode 100644 index aaec721d7..000000000 --- a/apps/client/src/widgets/ribbon_widgets/script_executor.ts +++ /dev/null @@ -1,70 +0,0 @@ -import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import keyboardActionService from "../../services/keyboard_actions.js"; -import { t } from "../../services/i18n.js"; -import type FNote from "../../entities/fnote.js"; - -const TPL = /*html*/` -
    - - -
    - -
    - -
    -
    `; - -export default class ScriptExecutorWidget extends NoteContextAwareWidget { - - private $executeButton!: JQuery; - private $executeDescription!: JQuery; - - isEnabled() { - return ( - super.isEnabled() && - this.note && - (this.note.mime.startsWith("application/javascript") || this.isTriliumSqlite()) && - (this.note.hasLabel("executeDescription") || this.note.hasLabel("executeButton")) - ); - } - - getTitle() { - return { - show: this.isEnabled(), - activate: true - }; - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - - this.$executeButton = this.$widget.find(".execute-button"); - this.$executeDescription = this.$widget.find(".execute-description"); - } - - async refreshWithNote(note: FNote) { - const executeTitle = note.getLabelValue("executeButton") || (this.isTriliumSqlite() ? t("script_executor.execute_query") : t("script_executor.execute_script")); - - this.$executeButton.text(executeTitle); - this.$executeButton.attr("title", executeTitle); - keyboardActionService.updateDisplayedShortcuts(this.$widget); - - const executeDescription = note.getLabelValue("executeDescription"); - - if (executeDescription) { - this.$executeDescription.show().html(executeDescription); - } else { - this.$executeDescription.empty().hide(); - } - } -} From cee4714665692cfcbbb269b11fd90e6b7db6b6c9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 17:31:06 +0300 Subject: [PATCH 197/532] feat(react/ribbon): port edited notes --- .../src/widgets/react/KeyboardShortcut.tsx | 7 +- apps/client/src/widgets/react/NoteLink.tsx | 21 ++++ apps/client/src/widgets/react/RawHtml.tsx | 4 +- apps/client/src/widgets/react/react_utils.tsx | 7 +- .../src/widgets/ribbon/EditedNotesTab.tsx | 51 ++++++++++ apps/client/src/widgets/ribbon/Ribbon.tsx | 13 ++- .../widgets/ribbon_widgets/edited_notes.ts | 98 ------------------- apps/server/src/routes/api/revisions.ts | 4 +- packages/commons/src/lib/server_api.ts | 7 ++ 9 files changed, 101 insertions(+), 111 deletions(-) create mode 100644 apps/client/src/widgets/react/NoteLink.tsx create mode 100644 apps/client/src/widgets/ribbon/EditedNotesTab.tsx delete mode 100644 apps/client/src/widgets/ribbon_widgets/edited_notes.ts diff --git a/apps/client/src/widgets/react/KeyboardShortcut.tsx b/apps/client/src/widgets/react/KeyboardShortcut.tsx index 049f54dd5..852fc8733 100644 --- a/apps/client/src/widgets/react/KeyboardShortcut.tsx +++ b/apps/client/src/widgets/react/KeyboardShortcut.tsx @@ -1,6 +1,7 @@ import { ActionKeyboardShortcut, KeyboardActionNames } from "@triliumnext/commons"; import { useEffect, useState } from "preact/hooks"; import keyboard_actions from "../../services/keyboard_actions"; +import { separateByCommas } from "./react_utils"; interface KeyboardShortcutProps { actionName: KeyboardActionNames; @@ -21,13 +22,13 @@ export default function KeyboardShortcut({ actionName }: KeyboardShortcutProps) <> {action.effectiveShortcuts?.map((shortcut, i) => { const keys = shortcut.split("+"); - return keys + return separateByCommas(keys .map((key, i) => ( <> {key} {i + 1 < keys.length && "+ "} - )) - }).reduce((acc, item) => (acc.length ? [...acc, ", ", item] : [item]), [])} + ))) + })} ); } \ No newline at end of file diff --git a/apps/client/src/widgets/react/NoteLink.tsx b/apps/client/src/widgets/react/NoteLink.tsx new file mode 100644 index 000000000..31dd4fbeb --- /dev/null +++ b/apps/client/src/widgets/react/NoteLink.tsx @@ -0,0 +1,21 @@ +import { useEffect, useMemo, useState } from "preact/hooks"; +import link from "../../services/link"; +import RawHtml from "./RawHtml"; + +interface NoteLinkOpts { + notePath: string | string[]; + showNotePath?: boolean; +} + +export default function NoteLink({ notePath, showNotePath }: NoteLinkOpts) { + const stringifiedNotePath = Array.isArray(notePath) ? notePath.join("/") : notePath; + const [ jqueryEl, setJqueryEl ] = useState>(); + + useEffect(() => { + link.createLink(stringifiedNotePath, { showNotePath: true }) + .then(setJqueryEl); + }, [ stringifiedNotePath, showNotePath ]) + + return + +} \ No newline at end of file diff --git a/apps/client/src/widgets/react/RawHtml.tsx b/apps/client/src/widgets/react/RawHtml.tsx index 1e5aef648..d74fda43a 100644 --- a/apps/client/src/widgets/react/RawHtml.tsx +++ b/apps/client/src/widgets/react/RawHtml.tsx @@ -4,7 +4,7 @@ type HTMLElementLike = string | HTMLElement | JQuery; interface RawHtmlProps { className?: string; - html: HTMLElementLike; + html?: HTMLElementLike; style?: CSSProperties; } @@ -19,7 +19,7 @@ export function RawHtmlBlock(props: RawHtmlProps) { function getProps({ className, html, style }: RawHtmlProps) { return { className: className, - dangerouslySetInnerHTML: getHtml(html), + dangerouslySetInnerHTML: getHtml(html ?? ""), style } } diff --git a/apps/client/src/widgets/react/react_utils.tsx b/apps/client/src/widgets/react/react_utils.tsx index e8b0752b4..db451b21a 100644 --- a/apps/client/src/widgets/react/react_utils.tsx +++ b/apps/client/src/widgets/react/react_utils.tsx @@ -1,4 +1,4 @@ -import { createContext, render, type JSX, type RefObject } from "preact"; +import { ComponentChild, createContext, render, type JSX, type RefObject } from "preact"; import Component from "../../components/component"; export const ParentComponent = createContext(null); @@ -39,4 +39,9 @@ export function renderReactWidgetAtElement(parentComponent: Component, el: JSX.E export function disposeReactWidget(container: Element) { render(null, container); +} + +export function separateByCommas(components: ComponentChild[]) { + return components.reduce((acc, item) => + (acc.length ? [...acc, ", ", item] : [item]), []); } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/EditedNotesTab.tsx b/apps/client/src/widgets/ribbon/EditedNotesTab.tsx new file mode 100644 index 000000000..c85ac4ad1 --- /dev/null +++ b/apps/client/src/widgets/ribbon/EditedNotesTab.tsx @@ -0,0 +1,51 @@ +import { useEffect, useState } from "preact/hooks"; +import { TabContext } from "./ribbon-interface"; +import { EditedNotesResponse } from "@triliumnext/commons"; +import server from "../../services/server"; +import { t } from "../../services/i18n"; +import froca from "../../services/froca"; +import NoteLink from "../react/NoteLink"; +import { separateByCommas } from "../react/react_utils"; + +export default function EditedNotesTab({ note }: TabContext) { + const [ editedNotes, setEditedNotes ] = useState(); + + useEffect(() => { + if (!note) return; + server.get(`edited-notes/${note.getLabelValue("dateNote")}`).then(async editedNotes => { + editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId); + const noteIds = editedNotes.flatMap((n) => n.noteId); + await froca.getNotes(noteIds, true); // preload all at once + setEditedNotes(editedNotes); + }); + }, [ note?.noteId ]); + + return ( +
    + {editedNotes ? ( +
    + {separateByCommas(editedNotes.map(editedNote => { + return ( + + {editedNote.isDeleted ? ( + {`${editedNote.title} ${t("edited_notes.deleted")}`} + ) : ( + <> + {editedNote.notePath ? : {editedNote.title} } + + )} + + ) + }))} +
    + ) : ( +
    {t("edited_notes.no_edited_notes_found")}
    + )} +
    + ) +} diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index e0bc25d9a..c043f84e3 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -11,6 +11,7 @@ import options from "../../services/options"; import { CommandNames } from "../../components/app_context"; import FNote from "../../entities/fnote"; import ScriptTab from "./ScriptTab"; +import EditedNotesTab from "./EditedNotesTab"; interface TitleContext { note: FNote | null | undefined; @@ -21,9 +22,9 @@ interface TabConfiguration { icon: string; // TODO: Mark as required after porting them all. content?: (context: TabContext) => VNode; - show?: (context: TitleContext) => boolean; + show?: (context: TitleContext) => boolean | null | undefined; toggleCommand?: CommandNames; - activate?: boolean; + activate?: boolean | ((context: TitleContext) => boolean); /** * By default the tab content will not be rendered unless the tab is active (i.e. selected by the user). Setting to `true` will ensure that the tab is rendered even when inactive, for cases where the tab needs to be accessible at all times (e.g. for the detached editor toolbar). */ @@ -44,7 +45,7 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ icon: "bx bx-play", content: ScriptTab, activate: true, - show: ({ note }) => !!note && + show: ({ note }) => note && (note.isTriliumScript() || note.isTriliumSqlite()) && (note.hasLabel("executeDescription") || note.hasLabel("executeButton")) }, @@ -54,9 +55,11 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ icon: "bx bx-search" }, { - // Edited NotesWidget title: t("edited_notes.title"), - icon: "bx bx-calendar-edit" + icon: "bx bx-calendar-edit", + content: EditedNotesTab, + show: ({ note }) => note?.hasOwnedLabel("dateNote"), + activate: ({ note }) => (note?.getPromotedDefinitionAttributes().length === 0 || !options.is("promotedAttributesOpenInRibbon")) && options.is("editedNotesOpenInRibbon") }, { // BookPropertiesWidget diff --git a/apps/client/src/widgets/ribbon_widgets/edited_notes.ts b/apps/client/src/widgets/ribbon_widgets/edited_notes.ts deleted file mode 100644 index 768d48566..000000000 --- a/apps/client/src/widgets/ribbon_widgets/edited_notes.ts +++ /dev/null @@ -1,98 +0,0 @@ -import linkService from "../../services/link.js"; -import server from "../../services/server.js"; -import froca from "../../services/froca.js"; -import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import options from "../../services/options.js"; -import { t } from "../../services/i18n.js"; -import type FNote from "../../entities/fnote.js"; - -const TPL = /*html*/` -
    - - -
    ${t("edited_notes.no_edited_notes_found")}
    - - -
    -`; - -// TODO: Deduplicate with server. -interface EditedNotesResponse { - noteId: string; - isDeleted: boolean; - title: string; - notePath: string[]; -} - -export default class EditedNotesWidget extends NoteContextAwareWidget { - - private $list!: JQuery; - private $noneFound!: JQuery; - - get name() { - return "editedNotes"; - } - - isEnabled() { - return super.isEnabled() && this.note?.hasOwnedLabel("dateNote"); - } - - getTitle() { - return { - show: this.isEnabled(), - // promoted attributes have priority over edited notes - activate: (this.note?.getPromotedDefinitionAttributes().length === 0 || !options.is("promotedAttributesOpenInRibbon")) && options.is("editedNotesOpenInRibbon"), - - }; - } - - async doRender() { - this.$widget = $(TPL); - this.contentSized(); - this.$list = this.$widget.find(".edited-notes-list"); - this.$noneFound = this.$widget.find(".no-edited-notes-found"); - } - - async refreshWithNote(note: FNote) { - let editedNotes = await server.get(`edited-notes/${note.getLabelValue("dateNote")}`); - - editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId); - - this.$list.empty(); - this.$noneFound.hide(); - - if (editedNotes.length === 0) { - this.$noneFound.show(); - return; - } - - const noteIds = editedNotes.flatMap((n) => n.noteId); - - await froca.getNotes(noteIds, true); // preload all at once - - for (let i = 0; i < editedNotes.length; i++) { - const editedNote = editedNotes[i]; - const $item = $(''); - - if (editedNote.isDeleted) { - const title = `${editedNote.title} ${t("edited_notes.deleted")}`; - $item.append($("").text(title).attr("title", title)); - } else { - $item.append(editedNote.notePath ? await linkService.createLink(editedNote.notePath.join("/"), { showNotePath: true }) : $("").text(editedNote.title)); - } - - if (i < editedNotes.length - 1) { - $item.append(", "); - } - - this.$list.append($item); - } - } -} diff --git a/apps/server/src/routes/api/revisions.ts b/apps/server/src/routes/api/revisions.ts index cdcabe6d2..055d0b75e 100644 --- a/apps/server/src/routes/api/revisions.ts +++ b/apps/server/src/routes/api/revisions.ts @@ -12,7 +12,7 @@ import type { Request, Response } from "express"; import type BRevision from "../../becca/entities/brevision.js"; import type BNote from "../../becca/entities/bnote.js"; import type { NotePojo } from "../../becca/becca-interface.js"; -import { RevisionItem, RevisionPojo, RevisionRow } from "@triliumnext/commons"; +import { EditedNotesResponse, RevisionItem, RevisionPojo, RevisionRow } from "@triliumnext/commons"; interface NotePath { noteId: string; @@ -184,7 +184,7 @@ function getEditedNotesOnDate(req: Request) { notePojo.notePath = notePath ? notePath.notePath : null; return notePojo; - }); + }) satisfies EditedNotesResponse; } function getNotePathData(note: BNote): NotePath | undefined { diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index 311e45fb0..199ab164b 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -162,3 +162,10 @@ export type ToggleInParentResponse = { success: false; message: string; } + +export type EditedNotesResponse = { + noteId: string; + isDeleted: boolean; + title?: string; + notePath?: string[] | null; +}[]; From 70728c274e138906cfaf66ec25c6dfbcde9ce3b9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 17:41:45 +0300 Subject: [PATCH 198/532] feat(react/ribbon): port note properties --- .../src/widgets/ribbon/NotePropertiesTab.tsx | 20 +++++++ apps/client/src/widgets/ribbon/Ribbon.tsx | 7 ++- .../widgets/ribbon_widgets/note_properties.ts | 53 ------------------- 3 files changed, 25 insertions(+), 55 deletions(-) create mode 100644 apps/client/src/widgets/ribbon/NotePropertiesTab.tsx delete mode 100644 apps/client/src/widgets/ribbon_widgets/note_properties.ts diff --git a/apps/client/src/widgets/ribbon/NotePropertiesTab.tsx b/apps/client/src/widgets/ribbon/NotePropertiesTab.tsx new file mode 100644 index 000000000..8cc0c2b85 --- /dev/null +++ b/apps/client/src/widgets/ribbon/NotePropertiesTab.tsx @@ -0,0 +1,20 @@ +import { t } from "../../services/i18n"; +import { useNoteLabel } from "../react/hooks"; +import { TabContext } from "./ribbon-interface"; + +/** + * TODO: figure out better name or conceptualize better. + */ +export default function NotePropertiesTab({ note }: TabContext) { + const [ pageUrl ] = useNoteLabel(note, "pageUrl"); + + return ( +
    + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index c043f84e3..5dbeb1c62 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -12,6 +12,7 @@ import { CommandNames } from "../../components/app_context"; import FNote from "../../entities/fnote"; import ScriptTab from "./ScriptTab"; import EditedNotesTab from "./EditedNotesTab"; +import NotePropertiesTab from "./NotePropertiesTab"; interface TitleContext { note: FNote | null | undefined; @@ -67,9 +68,11 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ icon: "bx bx-book" }, { - // NotePropertiesWidget title: t("note_properties.info"), - icon: "bx bx-info-square" + icon: "bx bx-info-square", + content: NotePropertiesTab, + show: ({ note }) => !!note?.getLabelValue("pageUrl"), + activate: true }, { // FilePropertiesWidget diff --git a/apps/client/src/widgets/ribbon_widgets/note_properties.ts b/apps/client/src/widgets/ribbon_widgets/note_properties.ts deleted file mode 100644 index 7f875e4e4..000000000 --- a/apps/client/src/widgets/ribbon_widgets/note_properties.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type FNote from "../../entities/fnote.js"; -import { t } from "../../services/i18n.js"; -import NoteContextAwareWidget from "../note_context_aware_widget.js"; - -const TPL = /*html*/` -
    - - -
    - ${t("note_properties.this_note_was_originally_taken_from")} -
    -
    `; - -/** - * TODO: figure out better name or conceptualize better. - */ -export default class NotePropertiesWidget extends NoteContextAwareWidget { - - private $pageUrl!: JQuery; - - isEnabled() { - return this.note && !!this.note.getLabelValue("pageUrl"); - } - - getTitle() { - return { - show: this.isEnabled(), - activate: true, - - }; - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - - this.$pageUrl = this.$widget.find(".page-url"); - } - - async refreshWithNote(note: FNote) { - const pageUrl = note.getLabelValue("pageUrl"); - - this.$pageUrl - .attr("href", pageUrl) - .attr("title", pageUrl) - .text(pageUrl ?? ""); - } -} From 77551b1fed428f39e2fcfa7c4824bedbdf37968c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 18:23:54 +0300 Subject: [PATCH 199/532] feat(react/ribbon): port note info tab --- apps/client/src/services/utils.ts | 6 +- apps/client/src/utils/formatters.ts | 6 +- .../src/widgets/react/LoadingSpinner.tsx | 3 + .../client/src/widgets/ribbon/NoteInfoTab.tsx | 84 +++++++++ apps/client/src/widgets/ribbon/Ribbon.tsx | 9 +- apps/client/src/widgets/ribbon/style.css | 24 +++ .../ribbon_widgets/note_info_widget.ts | 172 ------------------ apps/server/src/routes/api/notes.ts | 4 +- apps/server/src/routes/api/stats.ts | 5 +- packages/commons/src/lib/server_api.ts | 16 ++ 10 files changed, 148 insertions(+), 181 deletions(-) create mode 100644 apps/client/src/widgets/react/LoadingSpinner.tsx create mode 100644 apps/client/src/widgets/ribbon/NoteInfoTab.tsx delete mode 100644 apps/client/src/widgets/ribbon_widgets/note_info_widget.ts diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index bd924768f..7ae1899fe 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -185,7 +185,11 @@ export function escapeQuotes(value: string) { return value.replaceAll('"', """); } -function formatSize(size: number) { +export function formatSize(size: number | null | undefined) { + if (size === null || size === undefined) { + return ""; + } + size = Math.max(Math.round(size / 1024), 1); if (size < 1024) { diff --git a/apps/client/src/utils/formatters.ts b/apps/client/src/utils/formatters.ts index 755cc8153..d3209be7b 100644 --- a/apps/client/src/utils/formatters.ts +++ b/apps/client/src/utils/formatters.ts @@ -3,7 +3,11 @@ type DateTimeStyle = "full" | "long" | "medium" | "short" | "none" | undefined; /** * Formats the given date and time to a string based on the current locale. */ -export function formatDateTime(date: string | Date | number, dateStyle: DateTimeStyle = "medium", timeStyle: DateTimeStyle = "medium") { +export function formatDateTime(date: string | Date | number | null | undefined, dateStyle: DateTimeStyle = "medium", timeStyle: DateTimeStyle = "medium") { + if (!date) { + return ""; + } + const locale = navigator.language; let parsedDate; diff --git a/apps/client/src/widgets/react/LoadingSpinner.tsx b/apps/client/src/widgets/react/LoadingSpinner.tsx new file mode 100644 index 000000000..b3eb99e2e --- /dev/null +++ b/apps/client/src/widgets/react/LoadingSpinner.tsx @@ -0,0 +1,3 @@ +export default function LoadingSpinner() { + return +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx new file mode 100644 index 000000000..466410b26 --- /dev/null +++ b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx @@ -0,0 +1,84 @@ +import { useEffect, useState } from "preact/hooks"; +import { t } from "../../services/i18n"; +import { TabContext } from "./ribbon-interface"; +import { MetadataResponse, NoteSizeResponse, SubtreeSizeResponse } from "@triliumnext/commons"; +import server from "../../services/server"; +import Button from "../react/Button"; +import { formatDateTime } from "../../utils/formatters"; +import { formatSize } from "../../services/utils"; +import LoadingSpinner from "../react/LoadingSpinner"; + +export default function NoteInfoTab({ note }: TabContext) { + const [ metadata, setMetadata ] = useState(); + const [ isLoading, setIsLoading ] = useState(false); + const [ noteSizeResponse, setNoteSizeResponse ] = useState(); + const [ subtreeSizeResponse, setSubtreeSizeResponse ] = useState(); + + useEffect(() => { + if (note) { + server.get(`notes/${note?.noteId}/metadata`).then(setMetadata); + } + + setNoteSizeResponse(undefined); + setSubtreeSizeResponse(undefined); + setIsLoading(false); + }, [ note?.noteId ]); + + console.log("Got ", noteSizeResponse, subtreeSizeResponse); + + return ( +
    + {note && ( + + + + + + + + + + + + + + + + + +
    {t("note_info_widget.note_id")}:{note.noteId}{t("note_info_widget.created")}:{formatDateTime(metadata?.dateCreated)}{t("note_info_widget.modified")}:{formatDateTime(metadata?.dateModified)}
    {t("note_info_widget.type")}: + {note.type}{' '} + { note.mime && ({note.mime}) } + {t("note_info_widget.note_size")}: + {!isLoading && !noteSizeResponse && !subtreeSizeResponse && ( +
    + )} +
    + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index 5dbeb1c62..abe0e1fdd 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -13,6 +13,7 @@ import FNote from "../../entities/fnote"; import ScriptTab from "./ScriptTab"; import EditedNotesTab from "./EditedNotesTab"; import NotePropertiesTab from "./NotePropertiesTab"; +import NoteInfoTab from "./NoteInfoTab"; interface TitleContext { note: FNote | null | undefined; @@ -118,9 +119,11 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ icon: "bx bx-bar-chart" }, { - // NoteInfoWidget title: t("note_info_widget.title"), - icon: "bx bx-info-circle" + icon: "bx bx-info-circle", + show: ({ note }) => !!note, + content: NoteInfoTab, + toggleCommand: "toggleRibbonTabNoteInfo" } ]); @@ -128,7 +131,7 @@ export default function Ribbon() { const { note } = useNoteContext(); const titleContext: TitleContext = { note }; const [ activeTabIndex, setActiveTabIndex ] = useState(); - const filteredTabs = useMemo(() => TAB_CONFIGURATION.filter(tab => tab.show?.(titleContext)), [ titleContext, note ]) + const filteredTabs = useMemo(() => TAB_CONFIGURATION.filter(tab => tab.show?.(titleContext)), [ titleContext, note ]); return (
    diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 514ec98a4..678c24af7 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -158,4 +158,28 @@ .execute-description { margin-bottom: 10px; } +/* #endregion */ + +/* #region Note info */ +.note-info-widget { + padding: 12px; +} + +.note-info-widget-table { + max-width: 100%; + display: block; + overflow-x: auto; + white-space: nowrap; +} + +.note-info-widget-table td, .note-info-widget-table th { + padding: 5px; +} + +.note-info-mime { + max-width: 13em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} /* #endregion */ \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/note_info_widget.ts b/apps/client/src/widgets/ribbon_widgets/note_info_widget.ts deleted file mode 100644 index 3a390eb1d..000000000 --- a/apps/client/src/widgets/ribbon_widgets/note_info_widget.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { formatDateTime } from "../../utils/formatters.js"; -import { t } from "../../services/i18n.js"; -import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import server from "../../services/server.js"; -import utils from "../../services/utils.js"; -import type { EventData } from "../../components/app_context.js"; -import type FNote from "../../entities/fnote.js"; - -const TPL = /*html*/` -
    - - - - - - - - - - - - - - - - - - -
    ${t("note_info_widget.note_id")}:${t("note_info_widget.created")}:${t("note_info_widget.modified")}:
    ${t("note_info_widget.type")}: - - - ${t("note_info_widget.note_size")}: - - - - - -
    -
    -`; - -// TODO: Deduplicate with server -interface NoteSizeResponse { - noteSize: number; -} - -interface SubtreeSizeResponse { - subTreeNoteCount: number; - subTreeSize: number; -} - -interface MetadataResponse { - dateCreated: number; - dateModified: number; -} - -export default class NoteInfoWidget extends NoteContextAwareWidget { - - private $noteId!: JQuery; - private $dateCreated!: JQuery; - private $dateModified!: JQuery; - private $type!: JQuery; - private $mime!: JQuery; - private $noteSizesWrapper!: JQuery; - private $noteSize!: JQuery; - private $subTreeSize!: JQuery; - private $calculateButton!: JQuery; - - get name() { - return "noteInfo"; - } - - get toggleCommand() { - return "toggleRibbonTabNoteInfo"; - } - - isEnabled() { - return !!this.note; - } - - getTitle() { - return { - show: this.isEnabled(), - - }; - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - - this.$noteId = this.$widget.find(".note-info-note-id"); - this.$dateCreated = this.$widget.find(".note-info-date-created"); - this.$dateModified = this.$widget.find(".note-info-date-modified"); - this.$type = this.$widget.find(".note-info-type"); - this.$mime = this.$widget.find(".note-info-mime"); - - this.$noteSizesWrapper = this.$widget.find(".note-sizes-wrapper"); - this.$noteSize = this.$widget.find(".note-size"); - this.$subTreeSize = this.$widget.find(".subtree-size"); - - this.$calculateButton = this.$widget.find(".calculate-button"); - this.$calculateButton.on("click", async () => { - this.$noteSizesWrapper.show(); - this.$calculateButton.hide(); - - this.$noteSize.empty().append($('')); - this.$subTreeSize.empty().append($('')); - - const noteSizeResp = await server.get(`stats/note-size/${this.noteId}`); - this.$noteSize.text(utils.formatSize(noteSizeResp.noteSize)); - - const subTreeResp = await server.get(`stats/subtree-size/${this.noteId}`); - - if (subTreeResp.subTreeNoteCount > 1) { - this.$subTreeSize.text(t("note_info_widget.subtree_size", { size: utils.formatSize(subTreeResp.subTreeSize), count: subTreeResp.subTreeNoteCount })); - } else { - this.$subTreeSize.text(""); - } - }); - } - - async refreshWithNote(note: FNote) { - const metadata = await server.get(`notes/${this.noteId}/metadata`); - - this.$noteId.text(note.noteId); - this.$dateCreated.text(formatDateTime(metadata.dateCreated)).attr("title", metadata.dateCreated); - - this.$dateModified.text(formatDateTime(metadata.dateModified)).attr("title", metadata.dateModified); - - this.$type.text(note.type); - - if (note.mime) { - this.$mime.text(`(${note.mime})`); - } else { - this.$mime.empty(); - } - - this.$calculateButton.show(); - this.$noteSizesWrapper.hide(); - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (this.noteId && (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId))) { - this.refresh(); - } - } -} diff --git a/apps/server/src/routes/api/notes.ts b/apps/server/src/routes/api/notes.ts index fd938dc18..f481d199a 100644 --- a/apps/server/src/routes/api/notes.ts +++ b/apps/server/src/routes/api/notes.ts @@ -12,7 +12,7 @@ import ValidationError from "../../errors/validation_error.js"; import blobService from "../../services/blob.js"; import type { Request } from "express"; import type BBranch from "../../becca/entities/bbranch.js"; -import type { AttributeRow, DeleteNotesPreview } from "@triliumnext/commons"; +import type { AttributeRow, DeleteNotesPreview, MetadataResponse } from "@triliumnext/commons"; /** * @swagger @@ -101,7 +101,7 @@ function getNoteMetadata(req: Request) { utcDateCreated: note.utcDateCreated, dateModified: note.dateModified, utcDateModified: note.utcDateModified - }; + } satisfies MetadataResponse; } function createNote(req: Request) { diff --git a/apps/server/src/routes/api/stats.ts b/apps/server/src/routes/api/stats.ts index 15e28f083..aebca079e 100644 --- a/apps/server/src/routes/api/stats.ts +++ b/apps/server/src/routes/api/stats.ts @@ -1,6 +1,7 @@ import sql from "../../services/sql.js"; import becca from "../../becca/becca.js"; import type { Request } from "express"; +import { NoteSizeResponse, SubtreeSizeResponse } from "@triliumnext/commons"; function getNoteSize(req: Request) { const { noteId } = req.params; @@ -22,7 +23,7 @@ function getNoteSize(req: Request) { return { noteSize - }; + } satisfies NoteSizeResponse; } function getSubtreeSize(req: Request) { @@ -45,7 +46,7 @@ function getSubtreeSize(req: Request) { return { subTreeSize, subTreeNoteCount: subTreeNoteIds.length - }; + } satisfies SubtreeSizeResponse; } export default { diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index 199ab164b..46b403d0f 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -169,3 +169,19 @@ export type EditedNotesResponse = { title?: string; notePath?: string[] | null; }[]; + +export interface MetadataResponse { + dateCreated: string | undefined; + utcDateCreated: string; + dateModified: string | undefined; + utcDateModified: string | undefined; +} + +export interface NoteSizeResponse { + noteSize: number; +} + +export interface SubtreeSizeResponse { + subTreeNoteCount: number; + subTreeSize: number; +} From c5bb3106137f3be4b8267c4b5ba81c1983746822 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 19:07:04 +0300 Subject: [PATCH 200/532] chore(react/ribbon): bring back note info auto-refresh --- apps/client/src/widgets/ribbon/NoteInfoTab.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx index 466410b26..889b28b21 100644 --- a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx +++ b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "preact/hooks"; +import { useCallback, useEffect, useState } from "preact/hooks"; import { t } from "../../services/i18n"; import { TabContext } from "./ribbon-interface"; import { MetadataResponse, NoteSizeResponse, SubtreeSizeResponse } from "@triliumnext/commons"; @@ -7,6 +7,7 @@ import Button from "../react/Button"; import { formatDateTime } from "../../utils/formatters"; import { formatSize } from "../../services/utils"; import LoadingSpinner from "../react/LoadingSpinner"; +import { useTriliumEventBeta } from "../react/hooks"; export default function NoteInfoTab({ note }: TabContext) { const [ metadata, setMetadata ] = useState(); @@ -14,7 +15,7 @@ export default function NoteInfoTab({ note }: TabContext) { const [ noteSizeResponse, setNoteSizeResponse ] = useState(); const [ subtreeSizeResponse, setSubtreeSizeResponse ] = useState(); - useEffect(() => { + function refresh() { if (note) { server.get(`notes/${note?.noteId}/metadata`).then(setMetadata); } @@ -22,9 +23,15 @@ export default function NoteInfoTab({ note }: TabContext) { setNoteSizeResponse(undefined); setSubtreeSizeResponse(undefined); setIsLoading(false); - }, [ note?.noteId ]); + } - console.log("Got ", noteSizeResponse, subtreeSizeResponse); + useEffect(refresh, [ note?.noteId ]); + useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + const noteId = note?.noteId; + if (noteId && (loadResults.isNoteReloaded(noteId) || loadResults.isNoteContentReloaded(noteId))) { + refresh(); + } + }); return (
    From cc05572a3529ed1aba07464c77b7e3642ebe2272 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 19:27:58 +0300 Subject: [PATCH 201/532] feat(react/ribbon): port similar notes --- apps/client/src/widgets/react/NoteLink.tsx | 11 +- apps/client/src/widgets/ribbon/Ribbon.tsx | 7 +- .../src/widgets/ribbon/SimilarNotesTab.tsx | 40 ++++++ apps/client/src/widgets/ribbon/style.css | 21 ++++ .../widgets/ribbon_widgets/similar_notes.ts | 119 ------------------ apps/server/src/becca/similarity.ts | 7 +- apps/server/src/routes/api/similar_notes.ts | 3 +- packages/commons/src/lib/server_api.ts | 8 ++ 8 files changed, 85 insertions(+), 131 deletions(-) create mode 100644 apps/client/src/widgets/ribbon/SimilarNotesTab.tsx delete mode 100644 apps/client/src/widgets/ribbon_widgets/similar_notes.ts diff --git a/apps/client/src/widgets/react/NoteLink.tsx b/apps/client/src/widgets/react/NoteLink.tsx index 31dd4fbeb..21c0af3d3 100644 --- a/apps/client/src/widgets/react/NoteLink.tsx +++ b/apps/client/src/widgets/react/NoteLink.tsx @@ -5,16 +5,21 @@ import RawHtml from "./RawHtml"; interface NoteLinkOpts { notePath: string | string[]; showNotePath?: boolean; + style?: Record; } -export default function NoteLink({ notePath, showNotePath }: NoteLinkOpts) { +export default function NoteLink({ notePath, showNotePath, style }: NoteLinkOpts) { const stringifiedNotePath = Array.isArray(notePath) ? notePath.join("/") : notePath; const [ jqueryEl, setJqueryEl ] = useState>(); useEffect(() => { - link.createLink(stringifiedNotePath, { showNotePath: true }) + link.createLink(stringifiedNotePath, { showNotePath }) .then(setJqueryEl); - }, [ stringifiedNotePath, showNotePath ]) + }, [ stringifiedNotePath, showNotePath ]); + + if (style) { + jqueryEl?.css(style); + } return diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index abe0e1fdd..14d448d45 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -14,6 +14,7 @@ import ScriptTab from "./ScriptTab"; import EditedNotesTab from "./EditedNotesTab"; import NotePropertiesTab from "./NotePropertiesTab"; import NoteInfoTab from "./NoteInfoTab"; +import SimilarNotesTab from "./SimilarNotesTab"; interface TitleContext { note: FNote | null | undefined; @@ -114,9 +115,11 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ icon: "bx bxs-network-chart" }, { - // SimilarNotesWidget title: t("similar_notes.title"), - icon: "bx bx-bar-chart" + icon: "bx bx-bar-chart", + show: ({ note }) => note?.type !== "search" && !note?.isLabelTruthy("similarNotesWidgetDisabled"), + content: SimilarNotesTab, + toggleCommand: "toggleRibbonTabSimilarNotes" }, { title: t("note_info_widget.title"), diff --git a/apps/client/src/widgets/ribbon/SimilarNotesTab.tsx b/apps/client/src/widgets/ribbon/SimilarNotesTab.tsx new file mode 100644 index 000000000..b771f13bc --- /dev/null +++ b/apps/client/src/widgets/ribbon/SimilarNotesTab.tsx @@ -0,0 +1,40 @@ +import { useEffect, useState } from "preact/hooks"; +import { TabContext } from "./ribbon-interface"; +import { SimilarNoteResponse } from "@triliumnext/commons"; +import server from "../../services/server"; +import { t } from "../../services/i18n"; +import froca from "../../services/froca"; +import NoteLink from "../react/NoteLink"; + +export default function SimilarNotesTab({ note }: TabContext) { + const [ similarNotes, setSimilarNotes ] = useState(); + + useEffect(() => { + if (note) { + server.get(`similar-notes/${note.noteId}`).then(async similarNotes => { + if (similarNotes) { + const noteIds = similarNotes.flatMap((note) => note.notePath); + await froca.getNotes(noteIds, true); // preload all at once + } + setSimilarNotes(similarNotes); + }); + } + + }, [ note?.noteId ]); + + return ( +
    + {similarNotes?.length ? ( +
    + {similarNotes.map(({notePath, score}) => ( + + ))} +
    + ) : ( + <>{t("similar_notes.no_similar_notes_found")} + )} +
    + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 678c24af7..7200e6a00 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -182,4 +182,25 @@ text-overflow: ellipsis; white-space: nowrap; } +/* #endregion */ + +/* #region Similar Notes */ +.similar-notes-wrapper { + max-height: 200px; + overflow: auto; + padding: 12px; +} + +.similar-notes-wrapper a { + display: inline-block; + border: 1px dotted var(--main-border-color); + border-radius: 20px; + background-color: var(--accented-background-color); + padding: 0 10px 0 10px; + margin: 0 3px 0 3px; + max-width: 10em; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} /* #endregion */ \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/similar_notes.ts b/apps/client/src/widgets/ribbon_widgets/similar_notes.ts deleted file mode 100644 index f86fc714a..000000000 --- a/apps/client/src/widgets/ribbon_widgets/similar_notes.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { t } from "../../services/i18n.js"; -import linkService from "../../services/link.js"; -import server from "../../services/server.js"; -import froca from "../../services/froca.js"; -import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import type FNote from "../../entities/fnote.js"; -import type { EventData } from "../../components/app_context.js"; - -const TPL = /*html*/` -
    - - -
    -
    -`; - -// TODO: Deduplicate with server -interface SimilarNote { - score: number; - notePath: string[]; - noteId: string; -} - -export default class SimilarNotesWidget extends NoteContextAwareWidget { - - private $similarNotesWrapper!: JQuery; - private title?: string; - private rendered?: boolean; - - get name() { - return "similarNotes"; - } - - get toggleCommand() { - return "toggleRibbonTabSimilarNotes"; - } - - isEnabled() { - return super.isEnabled() && this.note?.type !== "search" && !this.note?.isLabelTruthy("similarNotesWidgetDisabled"); - } - - getTitle() { - return { - show: this.isEnabled(), - - }; - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - - this.$similarNotesWrapper = this.$widget.find(".similar-notes-wrapper"); - } - - async refreshWithNote(note: FNote) { - if (!this.note) { - return; - } - - // remember which title was when we found the similar notes - this.title = this.note.title; - - const similarNotes = await server.get(`similar-notes/${this.noteId}`); - - if (similarNotes.length === 0) { - this.$similarNotesWrapper.empty().append(t("similar_notes.no_similar_notes_found")); - - return; - } - - const noteIds = similarNotes.flatMap((note) => note.notePath); - - await froca.getNotes(noteIds, true); // preload all at once - - const $list = $("
    "); - - for (const similarNote of similarNotes) { - const note = await froca.getNote(similarNote.noteId, true); - - if (!note) { - continue; - } - - const $item = (await linkService.createLink(similarNote.notePath.join("/"))).css("font-size", 24 * (1 - 1 / (1 + similarNote.score))); - - $list.append($item); - } - - this.$similarNotesWrapper.empty().append($list); - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (this.note && this.title !== this.note.title) { - this.rendered = false; - - this.refresh(); - } - } -} diff --git a/apps/server/src/becca/similarity.ts b/apps/server/src/becca/similarity.ts index fe7ecda21..dc0a4c3f8 100644 --- a/apps/server/src/becca/similarity.ts +++ b/apps/server/src/becca/similarity.ts @@ -4,6 +4,7 @@ import beccaService from "./becca_service.js"; import dateUtils from "../services/date_utils.js"; import { JSDOM } from "jsdom"; import type BNote from "./entities/bnote.js"; +import { SimilarNote } from "@triliumnext/commons"; const DEBUG = false; @@ -36,12 +37,6 @@ interface DateLimits { maxDate: string; } -export interface SimilarNote { - score: number; - notePath: string[]; - noteId: string; -} - function filterUrlValue(value: string) { return value .replace(/https?:\/\//gi, "") diff --git a/apps/server/src/routes/api/similar_notes.ts b/apps/server/src/routes/api/similar_notes.ts index 8cd82dc72..6b9cbb926 100644 --- a/apps/server/src/routes/api/similar_notes.ts +++ b/apps/server/src/routes/api/similar_notes.ts @@ -4,13 +4,14 @@ import type { Request } from "express"; import similarityService from "../../becca/similarity.js"; import becca from "../../becca/becca.js"; +import { SimilarNoteResponse } from "@triliumnext/commons"; async function getSimilarNotes(req: Request) { const noteId = req.params.noteId; const _note = becca.getNoteOrThrow(noteId); - return await similarityService.findSimilarNotes(noteId); + return (await similarityService.findSimilarNotes(noteId) satisfies SimilarNoteResponse); } export default { diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index 46b403d0f..84a471670 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -185,3 +185,11 @@ export interface SubtreeSizeResponse { subTreeNoteCount: number; subTreeSize: number; } + +export interface SimilarNote { + score: number; + notePath: string[]; + noteId: string; +} + +export type SimilarNoteResponse = (SimilarNote[] | undefined); From 978d8291501e9b48cdb0629e952ca2e45c7241b2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 20:17:00 +0300 Subject: [PATCH 202/532] feat(react/ribbon): port file notes --- .vscode/snippets.code-snippets | 5 + apps/client/src/services/open.ts | 4 +- .../src/widgets/react/FormFileUpload.tsx | 16 +- .../src/widgets/ribbon/FilePropertiesTab.tsx | 97 +++++++++++ apps/client/src/widgets/ribbon/Ribbon.tsx | 8 +- apps/client/src/widgets/ribbon/style.css | 18 ++ .../widgets/ribbon_widgets/file_properties.ts | 155 ------------------ 7 files changed, 141 insertions(+), 162 deletions(-) create mode 100644 apps/client/src/widgets/ribbon/FilePropertiesTab.tsx delete mode 100644 apps/client/src/widgets/ribbon_widgets/file_properties.ts diff --git a/.vscode/snippets.code-snippets b/.vscode/snippets.code-snippets index 77b251a4a..c7255af76 100644 --- a/.vscode/snippets.code-snippets +++ b/.vscode/snippets.code-snippets @@ -20,5 +20,10 @@ "scope": "typescript", "prefix": "jqf", "body": ["private $${1:name}!: JQuery;"] + }, + "region": { + "scope": "css", + "prefix": "region", + "body": ["/* #region ${1:name} */\n$0\n/* #endregion */"] } } diff --git a/apps/client/src/services/open.ts b/apps/client/src/services/open.ts index 7dab08a1a..0bcda805e 100644 --- a/apps/client/src/services/open.ts +++ b/apps/client/src/services/open.ts @@ -35,7 +35,7 @@ function download(url: string) { } } -function downloadFileNote(noteId: string) { +export function downloadFileNote(noteId: string) { const url = `${getFileUrl("notes", noteId)}?${Date.now()}`; // don't use cache download(url); @@ -163,7 +163,7 @@ async function openExternally(type: string, entityId: string, mime: string) { } } -const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime); +export const openNoteExternally = async (noteId: string, mime: string) => await openExternally("notes", noteId, mime); const openAttachmentExternally = async (attachmentId: string, mime: string) => await openExternally("attachments", attachmentId, mime); function getHost() { diff --git a/apps/client/src/widgets/react/FormFileUpload.tsx b/apps/client/src/widgets/react/FormFileUpload.tsx index c2f53a5a9..0274aff8e 100644 --- a/apps/client/src/widgets/react/FormFileUpload.tsx +++ b/apps/client/src/widgets/react/FormFileUpload.tsx @@ -1,12 +1,22 @@ +import { Ref } from "preact"; + interface FormFileUploadProps { + name?: string; onChange: (files: FileList | null) => void; multiple?: boolean; + hidden?: boolean; + inputRef?: Ref; } -export default function FormFileUpload({ onChange, multiple }: FormFileUploadProps) { +export default function FormFileUpload({ inputRef, name, onChange, multiple, hidden }: FormFileUploadProps) { return ( - ) diff --git a/apps/client/src/widgets/ribbon/FilePropertiesTab.tsx b/apps/client/src/widgets/ribbon/FilePropertiesTab.tsx new file mode 100644 index 000000000..2af652135 --- /dev/null +++ b/apps/client/src/widgets/ribbon/FilePropertiesTab.tsx @@ -0,0 +1,97 @@ +import { useEffect, useRef, useState } from "preact/hooks"; +import { t } from "../../services/i18n"; +import { formatSize } from "../../services/utils"; +import FormFileUpload from "../react/FormFileUpload"; +import { useNoteLabel, useTriliumEventBeta } from "../react/hooks"; +import { TabContext } from "./ribbon-interface"; +import FBlob from "../../entities/fblob"; +import Button from "../react/Button"; +import protected_session_holder from "../../services/protected_session_holder"; +import { downloadFileNote, openNoteExternally } from "../../services/open"; +import toast from "../../services/toast"; +import server from "../../services/server"; + +export default function FilePropertiesTab({ note }: TabContext) { + const [ originalFileName ] = useNoteLabel(note, "originalFileName"); + const [ blob, setBlob ] = useState(); + const canAccessProtectedNote = !note?.isProtected || protected_session_holder.isProtectedSessionAvailable(); + const inputRef = useRef(null); + + function refresh() { + note?.getBlob().then(setBlob); + } + + useEffect(refresh, [ note?.noteId ]); + useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + if (note && loadResults.hasRevisionForNote(note.noteId)) { + refresh(); + } + }); + + return ( +
    + {note && ( + + + + + + + + + + + + + + + + + +
    {t("file_properties.note_id")}:{note.noteId}{t("file_properties.original_file_name")}:{originalFileName ?? "?"}
    {t("file_properties.file_type")}:{note.mime}{t("file_properties.file_size")}:{formatSize(blob?.contentLength ?? 0)}
    +
    +
    +
    + )} +
    + ); +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index 14d448d45..bfff47091 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -15,6 +15,7 @@ import EditedNotesTab from "./EditedNotesTab"; import NotePropertiesTab from "./NotePropertiesTab"; import NoteInfoTab from "./NoteInfoTab"; import SimilarNotesTab from "./SimilarNotesTab"; +import FilePropertiesTab from "./FilePropertiesTab"; interface TitleContext { note: FNote | null | undefined; @@ -77,9 +78,12 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ activate: true }, { - // FilePropertiesWidget title: t("file_properties.title"), - icon: "bx bx-file" + icon: "bx bx-file", + content: FilePropertiesTab, + show: ({ note }) => note?.type === "file", + toggleCommand: "toggleRibbonTabFileProperties", + activate: true }, { // ImagePropertiesWidget diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 7200e6a00..87baedfc2 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -203,4 +203,22 @@ white-space: nowrap; overflow: hidden; } +/* #endregion */ + +/* #region File Properties */ +.file-table { + width: 100%; + margin-top: 10px; +} + +.file-table th, .file-table td { + padding: 5px; + overflow-wrap: anywhere; +} + +.file-buttons { + padding: 10px; + display: flex; + justify-content: space-evenly; +} /* #endregion */ \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/file_properties.ts b/apps/client/src/widgets/ribbon_widgets/file_properties.ts deleted file mode 100644 index 4d4b419c3..000000000 --- a/apps/client/src/widgets/ribbon_widgets/file_properties.ts +++ /dev/null @@ -1,155 +0,0 @@ -import server from "../../services/server.js"; -import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import toastService from "../../services/toast.js"; -import openService from "../../services/open.js"; -import utils from "../../services/utils.js"; -import protectedSessionHolder from "../../services/protected_session_holder.js"; -import { t } from "../../services/i18n.js"; -import type FNote from "../../entities/fnote.js"; - -const TPL = /*html*/` -
    - - - - - - - - - - - - - - - - - - - -
    ${t("file_properties.note_id")}:${t("file_properties.original_file_name")}:
    ${t("file_properties.file_type")}:${t("file_properties.file_size")}:
    -
    - - - - - - - -
    -
    -
    `; - -export default class FilePropertiesWidget extends NoteContextAwareWidget { - - private $fileNoteId!: JQuery; - private $fileName!: JQuery; - private $fileType!: JQuery; - private $fileSize!: JQuery; - private $downloadButton!: JQuery; - private $openButton!: JQuery; - private $uploadNewRevisionButton!: JQuery; - private $uploadNewRevisionInput!: JQuery; - - get name() { - return "fileProperties"; - } - - get toggleCommand() { - return "toggleRibbonTabFileProperties"; - } - - isEnabled() { - return this.note && this.note.type === "file"; - } - - getTitle() { - return { - show: this.isEnabled(), - activate: true, - - }; - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - this.$fileNoteId = this.$widget.find(".file-note-id"); - this.$fileName = this.$widget.find(".file-filename"); - this.$fileType = this.$widget.find(".file-filetype"); - this.$fileSize = this.$widget.find(".file-filesize"); - this.$downloadButton = this.$widget.find(".file-download"); - this.$openButton = this.$widget.find(".file-open"); - this.$uploadNewRevisionButton = this.$widget.find(".file-upload-new-revision"); - this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input"); - - this.$downloadButton.on("click", () => this.noteId && openService.downloadFileNote(this.noteId)); - this.$openButton.on("click", () => this.noteId && this.note && openService.openNoteExternally(this.noteId, this.note.mime)); - - this.$uploadNewRevisionButton.on("click", () => { - this.$uploadNewRevisionInput.trigger("click"); - }); - - this.$uploadNewRevisionInput.on("change", async () => { - const fileToUpload = this.$uploadNewRevisionInput[0].files[0]; // copy to allow reset below - this.$uploadNewRevisionInput.val(""); - - const result = await server.upload(`notes/${this.noteId}/file`, fileToUpload); - - if (result.uploaded) { - toastService.showMessage(t("file_properties.upload_success")); - - this.refresh(); - } else { - toastService.showError(t("file_properties.upload_failed")); - } - }); - } - - async refreshWithNote(note: FNote) { - this.$widget.show(); - - if (!this.note) { - return; - } - - this.$fileNoteId.text(note.noteId); - this.$fileName.text(note.getLabelValue("originalFileName") || "?"); - this.$fileType.text(note.mime); - - const blob = await this.note.getBlob(); - - this.$fileSize.text(utils.formatSize(blob?.contentLength ?? 0)); - - // open doesn't work for protected notes since it works through a browser which isn't in protected session - this.$openButton.toggle(!note.isProtected); - this.$downloadButton.toggle(!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()); - this.$uploadNewRevisionButton.toggle(!note.isProtected || protectedSessionHolder.isProtectedSessionAvailable()); - } -} From 21683db0b887d4459226a4a9737b05516a5ce9c1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 22 Aug 2025 20:25:15 +0300 Subject: [PATCH 203/532] refactor(react/ribbon): dedicated component for file upload --- apps/client/src/widgets/react/Button.tsx | 2 +- .../src/widgets/react/FormFileUpload.tsx | 25 +++++++++++++++++++ .../src/widgets/ribbon/FilePropertiesTab.tsx | 13 +++------- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/apps/client/src/widgets/react/Button.tsx b/apps/client/src/widgets/react/Button.tsx index 0ab6eae48..ddfe225fe 100644 --- a/apps/client/src/widgets/react/Button.tsx +++ b/apps/client/src/widgets/react/Button.tsx @@ -4,7 +4,7 @@ import { useRef, useMemo } from "preact/hooks"; import { memo } from "preact/compat"; import { CommandNames } from "../../components/app_context"; -interface ButtonProps { +export interface ButtonProps { name?: string; /** Reference to the button element. Mostly useful for requesting focus. */ buttonRef?: RefObject; diff --git a/apps/client/src/widgets/react/FormFileUpload.tsx b/apps/client/src/widgets/react/FormFileUpload.tsx index 0274aff8e..c43008b09 100644 --- a/apps/client/src/widgets/react/FormFileUpload.tsx +++ b/apps/client/src/widgets/react/FormFileUpload.tsx @@ -1,4 +1,6 @@ import { Ref } from "preact"; +import Button, { ButtonProps } from "./Button"; +import { useRef } from "preact/hooks"; interface FormFileUploadProps { name?: string; @@ -20,4 +22,27 @@ export default function FormFileUpload({ inputRef, name, onChange, multiple, hid onChange={e => onChange((e.target as HTMLInputElement).files)} /> ) +} + +/** + * Combination of a button with a hidden file upload field. + * + * @param param the change listener for the file upload and the properties for the button. + */ +export function FormFileUploadButton({ onChange, ...buttonProps }: Omit & Pick) { + const inputRef = useRef(null); + + return ( + <> + -
    +
    `; export default class NoteMapRibbonWidget extends NoteContextAwareWidget { private openState!: "small" | "full"; - private noteMapWidget: NoteMapWidget; private $container!: JQuery; private $openFullButton!: JQuery; private $collapseButton!: JQuery; - constructor() { - super(); - - this.noteMapWidget = new NoteMapWidget("ribbon"); - this.child(this.noteMapWidget); - } - - get name() { - return "noteMap"; - } - - get toggleCommand() { - return "toggleRibbonTabNoteMap"; - } - - getTitle() { - return { - show: this.isEnabled() - }; - } doRender() { this.$widget = $(TPL); From 3e3cc8c54115f0a24deacc5b581c06a4cc305374 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 00:19:26 +0300 Subject: [PATCH 218/532] client/settings/disable motion: refactor --- .../src/widgets/containers/root_container.ts | 19 +++++++++++++++++-- .../type_widgets/options/appearance.tsx | 2 +- apps/server/src/assets/views/desktop.ejs | 9 +-------- apps/server/src/routes/index.ts | 1 - 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/client/src/widgets/containers/root_container.ts b/apps/client/src/widgets/containers/root_container.ts index c941cdd88..ef2491b64 100644 --- a/apps/client/src/widgets/containers/root_container.ts +++ b/apps/client/src/widgets/containers/root_container.ts @@ -1,6 +1,8 @@ -import utils from "../../services/utils.js"; -import type BasicWidget from "../basic_widget.js"; +import { EventData } from "../../components/app_context.js"; import FlexContainer from "./flex_container.js"; +import options from "../../services/options.js"; +import type BasicWidget from "../basic_widget.js"; +import utils from "../../services/utils.js"; /** * The root container is the top-most widget/container, from which the entire layout derives. @@ -20,6 +22,7 @@ export default class RootContainer extends FlexContainer { this.id("root-widget"); this.css("height", "100dvh"); this.originalViewportHeight = getViewportHeight(); + } render(): JQuery { @@ -27,15 +30,27 @@ export default class RootContainer extends FlexContainer { window.visualViewport?.addEventListener("resize", () => this.#onMobileResize()); } + this.#setMotion(options.is("motionEnabled")); + return super.render(); } + entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { + if (loadResults.isOptionReloaded("motionEnabled")) { + this.#setMotion(options.is("motionEnabled")); + } + } + #onMobileResize() { const currentViewportHeight = getViewportHeight(); const isKeyboardOpened = (currentViewportHeight < this.originalViewportHeight); this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened); } + #setMotion(enabled: boolean) { + document.body.classList.toggle("motion-disabled", !enabled); + jQuery.fx.off = !enabled; + } } function getViewportHeight() { diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index b54c8acc8..de22a9b1b 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -247,7 +247,7 @@ function ElectronIntegration() { } function Performance() { - const [ motionEnabled, setMotionEnabled ] = useTriliumOptionBool("motionEnabled", true); + const [ motionEnabled, setMotionEnabled ] = useTriliumOptionBool("motionEnabled"); return diff --git a/apps/server/src/assets/views/desktop.ejs b/apps/server/src/assets/views/desktop.ejs index 1158612be..374ed0b8c 100644 --- a/apps/server/src/assets/views/desktop.ejs +++ b/apps/server/src/assets/views/desktop.ejs @@ -9,7 +9,7 @@ Trilium Notes - + - - diff --git a/apps/server/src/routes/index.ts b/apps/server/src/routes/index.ts index 9fb682c14..79a40f186 100644 --- a/apps/server/src/routes/index.ts +++ b/apps/server/src/routes/index.ts @@ -53,7 +53,6 @@ function index(req: Request, res: Response) { isDev, isMainWindow: view === "mobile" ? true : !req.query.extraWindow, isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), - motionEnabled: options.motionEnabled === "true", maxContentWidth: Math.max(640, parseInt(options.maxContentWidth)), triliumVersion: packageJson.version, assetPath: assetPath, From 0a9c0234e23fef3b8c63669a7450ee2b84eb5d2b Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 00:38:06 +0300 Subject: [PATCH 219/532] client/settings/disable motion: update translation --- apps/client/src/translations/en/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 10fbc79aa..4537d09f6 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1115,7 +1115,7 @@ }, "ui-performance": { "title": "Performance", - "enable-motion": "Use transitions and animations" + "enable-motion": "Enable transitions and animations" }, "ai_llm": { "not_started": "Not started", From 85dd99a3c467ffce150e3e21aa0720a5fce7cd0b Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 00:43:49 +0300 Subject: [PATCH 220/532] client/settings/disable shadows: add an option to enable or disable shadows --- apps/server/src/routes/api/options.ts | 1 + apps/server/src/services/options_init.ts | 2 ++ packages/commons/src/lib/options_interface.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index 90bac90d3..7e53f1ab3 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -64,6 +64,7 @@ const ALLOWED_OPTIONS = new Set([ "weeklyBackupEnabled", "monthlyBackupEnabled", "motionEnabled", + "shadowsEnabled", "maxContentWidth", "compressImages", "downloadImagesAutomatically", diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index 4d0939310..c5e809789 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -153,6 +153,8 @@ const defaultOptions: DefaultOption[] = [ isSynced: false }, { name: "motionEnabled", value: "true", isSynced: false }, + { name: "shadowsEnabled", value: "true", isSynced: false }, + // Internationalization { name: "locale", value: "en", isSynced: true }, diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index ad4269fe8..781005722 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -94,6 +94,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Sat, 23 Aug 2025 00:49:35 +0300 Subject: [PATCH 221/532] client/settings/disable shadows: add the corresponding checkbox in the Appearance settings page --- apps/client/src/translations/en/translation.json | 3 ++- .../widgets/type_widgets/options/appearance.tsx | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 4537d09f6..986ea78de 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1115,7 +1115,8 @@ }, "ui-performance": { "title": "Performance", - "enable-motion": "Enable transitions and animations" + "enable-motion": "Enable transitions and animations", + "enable-shadows": "Enable shadows" }, "ai_llm": { "not_started": "Not started", diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index de22a9b1b..ce9dce3d4 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -248,13 +248,21 @@ function ElectronIntegration() { function Performance() { const [ motionEnabled, setMotionEnabled ] = useTriliumOptionBool("motionEnabled"); + const [ shadowsEnabled, setShadowsEnabled ] = useTriliumOptionBool("shadowsEnabled"); + return - + + + + } From 7468d6147a56de1430d00fb4665e2ba3554df548 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 00:55:46 +0300 Subject: [PATCH 222/532] client/settings/disable shadows: react to the option change --- apps/client/src/widgets/containers/root_container.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/client/src/widgets/containers/root_container.ts b/apps/client/src/widgets/containers/root_container.ts index ef2491b64..7bff4e369 100644 --- a/apps/client/src/widgets/containers/root_container.ts +++ b/apps/client/src/widgets/containers/root_container.ts @@ -31,6 +31,7 @@ export default class RootContainer extends FlexContainer { } this.#setMotion(options.is("motionEnabled")); + this.#setShadows(options.is("shadowsEnabled")); return super.render(); } @@ -39,6 +40,10 @@ export default class RootContainer extends FlexContainer { if (loadResults.isOptionReloaded("motionEnabled")) { this.#setMotion(options.is("motionEnabled")); } + + if (loadResults.isOptionReloaded("shadowsEnabled")) { + this.#setShadows(options.is("shadowsEnabled")); + } } #onMobileResize() { @@ -51,6 +56,10 @@ export default class RootContainer extends FlexContainer { document.body.classList.toggle("motion-disabled", !enabled); jQuery.fx.off = !enabled; } + + #setShadows(enabled: boolean) { + document.body.classList.toggle("shadows-disabled", !enabled); + } } function getViewportHeight() { From d35dbca18b6ff6c1b14b2da7a0a83b30740a51ba Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 00:58:50 +0300 Subject: [PATCH 223/532] client/settings/disable shadows: add the CSS implementation --- apps/client/src/stylesheets/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 9c15cfc60..cb6ddf099 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -36,6 +36,13 @@ body#trilium-app.motion-disabled *::after { animation: none !important; } +body#trilium-app.shadows-disabled *, +body#trilium-app.shadows-disabled *::before, +body#trilium-app.shadows-disabled *::after { + /* Disable shadows */ + box-shadow: none !important; +} + .table { --bs-table-bg: transparent !important; } From 94ddad3c49467f26999becd591b314e8fb83ec94 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 01:15:00 +0300 Subject: [PATCH 224/532] client/settings/disable backdrop effects: add an option to enable or disable backdrop effects --- apps/server/src/routes/api/options.ts | 1 + apps/server/src/services/options_init.ts | 1 + packages/commons/src/lib/options_interface.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/apps/server/src/routes/api/options.ts b/apps/server/src/routes/api/options.ts index 7e53f1ab3..290dd5743 100644 --- a/apps/server/src/routes/api/options.ts +++ b/apps/server/src/routes/api/options.ts @@ -65,6 +65,7 @@ const ALLOWED_OPTIONS = new Set([ "monthlyBackupEnabled", "motionEnabled", "shadowsEnabled", + "backdropEffectsEnabled", "maxContentWidth", "compressImages", "downloadImagesAutomatically", diff --git a/apps/server/src/services/options_init.ts b/apps/server/src/services/options_init.ts index c5e809789..e2c0a7389 100644 --- a/apps/server/src/services/options_init.ts +++ b/apps/server/src/services/options_init.ts @@ -154,6 +154,7 @@ const defaultOptions: DefaultOption[] = [ }, { name: "motionEnabled", value: "true", isSynced: false }, { name: "shadowsEnabled", value: "true", isSynced: false }, + { name: "backdropEffectsEnabled", value: "true", isSynced: false }, // Internationalization diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index 781005722..ae63d0250 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -95,6 +95,7 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions Date: Sat, 23 Aug 2025 01:20:54 +0300 Subject: [PATCH 225/532] client/settings/disable backdrop effects: add the corresponding checkbox in the Appearance settings page --- apps/client/src/translations/en/translation.json | 3 ++- .../client/src/widgets/type_widgets/options/appearance.tsx | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 986ea78de..6a7a966c9 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1116,7 +1116,8 @@ "ui-performance": { "title": "Performance", "enable-motion": "Enable transitions and animations", - "enable-shadows": "Enable shadows" + "enable-shadows": "Enable shadows", + "enable-backdrop-effects": "Enable background effects for menus, popups and panels" }, "ai_llm": { "not_started": "Not started", diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index ce9dce3d4..ba36a75d6 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -249,6 +249,7 @@ function ElectronIntegration() { function Performance() { const [ motionEnabled, setMotionEnabled ] = useTriliumOptionBool("motionEnabled"); const [ shadowsEnabled, setShadowsEnabled ] = useTriliumOptionBool("shadowsEnabled"); + const [ backdropEffectsEnabled, setBackdropEffectsEnabled ] = useTriliumOptionBool("backdropEffectsEnabled"); return @@ -264,6 +265,12 @@ function Performance() { currentValue={shadowsEnabled} onChange={setShadowsEnabled} /> + + + } From 8cca6637f7a3ea6685bcfc70493c55ba49fd26a3 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 01:23:20 +0300 Subject: [PATCH 226/532] client/settings/disable backdrop effects: react to the option change --- apps/client/src/widgets/containers/root_container.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/client/src/widgets/containers/root_container.ts b/apps/client/src/widgets/containers/root_container.ts index 7bff4e369..f29ce2940 100644 --- a/apps/client/src/widgets/containers/root_container.ts +++ b/apps/client/src/widgets/containers/root_container.ts @@ -32,6 +32,7 @@ export default class RootContainer extends FlexContainer { this.#setMotion(options.is("motionEnabled")); this.#setShadows(options.is("shadowsEnabled")); + this.#setBackdropEffects(options.is("backdropEffectsEnabled")); return super.render(); } @@ -44,6 +45,10 @@ export default class RootContainer extends FlexContainer { if (loadResults.isOptionReloaded("shadowsEnabled")) { this.#setShadows(options.is("shadowsEnabled")); } + + if (loadResults.isOptionReloaded("backdropEffectsEnabled")) { + this.#setBackdropEffects(options.is("backdropEffectsEnabled")); + } } #onMobileResize() { @@ -60,6 +65,10 @@ export default class RootContainer extends FlexContainer { #setShadows(enabled: boolean) { document.body.classList.toggle("shadows-disabled", !enabled); } + + #setBackdropEffects(enabled: boolean) { + document.body.classList.toggle("backdrop-effects-disabled", !enabled); + } } function getViewportHeight() { From 2e11681b5290c50656da9b4872e7f07960383f59 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 01:26:21 +0300 Subject: [PATCH 227/532] client/settings/disable backdrop effects: add the CSS implementation --- apps/client/src/stylesheets/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index cb6ddf099..46ad3559d 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -43,6 +43,13 @@ body#trilium-app.shadows-disabled *::after { box-shadow: none !important; } +body#trilium-app.backdrop-effects-disabled *, +body#trilium-app.backdrop-effects-disabled *::before, +body#trilium-app.backdrop-effects-disabled *::after { + /* Disable backdrop effects */ + backdrop-filter: none !important; +} + .table { --bs-table-bg: transparent !important; } From 9aab606debfb538300ce16ae4688d333283a9504 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 02:15:06 +0300 Subject: [PATCH 228/532] style: improve the support of disabled backdrop effects --- apps/client/src/stylesheets/theme-next-dark.css | 1 + apps/client/src/stylesheets/theme-next-light.css | 1 + apps/client/src/stylesheets/theme-next/base.css | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/apps/client/src/stylesheets/theme-next-dark.css b/apps/client/src/stylesheets/theme-next-dark.css index b27627bd1..b32e29f2b 100644 --- a/apps/client/src/stylesheets/theme-next-dark.css +++ b/apps/client/src/stylesheets/theme-next-dark.css @@ -89,6 +89,7 @@ --menu-text-color: #e3e3e3; --menu-background-color: #222222d9; + --menu-background-color-no-backdrop: #1b1b1b; --menu-item-icon-color: #8c8c8c; --menu-item-disabled-opacity: 0.5; --menu-item-keyboard-shortcut-color: #ffffff8f; diff --git a/apps/client/src/stylesheets/theme-next-light.css b/apps/client/src/stylesheets/theme-next-light.css index ff82e99ba..deb9fb2ec 100644 --- a/apps/client/src/stylesheets/theme-next-light.css +++ b/apps/client/src/stylesheets/theme-next-light.css @@ -83,6 +83,7 @@ --menu-text-color: #272727; --menu-background-color: #ffffffd9; + --menu-background-color-no-backdrop: #fdfdfd; --menu-item-icon-color: #727272; --menu-item-disabled-opacity: 0.6; --menu-item-keyboard-shortcut-color: #666666a8; diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index c992f7ecb..27fbf3557 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -83,6 +83,12 @@ --tab-note-icons: true; } +body.backdrop-effects-disabled { + /* Backdrop effects are disabled, replace the menu background color with the + * no-backdrop fallback color */ + --menu-background-color: var(--menu-background-color-no-backdrop); +} + /* * MENUS * From f7f98aa9a34c211b001d17f94329c7234cb06a11 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Sat, 23 Aug 2025 03:10:51 +0300 Subject: [PATCH 229/532] client/settings/ui-performance-settings: improve code formatting --- apps/client/src/widgets/containers/root_container.ts | 1 - apps/client/src/widgets/type_widgets/options/appearance.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/client/src/widgets/containers/root_container.ts b/apps/client/src/widgets/containers/root_container.ts index f29ce2940..6c2d87521 100644 --- a/apps/client/src/widgets/containers/root_container.ts +++ b/apps/client/src/widgets/containers/root_container.ts @@ -22,7 +22,6 @@ export default class RootContainer extends FlexContainer { this.id("root-widget"); this.css("height", "100dvh"); this.originalViewportHeight = getViewportHeight(); - } render(): JQuery { diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index ba36a75d6..9ac51b613 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -251,7 +251,6 @@ function Performance() { const [ shadowsEnabled, setShadowsEnabled ] = useTriliumOptionBool("shadowsEnabled"); const [ backdropEffectsEnabled, setBackdropEffectsEnabled ] = useTriliumOptionBool("backdropEffectsEnabled"); - return Date: Sat, 23 Aug 2025 03:22:59 +0300 Subject: [PATCH 230/532] client/settings/ui-performance-settings: remove form groups --- .../type_widgets/options/appearance.tsx | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index 9ac51b613..a6f5a77c6 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -252,24 +252,20 @@ function Performance() { const [ backdropEffectsEnabled, setBackdropEffectsEnabled ] = useTriliumOptionBool("backdropEffectsEnabled"); return - - - - - - - - - + + + + + } From eaa84a6b3940ad946b986e4e471e926ecb6ddb96 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 00:23:57 +0000 Subject: [PATCH 231/532] chore(deps): update dependency @types/jquery to v3.5.33 --- apps/client/package.json | 2 +- packages/ckeditor5/package.json | 2 +- pnpm-lock.yaml | 28 ++++++++++++++++------------ 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index e733d0e07..573f3b64e 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -62,7 +62,7 @@ "@ckeditor/ckeditor5-inspector": "5.0.0", "@preact/preset-vite": "2.10.2", "@types/bootstrap": "5.2.10", - "@types/jquery": "3.5.32", + "@types/jquery": "3.5.33", "@types/leaflet": "1.9.20", "@types/leaflet-gpx": "1.3.7", "@types/mark.js": "8.11.12", diff --git a/packages/ckeditor5/package.json b/packages/ckeditor5/package.json index 7a2970aaf..49562a8cc 100644 --- a/packages/ckeditor5/package.json +++ b/packages/ckeditor5/package.json @@ -39,6 +39,6 @@ "ckeditor5-premium-features": "46.0.2" }, "devDependencies": { - "@types/jquery": "3.5.32" + "@types/jquery": "3.5.33" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3b030a0f..0838a3cd6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -311,8 +311,8 @@ importers: specifier: 5.2.10 version: 5.2.10 '@types/jquery': - specifier: 3.5.32 - version: 3.5.32 + specifier: 3.5.33 + version: 3.5.33 '@types/leaflet': specifier: 1.9.20 version: 1.9.20 @@ -881,8 +881,8 @@ importers: version: 46.0.2(bufferutil@4.0.9)(ckeditor5@46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41))(utf-8-validate@6.0.5) devDependencies: '@types/jquery': - specifier: 3.5.32 - version: 3.5.32 + specifier: 3.5.33 + version: 3.5.33 packages/ckeditor5-admonition: devDependencies: @@ -5734,8 +5734,8 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - '@types/jquery@3.5.32': - resolution: {integrity: sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==} + '@types/jquery@3.5.33': + resolution: {integrity: sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==} '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} @@ -17101,8 +17101,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-decoupled@46.0.2': dependencies: @@ -17121,6 +17119,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-multi-root@46.0.2': dependencies: @@ -17179,6 +17179,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-undo': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-export-inline-styles@46.0.2': dependencies: @@ -17253,6 +17255,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-heading@46.0.2': dependencies: @@ -17310,8 +17314,6 @@ snapshots: '@ckeditor/ckeditor5-widget': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-icons@46.0.2': {} @@ -21837,7 +21839,7 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 - '@types/jquery@3.5.32': + '@types/jquery@3.5.33': dependencies: '@types/sizzle': 2.3.9 @@ -21885,7 +21887,7 @@ snapshots: '@types/mark.js@8.11.12': dependencies: - '@types/jquery': 3.5.32 + '@types/jquery': 3.5.33 '@types/mdast@4.0.4': dependencies: @@ -23554,6 +23556,8 @@ snapshots: ckeditor5-collaboration@46.0.2: dependencies: '@ckeditor/ckeditor5-collaboration-core': 46.0.2 + transitivePeerDependencies: + - supports-color ckeditor5-premium-features@46.0.2(bufferutil@4.0.9)(ckeditor5@46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41))(utf-8-validate@6.0.5): dependencies: From 419dc7edfbbfdefe4787ef712f7a4952f83e303a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 00:24:33 +0000 Subject: [PATCH 232/532] fix(deps): update dependency mermaid to v11.10.1 --- apps/client/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 40 +++++++++++++++++++++++----------------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index e733d0e07..36367ac25 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -47,7 +47,7 @@ "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", "marked": "16.2.0", - "mermaid": "11.10.0", + "mermaid": "11.10.1", "mind-elixir": "5.0.6", "normalize.css": "8.0.1", "panzoom": "9.4.3", diff --git a/package.json b/package.json index 74f97a7a0..41d65b90a 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "@nx/js": "patches/@nx__js.patch" }, "overrides": { - "mermaid": "11.10.0", + "mermaid": "11.10.1", "preact": "10.27.1", "roughjs": "4.6.6", "@types/express-serve-static-core": "5.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3b030a0f..92df8c12d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - mermaid: 11.10.0 + mermaid: 11.10.1 preact: 10.27.1 roughjs: 4.6.6 '@types/express-serve-static-core': 5.0.7 @@ -188,7 +188,7 @@ importers: version: 0.1.3(@types/leaflet@1.9.20)(leaflet@1.9.4)(maplibre-gl@5.6.1) '@mermaid-js/layout-elk': specifier: 0.1.9 - version: 0.1.9(mermaid@11.10.0) + version: 0.1.9(mermaid@11.10.1) '@mind-elixir/node-menu': specifier: 5.0.0 version: 5.0.0(mind-elixir@5.0.6) @@ -271,8 +271,8 @@ importers: specifier: 16.2.0 version: 16.2.0 mermaid: - specifier: 11.10.0 - version: 11.10.0 + specifier: 11.10.1 + version: 11.10.1 mind-elixir: specifier: 5.0.6 version: 5.0.6 @@ -3752,7 +3752,7 @@ packages: '@mermaid-js/layout-elk@0.1.9': resolution: {integrity: sha512-HuvaqFZBr6yT9PpWYockvKAZPJVd89yn/UjOYPxhzbZxlybL2v+2BjVCg7MVH6vRs1irUohb/s42HEdec1CCZw==} peerDependencies: - mermaid: 11.10.0 + mermaid: 11.10.1 '@mermaid-js/parser@0.6.2': resolution: {integrity: sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==} @@ -10798,8 +10798,8 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - mermaid@11.10.0: - resolution: {integrity: sha512-oQsFzPBy9xlpnGxUqLbVY8pvknLlsNIJ0NWwi8SUJjhbP1IT0E0o1lfhU4iYV3ubpy+xkzkaOyDUQMn06vQElQ==} + mermaid@11.10.1: + resolution: {integrity: sha512-0PdeADVWURz7VMAX0+MiMcgfxFKY4aweSGsjgFihe3XlMKNqmai/cugMrqTd3WNHM93V+K+AZL6Wu6tB5HmxRw==} methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} @@ -16863,8 +16863,6 @@ snapshots: '@ckeditor/ckeditor5-core': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-code-block@46.0.2(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -17090,8 +17088,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-classic@46.0.2': dependencies: @@ -17101,8 +17097,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-decoupled@46.0.2': dependencies: @@ -17121,6 +17115,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-multi-root@46.0.2': dependencies: @@ -17143,6 +17139,8 @@ snapshots: '@ckeditor/ckeditor5-table': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-emoji@46.0.2': dependencies: @@ -17179,6 +17177,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-undo': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-export-inline-styles@46.0.2': dependencies: @@ -17253,6 +17253,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-heading@46.0.2': dependencies: @@ -17610,6 +17612,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-restricted-editing@46.0.2': dependencies: @@ -18663,7 +18667,7 @@ snapshots: '@excalidraw/mermaid-to-excalidraw@1.1.2': dependencies: '@excalidraw/markdown-to-text': 0.1.2 - mermaid: 11.10.0 + mermaid: 11.10.1 nanoid: 5.1.5 transitivePeerDependencies: - supports-color @@ -19571,11 +19575,11 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} - '@mermaid-js/layout-elk@0.1.9(mermaid@11.10.0)': + '@mermaid-js/layout-elk@0.1.9(mermaid@11.10.1)': dependencies: d3: 7.9.0 elkjs: 0.9.3 - mermaid: 11.10.0 + mermaid: 11.10.1 '@mermaid-js/parser@0.6.2': dependencies: @@ -23554,6 +23558,8 @@ snapshots: ckeditor5-collaboration@46.0.2: dependencies: '@ckeditor/ckeditor5-collaboration-core': 46.0.2 + transitivePeerDependencies: + - supports-color ckeditor5-premium-features@46.0.2(bufferutil@4.0.9)(ckeditor5@46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41))(utf-8-validate@6.0.5): dependencies: @@ -28167,7 +28173,7 @@ snapshots: merge2@1.4.1: {} - mermaid@11.10.0: + mermaid@11.10.1: dependencies: '@braintree/sanitize-url': 7.1.1 '@iconify/utils': 2.3.0 From 4aa7e211f320d3c08abb0cf8ebea1aa3f0747ead Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 23 Aug 2025 00:25:46 +0000 Subject: [PATCH 233/532] fix(deps): update eslint monorepo to v9.34.0 --- _regroup/package.json | 2 +- apps/client/package.json | 2 +- pnpm-lock.yaml | 230 ++++++++++++++++++++------------------- 3 files changed, 121 insertions(+), 113 deletions(-) diff --git a/_regroup/package.json b/_regroup/package.json index 82a620e93..4d94fa43c 100644 --- a/_regroup/package.json +++ b/_regroup/package.json @@ -41,7 +41,7 @@ "@types/node": "22.17.2", "@types/yargs": "17.0.33", "@vitest/coverage-v8": "3.2.4", - "eslint": "9.33.0", + "eslint": "9.34.0", "eslint-plugin-simple-import-sort": "12.1.1", "esm": "3.2.25", "jsdoc": "4.0.4", diff --git a/apps/client/package.json b/apps/client/package.json index e733d0e07..9fea7481f 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -10,7 +10,7 @@ "url": "https://github.com/TriliumNext/Notes" }, "dependencies": { - "@eslint/js": "9.33.0", + "@eslint/js": "9.34.0", "@excalidraw/excalidraw": "0.18.0", "@fullcalendar/core": "6.1.19", "@fullcalendar/daygrid": "6.1.19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3b030a0f..8ee0a521c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,22 +50,22 @@ importers: version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.25.9)(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/eslint': specifier: 21.3.11 - version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) + version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/eslint-plugin': specifier: 21.3.11 - version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-config-prettier@10.1.8(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2) + version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)))(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2) '@nx/express': specifier: 21.3.11 - version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.33.0(jiti@2.5.1))(express@4.21.2)(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2) + version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.34.0(jiti@2.5.1))(express@4.21.2)(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2) '@nx/js': specifier: 21.3.11 version: 21.3.11(patch_hash=7201af3a8fb4840b046e4e18cc2758fa67ee3d0cf11d0783869dc828cfc79fc7)(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/node': specifier: 21.3.11 - version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2) + version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2) '@nx/playwright': specifier: 21.3.11 - version: 21.3.11(@babel/traverse@7.28.0)(@playwright/test@1.55.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2) + version: 21.3.11(@babel/traverse@7.28.0)(@playwright/test@1.55.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2) '@nx/vite': specifier: 21.3.11 version: 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1))(vitest@3.2.4) @@ -104,13 +104,13 @@ importers: version: 0.25.9 eslint: specifier: ^9.8.0 - version: 9.33.0(jiti@2.5.1) + version: 9.34.0(jiti@2.5.1) eslint-config-prettier: specifier: ^10.0.0 - version: 10.1.8(eslint@9.33.0(jiti@2.5.1)) + version: 10.1.8(eslint@9.34.0(jiti@2.5.1)) eslint-plugin-playwright: specifier: ^2.0.0 - version: 2.2.2(eslint@9.33.0(jiti@2.5.1)) + version: 2.2.2(eslint@9.34.0(jiti@2.5.1)) happy-dom: specifier: ~18.0.0 version: 18.0.1 @@ -143,7 +143,7 @@ importers: version: 5.9.2 typescript-eslint: specifier: ^8.19.0 - version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) upath: specifier: 2.0.1 version: 2.0.1 @@ -160,8 +160,8 @@ importers: apps/client: dependencies: '@eslint/js': - specifier: 9.33.0 - version: 9.33.0 + specifier: 9.34.0 + version: 9.34.0 '@excalidraw/excalidraw': specifier: 0.18.0 version: 0.18.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.1.0(react@16.14.0))(react@16.14.0) @@ -806,10 +806,10 @@ importers: devDependencies: '@eslint/compat': specifier: ^1.2.5 - version: 1.3.2(eslint@9.33.0(jiti@2.5.1)) + version: 1.3.2(eslint@9.34.0(jiti@2.5.1)) '@eslint/js': specifier: ^9.18.0 - version: 9.33.0 + version: 9.34.0 '@sveltejs/adapter-auto': specifier: ^6.0.0 version: 6.1.0(@sveltejs/kit@2.36.1(@sveltejs/vite-plugin-svelte@6.1.3(svelte@5.38.2)(vite@7.1.3(@types/node@24.2.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1)))(svelte@5.38.2)(vite@7.1.3(@types/node@24.2.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1))) @@ -827,10 +827,10 @@ importers: version: 4.1.12(vite@7.1.3(@types/node@24.2.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1)) eslint: specifier: ^9.18.0 - version: 9.33.0(jiti@2.5.1) + version: 9.34.0(jiti@2.5.1) eslint-plugin-svelte: specifier: ^3.0.0 - version: 3.11.0(eslint@9.33.0(jiti@2.5.1))(svelte@5.38.2)(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.2.1)(typescript@5.9.2)) + version: 3.11.0(eslint@9.34.0(jiti@2.5.1))(svelte@5.38.2)(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.2.1)(typescript@5.9.2)) globals: specifier: ^16.0.0 version: 16.3.0 @@ -851,7 +851,7 @@ importers: version: 5.9.2 typescript-eslint: specifier: ^8.20.0 - version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) vite: specifier: ^7.0.0 version: 7.1.3(@types/node@24.2.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1) @@ -897,10 +897,10 @@ importers: version: 4.0.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(bufferutil@4.0.9)(esbuild@0.25.9)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.40.0 - version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': specifier: ^8.0.0 - version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@vitest/browser': specifier: ^3.0.5 version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.17.2)(typescript@5.9.2))(playwright@1.55.0)(utf-8-validate@6.0.5)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.19.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)) @@ -912,10 +912,10 @@ importers: version: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) eslint: specifier: ^9.0.0 - version: 9.33.0(jiti@2.5.1) + version: 9.34.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -957,10 +957,10 @@ importers: version: 4.0.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(bufferutil@4.0.9)(esbuild@0.25.9)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.40.0 - version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': specifier: ^8.0.0 - version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@vitest/browser': specifier: ^3.0.5 version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.17.2)(typescript@5.9.2))(playwright@1.55.0)(utf-8-validate@6.0.5)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.19.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)) @@ -972,10 +972,10 @@ importers: version: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) eslint: specifier: ^9.0.0 - version: 9.33.0(jiti@2.5.1) + version: 9.34.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -1017,10 +1017,10 @@ importers: version: 4.0.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(bufferutil@4.0.9)(esbuild@0.25.9)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.40.0 - version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': specifier: ^8.0.0 - version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@vitest/browser': specifier: ^3.0.5 version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.17.2)(typescript@5.9.2))(playwright@1.55.0)(utf-8-validate@6.0.5)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.19.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)) @@ -1032,10 +1032,10 @@ importers: version: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) eslint: specifier: ^9.0.0 - version: 9.33.0(jiti@2.5.1) + version: 9.34.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -1084,10 +1084,10 @@ importers: version: 4.0.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(bufferutil@4.0.9)(esbuild@0.25.9)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.40.0 - version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': specifier: ^8.0.0 - version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@vitest/browser': specifier: ^3.0.5 version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.17.2)(typescript@5.9.2))(playwright@1.55.0)(utf-8-validate@6.0.5)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.19.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)) @@ -1099,10 +1099,10 @@ importers: version: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) eslint: specifier: ^9.0.0 - version: 9.33.0(jiti@2.5.1) + version: 9.34.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -1151,10 +1151,10 @@ importers: version: 4.0.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(bufferutil@4.0.9)(esbuild@0.25.9)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': specifier: ~8.40.0 - version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': specifier: ^8.0.0 - version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@vitest/browser': specifier: ^3.0.5 version: 3.2.4(bufferutil@4.0.9)(msw@2.7.5(@types/node@22.17.2)(typescript@5.9.2))(playwright@1.55.0)(utf-8-validate@6.0.5)(vite@7.1.3(@types/node@22.17.2)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(terser@5.43.1)(tsx@4.20.4)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.19.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)) @@ -1166,10 +1166,10 @@ importers: version: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) eslint: specifier: ^9.0.0 - version: 9.33.0(jiti@2.5.1) + version: 9.34.0(jiti@2.5.1) eslint-config-ckeditor5: specifier: '>=9.1.0' - version: 12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 12.1.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) http-server: specifier: ^14.1.0 version: 14.1.1 @@ -1372,10 +1372,10 @@ importers: version: 5.21.1 '@typescript-eslint/eslint-plugin': specifier: ^8.0.0 - version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/parser': specifier: ^8.0.0 - version: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + version: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) dotenv: specifier: ^17.0.0 version: 17.2.1 @@ -1384,7 +1384,7 @@ importers: version: 0.25.9 eslint: specifier: ^9.0.0 - version: 9.33.0(jiti@2.5.1) + version: 9.34.0(jiti@2.5.1) highlight.js: specifier: ^11.8.0 version: 11.11.1 @@ -3008,8 +3008,8 @@ packages: resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.33.0': - resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==} + '@eslint/js@9.34.0': + resolution: {integrity: sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/markdown@6.6.0': @@ -8561,8 +8561,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.33.0: - resolution: {integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==} + eslint@9.34.0: + resolution: {integrity: sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -16924,8 +16924,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 '@ckeditor/ckeditor5-watchdog': 46.0.2 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.2)': dependencies: @@ -17101,8 +17099,6 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-editor-decoupled@46.0.2': dependencies: @@ -17121,6 +17117,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-editor-multi-root@46.0.2': dependencies: @@ -17143,6 +17141,8 @@ snapshots: '@ckeditor/ckeditor5-table': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-emoji@46.0.2': dependencies: @@ -17179,6 +17179,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-undo': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-export-inline-styles@46.0.2': dependencies: @@ -17253,6 +17255,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-heading@46.0.2': dependencies: @@ -17610,6 +17614,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 46.0.2 '@ckeditor/ckeditor5-utils': 46.0.2 ckeditor5: 46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41) + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-restricted-editing@46.0.2': dependencies: @@ -18550,16 +18556,16 @@ snapshots: '@esbuild/win32-x64@0.25.9': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.5.1))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.34.0(jiti@2.5.1))': dependencies: - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} - '@eslint/compat@1.3.2(eslint@9.33.0(jiti@2.5.1))': + '@eslint/compat@1.3.2(eslint@9.34.0(jiti@2.5.1))': optionalDependencies: - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) '@eslint/config-array@0.21.0': dependencies: @@ -18593,7 +18599,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.33.0': {} + '@eslint/js@9.34.0': {} '@eslint/markdown@6.6.0': dependencies: @@ -19776,14 +19782,14 @@ snapshots: - supports-color - verdaccio - '@nx/eslint-plugin@21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint-config-prettier@10.1.8(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2)': + '@nx/eslint-plugin@21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)))(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2)': dependencies: '@nx/devkit': 21.3.11(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/js': 21.3.11(patch_hash=7201af3a8fb4840b046e4e18cc2758fa67ee3d0cf11d0783869dc828cfc79fc7)(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2) - '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/type-utils': 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.38.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.38.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) chalk: 4.1.2 confusing-browser-globals: 1.0.11 globals: 15.15.0 @@ -19791,7 +19797,7 @@ snapshots: semver: 7.7.2 tslib: 2.8.1 optionalDependencies: - eslint-config-prettier: 10.1.8(eslint@9.33.0(jiti@2.5.1)) + eslint-config-prettier: 10.1.8(eslint@9.34.0(jiti@2.5.1)) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -19803,11 +19809,11 @@ snapshots: - typescript - verdaccio - '@nx/eslint@21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))': + '@nx/eslint@21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))': dependencies: '@nx/devkit': 21.3.11(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/js': 21.3.11(patch_hash=7201af3a8fb4840b046e4e18cc2758fa67ee3d0cf11d0783869dc828cfc79fc7)(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) semver: 7.7.2 tslib: 2.8.1 typescript: 5.8.3 @@ -19822,11 +19828,11 @@ snapshots: - supports-color - verdaccio - '@nx/express@21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.33.0(jiti@2.5.1))(express@4.21.2)(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2)': + '@nx/express@21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.34.0(jiti@2.5.1))(express@4.21.2)(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2)': dependencies: '@nx/devkit': 21.3.11(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/js': 21.3.11(patch_hash=7201af3a8fb4840b046e4e18cc2758fa67ee3d0cf11d0783869dc828cfc79fc7)(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) - '@nx/node': 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2) + '@nx/node': 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2) tslib: 2.8.1 optionalDependencies: express: 4.21.2 @@ -19918,10 +19924,10 @@ snapshots: - nx - supports-color - '@nx/node@21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2)': + '@nx/node@21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(@zkochan/js-yaml@0.0.7)(babel-plugin-macros@3.1.0)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2)': dependencies: '@nx/devkit': 21.3.11(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) - '@nx/eslint': 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) + '@nx/eslint': 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/jest': 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(babel-plugin-macros@3.1.0)(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@22.17.2)(typescript@5.9.2))(typescript@5.9.2) '@nx/js': 21.3.11(patch_hash=7201af3a8fb4840b046e4e18cc2758fa67ee3d0cf11d0783869dc828cfc79fc7)(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) kill-port: 1.6.1 @@ -19974,10 +19980,10 @@ snapshots: '@nx/nx-win32-x64-msvc@21.3.11': optional: true - '@nx/playwright@21.3.11(@babel/traverse@7.28.0)(@playwright/test@1.55.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2)': + '@nx/playwright@21.3.11(@babel/traverse@7.28.0)(@playwright/test@1.55.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.9.2)': dependencies: '@nx/devkit': 21.3.11(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) - '@nx/eslint': 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.33.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) + '@nx/eslint': 21.3.11(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.34.0(jiti@2.5.1))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/js': 21.3.11(patch_hash=7201af3a8fb4840b046e4e18cc2758fa67ee3d0cf11d0783869dc828cfc79fc7)(@babel/traverse@7.28.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.3.11(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.9.2))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.9.2) minimatch: 9.0.3 @@ -21215,10 +21221,10 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@stylistic/eslint-plugin@4.4.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@stylistic/eslint-plugin@4.4.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.33.0(jiti@2.5.1) + '@typescript-eslint/utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + eslint: 9.34.0(jiti@2.5.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 @@ -22093,15 +22099,15 @@ snapshots: '@types/node': 22.17.2 optional: true - '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/scope-manager': 8.40.0 - '@typescript-eslint/type-utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.40.0 - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -22110,14 +22116,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 8.40.0 '@typescript-eslint/types': 8.40.0 '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) '@typescript-eslint/visitor-keys': 8.40.0 debug: 4.4.1(supports-color@6.0.0) - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -22158,25 +22164,25 @@ snapshots: dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.38.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.38.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) debug: 4.4.1(supports-color@6.0.0) - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/types': 8.40.0 '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) debug: 4.4.1(supports-color@6.0.0) - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: @@ -22218,24 +22224,24 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.38.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/utils@8.38.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1)) '@typescript-eslint/scope-manager': 8.38.0 '@typescript-eslint/types': 8.38.0 '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2) - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/utils@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1)) '@typescript-eslint/scope-manager': 8.40.0 '@typescript-eslint/types': 8.40.0 '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color @@ -23554,6 +23560,8 @@ snapshots: ckeditor5-collaboration@46.0.2: dependencies: '@ckeditor/ckeditor5-collaboration-core': 46.0.2 + transitivePeerDependencies: + - supports-color ckeditor5-premium-features@46.0.2(bufferutil@4.0.9)(ckeditor5@46.0.2(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41))(utf-8-validate@6.0.5): dependencies: @@ -25247,23 +25255,23 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-config-ckeditor5@12.1.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): + eslint-config-ckeditor5@12.1.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2): dependencies: - '@eslint/js': 9.33.0 + '@eslint/js': 9.34.0 '@eslint/markdown': 6.6.0 - '@stylistic/eslint-plugin': 4.4.1(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.33.0(jiti@2.5.1) + '@stylistic/eslint-plugin': 4.4.1(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + eslint: 9.34.0(jiti@2.5.1) eslint-plugin-ckeditor5-rules: 12.1.1 - eslint-plugin-mocha: 11.1.0(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-mocha: 11.1.0(eslint@9.34.0(jiti@2.5.1)) globals: 16.3.0 typescript: 5.9.2 - typescript-eslint: 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + typescript-eslint: 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) transitivePeerDependencies: - supports-color - eslint-config-prettier@10.1.8(eslint@9.33.0(jiti@2.5.1)): + eslint-config-prettier@10.1.8(eslint@9.34.0(jiti@2.5.1)): dependencies: - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) eslint-linter-browserify@9.33.0: {} @@ -25277,22 +25285,22 @@ snapshots: validate-npm-package-name: 6.0.2 yaml: 2.8.1 - eslint-plugin-mocha@11.1.0(eslint@9.33.0(jiti@2.5.1)): + eslint-plugin-mocha@11.1.0(eslint@9.34.0(jiti@2.5.1)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) - eslint: 9.33.0(jiti@2.5.1) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1)) + eslint: 9.34.0(jiti@2.5.1) globals: 15.15.0 - eslint-plugin-playwright@2.2.2(eslint@9.33.0(jiti@2.5.1)): + eslint-plugin-playwright@2.2.2(eslint@9.34.0(jiti@2.5.1)): dependencies: - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) globals: 13.24.0 - eslint-plugin-svelte@3.11.0(eslint@9.33.0(jiti@2.5.1))(svelte@5.38.2)(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.2.1)(typescript@5.9.2)): + eslint-plugin-svelte@3.11.0(eslint@9.34.0(jiti@2.5.1))(svelte@5.38.2)(ts-node@10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.2.1)(typescript@5.9.2)): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1)) '@jridgewell/sourcemap-codec': 1.5.4 - eslint: 9.33.0(jiti@2.5.1) + eslint: 9.34.0(jiti@2.5.1) esutils: 2.0.3 globals: 16.3.0 known-css-properties: 0.37.0 @@ -25320,15 +25328,15 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.33.0(jiti@2.5.1): + eslint@9.34.0(jiti@2.5.1): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.5.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.1 '@eslint/core': 0.15.2 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.33.0 + '@eslint/js': 9.34.0 '@eslint/plugin-kit': 0.3.5 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 @@ -32574,13 +32582,13 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2): + typescript-eslint@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/parser': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.40.0(@typescript-eslint/parser@8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) '@typescript-eslint/typescript-estree': 8.40.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.40.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.33.0(jiti@2.5.1) + '@typescript-eslint/utils': 8.40.0(eslint@9.34.0(jiti@2.5.1))(typescript@5.9.2) + eslint: 9.34.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color From 86dd9aa42a8f26727ca5f57cc61ccb392f7f7677 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 10:47:46 +0300 Subject: [PATCH 234/532] chore(react/ribbon): integrate expand/collapse button --- .../client/src/widgets/react/ActionButton.tsx | 7 +-- apps/client/src/widgets/react/hooks.tsx | 8 ++-- apps/client/src/widgets/ribbon/NoteMapTab.tsx | 45 +++++++++++++++++-- apps/client/src/widgets/ribbon/style.css | 2 +- .../src/widgets/ribbon_widgets/note_map.ts | 38 ---------------- 5 files changed, 51 insertions(+), 49 deletions(-) diff --git a/apps/client/src/widgets/react/ActionButton.tsx b/apps/client/src/widgets/react/ActionButton.tsx index 5adacc5c1..65eb60469 100644 --- a/apps/client/src/widgets/react/ActionButton.tsx +++ b/apps/client/src/widgets/react/ActionButton.tsx @@ -1,13 +1,14 @@ interface ActionButtonProps { text: string; icon: string; + className?: string; onClick?: () => void; } -export default function ActionButton({ text, icon, onClick }: ActionButtonProps) { +export default function ActionButton({ text, icon, className, onClick }: ActionButtonProps) { return - - - -
    `; - export default class NoteMapRibbonWidget extends NoteContextAwareWidget { private openState!: "small" | "full"; @@ -50,37 +42,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget { this.noteMapWidget.setDimensions(); }); - const handleResize = () => { - if (!this.noteMapWidget.graph) { - // no graph has been even rendered - return; - } - - if (this.openState === "full") { - this.setFullHeight(); - } else if (this.openState === "small") { - this.setSmallSize(); - } - }; - new ResizeObserver(handleResize).observe(this.$widget[0]); } - setSmallSize() { - const SMALL_SIZE_HEIGHT = 300; - const width = this.$widget.width() ?? 0; - - this.$widget.find(".note-map-container").height(SMALL_SIZE_HEIGHT).width(width); - } - - setFullHeight() { - const { top } = this.$widget[0].getBoundingClientRect(); - - const height = ($(window).height() ?? 0) - top; - const width = this.$widget.width() ?? 0; - - this.$widget.find(".note-map-container") - .height(height) - .width(width); - } } From f7c82d6b09cd289bc2ddec277f58761ac41f5158 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 11:00:25 +0300 Subject: [PATCH 235/532] chore(react/ribbon): watch note map size --- apps/client/src/widgets/react/hooks.tsx | 18 ++++++- apps/client/src/widgets/ribbon/NoteMapTab.tsx | 23 ++++----- .../src/widgets/ribbon_widgets/note_map.ts | 48 ------------------- 3 files changed, 29 insertions(+), 60 deletions(-) delete mode 100644 apps/client/src/widgets/ribbon_widgets/note_map.ts diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index d2a253377..6f1595bfb 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -12,7 +12,7 @@ import FNote from "../../entities/fnote"; import attributes from "../../services/attributes"; import FBlob from "../../entities/fblob"; import NoteContextAwareWidget from "../note_context_aware_widget"; -import { VNode } from "preact"; +import { RefObject, VNode } from "preact"; type TriliumEventHandler = (data: EventData) => void; const registeredHandlers: Map[]>> = new Map(); @@ -454,4 +454,20 @@ export function useLegacyWidget(widgetFactory: () => T, { }, [ noteContext ]); return [
    , widget ] +} + +export function useResizeObserver(ref: RefObject, callback: ResizeObserverCallback) { + useEffect(() => { + if (!ref.current) { + return; + } + + const element = ref.current; + const resizeObserver = new ResizeObserver(callback); + resizeObserver.observe(element); + return () => { + resizeObserver.unobserve(element); + resizeObserver.disconnect(); + } + }, [ ref, callback ]); } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/NoteMapTab.tsx b/apps/client/src/widgets/ribbon/NoteMapTab.tsx index 4a35ab3c0..a5b0a98e5 100644 --- a/apps/client/src/widgets/ribbon/NoteMapTab.tsx +++ b/apps/client/src/widgets/ribbon/NoteMapTab.tsx @@ -1,9 +1,9 @@ import { TabContext } from "./ribbon-interface"; import NoteMapWidget from "../note_map"; -import { useLegacyWidget } from "../react/hooks"; +import { useLegacyWidget, useResizeObserver } from "../react/hooks"; import ActionButton from "../react/ActionButton"; import { t } from "../../services/i18n"; -import { useEffect, useRef, useState } from "preact/hooks"; +import { useCallback, useEffect, useRef, useState } from "preact/hooks"; const SMALL_SIZE_HEIGHT = "300px"; @@ -17,19 +17,20 @@ export default function NoteMapTab({ note, noteContext }: TabContext) { containerClassName: "note-map-container" }); - useEffect(() => noteMapWidget.setDimensions(), [ height ]); - - function toggleExpanded(newValue: boolean) { - setExpanded(newValue); - - if (newValue && containerRef.current) { + const resizeIfNeeded = useCallback(() => { + console.log("Resize if needed"); + if (isExpanded && containerRef.current) { const { top } = containerRef.current.getBoundingClientRect(); const height = window.innerHeight - top; setHeight(height + "px"); } else { setHeight(SMALL_SIZE_HEIGHT); } - } + }, [ isExpanded, containerRef ]); + + useEffect(() => noteMapWidget.setDimensions(), [ height ]); + useEffect(() => resizeIfNeeded(), [ isExpanded ]); + useResizeObserver(containerRef, resizeIfNeeded); return (
    @@ -40,14 +41,14 @@ export default function NoteMapTab({ note, noteContext }: TabContext) { icon="bx bx-arrow-to-bottom" text={t("note_map.open_full")} className="open-full-button" - onClick={() => toggleExpanded(true)} + onClick={() => setExpanded(true)} /> ) : ( toggleExpanded(false)} + onClick={() => setExpanded(false)} /> )}
    diff --git a/apps/client/src/widgets/ribbon_widgets/note_map.ts b/apps/client/src/widgets/ribbon_widgets/note_map.ts deleted file mode 100644 index 93facf3db..000000000 --- a/apps/client/src/widgets/ribbon_widgets/note_map.ts +++ /dev/null @@ -1,48 +0,0 @@ -import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import NoteMapWidget from "../note_map.js"; -import { t } from "../../services/i18n.js"; - -export default class NoteMapRibbonWidget extends NoteContextAwareWidget { - - private openState!: "small" | "full"; - private $container!: JQuery; - private $openFullButton!: JQuery; - private $collapseButton!: JQuery; - - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - this.$container = this.$widget.find(".note-map-container"); - this.$container.append(this.noteMapWidget.render()); - - this.openState = "small"; - - this.$openFullButton = this.$widget.find(".open-full-button"); - this.$openFullButton.on("click", () => { - this.setFullHeight(); - - this.$openFullButton.hide(); - this.$collapseButton.show(); - - this.openState = "full"; - - this.noteMapWidget.setDimensions(); - }); - - this.$collapseButton = this.$widget.find(".collapse-button"); - this.$collapseButton.on("click", () => { - this.setSmallSize(); - - this.$openFullButton.show(); - this.$collapseButton.hide(); - - this.openState = "small"; - - this.noteMapWidget.setDimensions(); - }); - - new ResizeObserver(handleResize).observe(this.$widget[0]); - } - -} From 5f77ca31bd296b92ac9bdf2ac504bff99a13c054 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 11:12:14 +0300 Subject: [PATCH 236/532] chore(react/ribbon): react note map to height changes --- apps/client/src/widgets/react/hooks.tsx | 21 +++++++++++++++++++ apps/client/src/widgets/ribbon/NoteMapTab.tsx | 15 ++++++------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 6f1595bfb..910daf0da 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -470,4 +470,25 @@ export function useResizeObserver(ref: RefObject, callback: ResizeO resizeObserver.disconnect(); } }, [ ref, callback ]); +} + +export function useWindowSize() { + const [ size, setSize ] = useState<{ windowWidth: number, windowHeight: number }>({ + windowWidth: window.innerWidth, + windowHeight: window.innerHeight + }); + + useEffect(() => { + function onResize() { + setSize({ + windowWidth: window.innerWidth, + windowHeight: window.innerHeight + }); + } + + window.addEventListener("resize", onResize); + return () => window.removeEventListener("resize", onResize); + }); + + return size; } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/NoteMapTab.tsx b/apps/client/src/widgets/ribbon/NoteMapTab.tsx index a5b0a98e5..05f47b9fc 100644 --- a/apps/client/src/widgets/ribbon/NoteMapTab.tsx +++ b/apps/client/src/widgets/ribbon/NoteMapTab.tsx @@ -1,6 +1,6 @@ import { TabContext } from "./ribbon-interface"; import NoteMapWidget from "../note_map"; -import { useLegacyWidget, useResizeObserver } from "../react/hooks"; +import { useLegacyWidget, useWindowSize } from "../react/hooks"; import ActionButton from "../react/ActionButton"; import { t } from "../../services/i18n"; import { useCallback, useEffect, useRef, useState } from "preact/hooks"; @@ -11,26 +11,23 @@ export default function NoteMapTab({ note, noteContext }: TabContext) { const [ isExpanded, setExpanded ] = useState(false); const [ height, setHeight ] = useState(SMALL_SIZE_HEIGHT); const containerRef = useRef(null); + const { windowHeight } = useWindowSize(); const [ noteMapContainer, noteMapWidget ] = useLegacyWidget(() => new NoteMapWidget("ribbon"), { noteContext, containerClassName: "note-map-container" }); - - const resizeIfNeeded = useCallback(() => { - console.log("Resize if needed"); + + useEffect(() => { if (isExpanded && containerRef.current) { const { top } = containerRef.current.getBoundingClientRect(); - const height = window.innerHeight - top; + const height = windowHeight - top; setHeight(height + "px"); } else { setHeight(SMALL_SIZE_HEIGHT); } - }, [ isExpanded, containerRef ]); - + }, [ isExpanded, containerRef, windowHeight ]); useEffect(() => noteMapWidget.setDimensions(), [ height ]); - useEffect(() => resizeIfNeeded(), [ isExpanded ]); - useResizeObserver(containerRef, resizeIfNeeded); return (
    From 6c30e0836f107b3210416c21935a9726cac21017 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 11:19:35 +0300 Subject: [PATCH 237/532] chore(react/ribbon): also react to width, not just height --- apps/client/src/widgets/react/hooks.tsx | 25 ++++++++++++++++--- apps/client/src/widgets/ribbon/NoteMapTab.tsx | 14 +++++------ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 910daf0da..af74c6873 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -456,22 +456,41 @@ export function useLegacyWidget(widgetFactory: () => T, { return [
    , widget ] } -export function useResizeObserver(ref: RefObject, callback: ResizeObserverCallback) { +/** + * Attaches a {@link ResizeObserver} to the given ref and reads the bounding client rect whenever it changes. + * + * @param ref a ref to a {@link HTMLElement} to determine the size and observe the changes in size. + * @returns the size of the element, reacting to changes. + */ +export function useElementSize(ref: RefObject) { + const [ size, setSize ] = useState(ref.current?.getBoundingClientRect()); + useEffect(() => { if (!ref.current) { return; } + function onResize() { + setSize(ref.current?.getBoundingClientRect()); + } + const element = ref.current; - const resizeObserver = new ResizeObserver(callback); + const resizeObserver = new ResizeObserver(onResize); resizeObserver.observe(element); return () => { resizeObserver.unobserve(element); resizeObserver.disconnect(); } - }, [ ref, callback ]); + }, [ ref ]); + + return size; } +/** + * Obtains the inner width and height of the window, as well as reacts to changes in size. + * + * @returns the width and height of the window. + */ export function useWindowSize() { const [ size, setSize ] = useState<{ windowWidth: number, windowHeight: number }>({ windowWidth: window.innerWidth, diff --git a/apps/client/src/widgets/ribbon/NoteMapTab.tsx b/apps/client/src/widgets/ribbon/NoteMapTab.tsx index 05f47b9fc..ab71d245b 100644 --- a/apps/client/src/widgets/ribbon/NoteMapTab.tsx +++ b/apps/client/src/widgets/ribbon/NoteMapTab.tsx @@ -1,9 +1,9 @@ import { TabContext } from "./ribbon-interface"; import NoteMapWidget from "../note_map"; -import { useLegacyWidget, useWindowSize } from "../react/hooks"; +import { useElementSize, useLegacyWidget, useWindowSize } from "../react/hooks"; import ActionButton from "../react/ActionButton"; import { t } from "../../services/i18n"; -import { useCallback, useEffect, useRef, useState } from "preact/hooks"; +import { useEffect, useRef, useState } from "preact/hooks"; const SMALL_SIZE_HEIGHT = "300px"; @@ -12,6 +12,7 @@ export default function NoteMapTab({ note, noteContext }: TabContext) { const [ height, setHeight ] = useState(SMALL_SIZE_HEIGHT); const containerRef = useRef(null); const { windowHeight } = useWindowSize(); + const containerSize = useElementSize(containerRef); const [ noteMapContainer, noteMapWidget ] = useLegacyWidget(() => new NoteMapWidget("ribbon"), { noteContext, @@ -19,15 +20,14 @@ export default function NoteMapTab({ note, noteContext }: TabContext) { }); useEffect(() => { - if (isExpanded && containerRef.current) { - const { top } = containerRef.current.getBoundingClientRect(); - const height = windowHeight - top; + if (isExpanded && containerRef.current && containerSize) { + const height = windowHeight - containerSize.top; setHeight(height + "px"); } else { setHeight(SMALL_SIZE_HEIGHT); } - }, [ isExpanded, containerRef, windowHeight ]); - useEffect(() => noteMapWidget.setDimensions(), [ height ]); + }, [ isExpanded, containerRef, windowHeight, containerSize?.top ]); + useEffect(() => noteMapWidget.setDimensions(), [ containerSize?.width, height ]); return (
    From 2c33ef2b0d6b7be60363d130e9a3e06eec4a191b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 11:28:10 +0300 Subject: [PATCH 238/532] chore(react/ribbon): similar notes style --- apps/client/src/widgets/react/NoteLink.tsx | 7 +++-- .../src/widgets/ribbon/SimilarNotesTab.tsx | 30 +++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/apps/client/src/widgets/react/NoteLink.tsx b/apps/client/src/widgets/react/NoteLink.tsx index b7f3a3409..ec3c33640 100644 --- a/apps/client/src/widgets/react/NoteLink.tsx +++ b/apps/client/src/widgets/react/NoteLink.tsx @@ -7,9 +7,10 @@ interface NoteLinkOpts { showNotePath?: boolean; style?: Record; noPreview?: boolean; + noTnLink?: boolean; } -export default function NoteLink({ notePath, showNotePath, style, noPreview }: NoteLinkOpts) { +export default function NoteLink({ notePath, showNotePath, style, noPreview, noTnLink }: NoteLinkOpts) { const stringifiedNotePath = Array.isArray(notePath) ? notePath.join("/") : notePath; const [ jqueryEl, setJqueryEl ] = useState>(); @@ -27,7 +28,9 @@ export default function NoteLink({ notePath, showNotePath, style, noPreview }: N $linkEl?.addClass("no-tooltip-preview"); } - $linkEl?.addClass("tn-link"); + if (!noTnLink) { + $linkEl?.addClass("tn-link"); + } return diff --git a/apps/client/src/widgets/ribbon/SimilarNotesTab.tsx b/apps/client/src/widgets/ribbon/SimilarNotesTab.tsx index b771f13bc..57299c92c 100644 --- a/apps/client/src/widgets/ribbon/SimilarNotesTab.tsx +++ b/apps/client/src/widgets/ribbon/SimilarNotesTab.tsx @@ -23,18 +23,24 @@ export default function SimilarNotesTab({ note }: TabContext) { }, [ note?.noteId ]); return ( -
    - {similarNotes?.length ? ( -
    - {similarNotes.map(({notePath, score}) => ( - - ))} -
    - ) : ( - <>{t("similar_notes.no_similar_notes_found")} - )} +
    +
    + {similarNotes?.length ? ( +
    + {similarNotes.map(({notePath, score}) => ( + + ))} +
    + ) : ( + <>{t("similar_notes.no_similar_notes_found")} + )} +
    ) } \ No newline at end of file From 6d37e19b40ac2db8d3acf49c0e89bf0d48e2804f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 11:44:51 +0300 Subject: [PATCH 239/532] chore(react/ribbon): start implementing attribute editor --- .../attribute_widgets/attribute_editor.ts | 108 ------------------ .../src/widgets/ribbon/OwnedAttributesTab.tsx | 10 ++ apps/client/src/widgets/ribbon/Ribbon.tsx | 7 +- .../ribbon/components/AttributeEditor.tsx | 78 +++++++++++++ apps/client/src/widgets/ribbon/style.css | 64 +++++++++++ .../ribbon_widgets/owned_attribute_list.ts | 45 -------- 6 files changed, 157 insertions(+), 155 deletions(-) create mode 100644 apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx create mode 100644 apps/client/src/widgets/ribbon/components/AttributeEditor.tsx diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 3e97723a5..24a4e09d2 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -24,58 +24,6 @@ const HELP_TEXT = `

    ${t("attribute_editor.help_text_body3")}

    `; const TPL = /*html*/` -
    - - -
    @@ -84,61 +32,6 @@ const TPL = /*html*/`
    `; -const mentionSetup: MentionFeed[] = [ - { - marker: "@", - feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText), - itemRenderer: (_item) => { - const item = _item as Suggestion; - const itemElement = document.createElement("button"); - - itemElement.innerHTML = `${item.highlightedNotePathTitle} `; - - return itemElement; - }, - minimumCharacters: 0 - }, - { - marker: "#", - feed: async (queryText) => { - const names = await server.get(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`); - - return names.map((name) => { - return { - id: `#${name}`, - name: name - }; - }); - }, - minimumCharacters: 0 - }, - { - marker: "~", - feed: async (queryText) => { - const names = await server.get(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`); - - return names.map((name) => { - return { - id: `~${name}`, - name: name - }; - }); - }, - minimumCharacters: 0 - } -]; - -const editorConfig: EditorConfig = { - toolbar: { - items: [] - }, - placeholder: t("attribute_editor.placeholder"), - mention: { - feeds: mentionSetup - }, - licenseKey: "GPL" -}; - type AttributeCommandNames = FilteredCommandNames; export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> { @@ -324,7 +217,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.$editor.on("click", (e) => this.handleEditorClick(e)); - this.textEditor = await AttributeEditor.create(this.$editor[0], editorConfig); this.textEditor.model.document.on("change:data", () => this.dataChanged()); this.textEditor.editing.view.document.on( "enter", diff --git a/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx new file mode 100644 index 000000000..33554989c --- /dev/null +++ b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx @@ -0,0 +1,10 @@ +import AttributeEditor from "./components/AttributeEditor"; +import { TabContext } from "./ribbon-interface"; + +export default function OwnedAttributesTab({ note }: TabContext) { + return ( +
    + +
    + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index 4702dcf07..032a905a6 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -19,6 +19,7 @@ import FilePropertiesTab from "./FilePropertiesTab"; import ImagePropertiesTab from "./ImagePropertiesTab"; import NotePathsTab from "./NotePathsTab"; import NoteMapTab from "./NoteMapTab"; +import OwnedAttributesTab from "./OwnedAttributesTab"; interface TitleContext { note: FNote | null | undefined; @@ -105,9 +106,11 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ toggleCommand: "toggleRibbonTabBasicProperties" }, { - // OwnedAttributeListWidget title: t("owned_attribute_list.owned_attributes"), - icon: "bx bx-list-check" + icon: "bx bx-list-check", + content: OwnedAttributesTab, + show: ({note}) => !note?.isLaunchBarConfig(), + toggleCommand: "toggleRibbonTabOwnedAttributes" }, { // InheritedAttributesWidget diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx new file mode 100644 index 000000000..88bc85139 --- /dev/null +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -0,0 +1,78 @@ +import { useEffect, useRef } from "preact/hooks" +import { AttributeEditor as CKEditor, EditorConfig, MentionFeed } from "@triliumnext/ckeditor5"; +import { t } from "../../../services/i18n"; +import server from "../../../services/server"; +import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; + +const mentionSetup: MentionFeed[] = [ + { + marker: "@", + feed: (queryText) => note_autocomplete.autocompleteSourceForCKEditor(queryText), + itemRenderer: (_item) => { + const item = _item as Suggestion; + const itemElement = document.createElement("button"); + + itemElement.innerHTML = `${item.highlightedNotePathTitle} `; + + return itemElement; + }, + minimumCharacters: 0 + }, + { + marker: "#", + feed: async (queryText) => { + const names = await server.get(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`); + + return names.map((name) => { + return { + id: `#${name}`, + name: name + }; + }); + }, + minimumCharacters: 0 + }, + { + marker: "~", + feed: async (queryText) => { + const names = await server.get(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`); + + return names.map((name) => { + return { + id: `~${name}`, + name: name + }; + }); + }, + minimumCharacters: 0 + } +]; + +const editorConfig: EditorConfig = { + toolbar: { + items: [] + }, + placeholder: t("attribute_editor.placeholder"), + mention: { + feeds: mentionSetup + }, + licenseKey: "GPL" +}; + +export default function AttributeEditor() { + const editorContainerRef = useRef(null); + + useEffect(() => { + if (!editorContainerRef.current) return; + + CKEditor.create(editorContainerRef.current, editorConfig).then((textEditor) => { + + }); + }, []); + + return ( +
    +
    +
    + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index c09fa989f..22c9d7b43 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -267,4 +267,68 @@ color: var(--muted-text-color); display: none; } +/* #endregion */ + +/* #region Attribute editor */ +.attribute-list-editor { + border: 0 !important; + outline: 0 !important; + box-shadow: none !important; + padding: 0 0 0 5px !important; + margin: 0 !important; + max-height: 100px; + overflow: auto; + transition: opacity .1s linear; +} + +.attribute-list-editor.ck-content .mention { + color: var(--muted-text-color) !important; + background: transparent !important; +} + +.save-attributes-button { + color: var(--muted-text-color); + position: absolute; + bottom: 14px; + right: 25px; + cursor: pointer; + border: 1px solid transparent; + font-size: 130%; +} + +.add-new-attribute-button { + color: var(--muted-text-color); + position: absolute; + bottom: 13px; + right: 0; + cursor: pointer; + border: 1px solid transparent; + font-size: 130%; +} + +.add-new-attribute-button:hover, .save-attributes-button:hover { + border: 1px solid var(--button-border-color); + border-radius: var(--button-border-radius); + background: var(--button-background-color); + color: var(--button-text-color); +} + +.attribute-errors { + color: red; + padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */ +} +/* #endregion */ + +/* #region Owned Attributes */ +.attribute-list { + margin-left: 7px; + margin-right: 7px; + margin-top: 5px; + margin-bottom: 2px; + position: relative; +} + +.attribute-list-editor p { + margin: 0 !important; +} /* #endregion */ \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts b/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts index 113e03e0d..6b036bd0e 100644 --- a/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts +++ b/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts @@ -5,40 +5,12 @@ import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js"; import type { CommandListenerData } from "../../components/app_context.js"; import type FAttribute from "../../entities/fattribute.js"; -const TPL = /*html*/` -
    - - -
    -
    -`; - export default class OwnedAttributeListWidget extends NoteContextAwareWidget { private attributeDetailWidget: AttributeDetailWidget; private attributeEditorWidget: AttributeEditorWidget; private $title!: JQuery; - get name() { - return "ownedAttributes"; - } - - get toggleCommand() { - return "toggleRibbonTabOwnedAttributes"; - } - constructor() { super(); @@ -49,23 +21,6 @@ export default class OwnedAttributeListWidget extends NoteContextAwareWidget { this.child(this.attributeEditorWidget, this.attributeDetailWidget); } - getTitle() { - return { - show: !this.note?.isLaunchBarConfig(), - - }; - } - - doRender() { - this.$widget = $(TPL); - this.contentSized(); - - this.$widget.find(".attr-editor-placeholder").replaceWith(this.attributeEditorWidget.render()); - this.$widget.append(this.attributeDetailWidget.render()); - - this.$title = $("
    "); - } - async saveAttributesCommand() { await this.attributeEditorWidget.save(); } From 73038efccfda68104409a6b26f6aabc0ffb79d21 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 11:52:40 +0300 Subject: [PATCH 240/532] chore(react/ribbon): add some CKEditor events --- .../attribute_widgets/attribute_editor.ts | 23 -------------- .../ribbon/components/AttributeEditor.tsx | 30 +++++++++++++++++-- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 24a4e09d2..016bd8776 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -212,29 +212,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem return $("
    ").html(str).text(); } - async initEditor() { - this.$widget.show(); - - this.$editor.on("click", (e) => this.handleEditorClick(e)); - - this.textEditor.model.document.on("change:data", () => this.dataChanged()); - this.textEditor.editing.view.document.on( - "enter", - (event, data) => { - // disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422 - data.preventDefault(); - event.stop(); - }, - { priority: "high" } - ); - - // disable spellcheck for attribute editor - const documentRoot = this.textEditor.editing.view.document.getRoot(); - if (documentRoot) { - this.textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot)); - } - } - dataChanged() { this.lastUpdatedNoteId = this.noteId; diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 88bc85139..55c2d5968 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "preact/hooks" +import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks" import { AttributeEditor as CKEditor, EditorConfig, MentionFeed } from "@triliumnext/ckeditor5"; import { t } from "../../../services/i18n"; import server from "../../../services/server"; @@ -61,17 +61,43 @@ const editorConfig: EditorConfig = { export default function AttributeEditor() { const editorContainerRef = useRef(null); + const [ attributeDetailVisible, setAttributeDetailVisible ] = useState(false); + + const onClick = useCallback(() => { + console.log("Clicked"); + }, []); useEffect(() => { if (!editorContainerRef.current) return; CKEditor.create(editorContainerRef.current, editorConfig).then((textEditor) => { + function onDataChanged() { + console.log("Data changed"); + } + // Prevent newlines + textEditor.editing.view.document.on( + "enter", + (event, data) => { + // disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422 + data.preventDefault(); + event.stop(); + }, + { priority: "high" } + ); + + // disable spellcheck for attribute editor + const documentRoot = textEditor.editing.view.document.getRoot(); + if (documentRoot) { + textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot)); + } + + textEditor.model.document.on("change:data", onDataChanged); }); }, []); return ( -
    +
    ) From 1e004078649c47479c4f8286e99a283d56ee1e1c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 12:05:03 +0300 Subject: [PATCH 241/532] chore(react/ribbon): use separate component for editor --- apps/client/src/widgets/react/CKEditor.tsx | 53 +++++++++++++++ .../ribbon/components/AttributeEditor.tsx | 66 ++++++------------- 2 files changed, 72 insertions(+), 47 deletions(-) create mode 100644 apps/client/src/widgets/react/CKEditor.tsx diff --git a/apps/client/src/widgets/react/CKEditor.tsx b/apps/client/src/widgets/react/CKEditor.tsx new file mode 100644 index 000000000..7b28c30cf --- /dev/null +++ b/apps/client/src/widgets/react/CKEditor.tsx @@ -0,0 +1,53 @@ +import type { AttributeEditor, EditorConfig } from "@triliumnext/ckeditor5"; +import { useEffect, useRef } from "preact/compat"; + +interface CKEditorOpts { + className: string; + tabIndex?: number; + config: EditorConfig; + editor: typeof AttributeEditor; + disableNewlines?: boolean; + disableSpellcheck?: boolean; + onChange?: () => void; +} + +export default function CKEditor({ className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange }: CKEditorOpts) { + const editorContainerRef = useRef(null); + + useEffect(() => { + if (!editorContainerRef.current) return; + + editor.create(editorContainerRef.current, config).then((textEditor) => { + if (disableNewlines) { + textEditor.editing.view.document.on( + "enter", + (event, data) => { + // disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422 + data.preventDefault(); + event.stop(); + }, + { priority: "high" } + ); + } + + if (disableSpellcheck) { + const documentRoot = textEditor.editing.view.document.getRoot(); + if (documentRoot) { + textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot)); + } + } + + if (onChange) { + textEditor.model.document.on("change:data", onChange); + } + }); + }, []); + + return ( +
    + ) +} \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 55c2d5968..9ae3f2372 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -1,8 +1,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks" -import { AttributeEditor as CKEditor, EditorConfig, MentionFeed } from "@triliumnext/ckeditor5"; +import { AttributeEditor as CKEditorAttributeEditor, EditorConfig, MentionFeed } from "@triliumnext/ckeditor5"; import { t } from "../../../services/i18n"; import server from "../../../services/server"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; +import CKEditor from "../../react/CKEditor"; const mentionSetup: MentionFeed[] = [ { @@ -48,57 +49,28 @@ const mentionSetup: MentionFeed[] = [ } ]; -const editorConfig: EditorConfig = { - toolbar: { - items: [] - }, - placeholder: t("attribute_editor.placeholder"), - mention: { - feeds: mentionSetup - }, - licenseKey: "GPL" -}; export default function AttributeEditor() { - const editorContainerRef = useRef(null); + const [ attributeDetailVisible, setAttributeDetailVisible ] = useState(false); - const onClick = useCallback(() => { - console.log("Clicked"); - }, []); - - useEffect(() => { - if (!editorContainerRef.current) return; - - CKEditor.create(editorContainerRef.current, editorConfig).then((textEditor) => { - function onDataChanged() { - console.log("Data changed"); - } - - // Prevent newlines - textEditor.editing.view.document.on( - "enter", - (event, data) => { - // disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422 - data.preventDefault(); - event.stop(); - }, - { priority: "high" } - ); - - // disable spellcheck for attribute editor - const documentRoot = textEditor.editing.view.document.getRoot(); - if (documentRoot) { - textEditor.editing.view.change((writer) => writer.setAttribute("spellcheck", "false", documentRoot)); - } - - textEditor.model.document.on("change:data", onDataChanged); - }); - }, []); - return ( -
    -
    +
    + { + console.log("Data changed!"); + }} + disableNewlines disableSpellcheck + />
    ) } \ No newline at end of file From befc5a9530ef60c3f4277323d7dd0d1f684b3d2b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 12:31:54 +0300 Subject: [PATCH 242/532] feat(react/ribbon): display help tooltip in attribute editor --- .../attribute_widgets/attribute_editor.ts | 25 +---------- apps/client/src/widgets/react/CKEditor.tsx | 14 ++++++- apps/client/src/widgets/react/hooks.tsx | 28 ++++++++++++- .../ribbon/components/AttributeEditor.tsx | 41 ++++++++++++++++--- 4 files changed, 76 insertions(+), 32 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 016bd8776..4baecd691 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -16,12 +16,7 @@ import type { default as FAttribute, AttributeType } from "../../entities/fattri import type FNote from "../../entities/fnote.js"; import { escapeQuotes } from "../../services/utils.js"; -const HELP_TEXT = ` -

    ${t("attribute_editor.help_text_body1")}

    -

    ${t("attribute_editor.help_text_body2")}

    - -

    ${t("attribute_editor.help_text_body3")}

    `; const TPL = /*html*/` @@ -229,9 +224,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem } async handleEditorClick(e: JQuery.ClickEvent) { - const pos = this.textEditor.model.document.selection.getFirstPosition(); - - if (pos && pos.textNode && pos.textNode.data) { + if () { const clickIndex = this.getClickIndex(pos); let parsedAttrs; @@ -267,25 +260,9 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.showHelpTooltip(); } }, 100); - } else { - this.showHelpTooltip(); } } - showHelpTooltip() { - this.attributeDetailWidget.hide(); - - this.$editor.tooltip({ - trigger: "focus", - html: true, - title: HELP_TEXT, - placement: "bottom", - offset: "0,30" - }); - - this.$editor.tooltip("show"); - } - getClickIndex(pos: ModelPosition) { let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0); diff --git a/apps/client/src/widgets/react/CKEditor.tsx b/apps/client/src/widgets/react/CKEditor.tsx index 7b28c30cf..8f521a827 100644 --- a/apps/client/src/widgets/react/CKEditor.tsx +++ b/apps/client/src/widgets/react/CKEditor.tsx @@ -1,4 +1,4 @@ -import type { AttributeEditor, EditorConfig } from "@triliumnext/ckeditor5"; +import { CKTextEditor, type AttributeEditor, type EditorConfig, type ModelPosition } from "@triliumnext/ckeditor5"; import { useEffect, useRef } from "preact/compat"; interface CKEditorOpts { @@ -9,15 +9,19 @@ interface CKEditorOpts { disableNewlines?: boolean; disableSpellcheck?: boolean; onChange?: () => void; + onClick?: (pos?: ModelPosition | null) => void; } -export default function CKEditor({ className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange }: CKEditorOpts) { +export default function CKEditor({ className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange, onClick }: CKEditorOpts) { const editorContainerRef = useRef(null); + const textEditorRef = useRef(null); useEffect(() => { if (!editorContainerRef.current) return; editor.create(editorContainerRef.current, config).then((textEditor) => { + textEditorRef.current = textEditor; + if (disableNewlines) { textEditor.editing.view.document.on( "enter", @@ -48,6 +52,12 @@ export default function CKEditor({ className, tabIndex, editor, config, disableN ref={editorContainerRef} className={className} tabIndex={tabIndex} + onClick={() => { + if (onClick) { + const pos = textEditorRef.current?.model.document.selection.getFirstPosition(); + onClick(pos); + } + }} /> ) } \ No newline at end of file diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index af74c6873..6436b1734 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -12,7 +12,8 @@ import FNote from "../../entities/fnote"; import attributes from "../../services/attributes"; import FBlob from "../../entities/fblob"; import NoteContextAwareWidget from "../note_context_aware_widget"; -import { RefObject, VNode } from "preact"; +import { Ref, RefObject, VNode } from "preact"; +import { Tooltip } from "bootstrap"; type TriliumEventHandler = (data: EventData) => void; const registeredHandlers: Map[]>> = new Map(); @@ -510,4 +511,29 @@ export function useWindowSize() { }); return size; +} + +export function useTooltip(elRef: RefObject, config: Partial) { + useEffect(() => { + if (!elRef?.current) return; + + const $el = $(elRef.current); + $el.tooltip(config); + }, [ elRef, config ]); + + const showTooltip = useCallback(() => { + if (!elRef?.current) return; + + const $el = $(elRef.current); + $el.tooltip("show"); + }, [ elRef ]); + + const hideTooltip = useCallback(() => { + if (!elRef?.current) return; + + const $el = $(elRef.current); + $el.tooltip("hide"); + }, [ elRef ]); + + return { showTooltip, hideTooltip }; } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 9ae3f2372..d8071f7b9 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -1,9 +1,17 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks" -import { AttributeEditor as CKEditorAttributeEditor, EditorConfig, MentionFeed } from "@triliumnext/ckeditor5"; +import { useEffect, useRef, useState } from "preact/hooks" +import { AttributeEditor as CKEditorAttributeEditor, MentionFeed } from "@triliumnext/ckeditor5"; import { t } from "../../../services/i18n"; import server from "../../../services/server"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; import CKEditor from "../../react/CKEditor"; +import { useTooltip } from "../../react/hooks"; + +const HELP_TEXT = ` +

    ${t("attribute_editor.help_text_body1")}

    + +

    ${t("attribute_editor.help_text_body2")}

    + +

    ${t("attribute_editor.help_text_body3")}

    `; const mentionSetup: MentionFeed[] = [ { @@ -51,11 +59,27 @@ const mentionSetup: MentionFeed[] = [ export default function AttributeEditor() { - - const [ attributeDetailVisible, setAttributeDetailVisible ] = useState(false); + const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); + const wrapperRef = useRef(null); + const { showTooltip, hideTooltip } = useTooltip(wrapperRef, { + trigger: "focus", + html: true, + title: HELP_TEXT, + placement: "bottom", + offset: "0,30" + }); + + useEffect(() => { + if (state === "showHelpTooltip") { + showTooltip(); + } else { + hideTooltip(); + } + }, [ state ]); + return ( -
    +
    { console.log("Data changed!"); }} + onClick={(pos) => { + if (pos && pos.textNode && pos.textNode.data) { + setState("showAttributeDetail") + } else { + setState("showHelpTooltip"); + } + }} disableNewlines disableSpellcheck />
    From e5caf3769778459b0c6ff911ee150c597b0c1876 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 12:39:49 +0300 Subject: [PATCH 243/532] chore(react/ribbon): load current attributes in editor --- .../attribute_widgets/attribute_editor.ts | 15 ------------ apps/client/src/widgets/react/CKEditor.tsx | 12 +++++++++- .../src/widgets/ribbon/OwnedAttributesTab.tsx | 2 +- .../ribbon/components/AttributeEditor.tsx | 24 ++++++++++++++++++- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 4baecd691..3a9a62dbf 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -289,22 +289,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem $el.text(title); } - async refreshWithNote(note: FNote) { - await this.renderOwnedAttributes(note.getOwnedAttributes(), true); - } - async renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) { - // attrs are not resorted if position changes after the initial load - ownedAttributes.sort((a, b) => a.position - b.position); - - let htmlAttrs = (await attributeRenderer.renderAttributes(ownedAttributes, true)).html(); - - if (htmlAttrs.length > 0) { - htmlAttrs += " "; - } - - this.textEditor.setData(htmlAttrs); - if (saved) { this.lastSavedContent = this.textEditor.getData(); diff --git a/apps/client/src/widgets/react/CKEditor.tsx b/apps/client/src/widgets/react/CKEditor.tsx index 8f521a827..60d5f37ab 100644 --- a/apps/client/src/widgets/react/CKEditor.tsx +++ b/apps/client/src/widgets/react/CKEditor.tsx @@ -2,6 +2,7 @@ import { CKTextEditor, type AttributeEditor, type EditorConfig, type ModelPositi import { useEffect, useRef } from "preact/compat"; interface CKEditorOpts { + currentValue?: string; className: string; tabIndex?: number; config: EditorConfig; @@ -12,7 +13,7 @@ interface CKEditorOpts { onClick?: (pos?: ModelPosition | null) => void; } -export default function CKEditor({ className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange, onClick }: CKEditorOpts) { +export default function CKEditor({ currentValue, className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange, onClick }: CKEditorOpts) { const editorContainerRef = useRef(null); const textEditorRef = useRef(null); @@ -44,9 +45,18 @@ export default function CKEditor({ className, tabIndex, editor, config, disableN if (onChange) { textEditor.model.document.on("change:data", onChange); } + + if (currentValue) { + textEditor.setData(currentValue); + } }); }, []); + useEffect(() => { + if (!textEditorRef.current) return; + textEditorRef.current.setData(currentValue ?? ""); + }, [ currentValue ]); + return (
    - + { note && }
    ) } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index d8071f7b9..716c0f25c 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -5,6 +5,9 @@ import server from "../../../services/server"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; import CKEditor from "../../react/CKEditor"; import { useTooltip } from "../../react/hooks"; +import FAttribute from "../../../entities/fattribute"; +import attribute_renderer from "../../../services/attribute_renderer"; +import FNote from "../../../entities/fnote"; const HELP_TEXT = `

    ${t("attribute_editor.help_text_body1")}

    @@ -58,9 +61,10 @@ const mentionSetup: MentionFeed[] = [ ]; -export default function AttributeEditor() { +export default function AttributeEditor({ note }: { note: FNote }) { const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); + const [ currentValue, setCurrentValue ] = useState(""); const wrapperRef = useRef(null); const { showTooltip, hideTooltip } = useTooltip(wrapperRef, { trigger: "focus", @@ -77,6 +81,23 @@ export default function AttributeEditor() { hideTooltip(); } }, [ state ]); + + async function renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) { + // attrs are not resorted if position changes after the initial load + ownedAttributes.sort((a, b) => a.position - b.position); + + let htmlAttrs = (await attribute_renderer.renderAttributes(ownedAttributes, true)).html(); + + if (htmlAttrs.length > 0) { + htmlAttrs += " "; + } + + setCurrentValue(htmlAttrs); + } + + useEffect(() => { + renderOwnedAttributes(note.getOwnedAttributes(), true); + }, [ note ]); return (
    @@ -84,6 +105,7 @@ export default function AttributeEditor() { className="attribute-list-editor" tabIndex={200} editor={CKEditorAttributeEditor} + currentValue={currentValue} config={{ toolbar: { items: [] }, placeholder: t("attribute_editor.placeholder"), From 62372ed4c50b018f5c729e5b4be68abdfc6e6041 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 12:55:48 +0300 Subject: [PATCH 244/532] chore(react/ribbon): add logic for displaying attribute detail --- .../attribute_widgets/attribute_editor.ts | 71 ---------- apps/client/src/widgets/react/CKEditor.tsx | 12 +- .../ribbon/components/AttributeEditor.tsx | 126 ++++++++++++++---- 3 files changed, 104 insertions(+), 105 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 3a9a62dbf..508153dc8 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -1,12 +1,10 @@ import { t } from "../../services/i18n.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js"; import server from "../../services/server.js"; import contextMenuService from "../../menus/context_menu.js"; import attributeParser, { type Attribute } from "../../services/attribute_parser.js"; import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5"; import froca from "../../services/froca.js"; -import attributeRenderer from "../../services/attribute_renderer.js"; import noteCreateService from "../../services/note_create.js"; import attributeService from "../../services/attributes.js"; import linkService from "../../services/link.js"; @@ -16,8 +14,6 @@ import type { default as FAttribute, AttributeType } from "../../entities/fattri import type FNote from "../../entities/fnote.js"; import { escapeQuotes } from "../../services/utils.js"; - - const TPL = /*html*/`
    @@ -198,15 +194,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem } } - getPreprocessedData() { - const str = this.textEditor - .getData() - .replace(/]+href="(#[A-Za-z0-9_/]*)"[^>]*>[^<]*<\/a>/g, "$1") - .replace(/ /g, " "); // otherwise .text() below outputs non-breaking space in unicode - - return $("
    ").html(str).text(); - } - dataChanged() { this.lastUpdatedNoteId = this.noteId; @@ -223,64 +210,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem } } - async handleEditorClick(e: JQuery.ClickEvent) { - if () { - const clickIndex = this.getClickIndex(pos); - - let parsedAttrs; - - try { - parsedAttrs = attributeParser.lexAndParse(this.getPreprocessedData(), true); - } catch (e) { - // the input is incorrect because the user messed up with it and now needs to fix it manually - return null; - } - - let matchedAttr: Attribute | null = null; - - for (const attr of parsedAttrs) { - if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) { - matchedAttr = attr; - break; - } - } - - setTimeout(() => { - if (matchedAttr) { - this.$editor.tooltip("hide"); - - this.attributeDetailWidget.showAttributeDetail({ - allAttributes: parsedAttrs, - attribute: matchedAttr, - isOwned: true, - x: e.pageX, - y: e.pageY - }); - } else { - this.showHelpTooltip(); - } - }, 100); - } - } - - getClickIndex(pos: ModelPosition) { - let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0); - - let curNode: ModelNode | Text | ModelElement | null = pos.textNode; - - while (curNode?.previousSibling) { - curNode = curNode.previousSibling; - - if ((curNode as ModelElement).name === "reference") { - clickIndex += (curNode.getAttribute("href") as string).length + 1; - } else if ("data" in curNode) { - clickIndex += (curNode.data as string).length; - } - } - - return clickIndex; - } - async loadReferenceLinkTitle($el: JQuery, href: string) { const { noteId } = linkService.parseNavigationStateFromUrl(href); const note = noteId ? await froca.getNote(noteId, true) : null; diff --git a/apps/client/src/widgets/react/CKEditor.tsx b/apps/client/src/widgets/react/CKEditor.tsx index 60d5f37ab..e9080e310 100644 --- a/apps/client/src/widgets/react/CKEditor.tsx +++ b/apps/client/src/widgets/react/CKEditor.tsx @@ -9,8 +9,8 @@ interface CKEditorOpts { editor: typeof AttributeEditor; disableNewlines?: boolean; disableSpellcheck?: boolean; - onChange?: () => void; - onClick?: (pos?: ModelPosition | null) => void; + onChange?: (newValue?: string) => void; + onClick?: (e, pos?: ModelPosition | null) => void; } export default function CKEditor({ currentValue, className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange, onClick }: CKEditorOpts) { @@ -43,7 +43,9 @@ export default function CKEditor({ currentValue, className, tabIndex, editor, co } if (onChange) { - textEditor.model.document.on("change:data", onChange); + textEditor.model.document.on("change:data", () => { + onChange(textEditor.getData()) + }); } if (currentValue) { @@ -62,10 +64,10 @@ export default function CKEditor({ currentValue, className, tabIndex, editor, co ref={editorContainerRef} className={className} tabIndex={tabIndex} - onClick={() => { + onClick={(e) => { if (onClick) { const pos = textEditorRef.current?.model.document.selection.getFirstPosition(); - onClick(pos); + onClick(e, pos); } }} /> diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 716c0f25c..dc9e1aa1c 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -1,13 +1,15 @@ import { useEffect, useRef, useState } from "preact/hooks" -import { AttributeEditor as CKEditorAttributeEditor, MentionFeed } from "@triliumnext/ckeditor5"; +import { AttributeEditor as CKEditorAttributeEditor, MentionFeed, ModelElement, ModelNode, ModelPosition } from "@triliumnext/ckeditor5"; import { t } from "../../../services/i18n"; import server from "../../../services/server"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; import CKEditor from "../../react/CKEditor"; -import { useTooltip } from "../../react/hooks"; +import { useLegacyWidget, useTooltip } from "../../react/hooks"; import FAttribute from "../../../entities/fattribute"; import attribute_renderer from "../../../services/attribute_renderer"; import FNote from "../../../entities/fnote"; +import AttributeDetailWidget from "../../attribute_widgets/attribute_detail"; +import attribute_parser, { Attribute } from "../../../services/attribute_parser"; const HELP_TEXT = `

    ${t("attribute_editor.help_text_body1")}

    @@ -64,7 +66,8 @@ const mentionSetup: MentionFeed[] = [ export default function AttributeEditor({ note }: { note: FNote }) { const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); - const [ currentValue, setCurrentValue ] = useState(""); + const [ initialValue, setInitialValue ] = useState(""); + const currentValueRef = useRef(initialValue); const wrapperRef = useRef(null); const { showTooltip, hideTooltip } = useTooltip(wrapperRef, { trigger: "focus", @@ -74,6 +77,8 @@ export default function AttributeEditor({ note }: { note: FNote }) { offset: "0,30" }); + const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget()); + useEffect(() => { if (state === "showHelpTooltip") { showTooltip(); @@ -92,7 +97,7 @@ export default function AttributeEditor({ note }: { note: FNote }) { htmlAttrs += " "; } - setCurrentValue(htmlAttrs); + setInitialValue(htmlAttrs); } useEffect(() => { @@ -100,30 +105,93 @@ export default function AttributeEditor({ note }: { note: FNote }) { }, [ note ]); return ( -
    - { - console.log("Data changed!"); - }} - onClick={(pos) => { - if (pos && pos.textNode && pos.textNode.data) { - setState("showAttributeDetail") - } else { - setState("showHelpTooltip"); - } - }} - disableNewlines disableSpellcheck - /> -
    + <> +
    + { + currentValueRef.current = currentValue ?? ""; + }} + onClick={(e, pos) => { + if (pos && pos.textNode && pos.textNode.data) { + const clickIndex = getClickIndex(pos); + + let parsedAttrs; + + try { + parsedAttrs = attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current), true); + } catch (e) { + // the input is incorrect because the user messed up with it and now needs to fix it manually + return null; + } + + let matchedAttr: Attribute | null = null; + + for (const attr of parsedAttrs) { + if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) { + matchedAttr = attr; + break; + } + } + + setTimeout(() => { + if (matchedAttr) { + attributeDetailWidget.showAttributeDetail({ + allAttributes: parsedAttrs, + attribute: matchedAttr, + isOwned: true, + x: e.pageX, + y: e.pageY + }); + setState("showAttributeDetail"); + } else { + setState("showHelpTooltip"); + } + }, 100); + } else { + setState("showHelpTooltip"); + } + }} + disableNewlines disableSpellcheck + /> +
    + + {attributeDetailWidgetEl} + ) +} + +function getPreprocessedData(currentValue: string) { + const str = currentValue + .replace(/]+href="(#[A-Za-z0-9_/]*)"[^>]*>[^<]*<\/a>/g, "$1") + .replace(/ /g, " "); // otherwise .text() below outputs non-breaking space in unicode + + return $("
    ").html(str).text(); +} + +function getClickIndex(pos: ModelPosition) { + let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0); + + let curNode: ModelNode | Text | ModelElement | null = pos.textNode; + + while (curNode?.previousSibling) { + curNode = curNode.previousSibling; + + if ((curNode as ModelElement).name === "reference") { + clickIndex += (curNode.getAttribute("href") as string).length + 1; + } else if ("data" in curNode) { + clickIndex += (curNode.data as string).length; + } + } + + return clickIndex; } \ No newline at end of file From 12053e75bb600d31527b9dfe8207144beedeca29 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 13:13:01 +0300 Subject: [PATCH 245/532] chore(react/ribbon): fix size of attribute widget --- apps/client/src/widgets/react/CKEditor.tsx | 2 +- apps/client/src/widgets/react/hooks.tsx | 6 ++++-- apps/client/src/widgets/ribbon/style.css | 5 +++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/react/CKEditor.tsx b/apps/client/src/widgets/react/CKEditor.tsx index e9080e310..00399c6e8 100644 --- a/apps/client/src/widgets/react/CKEditor.tsx +++ b/apps/client/src/widgets/react/CKEditor.tsx @@ -10,7 +10,7 @@ interface CKEditorOpts { disableNewlines?: boolean; disableSpellcheck?: boolean; onChange?: (newValue?: string) => void; - onClick?: (e, pos?: ModelPosition | null) => void; + onClick?: (e: MouseEvent, pos?: ModelPosition | null) => void; } export default function CKEditor({ currentValue, className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange, onClick }: CKEditorOpts) { diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 6436b1734..304b9b22f 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -14,6 +14,7 @@ import FBlob from "../../entities/fblob"; import NoteContextAwareWidget from "../note_context_aware_widget"; import { Ref, RefObject, VNode } from "preact"; import { Tooltip } from "bootstrap"; +import { CSSProperties } from "preact/compat"; type TriliumEventHandler = (data: EventData) => void; const registeredHandlers: Map[]>> = new Map(); @@ -415,9 +416,10 @@ export function useNoteBlob(note: FNote | null | undefined): [ FBlob | null | un return [ blob ] as const; } -export function useLegacyWidget(widgetFactory: () => T, { noteContext, containerClassName }: { +export function useLegacyWidget(widgetFactory: () => T, { noteContext, containerClassName, containerStyle }: { noteContext?: NoteContext; containerClassName?: string; + containerStyle?: CSSProperties; } = {}): [VNode, T] { const ref = useRef(null); const parentComponent = useContext(ParentComponent); @@ -454,7 +456,7 @@ export function useLegacyWidget(widgetFactory: () => T, { } }, [ noteContext ]); - return [
    , widget ] + return [
    , widget ] } /** diff --git a/apps/client/src/widgets/ribbon/style.css b/apps/client/src/widgets/ribbon/style.css index 22c9d7b43..b66302a42 100644 --- a/apps/client/src/widgets/ribbon/style.css +++ b/apps/client/src/widgets/ribbon/style.css @@ -331,4 +331,9 @@ .attribute-list-editor p { margin: 0 !important; } + +.attribute-list .attr-detail { + contain: none; +} + /* #endregion */ \ No newline at end of file From 735da2a855d05183fe4d2e0c1261a42ff8be8b37 Mon Sep 17 00:00:00 2001 From: Francis C Date: Thu, 21 Aug 2025 17:49:01 +0200 Subject: [PATCH 246/532] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (1560 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/ --- .../client/src/translations/cn/translation.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index b7fbe27b6..7ad6618a0 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -1871,7 +1871,12 @@ "selected_provider": "已选提供商", "selected_provider_description": "选择用于聊天和补全功能的AI提供商", "select_model": "选择模型...", - "select_provider": "选择提供商..." + "select_provider": "选择提供商...", + "ai_enabled": "已启用 AI 功能", + "ai_disabled": "已禁用 AI 功能", + "no_models_found_online": "找不到模型。请检查您的 API 密钥及设置。", + "no_models_found_ollama": "找不到 Ollama 模型。请确认 Ollama 是否正在运行。", + "error_fetching": "获取模型失败:{{error}}" }, "code-editor-options": { "title": "编辑器" @@ -1999,5 +2004,15 @@ "next_theme_message": "当前使用旧版主题,要试用新主题吗?", "next_theme_button": "试用新主题", "dismiss": "关闭" + }, + "settings": { + "related_settings": "相关设置" + }, + "settings_appearance": { + "related_code_blocks": "文本笔记中代码块的色彩方案", + "related_code_notes": "代码笔记的色彩方案" + }, + "units": { + "percentage": "%" } } From 8ad05b92c084599cc67b50cd442bac01a85eea09 Mon Sep 17 00:00:00 2001 From: rodrigomescua Date: Fri, 22 Aug 2025 05:53:25 +0200 Subject: [PATCH 247/532] Translated using Weblate (Portuguese (Brazil)) Currently translated at 60.8% (950 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/pt_BR/ --- .../src/translations/pt_br/translation.json | 793 +++++++++++++++++- 1 file changed, 790 insertions(+), 3 deletions(-) diff --git a/apps/client/src/translations/pt_br/translation.json b/apps/client/src/translations/pt_br/translation.json index 2c8b93190..affef1885 100644 --- a/apps/client/src/translations/pt_br/translation.json +++ b/apps/client/src/translations/pt_br/translation.json @@ -78,7 +78,121 @@ "n_notes_queued_2": "{{ count }} notas enfileiradas para indexação", "notes_indexed_0": "{{ count }} nota indexada", "notes_indexed_1": "{{ count }} notas indexadas", - "notes_indexed_2": "{{ count }} notas indexadas" + "notes_indexed_2": "{{ count }} notas indexadas", + "temperature": "Temperatura", + "retry_queued": "Nota enfileirada para nova tentativa", + "queued_notes": "Notas Enfileiradas", + "empty_key_warning": { + "voyage": "A chave de API da Voyage API está vazia. Por favor, digite uma chave de API válida.", + "ollama": "A chave de API da Ollama API está vazia. Por favor, digite uma chave de API válida.", + "anthropic": "A chave de API Anthropic está vazia. Por favor, digite uma chave de API válida.", + "openai": "A chave de API OpenAI está vazia. Por favor, digite uma chave de API válida." + }, + "not_started": "Não iniciado", + "title": "Configurações de IA", + "processed_notes": "Notas Processadas", + "total_notes": "Total de Notas", + "progress": "Andamento", + "failed_notes": "Notas com Falha", + "last_processed": "Últimas Processadas", + "refresh_stats": "Atualizar Estatísticas", + "enable_ai_features": "Ativar recurso IA/LLM", + "enable_ai_description": "Ativar recursos IA como sumarização de notas, geração de conteúdo, e outras capacidades de LLM", + "openai_tab": "OpenAI", + "anthropic_tab": "Anthropic", + "voyage_tab": "Voyage AI", + "enable_ai": "Ativar recursos IA/LLM", + "provider_configuration": "Configuração de Provedor de IA", + "system_prompt": "Prompt de Sistema", + "system_prompt_description": "Prompt padrão de sistema usado para todas as interações de IA", + "openai_configuration": "Configuração OpenAI", + "openai_settings": "Opções OpenAI", + "api_key": "Chave de API", + "url": "URL Base", + "model": "Modelo", + "openai_api_key_description": "Sua API Key da OpenAI para acessar os serviços de IA", + "anthropic_api_key_description": "Sua API Key da Anthropic para acessar os modelos Claude", + "default_model": "Modelo Padrão", + "openai_model_description": "Exemplos: gpt-4o, gpt-4-turbo, gpt-3.5-turbo", + "base_url": "URL Base", + "openai_url_description": "Padrão: https://api.openai.com/v1", + "anthropic_settings": "Configurações Anthropic", + "anthropic_url_description": "URL Base da API Anthropic (padrão: https://api.anthropic.com)", + "anthropic_model_description": "Modelos Claude da Anthropic para completar conversas", + "voyage_settings": "Configurações Voyage AI", + "retry": "Tentar novamente", + "retry_failed": "Falha ao enfileirar nota para nova tentativa", + "max_notes_per_llm_query": "Máximo de Notas por Consulta", + "max_notes_per_llm_query_description": "Número máximo de notas similares para incluir no contexto da IA", + "active_providers": "Provedores Ativos", + "disabled_providers": "Provedores Desativados", + "remove_provider": "Remover provedor da pesquisa", + "restore_provider": "Restaurar provedor na pesquisa", + "similarity_threshold": "Tolerância de Similaridade", + "similarity_threshold_description": "Pontuação máxima de similaridade (0-1) para notas a serem incluídas no contexto das consultas de LLM", + "reprocess_index": "Reconstruir Índice de Pesquisa", + "reprocessing_index": "Reconstruindo…", + "reprocess_index_started": "Otimiação do índice de pesquisa iniciado em plano de fundo", + "reprocess_index_error": "Erro ao reconstruir índice de pesquisa", + "index_rebuild_progress": "Andamento da Reconstrução do Índice", + "index_rebuilding": "Otimizando índice ({{percentage}}%)", + "index_rebuild_complete": "Otimização de índice finalizada", + "index_rebuild_status_error": "Erro ao verificar o estado da reconstrução do índice", + "never": "Nunca", + "processing": "Processando ({{percentage}}%)", + "incomplete": "Incompleto ({{percentage}}%)", + "complete": "Completo (100%)", + "refreshing": "Atualizando…", + "auto_refresh_notice": "Atualizando automaticamente a cada {{seconds}} segundos", + "note_queued_for_retry": "Nota enfileirada para nova tentativa", + "failed_to_retry_note": "Falha ao retentar nota", + "all_notes_queued_for_retry": "Todas as notas com falha enfileiradas para nova tentativa", + "failed_to_retry_all": "Falha ao retentar notas", + "ai_settings": "Configurações IA", + "api_key_tooltip": "Chave de API para acessar o serviço", + "agent": { + "processing": "Processando…", + "thinking": "Pensando…", + "loading": "Carregando…", + "generating": "Gerando…" + }, + "name": "IA", + "openai": "OpenAI", + "use_enhanced_context": "Usar contexto aprimorado", + "enhanced_context_description": "Alimentar IA com mais contexto sobre a nota e suas notas relacionadas para melhores respostas", + "show_thinking": "Exibir pensamento", + "enter_message": "Digite sua mensagem…", + "error_contacting_provider": "Erro ao contatar o provedor de IA. Por favor, verifique suas configurações e sua conexão de internet.", + "error_generating_response": "Erro ao gerar resposta da IA", + "index_all_notes": "Indexar Todas as Notas", + "index_status": "Estado do Índice", + "indexed_notes": "Notas Indexadas", + "indexing_stopped": "Indexação interrompida", + "indexing_in_progress": "Indexação em andamento…", + "last_indexed": "Última Indexada", + "note_chat": "Conversa de Nota", + "sources": "Origens", + "start_indexing": "Iniciar Indexação", + "use_advanced_context": "Usar Contexto Avançado", + "ollama_no_url": "Ollama não está configurado. Por favor, digite uma URL válida.", + "chat": { + "root_note_title": "Conversas IA", + "root_note_content": "Esta nota contém suas conversas com IA salvas.", + "new_chat_title": "Nova Conversa", + "create_new_ai_chat": "Criar nova Conversa IA" + }, + "create_new_ai_chat": "Criar nova Conversa IA", + "configuration_warnings": "Existem alguns problemas com sua configuração de IA. Por fovor, verifique suas configurações.", + "experimental_warning": "O recurso de LLM atualmente é experimental - você foi avisado.", + "selected_provider": "Provedor Selecionado", + "selected_provider_description": "Escolha o provedor de IA para conversas e recursos de completar", + "select_model": "Selecionar modelo…", + "select_provider": "Selecionar provedor…", + "ai_enabled": "Recursos de IA habilitados", + "ai_disabled": "Recursos de IA desabilitados", + "no_models_found_online": "Nenhum modelo encontrado. Por favor, verifique sua chave de API e as configurações.", + "no_models_found_ollama": "Nenhum modelo Ollama encontrado. Por favor, verifique se o Ollama está em execução.", + "error_fetching": "Erro ao obter modelos: {{error}}" }, "confirm": { "confirmation": "Confirmação", @@ -405,7 +519,7 @@ "share_index": "notas com este rótulo irão listar todas as raízes das notas compartilhadas", "display_relations": "nomes das relações separados por vírgula que devem ser exibidos. Todas as outras serão ocultadas.", "hide_relations": "nomes das relações separados por vírgula que devem ser ocultados. Todas as outras serão exibidas.", - "title_template": "Título padrão das notas criadas como filhas desta nota. O valor é avaliado como uma string JavaScript e pode ser enriquecido com conteúdo dinâmico usando as variáveis injetadas now e parentNote. Exemplos:\n\n
      \n
    • ${parentNote.getLabelValue('authorName')}'s literary works
    • \n
    • Log for ${now.format('YYYY-MM-DD HH:mm:ss')}
    • \n
    \n\nVeja a wiki com detalhes, a documentação da API para parentNote e para now para mais informações.", + "title_template": "título padrão das notas criadas como filhas desta nota. O valor é avaliado como uma string JavaScript \n e pode ser enriquecido com conteúdo dinâmico usando as variáveis injetadas now e parentNote. Exemplos:\n \n
      \n
    • ${parentNote.getLabelValue('authorName')}'s literary works
    • \n
    • Log for ${now.format('YYYY-MM-DD HH:mm:ss')}
    • \n
    \n \n Veja a wiki com detalhes, a documentação da API para parentNote e para now para mais informações.", "template": "Esta nota aparecerá na seleção de modelos disponíveis ao criar uma nova nota", "toc": "#toc ou #toc=show irá forçar a exibição do Sumário, #toc=hide irá forçar que ele fique oculto. Se o rótulo não existir, será considerado o ajuste global", "color": "define a cor da nota na árvore de notas, links etc. Use qualquer valor de cor CSS válido, como 'red' ou #a13d5f", @@ -414,6 +528,679 @@ "keep_current_hoisting": "Abrir este link não alterará o destaque, mesmo que a nota não seja exibível na subárvore destacada atual.", "execute_button": "Titulo do botão que executará a nota de código atual", "exclude_from_note_map": "Notas com este rótulo ficarão ocultas no Mapa de Notas", - "new_notes_on_top": "Novas notas serão criadas no topo da nota raiz, não na parte inferior." + "new_notes_on_top": "Novas notas serão criadas no topo da nota raiz, não na parte inferior.", + "execute_description": "Descrição longa da nota de código atualmente exibida junto ao botão executar", + "print_page_size": "Quando exportando para PDF, altera o tamanho da página. Valores suportados: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", + "and_more": "... e {{count}} mais.", + "other_notes_with_name": "Outras notas com {{attributeType}} igual a \"{{attributeName}}\"", + "color_type": "Cor", + "run_on_note_creation": "executa quando a nota é criada no backend. Use esta relação se quiser executar o script para todas as notas criadas em uma subárvore específica. Neste caso, crie-a na nota raiz da subárvore e torne-a herdável. Uma nova nota criada dentro da subárvore (qualquer profundidade) irá acionar o script.", + "run_on_child_note_creation": "executa quando uma nova nota é criada sob a nota onde esta relação está definida", + "run_on_note_title_change": "executa quando o título da nota é alterado (inclusive na criação de nota)", + "run_on_note_content_change": "executa quando o conteúdo da nota é alterado (inclusive na criação de nota).", + "run_on_note_change": "executa quando a nota é alterada (inclusive na criação de nota). Não incluí alterações no conteúdo", + "run_on_note_deletion": "executa quando a nota está sendo excluída", + "run_on_branch_creation": "executa quando uma ramificação é criada. Ramificação é uma ligação entre nota pai e nota filha e é criado, por exemplo, ao clonar ou mover uma nota.", + "run_on_branch_change": "executa quando uma remificação é atualizada.", + "run_on_attribute_creation": "executa quando um novo atributo é criado para a nota que define esta relação", + "run_on_attribute_change": " executa quando o atributo é alterado na nota que define esta relação. Também é disparado quando o atributo é excluído", + "widget_relation": "o destino desta relação será executado e renderizado como um widget na barra lateral" + }, + "attachments_actions": { + "delete_attachment": "Excluir anexo", + "open_externally": "Abrir externamente", + "open_custom": "Abrir customizado", + "download": "Baixar", + "rename_attachment": "Renomear anexo", + "upload_new_revision": "Enviar nova revisão", + "copy_link_to_clipboard": "Copiar link para a área de transferência", + "convert_attachment_into_note": "Converter anexo para nota", + "upload_success": "Uma nova revisão de anexo foi enviada.", + "upload_failed": "O envio de uma nova revisão de anexo falhou.", + "delete_success": "O anexo '{{title}}' foi excluído.", + "convert_success": "O anexo '{{title}}' foi convertido para uma nota.", + "enter_new_name": "Por favor, digite o novo nome do anexo", + "delete_confirm": "Tem certeza que deseja excluir o anexo '{{title}}'?", + "convert_confirm": "Tem certeza que deseja converter o anexo '{{title}}' em uma nota separada?", + "open_externally_title": "O arquivo será aberto em uma aplicação externa e monitorado por alterações. Você então poderá enviar a versão modificada de volta para o Trilium.", + "open_custom_title": "O arquivo será aberto em uma aplicação externa e monitorado por alterações. Você então poderá enviar a versão modificada de volta para o Trilium.", + "open_externally_detail_page": "A abertura de anexo externamente só está disponível através da página de detalhes. Por favor, primeiro clique nos detalhes do anexo e repita a ação.", + "open_custom_client_only": "A abertura customizada de anexos só pode ser feita usando o cliente de desktop." + }, + "attachment_detail": { + "you_can_also_open": ", você também pode abrir o(a) ", + "open_help_page": "Abrir página de ajuda nos anexos", + "list_of_all_attachments": "Lista de todos os anexos", + "attachment_deleted": "Este anexo foi excluído." + }, + "ancestor": { + "depth_gt": "é maior que {{count}}", + "label": "Ancestral", + "placeholder": "buscar notas pelo nome", + "depth_label": "profundidade", + "depth_doesnt_matter": "não importa", + "depth_eq": "é exatamente {{count}}", + "direct_children": "filho direto", + "depth_lt": "é menor que {{count}}" + }, + "add_relation": { + "add_relation": "Adicionar relação", + "allowed_characters": "Caracteres alfanuméricos, underscore e vírgula são permitidos.", + "relation_name": "nome da relação", + "to": "para", + "target_note": "nota destino", + "create_relation_on_all_matched_notes": "Crie a relação informada em todas as notas correspondentes." + }, + "delete_label": { + "label_name_placeholder": "nome da etiqueta", + "label_name_title": "Caracteres alfanuméricos, underscore e vírgula são permitidos.", + "delete_label": "Excluir etiqueta" + }, + "rename_label": { + "rename_label": "Renomear etiqueta", + "rename_label_from": "Renomear etiqueta de", + "old_name_placeholder": "nome antigo", + "to": "Para", + "new_name_placeholder": "novo nome", + "name_title": "Caracteres alfanuméricos, underscore e vírgula são permitidos." + }, + "execute_script": { + "example_1": "Por exemplo para anexar um texto ao título de uma nota, use este pequeno script:", + "execute_script": "Executar script", + "help_text": "Você pode executar scripts simples nas notas correspondentes.", + "example_2": "Um exemplo mais complexo seria excluir todos os atributos das notas correspondentes:" + }, + "attribute_editor": { + "help_text_body1": "Para adicionar uma etiqueta, digite por exemplo #rock ou se você também quer adicionar um valor então por exemplo #year = 2020", + "help_text_body2": "Para relação, digite ~author = @, que deve ser exibido um autocompletar onde você pode encontrar a nota desejada.", + "help_text_body3": "Alternativamente, você pode adicionar etiqueta e relação usando o botão + no lado direito.", + "save_attributes": "Salvar atributos ", + "add_a_new_attribute": "Adicionar um novo atributo", + "add_new_label": "Adicionar nova etiqueta ", + "add_new_relation": "Adicionar nova relação ", + "add_new_label_definition": "Adicionar nova definição de etiqueta", + "add_new_relation_definition": "Adicionar nova definição de relação", + "placeholder": "Digite as etiquetas e relações aqui" + }, + "abstract_bulk_action": { + "remove_this_search_action": "Remover esta ação de busca" + }, + "add_label": { + "add_label": "Adicionar etiqueta", + "label_name_placeholder": "nome da etiqueta", + "label_name_title": "Caracteres alfanuméricos, underscore e vírgula são permitidos.", + "to_value": "para o valor", + "new_value_placeholder": "novo valor", + "help_text": "Em todas as notas correspondentes:", + "help_text_item1": "criar a etiqueta indicada se a nota ainda não tiver uma", + "help_text_item2": "ou altere o valor da etiqueta existente", + "help_text_note": "Você também pode chamar este método sem valor, neste caso a etiqueta será atribuída à nota sem valor." + }, + "update_label_value": { + "update_label_value": "Atualizar valor da etiqueta", + "label_name_placeholder": "nome da etiqueta", + "label_name_title": "Caracteres alfanuméricos, underscore e vírgula são permitidos.", + "new_value_placeholder": "novo valor", + "to_value": "para o valor", + "help_text": "Em todas as notas correspondentes, altera o valor da etiqueta existente.", + "help_text_note": "Você também pode chamar este método sem um valor, neste caso a etiqueta será à nota sem um valor." + }, + "delete_relation": { + "allowed_characters": "Caracteres alfanuméricos, underscore e vírgula são permitidos.", + "delete_relation": "Excluir relação", + "relation_name": "nome da relação" + }, + "rename_relation": { + "allowed_characters": "Caracteres alfanuméricos, underscore e vírgula são permitidos.", + "rename_relation": "Renomar relação", + "rename_relation_from": "Renomear relação de", + "old_name": "nome antigo", + "to": "Para", + "new_name": "novo nome" + }, + "update_relation_target": { + "allowed_characters": "Caracteres alfanuméricos, underscore e vírgula são permitidos.", + "to": "para", + "target_note": "nota destino", + "on_all_matched_notes": "Em todas as notas correspondentes", + "change_target_note": "alterar nota destino da relação existente", + "update_relation_target": "Atualizar destino da relação", + "update_relation": "Atualizar relação", + "relation_name": "nome da relação" + }, + "content_renderer": { + "open_externally": "Abrir externamente" + }, + "modal": { + "close": "Fechar" + }, + "api_log": { + "close": "Fechar" + }, + "attachment_detail_2": { + "will_be_deleted_in": "Este anexo será excluído automaticamente em {{time}}", + "will_be_deleted_soon": "Este anexo será excluído automaticamente em breve", + "deletion_reason": ", porque o anexo não está associado ao conteúdo da nota. Para evitar a exclusão, adicione o anexo novamente ao conteúdo ou converta o anexo em uma nota.", + "role_and_size": "Regra: {{role}}, Tamanho: {{size}}", + "link_copied": "Link do anexo copiado para a área de transferência.", + "unrecognized_role": "Regra desconhecida de anexo '{{role}}'." + }, + "bookmark_switch": { + "bookmark": "Favorito", + "bookmark_this_note": "Favoritar esta nota no painel da esquerda", + "remove_bookmark": "Remover favorito" + }, + "editability_select": { + "auto": "Auto", + "read_only": "Somente leitura", + "always_editable": "Sempre Editável", + "note_is_editable": "A nota é editável se não for muito longa.", + "note_is_read_only": "A nota é somente leitura, mas pode ser editada com um clique no botão.", + "note_is_always_editable": "A nota é sempre editável, independentemente do seu tamanho." + }, + "note-map": { + "button-link-map": "Mapa de Links", + "button-tree-map": "Mapa em Árvore" + }, + "tree-context-menu": { + "open-in-a-new-tab": "Abrir em uma nova aba Ctrl+Click", + "open-in-a-new-split": "Abrir em um novo painel dividido", + "insert-note-after": "Inserir nota após", + "insert-child-note": "Inserir nota filha", + "delete": "Excluir", + "search-in-subtree": "Buscar na subárvore" + }, + "command_palette": { + "search_subtree_title": "Buscar na Subárvore", + "search_subtree_description": "Buscar dentro da subárvore atual", + "search_history_title": "Exibir Histórico de Busca", + "search_history_description": "Visualizar buscas anteriores", + "configure_launch_bar_title": "Configurar Barra de Execução" + }, + "delete_note": { + "delete_note": "Excluir nota", + "delete_matched_notes": "Excluir notas correspondentes", + "delete_matched_notes_description": "Isso irá excluir as notas correspondentes.", + "undelete_notes_instruction": "Depois da exclusão, é possível desfazer através da janela de Alterações Recentes.", + "erase_notes_instruction": "Para apagar notas permanentemente, você pode fazer isso depois da exclusão indo em Opções -> Outros e clicar no botão \"Apagar notas excluídas agora\"." + }, + "delete_revisions": { + "delete_note_revisions": "Excluir revisões da nota", + "all_past_note_revisions": "Todas as revisões anteriores das notas correspondentes serão excluídas. A nota em si será perservada. Ou seja, o histórico da nota será removido." + }, + "move_note": { + "move_note": "Mover nota", + "to": "para", + "target_parent_note": "nota pai destino", + "on_all_matched_notes": "Em todas as notas correspondentes" + }, + "rename_note": { + "rename_note": "Renomear nota", + "rename_note_title_to": "Renomear título da nota para", + "new_note_title": "novo título da nota", + "click_help_icon": "Clique no ícone de ajuda a direita para ver todas as opções", + "example_note": "Nota - todas as notas correspondentes serão renomeadas para 'Nota'", + "example_new_title": "NOVO: ${note.title} - o título das notas correspondentes receberá o prefixo 'NOVO: '", + "example_date_prefix": "${note.dateCreatedObj.format('MM-DD:')}: ${note.title} - notas correspondentes receberão um prefixo com o mês-dia da data de criação da nota", + "api_docs": "Veja da documentação da API para nota e suas propriedades dateCreatedObj / utcDateCreatedObj para detalhes." + }, + "calendar": { + "mon": "Seg", + "tue": "Ter", + "wed": "Qua", + "thu": "Qui", + "fri": "Sex", + "sat": "Sáb", + "sun": "Dom", + "cannot_find_day_note": "Nota do dia não encontrada", + "cannot_find_week_note": "Nota semanal não encontrada", + "january": "Janeiro", + "febuary": "Fevereiro", + "march": "Março", + "april": "Abril", + "may": "Maio", + "june": "Junho", + "july": "Julho", + "august": "Agosto", + "september": "Setembro", + "october": "Outubro", + "november": "Novembro", + "december": "Dezembro" + }, + "close_pane_button": { + "close_this_pane": "Fechar este painel" + }, + "create_pane_button": { + "create_new_split": "Criar nova divisão" + }, + "edit_button": { + "edit_this_note": "Editar esta nota" + }, + "show_toc_widget_button": { + "show_toc": "Mostrar Tabela de Conteúdo" + }, + "show_highlights_list_widget_button": { + "show_highlights_list": "Mostrar Lista de Destaques" + }, + "global_menu": { + "menu": "Menu", + "options": "Opções", + "open_new_window": "Abrir Nova Janela", + "switch_to_mobile_version": "Alternar para Versão Mobile", + "switch_to_desktop_version": "Alternar para Versão Desktop", + "zoom": "Zoom", + "toggle_fullscreen": "Alternar Tela Cheia", + "zoom_out": "Reduzir", + "reset_zoom_level": "Redefinir Zoom", + "zoom_in": "Aumentar", + "configure_launchbar": "Configurar Barra de Lançamento", + "show_shared_notes_subtree": "Exibir Subárvore de Notas Compartilhadas", + "advanced": "Avançado", + "open_dev_tools": "Abrir Ferramentas de Desenvolvedor", + "open_sql_console": "Abrir Console SQL", + "open_sql_console_history": "Abrir Histórico de Console SQL", + "open_search_history": "Abrir Histórico de Busca", + "show_backend_log": "Abrir Log do Servidor", + "reload_frontend": "Recarregar Frontend", + "show_hidden_subtree": "Exibir Subárvore Oculta", + "show_help": "Exibir Ajuda", + "about": "Sobre o Trilium Notes", + "logout": "Sair", + "show-cheatsheet": "Exibir Cheatsheet", + "toggle-zen-mode": "Modo Zen", + "reload_hint": "Recarregar pode ajudar com alguns problemas visuais sem reiniciar toda a aplicação." + }, + "zen_mode": { + "button_exit": "Sair do Modo Zen" + }, + "sync_status": { + "in_progress": "Sincronização com o servidor em andamento.", + "unknown": "

    O estado da sincronização será conhecido assim que a próxima tentativa começar.

    Clique para iniciar a sincronização agora.

    ", + "connected_with_changes": "

    Conectado ao servidor de sincronização.
    Existem algumas alterações esperando para serem sincronizadas.

    Clique para sincronizar.

    ", + "connected_no_changes": "

    Conectado ao servidor de sincronização.
    Todas as alterações já foram sincronizadas.

    Clique para sincronizar.

    ", + "disconnected_with_changes": "

    A conexão ao servidor de sincronização falhou.
    Existem algumas alterações esperando para serem sincronizadas.

    Clique para sincronizar.

    ", + "disconnected_no_changes": "

    A conexão ao servidor de sincronização falhou.
    Todas as alterações já foram sincronizadas.

    Clique para sincronizar.

    " + }, + "left_pane_toggle": { + "show_panel": "Exibir painel", + "hide_panel": "Esconder painel" + }, + "move_pane_button": { + "move_left": "Mover para a esquerda", + "move_right": "Mover para a direita" + }, + "note_actions": { + "convert_into_attachment": "Converter para anexo", + "re_render_note": "Renderizar nota novamente", + "search_in_note": "Buscar na nota", + "note_source": "Código Fonte da nota", + "note_attachments": "Anexos da nota", + "open_note_externally": "Abrir nota externamente", + "open_note_custom": "Abrir nota de forma customizada", + "import_files": "Importar arquivos", + "export_note": "Exportar nota", + "delete_note": "Excluir nota", + "print_note": "Imprimir nota", + "save_revision": "Salvar revisão", + "convert_into_attachment_failed": "A conversão da nota '{{title}}' falhou.", + "convert_into_attachment_successful": "A nota '{{title}}' foi convertida para anexo.", + "print_pdf": "Exportar como PDF…", + "open_note_externally_title": "O arquivo será aberto em uma aplicação externa e monitorado por alterações. Você então poderá enviar a versão modificada de volta para o Trilium.", + "convert_into_attachment_prompt": "Você tem certeza que quer converter a nota '{{title}}' em um anexo da nota pai?" + }, + "protected_session_status": { + "inactive": "Clique para entrar na sessão protegida", + "active": "Sessão protegida está ativada. Clique para deixar a sessão protegida." + }, + "revisions_button": { + "note_revisions": "Revisões da Nota" + }, + "update_available": { + "update_available": "Atualização disponível" + }, + "code_buttons": { + "execute_button_title": "Executar script", + "trilium_api_docs_button_title": "Abrir documentação da Trilium API", + "save_to_note_button_title": "Salvar para uma nota", + "opening_api_docs_message": "Abrindo documentação da API…", + "sql_console_saved_message": "Nota do Console SQL foi salva no caminho {{note_path}}" + }, + "hide_floating_buttons_button": { + "button_title": "Esconder botões" + }, + "show_floating_buttons_button": { + "button_title": "Exibir botões" + }, + "svg_export_button": { + "button_title": "Exportar diagrama como SVG" + }, + "relation_map_buttons": { + "zoom_in_title": "Aumentar", + "zoom_out_title": "Reduzir", + "create_child_note_title": "Criar nova nota filha e adicione neste mapa de relação" + }, + "zpetne_odkazy": { + "backlink": "{{count}} Links Reversos", + "backlinks": "{{count}} Links Reversos", + "relation": "relação" + }, + "mobile_detail_menu": { + "insert_child_note": "Inserir nota filha", + "delete_this_note": "Excluir essa nota", + "error_unrecognized_command": "Comando não reconhecido {{command}}" + }, + "note_icon": { + "change_note_icon": "Alterar ícone da nota", + "category": "Categoria:", + "search": "Busca:", + "reset-default": "Redefinir para o ícone padrão" + }, + "basic_properties": { + "note_type": "Tipo da nota", + "editable": "Editável", + "basic_properties": "Propriedades Básicas", + "language": "Idioma" + }, + "book_properties": { + "view_type": "Tipo de visualização", + "grid": "Grade", + "list": "Lista", + "collapse_all_notes": "Recolher todas as notas", + "expand_all_children": "Expandir todos os filhos", + "collapse": "Recolher", + "expand": "Expandir", + "book_properties": "Propriedades da Coleção", + "invalid_view_type": "Tipo de visualização inválido '{{type}}'", + "calendar": "Calendário", + "table": "Tabela", + "geo-map": "Geo Map", + "board": "Quadro" + }, + "edited_notes": { + "no_edited_notes_found": "Ainda não há nenhuma nota editada neste dia…", + "title": "Notas Editadas", + "deleted": "(excluído)" + }, + "file_properties": { + "note_id": "ID da Nota", + "original_file_name": "Nome original do arquivo", + "file_type": "Tipo do arquivo", + "file_size": "Tamanho do arquivo", + "download": "Baixar", + "open": "Abrir", + "upload_new_revision": "Enviar nova revisão", + "upload_success": "Uma nova revisão de arquivo foi enviada.", + "upload_failed": "O envio de uma nova revisão de arquivo falhou.", + "title": "Arquivo" + }, + "image_properties": { + "original_file_name": "Nome original do arquivo", + "file_type": "Tipo do arquivo", + "file_size": "Tamanho do arquivo", + "download": "Baixar", + "open": "Abrir", + "copy_reference_to_clipboard": "Copiar referência para a área de transferência", + "upload_new_revision": "Enviar nova revisão", + "upload_success": "Uma nova revisão de imagem foi enviado.", + "upload_failed": "O envio de uma nova revisão de imagem falhou: {{message}}", + "title": "Imagem" + }, + "inherited_attribute_list": { + "title": "Atributos Herdados", + "no_inherited_attributes": "Nenhum atributo herdado." + }, + "note_info_widget": { + "note_id": "ID da Nota", + "created": "Criado", + "modified": "Editado", + "type": "Tipo", + "note_size": "Tamanho da nota", + "calculate": "calcular", + "title": "Informações da nota", + "subtree_size": "(tamanho da subárvore: {{size}} em {{count}} notas)" + }, + "note_map": { + "open_full": "Expandir completamente", + "collapse": "Recolher para tamanho normal", + "title": "Mapa de Notas", + "fix-nodes": "Fixar nós", + "link-distance": "Distância do Link" + }, + "note_paths": { + "title": "Caminho das Notas", + "clone_button": "Clonar nota para novo local…", + "intro_placed": "Esta nova está localizada nos caminhos:", + "intro_not_placed": "Esta nota ainda não está em nenhuma árvore de notas.", + "archived": "Arquivado", + "search": "Pesquisar" + }, + "note_properties": { + "this_note_was_originally_taken_from": "Esta nota foi originalmente obtida de:", + "info": "Informações" + }, + "promoted_attributes": { + "promoted_attributes": "Atributos Promovidos", + "unset-field-placeholder": "não atribuído", + "open_external_link": "Abrir link externo", + "unknown_label_type": "Tipo de etiqueta desconhecido '{{type}}'", + "unknown_attribute_type": "Tipo de atributo desconhecido '{{type}}'", + "add_new_attribute": "Adicionar novo atributo", + "remove_this_attribute": "Remover este atributo", + "remove_color": "Remover a etiqueta de cor" + }, + "script_executor": { + "query": "Consulta", + "script": "Script", + "execute_query": "Executar Consulta", + "execute_script": "Executar Script" + }, + "search_definition": { + "add_search_option": "Adicionar opção de pesquisa:", + "search_string": "pesquisa de texto", + "search_script": "pesquisa de script", + "ancestor": "ancestral", + "fast_search": "pesquisa rápida", + "include_archived": "incluir arquivados", + "order_by": "ordenar por", + "limit": "limite", + "limit_description": "Limitar número de resultados", + "debug": "depurar", + "action": "ação", + "search_button": "Pesquisar enter", + "search_execute": "Pesquisar & Executar ações", + "save_to_note": "Salvar para nota", + "search_parameters": "Parâmetros de Pesquisa", + "unknown_search_option": "Opção de pesquisa desconhecida {{searchOptionName}}", + "actions_executed": "As ações foram executadas.", + "search_note_saved": "Nota de pesquisa foi salva em {{- notePathTitle}}" + }, + "similar_notes": { + "title": "Notas Similares", + "no_similar_notes_found": "Nenhum nota similar encontrada." + }, + "abstract_search_option": { + "remove_this_search_option": "Remover esta opção de pesquisa", + "failed_rendering": "A renderização da opção de busca falhou: {{dto}} com o erro: {{error}} {{stack}}" + }, + "debug": { + "debug": "Depurar" + }, + "fast_search": { + "fast_search": "Pesquisa rápida" + }, + "include_archived_notes": { + "include_archived_notes": "Incluir notas arquivadas" + }, + "limit": { + "limit": "Limite", + "take_first_x_results": "Pegar apenas os X primeiros resultados." + }, + "order_by": { + "order_by": "Ordenar por", + "relevancy": "Relevância (padrão)", + "title": "Título", + "date_created": "Data de criação", + "date_modified": "Data da última modificação", + "content_size": "Tamaho do conteúdo da nota", + "content_and_attachments_size": "Tamanho do conteúdo da nota incluindo anexos", + "content_and_attachments_and_revisions_size": "Tamanho do conteúdo da nota incluindo anexos e revisões", + "revision_count": "Número de revisões", + "children_count": "Número de notas filhas", + "parent_count": "Número de clones", + "owned_label_count": "Número de etiquetas", + "owned_relation_count": "Número de relações", + "target_relation_count": "Número de relações para esta nota", + "random": "Ordem aleatória", + "asc": "Crescente (padrão)", + "desc": "Decrescente" + }, + "search_script": { + "title": "Buscar script:", + "placeholder": "buscar notas pelo nome", + "example_title": "Veja este exemplo:", + "example_code": "// 1. pré-filtro usando pesquisa padrão\nconst candidateNotes = api.searchForNotes(\"#journal\"); \n\n// 2. aplicando critérios de pesquisa customizados\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;" + }, + "search_string": { + "title_column": "Buscar texto:", + "search_syntax": "Sintaxe de pesquisa", + "also_see": "veja também", + "full_text_search": "Digite qualquer texto para busca por texto completo", + "label_abc": "retorna notas com a etiqueta abc", + "label_year": "corresponde notas com a etiqueta de ano 2019", + "label_rock_pop": "corresponde notas que tenham tanto a etiqueta rock quando pop", + "label_rock_or_pop": "apenas uma das etiquetas deve estar presente", + "label_year_comparison": "comparação numérica (também >, >=, <).", + "label_date_created": "notas criadas no último mês", + "error": "Erro na busca: {{error}}", + "search_prefix": "Busca:" + }, + "attachment_list": { + "open_help_page": "Abrir página de ajuda nos anexos", + "upload_attachments": "Enviar anexos", + "no_attachments": "Esta nota não possuí anexos." + }, + "editable_code": { + "placeholder": "Digite o conteúdo da sua nota de código aqui…" + }, + "editable_text": { + "placeholder": "Digite o conteúdo da sua nota aqui…" + }, + "empty": { + "search_placeholder": "buscar uma nota pelo nome", + "enter_workspace": "Entrar no workspace {{title}}" + }, + "file": { + "file_preview_not_available": "Prévia não disponível para este formato de arquivo." + }, + "protected_session": { + "enter_password_instruction": "É necessário digitar sua senha para mostar notas protegidas:", + "started": "A sessão protegida foi iniciada.", + "wrong_password": "Senha incorreta.", + "protecting-finished-successfully": "A proteção foi finalizada com sucesso.", + "unprotecting-finished-successfully": "A remoção da proteção foi finalizada com sucesso.", + "protecting-in-progress": "Proteções em andamento: {{count}}", + "unprotecting-in-progress-count": "Remoções de proteção em andamento: {{count}}", + "protecting-title": "Estado da proteção", + "unprotecting-title": "Estado da remoção de proteção" + }, + "relation_map": { + "open_in_new_tab": "Abrir em nova aba", + "remove_note": "Remover nota", + "edit_title": "Editar título", + "rename_note": "Renomear nota", + "enter_new_title": "Digite o novo título da nota:", + "remove_relation": "Remover relação", + "confirm_remove_relation": "Tem certeza que deseja remover esta relação?", + "connection_exists": "A conexão '{{name}}' já existe entre estas notas.", + "note_not_found": "Nota {{noteId}} não encontrada!", + "note_already_in_diagram": "A nota \"{{title}}\" já está no diagrama.", + "enter_title_of_new_note": "Digite o título da nova nota", + "default_new_note_title": "nova nota", + "click_on_canvas_to_place_new_note": "Clique no quadro para incluir uma nova nota" + }, + "web_view": { + "web_view": "Web View" + }, + "backend_log": { + "refresh": "Recarregar" + }, + "consistency_checks": { + "title": "Chegagem de Consistência", + "find_and_fix_button": "Encontrar e corrigir problemas de consistência", + "finding_and_fixing_message": "Buscando e corrigindo problemas de consistência…", + "issues_fixed_message": "Qualquer problema de consistência encontrado foi corrigido." + }, + "database_integrity_check": { + "check_button": "Verificar integridade do banco de dados", + "checking_integrity": "Verificando integridade do banco de dados…", + "integrity_check_succeeded": "Verificação de integridade bem sucedida - nenhum problema encontrado.", + "integrity_check_failed": "Verificação de integridade falhou: {{results}}" + }, + "sync": { + "title": "Sincronizar", + "force_full_sync_button": "Forçar sincronização completa", + "full_sync_triggered": "Sincronização completa iniciada", + "finished-successfully": "Sincronização finalizada com sucesso.", + "failed": "Sincronização falhou: {{message}}" + }, + "vacuum_database": { + "description": "Isso irá reconstruir o banco de dados, o que normalmente irá resultar em uma redução do arquivo do banco de dados. Nenhum dado será alterado." + }, + "fonts": { + "theme_defined": "Tema definido", + "fonts": "Fontes", + "main_font": "Fonte Principal", + "font_family": "Família da fonte", + "size": "Tamanho", + "note_tree_font": "Fonte da Árvore de Notas", + "note_detail_font": "Fonte Padrão da Nota", + "monospace_font": "Fonte Monospace (código)", + "not_all_fonts_available": "Nem todas as fontes listadas podem estar disponíveis em seu sistema.", + "apply_font_changes": "Para aplicar as alterações de fonte, clique em", + "reload_frontend": "recarregar frontend", + "generic-fonts": "Fontes genéricas", + "sans-serif-system-fonts": "Fontes sem serifa de sistema", + "serif-system-fonts": "Fontes serifadas de sistema", + "monospace-system-fonts": "Fontes monospace de sistema", + "handwriting-system-fonts": "Fontes de escrita à mão de sistema", + "serif": "Serifa", + "sans-serif": "Sem Serifa", + "monospace": "Monospace", + "system-default": "Padrão do Sistema" + }, + "max_content_width": { + "title": "Largura do Conteúdo", + "max_width_label": "Largura máxima do conteúdo", + "max_width_unit": "pixels", + "apply_changes_description": "Para aplicar as alterações de largura do conteúdo, clique em", + "reload_button": "recarregar frontend", + "reload_description": "alterações de opções de aparência" + }, + "native_title_bar": { + "title": "Barra de Título Nativa (requer recarregar o app)", + "enabled": "ativada", + "disabled": "desativada" + }, + "theme": { + "title": "Tema da Aplicação", + "theme_label": "Tema", + "override_theme_fonts_label": "Sobrepor fontes do tema", + "auto_theme": "Legado (Seguir esquema de cor do sistema)", + "light_theme": "Legado (Claro)", + "dark_theme": "Legado (Escuro)", + "triliumnext": "Trilium (Seguir esquema de cor do sistema)", + "triliumnext-light": "Trilium (Claro)", + "triliumnext-dark": "Trilium (Escuro)", + "layout": "Layout", + "layout-vertical-title": "Vertical", + "layout-horizontal-title": "Horizontal", + "layout-vertical-description": "barra de lançamento está a esquerda (padrão)", + "layout-horizontal-description": "barra de lançamento está abaixo da barra de abas, a barra de abas agora tem a largura total." + }, + "note_launcher": { + "this_launcher_doesnt_define_target_note": "Este lançador não define uma nota destino." + }, + "copy_image_reference_button": { + "button_title": "Copiar referência da imagem para a área de transferência, pode ser colado em uma nota de texto." } } From 206007bbce9b1bddd7abefc1eb08d97511cffd61 Mon Sep 17 00:00:00 2001 From: Newcomer1989 Date: Thu, 21 Aug 2025 23:31:13 +0200 Subject: [PATCH 248/532] Translated using Weblate (German) Currently translated at 81.7% (309 of 378 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/de/ --- .../src/assets/translations/de/server.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/de/server.json b/apps/server/src/assets/translations/de/server.json index 9a050ae9c..840e823f3 100644 --- a/apps/server/src/assets/translations/de/server.json +++ b/apps/server/src/assets/translations/de/server.json @@ -333,6 +333,21 @@ "show-backend-log": "Zeige Backend-Protokoll", "show-help": "Zeige Hilfe", "show-cheatsheet": "Zeige Cheatsheet", - "add-link-to-text": "Link zum Text hinzufügen" + "add-link-to-text": "Link zum Text hinzufügen", + "cut-into-note": "In neue Notiz verschieben", + "add-new-label": "Neues Label hinzufügen", + "add-new-relation": "Neue Beziehung hinzufügen", + "print-active-note": "Drucke aktive Notiz", + "export-active-note-as-pdf": "Exportiere aktive Notiz als PDF", + "move-note-up-in-hierarchy": "Notiz in der Hierarchie nach oben verschieben", + "move-note-down-in-hierarchy": "Notiz in der Hierarchie nach unten verschieben", + "edit-branch-prefix": "Zweigpräfix bearbeiten", + "select-all-notes-in-parent": "Alle Notizen in übergeordnetem Element auswählen", + "duplicate-subtree": "Unterbaum duplizieren", + "follow-link-under-cursor": "Folge Link unterhalb des Mauszeigers", + "insert-date-and-time-to-text": "Datum und Uhrzeit in Text einfügen", + "paste-markdown-into-text": "Markdown in Text einfügen", + "add-include-note-to-text": "Notiz in Text einfügen", + "edit-read-only-note": "Schreibgeschützte Notiz bearbeiten" } } From afe369c8765a61029d113efeab83b51d03220168 Mon Sep 17 00:00:00 2001 From: Francis C Date: Thu, 21 Aug 2025 17:44:13 +0200 Subject: [PATCH 249/532] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (1560 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/ --- .../client/src/translations/tw/translation.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/tw/translation.json b/apps/client/src/translations/tw/translation.json index b43ee5f68..4b5da1d69 100644 --- a/apps/client/src/translations/tw/translation.json +++ b/apps/client/src/translations/tw/translation.json @@ -1771,7 +1771,12 @@ "selected_provider": "已選提供者", "selected_provider_description": "選擇用於聊天和補全功能的 AI 提供者", "select_model": "選擇模型…", - "select_provider": "選擇提供者…" + "select_provider": "選擇提供者…", + "ai_enabled": "已啟用 AI 功能", + "ai_disabled": "已禁用 AI 功能", + "no_models_found_online": "找不到模型。請檢查您的 API 金鑰及設定。", + "no_models_found_ollama": "找不到 Ollama 模型。請確認 Ollama 是否正在執行。", + "error_fetching": "獲取模型失敗:{{error}}" }, "code-editor-options": { "title": "編輯器" @@ -1999,5 +2004,15 @@ "next_theme_message": "您正在使用舊版主題,要試用新主題嗎?", "next_theme_button": "試用新主題", "dismiss": "關閉" + }, + "settings": { + "related_settings": "相關設定" + }, + "settings_appearance": { + "related_code_blocks": "文字筆記中程式碼區塊的配色方案", + "related_code_notes": "程式碼筆記的配色方案" + }, + "units": { + "percentage": "%" } } From bd710ba66539a10e5261dcd66b8a1c258dded6ab Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 22 Aug 2025 07:27:19 +0200 Subject: [PATCH 250/532] Translated using Weblate (Russian) Currently translated at 100.0% (1560 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/ --- .../src/translations/ru/translation.json | 503 ++++++++++++++---- 1 file changed, 406 insertions(+), 97 deletions(-) diff --git a/apps/client/src/translations/ru/translation.json b/apps/client/src/translations/ru/translation.json index ca6bfe880..9be536c50 100644 --- a/apps/client/src/translations/ru/translation.json +++ b/apps/client/src/translations/ru/translation.json @@ -98,7 +98,8 @@ "snapshot_number_limit_unit": "снимков", "note_revisions_snapshot_limit_title": "Максимальное количество снимков заметок", "snapshot_number_limit_label": "Максимальное количество снимков:", - "erase_excess_revision_snapshots_prompt": "Удалить лишние снимки." + "erase_excess_revision_snapshots_prompt": "Удалить лишние снимки.", + "erase_excess_revision_snapshots": "Удалить лишние снимки версий" }, "password": { "alert_message": "Пожалуйста, запомните новый пароль. Пароль используется для входа в веб-интерфейс и шифрования защищённых заметок. Если вы забудете пароль, все ваши защищённые заметки будут потеряны навсегда.", @@ -115,10 +116,14 @@ "protected_session_timeout": "Тайм-аут защищенного сеанса", "protected_session_timeout_label": "Тайм-аут защищенного сеанса:", "protected_session_timeout_description": "Тайм-аут защищенного сеанса - это период времени, по истечении которого защищенный сеанс удаляется из памяти браузера. Он отсчитывается с момента последнего взаимодействия с защищенными заметками. См", - "for_more_info": "для получения более подробной информации." + "for_more_info": "для получения более подробной информации.", + "reset_confirmation": "Сбросив пароль, вы навсегда потеряете доступ ко всем своим защищённым заметкам. Вы действительно хотите сбросить пароль?", + "password_changed_success": "Пароль изменён. Trilium будет перезагружен после нажатия кнопки «ОК».", + "password_mismatch": "Новые пароли не совпадают.", + "reset_success_message": "Пароль был сброшен. Пожалуйста, установите новый пароль" }, "content_language": { - "description": "Выберите один или несколько языков, которые должны отображаться в разделе «Основные свойства» текстовой заметки, доступной только для чтения или редактируемой. Это позволит реализовать такие функции, как проверка орфографии и поддержка письма справа налево.", + "description": "Выберите один или несколько языков, которые должны отображаться в разделе «Общее» текстовой заметки, доступной только для чтения или редактируемой. Это позволит реализовать такие функции, как проверка орфографии и поддержка письма справа налево.", "title": "Языки контента" }, "theme": { @@ -133,7 +138,9 @@ "layout-horizontal-title": "Горизонтальный", "auto_theme": "Legacy (следует системной цветовой схеме)", "light_theme": "Legacy (светлая)", - "dark_theme": "Legacy (темная)" + "dark_theme": "Legacy (темная)", + "layout-horizontal-description": "панель запуска находится под панелью вкладок, панель вкладок теперь занимает всю ширину.", + "layout-vertical-description": "панель запуска находится слева (по умолчанию)" }, "tasks": { "due": { @@ -154,7 +161,8 @@ "reopen_last_tab": "Повторно открыть последнюю закрытую вкладку", "close_all_tabs": "Закрыть все вкладки", "close_other_tabs": "Закрыть остальные вкладки", - "add_new_tab": "Добавить новую вкладку" + "add_new_tab": "Добавить новую вкладку", + "close_right_tabs": "Закрыть вкладки справа" }, "table_view": { "new-row": "Новая строка", @@ -182,7 +190,10 @@ "label_name_title": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие.", "new_value_placeholder": "новое значение", "to_value": "на значение", - "help_text": "На всех совпадающих заметках:" + "help_text": "На всех совпадающих заметках:", + "help_text_note": "Вы также можете вызвать этот метод без значения, в таком случае метка будет присвоена заметке без значения.", + "help_text_item1": "создать заданную метку, если у заметки ее еще нет", + "help_text_item2": "или изменить значение существующей метки" }, "delete_label": { "delete_label": "Удалить метку", @@ -273,16 +284,17 @@ "selectNote": "выбрать заметку", "copyNotes": "скопировать активную заметку (или выделение) в буфер обмер (используется для клонирования)", "createEditLink": "создать/редактировать внешнюю ссылку", - "headings": "##, ###, #### и т. д., за которыми следует пробел для заголовков.", + "headings": "##, ###, #### и т. д., за которыми следует пробел для заголовков", "bulletList": "* или - с последующим пробелом для маркированного списка", "numberedList": "1. или 1) с последующим пробелом для нумерованного списка", "blockQuote": "начните строку с >, а затем пробела для блока цитаты", "quickSearch": "сфокусироваться на полее ввода быстрого поиска", - "editNoteTitle": "В области дерева переключится с области дерева на заголовок заметки. Сочетание клавиш Enter из области заголовка заметки переключит фокус на текстовый редактор. Ctrl+. переключит обратно с редактора на область дерева.", + "editNoteTitle": "в области дерева переключится с области дерева на заголовок заметки. Сочетание клавиш Enter из области заголовка заметки переключит фокус на текстовый редактор. Ctrl+. переключит обратно с редактора на область дерева.", "title": "Справка" }, "modal": { - "close": "Закрыть" + "close": "Закрыть", + "help_title": "Показать больше информации об этом экране" }, "import": { "importIntoNote": "Импортировать в заметку", @@ -327,7 +339,7 @@ "password_not_set": { "title": "Пароль не установлен", "body1": "Защищенные заметки шифруются с помощью пароля пользователя, но пароль еще не установлен.", - "body2": "Чтобы иметь возможность защищать заметки, нажмите здесь, чтобы установить пароль.", + "body2": "Чтобы защитить заметки, нажмите кнопку ниже, чтобы открыть диалоговое окно «Параметры» и установить пароль.", "go_to_password_options": "Перейти к параметрам пароля" }, "protected_session_password": { @@ -339,7 +351,7 @@ }, "recent_changes": { "title": "Последние изменения", - "erase_notes_button": "Удалить заметки, помеченные на удаление сейчас", + "erase_notes_button": "Стереть удаленные заметки сейчас", "undelete_link": "восстановить", "no_changes_message": "Еще нет изменений...", "deleted_notes_message": "Удаленные заметки были стерты окончательно.", @@ -389,7 +401,7 @@ "upload_attachments": { "upload_attachments_to_note": "Загрузить вложения к заметке", "choose_files": "Выберите файлы", - "files_will_be_uploaded": "Файлы будут загружены как приложения в", + "files_will_be_uploaded": "Файлы будут загружены как приложения в {{noteTitle}}", "options": "Параметры", "shrink_images": "Сжать изображения", "tooltip": "Если этот параметр включен, Trilium попытается уменьшить размер загружаемых изображений путём масштабирования и оптимизации, что может повлиять на воспринимаемое качество изображения. Если этот параметр не включен, изображения будут загружаться без изменений.", @@ -425,7 +437,7 @@ "target_note_title": "Отношение — это именованная связь между исходной и целевой заметками.", "promoted_title": "Выделенный атрибут отображается в заметке явно.", "promoted": "Выделенный", - "promoted_alias_title": "Название, которое будет отображаться в интерфейсе выделенных атрибутов.", + "promoted_alias_title": "Название, которое будет отображаться в интерфейсе продвигаемых атрибутов.", "multiplicity_title": "Множественность определяет, сколько атрибутов с одним и тем же именем можно создать — максимум 1 или более 1.", "label_type_title": "Тип метки поможет Trilium выбрать подходящий интерфейс для ввода значения метки.", "precision_title": "Какое количество цифр после плавающей запятой должно быть доступно в интерфейсе настройки значения.", @@ -446,10 +458,10 @@ "run_at_hour": "В какой час это должно выполняться? Следует использовать вместе с #run=hourly. Можно задать несколько раз для большего количества запусков в течение дня.", "disable_inclusion": "скрипты с этой меткой не будут включены в выполнение родительского скрипта.", "sorted": "сохраняет алфавитную сортировку дочерних заметок", - "sort_direction": "ASC (по умолчанию) или DESC", + "sort_direction": "ASC (по возрастани, по умолчанию) или DESC (по убыванию)", "sort_folders_first": "Папки (заметки, включая дочерние) должны быть отсортированы вверх", "top": "закрепить заданную заметку наверху в ее родителе (применяется только к отсортированным родительским заметкам)", - "hide_promoted_attributes": "Скрыть выделенные атрибуты в этой заметке", + "hide_promoted_attributes": "Скрыть продвигаемых атрибуты в этой заметке", "read_only": "редактор находится в режиме только для чтения. Работает только с текстом и заметками типа \"код\".", "auto_read_only_disabled": "текстовые/заметки с кодом могут автоматически переводиться в режим чтения, если они слишком большие. Вы можете отключить это поведение для каждой заметки, добавив к ней соответствующую метку", "app_css": "отмечает заметки CSS, которые загружаются в приложение Trilium и, таким образом, могут использоваться для изменения внешнего вида Trilium.", @@ -461,10 +473,64 @@ "workspace_template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки, но только если она будет перемещена в рабочую область, содержащую этот шаблон", "workspace_search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки при перемещении их к какому-либо предку этой заметки рабочей области", "workspace_calendar_root": "Определяет корень календаря для каждого рабочего пространства", - "hide_highlight_widget": "Скрыть виджет «Список выделенного»", + "hide_highlight_widget": "Скрыть виджет «Выделенное»", "is_owned_by_note": "принадлежит записке", "and_more": "... и ещё {{count}}.", - "app_theme": "отмечает заметки CSS, которые являются полноценными темами Trilium и, таким образом, доступны в опциях Trilium." + "app_theme": "отмечает заметки CSS, которые являются полноценными темами Trilium и, таким образом, доступны в опциях Trilium.", + "title_template": "Заголовок по умолчанию для заметок, создаваемых как дочерние элементы данной заметки. Значение вычисляется как строка JavaScript\n и, таким образом, может быть дополнено динамическим контентом с помощью внедренных переменных now и parentNote. Примеры:\n \n
      \n
    • Литературные произведения ${parentNote.getLabelValue('authorName')}
    • \n
    • Лог для ${now.format('YYYY-MM-DD HH:mm:ss')}
    • \n
    \n \n Подробности см. в вики, документации API для parentNote и now.", + "icon_class": "значение этой метки добавляется в виде CSS-класса к значку в дереве, что помогает визуально различать заметки в дереве. Примером может служить bx bx-home — значки берутся из boxicons. Может использоваться в шаблонах заметок.", + "share_favicon": "Заметка о фавиконе должна быть размещена на странице общего доступа. Обычно её назначают корневой папке общего доступа и делают наследуемой. Заметка о фавиконе также должна находиться в поддереве общего доступа. Рассмотрите возможность использования атрибута 'share_hidden_from_tree'.", + "inbox": "расположение папки «Входящие» по умолчанию для новых заметок — при создании заметки с помощью кнопки «Новая заметка» на боковой панели заметки будут созданы как дочерние заметки в заметке, помеченной меткой #inbox.", + "share_css": "CSS-заметка, которая будет добавлена на страницу общего доступа. CSS-заметка также должна находиться в общем поддереве. Также рассмотрите возможность использования 'share_hidden_from_tree' и 'share_omit_default_css'.", + "run_on_branch_deletion": "выполняется при удалении ветви. Ветка — это связь между родительской и дочерней заметками и удаляется, например, при перемещении заметки (старая ветвь/ссылка удаляется).", + "share_template": "Встроенная заметка JavaScript, которая будет использоваться в качестве шаблона для отображения общей заметки. Возвращается к шаблону по умолчанию. Рекомендуется использовать 'share_hidden_from_tree'.", + "print_page_size": "При экспорте в PDF изменяет размер страницы. Поддерживаемые значения: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", + "keyboard_shortcut": "Определяет сочетание клавиш для немедленного перехода к этой заметке. Пример: Ctrl+Alt+E. Для вступления изменений в силу требуется перезагрузка интерфейса.", + "new_notes_on_top": "Новые заметки будут создаваться вверху родительской заметки, а не внизу.", + "print_landscape": "При экспорте в PDF изменяет ориентацию страницы с книжной на альбомную.", + "hide_relations": "имена отношений, которые следует скрыть, разделённые запятыми. Все остальные будут отображены.", + "run_on_note_change": "выполняется при изменении заметки (включая создание заметки). Не включает изменения содержимого", + "display_relations": "названия отношений, разделённые запятыми, которые следует отобразить. Все остальные будут скрыты.", + "template": "Эта заметка появится в списке доступных шаблонов при создании новой заметки", + "execute_button": "Название кнопки, которая выполнит текущую заметку типа \"Код\"", + "page_size": "количество элементов на странице в списке заметок", + "custom_request_handler": "см. Пользовательский обработчик запросов", + "custom_resource_provider": "см. Пользовательский обработчик запросов", + "widget": "отмечает эту заметку как пользовательский виджет, который будет добавлен в дерево компонентов Trilium", + "search_home": "новые заметки поиска будут созданы как дочерние записи этой заметки", + "workspace_inbox": "расположение в папке «Входящие» по умолчанию для новых заметок при перемещении их в некую родственную папку этой заметки в рабочей области", + "sql_console_home": "расположение заметок консоли SQL по умолчанию", + "css_class": "значение этой метки затем добавляется как CSS-класс к узлу, представляющему данную заметку в дереве. Это может быть полезно для изменения внешнего вида заметки. Может использоваться в шаблонах заметок.", + "bookmark_folder": "заметка с этой меткой появится в закладках как папка (с предоставлением доступа к ее дочерним элементам)", + "share_hidden_from_tree": "эта заметка скрыта в левом навигационном дереве, но по-прежнему доступна по ее URL-адресу", + "share_external_link": "заметка будет действовать как ссылка на внешний веб-сайт в дереве общего доступа", + "share_alias": "определить псевдоним, с помощью которого заметка будет доступна по адресу https://ссылка_на_ваш_trilium/share/[ваш_псевдоним]", + "share_omit_default_css": "CSS-код страницы общего доступа по умолчанию будет пропущен. Используйте его при внесении существенных изменений в стили.", + "share_root": "помечает заметку, которая будет выступать корнвой страницей /share общедоступного сайта.", + "share_description": "определение текста, который будет добавлен в HTML-тег meta для описания", + "share_raw": "заметка будет передана в исходном виде, без HTML-обертки", + "share_disallow_robot_indexing": "запретит индексацию этой заметки роботами через заголовок X-Robots-Tag: noindex", + "share_credentials": "для доступа к этой общедоступной заметке требуются учётные данные. Значение должно быть в формате 'имя пользователя:пароль'. Не забудьте сделать этот атрибут наследуемым для применения к дочерним заметкам/изображениям.", + "share_index": "заметка с этой меткой будет содержать список всех корневых узлов общедоступных заметок", + "toc": "#toc или #toc=show принудительно отобразят оглавление, #toc=hide — скроют его. Если метка отсутствует, применяется глобальная настройка", + "color": "определяет цвет заметки в дереве заметок, ссылках и т. д. Используйте любое допустимое значение цвета CSS, например «red» или #a13d5f", + "keep_current_hoisting": "Открытие этой ссылки не изменит закрепление, даже если заметка не отображается в текущем закрепленном поддереве.", + "execute_description": "Более подробное описание текущей заметки типа \"Код\", отображаемое вместе с кнопкой \"Выполнить\"", + "run_on_note_creation": "выполняется при создании заметки на сервере. Используйте это отношение, если хотите запустить скрипт для всех заметок, созданных в определённом поддереве. В этом случае создайте его в корневой заметке поддерева и сделайте его наследуемым. Новая заметка, созданная в поддереве (любой глубины), запустит скрипт.", + "run_on_child_note_creation": "выполняется, когда создается новая заметка под заметкой, в которой определено это отношение", + "run_on_note_title_change": "выполняется при изменении заголовка заметки (включая создание заметки)", + "run_on_note_content_change": "выполняется при изменении содержимого заметки (включая создание заметки).", + "run_on_note_deletion": "выполняется при удалении заметки", + "run_on_branch_creation": "выполняется при создании ветви. Ветвь — это связующее звено между родительской и дочерней заметками и создаётся, например, при клонировании или перемещении заметки.", + "run_on_branch_change": "выполняется при обновлении ветки.", + "run_on_attribute_creation": "выполняется, когда создается новый атрибут для заметка, определяющей это отношение", + "run_on_attribute_change": " выполняется при изменении атрибута заметки, определяющей это отношение. Также срабатывает при удалении атрибута", + "relation_template": "атрибуты заметки будут унаследованы даже без родительско-дочерних отношений. Содержимое заметки и её поддерево будут добавлены к экземпляру заметки, если оно пустое. Подробности см. в документации.", + "inherit": "атрибуты заметки будут унаследованы даже без родительско-дочерних отношений. См. описание шаблонных отношений для получения аналогичной информации. См. раздел «Наследование атрибутов» в документации.", + "render_note": "заметки типа «Рендер HTML» будут отображаться с использованием кодовой заметки (HTML или скрипта), и необходимо указать с помощью этой связи, какую заметку следует отобразить", + "widget_relation": "заметка, на которую ссылается отношение будет выполнена и отображена как виджет на боковой панели", + "share_js": "JavaScript-заметка, которая будет добавлена на страницу общего доступа. JavaScript-заметка также должна находиться в общем поддереве. Рекомендуется использовать 'share_hidden_from_tree'.", + "other_notes_with_name": "Другие заметки с {{attributeType}} названием \"{{attributeName}}\"" }, "command_palette": { "configure_launch_bar_description": "Откройте конфигурацию панели запуска, чтобы добавить или удалить элементы.", @@ -542,7 +608,8 @@ "not_set": "Не установлен" }, "time_selector": { - "invalid_input": "Введенное значение времени не является допустимым числом." + "invalid_input": "Введенное значение времени не является допустимым числом.", + "minimum_input": "Введенное значение времени должно быть не менее {{minimumSeconds}} секунд." }, "share": { "share_root_not_found": "Заметка с меткой #shareRoot не найдена", @@ -550,7 +617,10 @@ "redirect_bare_domain_description": "Перенаправлять анонимных пользователей на страницу общедоступных заметок вместо отображения страницы входа", "show_login_link": "Показать ссылку для аутентификации в интерфейсе общедоступных заметок", "show_login_link_description": "Добавить ссылку для аутентификации в нижний колонтитул интерфейса общедоступных заметок", - "title": "Настройки общего доступа" + "title": "Настройки общего доступа", + "check_share_root": "Проверка состояния корневой заметки для общедоступного сайта", + "share_root_not_shared": "Заметка '{{noteTitle}}' имеет метку #shareRoot, но не является общедоступной", + "share_root_found": "Заметка корня общедоступного сайта '{{noteTitle}}' готова" }, "duration": { "days": "Дни", @@ -564,7 +634,9 @@ "open-location": "Открыть местоположение" }, "geo-map": { - "unable-to-load-map": "Не удалось загрузить карту." + "unable-to-load-map": "Не удалось загрузить карту.", + "create-child-note-instruction": "Щелкните по карте, чтобы создать новую заметку в этом месте, или нажмите Escape, чтобы закрыть ее.", + "create-child-note-title": "Создать новую дочернюю заметку и добавить ее на карту" }, "note_tooltip": { "quick-edit": "Быстрое редактирование", @@ -574,7 +646,9 @@ "full-text-search": "Полнотекстовый поиск", "show-recent-notes": "Показать последние заметки", "search-for": "Поиск \"{{term}}\"", - "clear-text-field": "Очистить текстовое поле" + "clear-text-field": "Очистить текстовое поле", + "insert-external-link": "Вставить внешнюю ссылку \"{{term}}\"", + "create-note": "Создать и связать дочернюю заметку \"{{term}}\"" }, "electron_integration": { "zoom-factor": "Коэффициент масштабирования", @@ -592,7 +666,8 @@ "open_note_in_new_split": "Открыть заметку в новой панели" }, "image_context_menu": { - "copy_image_to_clipboard": "Копировать изображение в буфер обмена" + "copy_image_to_clipboard": "Копировать изображение в буфер обмена", + "copy_reference_to_clipboard": "Скопировать ссылку в буфер обмена" }, "electron_context_menu": { "paste-as-plain-text": "Вставить как обычный текст", @@ -600,7 +675,8 @@ "copy-link": "Скопировать ссылку", "copy": "Скопировать", "cut": "Вырезать", - "search_online": "Поиск \"{{term}}\" в {{searchEngine}}" + "search_online": "Поиск \"{{term}}\" в {{searchEngine}}", + "add-term-to-dictionary": "Добавить \"{{term}}\" в словарь" }, "editing": { "editor_type": { @@ -631,7 +707,8 @@ }, "highlighting": { "color-scheme": "Цветовая схема", - "title": "Блоки кода" + "title": "Блоки кода", + "description": "Управляет подсветкой синтаксиса для блоков кода внутри текстовых заметок. Заметки с типом \"Код\" не будут затронуты." }, "editable-text": { "auto-detect-language": "Определен автоматически" @@ -642,7 +719,11 @@ "add-custom-widget": "Добавить пользовательский виджет", "move-to-visible-launchers": "Переместить к видимым лаунчерам", "move-to-available-launchers": "Переместить к доступным лаунчерам", - "delete": "Удалить " + "delete": "Удалить ", + "add-note-launcher": "Добавить лаунчер заметки", + "add-script-launcher": "Добавить лаунчер скрипта", + "duplicate-launcher": "Создать копию лаунчера ", + "reset_launcher_confirm": "Вы действительно хотите сбросить \"{{title}}\"? Все данные/настройки в этой заметке (и её дочерних заметках) будут потеряны, а панель запуска будет возвращена в исходное местоположение." }, "toc": { "table_of_contents": "Оглавление", @@ -660,13 +741,15 @@ "create-child-note": "Создать дочернюю заметку", "save-changes": "Сохранить и применить изменения", "saved-search-note-refreshed": "Сохраненная поисковая заметка обновлена.", - "refresh-saved-search-results": "Обновить сохраненные результаты поиска" + "refresh-saved-search-results": "Обновить сохраненные результаты поиска", + "automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве." }, "quick-search": { "no-results": "Результаты не найдены", "placeholder": "Быстрый поиск", "searching": "Поиск...", - "show-in-full-search": "Расширенный поиск" + "show-in-full-search": "Расширенный поиск", + "more-results": "... и еще {{number}} результатов." }, "find": { "replace_all": "Заменить все", @@ -678,7 +761,8 @@ }, "template_switch": { "template": "Шаблон", - "toggle-off-hint": "Удалить заметку как шаблон" + "toggle-off-hint": "Удалить заметку как шаблон", + "toggle-on-hint": "Сделать заметку шаблоном" }, "note_types": { "collections": "Коллекции", @@ -702,7 +786,8 @@ "mind-map": "Mind Map", "geo-map": "Географическая карта", "ai-chat": "ИИ Чат", - "task-list": "Список задач" + "task-list": "Список задач", + "confirm-change": "Не рекомендуется менять тип заметки, если её содержимое не пустое. Вы всё равно хотите продолжить?" }, "tree-context-menu": { "open-in-popup": "Быстрое редактирование", @@ -732,7 +817,10 @@ "edit-branch-prefix": "Изменить префикс ветки", "convert-to-attachment": "Преобразовать в приложение", "apply-bulk-actions": "Применить массовые действия", - "recent-changes-in-subtree": "Последние изменения в поддереве" + "recent-changes-in-subtree": "Последние изменения в поддереве", + "copy-note-path-to-clipboard": "Копировать путь к заметке в буфер обмена", + "convert-to-attachment-confirm": "Вы уверены, что хотите преобразовать выбранные заметки во вложения их родительских заметок?", + "converted-to-attachments": "{{count}} заметок были преобразованы во вложения." }, "info": { "closeButton": "Закрыть", @@ -770,14 +858,17 @@ "to": "в", "add_relation": "Добавить отношение", "relation_name": "название отношения", - "target_note": "целевая заметка" + "target_note": "целевая заметка", + "allowed_characters": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие.", + "create_relation_on_all_matched_notes": "Для всех соответствующих заметок создать заданную связь." }, "rename_relation": { "to": "В", "rename_relation": "Переименовать отношение", "old_name": "старое наименование", "new_name": "новое наименование", - "rename_relation_from": "Переименовать отношение из" + "rename_relation_from": "Переименовать отношение из", + "allowed_characters": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие." }, "update_relation_target": { "to": "в", @@ -785,7 +876,9 @@ "relation_name": "название отношения", "target_note": "целевая заметка", "update_relation_target": "Обновить целевой элемент отношения", - "on_all_matched_notes": "На всех совпадающих заметках" + "on_all_matched_notes": "На всех совпадающих заметках", + "allowed_characters": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие.", + "change_target_note": "изменить целевую заметку существующего отношения" }, "attachments_actions": { "download": "Скачать", @@ -799,7 +892,14 @@ "open_custom_title": "Файл будет открыт во внешнем приложении и отслеживаться на наличие изменений. После этого вы сможете загрузить изменённую версию обратно в Trilium.", "open_externally_title": "Файл будет открыт во внешнем приложении и отслеживаться на наличие изменений. После этого вы сможете загрузить изменённую версию обратно в Trilium.", "copy_link_to_clipboard": "Копировать ссылку в буфер обмена", - "convert_attachment_into_note": "Преобразовать вложение в заметку" + "convert_attachment_into_note": "Преобразовать вложение в заметку", + "delete_success": "Вложение \"{{title}}\" удалено.", + "enter_new_name": "Введите новое название вложения", + "upload_success": "Загружена новая версия вложения.", + "upload_failed": "Не удалось загрузить новую версию вложения.", + "delete_confirm": "Вы уверены, что хотите удалить вложение '{{title}}'?", + "convert_confirm": "Вы уверены, что хотите преобразовать вложение '{{title}}' в отдельную заметку?", + "convert_success": "Вложение '{{title}}' преобразовано в заметку." }, "calendar": { "mon": "Пн", @@ -867,7 +967,7 @@ "editable": "Изменяемое", "language": "Язык", "note_type": "Тип", - "basic_properties": "Общие параметры" + "basic_properties": "Общее" }, "book_properties": { "grid": "Сетка", @@ -886,8 +986,8 @@ }, "edited_notes": { "deleted": "(удалено)", - "title": "Отредактированные заметки", - "no_edited_notes_found": "Пока нет отредактированных заметок за этот день..." + "title": "Измененные заметки", + "no_edited_notes_found": "Пока нет измененных заметок за этот день..." }, "file_properties": { "download": "Скачать", @@ -898,7 +998,8 @@ "file_size": "Размер файла", "file_type": "Тип файла", "original_file_name": "Исходное имя файла", - "note_id": "ID заметки" + "note_id": "ID заметки", + "upload_failed": "Загрузка новой версии файла не удалась." }, "image_properties": { "download": "Скачать", @@ -919,7 +1020,9 @@ "note_id": "ID заметки", "note_size": "Размер заметки", "title": "Информация", - "calculate": "подсчитать" + "calculate": "подсчитать", + "note_size_info": "Размер заметки позволяет приблизительно оценить требования к объёму хранилища для данной заметки. Он учитывает её содержание и содержание её сохраненных версий.", + "subtree_size": "(размер поддерева: {{size}} в {{count}} заметках)" }, "note_paths": { "search": "Поиск", @@ -927,11 +1030,12 @@ "clone_button": "Клонировать заметку в новое место...", "intro_placed": "Эта заметка размещена по следующим путям:", "intro_not_placed": "Эта заметка еще не помещена в дерево заметок.", - "outside_hoisted": "Этот путь находится за пределами выделенный заметки, и вам придется снять выделение.", + "outside_hoisted": "Этот путь находится за пределами закрепленной заметки, и вам придется снять закрепление.", "archived": "Архивировано" }, "note_properties": { - "info": "Информация" + "info": "Информация", + "this_note_was_originally_taken_from": "Эта заметка была первоначально взята из:" }, "promoted_attributes": { "url_placeholder": "http://website...", @@ -989,7 +1093,8 @@ "access_info": "Чтобы получить доступ к отладочной информации, выполните запрос и нажмите «Показать лог бэкенда» в левом верхнем углу." }, "limit": { - "limit": "Ограничение" + "limit": "Ограничение", + "take_first_x_results": "Взять только первые X указанных результатов." }, "order_by": { "title": "Названию", @@ -1005,7 +1110,10 @@ "owned_label_count": "Количество меток", "owned_relation_count": "Количество отношений", "date_modified": "Дата последнего изменения", - "children_count": "Количество дочерних заметок" + "children_count": "Количество дочерних заметок", + "content_and_attachments_size": "Размер содержимого заметки, включая вложения", + "content_and_attachments_and_revisions_size": "Размер содержимого заметки, включая вложения и версии.", + "target_relation_count": "Количество отношений, направленных на заметку" }, "search_string": { "search_prefix": "Поиск:", @@ -1020,7 +1128,8 @@ "label_rock_or_pop": "должна присутствовать только одна из vtnjr", "label_year_comparison": "числовое сравнение (также >, >=, <).", "label_date_created": "заметки, созданные за последний месяц", - "error": "Ошибка поиска: {{error}}" + "error": "Ошибка поиска: {{error}}", + "placeholder": "полнотекстовые ключевые слова, #tag = value..." }, "backend_log": { "refresh": "Обновить" @@ -1032,7 +1141,8 @@ "full_sync_triggered": "Полная синхронизация запущена", "finished-successfully": "Синхронизация успешно завершена.", "failed": "Синхронизация не удалась: {{message}}", - "sync_rows_filled_successfully": "Строки синхронизации успешно заполнены" + "sync_rows_filled_successfully": "Строки синхронизации успешно заполнены", + "filling_entity_changes": "Заполнение строк изменений сущностей..." }, "fonts": { "fonts": "Шрифты", @@ -1052,7 +1162,10 @@ "sans-serif-system-fonts": "Системные шрифты без засечек", "serif-system-fonts": "Системные шрифты с засечками", "monospace-system-fonts": "Моноширинные системные шрифты", - "handwriting-system-fonts": "Шрифты системы рукописного ввода" + "handwriting-system-fonts": "Шрифты системы рукописного ввода", + "note_tree_and_detail_font_sizing": "Обратите внимание, что размер шрифта дерева и детальной страницы зависит от настройки размера основного шрифта.", + "apply_font_changes": "Чтобы применить изменения шрифта, нажмите", + "not_all_fonts_available": "Не все перечисленные шрифты могут быть доступны в вашей системе." }, "max_content_width": { "max_width_unit": "пикселей", @@ -1129,13 +1242,14 @@ "index_status": "Статус индексирования", "indexed_notes": "Проиндексированные заметки", "indexing_stopped": "Индексирование остановлено", - "last_indexed": "Последние проиндексированные", + "last_indexed": "Индексировано в последний раз", "note_chat": "Чат по заметке", "start_indexing": "Начать индексирование", "chat": { "root_note_title": "Чаты с AI", "new_chat_title": "Новый чат", - "create_new_ai_chat": "Создать новый чат с ИИ" + "create_new_ai_chat": "Создать новый чат с ИИ", + "root_note_content": "В этой заметке содержатся сохраненные вами разговоры в чате ИИ." }, "selected_provider": "Выбранный провайдер", "select_model": "Выбрать модель...", @@ -1151,7 +1265,10 @@ "temperature_description": "Контролирует случайность ответов (0 = детерминированный, 2 = максимальная случайность)", "system_prompt_description": "Системный промпт по умолчанию, используемый для всех взаимодействий с ИИ", "empty_key_warning": { - "openai": "Ключ API OpenAI пуст. Введите действительный ключ API." + "openai": "Ключ API OpenAI пуст. Введите действительный ключ API.", + "ollama": "API-ключ Ollama пуст. Введите действительный API-ключ.", + "voyage": "Ключ API Voyage пуст. Введите действительный ключ API.", + "anthropic": "Ключ API Anthropic пуст. Введите действительный ключ API." }, "openai_api_key_description": "Ваш ключ API OpenAI для доступа к их службам ИИ", "provider_precedence_description": "Список провайдеров, разделенных запятыми, в порядке приоритета (например, \"openai,anthropic,ollama\")", @@ -1181,7 +1298,37 @@ "failed_to_retry_note": "Не удалось повторить попытку", "failed_to_retry_all": "Не удалось повторить попытку", "error_generating_response": "Ошибка генерации ответа ИИ", - "create_new_ai_chat": "Создать новый чат с ИИ" + "create_new_ai_chat": "Создать новый чат с ИИ", + "ai_enabled": "Возможности ИИ активны", + "ai_disabled": "Возможности ИИ неактивны", + "restore_provider": "Восстановить значение провайдера", + "error_fetching": "Ошибка получения списка моделей: {{error}}", + "index_rebuild_status_error": "Ошибка проверки статуса перестроения индекса", + "enhanced_context_description": "Предоставляет ИИ больше контекста из заметки и связанных с ней заметок для более точных ответов", + "n_notes_queued_0": "{{ count }} заметка в очереди на индексирование", + "n_notes_queued_1": "{{ count }} заметки в очереди на индексирование", + "n_notes_queued_2": "{{ count }} заметок в очереди на индексирование", + "no_models_found_ollama": "Модели Ollama не найдены. Проверьте, запущена ли Ollama.", + "no_models_found_online": "Модели не найдены. Проверьте ваш ключ API и настройки.", + "experimental_warning": "Функция LLM в настоящее время является экспериментальной — вы предупреждены.", + "ollama_no_url": "Ollama не настроена. Введите корректный URL-адрес.", + "notes_indexed_0": "{{ count }} заметка проиндексирована", + "notes_indexed_1": "{{ count }} заметки проиндексировано", + "notes_indexed_2": "{{ count }} заметок проиндексировано", + "show_thinking_description": "Показать цепочку мыслительного процесса ИИ", + "api_key_tooltip": "API-ключ для доступа к сервису", + "all_notes_queued_for_retry": "Все неудачные заметки поставлены в очередь на повторную попытку", + "reprocess_index_started": "Оптимизация поискового индекса запущена в фоновом режиме", + "similarity_threshold_description": "Минимальный показатель сходства (similarity score, 0–1) для заметок, которые следует включить в контекст запросов LLM", + "max_notes_per_llm_query_description": "Максимальное количество похожих заметок для включения в контекст ИИ", + "retry_failed": "Не удалось поставить заметку в очередь для повторной попытки", + "rebuild_index_error": "Ошибка при запуске перестроения индекса. Подробности смотрите в логах.", + "enable_ollama_description": "Включить Ollama для использования локальной модели ИИ", + "anthropic_model_description": "Модели Anthropic Claude для автодополнения чата", + "anthropic_url_description": "Базовый URL для Anthropic API (по умолчанию: https://api.anthropic.com)", + "anthropic_api_key_description": "Ваш ключ Anthropic API для доступа к моделям Claude", + "enable_ai_desc": "Включить функции ИИ, такие как резюмирование заметок, генерация контента и другие возможности LLM", + "enable_ai_description": "Включить функции ИИ, такие как резюмирование заметок, генерация контента и другие возможности LLM" }, "code-editor-options": { "title": "Редактор" @@ -1245,7 +1392,7 @@ }, "backup": { "path": "Путь", - "backup_now": "Резервное копирование сейчас", + "backup_now": "Принудительное резервное копирование", "existing_backups": "Существующие резервные копии", "automatic_backup": "Автоматическое резервное копирование", "automatic_backup_description": "Trilium может автоматически создавать резервную копию базы данных:", @@ -1254,7 +1401,9 @@ "enable_monthly_backup": "Включить ежемесячное резервное копирование", "backup_database_now": "Создать резервную копию", "date-and-time": "Дата и время", - "no_backup_yet": "нет резервных копий" + "no_backup_yet": "нет резервных копий", + "database_backed_up_to": "Резервная копия базы данных создана в {{backupFilePath}}", + "backup_recommendation": "Рекомендуется держать резервное копирование включенным, но это может замедлить запуск приложений при использовании больших баз данных и/или медленных устройств хранения." }, "etapi": { "title": "ETAPI", @@ -1272,7 +1421,14 @@ "swagger_ui": "Пользовательский интерфейс ETAPI Swagger", "new_token_title": "Новый токен ETAPI", "token_created_title": "Создан токен ETAPI", - "rename_token": "Переименовать этот токен" + "rename_token": "Переименовать этот токен", + "new_token_message": "Введите название нового токена", + "error_empty_name": "Имя токена не может быть пустым", + "delete_token": "Удалить/деактивировать этот токен", + "rename_token_message": "Пожалуйста, введите имя нового токена", + "token_created_message": "Скопируйте созданный токен в буфер обмена. Trilium сохранит хеш токена, и вы его больше не сможете увидеть.", + "delete_token_confirmation": "Вы уверены, что хотите удалить токен ETAPI \"{{name}}\"?", + "no_tokens_yet": "Токенов пока нет. Нажмите кнопку выше, чтобы создать токен." }, "multi_factor_authentication": { "oauth_title": "OAuth/OpenID", @@ -1296,7 +1452,16 @@ "totp_title": "Одноразовый пароль с ограничением по времени (TOTP)", "recovery_keys_title": "Ключи восстановления единого входа", "recovery_keys_error": "Ошибка генерации кодов восстановления", - "recovery_keys_no_key_set": "Коды восстановления не установлены" + "recovery_keys_no_key_set": "Коды восстановления не установлены", + "recovery_keys_unused": "Код восстановления {{index}} не используется", + "description": "Многофакторная аутентификация (MFA) добавляет дополнительный уровень безопасности вашей учётной записи. Вместо простого ввода пароля для входа MFA требует предоставить один или несколько дополнительных документов для подтверждения вашей личности. Таким образом, даже если кто-то узнает ваш пароль, он всё равно не сможет получить доступ к вашей учётной записи без второго элемента данных. Это похоже на установку дополнительного замка на вашу дверь, значительно усложняя взлом.

    Следуйте инструкциям ниже, чтобы включить MFA. Если вы настроите её неправильно, для входа будет использоваться только пароль.", + "totp_description": "TOTP (одноразовый пароль с ограничением по времени) — это функция безопасности, которая генерирует уникальный временный код, который меняется каждые 30 секунд. Вы используете этот код вместе с паролем для входа в свою учётную запись, что значительно затрудняет доступ к ней посторонним лицам.", + "recovery_keys_description_warning": "Ключи восстановления больше не будут отображаться после выхода со страницы, сохраните их в надежном и безопасном месте.
    После использования ключа восстановления его нельзя будет использовать повторно.", + "totp_secret_regenerate_confirm": "Вы уверены, что хотите восстановить секрет TOTP? Это аннулирует предыдущий секрет TOTP и все существующие коды восстановления.", + "totp_secret_description_warning": "После создания нового секрета TOTP вам потребуется снова войти в систему, используя новый секрет TOTP.", + "recovery_keys_description": "Ключи восстановления единого входа используются для входа в систему, даже если вы не можете получить доступ к своим кодам аутентификатора.", + "totp_secret_warning": "Сохраните сгенерированный секретный ключ в безопасном месте. Он больше не будет показан.", + "no_totp_secret_warning": "Чтобы включить TOTP, вам сначала нужно сгенерировать секрет TOTP." }, "shortcuts": { "shortcuts": "Сочетания клавиш", @@ -1305,7 +1470,11 @@ "action_name": "Название действия", "default_shortcuts": "Сочетания клавиш по умолчанию", "multiple_shortcuts": "Несколько сочетаний клавиш для одного и того же действия можно разделить запятой.", - "electron_documentation": "Информацию о доступных модификаторах и кодах клавиш см. в документации Electron." + "electron_documentation": "Информацию о доступных модификаторах и кодах клавиш см. в документации Electron.", + "type_text_to_filter": "Введите текст для фильтрации сочетаний клавиш...", + "reload_app": "Перезагрузить приложение, чтобы применить изменения", + "confirm_reset": "Вы действительно хотите сбросить все сочетания клавиш до значений по умолчанию?", + "set_all_to_default": "Установить все сочетания клавиш по умолчанию" }, "sync_2": { "timeout_unit": "миллисекунд", @@ -1320,28 +1489,35 @@ "timeout": "Тайм-аут синхронизации", "test_description": "Это проверит подключение и подтверждение связи с сервером синхронизации. Если сервер синхронизации не инициализирован, он будет настроен на синхронизацию с локальным документом.", "test_title": "Тест синхронизации", - "test_button": "Проверка синхронизации" + "test_button": "Проверка синхронизации", + "handshake_failed": "Синхронизация с сервером не удалась, ошибка: {{message}}" }, "api_log": { "close": "Закрыть" }, "bookmark_switch": { "bookmark": "В закладки", - "remove_bookmark": "Удалить закладку" + "remove_bookmark": "Удалить закладку", + "bookmark_this_note": "Добавить эту заметку в закладки на левой боковой панели" }, "editability_select": { "auto": "Авто", "read_only": "Только для чтения", - "always_editable": "Всегда доступно для редактирования" + "always_editable": "Всегда доступно для редактирования", + "note_is_always_editable": "Заметку всегда можно редактировать, независимо от ее длины.", + "note_is_read_only": "Заметка доступна только для чтения, но ее можно редактировать одним нажатием кнопки.", + "note_is_editable": "Заметку можно редактировать, если она не слишком длинная." }, "shared_switch": { "shared": "Общий доступ", "toggle-on-title": "Сделать заметку общедоступной", - "toggle-off-title": "Отменить общий доступ к заметке" + "toggle-off-title": "Отменить общий доступ к заметке", + "shared-branch": "Эта заметка существует только как общая, и если отменить общий доступ, она будет удалена. Хотите продолжить и удалить эту заметку?", + "inherited": "Заметка не может быть убрана из общего доступа в данном случае, поскольку общий доступ передан по наследству от предка." }, "highlights_list_2": { "options": "Параметры", - "title": "Список выделений" + "title": "Список выделенного" }, "include_note": { "dialog_title": "Вставить заметку", @@ -1354,26 +1530,42 @@ "box_size_prompt": "Размер рамки вставленной заметки:" }, "execute_script": { - "execute_script": "Выполнить скрипт" + "execute_script": "Выполнить скрипт", + "help_text": "Вы можете выполнять простые скрипты на соответствующих заметках.", + "example_1": "Например, чтобы добавить строку к заголовку заметки, используйте этот небольшой скрипт:", + "example_2": "Более сложным примером будет удаление всех соответствующих атрибутов заметки:" }, "update_label_value": { "label_name_placeholder": "название метки", "new_value_placeholder": "новое значение", "update_label_value": "Обновить значение метки", - "to_value": "на значение" + "to_value": "на значение", + "help_text_note": "Вы также можете вызвать этот метод без значения, в таком случае метка будет присвоена заметке без значения.", + "label_name_title": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие.", + "help_text": "На всех соответствующих заметках изменить значение существующей метки." }, "delete_note": { "delete_note": "Удалить заметку", - "delete_matched_notes": "Удалить совпадающие заметки" + "delete_matched_notes": "Удалить совпадающие заметки", + "delete_matched_notes_description": "Это приведет к удалению соответствующих заметок.", + "erase_notes_instruction": "Чтобы навсегда стереть заметки, после удаления перейдите в раздел «Параметры» -> «Другие» и нажмите кнопку «Стереть удаленные заметки сейчас».", + "undelete_notes_instruction": "После удаления их можно восстановить из диалогового окна «Последние изменения»." }, "rename_note": { "rename_note": "Переименовать заметку", "new_note_title": "название новой заметки", - "rename_note_title_to": "Переименовать заголовок заметки на" + "rename_note_title_to": "Переименовать заголовок заметки на", + "click_help_icon": "Нажмите значок справки справа, чтобы увидеть все параметры", + "evaluated_as_js_string": "Указанное значение обрабатывается как строка JavaScript и, таким образом, может быть дополнено динамическим содержимым через внедренную переменную note (при этом заметка переименовывается). Примеры:", + "example_note": "Note — все соответствующие примечания переименовываются в \"Note\"", + "example_new_title": "NEW: ${note.title} — заголовки соответствующих заметок начинаются с префикса \"NEW:\"", + "example_date_prefix": "${note.dateCreatedObj.format('MM-DD:')}: ${note.title} — соответствующие заметки имеют префикс в виде месяца и даты создания заметки", + "api_docs": "Подробную информацию см. в документации API для note и его свойства dateCreatedObj / utcDateCreatedObj." }, "delete_relation": { "delete_relation": "Удалить отношение", - "relation_name": "название отношения" + "relation_name": "название отношения", + "allowed_characters": "Разрешены буквенно-цифровые символы, подчеркивание и двоеточие." }, "left_pane_toggle": { "show_panel": "Показать панель", @@ -1394,7 +1586,7 @@ "save_revision": "Сохранить версию", "convert_into_attachment": "Конвертировать во вложение", "search_in_note": "Поиск в заметке", - "print_pdf": "Экспорт в PDF", + "print_pdf": "Экспорт в PDF...", "convert_into_attachment_prompt": "Вы уверены, что хотите преобразовать заметку '{{title}}' во вложение родительской заметки?", "convert_into_attachment_successful": "Примечание '{{title}}' преобразовано во вложение.", "convert_into_attachment_failed": "Не удалось преобразовать заметку '{{title}}'.", @@ -1457,7 +1649,9 @@ }, "attachment_list": { "upload_attachments": "Загрузка вложений", - "owning_note": "Заметка-владелец: " + "owning_note": "Заметка-владелец: ", + "open_help_page": "Открыть справку по вложениям", + "no_attachments": "Заметка не содержит вложений." }, "protected_session": { "wrong_password": "Неверный пароль.", @@ -1467,7 +1661,9 @@ "unprotecting-finished-successfully": "Снятие защиты успешно завершено.", "start_session_button": "Начать защищенный сеанс enter", "protecting-in-progress": "Защита в процессе: {{count}}", - "unprotecting-in-progress-count": "Снятие защиты в процессе: {{count}}" + "unprotecting-in-progress-count": "Снятие защиты в процессе: {{count}}", + "started": "Защищенный сеанс запущен.", + "enter_password_instruction": "Для отображения защищенной заметки требуется ввести пароль:" }, "relation_map": { "remove_note": "Удалить заметку", @@ -1479,7 +1675,13 @@ "confirm_remove_relation": "Вы уверены, что хотите удалить отношение?", "enter_new_title": "Введите новое название заметки:", "note_not_found": "Заметка {{noteId}} не найдена!", - "cannot_match_transform": "Невозможно сопоставить преобразование: {{transform}}" + "cannot_match_transform": "Невозможно сопоставить преобразование: {{transform}}", + "enter_title_of_new_note": "Введите название новой заметки", + "click_on_canvas_to_place_new_note": "Щелкните по холсту, чтобы разместить новую заметку", + "note_already_in_diagram": "Заметка \"{{title}}\" уже есть на диаграмме.", + "connection_exists": "Связь '{{name}}' между этими заметками уже существует.", + "specify_new_relation_name": "Укажите новое имя отношения (допустимые символы: буквы, цифры, двоеточие и подчеркивание):", + "start_dragging_relations": "Начните перетягивать отношения отсюда на другую заметку." }, "vacuum_database": { "title": "Сжатие базы данных", @@ -1501,22 +1703,22 @@ "enable_tray": "Включить отображение иконки в системном трее (чтобы изменения вступили в силу, необходимо перезапустить Trilium)" }, "highlights_list": { - "title": "Список выделений", + "title": "Список выделенного", "bold": "Жирный текст", "italic": "Наклонный текст", "underline": "Подчеркнутый текст", "color": "Цветной текст", - "description": "Вы можете настроить список выделений, отображаемый на правой панели:", + "description": "Вы можете настроить список выделенного, отображаемый на правой панели:", "bg_color": "Текст с заливкой фона", "visibility_title": "Видимость списка выделений", - "visibility_description": "Вы можете скрыть виджет списка выделений, добавив атрибут #hideHighlightWidget к заметке.", - "shortcut_info": "Вы можете настроить сочетание клавиш для быстрого переключения правой панели (включая список выделений) в меню Параметры -> Сочетания клавиш (название \"toggleRightPane\")." + "visibility_description": "Вы можете скрыть виджет списка выделенного, добавив атрибут #hideHighlightWidget к заметке.", + "shortcut_info": "Вы можете настроить сочетание клавиш для быстрого переключения правой панели (включая список выделенного) в меню Параметры -> Сочетания клавиш (название \"toggleRightPane\")." }, "custom_date_time_format": { "format_string": "Строка форматирования:", "formatted_time": "Пример форматирования:", "title": "Пользовательский формат даты и времени", - "description": "Настройте формат даты и времени, вставляемых с помощью Alt+T или панели инструментов. Доступные токены формата см. в документации Day.js." + "description": "Настройте формат даты и времени, вставляемых с помощью или панели инструментов. Доступные токены форматирования см. в документации Day.js." }, "spellcheck": { "title": "Проверка орфографии", @@ -1525,13 +1727,20 @@ "multiple_languages_info": "Несколько языков можно разделять запятой, например, \"en-US, de-DE, cs\". ", "available_language_codes_label": "Доступные коды языков:", "restart-required": "Изменения параметров проверки орфографии вступят в силу после перезапуска приложения.", - "language_code_placeholder": "например \"en-US\", \"de-AT\"" + "language_code_placeholder": "например \"en-US\", \"de-AT\"", + "description": "Эти параметры применимы только для десктопных сборок, браузеры будут использовать собственную встроенную проверку орфографии." }, "attribute_editor": { "save_attributes": "Сохранить атрибуты ", "add_a_new_attribute": "Добавить новый атрибут", "add_new_label_definition": "Добавить новое определение метки", - "add_new_relation_definition": "Добавить новое определение отношения" + "add_new_relation_definition": "Добавить новое определение отношения", + "add_new_label": "Добавить новую метку ", + "add_new_relation": "Добавить новое отношение ", + "help_text_body1": "Чтобы добавить метку, просто введите, например, #rock или, если вы хотите добавить также значение, то, например, #year = 2020", + "help_text_body2": "Для отношения введите ~author = @, после чего должно появиться окно автозаполнения, где вы сможете найти нужную заметку.", + "help_text_body3": "В качестве альтернативы вы можете добавить метку и отношение, используя кнопку + с правой стороны.", + "placeholder": "Введите здесь метки и отношения" }, "delete_revisions": { "delete_note_revisions": "Удалить версии заметки", @@ -1547,7 +1756,7 @@ "edit_this_note": "Редактировать заметку" }, "show_highlights_list_widget_button": { - "show_highlights_list": "Показать список выделений" + "show_highlights_list": "Показать список выделенного" }, "zen_mode": { "button_exit": "Покинуть режим \"дзен\"" @@ -1587,7 +1796,9 @@ }, "protect_note": { "toggle-on": "Защитить заметку", - "toggle-off": "Снять защиту с заметки" + "toggle-off": "Снять защиту с заметки", + "toggle-off-hint": "Заметка защищена. Щелкните, чтобы снять защиту", + "toggle-on-hint": "Заметка не защищена. Щелкните, чтобы установить защиту" }, "note-map": { "button-link-map": "Карта связей", @@ -1604,15 +1815,21 @@ "consistency_checks": { "find_and_fix_button": "Найти и устранить проблемы целостности", "finding_and_fixing_message": "Поиск и устранение проблем целостности...", - "title": "Проверки целостности" + "title": "Проверки целостности", + "issues_fixed_message": "Все обнаруженные проблемы с согласованностью теперь устранены." }, "call_to_action": { "next_theme_message": "В настоящее время вы используете старую тему оформления. Хотите попробовать новую тему?", "dismiss": "Отклонить", - "background_effects_button": "Включить эффекты фона" + "background_effects_button": "Включить эффекты фона", + "next_theme_button": "Попробовать новую тему", + "background_effects_message": "На устройствах Windows фоновые эффекты теперь полностью стабильны. Они добавляют цвет в пользовательский интерфейс, размывая фон за ним. Этот приём также используется в других приложениях, например, в проводнике Windows.", + "background_effects_title": "Фоновые эффекты теперь стабильны", + "next_theme_title": "Попробуйте новую тему Trilium" }, "zoom_factor": { - "description": "Масштабированием также можно управлять с помощью сочетаний клавиш CTRL+- и CTRL+=." + "description": "Масштабированием также можно управлять с помощью сочетаний клавиш CTRL+- и CTRL+=.", + "title": "Коэффициент масштабирования (только для настольной версии)" }, "show_toc_widget_button": { "show_toc": "Показать оглавление" @@ -1621,7 +1838,8 @@ "title": "Доступные типы в выпадающем списке" }, "search_result": { - "no_notes_found": "По заданным параметрам поиска заметки не найдены." + "no_notes_found": "По заданным параметрам поиска заметки не найдены.", + "search_not_executed": "Поиск ещё не выполнен. Нажмите кнопку «Поиск» выше, чтобы увидеть результаты." }, "empty": { "search_placeholder": "поиск заметки по ее названию", @@ -1631,7 +1849,11 @@ "search_script": { "placeholder": "поиск заметки по ее названию", "title": "Скрипт поиска:", - "example_title": "См. этот пример:" + "example_title": "См. этот пример:", + "description1": "Скрипт поиска позволяет определить результаты поиска, запустив его. Это обеспечивает максимальную гибкость, когда стандартного поиска недостаточно.", + "description2": "Скрипт поиска должен иметь тип «Код» и подтип «JavaScript backend». Скрипт должен возвращать массив идентификаторов заметок или заметок.", + "note": "Обратите внимание, что скрипт поиска и строка поиска не могут быть объединены друг с другом.", + "example_code": "// 1. Предварительная фильтрация с использованием стандартного поиска\nconst candidateNotes = api.searchForNotes(\"#journal\"); \n\n// 2. Применение пользовательских критериев поиска\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;" }, "note_erasure_timeout": { "note_erasure_timeout_title": "Срок окончательного удаления заметок", @@ -1645,21 +1867,25 @@ "erase_unused_attachments_now": "Удалить неиспользуемые вложения прямо сейчас", "attachment_auto_deletion_description": "Вложения автоматически удаляются, если в заметке на них больше не ссылаются по истечении определенного времени.", "attachment_erasure_timeout": "Тайм-аут удаления вложения", - "erase_attachments_after": "Удалять неиспользуемые вложения через:" + "erase_attachments_after": "Удалять неиспользуемые вложения через:", + "unused_attachments_erased": "Неиспользуемые вложения были удалены.", + "manual_erasing_description": "Вы также можете запустить стирание вручную (без учета тайм-аута, определенного выше):" }, "revisions_snapshot_interval": { "note_revisions_snapshot_interval_title": "Интервал создания снимка версии заметки", - "note_revisions_snapshot_description": "Интервал создания снимка версии заметки - это время, по истечении которого для неё будет создана новая версия. Подробнее см. в wiki.", + "note_revisions_snapshot_description": "Интервал между снимками редакции заметки — это время, по истечении которого для заметки будет создана новая редакция. Подробнее см. wiki.", "snapshot_time_interval_label": "Интервал создания снимка версии заметки:" }, "title_bar_buttons": { "window-on-top": "Закрепить окно" }, "abstract_search_option": { - "remove_this_search_option": "Удалить эту опцию поиска" + "remove_this_search_option": "Удалить эту опцию поиска", + "failed_rendering": "Не удалось выполнить рендеринг опции поиска: {{dto}} с ошибкой: {{error}} {{stack}}" }, "image": { - "copied-to-clipboard": "Ссылка на изображение скопирована в буфер обмена. Её можно вставить в любую текстовую заметку." + "copied-to-clipboard": "Ссылка на изображение скопирована в буфер обмена. Её можно вставить в любую текстовую заметку.", + "cannot-copy": "Не удалось скопировать ссылку на изображение в буфер обмена." }, "abstract_bulk_action": { "remove_this_search_action": "Удалить это действие поиска" @@ -1668,26 +1894,38 @@ "cannot-move-notes-here": "Невозможно переместить заметки сюда.", "delete-status": "Статус удаления", "delete-finished-successfully": "Удаление успешно завершено.", - "undeleting-notes-finished-successfully": "Восстановление заметок успешно завершено." + "undeleting-notes-finished-successfully": "Восстановление заметок успешно завершено.", + "delete-notes-in-progress": "Удаление заметок в процессе: {{count}}", + "undeleting-notes-in-progress": "Идет восстановление заметок: {{count}}" }, "attachment_detail": { "owning_note": "Заметка-владелец: ", - "list_of_all_attachments": "Список всех вложений" + "list_of_all_attachments": "Список всех вложений", + "open_help_page": "Открыть справку по вложениям", + "attachment_deleted": "Это вложение было удалено.", + "you_can_also_open": ", вы также можете открыть " }, "web_view": { - "web_view": "Веб-страница" + "web_view": "Веб-страница", + "create_label": "Для начала создайте метку с URL-адресом, который вы хотите встроить, например, #webViewSrc=\"https://www.google.com\"", + "embed_websites": "Заметки типа \"Веб-страница\" позволяет встраивать веб-сайты в Trilium." }, "ribbon": { - "widgets": "Виджеты ленты" + "widgets": "Виджеты ленты", + "promoted_attributes_message": "Вкладка \"Продвигаемые атрибуты\" будет автоматически открыта, если таковые атрибуты установлены у заметки", + "edited_notes_message": "Вкладка ленты «Измененные заметки» будет автоматически открываться в заметках дня" }, "options_widget": { - "options_status": "Статус опций" + "options_status": "Статус опций", + "options_change_saved": "Изменения параметров сохранены." }, "spacer": { "configure_launchbar": "Конфигурация лаунчбара" }, "entrypoints": { - "note-executed": "Заметка выполнена." + "note-executed": "Заметка выполнена.", + "note-revision-created": "Снимок версии заметки создан успешно.", + "sql-error": "Произошла ошибка при выполнении SQL-запроса: {{message}}" }, "include_archived_notes": { "include_archived_notes": "Включить архивные заметки" @@ -1695,19 +1933,90 @@ "open-help-page": "Открыть страницу справки", "watched_file_update_status": { "upload_modified_file": "Загрузить измененный файл", - "ignore_this_change": "Игнорировать это изменение" + "ignore_this_change": "Игнорировать это изменение", + "file_last_modified": "Файл был последний раз изменен ." }, "clipboard": { - "copy_success": "Скопировано в буфер обмена." + "copy_success": "Скопировано в буфер обмена.", + "copy_failed": "Невозможно скопировать в буфер обмена из-за проблем с правами доступа.", + "copied": "Заметки скопированы в буфер обмена.", + "cut": "Заметки вырезаны в буфер обмена." }, "ws": { - "sync-check-failed": "Проверка синхронизации не удалась!" + "sync-check-failed": "Проверка синхронизации не удалась!", + "encountered-error": "Обнаружена ошибка \"{{message}}\", проверьте консоль.", + "consistency-checks-failed": "Проверка целостности не пройдена! Подробности смотрите в логах." }, "attachment_detail_2": { "role_and_size": "Роль: {{role}}, Размер: {{size}}", - "unrecognized_role": "Нераспознанная роль вложения '{{role}}'." + "unrecognized_role": "Нераспознанная роль вложения '{{role}}'.", + "link_copied": "Ссылка на вложение скопирована в буфер обмена.", + "will_be_deleted_soon": "Это вложение скоро будет автоматически удалено", + "will_be_deleted_in": "Это вложение будет автоматически удалено через {{time}}", + "deletion_reason": ", поскольку вложение не связано с содержимым заметки. Чтобы предотвратить удаление, добавьте ссылку на вложение обратно в содержимое или преобразуйте вложение в заметку." }, "note_title": { "placeholder": "введите здесь название заметки..." + }, + "units": { + "percentage": "%" + }, + "settings": { + "related_settings": "Связанные настройки" + }, + "content_widget": { + "unknown_widget": "Неизвестный виджет \"{{id}}\"." + }, + "wrap_lines": { + "wrap_lines_in_code_notes": "Переносить строки в заметках типа \"Код\"", + "enable_line_wrap": "Включить перенос строк (для вступления изменений в силу может потребоваться перезагрузка интерфейса)" + }, + "shared_info": { + "help_link": "Для получения справки посетите вики.", + "shared_locally": "Заметка общедоступна локально в", + "shared_publicly": "Заметка общедоступна публично в" + }, + "note_create": { + "duplicated": "Создан дубль заметки \"{{title}}\"." + }, + "help-button": { + "title": "Открыть соответствующую страницу справки" + }, + "render": { + "note_detail_render_help_2": "Тип заметки «Рендер HTML» используется для скриптинга. Если коротко, у вас есть заметка с HTML-кодом (возможно, с добавлением JavaScript), и эта заметка её отобразит. Для этого необходимо определить отношение с именем «renderNote», указывающее на HTML-заметку для отрисовки.", + "note_detail_render_help_1": "Эта справочная заметка отображается, поскольку эта справка типа Render HTML не имеет необходимой связи для правильной работы." + }, + "file": { + "too_big": "В целях повышения производительности в режиме предварительного просмотра отображаются только первые {{maxNumChars}} символов файла. Загрузите файл и откройте его во внешнем браузере, чтобы увидеть всё содержимое.", + "file_preview_not_available": "Предварительный просмотр файла недоступен для этого файла." + }, + "app_context": { + "please_wait_for_save": "Подождите несколько секунд, пока сохранение завершится, затем попробуйте еще раз." + }, + "settings_appearance": { + "related_code_blocks": "Цветовая схема для блоков кода в текстовых заметках", + "related_code_notes": "Цветовая схема для заметок типа \"Код\"" + }, + "sql_result": { + "no_rows": "По этому запросу не возвращено ни одной строки" + }, + "editable_code": { + "placeholder": "Введите содержимое для заметки с кодом..." + }, + "editable_text": { + "placeholder": "Введите содержимое для заметки..." + }, + "hoisted_note": { + "confirm_unhoisting": "Запрошенная заметка «{{requestedNote}}» находится за пределами поддерева закрепленной заметки \"{{hoistedNote}}\", и для доступа к ней необходимо снять закрепление. Открепить заметку?" + }, + "frontend_script_api": { + "sync_warning": "Вы передаете синхронную функцию в `api.runAsyncOnBackendWithManualTransactionHandling()`, \\nхотя вместо этого вам, скорее всего, следует использовать `api.runOnBackend()`.", + "async_warning": "Вы передаете асинхронную функцию в `api.runOnBackend()`, которая, скорее всего, не будет работать так, как вы предполагали.\\nЛибо сделайте функцию синхронной (удалив ключевое слово `async`), либо используйте `api.runAsyncOnBackendWithManualTransactionHandling()`." + }, + "note_detail": { + "could_not_find_typewidget": "Не удалось найти typeWidget для типа '{{type}}'" + }, + "book": { + "no_children_help": "В этой коллекции нет дочерних заметок, поэтому отображать нечего. Подробности см. на wiki." } } From 4d9801a3725afae96ad45d686d2c0ba473199d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=20=D0=9A=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D1=82=D1=96=D0=BD?= Date: Fri, 22 Aug 2025 12:53:07 +0200 Subject: [PATCH 251/532] Translated using Weblate (Russian) Currently translated at 100.0% (1560 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/ --- apps/client/src/translations/ru/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/translations/ru/translation.json b/apps/client/src/translations/ru/translation.json index 9be536c50..df1ca0845 100644 --- a/apps/client/src/translations/ru/translation.json +++ b/apps/client/src/translations/ru/translation.json @@ -506,7 +506,7 @@ "share_external_link": "заметка будет действовать как ссылка на внешний веб-сайт в дереве общего доступа", "share_alias": "определить псевдоним, с помощью которого заметка будет доступна по адресу https://ссылка_на_ваш_trilium/share/[ваш_псевдоним]", "share_omit_default_css": "CSS-код страницы общего доступа по умолчанию будет пропущен. Используйте его при внесении существенных изменений в стили.", - "share_root": "помечает заметку, которая будет выступать корнвой страницей /share общедоступного сайта.", + "share_root": "помечает заметку, которая будет выступать корневой страницей /share общедоступного сайта.", "share_description": "определение текста, который будет добавлен в HTML-тег meta для описания", "share_raw": "заметка будет передана в исходном виде, без HTML-обертки", "share_disallow_robot_indexing": "запретит индексацию этой заметки роботами через заголовок X-Robots-Tag: noindex", From 196b3b873f0e0424ed946caa5fcde10ceac29fd4 Mon Sep 17 00:00:00 2001 From: Kuzma Simonov Date: Fri, 22 Aug 2025 07:24:01 +0200 Subject: [PATCH 252/532] Translated using Weblate (Russian) Currently translated at 100.0% (378 of 378 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/ --- .../src/assets/translations/ru/server.json | 70 ++++++++++++++++--- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/apps/server/src/assets/translations/ru/server.json b/apps/server/src/assets/translations/ru/server.json index ebcf5f9a7..8d8d4eeff 100644 --- a/apps/server/src/assets/translations/ru/server.json +++ b/apps/server/src/assets/translations/ru/server.json @@ -71,10 +71,10 @@ "add-new-label": "Создать новую заметку", "create-new-relation": "Создать новое отношение", "ribbon-tabs": "Вкладки ленты", - "toggle-basic-properties": "Перейти к основным свойствам", + "toggle-basic-properties": "Перейти к общим параметрам", "toggle-file-properties": "Перейти к свойствам файла", "toggle-image-properties": "Перейти к свойствам изображения", - "toggle-owned-attributes": "Перейти к собственным атрибутами", + "toggle-owned-attributes": "Перейти к атрибутам", "toggle-inherited-attributes": "Перейти к унаследованным атрибутам", "toggle-promoted-attributes": "Перейти к продвигаемым атрибутам", "toggle-link-map": "Перейти к картуессылок", @@ -100,7 +100,10 @@ "toggle-book-properties": "Перейти к свойствам коллекции", "toggle-classic-editor-toolbar": "Перейти на вкладку «Форматирование» для редактора с фиксированной панелью инструментов", "export-as-pdf": "Экспортировать текущую заметку в формате PDF", - "toggle-zen-mode": "Включает/отключает режим дзен (минимальный пользовательский интерфейс для фокусирования)" + "toggle-zen-mode": "Включает/отключает режим дзен (минимальный пользовательский интерфейс для фокусирования)", + "toggle-note-hoisting": "Переключить закрепление активной заметки", + "unhoist": "Убрать закрепление везде", + "force-save-revision": "Принудительное создать/сохранить снимок версии активной заметки" }, "hidden-subtree": { "localization": "Язык и регион", @@ -147,12 +150,13 @@ "visible-launchers-title": "Видимые лаунчеры", "user-guide": "Руководство пользователя", "sql-console-history-title": "История консоли SQL", - "user-hidden-title": "Пользователь скрыт", + "user-hidden-title": "Скрытый пользователь", "launch-bar-templates-title": "Шаблоны панели запуска", "base-abstract-launcher-title": "Базовый абстрактный лаунчер", "go-to-previous-note-title": "К предыдущей заметке", "go-to-next-note-title": "К следующей заметке", - "open-today-journal-note-title": "Открыть сегодняшнюю заметку в журнале" + "open-today-journal-note-title": "Открыть сегодняшнюю заметку в журнале", + "llm-chat-title": "ИИ чат с заметками" }, "tray": { "bookmarks": "Закладки", @@ -160,7 +164,9 @@ "close": "Выйти из Trilium", "recents": "Последние заметки", "new-note": "Новая заметка", - "show-windows": "Показать окна" + "show-windows": "Показать окна", + "open_new_window": "Открыть новое окно", + "today": "Открыть заметку дня" }, "keyboard_action_names": { "scroll-to-active-note": "Прокрутить к активной заметке", @@ -238,7 +244,26 @@ "toggle-full-screen": "Переключить на полный экран", "reset-zoom-level": "Сбросить уровень масштабирования", "copy-without-formatting": "Копировать без форматирования", - "force-save-revision": "Принудительное сохранение версии" + "force-save-revision": "Принудительное сохранение версии", + "unhoist-note": "Открепить заметку", + "toggle-right-pane": "Переключить правую панель", + "print-active-note": "Печать активной заметки", + "render-active-note": "Рендеринг активной заметки", + "run-active-note": "Запуск активной заметки", + "add-include-note-to-text": "Добавить включение другой заметки в текст", + "toggle-ribbon-tab-basic-properties": "Переключить на вкладку \"Общее\"", + "toggle-ribbon-tab-book-properties": "Переключить на вкладку \"Свойства книги\"", + "toggle-ribbon-tab-file-properties": "Переключить на вкладку \"Свойства файла\"", + "toggle-ribbon-tab-image-properties": "Переключить на вкладку \"Свойства изображения\"", + "toggle-ribbon-tab-owned-attributes": "Переключить на вкладку \"Атрибуты\"", + "toggle-ribbon-tab-inherited-attributes": "Переключить на вкладку \"Унаследованные атрибуты\"", + "toggle-ribbon-tab-promoted-attributes": "Переключить на вкладку \"Продвигаемые атрибуты\"", + "toggle-ribbon-tab-note-map": "Переключить на вкладку \"Карта заметок\"", + "toggle-ribbon-tab-note-info": "Переключить на вкладку \"Информация о заметке\"", + "toggle-ribbon-tab-note-paths": "Переключить на вкладку \"Пути к заметке\"", + "toggle-ribbon-tab-similar-notes": "Переключить на вкладку \"Похожие заметки\"", + "export-active-note-as-pdf": "Экспортировать активную заметку в формате PDF", + "toggle-note-hoisting": "Переключить закрепление заметки" }, "months": { "august": "Август", @@ -318,7 +343,8 @@ }, "notes": { "duplicate-note-suffix": "(дубликат)", - "new-note": "Новая заметка" + "new-note": "Новая заметка", + "duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}" }, "modals": { "error_title": "Ошибка" @@ -328,7 +354,9 @@ "subpages": "Подстраницы:", "expand": "Развернуть", "site-theme": "Тема оформления сайта", - "image_alt": "Изображение статьи" + "image_alt": "Изображение статьи", + "on-this-page": "На текущей странице", + "last-updated": "Последнее обновление: {{- date}}" }, "hidden_subtree_templates": { "description": "Описание", @@ -359,12 +387,14 @@ }, "share_page": { "child-notes": "Дочерние заметки:", - "no-content": "Эта заметка пуста." + "no-content": "Эта заметка пуста.", + "parent": "родитель:", + "clipped-from": "Эта заметка изначально была вырезана из {{- url}}" }, "javascript-required": "Для работы Trilium требуется JavaScript.", "setup_sync-from-desktop": { "heading": "Синхронизация с приложения ПК", - "description": "Эту настройку необходимо инициировать из приложения для ПК.", + "description": "Эту настройку необходимо инициировать из приложения для ПК:", "step1": "Откройте приложение Trilium Notes на ПК.", "step2": "В меню Trilium выберите «Параметры».", "step3": "Нажмите на категорию «Синхронизация».", @@ -376,5 +406,23 @@ "test_sync": { "not-configured": "Адрес сервера синхронизации не установлен. Сначала настройте синхронизацию.", "successful": "Установление связи с сервером синхронизации прошло успешно, синхронизация начата." + }, + "pdf": { + "export_filter": "Документ PDF (*.pdf)", + "unable-to-save-message": "Не удалось записать выбранный файл. Попробуйте ещё раз или выберите другой путь.", + "unable-to-export-message": "Текущую заметку невозможно экспортировать в формате PDF.", + "unable-to-export-title": "Невозможно экспортировать в PDF" + }, + "migration": { + "wrong_db_version": "Версия базы данных ({{version}}) новее, чем та, которую ожидает приложение ({{targetVersion}}). Это означает, что она была создана более новой и несовместимой версией Trilium. Для решения этой проблемы обновите Trilium до последней версии.", + "old_version": "Прямая миграция с текущей версии не поддерживается. Сначала обновитесь до последней версии 0.60.4, а затем — до этой.", + "error_message": "Ошибка при миграции на версию {{version}}: {{stack}}" + }, + "backend_log": { + "reading-log-failed": "Не удалось прочитать файл лога бэкенда '{{ fileName }}'.", + "log-does-not-exist": "Файл лога бэкенда '{{ fileName }}' (пока) не существует." + }, + "content_renderer": { + "note-cannot-be-displayed": "Этот тип заметки не может быть отображен." } } From 268acb0b88530c1a6ac24e6acd6f499345815ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=20=D0=9A=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D1=82=D1=96=D0=BD?= Date: Fri, 22 Aug 2025 17:00:05 +0200 Subject: [PATCH 253/532] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1560 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/uk/ --- .../src/translations/uk/translation.json | 1354 ++++++++++++++++- 1 file changed, 1323 insertions(+), 31 deletions(-) diff --git a/apps/client/src/translations/uk/translation.json b/apps/client/src/translations/uk/translation.json index 401a5b43e..447fca001 100644 --- a/apps/client/src/translations/uk/translation.json +++ b/apps/client/src/translations/uk/translation.json @@ -3,7 +3,7 @@ "add_link": "Додати посилання", "help_on_links": "Довідка щодо посилань", "note": "Нотатка", - "search_note": "пошук нотатки за її назвою", + "search_note": "пошук нотатки за назвою", "link_title_mirrors": "заголовок посилання відображає поточний заголовок нотатки", "link_title_arbitrary": "заголовок посилання можна змінювати довільно", "link_title": "Заголовок посилання", @@ -36,7 +36,7 @@ "zoom": "Масштаб", "toggle_fullscreen": "Увімкнути повноекранний режим", "zoom_out": "Зменшити масштаб", - "reset_zoom_level": "Скинути масштабування", + "reset_zoom_level": "Скинути Масштабування", "zoom_in": "Збільшити масштаб", "configure_launchbar": "Налаштувати панель запуску", "show_shared_notes_subtree": "Показати піддерево спільних нотаток", @@ -55,7 +55,8 @@ "toggle-zen-mode": "Дзен-режим" }, "modal": { - "help_title": "Показати більше інформації про це вікно" + "help_title": "Показати більше інформації про це вікно", + "close": "Закрити" }, "toast": { "critical-error": { @@ -64,17 +65,17 @@ }, "widget-error": { "title": "Не вдалася ініціалізація віджета", - "message-custom": "Не вдалося ініціалізувати користувацький віджет із нотатки з ID \"{{id}}\" під назвою \"{{title}}\" через:\n\n{{message}}", + "message-custom": "Не вдалося ініціалізувати користувацький віджет із нотатки ID \"{{id}}\" з заголовком \"{{title}}\" через:\n\n{{message}}", "message-unknown": "Невідомий віджет не вдалося ініціалізувати через:\n\n{{message}}" }, "bundle-error": { "title": "Не вдалося завантажити користувацький скрипт", - "message": "Скрипт з нотатки з ID \"{{id}}\" з заголовком \"{{title}}\" не вдалося виконати через:\n\n{{message}}" + "message": "Скрипт з нотатки ID \"{{id}}\" з заголовком \"{{title}}\" не вдалося виконати через:\n\n{{message}}" } }, "bulk_actions": { "bulk_actions": "Масові дії", - "affected_notes": "Застосовані нотатки", + "affected_notes": "Зачеплені нотатки", "available_actions": "Доступні дії", "chosen_actions": "Обрані дії", "execute_bulk_actions": "Виконання масових дій", @@ -96,34 +97,53 @@ "prefix_optional": "Префікс (необов'язково)", "clone_to_selected_note": "Клонувати до вибраної нотатки", "no_path_to_clone_to": "Немає шляху для клонування.", - "note_cloned": "Нотатку \"{{clonedTitle}}\" було клоновано в \"{{targetTitle}}\"" + "note_cloned": "Нотатка \"{{clonedTitle}}\" була клонована в \"{{targetTitle}}\"" }, "clipboard": { "copied": "Нотатку(-и) було скопійовано в буфер.", "copy_failed": "Не вдалося скопіювати в буфер через проблеми з дозволами.", - "copy_success": "Скопійовано в буфер." + "copy_success": "Скопійовано в буфер.", + "cut": "Нотатку(и) було вирізано в буфер обміну." }, "entrypoints": { - "sql-error": "Виникла помилка при виконанні запиту SQL: {{message}}" + "sql-error": "Виникла помилка при виконанні запиту SQL: {{message}}", + "note-revision-created": "Створено версію нотатки.", + "note-executed": "Нотатка виконана." }, "branches": { "undeleting-notes-finished-successfully": "Нотатки вдало відновлено.", "undeleting-notes-in-progress": "Відновлюємо нотатки: {{count}}", "delete-notes-in-progress": "Видаляємо нотатки: {{count}}", - "delete-finished-successfully": "Нотатки вдало видалено." + "delete-finished-successfully": "Нотатки вдало видалено.", + "cannot-move-notes-here": "Неможливо перемістити нотатки сюди.", + "delete-status": "Видалити статус" }, "launcher_context_menu": { "add-spacer": "Додати розділювач", - "reset": "Скинути" + "reset": "Скинути", + "reset_launcher_confirm": "Ви справді хочете скинути налаштування \"{{title}}\"? Усі дані/налаштування в цій нотатці (та її дочірніх об'єктах) будуть втрачені, а панель запуску буде повернута до початкового розташування.", + "add-note-launcher": "Додати панель запуску нотаток", + "add-script-launcher": "Додати засіб запуску скриптів", + "add-custom-widget": "Додати користувацький віджет", + "delete": "Видалити ", + "move-to-visible-launchers": "Перейти до видимих панелей запуску", + "move-to-available-launchers": "Перейти до доступних лаунчерів", + "duplicate-launcher": "Дублікат програми запуску " }, "editable-text": { "auto-detect-language": "Автовизначена" }, "highlighting": { - "color-scheme": "Схема кольорів" + "color-scheme": "Схема кольорів", + "title": "Блоки коду", + "description": "Керує підсвічуванням синтаксису для блоків коду всередині текстових нотаток, на нотатки з кодом це не вплине." }, "code_block": { - "copy_title": "Скопіювати в буфер" + "copy_title": "Скопіювати в буфер", + "word_wrapping": "Перенос слів", + "theme_none": "Без підсвічування синтаксису", + "theme_group_light": "Світлі теми", + "theme_group_dark": "Темні теми" }, "classic_editor_toolbar": { "title": "Форматування" @@ -133,7 +153,16 @@ }, "editing": { "editor_type": { - "label": "Панель інструментів форматування" + "label": "Панель інструментів форматування", + "floating": { + "title": "Плаваюче", + "description": "інструменти редагування з’являються поруч із курсором;" + }, + "fixed": { + "title": "Фіксоване", + "description": "інструменти редагування відображаються на вкладці стрічки Форматування." + }, + "multiline-toolbar": "Відображати панель інструментів на кількох рядках, якщо вона не поміщається." } }, "confirm": { @@ -187,7 +216,7 @@ "jumpToParentNote": "перейти до батьківської нотатки", "collapseWholeTree": "згорнути все дерево нотаток", "collapseSubTree": "згорнути піддерево", - "tabShortcuts": "Швидкі клавіші вкладки", + "tabShortcuts": "Комбінації клавіш вкладки", "newTabNoteLink": "посилання на нотатку відкриває нотатку в новій вкладці", "newTabWithActivationNoteLink": "посилання на нотатку відкривається та активує нотатку в новій вкладці", "onlyInDesktop": "Тільки для ПК (збірка Electron)", @@ -217,7 +246,7 @@ "insertDateTime": "вставити поточну дату та час у позицію курсору", "jumpToTreePane": "перейти до панелі дерева та прокрутити до активної нотатки", "markdownAutoformat": "Автоформатування, подібне до Markdown", - "headings": "##, ###, #### тощо, а потім пробіл для заголовків", + "headings": "##, ###, #### тощо, а потім пробіл для заголовків", "bulletList": "* або - з пробілом для маркованого списку", "numberedList": "1. або 1), а потім пробіл для нумерованого списку", "blockQuote": "починайте рядок з >, а потім пробіл для цитування блоку", @@ -246,13 +275,13 @@ "import": "Імпорт", "failed": "Помилка імпорту: {{message}}.", "html_import_tags": { - "title": "Теги імпорту HTML", + "title": "Теги Імпорту HTML", "description": "Налаштуйте, які теги HTML слід зберігати під час імпорту нотаток. Теги, яких немає в цьому списку, будуть видалені під час імпорту. Деякі теги (наприклад, 'script') завжди видаляються з міркувань безпеки.", "placeholder": "Введіть теги HTML, по одному на рядок", "reset_button": "Скинути до Список за замовчуванням" }, - "import-status": "Статус імпорту", - "in-progress": "Триває імпорт: {{progress}}", + "import-status": "Статус Імпорту", + "in-progress": "Триває Імпорт: {{progress}}", "successful": "Імпорт успішно завершено." }, "prompt": { @@ -262,7 +291,7 @@ }, "protected_session_password": { "modal_title": "Захищений сеанс", - "help_title": "Довідка щодо захищених нотаток", + "help_title": "Довідка щодо Захищених нотаток", "close_label": "Закрити", "form_label": "Щоб продовжити запитувану дію, вам потрібно розпочати захищений сеанс, ввівши пароль:", "start_button": "Розпочати захищений сеанс" @@ -279,7 +308,7 @@ "note_revisions": "Версії нотаток", "delete_all_revisions": "Видалити всі версії цієї нотатки", "delete_all_button": "Видалити всі версії", - "help_title": "Довідка щодо версій нотаток", + "help_title": "Довідка щодо Версій нотаток", "revision_last_edited": "Цю версію востаннє редагували {{date}}", "confirm_delete_all": "Ви хочете видалити всі версії цієї нотатки?", "no_revisions": "Поки що немає версій цієї нотатки...", @@ -292,7 +321,7 @@ "revision_deleted": "Версію нотатки видалено.", "snapshot_interval": "Інтервал знімків версій нотатки: {{seconds}}s.", "maximum_revisions": "Ліміт знімків версій нотатки: {{number}}.", - "settings": "Налаштування версій нотатки", + "settings": "Налаштування Версії Нотатки", "download_button": "Завантажити", "mime": "МІМЕ: ", "file_size": "Розмір файлу:", @@ -302,12 +331,12 @@ "include_note": { "dialog_title": "Включити нотатку", "label_note": "Нотатка", - "placeholder_search": "пошук нотатки за її назвою", + "placeholder_search": "пошук нотатки за назвою", "box_size_prompt": "Розмір вмісту з вкладеною нотаткою:", "box_size_small": "маленький (~ 10 рядків)", "box_size_medium": "середній (~ 30 рядків)", "box_size_full": "повний (вміст показує повний текст)", - "button_include": "Включити Нотатку" + "button_include": "Включити нотатку" }, "info": { "modalTitle": "Інформаційне повідомлення", @@ -327,7 +356,7 @@ "move_to": { "dialog_title": "Перемістити нотатки до ...", "notes_to_move": "Нотатки для переміщення", - "search_placeholder": "пошук нотатки за її назвою", + "search_placeholder": "пошук нотатки за назвою", "move_button": "Перейти до вибраної нотатки", "error_no_path": "Немає шляху для переміщення.", "move_success_message": "Вибрані нотатки переміщено до ", @@ -345,7 +374,7 @@ "title": "Пароль не встановлено", "body1": "Захищені нотатки шифруються за допомогою пароля користувача, але пароль ще не встановлено.", "body2": "Щоб захистити нотатки, натисніть кнопку нижче, щоб відкрити діалогове вікно Параметри та встановити пароль.", - "go_to_password_options": "Перейти до параметрів пароля" + "go_to_password_options": "Перейти до Параметрів пароля" }, "sort_child_notes": { "sort_children_by": "Сортування дочірніх за...", @@ -410,12 +439,12 @@ "delete": "Видалити", "related_notes_title": "Інші нотатки з цією міткою", "more_notes": "Більше нотаток", - "label": "Деталі Мітки", + "label": "Деталі мітки", "label_definition": "Деталі визначення мітки", "relation": "Деталі зв'язку", "relation_definition": "Деталі визначення зв'язку", "disable_versioning": "вимикає автоматичне керування версіями. Корисно, наприклад, для великих, але неважливих нотаток, наприклад, великих JS-бібліотек, що використовуються для написання скриптів", - "calendar_root": "позначити нотатку, яка буде використовуватись для щоденника за замовчуванням. Тільки одна може бути такою.", + "calendar_root": "позначити нотатку, яка буде використовуватись для денних нотатків за замовчуванням. Тільки одна може бути такою.", "archived": "нотатки з цією міткою не будуть видимими за замовчуванням у результатах пошуку (також у діалогових вікнах Перейти до..., Додати посилання... тощо).", "exclude_from_export": "нотатки (з їхнім піддеревом) не будуть включені до жодного експорту нотаток", "run": "визначає, за яких подій має запускатися скрипт. Можливі значення:\n
      \n
    • frontendStartup – коли запускається (або оновлюється) інтерфейс Trilium, але не на мобільному пристрої.
    • \n
    • mobileStartup – коли запускається (або оновлюється) інтерфейс Trilium на мобільному пристрої.
    • \n
    • backendStartup – коли запускається бекенд Trilium
    • \n
    • hourly – запускається раз на годину. Ви можете використовувати додаткову мітку runAtHour, щоб вказати, о котрій годині.
    • \n
    • daily – запускається раз на день
    • \n
    ", @@ -431,7 +460,7 @@ "auto_read_only_disabled": "текстові/кодові нотатки можна автоматично перевести в режим читання, якщо вони занадто великі. Ви можете вимкнути цю поведінку для кожної окремої нотатки, додавши до неї цю позначку", "app_css": "позначає CSS-нотатки, які завантажуються в програму Trilium і, таким чином, можуть бути використані для зміни зовнішнього вигляду Trilium.", "app_theme": "позначає CSS-нотатки, які є повноцінними темами Trilium і тому доступні в параметрах Trilium.", - "app_theme_base": "встановіть значення \"наступна\", \"наступна-світла\" або \"наступна-темна\", щоб використовувати відповідну тему TriliumNext (автоматичну, світлу або темну) як основу для власної теми, замість застарілої.", + "app_theme_base": "встановіть значення \"next\", \"next-light\", or \"next-dark\", щоб використовувати відповідну тему TriliumNext (автоматичну, світлу або темну) як основу для власної теми, замість застарілої.", "css_class": "значення цієї мітки потім додається як CSS-клас до вузла, що представляє задану нотатку в дереві. Це може бути корисним для розширеного налаштування тем. Можна використовувати в шаблонах нотаток.", "icon_class": "значення цієї мітки додається як CSS-клас до значка на дереві, що може допомогти візуально розрізнити нотатки в дереві. Прикладом може бути bx bx-home - значки взяті з boxicons. Можна використовувати в шаблонах нотаток.", "page_size": "кількість елементів на сторінці у списку нотаток", @@ -499,7 +528,37 @@ "color_type": "Колір" }, "multi_factor_authentication": { - "mfa_method": "Метод МФА" + "mfa_method": "Метод МФА", + "title": "Багатофакторна автентифікація", + "description": "Багатофакторна автентифікація (MFA) додає додатковий рівень безпеки до вашого облікового запису. Замість того, щоб просто вводити пароль для входу, MFA вимагає від вас надання одного або кількох додаткових доказів для підтвердження вашої особи. Таким чином, навіть якщо хтось отримає ваш пароль, він все одно не зможе отримати доступ до вашого облікового запису без другої інформації. Це як додати додатковий замок на двері, що значно ускладнює проникнення будь-кому іншому.

    Будь ласка, дотримуйтесь інструкцій нижче, щоб увімкнути MFA. Якщо ви неправильно налаштуєте, вхід буде здійснюватися лише за допомогою пароля.", + "mfa_enabled": "Увімкнути багатофакторну автентифікацію", + "electron_disabled": "Багатофакторна автентифікація наразі не підтримується у збірці для ПК.", + "totp_title": "Одноразовий пароль на основі часу (TOTP)", + "totp_description": "TOTP (одноразовий пароль на основі часу) – це функція безпеки, яка генерує унікальний тимчасовий код, що змінюється кожні 30 секунд. Ви використовуєте цей код разом із паролем для входу до свого облікового запису, що значно ускладнює доступ до нього для будь-кого іншого.", + "totp_secret_title": "Згенерувати секретний код TOTP", + "totp_secret_generate": "Згенерувати секретний код TOTP", + "totp_secret_regenerate": "Оновити секретний код TOTP", + "no_totp_secret_warning": "Щоб увімкнути TOTP, спочатку потрібно згенерувати секретний код TOTP.", + "totp_secret_description_warning": "Після створення нового секретного коду TOTP вам потрібно буде знову ввійти в систему, використовуючи новий секретний код TOTP.", + "totp_secret_generated": "Згенеровано секретний код TOTP", + "totp_secret_warning": "Будь ласка, збережіть згенерований секретний код у безпечному місці. Він більше не відображатиметься.", + "totp_secret_regenerate_confirm": "Ви впевнені, що хочете повторно створити секретний код TOTP? Це призведе до анулювання попереднього секретного коду TOTP та всіх існуючих кодів відновлення.", + "recovery_keys_title": "Ключі відновлення єдиного входу", + "recovery_keys_description": "Ключі відновлення єдиного входу використовуються для входу, навіть якщо ви не маєте доступу до своїх кодів автентифікатора.", + "recovery_keys_description_warning": "Ключі відновлення більше не відображатимуться після того, як ви залишите сторінку, зберігайте їх у безпечному місці.
    Після використання ключа відновлення його не можна буде використати повторно.", + "recovery_keys_error": "Помилка створення кодів відновлення", + "recovery_keys_no_key_set": "Коди відновлення не встановлено", + "recovery_keys_generate": "Згенерувати коди відновлення", + "recovery_keys_regenerate": "Регенерувати коди відновлення", + "recovery_keys_used": "Використано: {{date}}", + "recovery_keys_unused": "Код відновлення {{index}} не використовується", + "oauth_title": "OAuth/OpenID", + "oauth_description": "OpenID – це стандартизований спосіб входу на веб-сайти за допомогою облікового запису з іншого сервісу, такого як Google, для підтвердження вашої особи. Емітентом за замовчуванням є Google, але ви можете змінити його на будь-якого іншого постачальника OpenID. Перегляньте тут для отримання додаткової інформації. Дотримуйтесь цих інструкцій, щоб налаштувати сервіс OpenID через Google.", + "oauth_description_warning": "Щоб увімкнути OAuth/OpenID, потрібно встановити базову URL-адресу OAuth/OpenID, ідентифікатор клієнта та секрет клієнта у файлі config.ini та перезапустити програму. Якщо ви хочете встановити зі змінних середовища, встановіть TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID та TRILIUM_OAUTH_CLIENT_SECRET.", + "oauth_missing_vars": "Відсутні налаштування: {{variables}}", + "oauth_user_account": "Обліковий запис користувача: ", + "oauth_user_email": "Електронна пошта користувача: ", + "oauth_user_not_logged_in": "Ви не ввійшли в систему!" }, "attribute_editor": { "help_text_body1": "Щоб додати мітку, просто введіть, наприклад, #rock або, якщо ви хочете додати також значення, то, наприклад, #year = 2020", @@ -605,7 +664,13 @@ "update_relation_target": "Оновити ціль зв'язку" }, "search_script": { - "example_code": "// 1. попередня фільтрація за допомогою стандартного пошуку\nconst candidateNotes = api.searchForNotes(\"#journal\");\n\n// 2. застосування користувацьких критеріїв пошуку\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;" + "example_code": "// 1. попередня фільтрація за допомогою стандартного пошуку\nconst candidateNotes = api.searchForNotes(\"#journal\");\n\n// 2. застосування користувацьких критеріїв пошуку\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;", + "title": "Пошуковий скрипт:", + "placeholder": "пошук нотатки за її назвою", + "description1": "Пошуковий скрипт дозволяє визначати результати пошуку, запускаючи скрипт. Це забезпечує максимальну гнучкість, коли стандартного пошуку недостатньо.", + "description2": "Скрипт пошуку має бути типу \"code\" та підтипу \"JavaScript backend\". Скрипт має повертати масив ідентифікаторів нотаток або нотатки.", + "example_title": "Дивіться цей приклад:", + "note": "Зверніть увагу, що пошуковий скрипт та рядок пошуку не можна поєднувати одне з одним." }, "delete_relation": { "delete_relation": "Видалити зв'язок", @@ -726,5 +791,1232 @@ }, "revisions_button": { "note_revisions": "Версії нотатки" + }, + "update_available": { + "update_available": "Доступне оновлення" + }, + "note_launcher": { + "this_launcher_doesnt_define_target_note": "Цей лаунчер не визначає цільову примітку." + }, + "code_buttons": { + "execute_button_title": "Виконати скрипт", + "trilium_api_docs_button_title": "Відкрити документацію API Trilium", + "save_to_note_button_title": "Зберегти до нотатки", + "opening_api_docs_message": "Відкриття документації API...", + "sql_console_saved_message": "Нотатку консолі SQL збережено в {{note_path}}" + }, + "copy_image_reference_button": { + "button_title": "Копіювати посилання на зображення в буфер обміну, можна вставити в текстову нотатку." + }, + "hide_floating_buttons_button": { + "button_title": "Приховати кнопки" + }, + "show_floating_buttons_button": { + "button_title": "Показати кнопки" + }, + "svg_export_button": { + "button_title": "Експорт діаграми у форматі SVG" + }, + "relation_map_buttons": { + "create_child_note_title": "Створити нову дочірню нотатку та додати її до цієї Карти зв'язків", + "reset_pan_zoom_title": "Скинути панорамування та масштабування до початкових координат і збільшення", + "zoom_in_title": "Збільшити масштаб", + "zoom_out_title": "Зменшити масштаб" + }, + "zpetne_odkazy": { + "backlink": "{{count}} Зворотне посилання", + "backlinks": "{{count}} Зворотні посилання", + "relation": "зв'язок" + }, + "mobile_detail_menu": { + "insert_child_note": "Вставити дочірню нотатку", + "delete_this_note": "Видалити цю нотатку", + "error_cannot_get_branch_id": "Не вдається отримати branchId для notePath '{{notePath}}'", + "error_unrecognized_command": "Нерозпізнана команда {{command}}" + }, + "note_icon": { + "change_note_icon": "Змінити значок нотатки", + "category": "Категорія:", + "search": "Пошук:", + "reset-default": "Скинути значок до стандартного значення" + }, + "basic_properties": { + "note_type": "Тип нотатки", + "editable": "Можна редагувати", + "basic_properties": "Основні властивості", + "language": "Мова" + }, + "book_properties": { + "view_type": "Тип перегляду", + "grid": "Сітка", + "list": "Список", + "collapse_all_notes": "Згорнути всі нотатки", + "expand_all_children": "Розгорнути всі дочірні елементи", + "collapse": "Згорнути", + "expand": "Розгорнути", + "book_properties": "Властивості колекції", + "invalid_view_type": "Недійсний тип перегляду '{{type}}'", + "calendar": "Календар", + "table": "Таблиця", + "geo-map": "Географічна карта", + "board": "Дошка" + }, + "edited_notes": { + "no_edited_notes_found": "Цього дня ще немає відредагованих нотаток...", + "title": "Відредаговані нотатки", + "deleted": "(видалено)" + }, + "file_properties": { + "note_id": "ID Нотатки", + "original_file_name": "Оригінальна назва файлу", + "file_type": "Тип файлу", + "file_size": "Розмір файлу", + "download": "Завантажити", + "open": "Відкрити", + "upload_new_revision": "Завантажити нову версію", + "upload_success": "Завантажено нову версію файлу.", + "upload_failed": "Не вдалося завантажити нову версію файлу.", + "title": "Файл" + }, + "image_properties": { + "original_file_name": "Оригінальна назва файлу", + "file_type": "Тип файлу", + "file_size": "Розмір файлу", + "download": "Завантажити", + "open": "Відкрити", + "copy_reference_to_clipboard": "Копіювати посилання в буфер обміну", + "upload_new_revision": "Завантажити нову версію", + "upload_success": "Нову версію зображення завантажено.", + "upload_failed": "Не вдалося завантажити нову версію зображення: {{message}}", + "title": "Зображення" + }, + "inherited_attribute_list": { + "title": "Успадковані атрибути", + "no_inherited_attributes": "Немає успадкованих атрибутів." + }, + "note_info_widget": { + "note_id": "ID Нотатки", + "created": "Створено", + "modified": "Змінено", + "type": "Тип", + "note_size": "Розмір нотатки", + "note_size_info": "Розмір нотатки надає приблизну оцінку вимог до зберігання для цієї нотатки. Він враховує вміст нотатки та вміст її версій.", + "calculate": "обчислити", + "subtree_size": "(розмір піддерева: {{size}} у {{count}} нотатках)", + "title": "Інформація про нотатку" + }, + "note_map": { + "open_full": "Розгорнути на повний розмір", + "collapse": "Згорнути до звичайного розміру", + "title": "Карта нотатки", + "fix-nodes": "Виправити вузли", + "link-distance": "Відстань зв'язку" + }, + "note_paths": { + "title": "Шляхи нотаток", + "clone_button": "Клонувати нотатку в нове місце...", + "intro_placed": "Цю нотатку розміщено за такими шляхами:", + "intro_not_placed": "Цю нотатку ще не розміщено в дереві нотаток.", + "archived": "Архівовано", + "search": "Пошук", + "outside_hoisted": "Цей шлях знаходиться поза межами закріпленої нотатки і вам доведеться відкріпити." + }, + "note_properties": { + "this_note_was_originally_taken_from": "Цю нотатку було спочатку взято з:", + "info": "Інформація" + }, + "owned_attribute_list": { + "owned_attributes": "Власні атрибути" + }, + "promoted_attributes": { + "promoted_attributes": "Просунуті атрибути", + "unset-field-placeholder": "не встановлено", + "url_placeholder": "http://website...", + "open_external_link": "Відкрити зовнішнє посилання", + "unknown_label_type": "Невідомий тип мітки '{{type}}'", + "unknown_attribute_type": "Невідомий тип атрибута '{{type}}'", + "add_new_attribute": "Додати новий атрибут", + "remove_this_attribute": "Видалити цей атрибут", + "remove_color": "Видалити кольорову мітку" + }, + "script_executor": { + "query": "Запит", + "script": "Скрипт", + "execute_query": "Виконати запит", + "execute_script": "Виконати скрипт" + }, + "search_definition": { + "add_search_option": "Додати опцію пошуку:", + "search_string": "рядок пошуку", + "search_script": "пошуковий скрипт", + "ancestor": "предок", + "fast_search": "швидкий пошук", + "fast_search_description": "Опція швидкого пошуку вимикає повнотекстовий пошук вмісту нотаток, що може пришвидшити пошук у великих базах даних.", + "include_archived": "включити архівовані", + "include_archived_notes_description": "Архівні нотатки за замовчуванням виключаються з результатів пошуку, але з цим параметром вони будуть включені.", + "order_by": "упорядкувати за", + "limit": "ліміт", + "limit_description": "Ліміт кількості результатів", + "debug": "debug", + "debug_description": "Debug виведе додаткову інформацію для налагодження в консоль, щоб допомогти у налагодженні складних запитів", + "action": "дія", + "search_button": "Пошук enter", + "search_execute": "Пошук & Виконання дій", + "save_to_note": "Зберегти до нотатки", + "search_parameters": "Параметри пошуку", + "unknown_search_option": "Невідомий варіант пошуку {{searchOptionName}}", + "actions_executed": "Дії виконано.", + "search_note_saved": "Нотатка з пошуку збережено у {{- notePathTitle}}" + }, + "similar_notes": { + "title": "Схожі нотатки", + "no_similar_notes_found": "Схожих нотаток не знайдено." + }, + "abstract_search_option": { + "remove_this_search_option": "Видалити цей параметр пошуку", + "failed_rendering": "Помилка відтворення параметру пошуку: {{dto}} з помилкою {{error}}{{stack}}" + }, + "ancestor": { + "label": "Предок", + "placeholder": "пошук нотатки за її назвою", + "depth_label": "глибина", + "depth_doesnt_matter": "не має значення", + "depth_eq": "дорівнює {{count}}", + "direct_children": "прямі дочірні", + "depth_gt": "більше ніж {{count}}", + "depth_lt": "менше ніж {{count}}" + }, + "debug": { + "debug": "Debug", + "debug_info": "Команда Debug виведе додаткову інформацію для налагодження в консоль, щоб допомогти у налагодженні складних запитів.", + "access_info": "Щоб отримати доступ до інформації про debug, виконайте запит і натисніть \"Show backend log\" у верхньому лівому куті." + }, + "fast_search": { + "fast_search": "Швидкий пошук", + "description": "Опція швидкого пошуку вимикає повнотекстовий пошук вмісту нотаток, що може пришвидшити пошук у великих базах даних." + }, + "include_archived_notes": { + "include_archived_notes": "Включити архівовані нотатки" + }, + "limit": { + "limit": "Ліміт", + "take_first_x_results": "Взяти лише перші X зазначених результатів." + }, + "order_by": { + "order_by": "Сортувати за", + "relevancy": "Релевантність (за замовчуванням)", + "title": "Заголовок", + "date_created": "Дата створення", + "date_modified": "Дата останньої зміни", + "content_size": "Розмір вмісту нотатки", + "content_and_attachments_size": "Розмір вмісту нотатки, включаючи вкладення", + "content_and_attachments_and_revisions_size": "Розмір вмісту нотатки, включаючи вкладення та версії", + "revision_count": "Номер версії", + "children_count": "Кількість дочірніх нотаток", + "parent_count": "Кількість клонів", + "owned_label_count": "Кількість міток", + "owned_relation_count": "Кількість зв'язків", + "target_relation_count": "Кількість зв'язків цільової нотатки", + "random": "Випадковий порядок", + "asc": "За зростанням (за замовчуванням)", + "desc": "За спаданням" + }, + "search_string": { + "title_column": "Рядок пошуку:", + "placeholder": "fulltext keywords, #tag = value...", + "search_syntax": "Синтаксис пошуку", + "also_see": "див. також", + "complete_help": "повна допомога щодо синтаксису пошуку", + "full_text_search": "Просто введіть будь-який текст для повнотекстового пошуку", + "label_abc": "повертає нотатки з міткою abc", + "label_year": "зіставляє нотатки з роком на мітці, що має значення 2019", + "label_rock_pop": "відповідає нотаткам, які мають мітки rock і pop", + "label_rock_or_pop": "має бути лише одна з міток", + "label_year_comparison": "числове порівняння (також >, >=, <).", + "label_date_created": "нотатки, створені за останній місяць", + "error": "Помилка пошуку: {{error}}", + "search_prefix": "Пошук:" + }, + "attachment_detail": { + "open_help_page": "Відкрити сторінку довідки щодо вкладень", + "owning_note": "Примітка власника: ", + "you_can_also_open": ", ви також можете відкрити ", + "list_of_all_attachments": "Список усіх вкладень", + "attachment_deleted": "Цей вкладення видалено." + }, + "attachment_list": { + "open_help_page": "Відкрити сторінку довідки щодо вкладень", + "owning_note": "Примітка власника: ", + "upload_attachments": "Завантажити вкладення", + "no_attachments": "Ця нотатка не має вкладень." + }, + "book": { + "no_children_help": "Ця колекція не має дочірніх нотаток, тому нічого відображати. Див. вікі для отримання детальної інформації." + }, + "editable_code": { + "placeholder": "Введіть тут вміст вашої нотатки з кодом..." + }, + "editable_text": { + "placeholder": "Введіть тут вміст вашої нотатки..." + }, + "empty": { + "open_note_instruction": "Відкрийте нотатку, ввівши її заголовок в поле нижче, або виберіть нотатку в дереві.", + "search_placeholder": "пошук нотатки за її назвою", + "enter_workspace": "Вхід до робочого простору {{title}}" + }, + "file": { + "file_preview_not_available": "Попередній перегляд файлу недоступний для цього формату файлу.", + "too_big": "З міркувань продуктивності у попередньому перегляді відображаються лише перші {{maxNumChars}} символів файлу. Завантажте файл і відкрийте його зовні, щоб побачити весь вміст." + }, + "protected_session": { + "enter_password_instruction": "Для відображення захищеної нотатки потрібно ввести пароль:", + "start_session_button": "Розпочати захищений сеанс enter", + "started": "Захищений сеанс розпочато.", + "wrong_password": "Неправильний пароль.", + "protecting-finished-successfully": "Захист успішно завершено.", + "unprotecting-finished-successfully": "Зняття захисту успішно завершено.", + "protecting-in-progress": "Триває захист: {{count}}", + "unprotecting-in-progress-count": "Триває зняття захисту: {{count}}", + "protecting-title": "Статус захисту", + "unprotecting-title": "Статус зняття захисту" + }, + "relation_map": { + "open_in_new_tab": "Відкрити в новій вкладці", + "remove_note": "Перемістити нотатку", + "edit_title": "Редагувати заголовок", + "rename_note": "Перейменувати нотатку", + "enter_new_title": "Введіть новий заголовок нотатки:", + "remove_relation": "Перемістити зв'язок", + "confirm_remove_relation": "Ви впевнені, що хочете перемістити зв'язок?", + "specify_new_relation_name": "Вкажіть нову назву зв'язку (дозволені символи: букви, цифри, двокрапка та підкреслення):", + "connection_exists": "Зв'язок '{{name}}' між цими нотатками вже існує.", + "start_dragging_relations": "Почніть перетягувати зв'язки звідси та перемістіть їх на іншу нотатку.", + "note_not_found": "Нотатка {{noteId}} не знайдена!", + "cannot_match_transform": "Неможливо порівняти перетворення: {{transform}}", + "note_already_in_diagram": "Нотатка\"{{title}}\" вже є на діаграмі.", + "enter_title_of_new_note": "Введіть заголовок нової нотатки", + "default_new_note_title": "нова нотатка", + "click_on_canvas_to_place_new_note": "Натисніть на полотно, щоб розмістити нову нотатку" + }, + "render": { + "note_detail_render_help_1": "Ця довідка відображається, оскільки ця нотатка типу Render HTML не має необхідного зв'язку для належного функціонування.", + "note_detail_render_help_2": "Тип нотатки Render HTML використовується для скриптів. Коротше кажучи, у вас є нотатка з HTML-кодом (за бажанням з деяким JavaScript), і ця нотатка її відобразить. Щоб це запрацювало, вам потрібно визначити відношення під назвою \"renderNote\", яке вказує на нотатку HTML для відображення." + }, + "web_view": { + "web_view": "Веб-перегляд", + "embed_websites": "Нотатка типу Веб-перегляд дозволяє вбудовувати веб-сайти в Trilium.", + "create_label": "Для початку створіть мітку з URL-адресою, яку ви хочете вбудувати, наприклад, #webViewSrc=\"https://www.google.com\"" + }, + "backend_log": { + "refresh": "Оновити" + }, + "consistency_checks": { + "title": "Перевірка узгодженості", + "find_and_fix_button": "Знайти та виправити проблеми узгодженості", + "finding_and_fixing_message": "Пошук та виправлення проблем узгодженості...", + "issues_fixed_message": "Будь-які проблеми з узгодженістю, які могли бути виявлені, тепер виправлені." + }, + "database_anonymization": { + "title": "Анонімізація Бази даних", + "full_anonymization": "Повна анонімізація", + "full_anonymization_description": "Ця дія створить нову копію бази даних та анонімізує її (видалить весь вміст нотаток, залишивши лише структуру та деякі неконфіденційні метадані) для обміну нею в Інтернеті з метою налагодження без побоювань витоку ваших особистих даних.", + "save_fully_anonymized_database": "Зберегти повністю анонімізовану базу даних", + "light_anonymization": "Легка анонімізація", + "light_anonymization_description": "Ця дія створить нову копію бази даних та проведе її легку анонімізацію — зокрема, буде видалено лише вміст усіх нотаток, але заголовки та атрибути залишаться. Крім того, збережуться користувацькі нотатки JS frontend/backend та користувацькі віджети. Це надає більше контексту для налагодження проблем.", + "choose_anonymization": "Ви можете самі вирішити, чи хочете ви надати повністю чи злегка анонімізовану базу даних. Навіть повністю анонімізована база даних дуже корисна, проте в деяких випадках злегка анонімізована база даних може пришвидшити процес виявлення та виправлення помилок.", + "save_lightly_anonymized_database": "Зберегти злегка анонімізовану базу даних", + "existing_anonymized_databases": "Існуючі анонімізовані бази даних", + "creating_fully_anonymized_database": "Створення повністю анонімізованої бази даних...", + "creating_lightly_anonymized_database": "Створення злегка анонімізованої бази даних...", + "error_creating_anonymized_database": "Не вдалося створити анонімізовану базу даних, перевірте backend logs для отримання детальної інформації", + "successfully_created_fully_anonymized_database": "Створено повністю анонімізовану базу даних у {{anonymized File Path}}", + "successfully_created_lightly_anonymized_database": "Створено злегка анонімізовану базу даних у {{anonymized File Path}}", + "no_anonymized_database_yet": "Поки що немає анонімізованої бази даних." + }, + "database_integrity_check": { + "title": "Перевірка цілісності бази даних", + "description": "Це перевірить, чи не пошкоджена база даних на рівні SQLite. Це може зайняти деякий час, залежно від розміру бази даних.", + "check_button": "Перевірка цілісності бази даних", + "checking_integrity": "Перевірка цілісності бази даних...", + "integrity_check_succeeded": "Перевірка цілісності пройшла успішно — проблем не виявлено.", + "integrity_check_failed": "Перевірка цілісності не вдалася: {{results}}" + }, + "sync": { + "title": "Синхронізація", + "force_full_sync_button": "Примусова повна синхронізація", + "fill_entity_changes_button": "Запис зміни заповнювачів", + "full_sync_triggered": "Повна синхронізація активована", + "filling_entity_changes": "Заповнювач змінює рядки...", + "sync_rows_filled_successfully": "Синхронізація рядків успішно заповнена", + "finished-successfully": "Синхронізацію успішно завершено.", + "failed": "Помилка синхронізації: {{message}}" + }, + "vacuum_database": { + "title": "Стиснення бази даних", + "description": "Це призведе до перебудови бази даних, що зазвичай призведе до зменшення розміру файлу бази даних. Фактично жодні дані не зміняться.", + "button_text": "Стиснення бази даних", + "vacuuming_database": "Стиснення бази даних...", + "database_vacuumed": "База даних була стиснена" + }, + "fonts": { + "theme_defined": "Тема визначена", + "fonts": "Шрифти", + "main_font": "Основний шрифт", + "font_family": "Сімейство шрифтів", + "size": "Розмір", + "note_tree_font": "Шрифт дерева нотаток", + "note_detail_font": "Шрифт деталей нотатки", + "monospace_font": "Monospace (кодовий) шрифт", + "note_tree_and_detail_font_sizing": "Зверніть увагу, що розмір шрифту дерева та деталей залежить від основного налаштування розміру шрифту.", + "not_all_fonts_available": "Не всі перелічені шрифти можуть бути доступні у вашій системі.", + "apply_font_changes": "Щоб застосувати зміни шрифту, натисніть на", + "reload_frontend": "перезавантажити інтерфейс", + "generic-fonts": "Загальні шрифти", + "sans-serif-system-fonts": "Системні шрифти Sans-serif", + "serif-system-fonts": "Системні шрифти Serif", + "monospace-system-fonts": "Monospace системні шрифти", + "handwriting-system-fonts": "Handwriting системні шрифти", + "serif": "Serif", + "sans-serif": "Sans Serif", + "monospace": "Monospace", + "system-default": "Системні за замовчуванням" + }, + "max_content_width": { + "title": "Ширина вмісту", + "default_description": "Trilium за замовчуванням обмежує максимальну ширину вмісту, щоб поліпшити читабельність на широкоформатних екранах у режимі максимального розширення.", + "max_width_label": "Максимальна ширина вмісту", + "max_width_unit": "пікселів", + "apply_changes_description": "Щоб застосувати зміни ширини вмісту, натисніть на", + "reload_button": "перезавантажити інтерфейс", + "reload_description": "зміни в параметрах зовнішнього вигляду" + }, + "native_title_bar": { + "title": "Нативний рядок заголовка (потрібен перезапуск)", + "enabled": "увімкнено", + "disabled": "вимкнено" + }, + "ribbon": { + "widgets": "Віджети стрічки", + "promoted_attributes_message": "Вкладка стрічки Просунуті атрибути відкриється автоматично, якщо в нотатці присутні просунуті атрибути", + "edited_notes_message": "Вкладка стрічки Редаговані нотатки автоматично відкриватиметься для денних нотаток" + }, + "theme": { + "title": "Тема програми", + "theme_label": "Тема", + "override_theme_fonts_label": "Перевизначити шрифти теми", + "auto_theme": "Legacy (Системна колірна схема)", + "light_theme": "Legacy (Світла)", + "dark_theme": "Legacy (Темна)", + "triliumnext": "Trilium (Системна колірна схема)", + "triliumnext-light": "Trilium (Світла)", + "triliumnext-dark": "Trilium (Темна)", + "layout": "Макет", + "layout-vertical-title": "Вертикальний", + "layout-horizontal-title": "Горизонтальний", + "layout-vertical-description": "Панель запуску знаходиться ліворуч (за замовчуванням)", + "layout-horizontal-description": "Панель запуску знаходиться під панеллю вкладок, панель вкладок тепер має повну ширину." + }, + "ai_llm": { + "not_started": "Не розпочато", + "title": "Параметри AI", + "processed_notes": "Оброблені нотатки", + "total_notes": "Всього нотаток", + "progress": "Прогрес", + "queued_notes": "Черга нотаток", + "failed_notes": "Невдалі нотатки", + "last_processed": "Остання обробка", + "refresh_stats": "Оновити статистику", + "enable_ai_features": "Увімкнути функції AI/LLM", + "enable_ai_description": "Увімкніть функції AI, такі як підсумовування нотаток, генерація контенту та інші можливості LLM", + "openai_tab": "OpenAI", + "anthropic_tab": "Anthropic", + "voyage_tab": "Voyage AI", + "ollama_tab": "Ollama", + "enable_ai": "Увімкнути функції AI/LLM", + "enable_ai_desc": "Увімкнути функції AI, такі як підсумовування нотаток, генерація контенту та інші можливості LLM", + "provider_configuration": "Конфігурація постачальника AI", + "provider_precedence": "Пріоритет постачальника", + "provider_precedence_description": "Список постачальників, розділених комами, у порядку пріоритету (наприклад, «openai,anthropic,ollama»)", + "temperature": "Температура", + "temperature_description": "Контролює випадковість відповідей (0 = детермінований, 2 = максимальна випадковість)", + "system_prompt": "Системний запит", + "system_prompt_description": "Системний запит за замовчуванням використовується для всіх взаємодій з AI", + "openai_configuration": "Конфігурація OpenAI", + "openai_settings": "Налаштування OpenAI", + "api_key": "API Key", + "url": "Base URL", + "model": "Модель", + "openai_api_key_description": "Ваш ключ OpenAI API для доступу до служб AI", + "anthropic_api_key_description": "Ваш ключ Anthropic API для доступу до моделей Claude", + "default_model": "Модель за замовчуванням", + "openai_model_description": "Наприклад: gpt-4o, gpt-4-turbo, gpt-3.5-turbo", + "base_url": "Base URL", + "openai_url_description": "За замовчуванням: https://api.openai.com/v1", + "anthropic_settings": "Налаштування Anthropic", + "anthropic_url_description": "Базова URL-адреса для Anthropic API (за замовчуванням: https://api.anthropic.com)", + "anthropic_model_description": "Моделі Anthropic Claude для чату", + "voyage_settings": "Налаштування Voyage AI", + "ollama_settings": "Налаштування Ollama", + "ollama_url_description": "URL для Ollama API (default: http://localhost:11434)", + "ollama_model_description": "Модель Ollama для чату", + "anthropic_configuration": "Конфігурація Anthropic", + "voyage_configuration": "Конфігурація Voyage AI", + "voyage_url_description": "За замовчуванням: https://api.voyageai.com/v1", + "ollama_configuration": "Конфігурація Ollama", + "enable_ollama": "Увімкнути Ollama", + "enable_ollama_description": "Увімкнути Ollama для локальної моделі AI", + "ollama_url": "Ollama URL", + "ollama_model": "Модель Ollama", + "refresh_models": "Оновити моделі", + "refreshing_models": "Оновлення...", + "enable_automatic_indexing": "Увімкнути автоматичне індексування", + "rebuild_index": "Перебудувати індекс", + "rebuild_index_error": "Помилка початку перебудови індексу. Перегляньте logs для інформації.", + "note_title": "Заголовок нотатки", + "error": "Помилка", + "last_attempt": "Остання спроба", + "actions": "Дії", + "retry": "Повторити спробу", + "partial": "{{ percentage }}% completed", + "retry_queued": "Нотатка в черзі на повторну спробу", + "retry_failed": "Не вдалося додати нотатку до черги для повторної спроби", + "max_notes_per_llm_query": "Максимальна кількість нотаток на запит", + "max_notes_per_llm_query_description": "Максимальна кількість схожих нотаток для включення в контекст AI", + "active_providers": "Активні постачальники", + "disabled_providers": "Вимкнути постачальників", + "remove_provider": "Видалити постачальника з пошуку", + "restore_provider": "Відновити постачальника для пошуку", + "similarity_threshold": "Поріг схожості", + "similarity_threshold_description": "Мінімальна оцінка схожості (0-1) для нотаток, що включатимуться в контекст для запитів LLM", + "reprocess_index": "Перебудувати індекс пошуку", + "reprocessing_index": "Відбудова...", + "reprocess_index_started": "Оптимізація пошукового індексу розпочата у фоновому режимі", + "reprocess_index_error": "Помилка відбудови індексу пошуку", + "index_rebuild_progress": "Прогрес відбудови індексу", + "index_rebuilding": "Індекс оптимізації ({{percentage}}%)", + "index_rebuild_complete": "Оптимізацію індексу завершено", + "index_rebuild_status_error": "Помилка перевірки стану перебудови індексу", + "never": "Ніколи", + "processing": "Обробка ({{percentage}}%)", + "incomplete": "Незавершено ({{percentage}}%)", + "complete": "Завершено (100%)", + "refreshing": "Оновлення...", + "auto_refresh_notice": "Автоматичне оновлення кожні {{seconds}} секунд", + "note_queued_for_retry": "Нотатка в черзі на повторну спробу", + "failed_to_retry_note": "Не вдалося повторити спробу", + "all_notes_queued_for_retry": "Усі невдалі нотатки поставлені в чергу на повторну спробу", + "failed_to_retry_all": "Не вдалося повторити спробу", + "ai_settings": "Налаштування AI", + "api_key_tooltip": "Ключ API для доступу до сервісу", + "empty_key_warning": { + "anthropic": "Ключ API Anthropic порожній. Будь ласка, введіть дійсний ключ API.", + "openai": "Ключ API OpenAI порожній. Будь ласка, введіть дійсний ключ API.", + "voyage": "Ключ Voyage API подорожі порожній. Будь ласка, введіть дійсний ключ API.", + "ollama": "Ключ API Ollama порожній. Будь ласка, введіть дійсний ключ API." + }, + "agent": { + "processing": "Обробка...", + "thinking": "Думаю...", + "loading": "Завантаження...", + "generating": "Генерування..." + }, + "name": "AI", + "openai": "OpenAI", + "use_enhanced_context": "Використовувати розширений контекст", + "enhanced_context_description": "Надає AI більше контексту з нотатки та пов'язаних з нею нотаток для кращих відповідей", + "show_thinking": "Показати міркування", + "show_thinking_description": "Показати ланцюжок міркувань AI", + "enter_message": "Введіть ваше повідомлення...", + "error_contacting_provider": "Помилка зв’язку з постачальником AI. Перевірте налаштування та підключення до Інтернету.", + "error_generating_response": "Помилка створення відповіді AI", + "index_all_notes": "Індексація усіх нотаток", + "index_status": "Статус індексації", + "indexed_notes": "Індексовані нотатки", + "indexing_stopped": "Індексацію зупинено", + "indexing_in_progress": "Триває індексація...", + "last_indexed": "Остання індексація", + "n_notes_queued_0": "{{ count }} нотатка в черзі на індексацію", + "n_notes_queued_1": "{{ count }} нотатки в черзі на індексацію", + "n_notes_queued_2": "{{ count }} нотаток в черзі на індексацію", + "note_chat": "Нотатка Чат", + "notes_indexed_0": "{{ count }} нотатка індексовано", + "notes_indexed_1": "{{ count }} нотатки індексовано", + "notes_indexed_2": "{{ count }} нотаток індексовано", + "sources": "Джерела", + "start_indexing": "Почати індексацію", + "use_advanced_context": "Використовувати розширений контекст", + "ollama_no_url": "Ollama не налаштовано. Будь ласка, введіть дійсну URL-адресу.", + "chat": { + "root_note_title": "AI Чати", + "root_note_content": "Ця нотатка містить ваші збережені розмови в чаті з AI.", + "new_chat_title": "Новий Чат", + "create_new_ai_chat": "Створити новий AI Чат" + }, + "create_new_ai_chat": "Створити новий AI Чат", + "configuration_warnings": "Виникли деякі проблеми з конфігурацією AI. Перевірте налаштування.", + "experimental_warning": "Функція LLM наразі є експериментальною – вас попередили.", + "selected_provider": "Вибраний постачальник", + "selected_provider_description": "Вибрати постачальника послуг AI для функцій чату та автозаповнення", + "select_model": "Виберіть модель...", + "select_provider": "Виберіть постачальника...", + "ai_enabled": "Функції AI увімкнено", + "ai_disabled": "Функції AI вимкнено", + "no_models_found_online": "Моделей не знайдено. Будь ласка, перевірте свій ключ API та налаштування.", + "no_models_found_ollama": "Моделей Ollama не знайдено. Перевірте, чи працює Ollama.", + "error_fetching": "Помилка отримання моделей: {{error}}" + }, + "backup": { + "automatic_backup": "Автоматичне резервне копіювання", + "automatic_backup_description": "Trilium може автоматично створювати резервні копії бази даних:", + "enable_daily_backup": "Увімкнути щоденне резервне копіювання", + "enable_weekly_backup": "Увімкнути щотижневе резервне копіювання", + "enable_monthly_backup": "Увімкнути щомісячне резервне копіювання", + "backup_recommendation": "Рекомендується залишати резервне копіювання ввімкненим, але це може уповільнити запуск програм із великими базами даних та/або повільними пристроями зберігання даних.", + "backup_now": "Резервне копіювання зараз", + "backup_database_now": "Резервне копіювання бази даних зараз", + "existing_backups": "Існуючі резервні копії", + "date-and-time": "Дата & час", + "path": "Шлях", + "database_backed_up_to": "Резервну копію бази даних створено у {{backupFilePath}}", + "no_backup_yet": "резервної копії поки що немає" + }, + "etapi": { + "title": "ETAPI", + "description": "ETAPI — це REST API, який використовується для програмного доступу до екземпляра Trilium без інтерфейсу користувача.", + "see_more": "Див. докладнішу інформацію у {{- link_to_wiki}} та {{- link_to_openapi_spec}} або {{- link_to_swagger_ui }}.", + "wiki": "вікі", + "openapi_spec": "ETAPI OpenAPI spec", + "swagger_ui": "ETAPI Swagger UI", + "create_token": "Створити новий токен ETAPI", + "existing_tokens": "Існуючі токени", + "no_tokens_yet": "Токенів поки що немає. Натисніть кнопку вище, щоб створити його.", + "token_name": "Назва токена", + "created": "Створено", + "actions": "Дії", + "new_token_title": "Новий токен ETAPI", + "new_token_message": "Будь ласка, введіть назву нового токена", + "default_token_name": "новий токен", + "error_empty_name": "Назва токена не може бути порожньою", + "token_created_title": "Створено токен ETAPI", + "token_created_message": "Скопіюйте створений токен у буфер обміну. Trilium зберігає токен у хешованому вигляді, і це останній раз, коли ви його бачите.", + "rename_token": "Перейменувати цей токен", + "delete_token": "Видалити / деактивувати цей токен", + "rename_token_title": "Перейменувати токен", + "rename_token_message": "Введіть назву нового токена", + "delete_token_confirmation": "Ви впевнені, що хочете видалити токен ETAPI \"{{name}}\"?" + }, + "options_widget": { + "options_status": "Статус параметрів", + "options_change_saved": "Зміни параметрів збережено." + }, + "password": { + "heading": "Пароль", + "alert_message": "Запам’ятайте свій новий пароль. Пароль використовується для входу у веб-інтерфейс та для шифрування захищених нотаток. Якщо ви забудете свій пароль, усі ваші захищені нотатки будуть втрачені назавжди.", + "reset_link": "Натисніть тут, щоб скинути його.", + "old_password": "Старий пароль", + "new_password": "Новий пароль", + "new_password_confirmation": "Підтвердження нового пароля", + "change_password": "Змінити пароль", + "protected_session_timeout": "Тайм-аут захищеного сеансу", + "protected_session_timeout_description": "Тайм-аут захищеного сеансу – це період часу, після якого захищений сеанс видаляється з пам’яті браузера. Він вимірюється з моменту останньої взаємодії із захищеними нотатками. Дивись", + "wiki": "вікі", + "for_more_info": "для отримання додаткової інформації.", + "protected_session_timeout_label": "Тайм-аут захищеного сеансу:", + "reset_confirmation": "Скинувши пароль, ви назавжди втратите доступ до всіх своїх існуючих захищених нотаток. Ви дійсно хочете скинути пароль?", + "reset_success_message": "Пароль скинуто. Будь ласка, встановіть новий пароль", + "change_password_heading": "Змінити пароль", + "set_password_heading": "Встановити пароль", + "set_password": "Встановити пароль", + "password_mismatch": "Нові паролі не однакові.", + "password_changed_success": "Пароль змінено. Trilium буде перезавантажено після натискання кнопки OK." + }, + "zoom_factor": { + "title": "Коефіцієнт масштабування (лише для ПК)", + "description": "Масштабуванням також можна керувати комбінацією клавіш CTRL+- та CTRL+=." + }, + "code_auto_read_only_size": { + "title": "Автоматичний розмір лише для читання", + "description": "Автоматичний розмір нотатки лише для читання – це розмір, після досягнення якого нотатки відображатимуться в режимі лише для читання (з міркувань продуктивності).", + "label": "Автоматичний розмір лише для читання (нотатки з кодом)", + "unit": "символи" + }, + "code-editor-options": { + "title": "Редактор" + }, + "code_mime_types": { + "title": "Доступні типи MIME у спадаючому списку" + }, + "vim_key_bindings": { + "use_vim_keybindings_in_code_notes": "Комбінації клавіш Vim", + "enable_vim_keybindings": "Увімкнути комбінації клавіш Vim у нотатках з кодом (без режиму ex)" + }, + "wrap_lines": { + "wrap_lines_in_code_notes": "Перенесення рядків у нотатках з кодом", + "enable_line_wrap": "Увімкнути перенесення рядків (для застосування змін може знадобитися перезавантаження інтерфейсу)" + }, + "images": { + "images_section_title": "Зображення", + "download_images_automatically": "Автоматично завантажувати зображення для використання офлайн.", + "download_images_description": "Вставлений HTML-код може містити посилання на онлайн-зображення, Trilium знайде ці посилання та завантажить зображення, щоб вони були доступні офлайн.", + "enable_image_compression": "Увімкнути стиснення зображень", + "max_image_dimensions": "Максимальна ширина / висота зображення (розмір зображення буде змінено, якщо воно перевищить це значення).", + "max_image_dimensions_unit": "пікселів", + "jpeg_quality_description": "Якість JPEG (10 – найгірша якість, 100 – найкраща якість, рекомендовано 50–85)" + }, + "attachment_erasure_timeout": { + "attachment_erasure_timeout": "Тайм-аут стирання вкладень", + "attachment_auto_deletion_description": "Вкладення автоматично видаляються (і стираються), якщо на них більше не посилаються в нотатці після певного часу очікування.", + "erase_attachments_after": "Стерти невикористані вкладення після:", + "manual_erasing_description": "Ви також можете запустити стирання вручну (без урахування часу очікування, визначеного вище):", + "erase_unused_attachments_now": "Стерти невикористані вкладення нотатки зараз", + "unused_attachments_erased": "Невикористані вкладення стерто." + }, + "network_connections": { + "network_connections_title": "Мережеві підключення", + "check_for_updates": "Автоматична перевірка оновлень" + }, + "note_erasure_timeout": { + "note_erasure_timeout_title": "Тайм-аут стирання нотатки", + "note_erasure_description": "Видалені нотатки (а також атрибути, версії...) спочатку лише позначаються як видалені, і їх можна відновити з діалогового вікна Останні нотатки. Через певний проміжок часу видалені нотатки стираються, що означає, що їхній вміст більше не можна відновити. Цей параметр дозволяє налаштувати тривалість періоду між видаленням і стиранням нотатки.", + "erase_notes_after": "Стерти нотатки після:", + "manual_erasing_description": "Ви також можете запустити стирання вручну (без урахування часу очікування, визначеного вище):", + "erase_deleted_notes_now": "Стерти видалені нотатки зараз", + "deleted_notes_erased": "Видалені нотатки стерто." + }, + "revisions_snapshot_interval": { + "note_revisions_snapshot_interval_title": "Інтервал знімків версії нотатки", + "note_revisions_snapshot_description": "Інтервал знімка версії нотатки – це час, після якого для неї буде створено нову версію. Докладніше див. у wiki.", + "snapshot_time_interval_label": "Часовий інтервал знімка версії нотатки:" + }, + "revisions_snapshot_limit": { + "note_revisions_snapshot_limit_title": "Ліміт знімків версії нотатки", + "note_revisions_snapshot_limit_description": "Обмеження кількості знімків версій нотаток стосується максимальної кількості версій, які можна зберегти для кожної нотатки. Де -1 означає відсутність обмежень, 0 означає видалення всіх версій. Ви можете встановити максимальну кількість версій для однієї нотатки за допомогою мітки #versioningLimit.", + "snapshot_number_limit_label": "Ліміт кількості знімків версій нотатки:", + "snapshot_number_limit_unit": "знімки", + "erase_excess_revision_snapshots": "Видалити зайві знімки версій зараз", + "erase_excess_revision_snapshots_prompt": "Зайві знімки версій видалено." + }, + "search_engine": { + "title": "Пошукова система", + "custom_search_engine_info": "Для користувацького пошуку потрібно вказати як назву, так і URL-адресу. Якщо жодного з цих параметрів не встановлено, DuckDuckGo використовуватиметься як пошукова система за замовчуванням.", + "predefined_templates_label": "Попередньо визначені шаблони пошукових систем", + "bing": "Bing", + "baidu": "Baidu", + "duckduckgo": "DuckDuckGo", + "google": "Google", + "custom_name_label": "Назва користувацької пошукової системи", + "custom_name_placeholder": "Налаштувати назву пошукової системи", + "custom_url_label": "URL-адреса користувацького пошукового запиту повинна містити {keyword} як заміну для пошукового терміна.", + "custom_url_placeholder": "Налаштувати URL-адресу пошукової системи", + "save_button": "Зберегти" + }, + "tray": { + "title": "Системний трей", + "enable_tray": "Увімкнути трей (щоб ця зміна набула чинності, потрібно перезапустити Trilium)" + }, + "heading_style": { + "title": "Стиль заголовка", + "plain": "Простий", + "underline": "Підкреслення", + "markdown": "Стиль Markdown" + }, + "highlights_list": { + "title": "Список основних моментів", + "description": "Ви можете налаштувати список основних моментів, що відображається на правій панелі:", + "bold": "Жирний текст", + "italic": "Курсив", + "underline": "Підкреслений текст", + "color": "Кольоровий текст", + "bg_color": "Текст із кольором фону", + "visibility_title": "Видимість списку основних моментів", + "visibility_description": "Ви можете приховати віджет основних моментів для кожної нотатки окремо, додавши мітку #hideHighlightWidget.", + "shortcut_info": "Ви можете налаштувати комбінацію клавіш для швидкого перемикання правої панелі (включно з основними моментами) у меню Параметри -> Комбінації клавіш (назва «toggleRightPane»)." + }, + "table_of_contents": { + "title": "Зміст", + "description": "Зміст відображатиметься в текстових нотатках, якщо нотатка містить більше заголовків, ніж визначено. Ви можете налаштувати цю кількість:", + "unit": "заголовки", + "disable_info": "Ви також можете скористатися цим параметром, щоб ефективно вимкнути зміст, встановивши дуже велике число.", + "shortcut_info": "Ви можете налаштувати комбінацію клавіш для швидкого перемикання правої панелі (включно зі змістом) у меню Параметри -> Комбінації клавіш (назва «toggleRightPane»)." + }, + "text_auto_read_only_size": { + "title": "Автоматичний розмір лише для читання", + "description": "Автоматичний розмір нотатки лише для читання – це розмір, після досягнення якого нотатки відображатимуться в режимі лише для читання (з міркувань продуктивності).", + "label": "Автоматичний розмір лише для читання (текстові нотатки)", + "unit": "символи" + }, + "custom_date_time_format": { + "title": "Користувацький формат дати/часу", + "description": "Налаштуйте формат дати та часу, що вставляються за допомогою або панелі інструментів. Див. Документацію Day.js для доступних токенів формату.", + "format_string": "Формат рядка:", + "formatted_time": "Відформатована дата/час:" + }, + "i18n": { + "title": "Локалізація", + "language": "Мова", + "first-day-of-the-week": "Перший день тижня", + "sunday": "Неділя", + "monday": "Понеділок", + "first-week-of-the-year": "Перший тиждень року", + "first-week-contains-first-day": "Перший тиждень містить перший день року", + "first-week-contains-first-thursday": "Перший тиждень включає перший четвер року", + "first-week-has-minimum-days": "Перший тиждень має мінімальну кількість днів", + "min-days-in-first-week": "Мінімальна кількість днів у першому тижні", + "first-week-info": "Перший тиждень, що містить перший четвер року, базується на стандарті ISO 8601.", + "first-week-warning": "Зміна параметрів першого тижня може призвести до дублювання з існуючими нотатками тижня, і існуючі нотатки тижня не будуть оновлені відповідно.", + "formatting-locale": "Формат дати & числа" + }, + "quick-search": { + "no-results": "Результатів не знайдено", + "more-results": "... та ще {{number}} результатів.", + "show-in-full-search": "Показати в повному пошуку", + "placeholder": "Швидкий пошук", + "searching": "Пошук..." + }, + "note_tree": { + "collapse-title": "Згорнути дерево нотаток", + "scroll-active-title": "Прокрутити до активної нотатки", + "tree-settings-title": "Налаштування дерева", + "hide-archived-notes": "Приховати архівовані нотатки", + "automatically-collapse-notes": "Автоматично згортати нотатки", + "automatically-collapse-notes-title": "Нотатки будуть згорнуті після певного періоду бездіяльності, щоб розвантажити дерево.", + "save-changes": "Зберегти & застосувати зміни", + "auto-collapsing-notes-after-inactivity": "Автоматичне згортання нотаток після бездіяльності...", + "saved-search-note-refreshed": "Збережену нотатку пошуку оновлено.", + "hoist-this-note-workspace": "Закріпити цю ноту (робочий простір)", + "refresh-saved-search-results": "Оновити збережені результати пошуку", + "create-child-note": "Створити дочірню нотатку", + "unhoist": "Відкріпити" + }, + "title_bar_buttons": { + "window-on-top": "Тримати вікно зверху" + }, + "note_detail": { + "could_not_find_typewidget": "Не вдалося знайти typeWidget для типу '{{type}}'" + }, + "note_title": { + "placeholder": "введіть тут заголовок нотатки..." + }, + "search_result": { + "no_notes_found": "За заданими параметрами пошуку нотаток не знайдено.", + "search_not_executed": "Пошук ще не виконано. Натисніть кнопку Пошук вище, щоб переглянути результати." + }, + "spacer": { + "configure_launchbar": "Налаштувати Панель запуску" + }, + "sql_result": { + "no_rows": "Для цього запиту не знайдено жодного рядка" + }, + "sql_table_schemas": { + "tables": "Таблиці" + }, + "tab_row": { + "close_tab": "Закрити вкладку", + "add_new_tab": "Додати нову вкладку", + "close": "Закрити", + "close_other_tabs": "Закрити інші вкладки", + "close_right_tabs": "Закрити вкладки праворуч", + "close_all_tabs": "Закрити всі вкладки", + "reopen_last_tab": "Відкрити останню закриту вкладку", + "move_tab_to_new_window": "Перемістити цю вкладку в нове вікно", + "copy_tab_to_new_window": "Скопіювати цю вкладку в нове вікно", + "new_tab": "Нова вкладка" + }, + "toc": { + "table_of_contents": "Зміст", + "options": "Параметри" + }, + "watched_file_update_status": { + "file_last_modified": "Файл востаннє змінювався .", + "upload_modified_file": "Завантажити змінений файл", + "ignore_this_change": "Ігнорувати цю зміну" + }, + "app_context": { + "please_wait_for_save": "Зачекайте кілька секунд, поки завершиться збереження, а потім спробуйте ще раз." + }, + "note_create": { + "duplicated": "Нотатку \"{{title}}\" продубльовано." + }, + "image": { + "copied-to-clipboard": "Посилання на зображення скопійовано в буфер обміну. Його можна вставити в будь-яку текстову нотатку.", + "cannot-copy": "Не вдалося скопіювати посилання на зображення в буфер обміну." + }, + "frontend_script_api": { + "async_warning": "Ви передаєте асинхронну функцію до `api.runOnBackend()`, яка, ймовірно, не працюватиме належним чином.\\nАбо зробіть функцію синхронною (видаливши ключове слово `async`), або використовуйте `api.runAsyncOnBackendWithManualTransactionHandling()`.", + "sync_warning": "Ви передаєте синхронну функцію до `api.runAsyncOnBackendWithManualTransactionHandling()`,\\nхоча, ймовірно, вам слід використовувати `api.runOnBackend()` замість цього." + }, + "ws": { + "sync-check-failed": "Перевірка синхронізації не вдалася!", + "consistency-checks-failed": "Перевірка узгодженості не вдалася! Див. logs для отримання інформації.", + "encountered-error": "Виникла помилка \"{{message}}\", перевірте консоль." + }, + "hoisted_note": { + "confirm_unhoisting": "Запитана нотатка '{{requestedNote}}' знаходиться поза піддеревом закріплених нотаток '{{hoistedNote}}', і вам потрібно відкріпити нотатку, щоб отримати до неї доступ. Ви хочете продовжити відкріплення?" + }, + "electron_context_menu": { + "add-term-to-dictionary": "Додати \"{{term}}\" до словника", + "cut": "Вирізати", + "copy": "Копіювати", + "copy-link": "Копіювати посилання", + "paste": "Вставити", + "paste-as-plain-text": "Вставити як звичайний текст", + "search_online": "Пошук за запитом \"{{term}}\" за допомогою {{search Engine}}" + }, + "image_context_menu": { + "copy_reference_to_clipboard": "Копіювати посилання в буфер обміну", + "copy_image_to_clipboard": "Копіювати зображення в буфер обміну" + }, + "link_context_menu": { + "open_note_in_new_tab": "Відкрити нотатку в новій вкладці", + "open_note_in_new_split": "Відкрити ноту в новому розділі", + "open_note_in_new_window": "Відкрити нотатку в новому вікні", + "open_note_in_popup": "Швидке редагування" + }, + "electron_integration": { + "desktop-application": "Настільний додаток", + "native-title-bar": "Нативний рядок заголовка", + "native-title-bar-description": "У Windows та macOS вимкнення рядка заголовка робить програму компактнішою. У Linux увімкнення рядка заголовка краще інтегрується з рештою системи.", + "background-effects": "Увімкнення фонових ефектів (лише для Windows 11)", + "background-effects-description": "Ефект слюди додає розмитий, стильний фон вікнам програм, створюючи глибину та сучасний вигляд.", + "restart-app-button": "Перезапустіть програму, щоб переглянути зміни", + "zoom-factor": "Коефіцієнт масштабування" + }, + "note_autocomplete": { + "search-for": "Пошук за запитом \"{{term}}\"", + "create-note": "Створити та зв'язати дочірню нотатку \"{{term}}\"", + "insert-external-link": "Вставити зовнішнє посилання на \"{{term}}\"", + "clear-text-field": "Очистити текстове поле", + "show-recent-notes": "Показати останні нотатки", + "full-text-search": "Повнотекстовий пошук" + }, + "note_tooltip": { + "note-has-been-deleted": "Нотатку видалено.", + "quick-edit": "Швидке редагування" + }, + "geo-map": { + "create-child-note-title": "Створіть нову дочірню нотатку та додайте її до карти", + "create-child-note-instruction": "Клацніть на карті, щоб створити нову нотатку в цьому місці, або натисніть Escape, щоб закрити.", + "unable-to-load-map": "Не вдалося завантажити карту." + }, + "geo-map-context": { + "open-location": "Відкрите місцезнаходження", + "remove-from-map": "Видалити з карти", + "add-note": "Додати маркер у цьому місці" + }, + "help-button": { + "title": "Відкрийте відповідну сторінку довідки" + }, + "duration": { + "seconds": "Секунди", + "minutes": "Хвилини", + "hours": "Години", + "days": "Дні" + }, + "share": { + "title": "Налаштування спільного доступу", + "redirect_bare_domain": "Перенаправити домен без доменного імені на сторінку Поділитися", + "redirect_bare_domain_description": "Перенаправляти анонімних користувачів на сторінку спільного доступу замість відображення сторінки входу", + "check_share_root": "Перевірити стан кореневого каталогу", + "share_root_found": "Коренева нотатка спільного доступу '{{noteTitle}}' готова", + "share_root_not_found": "Нотатки з міткою #shareRoot не знайдено", + "share_root_not_shared": "Нотатка '{{noteTitle}}' має мітку #shareRoot, але не є спільною", + "show_login_link": "Показати посилання для входу у спільних нотатках", + "show_login_link_description": "Додати посилання для входу до нижнього колонтитула у спільних нотатках" + }, + "time_selector": { + "invalid_input": "Введене значення часу не є дійсним числом.", + "minimum_input": "Введене значення часу має бути щонайменше {{minimumSeconds}} секунд." + }, + "tasks": { + "due": { + "today": "Сьогодні", + "tomorrow": "Завтра", + "yesterday": "Вчора" + } + }, + "content_widget": { + "unknown_widget": "Невідомий віджет для \"{{id}}\"." + }, + "note_language": { + "not_set": "Не встановлено", + "configure-languages": "Налаштувати мови..." + }, + "content_language": { + "title": "Мови контенту", + "description": "Виберіть одну або кілька мов, які мають відображатися у списку мов у розділі Основні властивості текстової нотатки, доступної лише для читання або редагування. Це дозволить використовувати такі функції, як перевірка орфографії або підтримка письма справа наліво." + }, + "switch_layout_button": { + "title_vertical": "Перемістити панель редагування вниз", + "title_horizontal": "Перемістити панель редагування ліворуч" + }, + "toggle_read_only_button": { + "unlock-editing": "Розблокувати редагування", + "lock-editing": "Заблокувати редагування" + }, + "png_export_button": { + "button_title": "Експорт діаграми у форматі PNG" + }, + "svg": { + "export_to_png": "Не вдалося експортувати діаграму у формат PNG." + }, + "code_theme": { + "title": "Зовнішній вигляд", + "word_wrapping": "Перенос слів", + "color-scheme": "Колірна схема" + }, + "cpu_arch_warning": { + "title": "Будь ласка, завантажте версію ARM64", + "message_macos": "TriliumNext зараз працює під керуванням версії Rosetta 2, що означає, що ви використовуєте версію Intel (x64) на Apple Silicon Mac. Це суттєво вплине на продуктивність та час роботи від батареї.", + "message_windows": "TriliumNext зараз використовує емуляцію, а це означає, що ви використовуєте версію Intel (x64) на пристрої Windows на ARM. Це суттєво вплине на продуктивність та час роботи від батареї.", + "recommendation": "Для найкращого досвіду, будь ласка, завантажте рідну версію TriliumNext для ARM64 з нашої сторінки релізів.", + "download_link": "Завантажити Нативну версію", + "continue_anyway": "Продовжити все одно", + "dont_show_again": "Не показувати це попередження більше" + }, + "editorfeatures": { + "title": "Особливості", + "emoji_completion_enabled": "Увімкнути автозаповнення емодзі", + "note_completion_enabled": "Увімкнути автозаповнення нотаток" + }, + "table_view": { + "new-row": "Новий рядок", + "new-column": "Новий стовпець", + "sort-column-by": "Сортувати за \"{{title}}\"", + "sort-column-ascending": "Зростаючий", + "sort-column-descending": "Спадання", + "sort-column-clear": "Очистити сортування", + "hide-column": "Приховати стовпець \"{{title}}\"", + "show-hide-columns": "Показати/приховати стовпці", + "row-insert-above": "Вставити рядок вище", + "row-insert-below": "Вставити рядок нижче", + "row-insert-child": "Вставити дочірню нотатку", + "add-column-to-the-left": "Додати стовпець ліворуч", + "add-column-to-the-right": "Додати стовпець праворуч", + "edit-column": "Редагувати стовпець", + "delete_column_confirmation": "Ви впевнені, що хочете видалити цей стовпець? Відповідний атрибут буде видалено з усіх нотаток.", + "delete-column": "Видалити стовпець", + "new-column-label": "Мітка", + "new-column-relation": "Зв'язок" + }, + "book_properties_config": { + "hide-weekends": "Приховати вихідні", + "display-week-numbers": "Відображення номерів тижнів", + "map-style": "Стиль карти:", + "max-nesting-depth": "Максимальна глибина вкладення:", + "raster": "Растр", + "vector_light": "Вектор (Світла)", + "vector_dark": "Вектор (Темна)", + "show-scale": "Показати масштаб" + }, + "shortcuts": { + "keyboard_shortcuts": "Комбінації клавіш", + "multiple_shortcuts": "Кілька комбінацій клавіш для однієї й тієї ж дії можна розділяти комами.", + "electron_documentation": "Див. документацію Electron для отримання інформації про доступні модифікатори та коди клавіш.", + "type_text_to_filter": "Введіть текст, щоб відфільтрувати комбінації клавіш...", + "action_name": "Назва дії", + "shortcuts": "Комбінація клавіш", + "default_shortcuts": "Стандартні комбінації клавіш", + "description": "Опис", + "reload_app": "Перезавантажте програму, щоб застосувати зміни", + "set_all_to_default": "Встановити всі комбінації клавіш за замовчуванням", + "confirm_reset": "Ви дійсно хочете скинути всі комбінації клавіш до значень за замовчуванням?" + }, + "spellcheck": { + "title": "Перевірка орфографії", + "description": "Ці параметри застосовуються лише для збірок для ПК, браузери використовуватимуть власну вбудовану перевірку орфографії.", + "enable": "Увімкнути перевірку орфографії", + "language_code_label": "Код(и) мови", + "language_code_placeholder": "наприклад, \"en-US\", \"de-AT\"", + "multiple_languages_info": "Кілька мов можна розділяти комами, наприклад, \"en-US, de-DE, cs\". ", + "available_language_codes_label": "Доступні коди мови:", + "restart-required": "Зміни в параметрах перевірки орфографії набудуть чинності після перезапуску програми." + }, + "sync_2": { + "config_title": "Конфігурація синхронізації", + "server_address": "Адреса екземпляра сервера", + "timeout": "Тайм-аут синхронізації", + "timeout_unit": "мілісекунди", + "proxy_label": "Синхронізація проксі-сервера (необов'язково)", + "note": "Нотатка", + "note_description": "Якщо залишити налаштування проксі-сервера порожнім, буде використано системний проксі-сервер (стосується лише збірки для ПК/електронної версії).", + "special_value_description": "Інше спеціальне значення — noproxy, яке змушує ігнорувати навіть системний проксі-сервер та враховує NODE_TLS_REJECT_UNAUTHORIZED.", + "save": "Зберегти", + "help": "Допомога", + "test_title": "Тест синхронізації", + "test_description": "Це перевірить з’єднання та встановлення зв’язку із сервером синхронізації. Якщо сервер синхронізації не ініціалізовано, це налаштує його на синхронізацію з локальним документом.", + "test_button": "Тест синхронізації", + "handshake_failed": "Не вдалося підтвердити синхронізацію з сервером, помилка: {{message}}" + }, + "api_log": { + "close": "Закрити" + }, + "attachment_detail_2": { + "will_be_deleted_in": "Це вкладення буде автоматично видалено через {{time}}", + "will_be_deleted_soon": "Це вкладення незабаром буде автоматично видалено", + "deletion_reason": ", оскільки вкладення не має посилання у вмісті нотатки. Щоб запобігти видаленню, додайте посилання на вкладення назад у вміст або перетворіть вкладення на нотатку.", + "role_and_size": "Роль: {{role}}, Розмір: {{size}}", + "link_copied": "Посилання на вкладення скопійовано в буфер обміну.", + "unrecognized_role": "Нерозпізнана роль вкладення '{{role}}'." + }, + "bookmark_switch": { + "bookmark": "Закладка", + "bookmark_this_note": "Додати цю нотатку до закладок на лівій бічній панелі", + "remove_bookmark": "Видалити закладку" + }, + "editability_select": { + "auto": "Авто", + "read_only": "Тільки для читання", + "always_editable": "Завжди доступно для редагування", + "note_is_editable": "Нотатку можна редагувати, якщо вона не надто довга.", + "note_is_read_only": "Нотатка доступна лише для читання, але її можна редагувати одним натисканням кнопки.", + "note_is_always_editable": "Нотатку завжди можна редагувати, незалежно від її довжини." + }, + "note-map": { + "button-link-map": "Карта посилань", + "button-tree-map": "Карта дерева" + }, + "tree-context-menu": { + "open-in-a-new-tab": "Відкрити в новій вкладці Ctrl+Клік", + "open-in-a-new-split": "Відкрити в новому розділі", + "insert-note-after": "Вставити нотатку після", + "insert-child-note": "Вставити дочірню нотатку", + "delete": "Видалити", + "search-in-subtree": "Пошук у піддереві", + "hoist-note": "Закріпити нотатку", + "unhoist-note": "Відкріпити нотатку", + "edit-branch-prefix": "Редагувати префікс гілки", + "advanced": "Розширені", + "expand-subtree": "Розгорнути піддерево", + "collapse-subtree": "Згорнути піддерево", + "sort-by": "Сортувати за...", + "recent-changes-in-subtree": "Останні зміни в піддереві", + "convert-to-attachment": "Конвертувати у вкладення", + "copy-note-path-to-clipboard": "Копіювати шлях до нотатки в буфер обміну", + "protect-subtree": "Захистити піддерево", + "unprotect-subtree": "Зняти захист піддерева", + "copy-clone": "Копіювати / клонувати", + "clone-to": "Клонувати до...", + "cut": "Вирізати", + "move-to": "Перемістити до...", + "paste-into": "Вставити в", + "paste-after": "Вставити після", + "duplicate": "Дублікат", + "export": "Експорт", + "import-into-note": "Імпортувати в нотатку", + "apply-bulk-actions": "Застосувати масові дії", + "converted-to-attachments": "({{count}}) нотаток перетворено на вкладення.", + "convert-to-attachment-confirm": "Ви впевнені, що хочете конвертувати вибрані нотатки у вкладення до їхніх батьківських нотаток?", + "open-in-popup": "Швидке редагування" + }, + "shared_info": { + "shared_publicly": "Ця нотатка опублікована на", + "shared_locally": "Цю нотатку опубліковано локально на", + "help_link": "Щоб отримати допомогу, відвідайте вікі." + }, + "note_types": { + "text": "Текст", + "code": "Код", + "saved-search": "Збережений пошук", + "relation-map": "Карта зв'язків", + "note-map": "Карта нотатки", + "book": "Колекція", + "mermaid-diagram": "Діаграма Mermaid", + "canvas": "Полотно", + "web-view": "Веб-перегляд", + "mind-map": "Карта розуму", + "file": "Файл", + "image": "Зображення", + "launcher": "Запуск", + "doc": "Doc", + "widget": "Віджет", + "confirm-change": "Не рекомендується змінювати тип нотатки, якщо її вміст не порожній. Ви все одно хочете продовжити?", + "geo-map": "Географічна карта", + "beta-feature": "Бета", + "ai-chat": "Чат AI", + "task-list": "Список завдань", + "new-feature": "Нова", + "collections": "Колекції", + "render-note": "Рендерінг нотатки" + }, + "protect_note": { + "toggle-on": "Захист нотатки", + "toggle-off": "Зняти захист нотатки", + "toggle-on-hint": "Нотатку не захищено, натисніть, щоб захистити її", + "toggle-off-hint": "Нотатку захищено, натисніть, щоб зняти захист" + }, + "shared_switch": { + "shared": "Спільне", + "toggle-on-title": "Поділитися нотаткою", + "toggle-off-title": "Скасувати спільний доступ до нотатки", + "shared-branch": "Ця нотатка існує лише як спільна нотатка, скасування спільного доступу призведе до її видалення. Ви хочете продовжити та таким чином видалити цю нотатку?", + "inherited": "Спільний доступ до нотатки тут не можна скасувати, оскільки вона успадкована від предка." + }, + "template_switch": { + "template": "Шаблон", + "toggle-on-hint": "Зробити нотатку шаблоном", + "toggle-off-hint": "Видалити нотатку як шаблон" + }, + "open-help-page": "Відкрити сторінку допомоги", + "find": { + "case_sensitive": "Чутливість до регістру", + "match_words": "Збіг слів", + "find_placeholder": "Знайти в тексті...", + "replace_placeholder": "Замінити на...", + "replace": "Замінити", + "replace_all": "Замінити все" + }, + "highlights_list_2": { + "title": "Список основних моментів", + "options": "Параметри" + }, + "table_context_menu": { + "delete_row": "Видалити рядок" + }, + "board_view": { + "delete-note": "Видалити нотатку", + "move-to": "Перемістити до", + "insert-above": "Вставити вище", + "insert-below": "Вставити нижче", + "delete-column": "Видалити стовпець", + "delete-column-confirmation": "Ви впевнені, що хочете видалити цей стовпець? Відповідний атрибут також буде видалено в нотатках під цим стовпцем.", + "new-item": "Новий елемент", + "add-column": "Додати стовпець" + }, + "command_palette": { + "tree-action-name": "Дерево: {{name}}", + "export_note_title": "Експорт нотатки", + "export_note_description": "Експорт поточної нотатки", + "show_attachments_title": "Показати вкладення", + "show_attachments_description": "Перегляд вкладення до нотатки", + "search_notes_title": "Пошук нотаток", + "search_notes_description": "Відкрити розширений пошук", + "search_subtree_title": "Пошук у піддереві", + "search_subtree_description": "Пошук у поточному піддереві", + "search_history_title": "Показати історію пошуку", + "search_history_description": "Переглянути попередні пошукові запити", + "configure_launch_bar_title": "Налаштувати панель запуску", + "configure_launch_bar_description": "Відкрийте конфігурацію панелі запуску, щоб додати або видалити елементи." + }, + "content_renderer": { + "open_externally": "Відкрити у зовнішній програмі" + }, + "call_to_action": { + "next_theme_title": "Спробуйте нову тему Trilium", + "next_theme_message": "Ви зараз використовуєте стару тему. Бажаєте спробувати нову?", + "next_theme_button": "Спробувати нову тему", + "background_effects_title": "Фонові ефекти тепер стабільні", + "background_effects_message": "На пристроях Windows фонові ефекти тепер повністю стабільні. Фонові ефекти додають колір інтерфейсу користувача, розмиваючи фон позаду нього. Цей метод також використовується в інших програмах, таких як Провідник Windows.", + "background_effects_button": "Увімкнути фонові ефекти", + "dismiss": "Відхилити" + }, + "settings": { + "related_settings": "Пов'язані налаштування" + }, + "settings_appearance": { + "related_code_blocks": "Колірна схема для блоків коду в текстових нотатках", + "related_code_notes": "Колірна схема для нотаток з кодом" + }, + "units": { + "percentage": "%" } } From 3abf5c65c69ce3e32901daae85190f39b6c04d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=20=D0=9A=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D1=82=D1=96=D0=BD?= Date: Fri, 22 Aug 2025 17:02:00 +0200 Subject: [PATCH 254/532] Translated using Weblate (Ukrainian) Currently translated at 100.0% (378 of 378 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/uk/ --- .../src/assets/translations/uk/server.json | 174 +++++++++--------- 1 file changed, 88 insertions(+), 86 deletions(-) diff --git a/apps/server/src/assets/translations/uk/server.json b/apps/server/src/assets/translations/uk/server.json index b99448967..ca1d037b8 100644 --- a/apps/server/src/assets/translations/uk/server.json +++ b/apps/server/src/assets/translations/uk/server.json @@ -3,25 +3,25 @@ "back-in-note-history": "Перейти до попередньої нотатки в історії", "forward-in-note-history": "Перейти до наступної нотатки в історії", "open-command-palette": "Відкрити палітру команд", - "scroll-to-active-note": "Прокрутити дерево нотаток до активної нотатки", + "scroll-to-active-note": "Прокрутити дерево до активної нотатки", "quick-search": "Активувати панель швидкого пошуку", "search-in-subtree": "Пошук нотаток в піддереві активної нотатки", - "expand-subtree": "Розкрити піддерево поточної нотатки", + "expand-subtree": "Розгорнути піддерево поточної нотатки", "collapse-tree": "Згорнути все дерево нотаток", - "open-jump-to-note-dialog": "Відкрити вікно \"Перейти до нотатки\"", + "open-jump-to-note-dialog": "Відкрити діалог \"Перейти до нотатки\"", "collapse-subtree": "Згорнути піддерево поточної нотатки", "sort-child-notes": "Сортувати дочірні нотатки", "creating-and-moving-notes": "Створення та переміщення нотаток", "create-note-after": "Створити нотатку після активної нотатки", "create-note-into": "Створити нотатку як дочірню до активної нотатки", - "create-note-into-inbox": "Створити нотатку у \"Вхідні\" (якщо визначено) або в щоденнику", + "create-note-into-inbox": "Створити нотатку у вхідні (якщо визначено) або денну нотатку", "delete-note": "Видалити нотатку", "move-note-up": "Перемістити нотатку вгору", "move-note-down": "Перемістити нотатку вниз", "move-note-up-in-hierarchy": "Перемістити нотатку вище в ієрархії", "move-note-down-in-hierarchy": "Перемістити нотатку вниз в ієрархії", "edit-note-title": "Перейти від дерева до деталей нотатки та редагувати заголовок", - "edit-branch-prefix": "Показати вікно \"Редагувати префікс гілки\"", + "edit-branch-prefix": "Показати діалог \"Редагувати префікс гілки\"", "clone-notes-to": "Клонувати вибрані нотатки", "move-notes-to": "Перемістити вибрані нотатки", "note-clipboard": "Буфер обміну нотатки", @@ -29,7 +29,7 @@ "paste-notes-from-clipboard": "Вставити нотатки з буферу обміну в активну нотатку", "cut-notes-to-clipboard": "Вирізати вибрані нотатки в буфер обміну", "select-all-notes-in-parent": "Вибрати всі нотатки з поточного рівня нотаток", - "add-note-above-to-the-selection": "Додати нотатку вище до виділення", + "add-note-above-to-the-selection": "Додати нотатку вище до вибраного", "add-note-below-to-selection": "Додати нотатку нижче до вибраного", "duplicate-subtree": "Дублювання піддерева", "tabs-and-windows": "Вкладки & Вікна", @@ -51,21 +51,21 @@ "ninth-tab": "Активувати дев'яту вкладку у списку", "last-tab": "Активувати останню вкладку у списку", "dialogs": "Діалоги", - "show-note-source": "Показати \"Джерело нотатки\"", - "show-options": "Відкрити \"Параметри\"", - "show-revisions": "Показати \"Версії нотаток\"", - "show-recent-changes": "Показати \"Останні зміни\"", - "show-sql-console": "Відкрити \"Консоль SQL\"", - "show-backend-log": "Відкрити \"Backend Log\"", + "show-note-source": "Показати діалог \"Джерело нотатки\"", + "show-options": "Відкрити сторінку \"Параметри\"", + "show-revisions": "Показати діалог \"Версії нотаток\"", + "show-recent-changes": "Показати діалог \"Останні зміни\"", + "show-sql-console": "Відкрити сторінку \"Консоль SQL\"", + "show-backend-log": "Відкрити сторінку \"Backend Log\"", "show-help": "Відкрити вбудований Посібник користувача", "show-cheatsheet": "Показати вікно зі стандартними діями клавіатури", "text-note-operations": "Дії з текстовими нотатками", - "add-link-to-text": "Відкрити діалогове вікно для додавання посилання до тексту", - "follow-link-under-cursor": "Перейдіть за посиланням, на якому знаходиться курсор", + "add-link-to-text": "Відкрити діалог для додавання посилання до тексту", + "follow-link-under-cursor": "Перехід за посиланням, на якому знаходиться курсор", "insert-date-and-time-to-text": "Вставити поточну дату & час у текст", "paste-markdown-into-text": "Вставити Markdown з буфера обміну в текстову нотатку", "cut-into-note": "Вирізати виділений фрагмент із поточної нотатки та створити піднотатку з виділеним текстом", - "add-include-note-to-text": "Відкрити вікно для додавання нотатки", + "add-include-note-to-text": "Відкрити діалог для додавання нотатки", "edit-readonly-note": "Редагувати нотатку, доступну тільки для читання", "attributes-labels-and-relations": "Атрибути (мітки & зв'язки)", "add-new-label": "Створити нову мітку", @@ -85,121 +85,122 @@ "toggle-right-pane": "Увімкнути відображення правої панелі, яка містить Зміст та Основні моменти", "print-active-note": "Друк активної нотатки", "open-note-externally": "Відкрити нотатку як файл у програмі за замовчуванням", - "render-active-note": "Відтворити (повторно відтворити) активну нотатку", + "render-active-note": "Рендеринг (перерендерінг) активної нотатки", "run-active-note": "Виконати активний код JavaScript (frontend/backend) нотатки з кодом", "toggle-note-hoisting": "Увімкнути хостинг активної нотатки", - "unhoist": "Зняти з будь-якого місця", + "unhoist": "Відкріпити з будь-якого місця", "reload-frontend-app": "Перезавантажити інтерфейс", "open-dev-tools": "Відкрити інструменти розробника", "find-in-text": "Увімкнути панель пошуку", - "toggle-left-note-tree-panel": "Увімкнути ліву панель (дерево нотаток)", + "toggle-left-note-tree-panel": "Увімкнути ліву панель (дерево нотатки)", "toggle-full-screen": "Увімкнути повноекранний режим", "zoom-out": "Зменшити масштаб", "zoom-in": "Збільшити масштаб", "note-navigation": "Навігація по нотатках", - "reset-zoom-level": "Скинути масштабування", + "reset-zoom-level": "Скинути рівень масштабування", "copy-without-formatting": "Копіювати виділений текст без форматування", "force-save-revision": "Примусове створення/збереження нової версії активної нотатки", "toggle-book-properties": "Увімкнути Властивості Колекції", "toggle-classic-editor-toolbar": "Увімкнути вкладку Форматування для редактора з фіксованою панеллю інструментів", "export-as-pdf": "Експортувати поточну нотатку в PDF", - "toggle-zen-mode": "Вмикає/вимикає Дзен-режим (мінімальний інтерфейс для більш сфокусованого редагування)" + "toggle-zen-mode": "Вмикає/вимикає Дзен-режим (мінімальний інтерфейс сфокусованого редагування)" }, "keyboard_action_names": { - "back-in-note-history": "Назад до Історії Нотаток", - "forward-in-note-history": "Вперед в Історія Нотаток", + "back-in-note-history": "Назад в Історії нотаток", + "forward-in-note-history": "Вперед в Історії нотаток", "jump-to-note": "Перейти до...", "command-palette": "Палітра Команд", - "scroll-to-active-note": "Прокрутити до Активної Нотатки", + "scroll-to-active-note": "Прокрутити до Активної нотатки", "quick-search": "Швидкий Пошук", "search-in-subtree": "Пошук у Піддереві", "expand-subtree": "Розгорнути Піддерево", "collapse-tree": "Згорнути Дерево", "collapse-subtree": "Згорнути Піддерево", - "sort-child-notes": "Сортувати Дочірні Нотатки", + "sort-child-notes": "Сортувати Дочірні нотатки", "create-note-after": "Створити Нотатку після", "create-note-into": "Створити Нотатку в", - "create-note-into-inbox": "Створити Нотатку у \"Вхідні\"", + "create-note-into-inbox": "Створити Нотатку у Вхідні", "delete-notes": "Видалити Нотатки", "move-note-up": "Перемістити Нотатку вгору", "move-note-down": "Перемістити Нотатку вниз", - "move-note-up-in-hierarchy": "Перемістити Нотатку вгору в ієрархії", - "move-note-down-in-hierarchy": "Перемістити Нотатку вниз в ієрархії", - "edit-note-title": "Редагувати Заголовок Нотатки", - "edit-branch-prefix": "Редагувати Префікс Гілки", - "clone-notes-to": "Клонувати Нотатки до", - "move-notes-to": "Перемістити Нотатки до", - "copy-notes-to-clipboard": "Копіювати Нотатки в Буфер обміну", - "paste-notes-from-clipboard": "Вставити Нотатки з Буфера обміну", - "cut-notes-to-clipboard": "Вирізати Нотатки в Буфер обміну", - "select-all-notes-in-parent": "Вибрати всі Нотатки в \"Батьківські\"", - "add-note-above-to-selection": "Додати Нотатку вище до виділення", - "add-note-below-to-selection": "Додати Нотатку нижче до виділення", + "move-note-up-in-hierarchy": "Перемістити Нотатку вгору в Ієрархії", + "move-note-down-in-hierarchy": "Перемістити Нотатку вниз в Ієрархії", + "edit-note-title": "Редагувати Заголовок нотатки", + "edit-branch-prefix": "Редагувати префікс Гілки", + "clone-notes-to": "Клонувати нотатки до", + "move-notes-to": "Перемістити нотатки до", + "copy-notes-to-clipboard": "Копіювати нотатки в Буфер обміну", + "paste-notes-from-clipboard": "Вставити нотатки з Буфера обміну", + "cut-notes-to-clipboard": "Вирізати нотатки в Буфер обміну", + "select-all-notes-in-parent": "Вибрати всі нотатки в Батьківські", + "add-note-above-to-selection": "Додати Нотатку вище до вибраного", + "add-note-below-to-selection": "Додати Нотатку нижче до вибраного", "duplicate-subtree": "Дублікат Піддерева", - "open-new-tab": "Відкрити Нову Вкладку", - "close-active-tab": "Закрити Активну Вкладку", - "reopen-last-tab": "Відкрити Останню Вкладку", - "activate-next-tab": "Активувати Наступну Вкладку", - "activate-previous-tab": "Активувати Попередню Вкладку", - "open-new-window": "Відкрити Нове Вікно", + "open-new-tab": "Відкрити Нову вкладку", + "close-active-tab": "Закрити активну вкладку", + "reopen-last-tab": "Відкрити останню вкладку", + "activate-next-tab": "Активувати наступну вкладку", + "activate-previous-tab": "Активувати попередню вкладку", + "open-new-window": "Відкрити Нове вікно", "toggle-system-tray-icon": "Увімкнути Значок системного трея", "toggle-zen-mode": "Увімкнути Дзен-режим", "switch-to-first-tab": "Перейти до першої вкладки", "switch-to-second-tab": "Перейти до другої вкладки", "switch-to-third-tab": "Перейти до третьої вкладки", "switch-to-fourth-tab": "Перейти до четвертої вкладки", - "switch-to-fifth-tab": "Перейти на п'яту вкладку", + "switch-to-fifth-tab": "Перейти до п'ятої вкладки", "switch-to-sixth-tab": "Перейти до шостої вкладки", "switch-to-seventh-tab": "Перейти до сьомої вкладки", "switch-to-eighth-tab": "Перейти до восьмої вкладки", "find-in-text": "Знайти в тексті", - "toggle-left-pane": "Увімкнути ліву панель", - "toggle-full-screen": "Увімкнути повноекранний режим", + "toggle-left-pane": "Увімкнути Ліву панель", + "toggle-full-screen": "Увімкнути Повноекранний режим", "zoom-out": "Зменшити масштаб", "zoom-in": "Збільшити масштаб", - "reset-zoom-level": "Скинути масштабування", + "reset-zoom-level": "Скинути Масштабування", "copy-without-formatting": "Копіювати без форматування", "force-save-revision": "Примусове збереження версії", - "switch-to-ninth-tab": "Перейти на дев'яту вкладку", + "switch-to-ninth-tab": "Перейти до дев'ятої вкладки", "switch-to-last-tab": "Перейти до останньої вкладки", - "show-note-source": "Показати Джерело Нотатки", + "show-note-source": "Показати Джерело нотатки", "show-options": "Показати Параметри", "show-revisions": "Показати Версії", - "show-recent-changes": "Показати Останні Зміни", - "show-sql-console": "Показати консоль SQL", + "show-recent-changes": "Показати Останні зміни", + "show-sql-console": "Показати Консоль SQL", "show-backend-log": "Показати Backend Log", - "show-help": "Показати Довідку", + "show-help": "Показати Допомогу", "show-cheatsheet": "Показати Шпаргалку", - "add-link-to-text": "Додати посилання до тексту", - "follow-link-under-cursor": "Перейти за посиланням під курсором", + "add-link-to-text": "Додати Посилання до тексту", + "follow-link-under-cursor": "Перейти за Посиланням під курсором", "insert-date-and-time-to-text": "Вставити Дату та Час у текст", "paste-markdown-into-text": "Вставити Markdown у текст", - "cut-into-note": "Вирізати у Нотатку", - "add-include-note-to-text": "Додати включену Нотатку до тексту", + "cut-into-note": "Вирізати у нотатку", + "add-include-note-to-text": "Додати включену нотатку до тексту", "edit-read-only-note": "Редагувати нотатку лише для читання", - "add-new-label": "Додати Нову Мітку", - "add-new-relation": "Додати Новий Зв'язок", - "toggle-ribbon-tab-classic-editor": "Включити вкладку стрічки \"Класичний Редактор\"", - "toggle-ribbon-tab-basic-properties": "Включити вкладку стрічки \"Основні властивості\"", - "toggle-ribbon-tab-book-properties": "Включити вкладку стрічки \"Властивості Книги\"", - "toggle-ribbon-tab-file-properties": "Включити вкладку стрічки \"Властивості файлу\"", - "toggle-ribbon-tab-image-properties": "Включити вкладку стрічки \"Властивості Зображення\"", - "toggle-ribbon-tab-owned-attributes": "Включити вкладку стрічки \"Власні атрибути\"", - "toggle-ribbon-tab-inherited-attributes": "Включити вкладку стрічки \"Успадковані Атрибути\"", - "toggle-ribbon-tab-promoted-attributes": "Включити вкладку стрічки \"Просунуті Атрибути\"", - "toggle-ribbon-tab-note-map": "Включити вкладку стрічки \"Карта Нотатки\"", - "toggle-ribbon-tab-note-info": "Включити вкладку стрічки \"Інформація про Нотатку\"", - "toggle-ribbon-tab-note-paths": "Включити вкладку стрічки \"Шляхи Нотатки\"", - "toggle-ribbon-tab-similar-notes": "Включити вкладку стрічки \"Схожі Нотатки\"", - "toggle-right-pane": "Включити праву панель", + "add-new-label": "Додати Нову мітку", + "add-new-relation": "Додати Новий зв'язок", + "toggle-ribbon-tab-classic-editor": "Включити Вкладку стрічки Класичний Редактор", + "toggle-ribbon-tab-basic-properties": "Включити Вкладку стрічки Основні властивості", + "toggle-ribbon-tab-book-properties": "Включити вкладку стрічки Властивості книги", + "toggle-ribbon-tab-file-properties": "Включити вкладку стрічки Властивості Файлу", + "toggle-ribbon-tab-image-properties": "Включити вкладку стрічки Властивості Зображення", + "toggle-ribbon-tab-owned-attributes": "Включити вкладку стрічки Власні Атрибути", + "toggle-ribbon-tab-inherited-attributes": "Включити вкладку стрічки Успадковані Атрибути", + "toggle-ribbon-tab-promoted-attributes": "Включити вкладку стрічки Просунуті Атрибути", + "toggle-ribbon-tab-note-map": "Включити вкладку стрічки Карта Нотатки", + "toggle-ribbon-tab-note-info": "Включити вкладку стрічки Інформація про Нотатку", + "toggle-ribbon-tab-note-paths": "Включити вкладку стрічки Шляхи Нотатки", + "toggle-ribbon-tab-similar-notes": "Включити вкладку стрічки Схожі Нотатки", + "toggle-right-pane": "Включити Праву панель", "print-active-note": "Друк Активної Нотатки", - "export-active-note-as-pdf": "Експорт активної нотатки у PDF", + "export-active-note-as-pdf": "Експорт Активної нотатки у PDF", "open-note-externally": "Відкрити Нотатку зовнішньою програмою", - "render-active-note": "Відтворити Активну Нотатку", + "render-active-note": "Рендеринг Активної Нотатки", "run-active-note": "Запустити Активну Нотатку", "toggle-note-hoisting": "Включити хостинг нотатки", - "reload-frontend-app": "Перезавантажити інтерфейс програми", - "open-developer-tools": "Відкрити Інструменти розробника" + "reload-frontend-app": "Перезавантажити Інтерфейс програми", + "open-developer-tools": "Відкрити Інструменти розробника", + "unhoist-note": "Відкріпити Нотатку" }, "login": { "title": "Увійти", @@ -212,7 +213,7 @@ "sign_in_with_sso": "Увійти за допомогою {{ ssoIssuerName }}" }, "set_password": { - "title": "Встановити пароль", + "title": "Встановити Пароль", "heading": "Встановити пароль", "description": "Перш ніж почати користуватися Trilium з веб-сайту, вам потрібно спочатку встановити пароль. Потім ви будете використовувати цей пароль для входу в систему.", "password": "Пароль", @@ -305,29 +306,29 @@ }, "hidden-subtree": { "root-title": "Приховані Нотатки", - "search-history-title": "Історія Пошуку", + "search-history-title": "Історія пошуку", "note-map-title": "Карта Нотатки", "sql-console-history-title": "Історія консолі SQL", "shared-notes-title": "Спільні Нотатки", "bulk-action-title": "Масова дія", "backend-log-title": "Backend Log", "user-hidden-title": "Прихований користувач", - "launch-bar-templates-title": "Шаблони Панелі запуску", - "base-abstract-launcher-title": "Базовий Абстрактний лаунчер", - "command-launcher-title": "Командний лаунчер", - "note-launcher-title": "Лаунчер нотаток", - "script-launcher-title": "Запуск скриптів", + "launch-bar-templates-title": "Запуск Шаблони панелей", + "base-abstract-launcher-title": "Базовий Лаунчер Abstract", + "command-launcher-title": "Лаунчер Command", + "note-launcher-title": "Лаунчер Note", + "script-launcher-title": "Лаунчер Script", "built-in-widget-title": "Вбудований віджет", "custom-widget-title": "Користувацький віджет", "launch-bar-title": "Панель запуску", - "available-launchers-title": "Доступні лаунчери", + "available-launchers-title": "Доступні Лаунчери", "go-to-previous-note-title": "Перейти до попередньої нотатки", "go-to-next-note-title": "Перейти до наступної нотатки", - "new-note-title": "Нова нотатка", + "new-note-title": "Нова Нотатка", "search-notes-title": "Пошук нотаток", "jump-to-note-title": "Перейти до...", "calendar-title": "Календар", - "recent-changes-title": "Останні зміни", + "recent-changes-title": "Останні Зміни", "bookmarks-title": "Закладки", "open-today-journal-note-title": "Відкрити Щоденник за сьогодні", "quick-search-title": "Швидкий пошук", @@ -353,7 +354,8 @@ "visible-launchers-title": "Видимі лаунчери", "user-guide": "Посібник користувача", "localization": "Мова & регіон", - "inbox-title": "Вхідні" + "inbox-title": "Вхідні", + "spacer-title": "Роздільник" }, "notes": { "new-note": "Нова нотатка", From e72298f0b46eef261abfbe1907cf5d28a6527abb Mon Sep 17 00:00:00 2001 From: Astryd Park Date: Thu, 21 Aug 2025 16:37:23 +0200 Subject: [PATCH 255/532] Translated using Weblate (Korean) Currently translated at 0.2% (1 of 378 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/ko/ --- apps/server/src/assets/translations/ko/server.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/server/src/assets/translations/ko/server.json b/apps/server/src/assets/translations/ko/server.json index 0967ef424..24c86869b 100644 --- a/apps/server/src/assets/translations/ko/server.json +++ b/apps/server/src/assets/translations/ko/server.json @@ -1 +1,5 @@ -{} +{ + "keyboard_actions": { + "back-in-note-history": "이전으로 돌아가기" + } +} From e5235e7f22dfb364fffb28e5975153f88bff4c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=20=D0=9A=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D1=82=D1=96=D0=BD?= Date: Fri, 22 Aug 2025 18:43:40 +0200 Subject: [PATCH 256/532] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1560 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/uk/ --- .../src/translations/uk/translation.json | 154 +++++++++--------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/apps/client/src/translations/uk/translation.json b/apps/client/src/translations/uk/translation.json index 447fca001..efb01f942 100644 --- a/apps/client/src/translations/uk/translation.json +++ b/apps/client/src/translations/uk/translation.json @@ -34,22 +34,22 @@ "switch_to_mobile_version": "Перейти на мобільну версію", "switch_to_desktop_version": "Перейти на версію для ПК", "zoom": "Масштаб", - "toggle_fullscreen": "Увімкнути повноекранний режим", + "toggle_fullscreen": "Увімкнути Повноекранний режим", "zoom_out": "Зменшити масштаб", "reset_zoom_level": "Скинути Масштабування", "zoom_in": "Збільшити масштаб", - "configure_launchbar": "Налаштувати панель запуску", + "configure_launchbar": "Налаштувати Панель запуску", "show_shared_notes_subtree": "Показати піддерево спільних нотаток", "advanced": "Розширені", - "open_dev_tools": "Відкрити інструменти розробника", + "open_dev_tools": "Відкрити Інструменти розробника", "open_sql_console": "Відкрити консоль SQL", "open_sql_console_history": "Відкрити історію консолі SQL", "open_search_history": "Відкрити історію пошуку", "show_backend_log": "Показати Backend Log", "reload_hint": "Перезавантаження може допомогти з деякими візуальними збоями без перезавантаження всієї програми.", - "reload_frontend": "Перезавантажити інтерфейс", + "reload_frontend": "Перезавантажити Інтерфейс", "show_hidden_subtree": "Показати приховане піддерево", - "show_help": "Показати довідку", + "show_help": "Показати Довідку", "logout": "Вийти", "show-cheatsheet": "Показати Шпаргалку", "toggle-zen-mode": "Дзен-режим" @@ -102,7 +102,7 @@ "clipboard": { "copied": "Нотатку(-и) було скопійовано в буфер.", "copy_failed": "Не вдалося скопіювати в буфер через проблеми з дозволами.", - "copy_success": "Скопійовано в буфер.", + "copy_success": "Скопійовано в буфер обміну.", "cut": "Нотатку(и) було вирізано в буфер обміну." }, "entrypoints": { @@ -131,7 +131,7 @@ "duplicate-launcher": "Дублікат програми запуску " }, "editable-text": { - "auto-detect-language": "Автовизначена" + "auto-detect-language": "Автовизначено" }, "highlighting": { "color-scheme": "Схема кольорів", @@ -139,7 +139,7 @@ "description": "Керує підсвічуванням синтаксису для блоків коду всередині текстових нотаток, на нотатки з кодом це не вплине." }, "code_block": { - "copy_title": "Скопіювати в буфер", + "copy_title": "Скопіювати в буфер обміну", "word_wrapping": "Перенос слів", "theme_none": "Без підсвічування синтаксису", "theme_group_light": "Світлі теми", @@ -456,7 +456,7 @@ "sort_folders_first": "Папки (нотатки з дочірніми) слід сортувати зверху", "top": "зберегти задану нотатку зверху в батьківській (застосовується лише до відсортованих батьківських)", "hide_promoted_attributes": "Сховати просунуті атрибути для цієї нотатки", - "read_only": "редактор перебуває в режимі лише для читання. Працює лише для тексту та нотаток з кодом.", + "read_only": "редактор в режимі \"тільки для читання\". Працює лише для текстових та нотаток з кодом.", "auto_read_only_disabled": "текстові/кодові нотатки можна автоматично перевести в режим читання, якщо вони занадто великі. Ви можете вимкнути цю поведінку для кожної окремої нотатки, додавши до неї цю позначку", "app_css": "позначає CSS-нотатки, які завантажуються в програму Trilium і, таким чином, можуть бути використані для зміни зовнішнього вигляду Trilium.", "app_theme": "позначає CSS-нотатки, які є повноцінними темами Trilium і тому доступні в параметрах Trilium.", @@ -499,7 +499,7 @@ "execute_description": "Більш детальний опис поточного коду, що відображається разом із кнопкою виконання", "exclude_from_note_map": "Нотатки з цією міткою будуть приховані на Карті Нотатки", "new_notes_on_top": "Нові нотатки будуть створені зверху батьківської нотатки, а не знизу.", - "hide_highlight_widget": "Приховати віджет Списку виділення", + "hide_highlight_widget": "Приховати віджет Основні моменти", "run_on_note_creation": "виконується, коли нотатка створюється на серверній частині. Використовуйте цей зв'язок, якщо потрібно запустити скрипт для всіх нотаток, створених у певному піддереві. У такому випадку створіть його на батьківській нотатці піддерева та зробіть його успадковуваним. Нова нотатка, створена в піддереві (будь-якої глибини), запустить скрипт.", "run_on_child_note_creation": "виконується, коли створюється нова нотатка під нотаткою, де визначено цей зв'язок", "run_on_note_title_change": "виконується, коли змінюється заголовок нотатки (включає також створення нотатки)", @@ -509,7 +509,7 @@ "run_on_branch_creation": "виконується під час створення гілки. Гілка — це зв'язок між батьківською та дочірньою нотаткою та створюється, наприклад, під час клонування або переміщення нотатки.", "run_on_branch_change": "виконується, коли гілка оновлюється.", "run_on_branch_deletion": "виконується, коли гілку видаляють. Гілка — це зв'язок між батьківською та дочірньою нотатками та видаляється, наприклад, під час переміщення нотатки (стара гілка/посилання видаляється).", - "share_root": "позначає нотатку, яка подається на кореневому каталозі /share.", + "share_root": "позначає нотатку, яка подається /спільний корінь.", "run_on_attribute_creation": "виконується, коли для нотатки створюється новий атрибут, який визначає цей зв'язок", "run_on_attribute_change": " виконується, коли змінюється атрибут нотатки, яка визначає цей зв'язок. Це також спрацьовує, коли атрибут видаляється", "relation_template": "атрибути нотатки будуть успадковані навіть без зв'язку \"батьківський-дочірній\", вміст нотатки та піддерево будуть додані до екземпляра нотатки, якщо він порожній. Див. документацію для отримання детальної інформації.", @@ -521,14 +521,14 @@ "share_template": "Вбудована нотатка JavaScript, яка використовуватиметься як шаблон для відображення спільної нотатки. Повертатиметься до шаблону за замовчуванням. Розгляньте можливість використання 'share_hidden_from_tree'.", "share_favicon": "Нотатку до значка веб-сторінки, яку потрібно встановити на спільній сторінці. Зазвичай потрібно встановити її як спільний кореневий каталог і зробити успадковуваною. Нотатку до значка веб-сторінки також потрібно розмістити у спільному піддереві. Розгляньте можливість використання 'share_hidden_from_tree'.", "is_owned_by_note": "належить до нотатки", - "other_notes_with_name": "Інші нотатки з назвою {{attributeType}} \"{{attributeName}}\"", + "other_notes_with_name": "Інші нотатки {{attributeType}} з назвою \"{{attributeName}}\"", "and_more": "... та ще {{count}}.", "print_landscape": "Під час експорту в PDF змінює орієнтацію сторінки на альбомну замість портретної.", "print_page_size": "Під час експорту в PDF змінює розмір сторінки. Підтримувані значення: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", "color_type": "Колір" }, "multi_factor_authentication": { - "mfa_method": "Метод МФА", + "mfa_method": "Метод MFA", "title": "Багатофакторна автентифікація", "description": "Багатофакторна автентифікація (MFA) додає додатковий рівень безпеки до вашого облікового запису. Замість того, щоб просто вводити пароль для входу, MFA вимагає від вас надання одного або кількох додаткових доказів для підтвердження вашої особи. Таким чином, навіть якщо хтось отримає ваш пароль, він все одно не зможе отримати доступ до вашого облікового запису без другої інформації. Це як додати додатковий замок на двері, що значно ускладнює проникнення будь-кому іншому.

    Будь ласка, дотримуйтесь інструкцій нижче, щоб увімкнути MFA. Якщо ви неправильно налаштуєте, вхід буде здійснюватися лише за допомогою пароля.", "mfa_enabled": "Увімкнути багатофакторну автентифікацію", @@ -611,7 +611,7 @@ "label_name_title": "Дозволено використовувати буквено-цифрові символи, підкреслення та двокрапку.", "to_value": "до значення", "new_value_placeholder": "нове значення", - "help_text": "Для всіх нотаток, що збігаються, змініть значення існуючої мітки.", + "help_text": "Для всіх нотаток, що збігаються, змінити значення існуючої мітки.", "help_text_note": "Ви також можете викликати цей метод без значення, у такому випадку мітку буде присвоєно нотатці без значення." }, "delete_note": { @@ -666,7 +666,7 @@ "search_script": { "example_code": "// 1. попередня фільтрація за допомогою стандартного пошуку\nconst candidateNotes = api.searchForNotes(\"#journal\");\n\n// 2. застосування користувацьких критеріїв пошуку\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;", "title": "Пошуковий скрипт:", - "placeholder": "пошук нотатки за її назвою", + "placeholder": "пошук нотатки за назвою", "description1": "Пошуковий скрипт дозволяє визначати результати пошуку, запускаючи скрипт. Це забезпечує максимальну гнучкість, коли стандартного пошуку недостатньо.", "description2": "Скрипт пошуку має бути типу \"code\" та підтипу \"JavaScript backend\". Скрипт має повертати масив ідентифікаторів нотаток або нотатки.", "example_title": "Дивіться цей приклад:", @@ -688,23 +688,23 @@ "attachments_actions": { "open_externally": "Відкрити у зовнішній програмі", "open_externally_title": "Файл буде відкрито в зовнішній програмі та відстежуватися на наявність змін. Після цього ви зможете завантажити змінену версію назад до Trilium.", - "open_custom": "Відкрити користувацький", + "open_custom": "Відкрити як...", "open_custom_title": "Файл буде відкрито в зовнішній програмі та відстежуватися на наявність змін. Після цього ви зможете завантажити змінену версію назад до Trilium.", "download": "Завантажити", "rename_attachment": "Перейменувати вкладення", "upload_new_revision": "Завантажити нову версію", - "copy_link_to_clipboard": "Копіювати посилання в буфер обміну", + "copy_link_to_clipboard": "Копіювати посилання в Буфер обміну", "convert_attachment_into_note": "Перетворити вкладення на нотатку", "delete_attachment": "Видалити вкладення", - "upload_success": "Нову версію вкладеного файлу завантажено.", - "upload_failed": "Не вдалося завантажити нову версію вкладеного файлу.", + "upload_success": "Нову версію вкладення завантажено.", + "upload_failed": "Не вдалося завантажити нову версію вкладення.", "open_externally_detail_page": "Відкриття вкладення ззовні доступне лише зі сторінки з деталями, спочатку натисніть на деталі вкладення та повторіть дію.", "open_custom_client_only": "Налаштування відкриття вкладень можна виконати лише з клієнтської версії для ПК.", "delete_confirm": "Ви впевнені, що хочете видалити вкладення '{{title}}'?", "delete_success": "Вкладення '{{title}}' видалено.", "convert_confirm": "Ви впевнені, що хочете перетворити вкладення '{{title}}' на окрему нотатку?", "convert_success": "Вкладення '{{title}}' перетворено на нотатку.", - "enter_new_name": "Будь ласка, введіть назву нового вкладення" + "enter_new_name": "Введіть назву нового вкладення" }, "calendar": { "mon": "Пн", @@ -715,7 +715,7 @@ "sat": "Сб", "sun": "Нд", "cannot_find_day_note": "Не вдається знайти денну нотатку", - "cannot_find_week_note": "Не вдається знайти нотатку тижня", + "cannot_find_week_note": "Не вдається знайти тижневу нотатку", "january": "Січень", "febuary": "Лютий", "march": "Березень", @@ -733,16 +733,16 @@ "close_this_pane": "Закрити цю панель" }, "create_pane_button": { - "create_new_split": "Створити новий поділ" + "create_new_split": "Створити новий розділ" }, "edit_button": { "edit_this_note": "Редагувати цю нотатку" }, "show_toc_widget_button": { - "show_toc": "Показати зміст" + "show_toc": "Показати Зміст" }, "show_highlights_list_widget_button": { - "show_highlights_list": "Показати Список основних моментів" + "show_highlights_list": "Показати Список Основних моментів" }, "zen_mode": { "button_exit": "Вихід з Дзен-режиму" @@ -765,13 +765,13 @@ }, "note_actions": { "convert_into_attachment": "Перетворити на вкладення", - "re_render_note": "Повторно відобразити нотатку", + "re_render_note": "Перерендеринг Нотатки", "search_in_note": "Пошук у нотатці", "note_source": "Джерело нотатки", "note_attachments": "Вкладення нотатки", "open_note_externally": "Відкрити нотатку у зовнішній програмі", "open_note_externally_title": "Файл буде відкрито в зовнішній програмі та відстежуватися на наявність змін. Після цього ви зможете завантажити змінену версію назад до Trilium.", - "open_note_custom": "Відкрити нотатку користувача", + "open_note_custom": "Відкрити нотатку як...", "import_files": "Імпорт файлів", "export_note": "Експорт нотатки", "delete_note": "Видалити нотатку", @@ -790,17 +790,17 @@ "inactive": "Натисніть, щоб увійти до захищеного сеансу" }, "revisions_button": { - "note_revisions": "Версії нотатки" + "note_revisions": "Версії Нотатки" }, "update_available": { "update_available": "Доступне оновлення" }, "note_launcher": { - "this_launcher_doesnt_define_target_note": "Цей лаунчер не визначає цільову примітку." + "this_launcher_doesnt_define_target_note": "Цей Лаунчер не визначає цільову нотатку." }, "code_buttons": { "execute_button_title": "Виконати скрипт", - "trilium_api_docs_button_title": "Відкрити документацію API Trilium", + "trilium_api_docs_button_title": "Відкрити документацію Trilium API", "save_to_note_button_title": "Зберегти до нотатки", "opening_api_docs_message": "Відкриття документації API...", "sql_console_saved_message": "Нотатку консолі SQL збережено в {{note_path}}" @@ -842,8 +842,8 @@ }, "basic_properties": { "note_type": "Тип нотатки", - "editable": "Можна редагувати", - "basic_properties": "Основні властивості", + "editable": "Редагув.", + "basic_properties": "Основні Властивості", "language": "Мова" }, "book_properties": { @@ -851,10 +851,10 @@ "grid": "Сітка", "list": "Список", "collapse_all_notes": "Згорнути всі нотатки", - "expand_all_children": "Розгорнути всі дочірні елементи", + "expand_all_children": "Розгорнути всі дочірні", "collapse": "Згорнути", "expand": "Розгорнути", - "book_properties": "Властивості колекції", + "book_properties": "Властивості Колекції", "invalid_view_type": "Недійсний тип перегляду '{{type}}'", "calendar": "Календар", "table": "Таблиця", @@ -862,13 +862,13 @@ "board": "Дошка" }, "edited_notes": { - "no_edited_notes_found": "Цього дня ще немає відредагованих нотаток...", - "title": "Відредаговані нотатки", + "no_edited_notes_found": "Цього дня ще немає редагованих нотаток...", + "title": "Редаговані нотатки", "deleted": "(видалено)" }, "file_properties": { "note_id": "ID Нотатки", - "original_file_name": "Оригінальна назва файлу", + "original_file_name": "Оригінальне ім'я файлу", "file_type": "Тип файлу", "file_size": "Розмір файлу", "download": "Завантажити", @@ -879,7 +879,7 @@ "title": "Файл" }, "image_properties": { - "original_file_name": "Оригінальна назва файлу", + "original_file_name": "Оригінальне ім'я файлу", "file_type": "Тип файлу", "file_size": "Розмір файлу", "download": "Завантажити", @@ -891,7 +891,7 @@ "title": "Зображення" }, "inherited_attribute_list": { - "title": "Успадковані атрибути", + "title": "Успадковані Атрибути", "no_inherited_attributes": "Немає успадкованих атрибутів." }, "note_info_widget": { @@ -908,12 +908,12 @@ "note_map": { "open_full": "Розгорнути на повний розмір", "collapse": "Згорнути до звичайного розміру", - "title": "Карта нотатки", + "title": "Карта Нотатки", "fix-nodes": "Виправити вузли", "link-distance": "Відстань зв'язку" }, "note_paths": { - "title": "Шляхи нотаток", + "title": "Шляхи Нотатки", "clone_button": "Клонувати нотатку в нове місце...", "intro_placed": "Цю нотатку розміщено за такими шляхами:", "intro_not_placed": "Цю нотатку ще не розміщено в дереві нотаток.", @@ -926,10 +926,10 @@ "info": "Інформація" }, "owned_attribute_list": { - "owned_attributes": "Власні атрибути" + "owned_attributes": "Власні Атрибути" }, "promoted_attributes": { - "promoted_attributes": "Просунуті атрибути", + "promoted_attributes": "Просунуті Атрибути", "unset-field-placeholder": "не встановлено", "url_placeholder": "http://website...", "open_external_link": "Відкрити зовнішнє посилання", @@ -964,9 +964,9 @@ "search_execute": "Пошук & Виконання дій", "save_to_note": "Зберегти до нотатки", "search_parameters": "Параметри пошуку", - "unknown_search_option": "Невідомий варіант пошуку {{searchOptionName}}", + "unknown_search_option": "Невідомий параметр пошуку {{searchOptionName}}", "actions_executed": "Дії виконано.", - "search_note_saved": "Нотатка з пошуку збережено у {{- notePathTitle}}" + "search_note_saved": "Нотатка з пошуку збережена у {{- notePathTitle}}" }, "similar_notes": { "title": "Схожі нотатки", @@ -1026,7 +1026,7 @@ "placeholder": "fulltext keywords, #tag = value...", "search_syntax": "Синтаксис пошуку", "also_see": "див. також", - "complete_help": "повна допомога щодо синтаксису пошуку", + "complete_help": "повна довідка щодо синтаксису пошуку", "full_text_search": "Просто введіть будь-який текст для повнотекстового пошуку", "label_abc": "повертає нотатки з міткою abc", "label_year": "зіставляє нотатки з роком на мітці, що має значення 2019", @@ -1082,12 +1082,12 @@ }, "relation_map": { "open_in_new_tab": "Відкрити в новій вкладці", - "remove_note": "Перемістити нотатку", + "remove_note": "Видалити нотатку", "edit_title": "Редагувати заголовок", "rename_note": "Перейменувати нотатку", "enter_new_title": "Введіть новий заголовок нотатки:", - "remove_relation": "Перемістити зв'язок", - "confirm_remove_relation": "Ви впевнені, що хочете перемістити зв'язок?", + "remove_relation": "Видалити зв'язок", + "confirm_remove_relation": "Ви впевнені, що хочете видалити зв'язок?", "specify_new_relation_name": "Вкажіть нову назву зв'язку (дозволені символи: букви, цифри, двокрапка та підкреслення):", "connection_exists": "Зв'язок '{{name}}' між цими нотатками вже існує.", "start_dragging_relations": "Почніть перетягувати зв'язки звідси та перемістіть їх на іншу нотатку.", @@ -1118,10 +1118,10 @@ }, "database_anonymization": { "title": "Анонімізація Бази даних", - "full_anonymization": "Повна анонімізація", + "full_anonymization": "Повна Анонімізація", "full_anonymization_description": "Ця дія створить нову копію бази даних та анонімізує її (видалить весь вміст нотаток, залишивши лише структуру та деякі неконфіденційні метадані) для обміну нею в Інтернеті з метою налагодження без побоювань витоку ваших особистих даних.", "save_fully_anonymized_database": "Зберегти повністю анонімізовану базу даних", - "light_anonymization": "Легка анонімізація", + "light_anonymization": "Легка Анонімізація", "light_anonymization_description": "Ця дія створить нову копію бази даних та проведе її легку анонімізацію — зокрема, буде видалено лише вміст усіх нотаток, але заголовки та атрибути залишаться. Крім того, збережуться користувацькі нотатки JS frontend/backend та користувацькі віджети. Це надає більше контексту для налагодження проблем.", "choose_anonymization": "Ви можете самі вирішити, чи хочете ви надати повністю чи злегка анонімізовану базу даних. Навіть повністю анонімізована база даних дуже корисна, проте в деяких випадках злегка анонімізована база даних може пришвидшити процес виявлення та виправлення помилок.", "save_lightly_anonymized_database": "Зберегти злегка анонімізовану базу даних", @@ -1134,7 +1134,7 @@ "no_anonymized_database_yet": "Поки що немає анонімізованої бази даних." }, "database_integrity_check": { - "title": "Перевірка цілісності бази даних", + "title": "Перевірка цілісності Бази даних", "description": "Це перевірить, чи не пошкоджена база даних на рівні SQLite. Це може зайняти деякий час, залежно від розміру бази даних.", "check_button": "Перевірка цілісності бази даних", "checking_integrity": "Перевірка цілісності бази даних...", @@ -1147,12 +1147,12 @@ "fill_entity_changes_button": "Запис зміни заповнювачів", "full_sync_triggered": "Повна синхронізація активована", "filling_entity_changes": "Заповнювач змінює рядки...", - "sync_rows_filled_successfully": "Синхронізація рядків успішно заповнена", + "sync_rows_filled_successfully": "Синхронізація заповнення рядків успішна", "finished-successfully": "Синхронізацію успішно завершено.", "failed": "Помилка синхронізації: {{message}}" }, "vacuum_database": { - "title": "Стиснення бази даних", + "title": "Стиснення Бази даних", "description": "Це призведе до перебудови бази даних, що зазвичай призведе до зменшення розміру файлу бази даних. Фактично жодні дані не зміняться.", "button_text": "Стиснення бази даних", "vacuuming_database": "Стиснення бази даних...", @@ -1164,7 +1164,7 @@ "main_font": "Основний шрифт", "font_family": "Сімейство шрифтів", "size": "Розмір", - "note_tree_font": "Шрифт дерева нотаток", + "note_tree_font": "Шрифт Дерева нотатки", "note_detail_font": "Шрифт деталей нотатки", "monospace_font": "Monospace (кодовий) шрифт", "note_tree_and_detail_font_sizing": "Зверніть увагу, що розмір шрифту дерева та деталей залежить від основного налаштування розміру шрифту.", @@ -1322,7 +1322,7 @@ }, "name": "AI", "openai": "OpenAI", - "use_enhanced_context": "Використовувати розширений контекст", + "use_enhanced_context": "Використовувати покращений контекст", "enhanced_context_description": "Надає AI більше контексту з нотатки та пов'язаних з нею нотаток для кращих відповідей", "show_thinking": "Показати міркування", "show_thinking_description": "Показати ланцюжок міркувань AI", @@ -1424,9 +1424,9 @@ "protected_session_timeout_label": "Тайм-аут захищеного сеансу:", "reset_confirmation": "Скинувши пароль, ви назавжди втратите доступ до всіх своїх існуючих захищених нотаток. Ви дійсно хочете скинути пароль?", "reset_success_message": "Пароль скинуто. Будь ласка, встановіть новий пароль", - "change_password_heading": "Змінити пароль", - "set_password_heading": "Встановити пароль", - "set_password": "Встановити пароль", + "change_password_heading": "Змінити Пароль", + "set_password_heading": "Встановити Пароль", + "set_password": "Встановити Пароль", "password_mismatch": "Нові паролі не однакові.", "password_changed_success": "Пароль змінено. Trilium буде перезавантажено після натискання кнопки OK." }, @@ -1468,7 +1468,7 @@ "attachment_auto_deletion_description": "Вкладення автоматично видаляються (і стираються), якщо на них більше не посилаються в нотатці після певного часу очікування.", "erase_attachments_after": "Стерти невикористані вкладення після:", "manual_erasing_description": "Ви також можете запустити стирання вручну (без урахування часу очікування, визначеного вище):", - "erase_unused_attachments_now": "Стерти невикористані вкладення нотатки зараз", + "erase_unused_attachments_now": "Стерти невикористані вкладення нотаток зараз", "unused_attachments_erased": "Невикористані вкладення стерто." }, "network_connections": { @@ -1528,7 +1528,7 @@ "underline": "Підкреслений текст", "color": "Кольоровий текст", "bg_color": "Текст із кольором фону", - "visibility_title": "Видимість списку основних моментів", + "visibility_title": "Видимість Списку основних моментів", "visibility_description": "Ви можете приховати віджет основних моментів для кожної нотатки окремо, додавши мітку #hideHighlightWidget.", "shortcut_info": "Ви можете налаштувати комбінацію клавіш для швидкого перемикання правої панелі (включно з основними моментами) у меню Параметри -> Комбінації клавіш (назва «toggleRightPane»)." }, @@ -1583,7 +1583,7 @@ "save-changes": "Зберегти & застосувати зміни", "auto-collapsing-notes-after-inactivity": "Автоматичне згортання нотаток після бездіяльності...", "saved-search-note-refreshed": "Збережену нотатку пошуку оновлено.", - "hoist-this-note-workspace": "Закріпити цю ноту (робочий простір)", + "hoist-this-note-workspace": "Закріпити цю нотатку (робочий простір)", "refresh-saved-search-results": "Оновити збережені результати пошуку", "create-child-note": "Створити дочірню нотатку", "unhoist": "Відкріпити" @@ -1668,12 +1668,12 @@ }, "link_context_menu": { "open_note_in_new_tab": "Відкрити нотатку в новій вкладці", - "open_note_in_new_split": "Відкрити ноту в новому розділі", + "open_note_in_new_split": "Відкрити нотатку в новому розділі", "open_note_in_new_window": "Відкрити нотатку в новому вікні", "open_note_in_popup": "Швидке редагування" }, "electron_integration": { - "desktop-application": "Настільний додаток", + "desktop-application": "Додаток для ПК", "native-title-bar": "Нативний рядок заголовка", "native-title-bar-description": "У Windows та macOS вимкнення рядка заголовка робить програму компактнішою. У Linux увімкнення рядка заголовка краще інтегрується з рештою системи.", "background-effects": "Увімкнення фонових ефектів (лише для Windows 11)", @@ -1682,9 +1682,9 @@ "zoom-factor": "Коефіцієнт масштабування" }, "note_autocomplete": { - "search-for": "Пошук за запитом \"{{term}}\"", + "search-for": "Пошук для \"{{term}}\"", "create-note": "Створити та зв'язати дочірню нотатку \"{{term}}\"", - "insert-external-link": "Вставити зовнішнє посилання на \"{{term}}\"", + "insert-external-link": "Вставити зовнішнє посилання у \"{{term}}\"", "clear-text-field": "Очистити текстове поле", "show-recent-notes": "Показати останні нотатки", "full-text-search": "Повнотекстовий пошук" @@ -1699,7 +1699,7 @@ "unable-to-load-map": "Не вдалося завантажити карту." }, "geo-map-context": { - "open-location": "Відкрите місцезнаходження", + "open-location": "Відкрити місцезнаходження", "remove-from-map": "Видалити з карти", "add-note": "Додати маркер у цьому місці" }, @@ -1782,7 +1782,7 @@ "new-row": "Новий рядок", "new-column": "Новий стовпець", "sort-column-by": "Сортувати за \"{{title}}\"", - "sort-column-ascending": "Зростаючий", + "sort-column-ascending": "Зростання", "sort-column-descending": "Спадання", "sort-column-clear": "Очистити сортування", "hide-column": "Приховати стовпець \"{{title}}\"", @@ -1841,7 +1841,7 @@ "note_description": "Якщо залишити налаштування проксі-сервера порожнім, буде використано системний проксі-сервер (стосується лише збірки для ПК/електронної версії).", "special_value_description": "Інше спеціальне значення — noproxy, яке змушує ігнорувати навіть системний проксі-сервер та враховує NODE_TLS_REJECT_UNAUTHORIZED.", "save": "Зберегти", - "help": "Допомога", + "help": "Довідка", "test_title": "Тест синхронізації", "test_description": "Це перевірить з’єднання та встановлення зв’язку із сервером синхронізації. Якщо сервер синхронізації не ініціалізовано, це налаштує його на синхронізацію з локальним документом.", "test_button": "Тест синхронізації", @@ -1917,17 +1917,17 @@ "text": "Текст", "code": "Код", "saved-search": "Збережений пошук", - "relation-map": "Карта зв'язків", - "note-map": "Карта нотатки", + "relation-map": "Карта Зв'язків", + "note-map": "Карта Нотатки", "book": "Колекція", "mermaid-diagram": "Діаграма Mermaid", "canvas": "Полотно", "web-view": "Веб-перегляд", - "mind-map": "Карта розуму", + "mind-map": "Карта Розуму", "file": "Файл", "image": "Зображення", - "launcher": "Запуск", - "doc": "Doc", + "launcher": "Лаунчер", + "doc": "Документ", "widget": "Віджет", "confirm-change": "Не рекомендується змінювати тип нотатки, якщо її вміст не порожній. Ви все одно хочете продовжити?", "geo-map": "Географічна карта", @@ -1936,7 +1936,7 @@ "task-list": "Список завдань", "new-feature": "Нова", "collections": "Колекції", - "render-note": "Рендерінг нотатки" + "render-note": "Рендерінг Нотатки" }, "protect_note": { "toggle-on": "Захист нотатки", @@ -1956,7 +1956,7 @@ "toggle-on-hint": "Зробити нотатку шаблоном", "toggle-off-hint": "Видалити нотатку як шаблон" }, - "open-help-page": "Відкрити сторінку допомоги", + "open-help-page": "Відкрити сторінку довідки", "find": { "case_sensitive": "Чутливість до регістру", "match_words": "Збіг слів", @@ -1984,13 +1984,13 @@ }, "command_palette": { "tree-action-name": "Дерево: {{name}}", - "export_note_title": "Експорт нотатки", + "export_note_title": "Експорт Нотатки", "export_note_description": "Експорт поточної нотатки", "show_attachments_title": "Показати вкладення", "show_attachments_description": "Перегляд вкладення до нотатки", "search_notes_title": "Пошук нотаток", "search_notes_description": "Відкрити розширений пошук", - "search_subtree_title": "Пошук у піддереві", + "search_subtree_title": "Пошук у Піддереві", "search_subtree_description": "Пошук у поточному піддереві", "search_history_title": "Показати історію пошуку", "search_history_description": "Переглянути попередні пошукові запити", From 211ca43a822fa9d49e4410c1a8c75f6e78c26772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=20=D0=9A=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D1=82=D1=96=D0=BD?= Date: Fri, 22 Aug 2025 18:40:11 +0200 Subject: [PATCH 257/532] Translated using Weblate (Ukrainian) Currently translated at 100.0% (378 of 378 strings) Translation: Trilium Notes/Server Translate-URL: https://hosted.weblate.org/projects/trilium/server/uk/ --- .../src/assets/translations/uk/server.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/server/src/assets/translations/uk/server.json b/apps/server/src/assets/translations/uk/server.json index ca1d037b8..26282db65 100644 --- a/apps/server/src/assets/translations/uk/server.json +++ b/apps/server/src/assets/translations/uk/server.json @@ -330,30 +330,30 @@ "calendar-title": "Календар", "recent-changes-title": "Останні Зміни", "bookmarks-title": "Закладки", - "open-today-journal-note-title": "Відкрити Щоденник за сьогодні", - "quick-search-title": "Швидкий пошук", - "protected-session-title": "Захищений сеанс", + "open-today-journal-note-title": "Відкрити Щоденник за Сьогодні", + "quick-search-title": "Швидкий Пошук", + "protected-session-title": "Захищений Сеанс", "sync-status-title": "Статус синхронізації", "settings-title": "Налаштування", "llm-chat-title": "Чат з Нотатками", "options-title": "Параметри", "appearance-title": "Зовнішній вигляд", - "shortcuts-title": "Швидкі клавіші", + "shortcuts-title": "Комбінації клавіші", "text-notes": "Текстові Нотатки", "code-notes-title": "Нотатка з кодом", "images-title": "Зображення", - "spellcheck-title": "Перевірка орфографії", + "spellcheck-title": "Перевірка Орфографії", "password-title": "Пароль", - "multi-factor-authentication-title": "МФА", + "multi-factor-authentication-title": "MFA", "etapi-title": "ETAPI", "backup-title": "Резервне копіювання", "sync-title": "Синхронізація", "ai-llm-title": "AI/LLM", "other": "Інше", "advanced-title": "Розширені", - "visible-launchers-title": "Видимі лаунчери", + "visible-launchers-title": "Видимі Лаунчери", "user-guide": "Посібник користувача", - "localization": "Мова & регіон", + "localization": "Мова & Регіон", "inbox-title": "Вхідні", "spacer-title": "Роздільник" }, @@ -380,7 +380,7 @@ "close": "Вихід з Trilium", "recents": "Останні нотатки", "bookmarks": "Закладки", - "today": "Відкрити Щоденник за сьогодні", + "today": "Відкрити щоденник за сьогодні", "new-note": "Нова нотатка", "show-windows": "Показати вікна", "open_new_window": "Відкрити нове вікно" @@ -396,7 +396,7 @@ "share_theme": { "site-theme": "Тема сайту", "search_placeholder": "Пошук...", - "image_alt": "Зображення статті", + "image_alt": "Зображення до статті", "last-updated": "Останнє оновлення: {{- date}}", "subpages": "Підсторінки:", "on-this-page": "На цій сторінці", From 5351310a383bbf14ae255261ef429e946e27cf11 Mon Sep 17 00:00:00 2001 From: Francis C Date: Sat, 23 Aug 2025 05:47:01 +0200 Subject: [PATCH 258/532] Translated using Weblate (Japanese) Currently translated at 67.9% (1060 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- apps/client/src/translations/ja/translation.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 566631f4d..6bdd89926 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -692,7 +692,8 @@ "placeholder_search": "ノート名で検索", "dialog_title": "埋め込みノート", "box_size_prompt": "埋め込みノート枠のサイズ:", - "button_include": "埋め込みノート" + "button_include": "埋め込みノート", + "label_note": "ノート" }, "ancestor": { "placeholder": "ノート名で検索" From 4e755dc53750620d20e1d4c361dce8d1eccc04da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=20=D0=9A=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D1=82=D1=96=D0=BD?= Date: Sat, 23 Aug 2025 09:32:29 +0200 Subject: [PATCH 259/532] Translated using Weblate (Ukrainian) Currently translated at 100.0% (1560 of 1560 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/uk/ --- apps/client/src/translations/uk/translation.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/client/src/translations/uk/translation.json b/apps/client/src/translations/uk/translation.json index efb01f942..c2398bcc9 100644 --- a/apps/client/src/translations/uk/translation.json +++ b/apps/client/src/translations/uk/translation.json @@ -285,9 +285,9 @@ "successful": "Імпорт успішно завершено." }, "prompt": { - "title": "Підказка", + "title": "Запит(prompt)", "ok": "ОК", - "defaultTitle": "Підказка" + "defaultTitle": "Запит(Prompt)" }, "protected_session_password": { "modal_title": "Захищений сеанс", @@ -1239,8 +1239,8 @@ "provider_precedence_description": "Список постачальників, розділених комами, у порядку пріоритету (наприклад, «openai,anthropic,ollama»)", "temperature": "Температура", "temperature_description": "Контролює випадковість відповідей (0 = детермінований, 2 = максимальна випадковість)", - "system_prompt": "Системний запит", - "system_prompt_description": "Системний запит за замовчуванням використовується для всіх взаємодій з AI", + "system_prompt": "Системний Запит (Prompt)", + "system_prompt_description": "Системний запит (prompt) за замовчуванням використовується для всіх взаємодій з AI", "openai_configuration": "Конфігурація OpenAI", "openai_settings": "Налаштування OpenAI", "api_key": "API Key", From f049b8b9156356a599faacbf008976564e08f649 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 18:23:38 +0300 Subject: [PATCH 260/532] chore(react/ribbon): save attribute changes --- .../attribute_widgets/attribute_editor.ts | 43 -------------- .../ribbon/components/AttributeEditor.tsx | 56 ++++++++++++++++++- 2 files changed, 54 insertions(+), 45 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 508153dc8..0d173cf92 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -16,7 +16,6 @@ import { escapeQuotes } from "../../services/utils.js"; const TPL = /*html*/` -
    @@ -49,11 +48,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.initialized = this.initEditor(); this.$editor.on("keydown", async (e) => { - if (e.which === 13) { - // allow autocomplete to fill the result textarea - setTimeout(() => this.save(), 100); - } - this.attributeDetailWidget.hide(); }); @@ -62,7 +56,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button"); this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e)); - this.$saveAttributesButton = this.$widget.find(".save-attributes-button"); this.$saveAttributesButton.on("click", () => this.save()); this.$errors = this.$widget.find(".attribute-errors"); @@ -170,39 +163,11 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem console.warn("Ignoring blur event because a different note is loaded."); return; } - - const attributes = this.parseAttributes(); - - if (attributes) { - await server.put(`notes/${this.noteId}/attributes`, attributes, this.componentId); - - this.$saveAttributesButton.fadeOut(); - - // blink the attribute text to give a visual hint that save has been executed - this.$editor.css("opacity", 0); - - // revert back - setTimeout(() => this.$editor.css("opacity", 1), 100); - } - } - - parseAttributes() { - try { - return attributeParser.lexAndParse(this.getPreprocessedData()); - } catch (e: any) { - this.$errors.text(e.message).slideDown(); - } } dataChanged() { this.lastUpdatedNoteId = this.noteId; - if (this.lastSavedContent === this.textEditor.getData()) { - this.$saveAttributesButton.fadeOut(); - } else { - this.$saveAttributesButton.fadeIn(); - } - if (this.$errors.is(":visible")) { // using .hide() instead of .slideUp() since this will also hide the error after confirming // mention for relation name which suits up. When using.slideUp() error will appear and the slideUp which is weird @@ -218,14 +183,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem $el.text(title); } - async renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) { - if (saved) { - this.lastSavedContent = this.textEditor.getData(); - - this.$saveAttributesButton.fadeOut(0); - } - } - async createNoteForReferenceLink(title: string) { let result; if (this.notePath) { diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index dc9e1aa1c..c1e59b114 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -10,6 +10,8 @@ import attribute_renderer from "../../../services/attribute_renderer"; import FNote from "../../../entities/fnote"; import AttributeDetailWidget from "../../attribute_widgets/attribute_detail"; import attribute_parser, { Attribute } from "../../../services/attribute_parser"; +import ActionButton from "../../react/ActionButton"; +import { escapeQuotes } from "../../../services/utils"; const HELP_TEXT = `

    ${t("attribute_editor.help_text_body1")}

    @@ -63,10 +65,13 @@ const mentionSetup: MentionFeed[] = [ ]; -export default function AttributeEditor({ note }: { note: FNote }) { +export default function AttributeEditor({ note, componentId }: { note: FNote, componentId: string }) { const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); + const [ error, setError ] = useState(); + const [ needsSaving, setNeedsSaving ] = useState(false); const [ initialValue, setInitialValue ] = useState(""); + const lastSavedContent = useRef(); const currentValueRef = useRef(initialValue); const wrapperRef = useRef(null); const { showTooltip, hideTooltip } = useTooltip(wrapperRef, { @@ -97,16 +102,55 @@ export default function AttributeEditor({ note }: { note: FNote }) { htmlAttrs += " "; } + if (saved) { + lastSavedContent.current = currentValueRef.current; + setNeedsSaving(false); + } + setInitialValue(htmlAttrs); } + function parseAttributes() { + try { + return attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current)); + } catch (e: any) { + setError(e); + } + } + + async function save() { + const attributes = parseAttributes(); + if (!attributes) { + // An error occurred and will be reported to the user. + return; + } + + await server.put(`notes/${note.noteId}/attributes`, attributes, componentId); + setNeedsSaving(false); + + // blink the attribute text to give a visual hint that save has been executed + if (wrapperRef.current) { + wrapperRef.current.style.opacity = "0"; + setTimeout(() => wrapperRef.current!.style.opacity = "1", 100); + } + } + useEffect(() => { renderOwnedAttributes(note.getOwnedAttributes(), true); }, [ note ]); return ( <> -
    +
    { + if (e.key === "Enter") { + // allow autocomplete to fill the result textarea + setTimeout(() => save(), 100); + } + }} + > { currentValueRef.current = currentValue ?? ""; + setNeedsSaving(lastSavedContent.current !== currentValue); }} onClick={(e, pos) => { if (pos && pos.textNode && pos.textNode.data) { @@ -163,6 +208,13 @@ export default function AttributeEditor({ note }: { note: FNote }) { }} disableNewlines disableSpellcheck /> + + { needsSaving && }
    {attributeDetailWidgetEl} From e8ae5486c844b08a4ce768f187dda08c8d8bc218 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 18:28:42 +0300 Subject: [PATCH 261/532] chore(react/ribbon): display attribute errors --- .../widgets/attribute_widgets/attribute_editor.ts | 13 ------------- .../widgets/ribbon/components/AttributeEditor.tsx | 9 ++++++++- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 0d173cf92..4d0bc64e5 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -11,14 +11,11 @@ import linkService from "../../services/link.js"; import type AttributeDetailWidget from "./attribute_detail.js"; import type { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js"; import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js"; -import type FNote from "../../entities/fnote.js"; import { escapeQuotes } from "../../services/utils.js"; const TPL = /*html*/`
    - -
    `; @@ -29,11 +26,9 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem private $editor!: JQuery; private $addNewAttributeButton!: JQuery; private $saveAttributesButton!: JQuery; - private $errors!: JQuery; private textEditor!: AttributeEditor; private lastUpdatedNoteId!: string | undefined; - private lastSavedContent!: string; constructor(attributeDetailWidget: AttributeDetailWidget) { super(); @@ -57,8 +52,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e)); this.$saveAttributesButton.on("click", () => this.save()); - - this.$errors = this.$widget.find(".attribute-errors"); } addNewAttribute(e: JQuery.ClickEvent) { @@ -167,12 +160,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem dataChanged() { this.lastUpdatedNoteId = this.noteId; - - if (this.$errors.is(":visible")) { - // using .hide() instead of .slideUp() since this will also hide the error after confirming - // mention for relation name which suits up. When using.slideUp() error will appear and the slideUp which is weird - this.$errors.hide(); - } } async loadReferenceLinkTitle($el: JQuery, href: string) { diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index c1e59b114..36c0f6dcf 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -165,12 +165,13 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co onChange={(currentValue) => { currentValueRef.current = currentValue ?? ""; setNeedsSaving(lastSavedContent.current !== currentValue); + setError(undefined); }} onClick={(e, pos) => { if (pos && pos.textNode && pos.textNode.data) { const clickIndex = getClickIndex(pos); - let parsedAttrs; + let parsedAttrs: Attribute[]; try { parsedAttrs = attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current), true); @@ -215,6 +216,12 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co text={escapeQuotes(t("attribute_editor.save_attributes"))} onClick={save} /> } + + { error && ( +
    + {typeof error === "object" && "message" in error && typeof error.message === "string" && error.message} +
    + )}
    {attributeDetailWidgetEl} From 168d25c0207a971560a9ef3487b71614dff27035 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 19:13:48 +0300 Subject: [PATCH 262/532] chore(react/ribbon): fix save icon displayed when it shouldn't --- .../ribbon/components/AttributeEditor.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 36c0f6dcf..6a8981431 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -96,16 +96,16 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co // attrs are not resorted if position changes after the initial load ownedAttributes.sort((a, b) => a.position - b.position); - let htmlAttrs = (await attribute_renderer.renderAttributes(ownedAttributes, true)).html(); + let htmlAttrs = getPreprocessedData("

    " + (await attribute_renderer.renderAttributes(ownedAttributes, true)).html() + "

    "); + + if (saved) { + lastSavedContent.current = htmlAttrs; + setNeedsSaving(false); + } if (htmlAttrs.length > 0) { htmlAttrs += " "; - } - - if (saved) { - lastSavedContent.current = currentValueRef.current; - setNeedsSaving(false); - } + } setInitialValue(htmlAttrs); } @@ -164,7 +164,7 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co }} onChange={(currentValue) => { currentValueRef.current = currentValue ?? ""; - setNeedsSaving(lastSavedContent.current !== currentValue); + setNeedsSaving((lastSavedContent.current ?? "").trimEnd() !== getPreprocessedData(currentValue ?? "").trimEnd()); setError(undefined); }} onClick={(e, pos) => { From 73ca285b7a6973eadc263e329ff499810d9186f9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 19:26:23 +0300 Subject: [PATCH 263/532] chore(react/ribbon): support reference links in attributes --- .../attribute_widgets/attribute_editor.ts | 8 ------ .../ribbon/components/AttributeEditor.tsx | 28 +++++++++++++++++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 4d0bc64e5..9dc426c6e 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -162,14 +162,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.lastUpdatedNoteId = this.noteId; } - async loadReferenceLinkTitle($el: JQuery, href: string) { - const { noteId } = linkService.parseNavigationStateFromUrl(href); - const note = noteId ? await froca.getNote(noteId, true) : null; - const title = note ? note.title : "[missing]"; - - $el.text(title); - } - async createNoteForReferenceLink(title: string) { let result; if (this.notePath) { diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 6a8981431..9ea7ebaca 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "preact/hooks" +import { useContext, useEffect, useRef, useState } from "preact/hooks" import { AttributeEditor as CKEditorAttributeEditor, MentionFeed, ModelElement, ModelNode, ModelPosition } from "@triliumnext/ckeditor5"; import { t } from "../../../services/i18n"; import server from "../../../services/server"; @@ -12,6 +12,10 @@ import AttributeDetailWidget from "../../attribute_widgets/attribute_detail"; import attribute_parser, { Attribute } from "../../../services/attribute_parser"; import ActionButton from "../../react/ActionButton"; import { escapeQuotes } from "../../../services/utils"; +import { ParentComponent } from "../../react/react_utils"; +import Component from "../../../components/component"; +import link from "../../../services/link"; +import froca from "../../../services/froca"; const HELP_TEXT = `

    ${t("attribute_editor.help_text_body1")}

    @@ -66,14 +70,18 @@ const mentionSetup: MentionFeed[] = [ export default function AttributeEditor({ note, componentId }: { note: FNote, componentId: string }) { + const parentComponent = useContext(ParentComponent); + injectLoadReferenceLinkTitle(parentComponent); const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); const [ error, setError ] = useState(); const [ needsSaving, setNeedsSaving ] = useState(false); const [ initialValue, setInitialValue ] = useState(""); + const lastSavedContent = useRef(); const currentValueRef = useRef(initialValue); const wrapperRef = useRef(null); + const { showTooltip, hideTooltip } = useTooltip(wrapperRef, { trigger: "focus", html: true, @@ -96,7 +104,7 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co // attrs are not resorted if position changes after the initial load ownedAttributes.sort((a, b) => a.position - b.position); - let htmlAttrs = getPreprocessedData("

    " + (await attribute_renderer.renderAttributes(ownedAttributes, true)).html() + "

    "); + let htmlAttrs = ("

    " + (await attribute_renderer.renderAttributes(ownedAttributes, true)).html() + "

    "); if (saved) { lastSavedContent.current = htmlAttrs; @@ -164,7 +172,10 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co }} onChange={(currentValue) => { currentValueRef.current = currentValue ?? ""; - setNeedsSaving((lastSavedContent.current ?? "").trimEnd() !== getPreprocessedData(currentValue ?? "").trimEnd()); + + const oldValue = getPreprocessedData(lastSavedContent.current ?? "").trimEnd(); + const newValue = getPreprocessedData(currentValue ?? "").trimEnd(); + setNeedsSaving(oldValue !== newValue); setError(undefined); }} onClick={(e, pos) => { @@ -253,4 +264,15 @@ function getClickIndex(pos: ModelPosition) { } return clickIndex; +} + +function injectLoadReferenceLinkTitle(component: Component | null) { + if (!component) return; + (component as any).loadReferenceLinkTitle = async ($el: JQuery, href: string) => { + const { noteId } = link.parseNavigationStateFromUrl(href); + const note = noteId ? await froca.getNote(noteId, true) : null; + const title = note ? note.title : "[missing]"; + + $el.text(title); + } } \ No newline at end of file From 3f3c7cfe88d2bad6cf6fdb7837281d53814a7922 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 19:48:01 +0300 Subject: [PATCH 264/532] chore(react/ribbon): add menu --- .../attribute_widgets/attribute_editor.ts | 94 ------------------- .../client/src/widgets/react/ActionButton.tsx | 2 +- .../ribbon/components/AttributeEditor.tsx | 88 +++++++++++++++++ 3 files changed, 89 insertions(+), 95 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 9dc426c6e..cb6d8272e 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -1,25 +1,12 @@ import { t } from "../../services/i18n.js"; import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import server from "../../services/server.js"; import contextMenuService from "../../menus/context_menu.js"; -import attributeParser, { type Attribute } from "../../services/attribute_parser.js"; import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5"; -import froca from "../../services/froca.js"; import noteCreateService from "../../services/note_create.js"; import attributeService from "../../services/attributes.js"; -import linkService from "../../services/link.js"; import type AttributeDetailWidget from "./attribute_detail.js"; import type { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js"; import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js"; -import { escapeQuotes } from "../../services/utils.js"; - -const TPL = /*html*/` - -
    -
    -`; - -type AttributeCommandNames = FilteredCommandNames; export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> { private attributeDetailWidget: AttributeDetailWidget; @@ -48,30 +35,9 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.$editor.on("blur", () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/zadam/trilium/issues/4160 - this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button"); - this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e)); - this.$saveAttributesButton.on("click", () => this.save()); } - addNewAttribute(e: JQuery.ClickEvent) { - contextMenuService.show({ - x: e.pageX, - y: e.pageY, - orientation: "left", - items: [ - { title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" }, - { title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" }, - { title: "----" }, - { title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" }, - { title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" } - ], - selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command) - }); - // Prevent automatic hiding of the context menu due to the button being clicked. - e.stopPropagation(); - } - // triggered from keyboard shortcut async addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) { if (this.isNoteContext(ntxId)) { @@ -90,66 +56,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem } } - async handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) { - // TODO: Not sure what the relation between FAttribute[] and Attribute[] is. - const attrs = this.parseAttributes() as FAttribute[]; - - if (!attrs) { - return; - } - - let type: AttributeType; - let name; - let value; - - if (command === "addNewLabel") { - type = "label"; - name = "myLabel"; - value = ""; - } else if (command === "addNewRelation") { - type = "relation"; - name = "myRelation"; - value = ""; - } else if (command === "addNewLabelDefinition") { - type = "label"; - name = "label:myLabel"; - value = "promoted,single,text"; - } else if (command === "addNewRelationDefinition") { - type = "label"; - name = "relation:myRelation"; - value = "promoted,single"; - } else { - return; - } - - // TODO: Incomplete type - //@ts-ignore - attrs.push({ - type, - name, - value, - isInheritable: false - }); - - await this.renderOwnedAttributes(attrs, false); - - this.$editor.scrollTop(this.$editor[0].scrollHeight); - - const rect = this.$editor[0].getBoundingClientRect(); - - setTimeout(() => { - // showing a little bit later because there's a conflict with outside click closing the attr detail - this.attributeDetailWidget.showAttributeDetail({ - allAttributes: attrs, - attribute: attrs[attrs.length - 1], - isOwned: true, - x: (rect.left + rect.right) / 2, - y: rect.bottom, - focus: "name" - }); - }, 100); - } - async save() { if (this.lastUpdatedNoteId !== this.noteId) { // https://github.com/zadam/trilium/issues/3090 diff --git a/apps/client/src/widgets/react/ActionButton.tsx b/apps/client/src/widgets/react/ActionButton.tsx index 65eb60469..a6a1ee740 100644 --- a/apps/client/src/widgets/react/ActionButton.tsx +++ b/apps/client/src/widgets/react/ActionButton.tsx @@ -2,7 +2,7 @@ interface ActionButtonProps { text: string; icon: string; className?: string; - onClick?: () => void; + onClick?: (e: MouseEvent) => void; } export default function ActionButton({ text, icon, className, onClick }: ActionButtonProps) { diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 9ea7ebaca..f868744c5 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -16,6 +16,11 @@ import { ParentComponent } from "../../react/react_utils"; import Component from "../../../components/component"; import link from "../../../services/link"; import froca from "../../../services/froca"; +import contextMenu from "../../../menus/context_menu"; +import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; +import { AttributeType } from "@triliumnext/commons"; + +type AttributeCommandNames = FilteredCommandNames; const HELP_TEXT = `

    ${t("attribute_editor.help_text_body1")}

    @@ -143,6 +148,65 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co } } + async function handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) { + // TODO: Not sure what the relation between FAttribute[] and Attribute[] is. + const attrs = parseAttributes() as FAttribute[]; + + if (!attrs) { + return; + } + + let type: AttributeType; + let name; + let value; + + if (command === "addNewLabel") { + type = "label"; + name = "myLabel"; + value = ""; + } else if (command === "addNewRelation") { + type = "relation"; + name = "myRelation"; + value = ""; + } else if (command === "addNewLabelDefinition") { + type = "label"; + name = "label:myLabel"; + value = "promoted,single,text"; + } else if (command === "addNewRelationDefinition") { + type = "label"; + name = "relation:myRelation"; + value = "promoted,single"; + } else { + return; + } + + // TODO: Incomplete type + //@ts-ignore + attrs.push({ + type, + name, + value, + isInheritable: false + }); + + await renderOwnedAttributes(attrs, false); + + // this.$editor.scrollTop(this.$editor[0].scrollHeight); + const rect = wrapperRef.current?.getBoundingClientRect(); + + setTimeout(() => { + // showing a little bit later because there's a conflict with outside click closing the attr detail + attributeDetailWidget.showAttributeDetail({ + allAttributes: attrs, + attribute: attrs[attrs.length - 1], + isOwned: true, + x: rect ? (rect.left + rect.right) / 2 : 0, + y: rect?.bottom ?? 0, + focus: "name" + }); + }, 100); + } + useEffect(() => { renderOwnedAttributes(note.getOwnedAttributes(), true); }, [ note ]); @@ -228,6 +292,30 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co onClick={save} /> } + { + // Prevent automatic hiding of the context menu due to the button being clicked. + e.stopPropagation(); + + contextMenu.show({ + x: e.pageX, + y: e.pageY, + orientation: "left", + items: [ + { title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" }, + { title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" }, + { title: "----" }, + { title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" }, + { title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" } + ], + selectMenuItemHandler: (item) => handleAddNewAttributeCommand(item.command) + }); + }} + /> + { error && (
    {typeof error === "object" && "message" in error && typeof error.message === "string" && error.message} From efd713dc6129114b5988352ea27f08de0628f3c7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 19:54:02 +0300 Subject: [PATCH 265/532] chore(react/ribbon): add blur & keydown events --- .../attribute_widgets/attribute_editor.ts | 38 ------------------- apps/client/src/widgets/react/CKEditor.tsx | 7 ++-- .../ribbon/components/AttributeEditor.tsx | 2 + 3 files changed, 6 insertions(+), 41 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index cb6d8272e..719c77b85 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -1,42 +1,4 @@ -import { t } from "../../services/i18n.js"; -import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import contextMenuService from "../../menus/context_menu.js"; -import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5"; -import noteCreateService from "../../services/note_create.js"; -import attributeService from "../../services/attributes.js"; -import type AttributeDetailWidget from "./attribute_detail.js"; -import type { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js"; -import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js"; - export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> { - private attributeDetailWidget: AttributeDetailWidget; - private $editor!: JQuery; - private $addNewAttributeButton!: JQuery; - private $saveAttributesButton!: JQuery; - - private textEditor!: AttributeEditor; - private lastUpdatedNoteId!: string | undefined; - - constructor(attributeDetailWidget: AttributeDetailWidget) { - super(); - - this.attributeDetailWidget = attributeDetailWidget; - } - - doRender() { - this.$widget = $(TPL); - this.$editor = this.$widget.find(".attribute-list-editor"); - - this.initialized = this.initEditor(); - - this.$editor.on("keydown", async (e) => { - this.attributeDetailWidget.hide(); - }); - - this.$editor.on("blur", () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/zadam/trilium/issues/4160 - - this.$saveAttributesButton.on("click", () => this.save()); - } // triggered from keyboard shortcut async addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) { diff --git a/apps/client/src/widgets/react/CKEditor.tsx b/apps/client/src/widgets/react/CKEditor.tsx index 00399c6e8..fcd61179b 100644 --- a/apps/client/src/widgets/react/CKEditor.tsx +++ b/apps/client/src/widgets/react/CKEditor.tsx @@ -11,9 +11,11 @@ interface CKEditorOpts { disableSpellcheck?: boolean; onChange?: (newValue?: string) => void; onClick?: (e: MouseEvent, pos?: ModelPosition | null) => void; + onKeyDown?: (e: KeyboardEvent) => void; + onBlur?: () => void; } -export default function CKEditor({ currentValue, className, tabIndex, editor, config, disableNewlines, disableSpellcheck, onChange, onClick }: CKEditorOpts) { +export default function CKEditor({ currentValue, editor, config, disableNewlines, disableSpellcheck, onChange, onClick, ...restProps }: CKEditorOpts) { const editorContainerRef = useRef(null); const textEditorRef = useRef(null); @@ -62,14 +64,13 @@ export default function CKEditor({ currentValue, className, tabIndex, editor, co return (
    { if (onClick) { const pos = textEditorRef.current?.model.document.selection.getFirstPosition(); onClick(e, pos); } }} + {...restProps} /> ) } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index f868744c5..0e60d847e 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -282,6 +282,8 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co setState("showHelpTooltip"); } }} + onKeyDown={() => attributeDetailWidget.hide()} + onBlur={() => save()} disableNewlines disableSpellcheck /> From db687197de06e0efd982b621130aa081e815435b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 20:31:00 +0300 Subject: [PATCH 266/532] chore(react/ribbon): add focus to attribute editor --- .../attribute_widgets/attribute_editor.ts | 22 ---------------- apps/client/src/widgets/react/CKEditor.tsx | 23 +++++++++++++++-- apps/client/src/widgets/ribbon/Ribbon.tsx | 5 ++-- .../ribbon/components/AttributeEditor.tsx | 25 ++++++++++++++++--- .../src/widgets/ribbon/ribbon-interface.ts | 1 + 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 719c77b85..6c732b779 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -18,14 +18,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem } } - async save() { - if (this.lastUpdatedNoteId !== this.noteId) { - // https://github.com/zadam/trilium/issues/3090 - console.warn("Ignoring blur event because a different note is loaded."); - return; - } - } - dataChanged() { this.lastUpdatedNoteId = this.noteId; } @@ -49,20 +41,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem focus() { this.$editor.trigger("focus"); - this.textEditor.model.change((writer) => { - const documentRoot = this.textEditor.editing.model.document.getRoot(); - if (!documentRoot) { - return; - } - const positionAt = writer.createPositionAt(documentRoot, "end"); - writer.setSelection(positionAt); - }); - } - - entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (loadResults.getAttributeRows(this.componentId).find((attr) => attributeService.isAffecting(attr, this.note))) { - this.refresh(); - } } } diff --git a/apps/client/src/widgets/react/CKEditor.tsx b/apps/client/src/widgets/react/CKEditor.tsx index fcd61179b..a9f49d887 100644 --- a/apps/client/src/widgets/react/CKEditor.tsx +++ b/apps/client/src/widgets/react/CKEditor.tsx @@ -1,7 +1,13 @@ import { CKTextEditor, type AttributeEditor, type EditorConfig, type ModelPosition } from "@triliumnext/ckeditor5"; -import { useEffect, useRef } from "preact/compat"; +import { useEffect, useImperativeHandle, useRef } from "preact/compat"; +import { MutableRef } from "preact/hooks"; + +export interface CKEditorApi { + focus: () => void; +} interface CKEditorOpts { + apiRef: MutableRef; currentValue?: string; className: string; tabIndex?: number; @@ -15,9 +21,22 @@ interface CKEditorOpts { onBlur?: () => void; } -export default function CKEditor({ currentValue, editor, config, disableNewlines, disableSpellcheck, onChange, onClick, ...restProps }: CKEditorOpts) { +export default function CKEditor({ apiRef, currentValue, editor, config, disableNewlines, disableSpellcheck, onChange, onClick, ...restProps }: CKEditorOpts) { const editorContainerRef = useRef(null); const textEditorRef = useRef(null); + useImperativeHandle(apiRef, () => { + return { + focus() { + editorContainerRef.current?.focus(); + textEditorRef.current?.model.change((writer) => { + const documentRoot = textEditorRef.current?.editing.model.document.getRoot(); + if (documentRoot) { + writer.setSelection(writer.createPositionAt(documentRoot, "end")); + } + }); + } + }; + }, [ editorContainerRef ]); useEffect(() => { if (!editorContainerRef.current) return; diff --git a/apps/client/src/widgets/ribbon/Ribbon.tsx b/apps/client/src/widgets/ribbon/Ribbon.tsx index 032a905a6..74e1d19b4 100644 --- a/apps/client/src/widgets/ribbon/Ribbon.tsx +++ b/apps/client/src/widgets/ribbon/Ribbon.tsx @@ -148,7 +148,7 @@ const TAB_CONFIGURATION = numberObjectsInPlace([ ]); export default function Ribbon() { - const { note, ntxId, hoistedNoteId, notePath, noteContext } = useNoteContext(); + const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext(); const titleContext: TitleContext = { note }; const [ activeTabIndex, setActiveTabIndex ] = useState(); const filteredTabs = useMemo(() => TAB_CONFIGURATION.filter(tab => typeof tab.show === "boolean" ? tab.show : tab.show?.(titleContext)), [ titleContext, note ]); @@ -190,7 +190,8 @@ export default function Ribbon() { ntxId, hoistedNoteId, notePath, - noteContext + noteContext, + componentId }); })}
    diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 0e60d847e..7a82962cc 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -3,8 +3,8 @@ import { AttributeEditor as CKEditorAttributeEditor, MentionFeed, ModelElement, import { t } from "../../../services/i18n"; import server from "../../../services/server"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; -import CKEditor from "../../react/CKEditor"; -import { useLegacyWidget, useTooltip } from "../../react/hooks"; +import CKEditor, { CKEditorApi } from "../../react/CKEditor"; +import { useLegacyWidget, useTooltip, useTriliumEventBeta } from "../../react/hooks"; import FAttribute from "../../../entities/fattribute"; import attribute_renderer from "../../../services/attribute_renderer"; import FNote from "../../../entities/fnote"; @@ -19,6 +19,7 @@ import froca from "../../../services/froca"; import contextMenu from "../../../menus/context_menu"; import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; import { AttributeType } from "@triliumnext/commons"; +import attributes from "../../../services/attributes"; type AttributeCommandNames = FilteredCommandNames; @@ -86,6 +87,7 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co const lastSavedContent = useRef(); const currentValueRef = useRef(initialValue); const wrapperRef = useRef(null); + const editorRef = useRef(); const { showTooltip, hideTooltip } = useTooltip(wrapperRef, { trigger: "focus", @@ -207,9 +209,23 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co }, 100); } - useEffect(() => { + // Refresh with note + function refresh() { renderOwnedAttributes(note.getOwnedAttributes(), true); - }, [ note ]); + } + + useEffect(() => refresh(), [ note ]); + useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => { + if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) { + console.log("Trigger due to entities reloaded"); + refresh(); + } + }); + + // Focus on show. + useEffect(() => { + setTimeout(() => editorRef.current?.focus(), 0); + }, []); return ( <> @@ -224,6 +240,7 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co }} > Date: Sat, 23 Aug 2025 20:35:19 +0300 Subject: [PATCH 267/532] chore(react/ribbon): unable to create notes in attribute editor --- .../attribute_widgets/attribute_editor.ts | 22 ------------------- .../src/widgets/ribbon/OwnedAttributesTab.tsx | 10 +++++++-- .../ribbon/components/AttributeEditor.tsx | 18 ++++++++++++--- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 6c732b779..87db417f5 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -18,29 +18,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem } } - dataChanged() { - this.lastUpdatedNoteId = this.noteId; - } - - async createNoteForReferenceLink(title: string) { - let result; - if (this.notePath) { - result = await noteCreateService.createNoteWithTypePrompt(this.notePath, { - activate: false, - title: title - }); - } - - return result?.note?.getBestNotePathString(); - } - async updateAttributeList(attributes: FAttribute[]) { await this.renderOwnedAttributes(attributes, false); } - - focus() { - this.$editor.trigger("focus"); - - - } } diff --git a/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx index 2c168841f..daf9d1495 100644 --- a/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx +++ b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx @@ -1,10 +1,16 @@ import AttributeEditor from "./components/AttributeEditor"; import { TabContext } from "./ribbon-interface"; -export default function OwnedAttributesTab({ note }: TabContext) { +export default function OwnedAttributesTab({ note, notePath, componentId }: TabContext) { return (
    - { note && } + { note && ( + + )}
    ) } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 7a82962cc..2ef34d22e 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -20,6 +20,7 @@ import contextMenu from "../../../menus/context_menu"; import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; import { AttributeType } from "@triliumnext/commons"; import attributes from "../../../services/attributes"; +import note_create from "../../../services/note_create"; type AttributeCommandNames = FilteredCommandNames; @@ -75,9 +76,9 @@ const mentionSetup: MentionFeed[] = [ ]; -export default function AttributeEditor({ note, componentId }: { note: FNote, componentId: string }) { +export default function AttributeEditor({ note, componentId, notePath }: { note: FNote, componentId: string, notePath?: string | null }) { const parentComponent = useContext(ParentComponent); - injectLoadReferenceLinkTitle(parentComponent); + injectLoadReferenceLinkTitle(parentComponent, notePath); const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); const [ error, setError ] = useState(); @@ -373,7 +374,7 @@ function getClickIndex(pos: ModelPosition) { return clickIndex; } -function injectLoadReferenceLinkTitle(component: Component | null) { +function injectLoadReferenceLinkTitle(component: Component | null, notePath?: string | null) { if (!component) return; (component as any).loadReferenceLinkTitle = async ($el: JQuery, href: string) => { const { noteId } = link.parseNavigationStateFromUrl(href); @@ -382,4 +383,15 @@ function injectLoadReferenceLinkTitle(component: Component | null) { $el.text(title); } + (component as any).createNoteForReferenceLink = async (title: string) => { + let result; + if (notePath) { + result = await note_create.createNoteWithTypePrompt(notePath, { + activate: false, + title: title + }); + } + + return result?.note?.getBestNotePathString(); + } } \ No newline at end of file From a934760960ee69967418ba7c7eb3727365c81ce7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 20:44:03 +0300 Subject: [PATCH 268/532] refactor(react/ribbon): use custom method for injecting handlers --- apps/client/src/widgets/react/hooks.tsx | 7 +++ .../ribbon/components/AttributeEditor.tsx | 51 +++++++++---------- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 304b9b22f..7a47674b8 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -538,4 +538,11 @@ export function useTooltip(elRef: RefObject, config: Partial) { + const parentComponent = useContext(ParentComponent); + useEffect(() => { + Object.assign(parentComponent as any, handlers); + }, [ handlers ]) } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 2ef34d22e..9670b06fe 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -1,10 +1,10 @@ -import { useContext, useEffect, useRef, useState } from "preact/hooks" +import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks" import { AttributeEditor as CKEditorAttributeEditor, MentionFeed, ModelElement, ModelNode, ModelPosition } from "@triliumnext/ckeditor5"; import { t } from "../../../services/i18n"; import server from "../../../services/server"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; import CKEditor, { CKEditorApi } from "../../react/CKEditor"; -import { useLegacyWidget, useTooltip, useTriliumEventBeta } from "../../react/hooks"; +import { useLegacyImperativeHandlers, useLegacyWidget, useTooltip, useTriliumEventBeta } from "../../react/hooks"; import FAttribute from "../../../entities/fattribute"; import attribute_renderer from "../../../services/attribute_renderer"; import FNote from "../../../entities/fnote"; @@ -77,9 +77,6 @@ const mentionSetup: MentionFeed[] = [ export default function AttributeEditor({ note, componentId, notePath }: { note: FNote, componentId: string, notePath?: string | null }) { - const parentComponent = useContext(ParentComponent); - injectLoadReferenceLinkTitle(parentComponent, notePath); - const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); const [ error, setError ] = useState(); const [ needsSaving, setNeedsSaving ] = useState(false); @@ -227,6 +224,28 @@ export default function AttributeEditor({ note, componentId, notePath }: { note: useEffect(() => { setTimeout(() => editorRef.current?.focus(), 0); }, []); + + // Interaction with CKEditor. + useLegacyImperativeHandlers(useMemo(() => ({ + loadReferenceLinkTitle: async ($el: JQuery, href: string) => { + const { noteId } = link.parseNavigationStateFromUrl(href); + const note = noteId ? await froca.getNote(noteId, true) : null; + const title = note ? note.title : "[missing]"; + + $el.text(title); + }, + createNoteForReferenceLink: async (title: string) => { + let result; + if (notePath) { + result = await note_create.createNoteWithTypePrompt(notePath, { + activate: false, + title: title + }); + } + + return result?.note?.getBestNotePathString(); + } + }), [ notePath ])) return ( <> @@ -373,25 +392,3 @@ function getClickIndex(pos: ModelPosition) { return clickIndex; } - -function injectLoadReferenceLinkTitle(component: Component | null, notePath?: string | null) { - if (!component) return; - (component as any).loadReferenceLinkTitle = async ($el: JQuery, href: string) => { - const { noteId } = link.parseNavigationStateFromUrl(href); - const note = noteId ? await froca.getNote(noteId, true) : null; - const title = note ? note.title : "[missing]"; - - $el.text(title); - } - (component as any).createNoteForReferenceLink = async (title: string) => { - let result; - if (notePath) { - result = await note_create.createNoteWithTypePrompt(notePath, { - activate: false, - title: title - }); - } - - return result?.note?.getBestNotePathString(); - } -} \ No newline at end of file From d53faa8c01b60f14aef0f62f2fbdc61c6046876a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 20:49:54 +0300 Subject: [PATCH 269/532] refactor(react/ribbon): imperative api for saving, reloading, updating attributes --- .../attribute_widgets/attribute_editor.ts | 4 -- .../ribbon/components/AttributeEditor.tsx | 11 ++++- .../ribbon_widgets/owned_attribute_list.ts | 40 ------------------- 3 files changed, 9 insertions(+), 46 deletions(-) delete mode 100644 apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts index 87db417f5..38eec14e5 100644 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts @@ -17,8 +17,4 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem this.handleAddNewAttributeCommand("addNewRelation"); } } - - async updateAttributeList(attributes: FAttribute[]) { - await this.renderOwnedAttributes(attributes, false); - } } diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index 9670b06fe..bb0e9b27e 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -17,7 +17,7 @@ import Component from "../../../components/component"; import link from "../../../services/link"; import froca from "../../../services/froca"; import contextMenu from "../../../menus/context_menu"; -import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; +import type { CommandData, CommandListenerData, FilteredCommandNames } from "../../../components/app_context"; import { AttributeType } from "@triliumnext/commons"; import attributes from "../../../services/attributes"; import note_create from "../../../services/note_create"; @@ -245,7 +245,14 @@ export default function AttributeEditor({ note, componentId, notePath }: { note: return result?.note?.getBestNotePathString(); } - }), [ notePath ])) + }), [ notePath ])); + + // Interaction with the attribute editor. + useLegacyImperativeHandlers(useMemo(() => ({ + saveAttributesCommand: save, + reloadAttributesCommand: refresh, + updateAttributeListCommand: ({ attributes }: CommandListenerData<"updateAttributeList">) => renderOwnedAttributes(attributes as FAttribute[], false) + }), [])); return ( <> diff --git a/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts b/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts deleted file mode 100644 index 6b036bd0e..000000000 --- a/apps/client/src/widgets/ribbon_widgets/owned_attribute_list.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { t } from "../../services/i18n.js"; -import NoteContextAwareWidget from "../note_context_aware_widget.js"; -import AttributeDetailWidget from "../attribute_widgets/attribute_detail.js"; -import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js"; -import type { CommandListenerData } from "../../components/app_context.js"; -import type FAttribute from "../../entities/fattribute.js"; - -export default class OwnedAttributeListWidget extends NoteContextAwareWidget { - - private attributeDetailWidget: AttributeDetailWidget; - private attributeEditorWidget: AttributeEditorWidget; - private $title!: JQuery; - - constructor() { - super(); - - this.attributeDetailWidget = new AttributeDetailWidget().contentSized().setParent(this); - - this.attributeEditorWidget = new AttributeEditorWidget(this.attributeDetailWidget).contentSized().setParent(this); - - this.child(this.attributeEditorWidget, this.attributeDetailWidget); - } - - async saveAttributesCommand() { - await this.attributeEditorWidget.save(); - } - - async reloadAttributesCommand() { - await this.attributeEditorWidget.refresh(); - } - - async updateAttributeListCommand({ attributes }: CommandListenerData<"updateAttributeList">) { - // TODO: See why we need FAttribute[] and Attribute[] - await this.attributeEditorWidget.updateAttributeList(attributes as FAttribute[]); - } - - focus() { - this.attributeEditorWidget.focus(); - } -} From 9f217b88e45256452c47a0c8b24656e6a973cc4b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 23 Aug 2025 20:59:13 +0300 Subject: [PATCH 270/532] refactor(react/ribbon): set up keyboard shortcuts --- .../attribute_widgets/attribute_editor.ts | 20 ------------------- apps/client/src/widgets/react/hooks.tsx | 1 - .../src/widgets/ribbon/OwnedAttributesTab.tsx | 8 ++------ .../ribbon/components/AttributeEditor.tsx | 12 ++++++++++- 4 files changed, 13 insertions(+), 28 deletions(-) delete mode 100644 apps/client/src/widgets/attribute_widgets/attribute_editor.ts diff --git a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts b/apps/client/src/widgets/attribute_widgets/attribute_editor.ts deleted file mode 100644 index 38eec14e5..000000000 --- a/apps/client/src/widgets/attribute_widgets/attribute_editor.ts +++ /dev/null @@ -1,20 +0,0 @@ -export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> { - - // triggered from keyboard shortcut - async addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) { - if (this.isNoteContext(ntxId)) { - await this.refresh(); - - this.handleAddNewAttributeCommand("addNewLabel"); - } - } - - // triggered from keyboard shortcut - async addNewRelationEvent({ ntxId }: EventData<"addNewRelation">) { - if (this.isNoteContext(ntxId)) { - await this.refresh(); - - this.handleAddNewAttributeCommand("addNewRelation"); - } - } -} diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 7a47674b8..45a751b10 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -450,7 +450,6 @@ export function useLegacyWidget(widgetFactory: () => T, { // Inject the note context. useEffect(() => { - console.log("Injecting note context"); if (noteContext && widget instanceof NoteContextAwareWidget) { widget.activeContextChangedEvent({ noteContext }); } diff --git a/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx index daf9d1495..bc1c73b3e 100644 --- a/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx +++ b/apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx @@ -1,15 +1,11 @@ import AttributeEditor from "./components/AttributeEditor"; import { TabContext } from "./ribbon-interface"; -export default function OwnedAttributesTab({ note, notePath, componentId }: TabContext) { +export default function OwnedAttributesTab({ note, ...restProps }: TabContext) { return (
    { note && ( - + )}
    ) diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index bb0e9b27e..14d96f6be 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -76,7 +76,7 @@ const mentionSetup: MentionFeed[] = [ ]; -export default function AttributeEditor({ note, componentId, notePath }: { note: FNote, componentId: string, notePath?: string | null }) { +export default function AttributeEditor({ note, componentId, notePath, ntxId }: { note: FNote, componentId: string, notePath?: string | null, ntxId?: string | null }) { const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">(); const [ error, setError ] = useState(); const [ needsSaving, setNeedsSaving ] = useState(false); @@ -253,6 +253,16 @@ export default function AttributeEditor({ note, componentId, notePath }: { note: reloadAttributesCommand: refresh, updateAttributeListCommand: ({ attributes }: CommandListenerData<"updateAttributeList">) => renderOwnedAttributes(attributes as FAttribute[], false) }), [])); + + // Keyboard shortcuts + useTriliumEventBeta("addNewLabel", ({ ntxId: eventNtxId }) => { + if (eventNtxId !== ntxId) return; + handleAddNewAttributeCommand("addNewLabel"); + }); + useTriliumEventBeta("addNewRelation", ({ ntxId: eventNtxId }) => { + if (eventNtxId !== ntxId) return; + handleAddNewAttributeCommand("addNewRelation"); + }); return ( <> From 753d5529b22b2a02e7d01882982affe9e7412d2a Mon Sep 17 00:00:00 2001 From: perf3ct Date: Sat, 23 Aug 2025 18:40:11 +0000 Subject: [PATCH 271/532] feat(jump_to): get the styling very close to what we want it to look like... --- apps/client/src/services/note_autocomplete.ts | 30 +++++++- apps/client/src/stylesheets/style.css | 76 +++++++++++++++++-- .../src/stylesheets/theme-next/base.css | 4 +- .../src/widgets/dialogs/jump_to_note.tsx | 29 ++++++- 4 files changed, 130 insertions(+), 9 deletions(-) diff --git a/apps/client/src/services/note_autocomplete.ts b/apps/client/src/services/note_autocomplete.ts index 1138b191e..66031f482 100644 --- a/apps/client/src/services/note_autocomplete.ts +++ b/apps/client/src/services/note_autocomplete.ts @@ -36,6 +36,8 @@ export interface Suggestion { commandId?: string; commandDescription?: string; commandShortcut?: string; + attributeSnippet?: string; + highlightedAttributeSnippet?: string; } export interface Options { @@ -323,7 +325,33 @@ function initNoteAutocomplete($el: JQuery, options?: Options) { html += '
    '; return html; } - return ` ${suggestion.highlightedNotePathTitle}`; + // Add special class for search-notes action + const actionClass = suggestion.action === "search-notes" ? "search-notes-action" : ""; + + // Choose appropriate icon based on action + let iconClass = suggestion.icon ?? "bx bx-note"; + if (suggestion.action === "search-notes") { + iconClass = "bx bx-search"; + } else if (suggestion.action === "create-note") { + iconClass = "bx bx-plus"; + } else if (suggestion.action === "external-link") { + iconClass = "bx bx-link-external"; + } + + // Simplified HTML structure without nested divs + let html = `
    `; + html += ``; + html += ``; + html += `${suggestion.highlightedNotePathTitle}`; + + // Add attribute snippet inline if available + if (suggestion.highlightedAttributeSnippet) { + html += `${suggestion.highlightedAttributeSnippet}`; + } + + html += ``; + html += `
    `; + return html; } }, // we can't cache identical searches because notes can be created / renamed, new recent notes can be added diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index c5b24f88e..915c99afe 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1773,20 +1773,69 @@ textarea { font-size: 1em; } +.jump-to-note-dialog .modal-dialog { + max-width: 900px; + width: 90%; +} + .jump-to-note-dialog .modal-header { align-items: center; } .jump-to-note-dialog .modal-body { padding: 0; + min-height: 200px; } .jump-to-note-results .aa-dropdown-menu { - max-height: 40vh; + max-height: calc(80vh - 200px); + width: 100%; + max-width: none; + overflow-y: auto; + overflow-x: hidden; + text-overflow: ellipsis; + box-shadow: -30px 50px 93px -50px black; +} + +.jump-to-note-results { + width: 100%; } .jump-to-note-results .aa-suggestions { - padding: 1rem; + padding: 0; + width: 100%; +} + +.jump-to-note-results .aa-dropdown-menu .aa-suggestion { + white-space: normal; + padding: 2px 12px !important; + line-height: 1.1; + position: relative; + border-radius: 0; + margin: 0 !important; +} + +.jump-to-note-results .note-suggestion { + margin: 0; + padding: 0; + line-height: 1; +} + +.jump-to-note-results .aa-suggestion:not(:last-child)::after { + display: none; /* Remove dividers for more compact look */ +} + +.jump-to-note-results .aa-suggestion:last-child::after { + display: none; +} + +.jump-to-note-results .aa-suggestion.disabled::after { + display: none; +} + +.jump-to-note-results .aa-dropdown-menu .aa-suggestion:hover, +.jump-to-note-results .aa-dropdown-menu .aa-cursor { + background-color: var(--hover-item-background-color, #f8f9fa); } /* Command palette styling */ @@ -1804,8 +1853,24 @@ textarea { .jump-to-note-dialog .aa-cursor .command-suggestion, .jump-to-note-dialog .aa-suggestion:hover .command-suggestion { - border-left-color: var(--link-color); - background-color: var(--hover-background-color); + background-color: transparent; +} + +.jump-to-note-dialog .show-in-full-search, +.jump-to-note-results .show-in-full-search { + border-top: 1px solid var(--main-border-color); + padding-top: 12px; + margin-top: 12px; +} + +.jump-to-note-results .aa-suggestion .search-notes-action { + border-top: 1px solid var(--main-border-color); + margin-top: 8px; + padding-top: 8px; +} + +.jump-to-note-results .aa-suggestion:has(.search-notes-action)::after { + display: none; } .jump-to-note-dialog .command-icon { @@ -2262,7 +2327,8 @@ footer.webview-footer button { /* Search result highlighting */ .search-result-title b, -.search-result-content b { +.search-result-content b, +.search-result-attributes b { font-weight: 900; color: var(--admonition-warning-accent-color); } diff --git a/apps/client/src/stylesheets/theme-next/base.css b/apps/client/src/stylesheets/theme-next/base.css index c992f7ecb..f8cdc1c98 100644 --- a/apps/client/src/stylesheets/theme-next/base.css +++ b/apps/client/src/stylesheets/theme-next/base.css @@ -532,8 +532,8 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after { /* List item */ .jump-to-note-dialog .aa-suggestions div, .note-detail-empty .aa-suggestions div { - border-radius: 6px; - padding: 6px 12px; + border-radius: 0; + padding: 12px 16px; color: var(--menu-text-color); cursor: default; } diff --git a/apps/client/src/widgets/dialogs/jump_to_note.tsx b/apps/client/src/widgets/dialogs/jump_to_note.tsx index 3af1b1aba..c3185585f 100644 --- a/apps/client/src/widgets/dialogs/jump_to_note.tsx +++ b/apps/client/src/widgets/dialogs/jump_to_note.tsx @@ -9,6 +9,7 @@ import appContext from "../../components/app_context"; import commandRegistry from "../../services/command_registry"; import { refToJQuerySelector } from "../react/react_utils"; import useTriliumEvent from "../react/hooks"; +import shortcutService from "../../services/shortcuts"; const KEEP_LAST_SEARCH_FOR_X_SECONDS = 120; @@ -83,6 +84,27 @@ function JumpToNoteDialogComponent() { $autoComplete .trigger("focus") .trigger("select"); + + // Add keyboard shortcut for full search + shortcutService.bindElShortcut($autoComplete, "ctrl+return", () => { + if (!isCommandMode) { + showInFullSearch(); + } + }); + } + + async function showInFullSearch() { + try { + setShown(false); + const searchString = actualText.current?.trim(); + if (searchString && !searchString.startsWith(">")) { + await appContext.triggerCommand("searchNotes", { + searchString + }); + } + } catch (error) { + console.error("Failed to trigger full search:", error); + } } return ( @@ -108,7 +130,12 @@ function JumpToNoteDialogComponent() { />} onShown={onShown} onHidden={() => setShown(false)} - footer={!isCommandMode &&
    + +
    @@ -114,7 +163,21 @@ function SearchOption({ note, title, children, help, attributeName, attributeTyp ) } -function SearchStringOption() { +function SearchStringOption({ note, refreshResults }: { note: FNote, refreshResults: () => void }) { + const currentValue = useRef(""); + const spacedUpdate = useSpacedUpdate(async () => { + const searchString = currentValue.current; + appContext.lastSearchString = searchString; + + await attributes.setAttribute(note, "label", "searchString", searchString); + + if (note.title.startsWith(t("search_string.search_prefix"))) { + await server.put(`notes/${note.noteId}/title`, { + title: `${t("search_string.search_prefix")} ${searchString.length < 30 ? searchString : `${searchString.substr(0, 30)}…`}` + }); + } + }, 1000); + return @@ -133,6 +196,21 @@ function SearchStringOption() { { + currentValue.current = text; + spacedUpdate.scheduleUpdate(); + }} + onKeyDown={async (e) => { + if (e.key === "Enter") { + e.preventDefault(); + + // this also in effect disallows new lines in query string. + // on one hand, this makes sense since search string is a label + // on the other hand, it could be nice for structuring long search string. It's probably a niche case though. + await spacedUpdate.updateNowIfNecessary(); + refreshResults(); + } + }} /> } \ No newline at end of file diff --git a/apps/client/src/widgets/ribbon_widgets/search_definition.ts b/apps/client/src/widgets/ribbon_widgets/search_definition.ts index 24e59c473..ed3f3dde7 100644 --- a/apps/client/src/widgets/ribbon_widgets/search_definition.ts +++ b/apps/client/src/widgets/ribbon_widgets/search_definition.ts @@ -43,7 +43,7 @@ const TPL = /*html*/`
    + + + + + }
    ) @@ -163,7 +191,7 @@ function SearchOption({ note, title, children, help, attributeName, attributeTyp ) } -function SearchStringOption({ note, refreshResults }: { note: FNote, refreshResults: () => void }) { +function SearchStringOption({ note, refreshResults }: SearchOptionProps) { const currentValue = useRef(""); const spacedUpdate = useSpacedUpdate(async () => { const searchString = currentValue.current; diff --git a/apps/client/src/widgets/ribbon_widgets/search_definition.ts b/apps/client/src/widgets/ribbon_widgets/search_definition.ts index ed3f3dde7..a77ebc743 100644 --- a/apps/client/src/widgets/ribbon_widgets/search_definition.ts +++ b/apps/client/src/widgets/ribbon_widgets/search_definition.ts @@ -153,24 +153,6 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { this.$searchOptions.empty(); - for (const OptionClass of OPTION_CLASSES) { - const { attributeType, optionName } = OptionClass; - - const attr = this.note.getAttribute(attributeType as AttributeType, optionName); - - this.$widget.find(`[data-search-option-add='${optionName}'`).toggle(!attr); - - if (attr) { - const searchOption = new OptionClass(attr, this.note).setParent(this); - this.child(searchOption); - - const renderedEl = searchOption.render(); - if (renderedEl) { - this.$searchOptions.append(renderedEl); - } - } - } - const actions = bulkActionService.parseActions(this.note); const renderedEls = actions .map((action) => renderReactWidget(this, action.doRender())) diff --git a/apps/client/src/widgets/search_options/ancestor.ts b/apps/client/src/widgets/search_options/ancestor.ts index 8a29b675a..fa9fc672e 100644 --- a/apps/client/src/widgets/search_options/ancestor.ts +++ b/apps/client/src/widgets/search_options/ancestor.ts @@ -51,13 +51,6 @@ const TPL = /*html*/` `; export default class Ancestor extends AbstractSearchOption { - static get optionName() { - return "ancestor"; - } - static get attributeType() { - return "relation"; - } - static async create(noteId: string) { await AbstractSearchOption.setAttribute(noteId, "relation", "ancestor", "root"); } diff --git a/apps/client/src/widgets/search_options/debug.ts b/apps/client/src/widgets/search_options/debug.ts index fb19541b4..bdcf585e4 100644 --- a/apps/client/src/widgets/search_options/debug.ts +++ b/apps/client/src/widgets/search_options/debug.ts @@ -20,13 +20,6 @@ const TPL = /*html*/` `; export default class Debug extends AbstractSearchOption { - static get optionName() { - return "debug"; - } - static get attributeType() { - return "label"; - } - static async create(noteId: string) { await AbstractSearchOption.setAttribute(noteId, "label", "debug"); } diff --git a/apps/client/src/widgets/search_options/fast_search.ts b/apps/client/src/widgets/search_options/fast_search.ts index 52cc4acfc..7b77b1ca8 100644 --- a/apps/client/src/widgets/search_options/fast_search.ts +++ b/apps/client/src/widgets/search_options/fast_search.ts @@ -19,12 +19,6 @@ const TPL = /*html*/` `; export default class FastSearch extends AbstractSearchOption { - static get optionName() { - return "fastSearch"; - } - static get attributeType() { - return "label"; - } static async create(noteId: string) { await AbstractSearchOption.setAttribute(noteId, "label", "fastSearch"); diff --git a/apps/client/src/widgets/search_options/include_archived_notes.ts b/apps/client/src/widgets/search_options/include_archived_notes.ts index 7e8eea778..98f33f4d6 100644 --- a/apps/client/src/widgets/search_options/include_archived_notes.ts +++ b/apps/client/src/widgets/search_options/include_archived_notes.ts @@ -13,12 +13,6 @@ const TPL = /*html*/` `; export default class IncludeArchivedNotes extends AbstractSearchOption { - static get optionName() { - return "includeArchivedNotes"; - } - static get attributeType() { - return "label"; - } static async create(noteId: string) { await AbstractSearchOption.setAttribute(noteId, "label", "includeArchivedNotes"); diff --git a/apps/client/src/widgets/search_options/limit.ts b/apps/client/src/widgets/search_options/limit.ts index 20fe0ec0a..1925f29f1 100644 --- a/apps/client/src/widgets/search_options/limit.ts +++ b/apps/client/src/widgets/search_options/limit.ts @@ -26,13 +26,6 @@ export default class Limit extends AbstractSearchOption { private $limit!: JQuery; - static get optionName() { - return "limit"; - } - static get attributeType() { - return "label"; - } - static async create(noteId: string) { await AbstractSearchOption.setAttribute(noteId, "label", "limit", "10"); } diff --git a/apps/client/src/widgets/search_options/order_by.ts b/apps/client/src/widgets/search_options/order_by.ts index 491693c84..1c6ddd46d 100644 --- a/apps/client/src/widgets/search_options/order_by.ts +++ b/apps/client/src/widgets/search_options/order_by.ts @@ -37,13 +37,6 @@ const TPL = /*html*/` export default class OrderBy extends AbstractSearchOption { - static get optionName() { - return "orderBy"; - } - static get attributeType() { - return "label"; - } - static async create(noteId: string) { await AbstractSearchOption.setAttribute(noteId, "label", "orderBy", "relevancy"); await AbstractSearchOption.setAttribute(noteId, "label", "orderDirection", "asc"); diff --git a/apps/client/src/widgets/search_options/search_script.ts b/apps/client/src/widgets/search_options/search_script.ts index d93a64a83..931886424 100644 --- a/apps/client/src/widgets/search_options/search_script.ts +++ b/apps/client/src/widgets/search_options/search_script.ts @@ -33,12 +33,6 @@ const TPL = /*html*/` `; export default class SearchScript extends AbstractSearchOption { - static get optionName() { - return "searchScript"; - } - static get attributeType() { - return "relation"; - } static async create(noteId: string) { await AbstractSearchOption.setAttribute(noteId, "relation", "searchScript", "root"); diff --git a/apps/client/src/widgets/search_options/search_string.ts b/apps/client/src/widgets/search_options/search_string.ts index 913dcb249..fe5dcf57b 100644 --- a/apps/client/src/widgets/search_options/search_string.ts +++ b/apps/client/src/widgets/search_options/search_string.ts @@ -11,13 +11,6 @@ export default class SearchString extends AbstractSearchOption { private $searchString!: JQuery; private spacedUpdate!: SpacedUpdate; - static get optionName() { - return "searchString"; - } - static get attributeType() { - return "label"; - } - static async create(noteId: string) { await AbstractSearchOption.setAttribute(noteId, "label", "searchString"); } From 80ad87671af29915e41bf95262cf9646eae966d3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 24 Aug 2025 16:02:15 +0300 Subject: [PATCH 293/532] chore(react/ribbon): search & execute button --- .../widgets/ribbon/SearchDefinitionTab.tsx | 10 ++++++++ .../ribbon_widgets/search_definition.ts | 25 ------------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx b/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx index 9607f3784..45954f1e8 100644 --- a/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx +++ b/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx @@ -156,6 +156,16 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) { keyboardShortcut="Enter" onClick={refreshResults} /> + +
    diff --git a/apps/client/src/widgets/ribbon_widgets/search_definition.ts b/apps/client/src/widgets/ribbon_widgets/search_definition.ts index a77ebc743..a948bc89b 100644 --- a/apps/client/src/widgets/ribbon_widgets/search_definition.ts +++ b/apps/client/src/widgets/ribbon_widgets/search_definition.ts @@ -41,16 +41,6 @@ const TPL = /*html*/`
    - - - -