Merge remote-tracking branch 'origin/main' into feat/llm-stop-generation

This commit is contained in:
Elian Doran
2026-04-12 00:37:27 +03:00
208 changed files with 11532 additions and 9157 deletions

View File

@@ -1,5 +1,7 @@
# Trilium Notes - AI Coding Agent Instructions
> **Note**: When updating this file, also update `CLAUDE.md` in the repository root to keep both AI coding assistants in sync.
## Project Overview
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. Built as a TypeScript monorepo using pnpm, it implements a three-layer caching architecture (Becca/Froca/Shaca) with a widget-based UI system and supports extensive user scripting capabilities.
@@ -115,6 +117,15 @@ class MyNoteWidget extends NoteContextAwareWidget {
**Important**: Widgets use jQuery (`this.$widget`) for DOM manipulation. Don't mix React patterns here.
### Reusable Preact Components
Common UI components are available in `apps/client/src/widgets/react/` — prefer reusing these over creating custom implementations:
- `NoItems` - Empty state placeholder with icon and message (use for "no results", "too many items", error states)
- `ActionButton` - Consistent button styling with icon support
- `FormTextBox` - Text input with validation and controlled input handling
- `Slider` - Range slider with label
- `Checkbox`, `RadioButton` - Form controls
- `CollapsibleSection` - Expandable content sections
## Development Workflow
### Running & Testing
@@ -322,8 +333,26 @@ Trilium provides powerful user scripting capabilities:
- When a translated string contains **interpolated components** (e.g. links, note references) whose order may vary across languages, use `<Trans>` from `react-i18next` instead of `t()`. This lets translators reorder components freely (e.g. `"<Note/> in <Parent/>"` vs `"in <Parent/>, <Note/>"`)
- When adding a new locale, follow the step-by-step guide in `docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md`
#### Client vs Server Translation Usage
- **Client-side**: `import { t } from "../services/i18n"` with keys in `apps/client/src/translations/en/translation.json`
- **Server-side**: `import { t } from "i18next"` with keys in `apps/server/src/assets/translations/en/server.json`
- **Interpolation**: Use `{{variable}}` for normal interpolation; use `{{- variable}}` (with hyphen) for **unescaped** interpolation when the value contains special characters like quotes that shouldn't be HTML-escaped
### Storing User Preferences
- **Do not use `localStorage`** for user preferences — Trilium has a synced options system that persists across devices
- To add a new user preference:
1. Add the option type to `OptionDefinitions` in `packages/commons/src/lib/options_interface.ts`
2. Add a default value in `apps/server/src/services/options_init.ts` in the `defaultOptions` array
3. **Whitelist the option** in `apps/server/src/routes/api/options.ts` by adding it to `ALLOWED_OPTIONS` (required for client updates)
4. Use `useTriliumOption("optionName")` hook in React components to read/write the option
- Available hooks: `useTriliumOption` (string), `useTriliumOptionBool`, `useTriliumOptionInt`, `useTriliumOptionJson`
- See `docs/Developer Guide/Developer Guide/Concepts/Options/Creating a new option.md` for detailed documentation
## Testing Conventions
- **Write concise tests**: Group related assertions together in a single test case rather than creating many one-shot tests
- **Extract and test business logic**: When adding pure business logic (e.g., data transformations, migrations, validations), extract it as a separate function and always write unit tests for it
```typescript
// ETAPI test pattern
describe("etapi/feature", () => {

View File

@@ -2,6 +2,8 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
> **Note**: When updating this file, also update `.github/copilot-instructions.md` to keep both AI coding assistants in sync.
## Overview
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using pnpm, with multiple applications and shared packages.
@@ -66,6 +68,15 @@ Frontend uses a widget system (`apps/client/src/widgets/`):
- `RightPanelWidget` - Widgets displayed in the right panel
- Type-specific widgets in `type_widgets/` directory
#### Reusable Preact Components
Common UI components are available in `apps/client/src/widgets/react/` — prefer reusing these over creating custom implementations:
- `NoItems` - Empty state placeholder with icon and message (use for "no results", "too many items", error states)
- `ActionButton` - Consistent button styling with icon support
- `FormTextBox` - Text input with validation and controlled input handling
- `Slider` - Range slider with label
- `Checkbox`, `RadioButton` - Form controls
- `CollapsibleSection` - Expandable content sections
#### API Architecture
- **Internal API**: REST endpoints in `apps/server/src/routes/api/`
- **ETAPI**: External API for third-party integrations (`apps/server/src/etapi/`)
@@ -108,6 +119,8 @@ Trilium supports multiple note types, each with specialized widgets:
- Client tests can run in parallel
- E2E tests use Playwright for both server and desktop apps
- Build validation tests check artifact integrity
- **Write concise tests**: Group related assertions together in a single test case rather than creating many one-shot tests
- **Extract and test business logic**: When adding pure business logic (e.g., data transformations, migrations, validations), extract it as a separate function and always write unit tests for it
### Scripting System
Trilium provides powerful user scripting capabilities:
@@ -122,6 +135,17 @@ Trilium provides powerful user scripting capabilities:
- Third-party components (e.g., mind-map context menu) should use i18next `t()` for their labels, with the English strings added to `en/translation.json` under a dedicated namespace (e.g., `"mind-map"`)
- When a translated string contains **interpolated components** (e.g. links, note references) whose order may vary across languages, use `<Trans>` from `react-i18next` instead of `t()`. This lets translators reorder components freely (e.g. `"<Note/> in <Parent/>"` vs `"in <Parent/>, <Note/>"`)
- When adding a new locale, follow the step-by-step guide in `docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md`
- **Server-side translations** (e.g. hidden subtree titles) go in `apps/server/src/assets/translations/en/server.json`, not in the client `translation.json`
#### Client vs Server Translation Usage
- **Client-side**: `import { t } from "../services/i18n"` with keys in `apps/client/src/translations/en/translation.json`
- **Server-side**: `import { t } from "i18next"` with keys in `apps/server/src/assets/translations/en/server.json`
- **Interpolation**: Use `{{variable}}` for normal interpolation; use `{{- variable}}` (with hyphen) for **unescaped** interpolation when the value contains special characters like quotes that shouldn't be HTML-escaped
### Electron Desktop App
- Desktop entry point: `apps/desktop/src/main.ts`, window management: `apps/server/src/services/window.ts`
- IPC communication: use `electron.ipcMain.on(channel, handler)` on server side, `electron.ipcRenderer.send(channel, data)` on client side
- Electron-only features should check `isElectron()` from `apps/client/src/services/utils.ts` (client) or `utils.isElectron` (server)
### Security Considerations
- Per-note encryption with granular protected sessions
@@ -133,6 +157,16 @@ Trilium provides powerful user scripting capabilities:
- **Do not use `crypto.randomUUID()`** or other Web Crypto APIs that require secure contexts - Trilium can run over HTTP, not just HTTPS
- Use `randomString()` from `apps/client/src/services/utils.ts` for generating IDs instead
### Storing User Preferences
- **Do not use `localStorage`** for user preferences — Trilium has a synced options system that persists across devices
- To add a new user preference:
1. Add the option type to `OptionDefinitions` in `packages/commons/src/lib/options_interface.ts`
2. Add a default value in `apps/server/src/services/options_init.ts` in the `defaultOptions` array
3. **Whitelist the option** in `apps/server/src/routes/api/options.ts` by adding it to `ALLOWED_OPTIONS` (required for client updates)
4. Use `useTriliumOption("optionName")` hook in React components to read/write the option
- Available hooks: `useTriliumOption` (string), `useTriliumOptionBool`, `useTriliumOptionInt`, `useTriliumOptionJson`
- See `docs/Developer Guide/Developer Guide/Concepts/Options/Creating a new option.md` for detailed documentation
### Shared Types Policy
- Types shared between client and server belong in `@triliumnext/commons` (`packages/commons/src/lib/`)
- Import shared types directly from `@triliumnext/commons` - do not re-export them from app-specific modules
@@ -153,6 +187,20 @@ Trilium provides powerful user scripting capabilities:
- Create new package in `packages/` following existing plugin structure
- Register in `packages/ckeditor5/src/plugins.ts`
### Adding Hidden System Notes
The hidden subtree (`_hidden`) contains system notes with predictable IDs (prefixed with `_`). Defined in `apps/server/src/services/hidden_subtree.ts` via the `HiddenSubtreeItem` interface from `@triliumnext/commons`.
1. Add the note definition to `buildHiddenSubtreeDefinition()` in `apps/server/src/services/hidden_subtree.ts`
2. Add a translation key for the title in `apps/server/src/assets/translations/en/server.json` under `"hidden-subtree"`
3. The note is auto-created on startup by `checkHiddenSubtree()` — uses deterministic IDs so all sync cluster instances generate the same structure
4. Key properties: `id` (must start with `_`), `title`, `type`, `icon` (format: `bx-icon-name` without `bx ` prefix), `attributes`, `children`, `content`
5. Use `enforceAttributes: true` to keep attributes in sync, `enforceBranches: true` for correct placement, `enforceDeleted: true` to remove deprecated notes
6. For launcher bar entries, see `hidden_subtree_launcherbar.ts`; for templates, see `hidden_subtree_templates.ts`
### Writing to Notes from Server Services
- `note.setContent()` requires a CLS (Continuation Local Storage) context — wrap calls in `cls.init(() => { ... })` (from `apps/server/src/services/cls.ts`)
- Operations called from Express routes already have CLS context; standalone services (schedulers, Electron IPC handlers) do not
### Adding New LLM Tools
Tools are defined using `defineTools()` in `apps/server/src/services/llm/tools/` and automatically registered for both the LLM chat and MCP server.

View File

@@ -57,11 +57,11 @@
"jquery": "4.0.0",
"jquery.fancytree": "2.38.5",
"jsplumb": "2.15.6",
"katex": "0.16.44",
"katex": "0.16.45",
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "17.0.5",
"marked": "18.0.0",
"mermaid": "11.14.0",
"mind-elixir": "5.10.0",
"panzoom": "9.4.4",
@@ -87,6 +87,6 @@
"happy-dom": "20.8.9",
"lightningcss": "1.32.0",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "4.0.0"
"vite-plugin-static-copy": "4.0.1"
}
}

View File

@@ -236,6 +236,16 @@ export default class FNote {
return this.hasAttribute("label", "archived");
}
/**
* Returns true if the note's metadata (title, icon) should not be editable.
* This applies to system notes like options, help, and launch bar configuration.
*/
get isMetadataReadOnly() {
return utils.isLaunchBarConfig(this.noteId)
|| this.noteId.startsWith("_help_")
|| this.noteId.startsWith("_options");
}
getChildNoteIds() {
return this.children;
}

View File

@@ -38,7 +38,7 @@ function setupContextMenu() {
items.push({
title: t("electron_context_menu.add-term-to-dictionary", { term: params.misspelledWord }),
uiIcon: "bx bx-plus",
handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
handler: () => electron.ipcRenderer.send("add-word-to-dictionary", params.misspelledWord)
});
items.push({ kind: "separator" });

View File

@@ -6,10 +6,8 @@ import froca from "./froca";
import server from "./server.js";
// Spy on server methods to track calls
// @ts-expect-error the generic typing is causing issues here
server.put = vi.fn(async <T> (url: string, data?: T) => ({} as T));
// @ts-expect-error the generic typing is causing issues here
server.remove = vi.fn(async <T> (url: string) => ({} as T));
server.put = vi.fn(async () => ({})) as typeof server.put;
server.remove = vi.fn(async () => ({})) as typeof server.remove;
describe("Set boolean with inheritance", () => {
beforeEach(() => {

View File

@@ -120,7 +120,7 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
if (moveToParent) {
try {
await activateParentNotePath();
await activateParentNotePath(branchIdsToDelete);
} catch (e) {
console.error(e);
}
@@ -152,13 +152,28 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
return true;
}
async function activateParentNotePath() {
// this is not perfect, maybe we should find the next/previous sibling, but that's more complex
async function activateParentNotePath(branchIdsToDelete: string[]) {
const activeContext = appContext.tabManager.getActiveContext();
const parentNotePathArr = activeContext?.notePathArray.slice(0, -1);
const activeNotePath = activeContext?.notePathArray ?? [];
if (parentNotePathArr && parentNotePathArr.length > 0) {
activeContext?.setNote(parentNotePathArr.join("/"));
// Find the deleted branch that appears earliest in the active note's path
let earliestIndex = activeNotePath.length;
for (const branchId of branchIdsToDelete) {
const branch = froca.getBranch(branchId);
if (branch) {
const index = activeNotePath.indexOf(branch.noteId);
if (index !== -1 && index < earliestIndex) {
earliestIndex = index;
}
}
}
// Navigate to the parent of the highest deleted ancestor
if (earliestIndex < activeNotePath.length) {
const parentPath = activeNotePath.slice(0, earliestIndex);
if (parentPath.length > 0) {
await activeContext?.setNote(parentPath.join("/"));
}
}
}

View File

@@ -1,4 +1,4 @@
import { Fragment, h, VNode } from "preact";
import { createContext, Fragment, h, VNode } from "preact";
import * as hooks from "preact/hooks";
import ActionButton from "../widgets/react/ActionButton";
@@ -47,6 +47,7 @@ export const preactAPI = Object.freeze({
// Core
h,
Fragment,
createContext,
/**
* Method that must be run for widget scripts that run on Preact, using JSX. The method just returns the same definition, reserved for future typechecking and perhaps validation purposes.

View File

@@ -68,7 +68,8 @@ async function autocompleteSourceForCKEditor(queryText: string) {
name: row.notePathTitle || "",
link: `#${row.notePath}`,
notePath: row.notePath,
highlightedNotePathTitle: row.highlightedNotePathTitle
highlightedNotePathTitle: row.highlightedNotePathTitle,
icon: row.icon
};
})
);

View File

@@ -18,6 +18,10 @@ async function render(note: FNote, $el: JQuery<HTMLElement>, onError?: ErrorHand
for (const renderNoteId of renderNoteIds) {
const bundle = await server.postWithSilentInternalServerError<Bundle>(`script/bundle/${renderNoteId}`);
if (!bundle) {
throw new Error(`Script note '${renderNoteId}' could not be loaded. It may be protected and require an active protected session.`);
}
const $scriptContainer = $("<div>");
$el.append($scriptContainer);

View File

@@ -0,0 +1,87 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import SpacedUpdate from "./spaced_update";
// Mock logError which is a global in Trilium
vi.stubGlobal("logError", vi.fn());
describe("SpacedUpdate", () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("should only call updater once per interval even with multiple pending callbacks", async () => {
const updater = vi.fn(async () => {
// Simulate a slow network request - this is where the race condition occurs
await new Promise((resolve) => setTimeout(resolve, 100));
});
const spacedUpdate = new SpacedUpdate(updater, 50);
// Simulate rapid typing - each keystroke calls scheduleUpdate()
// This queues multiple setTimeout callbacks due to recursive scheduleUpdate() calls
for (let i = 0; i < 10; i++) {
spacedUpdate.scheduleUpdate();
// Small delay between keystrokes
await vi.advanceTimersByTimeAsync(5);
}
// Advance time past the update interval to trigger the update
await vi.advanceTimersByTimeAsync(100);
// Let the "network request" complete and any pending callbacks run
await vi.advanceTimersByTimeAsync(200);
// The updater should have been called only ONCE, not multiple times
// With the bug, multiple pending setTimeout callbacks would all pass the time check
// during the async updater call and trigger multiple concurrent requests
expect(updater).toHaveBeenCalledTimes(1);
});
it("should call updater again if changes occur during the update", async () => {
const updater = vi.fn(async () => {
await new Promise((resolve) => setTimeout(resolve, 50));
});
const spacedUpdate = new SpacedUpdate(updater, 30);
// First update
spacedUpdate.scheduleUpdate();
await vi.advanceTimersByTimeAsync(40);
// Schedule another update while the first one is in progress
spacedUpdate.scheduleUpdate();
// Let first update complete
await vi.advanceTimersByTimeAsync(60);
// Advance past the interval again for the second update
await vi.advanceTimersByTimeAsync(100);
// Should have been called twice - once for each distinct change period
expect(updater).toHaveBeenCalledTimes(2);
});
it("should restore changed flag on error so retry can happen", async () => {
const updater = vi.fn()
.mockRejectedValueOnce(new Error("Network error"))
.mockResolvedValue(undefined);
const spacedUpdate = new SpacedUpdate(updater, 50);
spacedUpdate.scheduleUpdate();
// Advance to trigger first update (which will fail)
await vi.advanceTimersByTimeAsync(60);
// The error should have restored the changed flag, so scheduling again should work
spacedUpdate.scheduleUpdate();
await vi.advanceTimersByTimeAsync(60);
expect(updater).toHaveBeenCalledTimes(2);
});
});

View File

@@ -77,16 +77,22 @@ export default class SpacedUpdate {
}
if (Date.now() - this.lastUpdated > this.updateInterval) {
// Update these BEFORE the async call to prevent race conditions.
// Multiple setTimeout callbacks may be pending from recursive scheduleUpdate() calls.
// Without this, they would all pass the time check during the await and trigger multiple requests.
this.lastUpdated = Date.now();
this.changed = false;
this.onStateChanged("saving");
try {
await this.updater();
this.onStateChanged("saved");
this.changed = false;
} catch (e) {
// Restore changed flag on error so a retry can happen
this.changed = true;
this.onStateChanged("error");
logError(getErrorMessage(e));
}
this.lastUpdated = Date.now();
} else {
// update isn't triggered but changes are still pending, so we need to schedule another check
this.scheduleUpdate();

View File

@@ -33,6 +33,14 @@ export async function formatCodeBlocks($container: JQuery<HTMLElement>) {
applySingleBlockSyntaxHighlight($(codeBlock), normalizedMimeType);
}
}
// Add click-to-copy for inline code (code elements not inside pre)
if (glob.device !== "print") {
const inlineCodeElements = $container.find("code:not(pre code)");
for (const inlineCode of inlineCodeElements) {
applyInlineCodeCopy($(inlineCode));
}
}
}
export function applyCopyToClipboardButton($codeBlock: JQuery<HTMLElement>) {
@@ -51,6 +59,23 @@ export function applyCopyToClipboardButton($codeBlock: JQuery<HTMLElement>) {
$codeBlock.parent().append($copyButton);
}
export function applyInlineCodeCopy($inlineCode: JQuery<HTMLElement>) {
$inlineCode
.addClass("copyable-inline-code")
.attr("title", t("code_block.click_to_copy"))
.off("click")
.on("click", (e) => {
e.stopPropagation();
const text = $inlineCode.text();
if (!isShare) {
copyTextWithToast(text);
} else {
copyText(text);
}
});
}
/**
* Applies syntax highlight to the given code block (assumed to be <pre><code>), using highlight.js.
*/

View File

@@ -134,7 +134,7 @@ async function handleMessage(event: MessageEvent<any>) {
} else if (message.type === "api-log-messages") {
appContext.triggerEvent("apiLogMessages", { noteId: message.noteId, messages: message.messages });
} else if (message.type === "toast") {
toast.showMessage(message.message);
toast.showMessage(message.message, message.timeout);
} else if (message.type === "execute-script") {
const originEntity = message.originEntityId ? await froca.getNote(message.originEntityId) : null;

View File

@@ -99,7 +99,7 @@ class SetupController {
}
private async finish() {
const syncServerHost = this.syncServerHostInput.value.trim();
const syncServerHost = this.syncServerHostInput.value.trim().replace(/\/+$/, "");
const syncProxy = this.syncProxyInput.value.trim();
const password = this.passwordInput.value;

View File

@@ -1230,6 +1230,43 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
width: 100%;
}
/* Expandable include note styles */
.include-note-title-row {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
}
.include-note-title-row .include-note-title {
margin: 0;
}
.include-note-toggle {
background: none;
border: none;
padding: 2px;
cursor: pointer;
font-size: 1.2em;
color: var(--main-text-color);
transition: transform 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.include-note-toggle:hover {
color: var(--main-link-color);
}
.include-note-toggle.expanded {
transform: rotate(90deg);
}
.include-note[data-box-size="expandable"] .include-note-content {
margin-top: 10px;
}
.alert {
padding: 8px 14px;
width: auto;

View File

@@ -393,9 +393,7 @@
},
"delete_notes": {
"close": "غلق",
"cancel": "الغاء",
"ok": "نعم",
"delete_notes_preview": "حذف معاينة الملاحظات"
"cancel": "الغاء"
},
"export": {
"close": "غلق",
@@ -626,7 +624,8 @@
"date-and-time": "التاريخ والوقت",
"no_backup_yet": "لايوجد نسخة احتياطية لحد الان",
"enable_daily_backup": "تمكين النسخ الاحتياطي اليومي",
"backup_database_now": "نسخ اختياطي لقاعدة البيانات الان"
"backup_database_now": "نسخ اختياطي لقاعدة البيانات الان",
"download": "تنزيل"
},
"etapi": {
"created": "تم الأنشاء",
@@ -663,7 +662,6 @@
"default_shortcuts": "اختصارات افتراضية"
},
"sync_2": {
"timeout_unit": "ميلي ثانية",
"note": "ملاحظة",
"save": "حفظ",
"help": "المساعدة",
@@ -1129,9 +1127,7 @@
"spellcheck": {
"title": "التدقيق الاملائي",
"enable": "تفعيل التدقيق الاملائي",
"language_code_label": "رمز اللغة او رموز اللغات",
"available_language_codes_label": "رموز اللغات المتاحة:",
"language_code_placeholder": "على سبيل المثال \"en-US\", \"de-AI\""
"language_code_label": "رمز اللغة او رموز اللغات"
},
"note-map": {
"button-link-map": "خريطة الروابط",

View File

@@ -25,8 +25,7 @@
},
"delete_notes": {
"close": "Tanca",
"cancel": "Cancel·la",
"ok": "OK"
"cancel": "Cancel·la"
},
"export": {
"close": "Tanca",

View File

@@ -88,7 +88,6 @@
"also_delete_note": "同时删除笔记"
},
"delete_notes": {
"delete_notes_preview": "删除笔记预览",
"close": "关闭",
"delete_all_clones_description": "同时删除所有克隆(可以在最近修改中撤消)",
"erase_notes_description": "通常(软)删除仅标记笔记为已删除,可以在一段时间内通过最近修改对话框撤消。选中此选项将立即擦除笔记,不可撤销。",
@@ -96,9 +95,7 @@
"notes_to_be_deleted": "将删除以下笔记 ({{notesCount}})",
"no_note_to_delete": "没有笔记将被删除(仅克隆)。",
"broken_relations_to_be_deleted": "将删除以下关系并断开连接 ({{ relationCount}})",
"cancel": "取消",
"ok": "确定",
"deleted_relation_text": "笔记 {{- note}} (将被删除的笔记) 被以下关系 {{- relation}} 引用, 来自 {{- source}}。"
"cancel": "取消"
},
"export": {
"export_note_title": "导出笔记",
@@ -368,7 +365,7 @@
"calendar_root": "标记应用作为每日笔记的根。只应标记一个笔记。",
"archived": "含有此标签的笔记默认在搜索结果中不可见(也适用于跳转到、添加链接对话框等)。",
"exclude_from_export": "笔记(及其子树)不会包含在任何笔记导出中",
"run": "定义脚本应运行的事件。可能的值包括:\n<ul>\n<li>frontendStartup - Trilium前端启动时或刷新时但不会在移动端执行。</li>\n<li>mobileStartup - Trilium前端启动时或刷新时 在移动端会执行。</li>\n<li>backendStartup - Trilium后端启动时</li>\n<li>hourly - 每小时运行一次。您可以使用附加标签<code>runAtHour</code>指定小时。</li>\n<li>daily - 每天运行一次</li>\n</ul>",
"run": "定义脚本应运行的事件。可能的值包括:\n<ul>\n<li>frontendStartup - Trilium前端启动时或刷新时但不会在移动端执行。</li>\n<li>mobileStartup - Trilium前端启动时或刷新时 在移动端会执行。</li>\n<li>backendStartup - Trilium后端启动时</li>\n<li>hourly - 每小时运行一次。您可以使用附加标签<code>runAtHour</code>指定小时。</li>\n<li>daily - 每天运行一次</li>\n</ul>",
"run_on_instance": "定义应在哪个Trilium实例上运行。默认为所有实例。",
"run_at_hour": "应在哪个小时运行。应与<code>#run=hourly</code>一起使用。可以多次定义,以便一天内运行多次。",
"disable_inclusion": "含有此标签的脚本不会包含在父脚本执行中。",
@@ -804,7 +801,10 @@
"expand_first_level": "展开直接子代",
"expand_nth_level": "展开 {{depth}} 层",
"expand_all_levels": "展开所有层级",
"hide_child_notes": "隐藏树中的子笔记"
"hide_child_notes": "隐藏树中的子笔记",
"open_all_in_tabs": "全部打开",
"open_all_in_tabs_tooltip": "在新标签页中打开所有结果",
"open_all_confirm": "这将在新标签页中打开 {{count}} 个笔记。继续吗?"
},
"edited_notes": {
"no_edited_notes_found": "今天还没有编辑过的笔记...",
@@ -858,7 +858,8 @@
"collapse": "折叠到正常大小",
"title": "笔记地图",
"fix-nodes": "固定节点",
"link-distance": "链接距离"
"link-distance": "链接距离",
"too-many-notes": "此子树包含 {{count}} 个笔记,超过了笔记地图中可显示的 {{max}} 个笔记的限制。"
},
"note_paths": {
"title": "笔记路径",
@@ -1063,7 +1064,8 @@
"note_already_in_diagram": "笔记 \"{{title}}\" 已经在图中。",
"enter_title_of_new_note": "输入新笔记的标题",
"default_new_note_title": "新笔记",
"click_on_canvas_to_place_new_note": "点击画布以放置新笔记"
"click_on_canvas_to_place_new_note": "点击画布以放置新笔记",
"rename_relation": "重命名关系"
},
"backend_log": {
"refresh": "刷新"
@@ -1337,7 +1339,8 @@
"date-and-time": "日期和时间",
"path": "路径",
"database_backed_up_to": "数据库已备份到 {{backupFilePath}}",
"no_backup_yet": "尚无备份"
"no_backup_yet": "尚无备份",
"download": "下载"
},
"etapi": {
"title": "ETAPI",
@@ -1435,12 +1438,15 @@
"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": "拼写检查选项的更改将在应用重启后生效。"
"enable": "拼写检查",
"language_code_label": "拼写检查语言",
"restart-required": "拼写检查选项的更改将在应用重启后生效。",
"custom_dictionary_title": "自定义词典",
"custom_dictionary_description": "添加到词典中的单词会在您的所有设备上同步。",
"custom_dictionary_edit": "自定义词",
"custom_dictionary_edit_description": "编辑拼写检查器不应标记的单词列表。更改将在重启后生效。",
"custom_dictionary_open": "编辑词典",
"related_description": "配置拼写检查语言和自定义词典。"
},
"sync_2": {
"config_title": "同步配置",
@@ -1456,7 +1462,7 @@
"test_description": "测试和同步服务器之间的连接。如果同步服务器没有初始化,会将本地文档同步到同步服务器上。",
"test_button": "测试同步",
"handshake_failed": "同步服务器握手失败,错误:{{message}}",
"timeout_unit": "毫秒"
"timeout_description": "同步连接速度慢时,应该等待多久才放弃?如果网络不稳定,请增加等待时间。"
},
"api_log": {
"close": "关闭"
@@ -1879,7 +1885,7 @@
},
"content_language": {
"title": "内容语言",
"description": "选择一种或多种语言出现在只读或可编辑文本注释的基本属性,这将支持拼写检查从右向左之类的功能。"
"description": "在只读或可编辑文本笔记的“基本属性”部分,选择一种或多种语言,这些语言将显示在语言选择列表中。这将启用拼写检查从右到左的阅读支持和文本提取OCR功能。"
},
"switch_layout_button": {
"title_vertical": "将编辑面板移至底部",
@@ -2234,7 +2240,9 @@
"sample_xy": "散点图",
"sample_venn": "韦恩图",
"sample_ishikawa": "鱼骨图",
"placeholder": "输入你的美人鱼图的内容,或者使用下面的示例图之一。"
"placeholder": "输入你的美人鱼图的内容,或者使用下面的示例图之一。",
"sample_treeview": "树形视图",
"sample_wardley": "沃德利地图"
},
"llm_chat": {
"placeholder": "输入消息…",
@@ -2265,7 +2273,8 @@
"note_context_disabled": "点击即可将当前注释添加到上下文中",
"no_provider_message": "未配置人工智能提供商。添加一个即可开始对话。",
"add_provider": "添加人工智能提供商",
"note_tools": "笔记访问"
"note_tools": "笔记访问",
"sources_summary": "来自 {{sites}} 个网站的 {{count}} 个来源"
},
"sidebar_chat": {
"title": "AI对话",
@@ -2288,7 +2297,10 @@
"processing": "正在处理...",
"processing_started": "OCR识别已开始。请稍候片刻并刷新页面。",
"processing_failed": "OCR处理启动失败",
"view_extracted_text": "查看提取的文本OCR"
"view_extracted_text": "查看提取的文本OCR",
"processing_complete": "OCR识别处理完成。",
"text_filtered_low_confidence": "OCR 检测到文本,置信度为 {{confidence}}% ,但由于您的最小阈值为 {{threshold}}% ,因此该文本已被丢弃。",
"open_media_settings": "打开设置"
},
"mind-map": {
"addChild": "添加子节点",
@@ -2306,6 +2318,13 @@
},
"llm": {
"settings_description": "配置人工智能和大语言模型集成。",
"add_provider": "添加提供商"
"add_provider": "添加提供商",
"settings_title": "AI / LLM",
"feature_not_enabled": "在“设置”→“高级”→“实验性功能”中启用 LLM 实验性功能,即可使用 AI 集成。",
"add_provider_title": "添加AI供应商",
"configured_providers": "已配置的供应商",
"no_providers_configured": "尚未配置任何供应商。",
"provider_name": "名称",
"provider_type": "供应商"
}
}

View File

@@ -21,7 +21,7 @@
},
"bundle-error": {
"title": "Načtení uživatelského skriptu selhalo",
"message": "Uživatelský skript z poznámky s ID \"{{id}}\" a názvem \"{{title}}\" nemohl být spuštěn z důvodu: \n\n{{message}}"
"message": "Skript nebylo možné spustit z následujícího důvodu:\n\n{{message}}"
},
"widget-list-error": {
"title": "Nepodařilo se získat seznam widgetů ze serveru"
@@ -29,7 +29,7 @@
"widget-render-error": {
"title": "Nepodařilo se vykreslit vlastní React widget"
},
"widget-missing-parent": "Vlastní widget nemá definovanou povinnou vlastnost '{{property}}'.",
"widget-missing-parent": "Vlastní widget nemá definovanou povinnou vlastnost {{property}}“.\n\nPokud má být tento skript spuštěn bez prvku uživatelského rozhraní, použijte místo toho „#run=frontendStartup“.",
"open-script-note": "Otevřít poznámku se skriptem",
"scripting-error": "Chyba vlastního skriptu: {{title}}"
},
@@ -77,16 +77,13 @@
},
"delete_notes": {
"cancel": "Zrušit",
"ok": "OK",
"close": "Zavřít",
"delete_notes_preview": "Odstranit náhled poznámek",
"delete_all_clones_description": "Odstraňte také všechny klony (lze vrátit zpět v nedávných změnách)",
"erase_notes_description": "Normální (měkké) smazání pouze označí poznámky jako smazané a lze je během určité doby obnovit (v dialogovém okně posledních změn). Zaškrtnutím této možnosti se poznámky okamžitě vymažou a nebude možné je obnovit.",
"erase_notes_warning": "Trvale smažte poznámky (nelze vrátit zpět), včetně všech klonů. Tím se vynutí opětovné načtení aplikace.",
"notes_to_be_deleted": "Následující poznámky budou smazány ({{notesCount}})",
"no_note_to_delete": "Žádná poznámka nebude smazána (pouze klony).",
"broken_relations_to_be_deleted": "Následující vazby budou přerušeny a smazány ({{relationCount}})",
"deleted_relation_text": "Poznámka {{- note}} (bude smazána) je odkazována vazbou {{- relation}} pocházející z {{- source}}."
"broken_relations_to_be_deleted": "Následující vazby budou přerušeny a smazány ({{relationCount}})"
},
"export": {
"close": "Zavřít",
@@ -120,9 +117,9 @@
"note_cloned": "Poznámka „{{clonedTitle}}“ bylo naklonována do „{{targetTitle}}“"
},
"zpetne_odkazy": {
"backlink_one": "{{count}} zpětný odkaz",
"backlink_few": "{{count}} zpětné odkazy",
"backlink_other": "{{count}} zpětných odkazů",
"backlink_one": "{{count}} Zpětný odkaz",
"backlink_few": "{{count}} Zpětné odkazy",
"backlink_other": "{{count}} Zpětných odkazů",
"relation": "vazba"
},
"help": {
@@ -183,7 +180,7 @@
"import": {
"importIntoNote": "Import do poznámky",
"chooseImportFile": "Vyberte soubor k importu",
"importDescription": "Obsah vybraných souborů bude importován jako dceřiné poznámky do {{name}}",
"importDescription": "Obsah vybraných souborů bude importován jako dceřiné poznámky do",
"importZipRecommendation": "Při importu ZIP souboru bude hierarchie poznámek odrážet strukturu podadresářů v archivu.",
"options": "Nastavení",
"safeImportTooltip": "Exportované soubory Trilium <code>.zip</code> mohou obsahovat spustitelné skripty, které mohou mít škodlivé chování. Bezpečný import deaktivuje automatické spouštění všech importovaných skriptů. Zrušte zaškrtnutí \"Bezpečný import\" pouze v případě, že importovaný archiv má obsahovat spustitelné skripty a plně důvěřujete obsahu importovaného souboru.",
@@ -215,26 +212,26 @@
"box_size_small": "Malý (~ 10 řádků)",
"box_size_medium": "Střední (~ 30 řádků)",
"box_size_full": "Úplný (pole zobrazuje kompletní text)",
"button_include": "Zahrnout {{note}}"
"button_include": "Zahrnout poznámku"
},
"info": {
"modalTitle": "Informační zpráva",
"closeButton": "Zavřít",
"okButton": "OK",
"copy_to_clipboard": "Kopírovat do {{clipboard}}"
"copy_to_clipboard": "Kopírovat do schránky"
},
"jump_to_note": {
"search_placeholder": "Hledat {{note}} podle jména nebo typu > pro příkazy...",
"search_placeholder": "Hledat poznámku podle jména nebo typu > pro příkazy...",
"search_button": "Hledat v celém textu"
},
"markdown_import": {
"dialog_title": "{{Markdown}} import",
"modal_body_text": "Kvůli sandboxu prohlížeče není možné přímo číst z {{clipboard}} z {{JavaScript}}. Vložte prosím {{Markdown}} k importu do textového pole níže a klikněte na tlačítko Import.",
"dialog_title": "Markdown import",
"modal_body_text": "Vzhledem k omezením prohlížeče není možné přímo číst obsah schránky z JavaScriptu. Vložte prosím Markdown, který chcete importovat, do textového pole níže a klikněte na tlačítko Importovat",
"import_button": "Import",
"import_success": "{{Markdown}} obsah byl importován do dokumentu."
"import_success": "Markdown obsah byl importován do dokumentu."
},
"move_to": {
"dialog_title": "Přesunout {{notes}} do ...",
"dialog_title": "Přesunout poznámku do ...",
"notes_to_move": "Poznámky k přesunu",
"target_parent_note": "Cílová nadřazená poznámka",
"search_placeholder": "Hledat poznámku podle názvu",
@@ -333,12 +330,12 @@
"attr_detail_title": "Detail Atributu",
"close_button_title": "Zrušit změny a zavřít",
"attr_is_owned_by": "Atribut je vlastněn",
"attr_name_title": "Název atributu může obsahovat pouze alfanumerické znaky, dvojtečku a podtržítko.",
"attr_name_title": "Název atributu může obsahovat pouze alfanumerické znaky, dvojtečku a podtržítko",
"name": "Název",
"value": "Hodnota",
"target_note_title": "Vazba je pojmenované spojení mezi zdrojovou {{note}} a cílovou {{note}}.",
"target_note": "Cílová {{note}}",
"promoted_title": "Propagovaný atribut je zobrazen prominentně na {{note}}.",
"target_note_title": "Vazba je pojmenované spojení mezi zdrojovou poznámkou a cílovou poznámkou.",
"target_note": "Cílová poznámka",
"promoted_title": "Propagovaný atribut je zobrazen prominentně na poznámce.",
"promoted": "Propagováno",
"promoted_alias_title": "Název, který se má zobrazit v uživatelském rozhraní propagovaných atributů.",
"promoted_alias": "Alias",
@@ -374,51 +371,51 @@
"disable_versioning": "zakáže automatické verzování. Užitečné například pro velké, ale nepodstatné poznámky - například velké JavaScript knihovny používané pro scripting",
"calendar_root": "označí poznámku, která by měla být použita jako kořen pro denní poznámky. Pouze jedna by měla být takto označena.",
"archived": "Poznámky s tímto štítkem nebudou ve výchozím nastavení viditelné ve výsledcích vyhledávání (také v dialogových oknech Přeskočit na, Přidat odkaz atd.).",
"exclude_from_export": "Poznámky (včetně jejich podstromu) nebudou zahrnuty do žádného exportu poznámek.",
"run": "Definuje, při kterých událostech se má skript spustit. Možné hodnoty jsou: <ul> <li>frontendStartup - když se spustí frontend Trilium (nebo se obnoví), ale ne na mobilních zařízeních.</li> <li>mobileStartup - když se spustí frontend Trilium (nebo se obnoví), na mobilních zařízeních.</li> <li>backendStartup - když se spustí backend Trilium.</li> <li>hourly - spustit jednou za hodinu. Můžete použít další štítek <code>runAtHour</code> pro určení hodiny.</li> <li>daily - spustit jednou denně.</li> </ul>",
"exclude_from_export": "Poznámky (včetně jejich podstromu) nebudou zahrnuty do žádného exportu poznámek",
"run": "Definuje, při kterých událostech se má skript spustit. Možné hodnoty jsou: \n<ul>\n <li>frontendStartup - když se spustí frontend Trilium (nebo se obnoví), ale ne na mobilních zařízeních.</li>\n<li>mobileStartup - když se spustí frontend Trilium (nebo se obnoví), na mobilních zařízeních.</li>\n <li>backendStartup - když se spustí backend Trilium.</li>\n<li>hourly - spustit jednou za hodinu. Můžete použít další štítek <code>runAtHour</code> pro určení hodiny.</li>\n <li>daily - spustit jednou denně.</li>\n </ul>",
"run_on_instance": "Definujte, na které instanci Trilium se má toto spustit. Ve výchozím nastavení na všech instancích.",
"run_at_hour": "V kterou hodinu se má toto spustit. Mělo by se používat společně s <code>#run=hourly</code>. Lze definovat vícekrát pro více spuštění během dne.",
"disable_inclusion": "Skripty s tímto štítkem nebudou zahrnuty do spuštění nadřazeného skriptu.",
"sorted": "Udržuje podřízené poznámky seřazené podle názvu abecedně.",
"sorted": "Udržuje podřízené poznámky seřazené podle názvu abecedně",
"sort_direction": "ASC (výchozí) nebo DESC",
"sort_folders_first": "Složky (poznámky s podřízenými položkami) by měly být řazeny nahoře.",
"top": "Udržuje danou poznámku nahoře v rámci jejího nadřazeného (platí pouze pro seřazené nadřazené položky).",
"hide_promoted_attributes": "Skryje propagované atributy v této poznámce.",
"sort_folders_first": "Složky (poznámky s podřízenými položkami) by měly být řazeny nahoře",
"top": "Udržuje danou poznámku nahoře v rámci jejího nadřazeného (platí pouze pro seřazené nadřazené položky)",
"hide_promoted_attributes": "Skryje propagované atributy v této poznámce",
"read_only": "Editor je v režimu pouze pro čtení. Funguje pouze pro textové a kódové poznámky.",
"auto_read_only_disabled": "Textové/kódové poznámky se mohou automaticky nastavit do režimu pouze pro čtení, pokud jsou příliš velké. Toto chování můžete zakázat pro každou poznámku přidáním tohoto štítku do poznámky.",
"auto_read_only_disabled": "Textové/kódové poznámky se mohou automaticky nastavit do režimu pouze pro čtení, pokud jsou příliš velké. Toto chování můžete zakázat pro každou poznámku přidáním tohoto štítku do poznámky",
"app_css": "Označuje CSS poznámky, které jsou načteny do aplikace Trilium a mohou být tak použity k úpravě vzhledu Trilium.",
"app_theme": "Označuje CSS poznámky, které jsou kompletními motivy Trilium a jsou tak dostupné v možnostech Trilium.",
"app_theme_base": "nastavte na \"next\", \"next-light\" nebo \"next-dark\" pro použití odpovídajícího tématu TriliumNext (auto, světlé nebo tmavé) jako základu pro vlastní téma, namísto staršího.",
"css_class": "hodnota tohoto štítku je poté přidána jako CSS třída k uzlu reprezentujícímu danou {{Poznámka}} ve stromu. To může být užitečné pro pokročilé tématizace. Lze použít v šablonových {{Poznámka}}.",
"icon_class": "hodnota tohoto štítku je přidána jako CSS třída k ikoně ve stromu, což může pomoci vizuálně odlišit {{Poznámky}} ve stromu. Příkladem může být bx bx-home - ikony jsou převzaty z boxicons. Lze použít v šablonových {{Poznámka}}.",
"page_size": "počet položek na stránce v seznamu {{Poznámek}}",
"css_class": "hodnota tohoto štítku je poté přidána jako CSS třída k uzlu reprezentujícímu danou poznámku ve stromu. To může být užitečné pro pokročilé tématizace. Lze použít v šablonových poznámkách.",
"icon_class": "hodnota tohoto štítku je přidána jako CSS třída k ikoně ve stromu, což může pomoci vizuálně odlišit poznámky ve stromu. Příkladem může být bx bx-home - ikony jsou převzaty z boxicons. Lze použít v šablonových poznámkách.",
"page_size": "počet položek na stránce v seznamu poznámek",
"custom_request_handler": "viz <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Vlastní zpracovatel požadavků</a>",
"custom_resource_provider": "viz <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Vlastní zpracovatel požadavků</a>",
"widget": "označí tuto {{Poznámka}} jako vlastní widget, který bude přidán do stromu komponent Trilium",
"workspace": "označí tuto {{Poznámka}} jako pracovní prostor, který umožňuje snadné zúžení zobrazení",
"workspace_icon_class": "definuje CSS třídu ikony box, která bude použita na {{záložce}} při zúžení zobrazení na tuto {{Poznámka}}",
"workspace_tab_background_color": "CSS barva použitá na {{záložce}} při zúžení zobrazení na tuto {{Poznámka}}",
"workspace_calendar_root": "Definuje kořenový {{Kalendář}} pro pracovní prostor",
"workspace_template": "Tato {{Poznámka}} se zobrazí ve výběru dostupných {{šablon}} při vytváření nové {{Poznámky}}, ale pouze pokud je zúžena do pracovního prostoru obsahujícího tuto {{šablonu}}",
"search_home": "nové {{Poznámky}} {{Hledání}} budou vytvořeny jako {{Dceřiné}} této {{Poznámky}}",
"workspace_search_home": "nové {{Poznámky}} {{Hledání}} budou vytvořeny jako {{Dceřiné}} této {{Poznámky}} při zúžení zobrazení na některého z předků této {{Poznámky}} pracovního prostoru",
"inbox": "výchozí složka doručené pošty pro nové {{Poznámky}} - při vytváření {{Poznámky}} pomocí tlačítka \"nová {{Poznámka}}\" na postranním panelu budou {{Poznámky}} vytvořeny jako {{Dceřiné}} {{Poznámky}} označené štítkem <code>#inbox</code>.",
"workspace_inbox": "Výchozí umístění pro nové {{Poznámky}} při přesunutí do některého předka této {{Poznámky}} pracovního prostoru",
"sql_console_home": "Výchozí umístění {{Poznámky}} konzole {{SQL}}",
"bookmark_folder": "{{Poznámka}} s tímto {{štítek}} se zobrazí v {{záložkách}} jako složka (umožňuje přístup k jejím podřízeným {{Poznámky}})",
"share_hidden_from_tree": "Tato {{Poznámka}} je skrytá z levého navigačního {{stromu}}, ale je stále přístupná pomocí její {{URL}}",
"share_external_link": "{{Poznámka}} bude fungovat jako odkaz na externí webovou stránku ve sdíleném {{stromu}}",
"share_alias": "Definujte alias, pod kterým bude {{Poznámka}} dostupná na https://your_trilium_host/share/[your_alias]",
"share_omit_default_css": "Výchozí {{CSS}} stránky pro sdílení bude vynechána. Použijte, pokud provedete rozsáhlé změny stylu.",
"share_root": "Označuje {{Poznámku}}, která je obsluhována z kořene /share.",
"share_description": "Definujte text, který má být přidán do meta tagu {{HTML}} pro popis",
"share_raw": "{{Poznámka}} bude obsluhována v surovém formátu, bez obalu {{HTML}}",
"share_disallow_robot_indexing": "Zakáže indexování této {{Poznámky}} roboty pomocí hlavičky <code>X-Robots-Tag: noindex</code>",
"share_credentials": "Vyžaduje pověření pro přístup k této sdílené {{Poznámce}}. Hodnota se očekává ve formátu 'username:password'. Nezapomeňte ji učinit dědičnou, aby se použila na podřízené {{Poznámky}}/obrázky.",
"share_index": "{{Poznámka}} s tímto {{štítek}} zobrazí všechny kořeny sdílených {{Poznámek}}",
"display_relations": "Seznam jmen {{vazeb}} oddělených čárkami, které se mají zobrazit. Všechny ostatní budou skryté.",
"hide_relations": "Seznam jmen {{vazeb}} oddělených čárkami, které se mají skrýt. Všechny ostatní budou zobrazeny.",
"title_template": "Výchozí název poznámek vytvořených jako dceřiné této poznámky. Hodnota se vyhodnocuje jako JavaScript řetězec a může být tak obohacena o dynamický obsah pomocí vložených proměnných <code>now</code> a <code>parentNote</code>. Příklady: <ul> <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li> <li><code>Log pro ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li> </ul> Viz <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki s detaily</a>, API dokumentace pro <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> a <a href=\"https://day.js.org/docs/en/display/format\">now</a> pro detaily.",
"widget": "označí tuto poznámku jako vlastní widget, který bude přidán do stromu komponent Trilium",
"workspace": "označí tuto poznámku jako pracovní prostor, který umožňuje snadné zúžení zobrazení",
"workspace_icon_class": "definuje CSS třídu ikony box, která bude použita na záložce při zúžení zobrazení na tuto poznámku",
"workspace_tab_background_color": "CSS barva použitá na záložce při zúžení zobrazení na tuto poznámku",
"workspace_calendar_root": "Definuje kořenový kalendář pro pracovní prostor",
"workspace_template": "Tato poznámka se zobrazí ve výběru dostupných šablon při vytváření nové poznámky, ale pouze pokud je zúžena do pracovního prostoru obsahujícího tuto šablonu",
"search_home": "nové poznámky k vyhledávání budou vytvořeny jako podřízené položky této poznámky",
"workspace_search_home": "při přesunutí do některé nadřazené poznámky tohoto pracovního prostoru budou vytvořeny nové poznámky k vyhledávání jako podřízené této poznámky",
"inbox": "výchozí umístění schránky pro nové poznámky pokud vytvoříte poznámku pomocí tlačítka „Nová poznámka“ v postranním panelu, poznámky se uloží jako podřízené poznámky k poznámce označené štítkem <code>#inbox</code>.",
"workspace_inbox": "výchozí umístění doručené pošty pro nové poznámky při přesunutí do některé nadřazené poznámky tohoto pracovního prostoru",
"sql_console_home": "výchozí umístění poznámky konzole SQL",
"bookmark_folder": "poznámka s tímto štítkem se zobrazí v záložkách jako složka (umožňuje přístup k jejím podřízeným poznámkám)",
"share_hidden_from_tree": "Tato poznámka je skrytá z levého navigačního stromu, ale je stále přístupná pomocí její URL",
"share_external_link": "poznámka bude fungovat jako odkaz na externí webovou stránku ve sdíleném stromu",
"share_alias": "definujte alias, pod kterým bude poznámka dostupná na https://your_trilium_host/share/[your_alias]",
"share_omit_default_css": "výchozí CSS stránky pro sdílení bude vynechána. Použijte, pokud provedete rozsáhlé změny stylu.",
"share_root": "Označuje poznámku, která je obsluhována z kořene /share.",
"share_description": "Definujte text, který má být přidán do meta tagu HTML pro popis",
"share_raw": "poznámka bude obsluhována v surovém formátu, bez obalu HTML",
"share_disallow_robot_indexing": "zakáže indexování této poznámky roboty pomocí hlavičky <code>X-Robots-Tag: noindex</code>",
"share_credentials": "Vyžaduje pověření pro přístup k této sdílené poznámce. Hodnota se očekává ve formátu 'username:password'. Nezapomeňte ji učinit dědičnou, aby se použila na podřízené poznámky/obrázky.",
"share_index": "poznámka s tímto štítkem zobrazí všechny kořeny sdílených poznámek",
"display_relations": "seznam jmen vazeb oddělených čárkami, které se mají zobrazit. Všechny ostatní budou skryté.",
"hide_relations": "Seznam jmen vazeb oddělených čárkami, které se mají skrýt. Všechny ostatní budou zobrazeny.",
"title_template": "výchozí název poznámek vytvořených jako podřízené této poznámce. Hodnota je vyhodnocena jako řetězec v JavaScriptu \n a lze ji tedy obohatit o dynamický obsah pomocí vložených proměnných <code>now</code> a <code>parentNote</code>. Příklady:\n \n <ul>\n <li><code>${parentNote.getLabelValue(authorName)}ova literární díla</code></li>\n <li><code>Záznam pro ${now.format(YYYY-MM-DD HH:mm:ss)}</code></li>\n </ul>\n \n Viz <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html \">wiki s podrobnostmi</a>, dokumentaci API pro <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> a <a href=\"https://day.js.org/docs/en/display/format\">now</a> pro podrobnosti.",
"template": "Tato poznámka se zobrazí ve výběru dostupných šablon při vytváření nové poznámky",
"toc": "<code>#toc</code> nebo <code>#toc=show</code> vynutí zobrazení Obsahu, <code>#toc=hide</code> vynutí jeho skrytí. Pokud štítek neexistuje, je respektováno globální nastavení",
"color": "Definuje barvu poznámky ve stromu poznámek, odkazech atd. Použijte jakoukoli platnou hodnotu CSS barvy, například 'red' nebo #a13d5f",
@@ -429,20 +426,20 @@
"exclude_from_note_map": "Poznámky s tímto štítkem budou skryté v Mapě poznámek",
"new_notes_on_top": "Nové poznámky budou vytvářeny nahoře nadřazené poznámky, nikoli dole.",
"hide_highlight_widget": "Skrýt Widget Seznam zvýraznění",
"run_on_note_creation": "Spustí se při vytvoření {{name}} na backendu. Použijte tuto vazbu, pokud chcete spustit skript pro všechny {{name}} vytvořené pod určitým podstromem. V takovém případě ji vytvořte na kořenovém {{name}} podstromu a nastavte ji jako dědičnou. Nově vytvořená {{name}} v podstromu (v jakékoli hloubce) spustí skript.",
"run_on_child_note_creation": "Spustí se při vytvoření nové {{name}} pod {{name}}, kde je tato vazba definována.",
"run_on_note_title_change": "Spustí se při změně názvu {{name}} (zahrnuje i vytvoření {{name}}).",
"run_on_note_content_change": "Spustí se při změně obsahu {{name}} (zahrnuje i vytvoření {{name}}).",
"run_on_note_change": "Spustí se při změně {{name}} (zahrnuje i vytvoření {{name}}). Nezahrnuje změny obsahu.",
"run_on_note_deletion": "Spustí se při mazání {{name}}.",
"run_on_branch_creation": "Spustí se při vytvoření větve. Větev je odkaz mezi nadřazenou {{name}} a dceřinou {{name}} a je vytvořena například při klonování nebo přesouvání {{name}}.",
"run_on_note_creation": "spustí se při vytvoření poznámky na serveru. Tuto relaci použijte, pokud chcete skript spustit pro všechny poznámky vytvořené v rámci konkrétního podstromu. V takovém případě ji vytvořte na kořenové poznámce podstromu a nastavte ji jako dědičnou. Skript se spustí při vytvoření nové poznámky v rámci podstromu (v jakékoli úrovni).",
"run_on_child_note_creation": "provede se, když je pod poznámkou, ve které je tento vztah definován, vytvořena nová poznámka",
"run_on_note_title_change": "spustí se při změně názvu poznámky (včetně vytvoření poznámky)",
"run_on_note_content_change": "spustí se při změně obsahu poznámky (včetně jejího vytvoření).",
"run_on_note_change": "spustí se při změně noty (včetně jejího vytvoření). Nezahrnuje změny obsahu",
"run_on_note_deletion": "spustí se při mazání poznámky",
"run_on_branch_creation": "spustí se při vytvoření větve. Větev je propojení mezi nadřazenou a podřízenou poznámkou a vzniká například při klonování nebo přesouvání poznámky.",
"run_on_branch_change": "Spustí se při aktualizaci větve.",
"run_on_branch_deletion": "Spustí se při smazání větve. Větev je odkaz mezi nadřazenou {{name}} a dceřinou {{name}} a je smazána například při přesouvání {{name}} (stará větev/odkaz je smazán).",
"run_on_attribute_creation": "Spustí se při vytvoření nového atributu pro {{name}}, která definuje tuto vazbu.",
"relation_template": "Atributy {{name}} budou zděděny i bez vztahu rodič-dítě, obsah {{name}} a podstrom budou přidány k instančním {{name}}, pokud jsou prázdné. Podrobnosti naleznete v dokumentaci.",
"inherit": "Atributy {{name}} budou zděděny i bez vztahu rodič-dítě. Podobný koncept naleznete ve vazbě {{template}}. Viz dědičnost atributů v dokumentaci.",
"render_note": "{{name}} typu \"render HTML note\" budou vykresleny pomocí kódové {{name}} (HTML nebo skript) a je nutné pomocí této vazby určit, která {{name}} má být vykreslena.",
"widget_relation": "Cíl této vazby bude spuštěn a vykreslen jako widget na postranním panelu.",
"run_on_branch_deletion": "spustí se při smazání větve. Větev představuje propojení mezi nadřazenou a podřízenou poznámkou a je smazána např. při přesunu poznámky (původní větev/propojení je smazáno).",
"run_on_attribute_creation": "spustí se při vytvoření nového atributu pro poznámku, která definuje tuto vazbu",
"relation_template": "atributy poznámky budou zděděny i bez vztahu rodič-potomek; obsah poznámky a její podstrom budou přidány do instancí poznámek, pokud jsou prázdné. Podrobnosti najdete v dokumentaci.",
"inherit": "Atributy poznámky budou zděděny i bez vztahu rodičpotomek. Podobný koncept najdete v sekci „Vazby šablon“. Více informací najdete v dokumentaci v části „Dědičnost atributů.",
"render_note": "Poznámky typu „zobrazit HTML poznámku“ budou zobrazeny pomocí kódové poznámky (HTML nebo skript) a je nutné pomocí této vazby určit, která poznámka se má zobrazit",
"widget_relation": "cíl této vazby bude proveden a zobrazen jako widget v postranním panelu",
"share_css": "CSS poznámka, která bude vložena do sdílené stránky. CSS poznámka musí být také v sdíleném podstromu. Zvažte použití 'share_hidden_from_tree' a 'share_omit_default_css'.",
"share_js": "JavaScript poznámka, která bude vložena do sdílené stránky. JS poznámka musí být také v sdíleném podstromu. Zvažte použití 'share_hidden_from_tree'.",
"share_template": "Vložená JavaScript poznámka, která bude použita jako šablona pro zobrazení sdílené poznámky. Vrací se k výchozí šabloně. Zvažte použití 'share_hidden_from_tree'.",
@@ -452,7 +449,8 @@
"and_more": "... a {{count}} dalších.",
"print_landscape": "Při exportu do ⟨PDF⟩ změní orientaci stránky na landscape místo portrait.",
"print_page_size": "Při exportu do ⟨PDF⟩ změní velikost stránky. Podporované hodnoty: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
"color_type": "Barva"
"color_type": "Barva",
"run_on_attribute_change": " spustí se při změně atributu poznámky, která tuto vazbu definuje. Spustí se také při smazání tohoto atributu"
},
"attribute_editor": {
"help_text_body1": "Pro přidání štítku stačí zadat např. <code>#rock</code> nebo pokud chcete přidat i hodnotu, např. <code>#year = 2020</code>",
@@ -481,27 +479,27 @@
"label_name_title": "Povolené znaky jsou alfanumerické znaky, podtržítko a dvojtečka.",
"to_value": "k hodnotě",
"new_value_placeholder": "nová hodnota",
"help_text": "Na všech odpovídajících {{Note}}:",
"help_text_item1": "vytvořit daný {{label}}, pokud {{Note}} jej ještě nemá",
"help_text_item2": "nebo změnit hodnotu existujícího {{label}}",
"help_text_note": "Můžete také zavolat tuto metodu bez hodnoty, v takovém případě bude {{label}} přiřazen k {{Note}} bez hodnoty."
"help_text": "Na všech odpovídajících poznámkách:",
"help_text_item1": "vytvořit daný štítek, pokud jej poznámka ještě nemá",
"help_text_item2": "nebo změnit hodnotu existujícího štítku",
"help_text_note": "Můžete také zavolat tuto metodu bez hodnoty, v takovém případě bude štítek přiřazen k poznámce bez hodnoty."
},
"delete_label": {
"delete_label": "Smazat {{label}}",
"label_name_placeholder": "Název {{label}}",
"delete_label": "Smazat štítek",
"label_name_placeholder": "název štítku",
"label_name_title": "Povolené znaky jsou alfanumerické znaky, podtržítko a dvojtečka."
},
"rename_label": {
"rename_label": "Přejmenovat {{label}}",
"rename_label_from": "Přejmenovat {{label}} z",
"rename_label": "Přejmenovat štítek",
"rename_label_from": "Přejmenovat štítek z",
"old_name_placeholder": "starý název",
"to": "Na",
"new_name_placeholder": "nový název",
"name_title": "Povolené znaky jsou alfanumerické znaky, podtržítko a dvojtečka."
},
"update_label_value": {
"update_label_value": "Aktualizovat hodnotu {{label}}",
"label_name_placeholder": "Název {{label}}",
"update_label_value": "Aktualizovat hodnotu štítku",
"label_name_placeholder": "název štítku",
"label_name_title": "Povolené znaky jsou alfanumerické znaky, podtržítko a dvojtečka.",
"to_value": "na hodnotu",
"new_value_placeholder": "nová hodnota",
@@ -523,29 +521,29 @@
"move_note": "Přesunout poznámku",
"to": "do",
"target_parent_note": "cílové nadřazené poznámky",
"on_all_matched_notes": "Na všech odpovídajících {{Poznámka}}",
"move_note_new_parent": "přesunout {{Poznámka}} do nového nadřazeného, pokud má {{Poznámka}} pouze jednoho nadřazeného (tj. stará větev je odstraněna a nová větev je vytvořena do nového nadřazeného)",
"clone_note_new_parent": "klonovat {{Poznámka}} do nového nadřazeného, pokud má {{Poznámka}} více klonů/větví (není jasné, kterou větev by mělo odstranit)",
"nothing_will_happen": "nic se nestane, pokud nelze {{Poznámka}} přesunout do cílové {{Poznámka}} (tj. to by vytvořilo cyklus ve stromu)"
"on_all_matched_notes": "Na všech odpovídajících poznámkách",
"move_note_new_parent": "přesunout poznámku do nové nadřazené složky, pokud má poznámka pouze jednu nadřazenou složku (tj. stará větev je odstraněna a je vytvořena nová větev vedoucí do nové nadřazené složky)",
"clone_note_new_parent": "klonovat poznámku do novéhé nadřazené složky, pokud má poznámka více klonů/větví (není jasné, kterou větev by mělo odstranit)",
"nothing_will_happen": "nic se nestane, pokud nelze poznámku přesunout do cílové poznámky (tj. to by vytvořilo cyklus ve stromu)"
},
"rename_note": {
"rename_note": "Přejmenovat {{Poznámka}}",
"rename_note_title_to": "Přejmenovat název {{Poznámka}} na",
"new_note_title": "nový název {{Poznámka}}",
"rename_note": "Přejmenovat poznámku",
"rename_note_title_to": "Přejmenovat název poznámky na",
"new_note_title": "nový název poznámky",
"click_help_icon": "Klikněte na ikonu nápovědy vpravo a zobrazte všechny možnosti",
"evaluated_as_js_string": "Zadaná hodnota je vyhodnocena jako JavaScript řetězec a může být tak obohacena o dynamický obsah pomocí vložené proměnné <code>note</code> (přejmenovaná {{Poznámka}}). Příklady:",
"example_note": "<code>Poznámka</code> - všechny odpovídající {{Poznámka}} jsou přejmenovány na 'Poznámka'",
"example_new_title": "<code>NOVÝ: ${note.title}</code> - názvy odpovídajících {{Poznámka}} jsou předponovány 'NOVÝ: '",
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> - odpovídající {{Poznámka}} jsou předponovány měsícem a dnem vytvoření",
"api_docs": "Pro podrobnosti si přečtěte <a>API dokumentaci</a> pro {{note}} a jeho <a>vlastnosti dateCreatedObj / utcDateCreatedObj</a>."
"evaluated_as_js_string": "Zadaná hodnota je vyhodnocena jako JavaScript řetězec a může být tak obohacena o dynamický obsah pomocí vložené proměnné <code>note</code> (poznámka bude přejmenována). Příklady:",
"example_note": "<code>Poznámka</code> - všechny odpovídající poznámky jsou přejmenovány na 'Poznámka'",
"example_new_title": "<code>NOVÝ: ${note.title}</code> - názvy odpovídajících poznámek mají předponu 'NOVÝ: '",
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> - před názvem nalezených poznámek je uvedeno datum a měsíc jejich vytvoření",
"api_docs": "Podrobnosti najdete v dokumentaci k API v <a href='https://zadam.github.io/trilium/backend_api/Note.html'>poznámce</a> a u jejích vlastností <a href='https://day.js.org/docs/en/display/format'>dateCreatedObj / utcDateCreatedObj</a>."
},
"add_relation": {
"add_relation": "Přidat {{Vazba}}",
"relation_name": "Název {{Vazba}}",
"add_relation": "Přidat vazbu",
"relation_name": "název vazby",
"allowed_characters": "Povolené znaky jsou alfanumerické znaky, podtržítko a dvojtečka.",
"to": "do",
"target_note": "cílová {{name}}",
"create_relation_on_all_matched_notes": "Na všechny nalezené {{name}} vytvořte danou vazbu."
"target_note": "cílová poznámka",
"create_relation_on_all_matched_notes": "U všech shodných not vytvoř dannou vazbu."
},
"delete_relation": {
"delete_relation": "Smazat vazbu",
@@ -565,9 +563,9 @@
"relation_name": "název vazby",
"allowed_characters": "Povolené znaky jsou alfanumerické znaky, podtržítko a dvojtečka.",
"to": "do",
"target_note": "cílová {{name}}",
"on_all_matched_notes": "Na všech odpovídajících {{name}}",
"change_target_note": "změnit cílovou {{name}} existující vazby",
"target_note": "cílová poznámka",
"on_all_matched_notes": "Na všech odpovídajících poznámkách",
"change_target_note": "změnit cílovou poznámku existující vazby",
"update_relation_target": "Aktualizovat cílovou vazbu"
},
"attachments_actions": {
@@ -578,8 +576,8 @@
"download": "Stáhnout",
"rename_attachment": "Přejmenovat přílohu",
"upload_new_revision": "Nahrát novou revizi",
"copy_link_to_clipboard": "Kopírovat odkaz do {{clipboard}}",
"convert_attachment_into_note": "Převést přílohu na {{Note}}",
"copy_link_to_clipboard": "Kopírovat odkaz do schránky",
"convert_attachment_into_note": "Převést přílohu na poznámku",
"delete_attachment": "Smazat přílohu",
"upload_success": "Nová revize přílohy byla nahrána.",
"upload_failed": "Nahrání nové revize přílohy selhalo.",
@@ -599,8 +597,8 @@
"fri": "Pá",
"sat": "So",
"sun": "Ne",
"cannot_find_day_note": "Nelze najít denní {{name}}",
"cannot_find_week_note": "Nelze najít týdenní {{name}}",
"cannot_find_day_note": "Nelze najít denní poznámku",
"cannot_find_week_note": "Nelze najít týdenní poznámku",
"january": "Leden",
"february": "Únor",
"march": "Březen",
@@ -632,7 +630,7 @@
"create_new_split": "Vytvořit nové rozdělení"
},
"edit_button": {
"edit_this_note": "Upravit tuto {{Note}}"
"edit_this_note": "Upravit tuto poznámku"
},
"show_toc_widget_button": {
"show_toc": "Zobrazit Obsah"
@@ -718,7 +716,7 @@
"note_map": "Mapa poznámek"
},
"onclick_button": {
"no_click_handler": "Widget tlačítka '{{componentId}}' nemá definovaný obslužný program kliknutí."
"no_click_handler": "Widget tlačítka '{{componentId}}' nemá definovaný obslužný program kliknutí"
},
"protected_session_status": {
"active": "Chráněná relace je aktivní. Klikněte pro ukončení chráněné relace.",
@@ -775,8 +773,11 @@
"filter": "Filtr",
"filter-none": "Všechny ikony",
"filter-default": "Výchozí ikony",
"icon_tooltip": "{{name}} Balíček ikon: {{iconPack}}",
"no_results": "Žádné ikony nenalezeny."
"icon_tooltip": "{{name}}\nBalíček ikon: {{iconPack}}",
"no_results": "Žádné ikony nenalezeny.",
"search_placeholder_one": "Najít {{number}} ikonu v {{count}} balíčcích",
"search_placeholder_few": "Najít {{number}} ikony v {{count}} balíčcích",
"search_placeholder_other": "Najít {{number}} ikon v {{count}} balíčcích"
},
"basic_properties": {
"note_type": "Typ poznámky",
@@ -789,14 +790,14 @@
"view_type": "Typ zobrazení",
"grid": "Mřížka",
"list": "Seznam",
"collapse_all_notes": "Sbalit všechny {{Note}}s",
"expand_tooltip": "Rozbalí přímé {{Child}} této {{Collection}} (do hloubky jedné úrovně). Pro více možností stiskněte šipku vpravo.",
"collapse_all_notes": "Sbalit všechny poznámky",
"expand_tooltip": "Rozbalí přímé podprvky této kolekce (do hloubky jedné úrovně). Další možnosti zobrazíte kliknutím na šipku vpravo.",
"collapse": "Sbalit",
"expand": "Rozbalit",
"expand_first_level": "Rozbalit přímé {{Child}}",
"expand_first_level": "Rozbalit přímé potomky",
"expand_nth_level": "Rozbalit {{depth}} úrovně",
"expand_all_levels": "Rozbalit všechny úrovně",
"book_properties": "Vlastnosti {{Collection}}",
"book_properties": "Vlastnosti kolekce",
"invalid_view_type": "Neplatný typ zobrazení '{{type}}'",
"calendar": "Kalendář",
"table": "Tabulka",
@@ -829,7 +830,7 @@
"file_size": "Velikost souboru",
"download": "Stáhnout",
"open": "Otevřít",
"copy_reference_to_clipboard": "Kopírovat odkaz do {{clipboard}}",
"copy_reference_to_clipboard": "Kopírovat referenci do schránky",
"upload_new_revision": "Nahrát novou revizi",
"upload_success": "Nová revize obrázku byla nahrána.",
"upload_failed": "Nahrání nové revize obrázku selhalo: {{message}}",
@@ -906,7 +907,7 @@
"limit": "limit",
"limit_description": "Omezit počet výsledků",
"debug": "ladění",
"debug_description": "Ladění vypíše do konzole další ladicí informace, které pomohou při ladění složitých dotazů.",
"debug_description": "Ladění vypíše do konzole další ladicí informace, které pomohou při ladění složitých dotazů",
"action": "akce",
"option": "možnost",
"search_button": "Hledání",
@@ -974,23 +975,23 @@
"search_script": {
"title": "Skript pro hledání:",
"placeholder": "hledat Poznámku podle jejího názvu",
"description1": "Skript pro {{search}} umožňuje definovat výsledky {{search}} spuštěním skriptu. To poskytuje maximální flexibilitu, když standardní {{search}} nestačí.",
"description2": "Skript pro {{search}} musí být typu \"code\" a podtypu \"JavaScript {{backend}}\". Skript musí vrátit pole ID {{Poznámky}} nebo samotných {{Poznámky}}.",
"description1": "Vyhledávací skript umožňuje definovat výsledky vyhledávání spuštěním skriptu. To poskytuje maximální flexibilitu v případech, kdy standardní vyhledávání nestačí.",
"description2": "Vyhledávací skript musí být typu code a podtypu JavaScript backend. Skript musí vracet pole noteIds nebo notes.",
"example_title": "Podívejte se na tento příklad:",
"example_code": "// 1. předfiltrování pomocí standardního {{search}} const candidateNotes = api.searchForNotes(\"#journal\"); // 2. použití vlastních kritérií {{search}} const matchedNotes = candidateNotes .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/)); return matchedNotes;",
"note": "Upozorňujeme, že skript pro {{search}} a {{search}} řetězec nelze kombinovat."
"example_code": "// 1. Předběžné filtrování pomocí standardního vyhledávání\nconst candidateNotes = api.searchForNotes(#journal); \n\n// 2. Použití vlastních vyhledávacích kritérií\nconst matchedNotes = candidateNotes\n .filter(note => note.title.match(/[0-9]{1,2}\\. ?[0-9]{1,2}\\. ?[0-9]{4}/));\n\nreturn matchedNotes;",
"note": "Upozorňujeme, že vyhledávací skript a vyhledávací řetězec nelze vzájemně kombinovat."
},
"search_string": {
"title_column": "{{Search}} řetězec:",
"placeholder": "fulltext klíčová slova, #{{label}} = hodnota...",
"search_syntax": "Syntaxe {{search}}",
"title_column": "Vyhledávací řetězec:",
"placeholder": "fulltext klíčová slova, #tag = hodnota...",
"search_syntax": "Syntaxe vyhledávání",
"also_see": "také viz",
"complete_help": "kompletní nápověda k syntaxi {{search}}",
"full_text_search": "Stačí zadat jakýkoli text pro fulltextové {{search}}.",
"label_abc": "vrací {{Poznámky}} se {{štítek}} abc",
"label_year": "odpovídá {{Poznámky}} se {{štítek}} year s hodnotou 2019",
"label_rock_pop": "odpovídá {{Poznámky}}, které mají {{štítek}} rock i {{štítek}} pop",
"label_rock_or_pop": "stačí, aby byl přítomen jeden z {{štítek}}.",
"complete_help": "kompletní nápověda k syntaxi vyhledávání",
"full_text_search": "Stačí zadat jakýkoli text pro fulltextové vyhledávání",
"label_abc": "vrací poznámky se stítkem abc",
"label_year": "vyhledá poznámky, u nichž má štítek roku hodnotu 2019",
"label_rock_pop": "vyhledá poznámky, které mají štítek rock i pop",
"label_rock_or_pop": "stačí, aby byl přítomen jeden ze štít",
"label_year_comparison": "numerické porovnání (také >, >=, <).",
"label_date_created": "poznámky vytvořené za poslední měsíc",
"error": "Chyba vyhledávání: {{error}}",
@@ -1051,12 +1052,12 @@
"exit-picture-in-picture": "Ukončit obraz v obraze",
"fullscreen": "Celá obrazovka (F)",
"exit-fullscreen": "Ukončit celou obrazovku",
"unsupported-format": "Náhled médií není dostupný pro tento formát souboru: {{mime}}",
"unsupported-format": "Náhled médií není dostupný pro tento formát souboru:\n {{mime}}",
"zoom-to-fit": "Přiblížit do šířky",
"zoom-reset": "Obnovit přiblížení do šířky"
},
"protected_session": {
"enter_password_instruction": "Zobrazení chráněné {{name}} vyžaduje zadání hesla:",
"enter_password_instruction": "Zobrazení chráněné poznámky vyžadující zadání hesla:",
"start_session_button": "Spustit chráněnou relaci",
"started": "Chráněná relace byla spuštěna.",
"wrong_password": "Nesprávné heslo.",
@@ -1068,9 +1069,9 @@
"unprotecting-title": "Stav odchrany"
},
"relation_map": {
"remove_note": "Odstranit {{name}}",
"remove_note": "Odstranit poznámku",
"edit_title": "Upravit název",
"rename_note": "Přejmenovat {{name}}",
"rename_note": "Přejmenovat poznámku",
"enter_new_title": "Zadejte nový název poznámky:",
"remove_relation": "Odstranit vazbu",
"confirm_remove_relation": "Jste si jisti, že chcete odstranit vazbu?",
@@ -1093,7 +1094,7 @@
"disabled_button_enable": "Povolit poznámku pro vykreslení"
},
"web_view_setup": {
"title": "Vytvořte živý pohled na webovou stránku přímo v Trilium.",
"title": "Vytvořte živý pohled na webovou stránku přímo v Trilium",
"url_placeholder": "Zadejte nebo vložte webovou adresu, například https://triliumnotes.org",
"create_button": "Vytvořit Webový Pohled",
"invalid_url_title": "Neplatná adresa",
@@ -1116,13 +1117,13 @@
"full_anonymization_description": "Tato akce vytvoří novou kopii databáze a anonymizuje ji (odstraní veškerý obsah poznámek a ponechá pouze strukturu a některá nesenzitivní metadata) pro sdílení online pro účely ladění bez obav z úniku vašich osobních údajů.",
"save_fully_anonymized_database": "Uložit plně anonymizovanou databázi",
"light_anonymization": "Částečná Anonymizace",
"light_anonymization_description": "Tato akce vytvoří novou kopii databáze a provede na ní částečnou anonymizaci konkrétně bude odstraněn obsah všech {{Note}}, ale názvy a {{attributes}} zůstanou zachovány. Navíc zůstanou zachovány poznámky s vlastními skripty {{frontend}}/{{backend}} v {{JavaScript}} a vlastní {{widget}}y. To poskytuje více kontextu pro ladění problémů.",
"light_anonymization_description": "Tato akce vytvoří novou kopii databáze a provede v ní částečnou anonymizaci konkrétně budou odstraněny pouze obsahy všech poznámek, zatímco názvy a atributy zůstanou zachovány. Zachovány zůstanou také vlastní skripty poznámek v JS pro frontend i backend a vlastní widgety. To poskytuje více kontextu pro ladění problémů.",
"choose_anonymization": "Můžete se sami rozhodnout, zda chcete poskytnout plně nebo částečně anonymizovanou databázi. I plně anonymizovaná databáze je velmi užitečná, ale v některých případech může částečně anonymizovaná databáze urychlit proces identifikace a opravy chyb.",
"save_lightly_anonymized_database": "Uložit částečně anonymizovanou databázi",
"existing_anonymized_databases": "Existující anonymizované databáze",
"creating_fully_anonymized_database": "Vytváření plně anonymizované databáze...",
"creating_lightly_anonymized_database": "Vytváření částečně anonymizované databáze...",
"error_creating_anonymized_database": "Nepodařilo se vytvořit anonymizovanou databázi, zkontrolujte protokoly {{backend}} pro podrobnosti",
"error_creating_anonymized_database": "Nepodařilo se vytvořit anonymizovanou databázi, podrobnosti najdete v protokolech backendu",
"successfully_created_fully_anonymized_database": "Vytvořena plně anonymizovaná databáze v {{anonymizedFilePath}}",
"successfully_created_lightly_anonymized_database": "Vytvořena částečně anonymizovaná databáze v {{anonymizedFilePath}}",
"no_anonymized_database_yet": "Zatím žádná anonymizovaná databáze."
@@ -1197,8 +1198,8 @@
},
"ribbon": {
"widgets": "Widgety pásu karet",
"promoted_attributes_message": "Záložka Propagované atributy na pásu karet se automaticky otevře, pokud jsou na poznámce přítomny propagované atributy.",
"edited_notes_message": "Záložka Upravené poznámky na pásu karet se automaticky otevře u denních poznámek."
"promoted_attributes_message": "Záložka Propagované Atributy se automaticky otevře, pokud jsou v poznámce přítomny povýšené atributy",
"edited_notes_message": "Záložka Upravené poznámky na pásu karet se automaticky otevře u denních poznámek"
},
"theme": {
"title": "Vzhled aplikace",
@@ -1213,8 +1214,8 @@
"layout": "Rozložení",
"layout-vertical-title": "Vertikální",
"layout-horizontal-title": "Horizontální",
"layout-vertical-description": "lišta {{launcher}} je vlevo (výchozí)",
"layout-horizontal-description": "lišta {{launcher}} je pod záložkou, záložka je nyní celá šířka."
"layout-vertical-description": "panel spouštěčů je vlevo (výchozí nastavení)",
"layout-horizontal-description": "panel spouštěčů je pod záložkou, záložka je nyní celá šířka."
},
"ui-performance": {
"title": "Výkon",
@@ -1269,7 +1270,7 @@
"ocr_min_confidence": "Minimální spolehlivost",
"ocr_confidence_description": "Extrahovat pouze text nad touto hranicí spolehlivosti. Nižší hodnoty zahrnují více textu, ale mohou být méně přesné.",
"batch_ocr_title": "Zpracovat existující soubory",
"batch_ocr_description": "Extrahovat text ze všech existujících obrázků, PDF a Office dokumentů ve vašich {{Notes}}. To může trvat nějaký čas v závislosti na počtu souborů.",
"batch_ocr_description": "Extrahovat text ze všech existujících obrázků, PDF a Office dokumentů ve vašich poznámkách. To může trvat nějaký čas v závislosti na počtu souborů.",
"batch_ocr_start": "Spustit dávkové zpracování",
"batch_ocr_starting": "Spuštění dávkového zpracování...",
"batch_ocr_progress": "Zpracovávám {{processed}} z {{total}} souborů...",
@@ -1277,11 +1278,11 @@
"batch_ocr_error": "Chyba během dávkového zpracování: {{error}}"
},
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "Časový limit pro vymazání {{attachments}}",
"attachment_auto_deletion_description": "{{Attachments}} jsou automaticky smazány (a vymazány), pokud nejsou po definovaném časovém limitu odkazovány jejich {{Note}}.",
"erase_attachments_after": "Vymazat nepoužité {{attachments}} po:",
"attachment_erasure_timeout": "Časový limit pro vymazání příloh",
"attachment_auto_deletion_description": "Přílohy jsou automaticky smazány (a vymazány), pokud nejsou po definovaném časovém limitu odkazovány jejich poznámkách.",
"erase_attachments_after": "Vymazat nepoužité přílohy po:",
"manual_erasing_description": "Můžete také spustit vymazání ručně (bez ohledu na výše definovaný časový limit):",
"erase_unused_attachments_now": "Vymazat nepoužité {{attachments}} nyní",
"erase_unused_attachments_now": "Vymazat nepoužité přílohy nyní",
"unused_attachments_erased": "Nepoužívané přílohy byly vymazány."
},
"network_connections": {
@@ -1290,7 +1291,7 @@
},
"note_erasure_timeout": {
"note_erasure_timeout_title": "Časový limit pro vymazání Poznámky",
"note_erasure_description": "SmazaPoznámky (a {{attributes}}, {{revisions}}...) jsou zpočátku pouze označeny jako smazané a lze je obnovit z dialogu Nedávné Poznámky. Po určité době jsou smazaPoznámky \"vymazány\", což znamená, že jejich obsah nelze obnovit. Toto nastavení umožňuje konfigurovat délku doby mezi smazáním a vymazáním Poznámky.",
"note_erasure_description": "Odstraněpoznámky (a atributy, revize...) jsou zpočátku pouze označeny jako odstraněné a lze je obnovit v dialogovém okně „Poslední poznámky. Po uplynutí určité doby jsou odstraněpoznámky vymazány, což znamená, že jejich obsah již nelze obnovit. Toto nastavení vám umožňuje určit dobu, která uplyne mezi odstraněním a vymazáním poznámky.",
"erase_notes_after": "Vymazat Poznámky po:",
"manual_erasing_description": "Můžete také spustit vymazání ručně (bez ohledu na výše uvedený časový limit):",
"erase_deleted_notes_now": "Vymazat smazané Poznámky nyní",
@@ -1311,7 +1312,7 @@
},
"search_engine": {
"title": "Vyhledávač",
"custom_search_engine_info": "Vlastní vyhledávač vyžaduje nastavení jak názvu, tak {{URL}}. Pokud některý z nich není nastaven, bude použit DuckDuckGo jako výchozí vyhledávač.",
"custom_search_engine_info": "Vlastní vyhledávač vyžaduje nastavení jak názvu, tak URL. Pokud některý z nich není nastaven, bude použit DuckDuckGo jako výchozí vyhledávač.",
"predefined_templates_label": "Předdefinované šablony vyhledávačů",
"bing": "Bing",
"baidu": "Baidu",
@@ -1319,8 +1320,8 @@
"google": "Google",
"custom_name_label": "Název vlastního vyhledávače",
"custom_name_placeholder": "Přizpůsobit název vyhledávače",
"custom_url_label": "{{URL}} vlastního vyhledávače by měl obsahovat {keyword} jako zástupný symbol pro vyhledávací výraz.",
"custom_url_placeholder": "Přizpůsobit {{URL}} vyhledávače",
"custom_url_label": "URL vlastního vyhledávače by měl obsahovat {keyword} jako zástupný symbol pro vyhledávací výraz.",
"custom_url_placeholder": "Přizpůsobit URL vyhledávače",
"save_button": "Uložit"
},
"tray": {
@@ -1414,7 +1415,7 @@
"default_token_name": "nový token",
"error_empty_name": "Název tokenu nemůže být prázdný",
"token_created_title": "ETAPI token vytvořen",
"token_created_message": "Zkopírujte vytvořený token do {{clipboard}}. Trilium token ukládá hashovaný a toto je poslední možnost, jak jej uvidíte.",
"token_created_message": "Zkopírujte vytvořený token do schránky. Trilium token ukládá hashovaný a toto je poslední možnost, jak jej uvidíte.",
"rename_token": "Přejmenovat tento token",
"delete_token": "Smazat / deaktivovat tento token",
"rename_token_title": "Přejmenovat token",
@@ -1434,11 +1435,11 @@
"new_password_confirmation": "Potvrzení nového hesla",
"change_password": "Změnit heslo",
"protected_session_timeout": "Časový limit pro Chráněná relace",
"protected_session_timeout_description": "Časový limit chráněné relace je doba, po které se chráněná relace vymaže z paměti prohlížeče. Měří se od poslední interakce s chráněnými {{poznámkami}}. Viz",
"protected_session_timeout_description": "Časový limit chráněné relace je doba, po které se chráněná relace vymaže z paměti prohlížeče. Měří se od poslední interakce s chráněnými poznámkami. Viz",
"wiki": "wiki",
"for_more_info": "pro více informací.",
"protected_session_timeout_label": "Časový limit chráněné relace:",
"reset_confirmation": "Resetováním hesla navždy ztratíte přístup ke všem svým existujícím chráněným {{poznámkám}}. Opravdu chcete heslo resetovat?",
"reset_confirmation": "Resetováním hesla navždy ztratíte přístup ke všem svým existujícím chráněným poznámkám. Opravdu chcete heslo resetovat?",
"reset_success_message": "Heslo bylo resetováno. Nastavte nové heslo",
"change_password_heading": "Změnit heslo",
"set_password_heading": "Nastavit heslo",
@@ -1451,17 +1452,17 @@
"description": "Vícefaktorová autentizace (MFA) přidává další vrstvu zabezpečení k vašemu účtu. Místo pouhého zadání hesla pro přihlášení MFA vyžaduje, abyste poskytli jeden nebo více dalších důkazů pro ověření vaší identity. Tímto způsobem, i když se někdo dostane k vašemu heslu, nemůže se k vašemu účtu dostat bez druhého kusu informace. Je to jako přidání dalšího zámku ke dveřím, což ztěžuje vniknutí kohokoli jiného.<br><br>Postupujte podle pokynů níže a povolte MFA. Pokud to nenakonfigurujete správně, přihlášení se vrátí pouze k heslu.",
"mfa_enabled": "Povolit vícefaktorovou autentizaci",
"mfa_method": "Metoda MFA",
"electron_disabled": "Multifaktorová autentizace není v {{desktop}} verzi aktuálně podporována.",
"totp_title": "Časově založený jednorázový heslo ({{TOTP}})",
"totp_description": "{{TOTP}} (Časově založený jednorázový heslo) je bezpečnostní funkce, která generuje jedinečný, dočasný kód, který se mění každých 30 sekund. Tento kód spolu s vaším heslem použijete k přihlášení do svého účtu, což ztěžuje přístup k němu pro kohokoli jiného.",
"totp_secret_title": "Generovat {{TOTP}} tajný klíč",
"totp_secret_generate": "Generovat {{TOTP}} tajný klíč",
"totp_secret_regenerate": "Regenerovat {{TOTP}} tajný klíč",
"no_totp_secret_warning": "Pro povolení {{TOTP}} je nejprve nutné vygenerovat {{TOTP}} tajný klíč.",
"totp_secret_description_warning": "Po vygenerování nového {{TOTP}} tajného klíče budete muset znovu přihlásit pomocí nového {{TOTP}} tajného klíče.",
"totp_secret_generated": "{{TOTP}} tajný klíč vygenerován",
"electron_disabled": "Multifaktorová autentizace není v desktop verzi aktuálně podporována.",
"totp_title": "Časově založené jednorázové heslo (TOTP)",
"totp_description": "TOTP (Časově založené jednorázové heslo) je bezpečnostní funkce, která generuje jedinečný, dočasný kód, který se mění každých 30 sekund. Tento kód spolu s vaším heslem použijete k přihlášení do svého účtu, což ztěžuje přístup k němu pro kohokoli jiného.",
"totp_secret_title": "Generovat TOTP tajný klíč",
"totp_secret_generate": "Generovat TOTP tajný klíč",
"totp_secret_regenerate": "Regenerovat TOTP tajný klíč",
"no_totp_secret_warning": "Pro povolení TOTP je nejprve nutné vygenerovat TOTP tajný klíč.",
"totp_secret_description_warning": "Po vygenerování nového TOTP tajného klíče budete muset znovu přihlásit pomocí nového TOTP tajného klíče.",
"totp_secret_generated": "TOTP tajný klíč vygenerován",
"totp_secret_warning": "Uložte vygenerovaný tajný klíč na bezpečném místě. Nebude znovu zobrazen.",
"totp_secret_regenerate_confirm": "Jste si jisti, že chcete regenerovat {{TOTP}} tajný klíč? Tím se zneplatní předchozí {{TOTP}} tajný klíč a všechny existující záchranné kódy.",
"totp_secret_regenerate_confirm": "Jste si jisti, že chcete regenerovat TOTP tajný klíč? Tím se zneplatní předchozí TOTP tajný klíč a všechny existující záchranné kódy.",
"recovery_keys_title": "Záchranné klíče pro jednotné přihlašování",
"recovery_keys_description": "Záchranné klíče pro jednotné přihlašování se používají k přihlášení v případě, že nemáte přístup ke svým autentifikačním kódům.",
"recovery_keys_description_warning": "Záchranné klíče nebudou po opuštění stránky znovu zobrazeny, uchovejte je na bezpečném místě. Po použití záchranného klíče jej nelze znovu použít.",
@@ -1498,16 +1499,12 @@
"description": "Tato nastavení platí pouze pro ⟨desktop⟩ verze, prohlížeče budou používat vlastní nativní kontrolu pravopisu.",
"enable": "Povolit kontrolu pravopisu",
"language_code_label": "Kód jazyka(ů)",
"language_code_placeholder": "například \"en-US\", \"de-AT\"",
"multiple_languages_info": "Více jazyků lze oddělit čárkou, např. \"en-US, de-DE, cs\". ",
"available_language_codes_label": "Dostupné jazykové kódy:",
"restart-required": "Změny v nastavení kontroly pravopisu se projeví po restartu aplikace."
},
"sync_2": {
"config_title": "Konfigurace Synchronizace",
"server_address": "Adresa instance serveru",
"timeout": "Časový limit synchronizace",
"timeout_unit": "milisekund",
"proxy_label": "Proxy server pro synchronizaci (volitelné)",
"note": "Poznámka",
"note_description": "Pokud ponecháte nastavení proxy prázdné, bude použit systémový proxy (platí pouze pro desktop/electron build).",
@@ -1696,7 +1693,10 @@
"options": "Nastavení",
"modal_title": "Konfigurovat seznam zvýraznění",
"menu_configure": "Konfigurovat seznam zvýraznění...",
"no_highlights": "Nebyla nalezena žádná zvýraznění."
"no_highlights": "Nebyla nalezena žádná zvýraznění.",
"title_with_count_one": "{{count}} zvýraznění",
"title_with_count_few": "{{count}} zvýraznění",
"title_with_count_other": "{{count}} zvýraznění"
},
"quick-search": {
"placeholder": "Rychlé hledání",
@@ -1727,7 +1727,10 @@
"shared-indicator-tooltip-with-url": "Tato poznámka je sdílena veřejně na: {{- url}}",
"subtree-hidden-moved-title": "Přidáno do {{title}}",
"subtree-hidden-moved-description-collection": "Tato kolekce skrývá své dceřiné poznámky ve stromu.",
"subtree-hidden-moved-description-other": "Dceřiné poznámky jsou pro tuto poznámku skryty ve stromu."
"subtree-hidden-moved-description-other": "Dceřiné poznámky jsou pro tuto poznámku skryty ve stromu.",
"subtree-hidden-tooltip_one": "{{count}} podřízená poznámka, která je ve stromu skrytá",
"subtree-hidden-tooltip_few": "{{count}} podřízené poznámky, které jsou ve stromu skryté",
"subtree-hidden-tooltip_other": "{{count}} podřízených poznámek, které jsou ve stromu skryté"
},
"title_bar_buttons": {
"window-on-top": "Připítnout okno na horní část obrazovky"
@@ -1740,7 +1743,10 @@
"print_report_error_title": "Tisk se nezdařil",
"print_report_stack_trace": "Stack trace",
"print_report_collection_details_button": "Zobrazit podrobnosti",
"print_report_collection_details_ignored_notes": "Ignorované poznámky"
"print_report_collection_details_ignored_notes": "Ignorované poznámky",
"print_report_collection_content_one": "{{count}} poznámka ve sbírce nebyla možná vytisknout, protože není podporována nebo je chráněna.",
"print_report_collection_content_few": "{{count}} poznámky ve sbírce nebylo možné vytisknout, protože nejsou podporovány nebo jsou chráněny.",
"print_report_collection_content_other": "{{count}} poznámek ve sbírce nebylo možné vytisknout, protože nejsou podporovány nebo jsou chráněny."
},
"note_title": {
"placeholder": "Zde napište název poznámky...",
@@ -1892,7 +1898,7 @@
"search_online": "Hledat „{{term}}“ pomocí {{searchEngine}}"
},
"image_context_menu": {
"copy_reference_to_clipboard": "Kopírovat odkaz do schránky",
"copy_reference_to_clipboard": "Kopírovat referenci do schránky",
"copy_image_to_clipboard": "Kopírovat obrázek do schránky"
},
"link_context_menu": {
@@ -2086,7 +2092,10 @@
"processing": "Zpracovávání...",
"processing_started": "Zpracování OCR bylo zahájeno. Počkejte chvíli a obnovte stránku.",
"processing_failed": "Nepodařilo se zahájit zpracování OCR",
"view_extracted_text": "Zobrazit extraktovaný text (OCR)"
"view_extracted_text": "Zobrazit extraktovaný text (OCR)",
"processing_complete": "Zpracování pomocí OCR bylo dokončeno.",
"text_filtered_low_confidence": "OCR rozpoznal text s jistotou {{confidence}} %, ale výsledek byl vyřazen, protože vaše minimální prahová hodnota je {{threshold}} %.",
"open_media_settings": "Otevřít Nastavení"
},
"command_palette": {
"tree-action-name": "Strom: {{name}}",
@@ -2118,7 +2127,7 @@
"background_effects_message": "Na zařízeních Windows a macOS jsou efekty pozadí nyní stabilní. Efekty pozadí přidávají uživatelskému rozhraní nádech barvy rozostřením pozadí za ním.",
"background_effects_button": "Povolit efekty pozadí",
"new_layout_title": "Nové rozvržení",
"new_layout_message": "Pro Trilium jsme zavedli modernizované rozvržení. Pás karet byl odstraněn a plynule integrovan do hlavního rozhraní, přičemž nové stavové řádky a rozšiřitelné sekce (jako jsou propagované atributy) převzaly klíčové funkce. Nové rozvržení je padrãoně zapnuté a lze jej dočasně vypnout přes Nastavení → Vzhled.",
"new_layout_message": "Pro Trilium jsme zavedli modernizované rozvržení. Pás karet byl odstraněn a plynule integrovan do hlavního rozhraní, přičemž nové stavové řádky a rozšiřitelné sekce (jako jsou propagované atributy) převzaly klíčové funkce. \n\nNové rozvržení je padrãoně zapnuté a lze jej dočasně vypnout přes Nastavení → Vzhled.",
"new_layout_button": "Více informací",
"dismiss": "Zavřít"
},
@@ -2153,7 +2162,7 @@
},
"server": {
"unknown_http_error_title": "Chyba komunikace se serverem",
"unknown_http_error_content": "Stavový kód: {{statusCode}} URL: {{method}} {{url}} Zpráva: {{message}}",
"unknown_http_error_content": "Stavový kód: {{statusCode}} \nURL: {{method}} {{url}} \nZpráva: {{message}}",
"traefik_blocks_requests": "Pokud používáte reverzní proxy Traefik, došlo k wprowadzeniu zásadní změny, která ovlivňuje komunikaci se serverem."
},
"tab_history_navigation_buttons": {
@@ -2170,18 +2179,18 @@
},
"breadcrumb_badges": {
"read_only_explicit": "Pouze pro čtení",
"read_only_explicit_description": "Tato poznámka byla ručně nastavena jako pouze pro čtení. Kliknutím ji můžete dočasně upravit.",
"read_only_explicit_description": "Tato poznámka byla ručně nastavena jako pouze pro čtení.\nKliknutím ji můžete dočasně upravit.",
"read_only_auto": "Automatické pouze pro čtení",
"read_only_auto_description": "Tato poznámka byla automaticky nastavena do režimu pouze pro čtení z důvodu výkonu. Toto automatické omezení lze upravit v nastavení. Kliknutím ji můžete dočasně upravit.",
"read_only_auto_description": "Tato poznámka byla automaticky nastavena do režimu pouze pro čtení z důvodu výkonu. Toto automatické omezení lze upravit v nastavení.\n\nKliknutím ji můžete dočasně upravit.",
"read_only_temporarily_disabled": "Dočasně upravitelné",
"read_only_temporarily_disabled_description": "Tato poznámka je nyní upravitelná, ale normálně je pouze pro čtení. Jakmile přejdete na jinou poznámku, vrátí se do režimu pouze pro čtení. Kliknutím znovu aktivujete režim pouze pro čtení.",
"read_only_temporarily_disabled_description": "Tato poznámka je nyní upravitelná, ale normálně je pouze pro čtení. Jakmile přejdete na jinou poznámku, vrátí se do režimu pouze pro čtení.\n\nKliknutím znovu aktivujete režim pouze pro čtení.",
"shared_publicly": "Veřejně sdílené",
"shared_locally": "Lokálně sdílené",
"shared_copy_to_clipboard": "Kopírovat odkaz do schránky",
"shared_open_in_browser": "Otevřít odkaz v prohlížeči",
"shared_unshare": "Zrušit sdílení",
"clipped_note": "Webový výřez",
"clipped_note_description": "Tato poznámka byla původně převzata z {{url}}. Kliknutím přejdete na zdrojovou webovou stránku.",
"clipped_note_description": "Tato poznámka byla původně převzata z {{url}}.\n\nKliknutím přejdete na zdrojovou webovou stránku.",
"execute_script": "Spustit skript",
"execute_script_description": "Tato poznámka obsahuje skript. Kliknutím jej spustíte.",
"execute_sql": "Spustit SQL",
@@ -2199,7 +2208,25 @@
"note_info_title": "Zobrazit informace o poznámce (např. datum, velikost poznámky)",
"attributes_title": "Vlastní atributy a zděděné atributy",
"note_paths_title": "Cesty poznámky",
"code_note_switcher": "Změnit režim jazyka"
"code_note_switcher": "Změnit režim jazyka",
"backlinks_one": "{{count}} zpětný odkaz",
"backlinks_few": "{{count}} zpětné odkazy",
"backlinks_other": "{{count}} zpětných odkazů",
"backlinks_title_one": "Zobrazit zpětný odkaz",
"backlinks_title_few": "Zobrazit zpětné odkazy",
"backlinks_title_other": "Zobrazení zpětných odkazu",
"attachments_one": "{{count}} příloha",
"attachments_few": "{{count}} přílohy",
"attachments_other": "{{count}} příloh",
"attachments_title_one": "Zobrazit přílohu v nové záložce",
"attachments_title_few": "Zobrazit přílohy v nové záložce",
"attachments_title_other": "Zobrazit přílohy v nové záložce",
"attributes_one": "{{count}} atribut",
"attributes_few": "{{count}} atributy",
"attributes_other": "{{count}} atributů",
"note_paths_one": "{{count}} cesta",
"note_paths_few": "{{count}} cesty",
"note_paths_other": "{{count}} cest"
},
"attributes_panel": {
"title": "Atributy poznámky"
@@ -2212,13 +2239,25 @@
},
"pdf": {
"pages_alt": "Strana {{pageNumber}}",
"pages_loading": "Načítání..."
"pages_loading": "Načítání...",
"attachments_one": "{{count}} příloha",
"attachments_few": "{{count}} přílohy",
"attachments_other": "{{count}} příloh",
"layers_one": "{{count}} vrstva",
"layers_few": "{{count}} vrstvy",
"layers_other": "{{count}} vrstev",
"pages_one": "{{count}} stránka",
"pages_few": "{{count}} stránky",
"pages_other": "{{count}} stran"
},
"platform_indicator": {
"available_on": "Dostupné na {{platform}}"
},
"mobile_tab_switcher": {
"more_options": "Další nastavení"
"more_options": "Další nastavení",
"title_one": "{{count}} záložka",
"title_few": "{{count}} záložky",
"title_other": "{{count}} záložek"
},
"bookmark_buttons": {
"bookmarks": "Záložky (oblíbené)"
@@ -2276,7 +2315,9 @@
"sample_user_journey": "Cesta uživatele",
"sample_xy": "XY",
"sample_venn": "Vennův diagram",
"sample_ishikawa": "Ishikawa"
"sample_ishikawa": "Ishikawa",
"sample_treeview": "Stromové zobrazení",
"sample_wardley": "Wardleyho mapa"
},
"mind-map": {
"addChild": "Přidat dceřiný",

View File

@@ -88,7 +88,6 @@
"also_delete_note": "Auch die Notiz löschen"
},
"delete_notes": {
"delete_notes_preview": "Vorschau der Notizen löschen",
"close": "Schließen",
"delete_all_clones_description": "auch alle Klone löschen (kann bei letzte Änderungen rückgängig gemacht werden)",
"erase_notes_description": "Beim normalen (vorläufigen) Löschen werden die Notizen nur als gelöscht markiert und sie können innerhalb eines bestimmten Zeitraums (im Dialogfeld „Letzte Änderungen“) wiederhergestellt werden. Wenn du diese Option aktivierst, werden die Notizen sofort gelöscht und es ist nicht möglich, die Notizen wiederherzustellen.",
@@ -96,9 +95,7 @@
"notes_to_be_deleted": "Folgende Notizen werden gelöscht ({{notesCount}})",
"no_note_to_delete": "Es werden keine Notizen gelöscht (nur Klone).",
"broken_relations_to_be_deleted": "Folgende Beziehungen werden gelöst und gelöscht ({{ relationCount}})",
"cancel": "Abbrechen",
"ok": "OK",
"deleted_relation_text": "Notiz {{- note}} (soll gelöscht werden) wird von Beziehung {{- relation}} ausgehend von {{- source}} referenziert."
"cancel": "Abbrechen"
},
"export": {
"export_note_title": "Notiz exportieren",
@@ -1386,9 +1383,6 @@
"description": "Diese Optionen gelten nur für Desktop-Builds. Browser verwenden ihre eigene native Rechtschreibprüfung.",
"enable": "Aktiviere die Rechtschreibprüfung",
"language_code_label": "Sprachcode(s)",
"language_code_placeholder": "zum Beispiel \"en-US\", \"de-AT\"",
"multiple_languages_info": "Mehrere Sprachen können mit einem Komma getrennt werden z.B. \"en-US, de-DE, cs\". ",
"available_language_codes_label": "Verfügbare Sprachcodes:",
"restart-required": "Änderungen an den Rechtschreibprüfungsoptionen werden nach dem Neustart der Anwendung wirksam."
},
"sync_2": {
@@ -1404,8 +1398,7 @@
"test_title": "Synchronisierungstest",
"test_description": "Dadurch werden die Verbindung und der Handshake zum Synchronisierungsserver getestet. Wenn der Synchronisierungsserver nicht initialisiert ist, wird er dadurch für die Synchronisierung mit dem lokalen Dokument eingerichtet.",
"test_button": "Teste die Synchronisierung",
"handshake_failed": "Handshake des Synchronisierungsservers fehlgeschlagen, Fehler: {{message}}",
"timeout_unit": "Millisekunden"
"handshake_failed": "Handshake des Synchronisierungsservers fehlgeschlagen, Fehler: {{message}}"
},
"api_log": {
"close": "Schließen"

View File

@@ -4,7 +4,7 @@
"homepage": "Αρχική Σελίδα:",
"app_version": "Έκδοση εφαρμογής:",
"db_version": "Έκδοση βάσης δεδομένων:",
"sync_version": "Έκδοση πρωτοκόλου συγχρονισμού:",
"sync_version": "Έκδοση συγχρονισμού:",
"build_date": "Ημερομηνία χτισίματος εφαρμογής:",
"build_revision": "Αριθμός αναθεώρησης χτισίματος:",
"data_directory": "Φάκελος δεδομένων:"

View File

@@ -88,17 +88,23 @@
"also_delete_note": "Also delete the note"
},
"delete_notes": {
"delete_notes_preview": "Delete notes preview",
"title": "Delete notes",
"close": "Close",
"clones_label": "Clones",
"delete_clones_description_one": "Also delete {{count}} other clone. Can be undone in recent changes.",
"delete_clones_description_other": "Also delete {{count}} other clones. Can be undone in recent changes.",
"delete_all_clones_description": "Delete also all clones (can be undone in recent changes)",
"erase_notes_description": "Normal (soft) deletion only marks the notes as deleted and they can be undeleted (in recent changes dialog) within a period of time. Checking this option will erase the notes immediately and it won't be possible to undelete the notes.",
"erase_notes_label": "Erase permanently",
"erase_notes_description": "Erase notes immediately instead of soft deletion. This cannot be undone and will force application reload.",
"erase_notes_warning": "Erase notes permanently (can't be undone), including all clones. This will force application reload.",
"notes_to_be_deleted": "Following notes will be deleted ({{notesCount}})",
"notes_to_be_deleted": "Notes to be deleted ({{notesCount}})",
"no_note_to_delete": "No note will be deleted (only clones).",
"broken_relations_to_be_deleted": "Following relations will be broken and deleted ({{ relationCount}})",
"broken_relations_to_be_deleted": "Broken relations ({{relationCount}})",
"table_note_with_relation": "Note with relation",
"table_relation": "Relation",
"table_points_to": "Points to (deleted)",
"cancel": "Cancel",
"ok": "OK",
"deleted_relation_text": "Note {{- note}} (to be deleted) is referenced by relation {{- relation}} originating from {{- source}}."
"delete": "Delete"
},
"export": {
"export_note_title": "Export note",
@@ -209,6 +215,7 @@
"box_size_small": "small (~ 10 lines)",
"box_size_medium": "medium (~ 30 lines)",
"box_size_full": "full (box shows complete text)",
"box_size_expandable": "expandable (collapsed by default)",
"button_include": "Include note"
},
"info": {
@@ -806,7 +813,11 @@
"board": "Board",
"presentation": "Presentation",
"include_archived_notes": "Show archived notes",
"hide_child_notes": "Hide child notes in tree"
"hide_child_notes": "Hide child notes in tree",
"open_all_in_tabs": "Open all",
"open_all_in_tabs_tooltip": "Open all results in new tabs",
"open_all_confirm": "This will open {{count}} notes in new tabs. Continue?",
"open_all_too_many": "Too many results ({{count}}). Maximum is {{max}}."
},
"edited_notes": {
"no_edited_notes_found": "No edited notes on this day yet...",
@@ -860,7 +871,8 @@
"collapse": "Collapse to normal size",
"title": "Note Map",
"fix-nodes": "Fix nodes",
"link-distance": "Link distance"
"link-distance": "Link distance",
"too-many-notes": "This subtree contains {{count}} notes, which exceeds the limit of {{max}} that can be displayed in the note map."
},
"note_paths": {
"title": "Note Paths",
@@ -1074,6 +1086,7 @@
"edit_title": "Edit title",
"rename_note": "Rename note",
"enter_new_title": "Enter new note title:",
"rename_relation": "Rename relation",
"remove_relation": "Remove relation",
"confirm_remove_relation": "Are you sure you want to remove the relation?",
"specify_new_relation_name": "Specify new relation name (allowed characters: alphanumeric, colon and underscore):",
@@ -1400,7 +1413,8 @@
"date-and-time": "Date & time",
"path": "Path",
"database_backed_up_to": "Database has been backed up to {{backupFilePath}}",
"no_backup_yet": "no backup yet"
"no_backup_yet": "no backup yet",
"download": "Download"
},
"etapi": {
"title": "ETAPI",
@@ -1498,18 +1512,21 @@
"spellcheck": {
"title": "Spell Check",
"description": "These options apply only for desktop builds, browsers will use their own native spell check.",
"enable": "Enable spellcheck",
"language_code_label": "Language code(s)",
"language_code_placeholder": "for example \"en-US\", \"de-AT\"",
"multiple_languages_info": "Multiple languages can be separated by comma, e.g. \"en-US, de-DE, cs\". ",
"available_language_codes_label": "Available language codes:",
"restart-required": "Changes to the spell check options will take effect after application restart."
"enable": "Check spelling",
"language_code_label": "Spell Check Languages",
"restart-required": "Changes to the spell check options will take effect after application restart.",
"custom_dictionary_title": "Custom Dictionary",
"custom_dictionary_description": "Words added to the dictionary are synced across all your devices.",
"custom_dictionary_edit": "Custom words",
"custom_dictionary_edit_description": "Edit the list of words that should not be flagged by the spell checker. Changes will be visible after a restart.",
"custom_dictionary_open": "Edit dictionary",
"related_description": "Configure spell check languages and custom dictionary."
},
"sync_2": {
"config_title": "Sync Configuration",
"server_address": "Server instance address",
"timeout": "Sync timeout",
"timeout_unit": "milliseconds",
"timeout_description": "How long to wait before giving up on a slow sync connection. Increase if you have an unstable network.",
"proxy_label": "Sync proxy server (optional)",
"note": "Note",
"note_description": "If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).",
@@ -1867,7 +1884,8 @@
"theme_none": "No syntax highlighting",
"theme_group_light": "Light themes",
"theme_group_dark": "Dark themes",
"copy_title": "Copy to clipboard"
"copy_title": "Copy to clipboard",
"click_to_copy": "Click to copy"
},
"classic_editor_toolbar": {
"title": "Formatting"

View File

@@ -88,7 +88,6 @@
"also_delete_note": "También eliminar la nota"
},
"delete_notes": {
"delete_notes_preview": "Eliminar vista previa de notas",
"close": "Cerrar",
"delete_all_clones_description": "Eliminar también todos los clones (se puede deshacer en cambios recientes)",
"erase_notes_description": "La eliminación normal (suave) solo marca las notas como eliminadas y se pueden recuperar (en el cuadro de diálogo de cambios recientes) dentro de un periodo de tiempo. Al marcar esta opción se borrarán las notas inmediatamente y no será posible recuperarlas.",
@@ -96,9 +95,7 @@
"notes_to_be_deleted": "Las siguientes notas serán eliminadas ({{notesCount}})",
"no_note_to_delete": "No se eliminará ninguna nota (solo clones).",
"broken_relations_to_be_deleted": "Las siguientes relaciones se romperán y serán eliminadas ({{ relationCount}})",
"cancel": "Cancelar",
"ok": "Aceptar",
"deleted_relation_text": "Nota {{- note}} (para ser eliminada) está referenciado por la relación {{- relation}} que se origina en {{- source}}."
"cancel": "Cancelar"
},
"export": {
"export_note_title": "Exportar nota",
@@ -1332,7 +1329,8 @@
"date-and-time": "Fecha y hora",
"path": "Ruta",
"database_backed_up_to": "Se ha realizado una copia de seguridad de la base de datos en {{backupFilePath}}",
"no_backup_yet": "no hay copia de seguridad todavía"
"no_backup_yet": "no hay copia de seguridad todavía",
"download": "Descargar"
},
"etapi": {
"title": "ETAPI",
@@ -1432,16 +1430,12 @@
"description": "Estas opciones se aplican sólo para compilaciones de escritorio; los navegadores utilizarán su corrector ortográfico nativo.",
"enable": "Habilitar corrector ortográfico",
"language_code_label": "Código(s) de idioma",
"language_code_placeholder": "por ejemplo \"en-US\", \"de-AT\"",
"multiple_languages_info": "Múltiples idiomas se pueden separar por coma, por ejemplo \"en-US, de-DE, cs\". ",
"available_language_codes_label": "Códigos de idioma disponibles:",
"restart-required": "Los cambios en las opciones de corrección ortográfica entrarán en vigor después del reinicio de la aplicación."
},
"sync_2": {
"config_title": "Configuración de sincronización",
"server_address": "Dirección de la instancia del servidor",
"timeout": "Tiempo de espera de sincronización (milisegundos)",
"timeout_unit": "milisegundos",
"proxy_label": "Sincronizar servidor proxy (opcional)",
"note": "Nota",
"note_description": "Si deja la configuración del proxy en blanco, se utilizará el proxy del sistema (se aplica únicamente a la compilación de escritorio/electron).",

View File

@@ -62,12 +62,10 @@
"also_delete_note": "Poista myös muistio"
},
"delete_notes": {
"delete_notes_preview": "Poista muistion esikatselu",
"close": "Sulje",
"notes_to_be_deleted": "Seuraavat muistiot tullaan poistamaan ({{notesCount}})",
"no_note_to_delete": "Muistioita ei poisteta (vain kopiot).",
"cancel": "Peruuta",
"ok": "OK"
"cancel": "Peruuta"
},
"export": {
"export_note_title": "Vie muistio",

View File

@@ -88,7 +88,6 @@
"also_delete_note": "Supprimer également la note"
},
"delete_notes": {
"delete_notes_preview": "Supprimer la note",
"close": "Fermer",
"delete_all_clones_description": "Supprimer aussi les clones (peut être annulé dans des modifications récentes)",
"erase_notes_description": "La suppression normale (douce) marque uniquement les notes comme supprimées et elles peuvent être restaurées (dans la boîte de dialogue des Modifications récentes) dans un délai donné. Cocher cette option effacera les notes immédiatement et il ne sera pas possible de les restaurer.",
@@ -96,9 +95,7 @@
"notes_to_be_deleted": "Les notes suivantes seront supprimées ({{notesCount}})",
"no_note_to_delete": "Aucune note ne sera supprimée (uniquement les clones).",
"broken_relations_to_be_deleted": "Les relations suivantes seront rompues et supprimées ({{ relationCount}})",
"cancel": "Annuler",
"ok": "OK",
"deleted_relation_text": "Note {{- note}} (à supprimer) est référencée dans la relation {{- relation}} provenant de {{- source}}."
"cancel": "Annuler"
},
"export": {
"export_note_title": "Exporter la note",
@@ -1391,9 +1388,6 @@
"description": "Ces options s'appliquent uniquement aux versions de bureau, les navigateurs utiliseront leur propre vérification orthographique native.",
"enable": "Activer la vérification orthographique",
"language_code_label": "Code(s) de langue",
"language_code_placeholder": "par exemple \"fr-FR\", \"en-US\", \"de-AT\"",
"multiple_languages_info": "Plusieurs langues peuvent être séparées par une virgule, par ex. \"fr-FR, en-US, de-DE, cs\". ",
"available_language_codes_label": "Codes de langue disponibles :",
"restart-required": "Les modifications apportées aux options de vérification orthographique prendront effet après le redémarrage de l'application."
},
"sync_2": {
@@ -1409,8 +1403,7 @@
"test_title": "Test de synchronisation",
"test_description": "Testera la connexion et la prise de contact avec le serveur de synchronisation. Si le serveur de synchronisation n'est pas initialisé, cela le configurera pour qu'il se synchronise avec le document local.",
"test_button": "Tester la synchronisation",
"handshake_failed": "Échec de la négociation avec le serveur de synchronisation, erreur : {{message}}",
"timeout_unit": "millisecondes"
"handshake_failed": "Échec de la négociation avec le serveur de synchronisation, erreur : {{message}}"
},
"api_log": {
"close": "Fermer"

View File

@@ -119,7 +119,6 @@
"also_delete_note": "Scrios an nóta freisin"
},
"delete_notes": {
"delete_notes_preview": "Réamhamharc ar scriosadh nótaí",
"close": "Dún",
"delete_all_clones_description": "Scrios gach clón freisin (is féidir é seo a chealú in athruithe le déanaí)",
"erase_notes_description": "Ní mharcálann scriosadh gnáth (bog) ach na nótaí mar scriosta agus is féidir iad a dhíscriosadh (sa dialóg athruithe le déanaí) laistigh de thréimhse ama. Scriosfar na nótaí láithreach má sheiceálann tú an rogha seo agus ní bheidh sé indéanta na nótaí a dhíscriosadh.",
@@ -127,9 +126,7 @@
"notes_to_be_deleted": "Scriosfar na nótaí seo a leanas ({{notesCount}})",
"no_note_to_delete": "Ní scriosfar aon nóta (clóin amháin).",
"broken_relations_to_be_deleted": "Brisfear agus scriosfar na caidrimh seo a leanas ({{ relationCount}})",
"cancel": "Cealaigh",
"ok": "Ceart go leor",
"deleted_relation_text": "Tá tagairt don nóta {{- note}} (le scriosadh) le gaol {{- relation}} a thagann ó {{- source}}."
"cancel": "Cealaigh"
},
"export": {
"export_note_title": "Nóta easpórtála",
@@ -1071,7 +1068,8 @@
"note_already_in_diagram": "Tabhair faoi deara go bhfuil \"{{title}}\" sa léaráid cheana féin.",
"enter_title_of_new_note": "Cuir isteach teideal an nóta nua",
"default_new_note_title": "nóta nua",
"click_on_canvas_to_place_new_note": "Cliceáil ar chanbhás chun nóta nua a chur"
"click_on_canvas_to_place_new_note": "Cliceáil ar chanbhás chun nóta nua a chur",
"rename_relation": "Athainmnigh an gaol"
},
"backend_log": {
"refresh": "Athnuachan"
@@ -1468,18 +1466,20 @@
"spellcheck": {
"title": "Seiceáil Litrithe",
"description": "Ní bhaineann na roghanna seo ach le leaganacha deisce, úsáidfidh brabhsálaithe a seiceáil litrithe dúchasach féin.",
"enable": "Cumasaigh seiceáil litrithe",
"language_code_label": "Cód(anna) teanga",
"language_code_placeholder": "mar shampla \"en-US\", \"de-AT\"",
"multiple_languages_info": "Is féidir camóg a úsáid chun teangacha iolracha a dheighilt óna chéile, m.sh. \"en-US, de-DE, cs\". ",
"available_language_codes_label": "Cóid teanga atá ar fáil:",
"restart-required": "Tiocfaidh athruithe ar na roghanna seiceála litrithe i bhfeidhm tar éis atosú an fheidhmchláir."
"enable": "Seiceáil litriú",
"language_code_label": "Seiceáil Litrithe Teangacha",
"restart-required": "Tiocfaidh athruithe ar na roghanna seiceála litrithe i bhfeidhm tar éis atosú an fheidhmchláir.",
"custom_dictionary_title": "Foclóir Saincheaptha",
"custom_dictionary_description": "Déantar focail a chuirtear leis an bhfoclóir a sioncrónú ar fud do ghléasanna go léir.",
"custom_dictionary_edit": "Focail saincheaptha",
"custom_dictionary_edit_description": "Cuir an liosta focal in eagar nach ceart don seiceálaí litrithe a mharcáil. Beidh athruithe le feiceáil tar éis atosaithe.",
"custom_dictionary_open": "Cuir an foclóir in eagar",
"related_description": "Cumraigh teangacha seiceála litrithe agus foclóir saincheaptha."
},
"sync_2": {
"config_title": "Cumraíocht Sioncrónaithe",
"server_address": "Seoladh sampla an fhreastalaí",
"timeout": "Am scoir sioncrónaithe",
"timeout_unit": "milleasoicindí",
"proxy_label": "Sioncrónaigh freastalaí seachfhreastalaí (roghnach)",
"note": "Nóta",
"note_description": "Má fhágann tú an socrú seachfhreastalaí bán, úsáidfear seachfhreastalaí an chórais (baineann sé le tógáil deisce/leictreon amháin).",
@@ -2294,7 +2294,9 @@
"sample_user_journey": "Turas Úsáideora",
"sample_xy": "XY",
"sample_venn": "Venn",
"sample_ishikawa": "Ishikawa"
"sample_ishikawa": "Ishikawa",
"sample_treeview": "Radharc Crann",
"sample_wardley": "Léarscáil Wardley"
},
"llm_chat": {
"placeholder": "Clóscríobh teachtaireacht...",
@@ -2404,6 +2406,9 @@
"processing": "Ag próiseáil...",
"processing_started": "Tá próiseáil OCR tosaithe. Fan nóiméad agus athnuachan le do thoil.",
"processing_failed": "Theip ar phróiseáil OCR a thosú",
"view_extracted_text": "Féach ar théacs eastósctha (OCR)"
"view_extracted_text": "Féach ar théacs eastósctha (OCR)",
"processing_complete": "Próiseáil OCR críochnaithe.",
"text_filtered_low_confidence": "Bhraith OCR téacs le muinín {{confidence}}%, ach caitheadh leis é mar is é {{threshold}}% an tairseach íosta atá agat.",
"open_media_settings": "Oscail Socruithe"
}
}

View File

@@ -94,7 +94,6 @@
"if_you_dont_check": "अगर आप इसे चेक नहीं करते हैं, तो नोट केवल रिलेशन मैप से हटाया जाएगा।"
},
"delete_notes": {
"delete_notes_preview": "नोट्स प्रिव्यू डिलीट करें",
"close": "बंद करें",
"delete_all_clones_description": "सभी क्लोन भी डिलीट करें (हाल के बदलावों में वापस ला सकते हैं)",
"erase_notes_description": "सामान्य (सॉफ्ट) डिलीट करने पर नोट केवल 'डिलीटेड' मार्क होते हैं और उन्हें एक निश्चित समय के भीतर (हाल के बदलावों वाले डायलॉग में) वापस लाया जा सकता है। इस विकल्प को चुनने पर नोट तुरंत पूरी तरह मिटा दिए जाएंगे और उन्हें वापस लाना संभव नहीं होगा।",
@@ -102,9 +101,7 @@
"notes_to_be_deleted": "निम्नलिखित नोट डिलीट कर दिए जाएंगे ({{notesCount}})",
"no_note_to_delete": "कोई भी नोट डिलीट नहीं होगा (केवल क्लोन हटाए जाएंगे)।",
"broken_relations_to_be_deleted": "निम्नलिखित रिलेशन टूट जाएंगे और डिलीट हो जाएंगे ({{relationCount}})",
"cancel": "रद्द करें",
"ok": "ठीक है",
"deleted_relation_text": "नोट {{- note}} (जिसे डिलीट किया जाना है) का संदर्भ {{- source}} से शुरू होने वाले रिलेशन {{- relation}} में दिया गया है।"
"cancel": "रद्द करें"
},
"branch_prefix": {
"edit_branch_prefix": "ब्रांच प्रीफ़िक्स एडिट करें",
@@ -1461,16 +1458,12 @@
"description": "ये विकल्प सिर्फ़ डेस्कटॉप वर्जन के लिए हैं, ब्राउज़र अपना स्पेल चेक इस्तेमाल करेंगे।",
"enable": "स्पेल चेक चालू करें",
"language_code_label": "भाषा कोड (Language code)",
"language_code_placeholder": "जैसे \"en-US\", \"hi-IN\"",
"multiple_languages_info": "कई भाषाओं को कॉमा से अलग किया जा सकता है, जैसे \"en-US, hi-IN\"। ",
"available_language_codes_label": "उपलब्ध भाषा कोड:",
"restart-required": "स्पेल चेक में बदलाव ऐप रीस्टार्ट करने के बाद ही दिखेंगे।"
},
"sync_2": {
"config_title": "सिंक कॉन्फ़िगरेशन",
"server_address": "सर्वर एड्रेस (Address)",
"timeout": "सिंक समय-सीमा (Timeout)",
"timeout_unit": "मिलीसेकंड (milliseconds)",
"proxy_label": "सिंक प्रॉक्सी सर्वर (वैकल्पिक)",
"note": "नोट",
"note_description": "अगर आप प्रॉक्सी खाली छोड़ते हैं, तो सिस्टम प्रॉक्सी का इस्तेमाल होगा।",

View File

@@ -76,7 +76,6 @@
"confirmation": "Konfirmasi"
},
"delete_notes": {
"delete_notes_preview": "Hapus pratinjau catatan",
"close": "Tutup",
"delete_all_clones_description": "Hapus seluruh duplikat (bisa dikembalikan di menu revisi)",
"erase_notes_description": "Penghapusan normal hanya menandai catatan sebagai dihapus dan dapat dipulihkan (melalui dialog versi revisi) dalam jangka waktu tertentu. Mencentang opsi ini akan menghapus catatan secara permanen seketika dan catatan tidak akan bisa dipulihkan kembali.",
@@ -84,9 +83,7 @@
"notes_to_be_deleted": "Catatan-catatan berikut akan dihapuskan ({{notesCount}})",
"no_note_to_delete": "Tidak ada Catatan yang akan dihapus (hanya duplikat).",
"broken_relations_to_be_deleted": "Hubungan berikut akan diputus dan dihapus ({{ relationCount}})",
"cancel": "Batalkan",
"ok": "Setuju",
"deleted_relation_text": "Catatan {{- note}} (yang akan dihapus) dirujuk oleh relasi {{- relation}} yang berasal dari {{- source}}."
"cancel": "Batalkan"
},
"clone_to": {
"clone_notes_to": "Duplikat catatan ke…",

View File

@@ -88,17 +88,14 @@
"also_delete_note": "Rimuove anche la nota"
},
"delete_notes": {
"ok": "OK",
"close": "Chiudi",
"delete_notes_preview": "Anteprima di eliminazione delle note",
"delete_all_clones_description": "Elimina anche tutti i cloni (può essere ripristinato nella sezione cambiamenti recenti)",
"erase_notes_description": "L'eliminazione normale (soft) marca le note come eliminate e potranno essere recuperate entro un certo lasso di tempo (dalla finestra dei cambiamenti recenti). Selezionando questa opzione le note si elimineranno immediatamente e non sarà possibile recuperarle.",
"erase_notes_warning": "Elimina le note in modo permanente (non potrà essere disfatto), compresi tutti i cloni. Ciò forzerà un nuovo caricamento dell'applicazione.",
"cancel": "Annulla",
"notes_to_be_deleted": "Le seguenti note saranno eliminate ({{notesCount}})",
"no_note_to_delete": "Nessuna nota sarà eliminata (solo i cloni).",
"broken_relations_to_be_deleted": "Le seguenti relazioni saranno interrotte ed eliminate ({{relationCount}})",
"deleted_relation_text": "La nota {{- note}} (da eliminare) è referenziata dalla relazione {{- relation}} originata da {{- source}}."
"broken_relations_to_be_deleted": "Le seguenti relazioni saranno interrotte ed eliminate ({{relationCount}})"
},
"info": {
"okButton": "OK",
@@ -497,7 +494,6 @@
"proxy_label": "Server Proxy per la sincronizzazione (opzionale)",
"test_title": "Test di sincronizzazione",
"timeout": "Timeout per la sincronizzazione",
"timeout_unit": "millisecondi",
"save": "Salva",
"help": "Aiuto",
"server_address": "Indirizzo dell'istanza del server",
@@ -538,12 +534,12 @@
"new_tab": "Nuova scheda"
},
"toc": {
"table_of_contents": "Sommario",
"table_of_contents": "Tabella dei Contenuti",
"options": "Opzioni",
"no_headings": "Nessun titolo."
},
"table_of_contents": {
"title": "Sommario",
"title": "Tabella dei Contenuti",
"description": "L'indice apparirà nelle note di testo quando la nota contiene più di un numero definito di titoli. È possibile personalizzare questo numero:",
"unit": "titoli",
"disable_info": "È anche possibile utilizzare questa opzione per disattivare efficacemente l'indice impostando un numero molto alto.",
@@ -593,7 +589,7 @@
"collapseExpand": "collassa/espande il nodo",
"notSet": "non impostato",
"goBackForwards": "indietro/avanti nella cronologia",
"showJumpToNoteDialog": "mostra <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">finestra \"Vai a\"</a>",
"showJumpToNoteDialog": "mostra <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Vai a\"</a>",
"title": "Scheda riassuntiva",
"noteNavigation": "Nota navigazione",
"scrollToActiveNote": "scorri fino alla nota attiva",
@@ -853,7 +849,7 @@
"archived": "Le note con questa etichetta non saranno visibili per impostazione predefinita nei risultati di ricerca (anche nelle finestre di dialogo Vai a, Aggiungi collegamento ecc.).",
"run_on_instance": "Definire su quale istanza di Trilium eseguire questa operazione. L'impostazione predefinita è tutte le istanze.",
"exclude_from_export": "le note (con la loro sottostruttura) non saranno incluse in nessuna esportazione di note",
"run": "definisce su quali eventi deve essere eseguito lo script. I valori possibili sono:\n<ul>\n<li>frontendStartup - quando il frontend Trilium viene avviato (o aggiornato), ma non su dispositivi mobili.</li>\n<li>mobileStartup - quando il frontend Trilium viene avviato (o aggiornato) su dispositivi mobili.</li>\n<li>backendStartup - quando viene avviato il backend Trilium</li>\n<li>hourly - eseguire una volta all'ora. È possibile utilizzare l'etichetta aggiuntiva <code>runAtHour</code> per specificare a che ora.</li>\n<li>daily - eseguire una volta al giorno</li>\n</ul>",
"run": "definisce su quali eventi deve essere eseguito lo script. I valori possibili sono:\n<ul>\n<li>frontendStartup - quando il frontend Trilium viene avviato (o aggiornato), ma non su dispositivi mobili.</li>\n<li>mobileStartup - quando il frontend Trilium viene avviato (o aggiornato) su dispositivi mobili.</li>\n<li>backendStartup - quando viene avviato il backend Trilium.</li>\n<li>hourly - eseguire una volta all'ora. È possibile utilizzare l'etichetta aggiuntiva <code>runAtHour</code> per specificare a che ora.</li>\n<li>daily - eseguire una volta al giorno.</li>\n</ul>",
"run_at_hour": "A che ora deve essere eseguito. Deve essere utilizzato insieme a <code>#run=hourly</code>. Può essere definito più volte per più esecuzioni durante il giorno.",
"disable_inclusion": "gli script con questa etichetta non saranno inclusi nell'esecuzione dello script principale.",
"sorted": "mantiene le note figlie ordinate alfabeticamente per titolo",
@@ -1100,7 +1096,7 @@
"show_help": "Mostra aiuto",
"about": "Informazioni su Trilium Notes",
"logout": "Esci",
"show-cheatsheet": "Mostra il foglietto illustrativo",
"show-cheatsheet": "Mostra la scheda riassuntiva",
"toggle-zen-mode": "Modalità Zen",
"new-version-available": "Nuovo aggiornamento disponibile",
"download-update": "Ottieni la versione {{latestVersion}}",
@@ -1149,7 +1145,8 @@
"export_as_image": "Esporta come immagine",
"export_as_image_png": "PNG (raster)",
"export_as_image_svg": "SVG (vector)",
"note_map": "Mappa"
"note_map": "Mappa",
"view_ocr_text": "Visualizza il testo OCR"
},
"onclick_button": {
"no_click_handler": "Il widget pulsante '{{componentId}}' non ha un gestore di clic definito"
@@ -1439,7 +1436,8 @@
"note_already_in_diagram": "Nota che \"{{title}}\" è già presente nel diagramma.",
"enter_title_of_new_note": "Inserisci il titolo della nuova nota",
"default_new_note_title": "nuova nota",
"click_on_canvas_to_place_new_note": "Clicca sulla tela per inserire una nuova nota"
"click_on_canvas_to_place_new_note": "Clicca sulla tela per inserire una nuova nota",
"rename_relation": "Rinomina relazione"
},
"vacuum_database": {
"title": "Pulizia del database",
@@ -1541,12 +1539,28 @@
},
"images": {
"images_section_title": "Immagini",
"download_images_automatically": "Scarica automaticamente le immagini per l'utilizzo offline.",
"download_images_description": "L'HTML incollato può contenere riferimenti a immagini online; Trilium troverà tali riferimenti e scaricherà le immagini in modo che siano disponibili offline.",
"enable_image_compression": "Abilita la compressione delle immagini",
"max_image_dimensions": "Larghezza/altezza massima di un'immagine (l'immagine verrà ridimensionata se supera questa impostazione).",
"download_images_automatically": "Scarica automaticamente le immagini",
"download_images_description": "Scarica le immagini online a cui si fa riferimento nel codice HTML incollato, in modo che siano disponibili offline.",
"enable_image_compression": "Compressione delle immagini",
"max_image_dimensions": "Dimensioni massime dell'immagine",
"max_image_dimensions_unit": "pixel",
"jpeg_quality_description": "Qualità JPEG (10 - qualità peggiore, 100 - qualità migliore, 50 - 85 è consigliato)"
"jpeg_quality_description": "Il range consigliato è compreso tra 50 e 85. Valori più bassi riducono le dimensioni del file, mentre valori più alti preservano i dettagli.",
"enable_image_compression_description": "Comprimi e ridimensiona le immagini al momento del caricamento o dell'inserimento.",
"max_image_dimensions_description": "Le immagini che superano queste dimensioni verranno ridimensionate automaticamente.",
"jpeg_quality": "Qualità JPEG",
"ocr_section_title": "Estrazione di testo (OCR)",
"ocr_related_content_languages": "Lingue dei contenuti (utilizzate per l'estrazione del testo)",
"ocr_auto_process": "Elaborazione automatica dei nuovi file",
"ocr_auto_process_description": "Estrai automaticamente il testo dai file appena caricati o incollati.",
"ocr_min_confidence": "Livello minimo di confidenza",
"ocr_confidence_description": "Estrai solo il testo che supera questa soglia di affidabilità. Valori inferiori includono più testo, ma potrebbero risultare meno accurati.",
"batch_ocr_title": "Elabora i file esistenti",
"batch_ocr_description": "Estrai il testo da tutte le immagini, i PDF e i documenti Office presenti nei tuoi appunti. L'operazione potrebbe richiedere un po' di tempo a seconda del numero di file.",
"batch_ocr_start": "Avvia l'elaborazione in batch",
"batch_ocr_starting": "Avvio dell'elaborazione in batch...",
"batch_ocr_progress": "Elaborazione di {{processed}} su {{total}} file...",
"batch_ocr_completed": "Elaborazione in batch completata! Sono stati elaborati {{processed}} file.",
"batch_ocr_error": "Errore durante l'elaborazione in batch: {{error}}"
},
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "Timeout cancellazione allegato",
@@ -1654,12 +1668,15 @@
"spellcheck": {
"title": "Controllo ortografico",
"description": "Queste opzioni sono valide solo per le versioni desktop; i browser utilizzeranno il proprio controllo ortografico nativo.",
"enable": "Abilita il controllo ortografico",
"language_code_label": "Codice/i della lingua",
"language_code_placeholder": "ad esempio \"en-US\", \"de-AT\"",
"multiple_languages_info": "È possibile separare più lingue con una virgola, ad esempio \"en-US, de-DE, cs\". ",
"available_language_codes_label": "Codici lingua disponibili:",
"restart-required": "Le modifiche alle opzioni di controllo ortografico avranno effetto dopo il riavvio dell'applicazione."
"enable": "Controlla l'ortografia",
"language_code_label": "Lingue del controllo ortografico",
"restart-required": "Le modifiche alle opzioni di controllo ortografico avranno effetto dopo il riavvio dell'applicazione.",
"custom_dictionary_title": "Dizionario personalizzato",
"custom_dictionary_description": "Le parole aggiunte al dizionario vengono sincronizzate su tutti i tuoi dispositivi.",
"custom_dictionary_edit": "Parole personalizzate",
"custom_dictionary_edit_description": "Modifica l'elenco delle parole che non devono essere segnalate dal correttore ortografico. Le modifiche saranno visibili dopo il riavvio.",
"custom_dictionary_open": "Modifica il dizionario",
"related_description": "Configura le lingue del controllo ortografico e il dizionario personalizzato."
},
"api_log": {
"close": "Vicino"
@@ -1940,7 +1957,7 @@
},
"content_language": {
"title": "Lingue dei contenuti",
"description": "Seleziona una o più lingue che desideri visualizzare nella sezione \"Proprietà di base\" di una nota di testo di sola lettura o modificabile. Ciò consentirà funzionalità come il controllo ortografico o il supporto per la scrittura da destra a sinistra."
"description": "Seleziona una o più lingue che devono comparire nell'elenco di selezione delle lingue nella sezione \"Proprietà di base\" di una nota di testo in sola lettura o modificabile. Ciò consentirà di utilizzare funzioni quali il controllo ortografico, il supporto per la scrittura da destra a sinistra e l'estrazione del testo (OCR)."
},
"switch_layout_button": {
"title_vertical": "Sposta il riquadro di modifica in basso",
@@ -2247,7 +2264,9 @@
"sample_user_journey": "Percorso dell'utente",
"sample_xy": "XY",
"sample_venn": "Venn",
"sample_ishikawa": "Ishikawa"
"sample_ishikawa": "Ishikawa",
"sample_treeview": "TreeView",
"sample_wardley": "Mappa di Wardley"
},
"llm_chat": {
"placeholder": "Scrivi un messaggio...",
@@ -2278,7 +2297,8 @@
"note_context_enabled": "Clicca qui per disattivare il contesto della nota: {{title}}",
"note_context_disabled": "Clicca per includere la nota corrente nel contesto",
"no_provider_message": "Non è stato configurato alcun fornitore di IA. Aggiungine uno per iniziare a chattare.",
"add_provider": "Aggiungi un fornitore di IA"
"add_provider": "Aggiungi un fornitore di IA",
"sources_summary": "{{count}} fonti provenienti da {{sites}} siti"
},
"sidebar_chat": {
"title": "Chat AI",
@@ -2304,6 +2324,61 @@
"delete_provider_confirmation": "Sei sicuro di voler eliminare il provider \"{{name}}\"?",
"api_key": "Chiave API",
"api_key_placeholder": "Inserisci la tua chiave API",
"cancel": "Annulla"
"cancel": "Annulla",
"feature_not_enabled": "Abilita la funzione sperimentale LLM in Impostazioni → Avanzate → Funzioni sperimentali per utilizzare le integrazioni basate sull'intelligenza artificiale.",
"mcp_title": "MCP (Model Context Protocol)",
"mcp_enabled": "Server MCP",
"mcp_enabled_description": "Rendi pubblico un endpoint MCP (Model Context Protocol) in modo che gli assistenti di programmazione basati sull'intelligenza artificiale (ad esempio Claude Code, GitHub Copilot) possano leggere e modificare le tue note. L'endpoint è accessibile solo da localhost.",
"mcp_endpoint_title": "URL dell'endpoint",
"mcp_endpoint_description": "Aggiungi questo URL alla configurazione MCP del tuo assistente AI",
"tools": {
"search_notes": "Cerca nelle note",
"get_note": "Prendi nota",
"get_note_content": "Visualizza il contenuto della nota",
"update_note_content": "Aggiorna il contenuto della nota",
"append_to_note": "Aggiungi alla nota",
"create_note": "Crea nota",
"get_attributes": "Recupera gli attributi",
"get_attribute": "Ottieni attributo",
"set_attribute": "Imposta attributo",
"delete_attribute": "Elimina attributo",
"get_child_notes": "Recupera le note relative ai figli",
"get_subtree": "Ottieni sottostruttura",
"load_skill": "Carica skill",
"web_search": "Ricerca sul web",
"note_in_parent": "<Note/> in <Parent/>",
"get_attachment": "Scarica l'allegato",
"get_attachment_content": "Leggi il contenuto dell'allegato"
}
},
"ocr": {
"extracted_text": "Testo estratto (OCR)",
"extracted_text_title": "Testo estratto (OCR)",
"loading_text": "Caricamento del testo OCR in corso...",
"no_text_available": "Non è disponibile alcun testo OCR",
"no_text_explanation": "Questo documento non è stato sottoposto a elaborazione OCR per l'estrazione del testo oppure non è stato trovato alcun testo.",
"failed_to_load": "Impossibile caricare il testo OCR",
"process_now": "Elaborazione OCR",
"processing": "Elaborazione in corso...",
"processing_started": "L'elaborazione OCR è stata avviata. Attendere qualche istante e aggiorna.",
"processing_complete": "Elaborazione OCR completata.",
"processing_failed": "Impossibile avviare l'elaborazione OCR",
"text_filtered_low_confidence": "L'OCR ha rilevato il testo con un livello di affidabilità del {{confidence}}%, ma è stato scartato perché la soglia minima impostata è del {{threshold}}%.",
"open_media_settings": "Apri Impostazioni",
"view_extracted_text": "Visualizza il testo estratto (OCR)"
},
"mind-map": {
"addChild": "Aggiungi figlio",
"addParent": "Aggiungi genitore",
"addSibling": "Aggiungi un fratello o una sorella",
"removeNode": "Rimuovi nodo",
"focus": "Modalità Focus",
"cancelFocus": "Annulla modalità Focus",
"moveUp": "Sposta su",
"moveDown": "Sposta giù",
"link": "Collegamento",
"linkBidirectional": "Collegamento bidirezionale",
"clickTips": "Clicca sul nodo di destinazione",
"summary": "Sommario"
}
}

View File

@@ -111,11 +111,8 @@
"notes_to_be_deleted": "以下のノートが削除されます ({{notesCount}})",
"no_note_to_delete": "ノートは削除されません(クローンのみ)。",
"cancel": "キャンセル",
"ok": "OK",
"close": "閉じる",
"delete_notes_preview": "ノートのプレビューを削除",
"broken_relations_to_be_deleted": "次のリレーション ({{relationCount}})は壊れているので消去されます",
"deleted_relation_text": "削除予定のノート{{- note}}は{{- source}}からリレーション{{- relation}}によって参照されています."
"broken_relations_to_be_deleted": "次のリレーション ({{relationCount}})は壊れているので消去されます"
},
"calendar": {
"mon": "月",
@@ -576,7 +573,10 @@
"expand_first_level": "直下の子を展開",
"expand_nth_level": "{{depth}} 階層下まで展開",
"expand_all_levels": "すべての階層を展開",
"hide_child_notes": "ツリー内の子ノートを非表示"
"hide_child_notes": "ツリー内の子ノートを非表示",
"open_all_in_tabs": "すべて開く",
"open_all_in_tabs_tooltip": "すべての結果を新しいタブで開く",
"open_all_confirm": "{{count}} 件のノートが新しいタブで開かれます。続行しますか?"
},
"note_types": {
"geo-map": "ジオマップ",
@@ -1001,7 +1001,8 @@
"date-and-time": "日時",
"path": "パス",
"database_backed_up_to": "データベースは{{backupFilePath}}にバックアップされました",
"no_backup_yet": "バックアップがありません"
"no_backup_yet": "バックアップがありません",
"download": "ダウンロード"
},
"password": {
"wiki": "wiki",
@@ -1027,18 +1028,20 @@
"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": "スペルチェックオプションの変更は、アプリケーションの再起動後に有効になります。"
"enable": "スペルチェック",
"language_code_label": "スペルチェック対応言語",
"restart-required": "スペルチェックオプションの変更は、アプリケーションの再起動後に有効になります。",
"custom_dictionary_title": "カスタム辞書",
"custom_dictionary_description": "辞書に追加した単語は、すべてのデバイス間で同期されます。",
"custom_dictionary_edit": "カスタム単語",
"custom_dictionary_edit_description": "スペルチェッカーでエラーとして検出されないようにする単語リストを編集します。変更は再起動後に反映されます。",
"custom_dictionary_open": "辞書の編集",
"related_description": "スペルチェック対応言語とカスタム辞書を設定します。"
},
"sync_2": {
"config_title": "同期設定",
"server_address": "サーバーインスタンスのアドレス",
"timeout": "同期タイムアウト",
"timeout_unit": "ミリ秒",
"proxy_label": "同期プロキシサーバー(任意)",
"note": "注",
"note_description": "プロキシ設定を空白のままにすると、システムプロキシが使用されます(デスクトップ/electronビルドにのみ適用されます。",
@@ -1048,7 +1051,8 @@
"test_title": "同期のテスト",
"test_description": "これは同期サーバとの接続とハンドシェイクをテストします。同期サーバーが初期化されていない場合、ローカルドキュメントと同期するように設定します。",
"test_button": "同期試行",
"handshake_failed": "同期サーバーのハンドシェイクに失敗しました。エラー: {{message}}"
"handshake_failed": "同期サーバーのハンドシェイクに失敗しました。エラー: {{message}}",
"timeout_description": "同期接続が遅い場合に、接続を諦めるまでの待機時間。ネットワークが不安定な場合は、この時間を長く設定してください。"
},
"api_log": {
"close": "閉じる"
@@ -1539,7 +1543,8 @@
"collapse": "通常サイズに折りたたむ",
"title": "ノートマップ",
"link-distance": "リンク距離",
"fix-nodes": "ノードを修正"
"fix-nodes": "ノードを修正",
"too-many-notes": "このサブツリーには {{count}} 件のノートが含まれており、ノートマップに表示できる {{max}} の上限を超えています。"
},
"owned_attribute_list": {
"owned_attributes": "所有属性"
@@ -1570,7 +1575,8 @@
"click_on_canvas_to_place_new_note": "キャンバスをクリックして新しいノートを配置",
"connection_exists": "これらのノート間の接続 '{{name}}' は既に存在します。",
"start_dragging_relations": "ここからリレーションをドラッグして、別のノートにドロップします。",
"note_already_in_diagram": "ノート「{{title}}」はすでに図に含まれています。"
"note_already_in_diagram": "ノート「{{title}}」はすでに図に含まれています。",
"rename_relation": "リレーション名の変更"
},
"database_anonymization": {
"title": "データベースの匿名化",
@@ -2234,7 +2240,9 @@
"sample_user_journey": "ユーザージャーニー図",
"sample_xy": "XY チャート",
"sample_venn": "ベン図",
"sample_ishikawa": "石川図"
"sample_ishikawa": "石川図",
"sample_treeview": "ツリービュー",
"sample_wardley": "ウォードリーマップ"
},
"llm_chat": {
"placeholder": "メッセージを入力してください…",
@@ -2344,6 +2352,9 @@
"processing": "処理中…",
"processing_started": "OCR 処理が開始されました。しばらくお待ちいただき、ページを更新してください。",
"processing_failed": "OCR 処理の開始に失敗しました",
"view_extracted_text": "抽出されたテキストOCRを表示"
"view_extracted_text": "抽出されたテキストOCRを表示",
"processing_complete": "OCR 処理が完了しました。",
"text_filtered_low_confidence": "OCR は {{confidence}}% の信頼度でテキストを検出しましたが、最小しきい値が {{threshold}}% であるため、破棄されました。",
"open_media_settings": "設定を開く"
}
}

View File

@@ -100,9 +100,6 @@
"no_note_to_delete": "삭제되는 노트가 없습니다 (클론만 삭제됩니다).",
"broken_relations_to_be_deleted": "다음 관계가 끊어지고 삭제됩니다({{ relationCount}})",
"cancel": "취소",
"ok": "OK",
"deleted_relation_text": "삭제 예정인 노트 {{- note}} (은)는 {{- source}}에서 시작된 관계 {{- relation}}에 의해 참조되고 있습니다.",
"delete_notes_preview": "노트 미리보기 삭제",
"close": "닫기",
"delete_all_clones_description": "모든 복제본 삭제(최근 변경 사항에서 되돌릴 수 있습니다)"
},

View File

@@ -39,8 +39,7 @@
},
"delete_notes": {
"close": "Lukk",
"cancel": "Avbryt",
"ok": "OK"
"cancel": "Avbryt"
},
"export": {
"close": "Lukk",

View File

@@ -21,7 +21,7 @@
},
"bundle-error": {
"title": "Custom script laden mislukt",
"message": "Script van notitie met ID \"{{id}}\", getiteld \"{{title}}\" kon niet worden uitgevoerd vanwege:\n\n{{message}}"
"message": "Script voor de notitie met ID \"{{id}}\", getiteld \"{{title}}\" kon niet worden uitgevoerd vanwege:\n\n{{message}}"
},
"scripting-error": "Error met script: {{title}}",
"widget-list-error": {

View File

@@ -78,15 +78,12 @@
"delete_notes": {
"cancel": "Anuluj",
"close": "Zamknij",
"delete_notes_preview": "Podgląd usuwania notatek",
"delete_all_clones_description": "Usuń również wszystkie klony (można cofnąć w oknie Ostatnie zmiany)",
"erase_notes_description": "Normalne (miękkie) usuwanie jedynie oznacza notatki jako usunięte i można je przywrócić (w oknie Ostatnie zmiany) przez pewien czas. Zaznaczenie tej opcji spowoduje natychmiastowe wymazanie notatek i nie będzie możliwe ich przywrócenie.",
"erase_notes_warning": "Wymaż notatki trwale (nie można cofnąć), w tym wszystkie klony. Wymusi to przeładowanie aplikacji.",
"notes_to_be_deleted": "Następujące notatki zostaną usunięte ({{notesCount}})",
"no_note_to_delete": "Żadna notatka nie zostanie usunięta (tylko klony).",
"broken_relations_to_be_deleted": "Następujące relacje zostaną zerwane i usunięte ({{ relationCount}})",
"ok": "OK",
"deleted_relation_text": "Notatka {{- note}} (do usunięcia) jest powiązana relacją {{- relation}} pochodzącą z {{- source}}."
"broken_relations_to_be_deleted": "Następujące relacje zostaną zerwane i usunięte ({{ relationCount}})"
},
"export": {
"close": "Zamknij",
@@ -1665,16 +1662,12 @@
"description": "Te opcje dotyczą tylko wersji desktopowych, przeglądarki będą używać własnego natywnego sprawdzania pisowni.",
"enable": "Włącz sprawdzanie pisowni",
"language_code_label": "Kod(y) języka",
"language_code_placeholder": "na przykład \"pl-PL\", \"en-US\"",
"multiple_languages_info": "Wiele języków można oddzielić przecinkiem, np. \"en-US, de-DE, pl\". ",
"available_language_codes_label": "Dostępne kody języków:",
"restart-required": "Zmiany w opcjach sprawdzania pisowni wejdą w życie po ponownym uruchomieniu aplikacji."
},
"sync_2": {
"config_title": "Konfiguracja synchronizacji",
"server_address": "Adres instancji serwera",
"timeout": "Limit czasu synchronizacji",
"timeout_unit": "milisekund",
"proxy_label": "Serwer proxy synchronizacji (opcjonalnie)",
"note": "Uwaga",
"note_description": "Jeśli pozostawisz ustawienie proxy puste, zostanie użyte proxy systemowe (dotyczy tylko wersji desktop/electron).",

View File

@@ -88,7 +88,6 @@
"also_delete_note": "Também apagar a nota"
},
"delete_notes": {
"delete_notes_preview": "Apagar pré-visualização de notas",
"close": "Fechar",
"delete_all_clones_description": "Apagar também todos os clones (pode ser desfeito em alterações recentes)",
"erase_notes_description": "Apagar normal (suave) apenas marca as notas como apagadas, permitindo que sejam recuperadas (no diálogo de alterações recentes) num período. Se esta opção for marcada, as notas serão apagadas imediatamente e não será possível restaurá-las.",
@@ -96,9 +95,7 @@
"notes_to_be_deleted": "As seguintes notas serão apagadas ({{notesCount}})",
"no_note_to_delete": "Nenhuma nota será apagada (apenas os clones).",
"broken_relations_to_be_deleted": "As seguintes relações serão quebradas e apagadas ({{ relationCount}})",
"cancel": "Cancelar",
"ok": "OK",
"deleted_relation_text": "A nota {{- note}} (a ser apagada) está referenciada pela relação {{- relation}} originada de {{- source}}."
"cancel": "Cancelar"
},
"export": {
"export_note_title": "Exportar nota",
@@ -1435,16 +1432,12 @@
"description": "Estas opções aplicam-se apenas às versões desktop; os navegadores usarão a sua própria verificação ortográfica nativa.",
"enable": "Ativar verificação ortográfica",
"language_code_label": "Código(s) de idioma",
"language_code_placeholder": "por exemplo \"en-US\", \"de-AT\", \"pt-BR\"",
"multiple_languages_info": "Múltiplos idiomas podem ser separados por vírgula, por exemplo: \"en-US, de-DE, pt-BR, cs\". ",
"available_language_codes_label": "Códigos de idioma disponíveis:",
"restart-required": "As alterações nas opções de verificação ortográfica terão efeito após reiniciar a aplicação."
},
"sync_2": {
"config_title": "Configuração da Sincronização",
"server_address": "Endereço da instância do Servidor",
"timeout": "Tempo limite da sincronização",
"timeout_unit": "milisegundos",
"proxy_label": "Servidor proxy para sincronização (opcional)",
"note": "Nota",
"note_description": "Se deixar a configuração de proxy em branco, o proxy do sistema será usado (aplica-se apenas à versão desktop/Electron).",

View File

@@ -94,7 +94,6 @@
"also_delete_note": "Também excluir a nota"
},
"delete_notes": {
"delete_notes_preview": "Excluir pré-visualização de notas",
"close": "Fechar",
"delete_all_clones_description": "Excluir também todos os clones (pode ser desfeito em alterações recentes)",
"erase_notes_description": "A exclusão normal (suave) apenas marca as notas como excluídas, permitindo que sejam recuperadas (no diálogo de alterações recentes) dentro de um período de tempo. Se esta opção for marcada, as notas serão apagadas imediatamente e não será possível restaurá-las.",
@@ -102,9 +101,7 @@
"notes_to_be_deleted": "As seguintes notas serão excluídas ({{notesCount}})",
"no_note_to_delete": "Nenhuma nota será excluída (apenas os clones).",
"broken_relations_to_be_deleted": "As seguintes relações serão quebradas e excluídas ({{ relationCount}})",
"cancel": "Cancelar",
"ok": "OK",
"deleted_relation_text": "A nota {{- note}} (a ser excluída) está referenciada pela relação {{- relation}} originada de {{- source}}."
"cancel": "Cancelar"
},
"export": {
"export_note_title": "Exportar nota",
@@ -1944,16 +1941,12 @@
"description": "Estas opções se aplicam apenas às versões desktop; os navegadores usarão sua própria verificação ortográfica nativa.",
"enable": "Habilitar verificação ortográfica",
"language_code_label": "Código(s) de idioma",
"language_code_placeholder": "por exemplo \"en-US\", \"de-AT\", \"pt-BR\"",
"multiple_languages_info": "Múltiplos idiomas podem ser separados por vírgula, por exemplo: \"en-US, de-DE, pt-BR, cs\". ",
"available_language_codes_label": "Códigos de idioma disponíveis:",
"restart-required": "As alterações nas opções de verificação ortográfica terão efeito após reiniciar o aplicativo."
},
"sync_2": {
"config_title": "Configuração da Sincronização",
"server_address": "Endereço da instância do Servidor",
"timeout": "Tempo limite da sincronização",
"timeout_unit": "milisegundos",
"proxy_label": "Servidor proxy para sincronização (opcional)",
"note": "Nota",
"note_description": "Se você deixar a configuração de proxy em branco, o proxy do sistema será usado (aplica-se apenas à versão desktop/Electron).",

View File

@@ -459,13 +459,10 @@
"broken_relations_to_be_deleted": "Următoarele relații vor fi întrerupte și șterse ({{ relationCount}})",
"cancel": "Anulează",
"delete_all_clones_description": "Șterge și toate clonele (se pot recupera în ecranul Schimbări recente)",
"delete_notes_preview": "Previzualizare ștergerea notițelor",
"erase_notes_description": "Ștergerea obișnuită doar marchează notițele ca fiind șterse și pot fi recuperate (în ecranul Schimbări recente) pentru o perioadă de timp. Dacă se bifează această opțiune, notițele vor fi șterse imediat fără posibilitatea de a le recupera.",
"erase_notes_warning": "Șterge notițele permanent (nu se mai pot recupera), incluzând toate clonele. Va forța reîncărcarea aplicației.",
"no_note_to_delete": "Nicio notiță nu va fi ștearsă (doar clonele).",
"notes_to_be_deleted": "Următoarele notițe vor fi șterse ({{notesCount}})",
"ok": "OK",
"deleted_relation_text": "Notița {{- note}} ce va fi ștearsă este referențiată de relația {{- relation}}, originând din {{- source}}.",
"close": "Închide"
},
"delete_relation": {
@@ -1237,12 +1234,9 @@
"title": "titlu"
},
"spellcheck": {
"available_language_codes_label": "Coduri de limbă disponibile:",
"description": "Aceste opțiuni se aplică doar pentru aplicația de desktop, navigatoarele web folosesc propriile corectoare ortografice.",
"enable": "Activează corectorul ortografic",
"language_code_label": "Codurile de limbă",
"language_code_placeholder": "de exemplu „en-US”, „de-AT”",
"multiple_languages_info": "Mai multe limbi pot fi separate prin virgulă, e.g. \"en-US, de-DE, cs\". ",
"title": "Corector ortografic",
"restart-required": "Schimbările asupra setărilor corectorului ortografic vor fi aplicate după restartarea aplicației."
},
@@ -1269,8 +1263,7 @@
"test_button": "Probează sincronizarea",
"test_description": "Această opțiune va testa conexiunea și comunicarea cu serverul de sincronizare. Dacă serverul de sincronizare nu este inițializat, acest lucru va rula și o sincronizare cu documentul local.",
"test_title": "Probează sincronizarea",
"timeout": "Timp limită de sincronizare",
"timeout_unit": "milisecunde"
"timeout": "Timp limită de sincronizare"
},
"table_of_contents": {
"description": "Cuprinsul va apărea în notițele de tip text atunci când notița are un număr de titluri mai mare decât cel definit. Acest număr se poate personaliza:",

View File

@@ -83,10 +83,7 @@
"notes_to_be_deleted": "Следующие заметки будут удалены ({{notesCount}})",
"no_note_to_delete": "Заметка не будет удалена (только клоны).",
"broken_relations_to_be_deleted": "Следующие отношения будут разорваны и удалены ({{relationCount}})",
"cancel": "Отмена",
"ok": "ОК",
"deleted_relation_text": "Примечание {{- note}} (подлежит удалению) ссылается на отношение {{- relation}}, происходящее из {{- source}}.",
"delete_notes_preview": "Предпросмотр удаляемых заметок"
"cancel": "Отмена"
},
"database_anonymization": {
"light_anonymization_description": "Это действие создаст новую копию базы данных и выполнит её лёгкую анонимизацию — в частности, будет удалён только контент всех заметок, но заголовки и атрибуты останутся. Кроме того, будут сохранены пользовательские заметки, содержащие JavaScript-скрипты frontend/backend и пользовательские виджеты. Это даёт больше контекста для отладки проблем.",
@@ -1419,7 +1416,6 @@
"no_results": "Не найдено ярлыков, соответствующих '{{filter}}'"
},
"sync_2": {
"timeout_unit": "миллисекунд",
"note": "Заметка",
"save": "Сохранить",
"help": "Помощь",
@@ -1679,10 +1675,7 @@
"title": "Проверка орфографии",
"enable": "Включить проверку орфографии",
"language_code_label": "Код(ы) языков",
"multiple_languages_info": "Несколько языков можно разделять запятой, например, \"en-US, de-DE, cs\". ",
"available_language_codes_label": "Доступные коды языков:",
"restart-required": "Изменения параметров проверки орфографии вступят в силу после перезапуска приложения.",
"language_code_placeholder": "например \"en-US\", \"de-AT\"",
"description": "Эти параметры применимы только для десктопных сборок, браузеры будут использовать собственную встроенную проверку орфографии."
},
"attribute_editor": {

View File

@@ -76,7 +76,6 @@
"also_delete_note": "Takođe obriši belešku"
},
"delete_notes": {
"delete_notes_preview": "Obriši pregled beleške",
"close": "Zatvori",
"delete_all_clones_description": "Obriši i sve klonove (može biti poništeno u skorašnjim izmenama)",
"erase_notes_description": "Normalno (blago) brisanje samo označava beleške kao obrisane i one mogu biti vraćene (u dijalogu skorašnjih izmena) u određenom vremenskom periodu. Biranje ove opcije će momentalno obrisati beleške i ove beleške neće biti moguće vratiti.",
@@ -84,9 +83,7 @@
"notes_to_be_deleted": "Sledeće beleške će biti obrisane ({{- noteCount}})",
"no_note_to_delete": "Nijedna beleška neće biti obrisana (samo klonovi).",
"broken_relations_to_be_deleted": "Sledeći odnosi će biti prekinuti i obrisani ({{- relationCount}})",
"cancel": "Otkaži",
"ok": "U redu",
"deleted_relation_text": "Beleška {{- note}} (za brisanje) je referencirana sa odnosom {{- relation}} koji potiče iz {{- source}}."
"cancel": "Otkaži"
},
"export": {
"export_note_title": "Izvezi belešku",

View File

@@ -21,12 +21,32 @@
},
"delete_notes": {
"close": "Kapat",
"delete_notes_preview": "Not önizlemesini sil",
"delete_all_clones_description": "Tüm klonları da sil (son değişikliklerden geri alınabilir)",
"erase_notes_description": "Normal (yazılımsal) silme işlemi, notları yalnızca silinmiş olarak işaretler ve belirli bir süre içinde (son değişiklikler iletişim kutusunda) geri alınabilir. Bu seçeneği işaretlemek, notları hemen siler ve notların geri alınması mümkün olmaz."
"erase_notes_description": "Normal (yazılımsal) silme işlemi, notları yalnızca silinmiş olarak işaretler ve belirli bir süre içinde (son değişiklikler iletişim kutusunda) geri alınabilir. Bu seçeneği işaretlemek, notları hemen siler ve notların geri alınması mümkün olmaz.",
"erase_notes_warning": "Notları, tüm kopyaları da dahil olmak üzere kalıcı olarak silin (geri alınamaz). Bu işlem, uygulamanın yeniden yüklenmesine neden olacaktır.",
"notes_to_be_deleted": "Aşağıdaki notlar silinecektir. ({{notesCount}})",
"no_note_to_delete": "Hiçbir not silinmeyecek (sadece kopyaları silinecek).",
"broken_relations_to_be_deleted": "Aşağıdaki ilişkiler koparılacak ve silinecektir ({{ relationCount}})",
"cancel": "İptal"
},
"export": {
"close": "Kapat"
"close": "Kapat",
"export_note_title": "Notu dışa aktar",
"export_type_subtree": "Bu not ve tüm torunları",
"format_html": "HTML - tüm biçimlendirmeyi koruduğu için önerilir",
"format_html_zip": "ZIP arşivindeki HTML dosyaları - tüm biçimlendirmeyi koruduğu için bu yöntem önerilir.",
"format_markdown": "Markdown - bu, biçimlendirmenin büyük kısmını korur.",
"format_opml": "OPML - yalnızca metin için anahat değişim biçimi. Biçimlendirme, resimler ve dosyalar dahil edilmez.",
"opml_version_1": "OPML v1.0 - yalnızca düz metin",
"opml_version_2": "OPML v2.0 - HTML de destekler",
"export_type_single": "Yalnızca bu not, alt öğeleri olmadan",
"export": "Dışa aktar",
"choose_export_type": "Lütfen önce dışa aktarma türünü seçin",
"export_status": "Dışa aktarma durumu",
"export_in_progress": "Dışa aktarma devam ediyor: {{progressCount}}",
"export_finished_successfully": "Dışa aktarma başarıyla tamamlandı.",
"format_pdf": "PDF - yazdırma veya paylaşım amaçları için.",
"share-format": "Web yayını için HTML - paylaşılan notlarda kullanılan temayı kullanır, ancak statik bir web sitesi olarak yayınlanabilir."
},
"import": {
"chooseImportFile": "İçe aktarım dosyası",
@@ -58,7 +78,9 @@
"widget-render-error": {
"title": "Özel React widget'ı çizilirken sorun yaşandı"
},
"scripting-error": "Kullanıcı tanımlı betik hatası: {{title}}"
"scripting-error": "Kullanıcı tanımlı betik hatası: {{title}}",
"widget-missing-parent": "Özel widget'ın zorunlu '{{property}}' özelliği tanımlanmamıştır.\n\nBu komut dosyasının bir kullanıcı arayüzü öğesi olmadan çalıştırılması gerekiyorsa, bunun yerine '#run=frontendStartup' kullanın.",
"open-script-note": "Komut dosyası notunu aç"
},
"add_link": {
"add_link": "Bağlantı ekle",
@@ -103,5 +125,32 @@
"are_you_sure_remove_note": "\"{{title}}\" notunu ilişki haritasından kaldırmak istediğinize emin misiniz?. ",
"also_delete_note": "Notu da sil",
"if_you_dont_check": "Bunu işaretlemezseniz, not yalnızca ilişki haritasından kaldırılacaktır."
},
"help": {
"title": "Özet tablo",
"editShortcuts": "Klavye kısayollarını düzenle",
"noteNavigation": "Not içinde gezinme",
"goUpDown": "Notlar listesinde yukarı/aşağı gitmek",
"collapseExpand": "düğümü daralt/genişlet",
"notSet": "ayarlanmamış",
"goBackForwards": "tarihte geri/ileri git",
"showJumpToNoteDialog": "<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Şuraya Git\" iletişim kutusunu göster</a>",
"scrollToActiveNote": "Aktif nota kaydır",
"jumpToParentNote": "Üst nota git",
"collapseWholeTree": "Tüm not ağacını daralt",
"collapseSubTree": "Alt ağacı daralt",
"tabShortcuts": "Sekme kısayolları",
"newTabNoteLink": "Not bağlantısı notu yeni sekmede açılır",
"newTabWithActivationNoteLink": "Not bağlantısına tıklandığında not yeni bir sekmede açılır ve etkinleştirilir",
"onlyInDesktop": "Yalnızca masaüstünde (Electron derlemesi)",
"openEmptyTab": "boş sekmeyi aç",
"closeActiveTab": "aktif sekmeyi kapat",
"activateNextTab": "sonraki sekmeyi etkinleştir",
"activatePreviousTab": "önceki sekmeyi etkinleştir",
"creatingNotes": "Not oluşturma",
"createNoteAfter": "etkin nottan sonra yeni not oluşturma",
"createNoteInto": "aktif nota yeni bir alt not oluşturun",
"editBranchPrefix": "<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefix</a> değerini aktif not klonunun düzenle",
"movingCloningNotes": "Notları taşıma / klonlama"
}
}

View File

@@ -88,7 +88,6 @@
"also_delete_note": "同時刪除筆記"
},
"delete_notes": {
"delete_notes_preview": "刪除筆記預覽",
"delete_all_clones_description": "同時刪除所有克隆(可以在最近修改中撤消)",
"erase_notes_description": "通常(軟)刪除僅標記筆記為已刪除,可以在一段時間內透過最近修改對話方塊撤消。勾選此選項將立即擦除筆記,無法撤銷。",
"erase_notes_warning": "永久擦除筆記(無法撤銷),包括所有克隆。這將強制應用程式重新載入。",
@@ -96,8 +95,6 @@
"no_note_to_delete": "沒有筆記將被刪除(僅克隆)。",
"broken_relations_to_be_deleted": "將刪除以下關聯並斷開連接 ({{ relationCount}})",
"cancel": "取消",
"ok": "確定",
"deleted_relation_text": "筆記 {{- note}}(將被刪除的筆記)被以下關聯 {{- relation}} 引用,來自 {{- source}}。",
"close": "關閉"
},
"export": {
@@ -803,7 +800,10 @@
"expand_first_level": "展開直接子級",
"expand_nth_level": "展開 {{depth}} 層",
"expand_all_levels": "展開所有層級",
"hide_child_notes": "隱藏樹中的子筆記"
"hide_child_notes": "隱藏樹中的子筆記",
"open_all_in_tabs": "全部打開",
"open_all_in_tabs_tooltip": "在新分頁中開啟所有結果",
"open_all_confirm": "這將在新分頁中開啟 {{count}} 則筆記。要繼續嗎?"
},
"edited_notes": {
"no_edited_notes_found": "今天還沒有編輯過的筆記...",
@@ -857,7 +857,8 @@
"collapse": "收摺到正常大小",
"title": "筆記地圖",
"fix-nodes": "固定節點",
"link-distance": "連結距離"
"link-distance": "連結距離",
"too-many-notes": "此子樹包含 {{count}} 則筆記,已超過筆記地圖中可顯示的 {{max}} 則上限。"
},
"note_paths": {
"title": "筆記路徑",
@@ -1062,7 +1063,8 @@
"note_already_in_diagram": "筆記 \"{{title}}\" 已經在圖中。",
"enter_title_of_new_note": "輸入新筆記的標題",
"default_new_note_title": "新筆記",
"click_on_canvas_to_place_new_note": "點擊畫布以放置新筆記"
"click_on_canvas_to_place_new_note": "點擊畫布以放置新筆記",
"rename_relation": "重新命名關聯"
},
"backend_log": {
"refresh": "重新整理"
@@ -1331,7 +1333,8 @@
"date-and-time": "日期和時間",
"path": "路徑",
"database_backed_up_to": "資料庫已備份至 {{backupFilePath}}",
"no_backup_yet": "尚無備份"
"no_backup_yet": "尚無備份",
"download": "下載"
},
"etapi": {
"title": "ETAPI",
@@ -1396,12 +1399,15 @@
"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": "拼寫檢查選項的更改將在應用重啟後生效。"
"enable": "拼寫檢查",
"language_code_label": "拼寫檢查語言",
"restart-required": "拼寫檢查選項的更改將在應用重啟後生效。",
"custom_dictionary_title": "自訂字典",
"custom_dictionary_description": "新增至字典的詞彙會同步至您所有的裝置。",
"custom_dictionary_edit": "自訂詞彙",
"custom_dictionary_edit_description": "編輯拼寫檢查器不應標記的詞彙清單。變更將於重新啟動後生效。",
"custom_dictionary_open": "編輯字典",
"related_description": "設定拼寫檢查語言及自訂字典。"
},
"sync_2": {
"config_title": "同步設定",
@@ -1417,7 +1423,7 @@
"test_description": "測試和同步伺服器之間的連接。如果同步伺服器沒有初始化,這會將本地文件同步至同步伺服器上。",
"test_button": "測試同步",
"handshake_failed": "同步伺服器握手失敗,錯誤:{{message}}",
"timeout_unit": "毫秒"
"timeout_description": "在放棄慢速同步連線前應等待多久。若網路不穩定,請延長等待時間。"
},
"api_log": {
"close": "關閉"
@@ -2288,7 +2294,7 @@
"ocr": {
"processing_complete": "OCR 處理已完成。",
"processing_failed": "無法啟動 OCR 處理",
"text_filtered_low_confidence": "OCR 偵測到的信賴度為 {{confidence}}%,但因您的最低閾值設定為 {{threshold}}%,故該結果已被捨棄。",
"text_filtered_low_confidence": "OCR 偵測到的文字信賴度為 {{confidence}}%,但因您的最低閾值設定為 {{threshold}}%,故該結果已被捨棄。",
"open_media_settings": "開啟設定",
"view_extracted_text": "檢視擷取的文字 (OCR)",
"extracted_text": "已擷取的文字 (OCR)",

View File

@@ -186,7 +186,6 @@
"also_delete_note": "Також видалити нотатку"
},
"delete_notes": {
"delete_notes_preview": "Видалити попередній перегляд нотаток",
"close": "Закрити",
"delete_all_clones_description": "Видалити також усі клони (можна скасувати в останніх змінах)",
"erase_notes_description": "Звичайне (м’яке) видалення лише позначає нотатки як видалені і їх можна відновити (у діалоговому вікні останніх змін) протягом певного періоду часу. Якщо позначити цю опцію, нотатки будуть видалені негайно і їх неможливо буде відновити.",
@@ -194,9 +193,7 @@
"notes_to_be_deleted": "Наступні нотатки будуть видалені ({{notesCount}})",
"no_note_to_delete": "Жодну нотатку не буде видалено (лише клони).",
"broken_relations_to_be_deleted": "Наступні зв'язки будуть розірвані та видалені ({{ relationCount}})",
"cancel": "Скасувати",
"ok": "ОК",
"deleted_relation_text": "Нотатка {{- note}} (буде видалена) посилається на зв'язок {{- relation}}, що походить з {{- source}}."
"cancel": "Скасувати"
},
"export": {
"export_note_title": "Експорт нотатки",
@@ -1744,16 +1741,12 @@
"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": "Якщо залишити налаштування проксі-сервера порожнім, буде використано системний проксі-сервер (стосується лише збірки для ПК/електронної версії).",

View File

@@ -27,7 +27,6 @@
},
"delete_notes": {
"close": "Đóng",
"ok": "OK",
"cancel": "Huỷ"
},
"export": {

View File

@@ -87,7 +87,7 @@ function buildUserAttribute(attr: AttributeWithDefinitions): ComponentChildren {
content = <><Icon icon={value === "true" ? "bx bx-check-square" : "bx bx-square"} />{" "}<strong>{attr.friendlyName}</strong></>;
break;
case "url":
content = <a href={value} target="_blank" rel="noopener noreferrer">{attr.friendlyName}</a>;
content = <a href={value} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()}>{attr.friendlyName}</a>;
break;
case "color":
style = { backgroundColor: value, color: getReadableTextColor(value) };

View File

@@ -180,11 +180,13 @@ export function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOpt
// Refresh on alterations to the note subtree.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (note && loadResults.getBranchRows().some(branch =>
branch.parentNoteId === note.noteId
|| noteIds.includes(branch.parentNoteId ?? ""))
if (note && (
loadResults.getNoteReorderings().includes(note.noteId)
|| loadResults.getBranchRows().some(branch =>
branch.parentNoteId === note.noteId
|| noteIds.includes(branch.parentNoteId ?? ""))
|| loadResults.getAttributeRows().some(attr => attr.name === "archived" && attr.noteId && noteIds.includes(attr.noteId))
) {
)) {
refreshNoteIds();
}
});

View File

@@ -27,7 +27,7 @@ describe("Board data", () => {
froca.branches["note1_note2"] = branch;
froca.getNoteFromCache("note1")!.addChild("note2", "note1_note2", false);
const data = await getBoardData(parentNote, "status", {}, false);
const noteIds = Array.from(data.byColumn.values()).flat().map(item => item.note.noteId);
const noteIds = [...data.byColumn.values()].flat().map(item => item.note.noteId);
expect(noteIds.length).toBe(3);
});
});

View File

@@ -75,7 +75,7 @@ export async function buildEventsForCalendar(note: FNote, e: EventSourceFuncArg)
if (dateNote.hasChildren()) {
const childNoteIds = await dateNote.getSubtreeNoteIds();
const childNoteIds = dateNote.getChildNoteIds();
for (const childNoteId of childNoteIds) {
childNoteToDateMapping[childNoteId] = startDate;
}

View File

@@ -1,20 +1,9 @@
:root {
/* Default values to be overridden by themes */
--calendar-coll-event-background-lightness: 95%;
--calendar-coll-event-background-saturation: 80%;
--calendar-coll-event-background-color: var(--accented-background-color);
--calendar-coll-event-text-color: var(--main-text-color);
--calendar-coll-event-hover-filter: none;
--callendar-coll-event-archived-sripe-color: #00000013;
--calendar-coll-today-background-color: var(--more-accented-background-color);
}
.calendar-view {
--fc-event-border-color: var(--calendar-coll-event-text-color);
--fc-event-bg-color: var(--calendar-coll-event-background-color);
--fc-event-text-color: var(--calendar-coll-event-text-color);
--fc-event-border-color: var(--calendar-coll-event-text-color, var(--main-text-color));
--fc-event-bg-color: var(--calendar-coll-event-background-color, var(--accented-background-color));
--fc-event-text-color: var(--calendar-coll-event-text-color, var(--main-text-color));
--fc-event-selected-overlay-color: transparent;
--fc-today-bg-color: var(--calendar-coll-today-background-color);
--fc-today-bg-color: var(--calendar-coll-today-background-color, var(--more-accented-background-color));
overflow: hidden;
position: relative;
@@ -123,7 +112,7 @@
z-index: -1;
--c1: transparent;
--c2: var(--callendar-coll-event-archived-sripe-color);
--c2: var(--callendar-coll-event-archived-sripe-color, #00000013);
background: repeating-linear-gradient(45deg, var(--c1), var(--c1) 8px,
var(--c2) 8px, var(--c2) 16px);
@@ -153,8 +142,8 @@
--fc-event-text-color: var(--custom-color);
--fc-event-bg-color: hsl(var(--custom-color-hue),
var(--calendar-coll-event-background-saturation),
var(--calendar-coll-event-background-lightness)) !important;
var(--calendar-coll-event-background-saturation, 80%),
var(--calendar-coll-event-background-lightness, 95%)) !important;
}
.calendar-view a.fc-timegrid-event:focus-visible,
@@ -171,7 +160,7 @@
.calendar-view a.fc-timegrid-event:hover,
.calendar-view a.fc-daygrid-event:hover {
filter: var(--calendar-coll-event-hover-filter);
filter: var(--calendar-coll-event-hover-filter, none);
border-color: var(--fc-event-text-color);
text-decoration: none;
color: currentColor;

View File

@@ -144,7 +144,12 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
const event = api.getEventById(noteId);
const note = froca.getNoteFromCache(noteId);
if (!event || !note) continue;
event.setProp("title", note.title);
// Only update the title if it has actually changed.
// setProp() triggers FullCalendar's eventChange callback, which would
// re-save the event's dates and cause unwanted side effects.
if (event.title !== note.title) {
event.setProp("title", note.title);
}
}
});
@@ -299,6 +304,12 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, c
}, [ note, componentId ]);
const onEventChange = useCallback(async (e: EventChangeArg) => {
// Only process actual date/time changes, not other property changes (e.g., title via setProp).
const datesChanged = e.oldEvent.start?.getTime() !== e.event.start?.getTime()
|| e.oldEvent.end?.getTime() !== e.event.end?.getTime()
|| e.oldEvent.allDay !== e.event.allDay;
if (!datesChanged) return;
const { startDate, endDate } = parseStartEndDateFromEvent(e.event);
if (!startDate) return;

View File

@@ -51,6 +51,8 @@ export default function useRowTableEditing(api: RefObject<Tabulator>, attributeD
if (type === "labels") {
if (typeof newValue === "boolean") {
newValue = newValue ? "true" : "false";
} else if (typeof newValue === "number") {
newValue = String(newValue);
}
setLabel(noteId, name, newValue);
} else if (type === "relations") {

View File

@@ -0,0 +1,30 @@
.delete-notes-dialog .tn-card {
margin-bottom: 16px;
}
.delete-notes-dialog .tn-card:last-child {
margin-bottom: 0;
}
.delete-notes-dialog .preview-list {
margin: 0;
padding: 0;
list-style: none;
max-height: 200px;
overflow: auto;
}
.delete-notes-dialog .preview-list li {
padding: 6px 16px;
border-bottom: 1px solid var(--main-border-color);
}
.delete-notes-dialog .preview-list li:last-child {
border-bottom: none;
}
.delete-notes-dialog .preview-list small {
margin-inline-start: 8px;
font-size: 0.8em;
color: var(--muted-text-color);
}

View File

@@ -1,15 +1,22 @@
import { useRef, useState, useEffect } from "preact/hooks";
import { t } from "../../services/i18n.js";
import FormCheckbox from "../react/FormCheckbox.js";
import Modal from "../react/Modal.js";
import "./delete_notes.css";
import type { DeleteNotesPreview } from "@triliumnext/commons";
import server from "../../services/server.js";
import { useEffect, useRef, useState } from "preact/hooks";
import froca from "../../services/froca.js";
import FNote from "../../entities/fnote.js";
import link from "../../services/link.js";
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import Button from "../react/Button.jsx";
import Alert from "../react/Alert.jsx";
import { Card, CardSection } from "../react/Card.js";
import FormToggle from "../react/FormToggle.js";
import { useTriliumEvent } from "../react/hooks.jsx";
import Modal from "../react/Modal.js";
import NoteLink from "../react/NoteLink.js";
import OptionsRow from "../type_widgets/options/components/OptionsRow.js";
interface CloneInfo {
totalCloneCount: number;
}
export interface ResolveOptions {
proceed: boolean;
@@ -24,9 +31,9 @@ interface ShowDeleteNotesDialogOpts {
}
interface BrokenRelationData {
note: string;
relation: string;
source: string;
noteId: string;
relationName: string;
sourceNoteId: string;
}
export default function DeleteNotesDialog() {
@@ -34,20 +41,51 @@ export default function DeleteNotesDialog() {
const [ deleteAllClones, setDeleteAllClones ] = useState(false);
const [ eraseNotes, setEraseNotes ] = useState(!!opts.forceDeleteAllClones);
const [ brokenRelations, setBrokenRelations ] = useState<DeleteNotesPreview["brokenRelations"]>([]);
const [ noteIdsToBeDeleted, setNoteIdsToBeDeleted ] = useState<DeleteNotesPreview["noteIdsToBeDeleted"]>([]);
const [ noteIdsToBeDeleted, setNoteIdsToBeDeleted ] = useState<DeleteNotesPreview["noteIdsToBeDeleted"]>([]);
const [ shown, setShown ] = useState(false);
const [ cloneInfo, setCloneInfo ] = useState<CloneInfo>({ totalCloneCount: 0 });
const okButtonRef = useRef<HTMLButtonElement>(null);
useTriliumEvent("showDeleteNotesDialog", (opts) => {
setOpts(opts);
setDeleteAllClones(false);
setEraseNotes(!!opts.forceDeleteAllClones);
setShown(true);
})
});
// Calculate clone information when branches change
useEffect(() => {
const { branchIdsToDelete } = opts;
if (!branchIdsToDelete || branchIdsToDelete.length === 0) {
setCloneInfo({ totalCloneCount: 0 });
return;
}
async function calculateCloneInfo() {
const branches = froca.getBranches(branchIdsToDelete!, true);
const uniqueNoteIds = [...new Set(branches.map(b => b.noteId))];
const notes = await froca.getNotes(uniqueNoteIds);
let totalCloneCount = 0;
for (const note of notes) {
const parentBranches = note.getParentBranches();
// Clones are additional parent branches beyond the one being deleted
const otherBranches = parentBranches.filter(b => !branchIdsToDelete!.includes(b.branchId));
totalCloneCount += otherBranches.length;
}
setCloneInfo({ totalCloneCount });
}
calculateCloneInfo();
}, [opts.branchIdsToDelete]);
useEffect(() => {
const { branchIdsToDelete, forceDeleteAllClones } = opts;
if (!branchIdsToDelete || branchIdsToDelete.length === 0) {
return;
}
}
server.post<DeleteNotesPreview>("delete-notes-preview", {
branchIdsToDelete,
@@ -63,16 +101,16 @@ export default function DeleteNotesDialog() {
className="delete-notes-dialog"
size="xl"
scrollable
title={t("delete_notes.delete_notes_preview")}
title={t("delete_notes.title")}
onShown={() => okButtonRef.current?.focus()}
onHidden={() => {
opts.callback?.({ proceed: false })
opts.callback?.({ proceed: false });
setShown(false);
}}
footer={<>
<Button text={t("delete_notes.cancel")}
onClick={() => setShown(false)} />
<Button text={t("delete_notes.ok")} kind="primary"
<Button text={t("delete_notes.delete")} kind="primary"
buttonRef={okButtonRef}
onClick={() => {
opts.callback?.({ proceed: true, deleteAllClones, eraseNotes });
@@ -81,92 +119,117 @@ export default function DeleteNotesDialog() {
</>}
show={shown}
>
<FormCheckbox name="delete-all-clones" label={t("delete_notes.delete_all_clones_description")}
currentValue={deleteAllClones} onChange={setDeleteAllClones}
/>
<FormCheckbox
name="erase-notes" label={t("delete_notes.erase_notes_warning")}
disabled={opts.forceDeleteAllClones}
currentValue={eraseNotes} onChange={setEraseNotes}
/>
<Card>
<CardSection>
<DeleteAllClonesOption
cloneInfo={cloneInfo}
deleteAllClones={deleteAllClones}
setDeleteAllClones={setDeleteAllClones}
/>
<OptionsRow
name="erase-notes"
label={t("delete_notes.erase_notes_label")}
description={t("delete_notes.erase_notes_description")}
>
<FormToggle
disabled={opts.forceDeleteAllClones}
currentValue={eraseNotes}
onChange={setEraseNotes}
/>
</OptionsRow>
</CardSection>
</Card>
<DeletedNotes noteIdsToBeDeleted={noteIdsToBeDeleted} />
<BrokenRelations brokenRelations={brokenRelations} />
<DeletedNotes noteIdsToBeDeleted={noteIdsToBeDeleted} />
</Modal>
);
}
function DeletedNotes({ noteIdsToBeDeleted }: { noteIdsToBeDeleted: DeleteNotesPreview["noteIdsToBeDeleted"] }) {
const [ noteLinks, setNoteLinks ] = useState<string[]>([]);
interface DeleteAllClonesOptionProps {
cloneInfo: CloneInfo;
deleteAllClones: boolean;
setDeleteAllClones: (value: boolean) => void;
}
useEffect(() => {
froca.getNotes(noteIdsToBeDeleted).then(async (notes: FNote[]) => {
const noteLinks: string[] = [];
function DeleteAllClonesOption({ cloneInfo, deleteAllClones, setDeleteAllClones }: DeleteAllClonesOptionProps) {
const { totalCloneCount } = cloneInfo;
for (const note of notes) {
noteLinks.push((await link.createLink(note.noteId, { showNotePath: true })).html());
}
setNoteLinks(noteLinks);
});
}, [noteIdsToBeDeleted]);
if (noteIdsToBeDeleted.length) {
return (
<div className="delete-notes-list-wrapper" style={{paddingTop: "16px"}}>
<h4>{t("delete_notes.notes_to_be_deleted", { notesCount: noteIdsToBeDeleted.length })}</h4>
<ul className="delete-notes-list" style={{ maxHeight: "200px", overflow: "auto"}}>
{noteLinks.map((link, index) => (
<li key={index} dangerouslySetInnerHTML={{ __html: link }} />
))}
</ul>
</div>
);
} else {
return (
<Alert type="info">
{t("delete_notes.no_note_to_delete")}
</Alert>
)
if (totalCloneCount === 0) {
return null;
}
return (
<OptionsRow
name="delete-all-clones"
label={t("delete_notes.clones_label")}
description={t("delete_notes.delete_clones_description", { count: totalCloneCount })}
>
<FormToggle
currentValue={deleteAllClones}
onChange={setDeleteAllClones}
/>
</OptionsRow>
);
}
function DeletedNotes({ noteIdsToBeDeleted }: { noteIdsToBeDeleted: DeleteNotesPreview["noteIdsToBeDeleted"] }) {
return (
<Card heading={t("delete_notes.notes_to_be_deleted", { notesCount: noteIdsToBeDeleted.length })}>
<CardSection noPadding={noteIdsToBeDeleted.length > 0}>
{noteIdsToBeDeleted.length ? (
<ul className="preview-list">
{noteIdsToBeDeleted.map((noteId) => (
<li key={noteId}>
<NoteLink notePath={noteId} showNotePath showNoteIcon />
</li>
))}
</ul>
) : (
<span className="muted-text">{t("delete_notes.no_note_to_delete")}</span>
)}
</CardSection>
</Card>
);
}
function BrokenRelations({ brokenRelations }: { brokenRelations: DeleteNotesPreview["brokenRelations"] }) {
const [ notesWithBrokenRelations, setNotesWithBrokenRelations ] = useState<BrokenRelationData[]>([]);
useEffect(() => {
const noteIds = brokenRelations
.map(relation => relation.noteId)
.filter(noteId => noteId) as string[];
froca.getNotes(noteIds).then(async () => {
const notesWithBrokenRelations: BrokenRelationData[] = [];
for (const attr of brokenRelations) {
notesWithBrokenRelations.push({
note: (await link.createLink(attr.value)).html(),
relation: `<code>${attr.name}</code>`,
source: (await link.createLink(attr.noteId)).html()
});
}
setNotesWithBrokenRelations(notesWithBrokenRelations);
});
}, [brokenRelations]);
if (brokenRelations.length) {
return (
<Alert type="danger" title={t("delete_notes.broken_relations_to_be_deleted", { relationCount: brokenRelations.length })}>
<ul className="broken-relations-list" style={{ maxHeight: "200px", overflow: "auto" }}>
{brokenRelations.map((_, index) => {
return (
<li key={index}>
<span dangerouslySetInnerHTML={{ __html: t("delete_notes.deleted_relation_text", notesWithBrokenRelations[index] as unknown as Record<string, string>) }} />
</li>
);
})}
</ul>
</Alert>
);
} else {
return <></>;
if (!brokenRelations.length) {
return null;
}
const relationsData: BrokenRelationData[] = brokenRelations
.filter((attr) => attr.value && attr.noteId)
.map((attr) => ({
noteId: attr.value!,
relationName: attr.name,
sourceNoteId: attr.noteId!
}));
return (
<Card heading={t("delete_notes.broken_relations_to_be_deleted", { relationCount: brokenRelations.length })}>
<CardSection noPadding>
<div style={{ overflowX: "auto" }}>
<table className="table table-striped">
<thead>
<tr>
<th>{t("delete_notes.table_note_with_relation")}</th>
<th>{t("delete_notes.table_relation")}</th>
<th>{t("delete_notes.table_points_to")}</th>
</tr>
</thead>
<tbody>
{relationsData.map((relation, index) => (
<tr key={index}>
<td><NoteLink notePath={relation.sourceNoteId} showNoteIcon /></td>
<td><code>{relation.relationName}</code></td>
<td><NoteLink notePath={relation.noteId} showNoteIcon /></td>
</tr>
))}
</tbody>
</table>
</div>
</CardSection>
</Card>
);
}

View File

@@ -8,7 +8,7 @@ import Button from "../react/Button";
import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete";
import tree from "../../services/tree";
import froca from "../../services/froca";
import { useTriliumEvent } from "../react/hooks";
import { useTriliumEvent, useTriliumOption } from "../react/hooks";
import { type BoxSize, CKEditorApi } from "../type_widgets/text/CKEditorWithWatchdog";
export interface IncludeNoteOpts {
@@ -18,11 +18,13 @@ export interface IncludeNoteOpts {
export default function IncludeNoteDialog() {
const editorApiRef = useRef<CKEditorApi>(null);
const [suggestion, setSuggestion] = useState<Suggestion | null>(null);
const [boxSize, setBoxSize] = useState<string>("medium");
const [defaultBoxSize, setDefaultBoxSize] = useTriliumOption("includeNoteDefaultBoxSize");
const [boxSize, setBoxSize] = useState<string>(defaultBoxSize);
const [shown, setShown] = useState(false);
useTriliumEvent("showIncludeNoteDialog", ({ editorApi }) => {
editorApiRef.current = editorApi;
setBoxSize(defaultBoxSize); // Reset to default when opening dialog
setShown(true);
});
@@ -35,10 +37,14 @@ export default function IncludeNoteDialog() {
size="lg"
onShown={() => triggerRecentNotes(autoCompleteRef.current)}
onHidden={() => setShown(false)}
onSubmit={() => {
onSubmit={async () => {
if (!suggestion?.notePath || !editorApiRef.current) return;
setShown(false);
includeNote(suggestion.notePath, editorApiRef.current, boxSize as BoxSize);
await includeNote(suggestion.notePath, editorApiRef.current, boxSize as BoxSize);
// Save the selected box size as the new default
if (boxSize !== defaultBoxSize) {
setDefaultBoxSize(boxSize);
}
}}
footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />}
show={shown}
@@ -63,6 +69,7 @@ export default function IncludeNoteDialog() {
{ label: t("include_note.box_size_small"), value: "small" },
{ label: t("include_note.box_size_medium"), value: "medium" },
{ label: t("include_note.box_size_full"), value: "full" },
{ label: t("include_note.box_size_expandable"), value: "expandable" },
]}
/>
</FormGroup>

View File

@@ -80,9 +80,19 @@ export default function JumpToNoteDialogComponent() {
break;
}
$autoComplete
.trigger("focus")
.trigger("select");
$autoComplete.trigger("focus");
if (mode === "commands") {
// In command mode, place caret at end instead of selecting all text
// This preserves the ">" prefix when the user starts typing
const input = autocompleteRef.current;
if (input) {
const len = input.value.length;
input.setSelectionRange(len, len);
}
} else {
$autoComplete.trigger("select");
}
// Add keyboard shortcut for full search
shortcutService.bindElShortcut($autoComplete, "ctrl+return", () => {

View File

@@ -9,7 +9,6 @@ import appContext, { type EventData } from "../components/app_context.js";
import type FNote from "../entities/fnote.js";
import attributeService from "../services/attributes.js";
import { t } from "../services/i18n.js";
import katex from "../services/math.js";
import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js";
import RightPanelWidget from "./right_panel_widget.js";
@@ -125,77 +124,6 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility");
}
extractOuterTag(htmlStr: string | null) {
if (htmlStr === null) {
return null;
}
// Regular expressions that match only the outermost tag
const regex = /^<([a-zA-Z]+)([^>]*)>/;
const match = htmlStr.match(regex);
if (match) {
const tagName = match[1].toLowerCase(); // Extract tag name
const attributes = match[2].trim(); // Extract label attributes
return { tagName, attributes };
}
return null;
}
areOuterTagsConsistent(str1: string | null, str2: string | null) {
const tag1 = this.extractOuterTag(str1);
const tag2 = this.extractOuterTag(str2);
// If one of them has no label, returns false
if (!tag1 || !tag2) {
return false;
}
// Compare tag names and attributes to see if they are the same
return tag1.tagName === tag2.tagName && tag1.attributes === tag2.attributes;
}
/**
* Rendering formulas in strings using katex
*
* @param html Note's html content
* @returns The HTML content with mathematical formulas rendered by KaTeX.
*/
async replaceMathTextWithKatax(html: string) {
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
const matches = [...html.matchAll(mathTextRegex)];
let modifiedText = html;
if (matches.length > 0) {
// Process all matches asynchronously
for (const match of matches) {
const latexCode = match[1];
let rendered;
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
});
} catch (e) {
if (e instanceof ReferenceError && e.message.includes("katex is not defined")) {
// Load KaTeX if it is not already loaded
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
});
} catch (renderError) {
console.error("KaTeX rendering error after loading library:", renderError);
rendered = match[0]; // Fall back to original if error persists
}
} else {
console.error("KaTeX rendering error:", e);
rendered = match[0]; // Fall back to original on error
}
}
// Replace the matched formula in the modified text
modifiedText = modifiedText.replace(match[0], rendered);
}
}
return modifiedText;
}
async getHighlightList(content: string, optionsHighlightsList: string[]) {
// matches a span containing background-color
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
@@ -239,9 +167,6 @@ export default class HighlightsListWidget extends RightPanelWidget {
const $highlightsList = $("<ol>");
let prevEndIndex = -1,
hlLiCount = 0;
let prevSubHtml: string | null = null;
// Used to determine if a string is only a formula
const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/;
for (let match: RegExpMatchArray | null = null, hltIndex = 0; (match = combinedRegex.exec(content)) !== null; hltIndex++) {
const subHtml = match[0];
@@ -257,25 +182,14 @@ export default class HighlightsListWidget extends RightPanelWidget {
// If the previous element is connected to this element in HTML, then concatenate them into one.
$highlightsList.children().last().append(subHtml);
} else {
// TODO: can't be done with $(subHtml).text()?
//Cant remember why regular expressions are used here, but modified to $(subHtml).text() works as expected
//const hasText = [...subHtml.matchAll(/(?<=^|>)[^><]+?(?=<|$)/g)].map(matchTmp => matchTmp[0]).join('').trim();
const hasText = $(subHtml).text().trim();
if (hasText) {
const substring = content.substring(prevEndIndex, startIndex);
//If the two elements have the same style and there are only formulas in between, append the formulas and the current element to the end of the previous element.
if (this.areOuterTagsConsistent(prevSubHtml, subHtml) && onlyMathRegex.test(substring)) {
const $lastLi = $highlightsList.children("li").last();
$lastLi.append(await this.replaceMathTextWithKatax(substring));
$lastLi.append(subHtml);
} else {
$highlightsList.append(
$("<li>")
.html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
);
}
$highlightsList.append(
$("<li>")
.html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
);
hlLiCount++;
} else {
@@ -284,7 +198,6 @@ export default class HighlightsListWidget extends RightPanelWidget {
}
}
prevEndIndex = endIndex;
prevSubHtml = subHtml;
}
return {
$highlightsList,

View File

@@ -2,10 +2,13 @@ import "./CollectionProperties.css";
import { t } from "i18next";
import { ComponentChildren } from "preact";
import { useRef } from "preact/hooks";
import { useRef, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import appContext from "../../components/app_context";
import dialogService from "../../services/dialog";
import { ViewTypeOptions } from "../collections/interface";
import ActionButton from "../react/ActionButton";
import Dropdown from "../react/Dropdown";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useNoteProperty, useTriliumEvent } from "../react/hooks";
@@ -24,6 +27,8 @@ export const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
presentation: "bx bx-rectangle"
};
const MAX_OPEN_TABS = 50;
export default function CollectionProperties({ note, centerChildren, rightChildren }: {
note: FNote;
centerChildren?: ComponentChildren;
@@ -31,6 +36,7 @@ export default function CollectionProperties({ note, centerChildren, rightChildr
}) {
const [ viewType, setViewType ] = useViewType(note);
const noteType = useNoteProperty(note, "type");
const [ isOpening, setIsOpening ] = useState(false);
return ([ "book", "search" ].includes(noteType ?? "") &&
<div className="collection-properties">
@@ -43,11 +49,59 @@ export default function CollectionProperties({ note, centerChildren, rightChildr
</div>
<div className="right-container">
{rightChildren}
{noteType === "search" && (
<OpenAllButton note={note} isOpening={isOpening} setIsOpening={setIsOpening} />
)}
</div>
</div>
);
}
function OpenAllButton({ note, isOpening, setIsOpening }: {
note: FNote;
isOpening: boolean;
setIsOpening: (value: boolean) => void;
}) {
const noteIds = note.getChildNoteIds();
const count = noteIds.length;
const handleOpenAll = async () => {
if (count === 0) return;
if (count > MAX_OPEN_TABS) {
await dialogService.info(t("book_properties.open_all_too_many", { count, max: MAX_OPEN_TABS }));
return;
}
if (count > 10) {
const confirmed = await dialogService.confirm(t("book_properties.open_all_confirm", { count }));
if (!confirmed) return;
}
setIsOpening(true);
try {
for (let i = 0; i < noteIds.length; i++) {
const noteId = noteIds[i];
const isLast = i === noteIds.length - 1;
await appContext.tabManager.openTabWithNoteWithHoisting(noteId, {
activate: isLast
});
}
} finally {
setIsOpening(false);
}
};
return (
<ActionButton
icon={isOpening ? "bx bx-loader-alt bx-spin" : "bx bx-window-open"}
text={t("book_properties.open_all_in_tabs_tooltip")}
onClick={handleOpenAll}
disabled={count === 0 || isOpening}
/>
);
}
function ViewTypeSwitcher({ viewType, setViewType }: { viewType: ViewTypeOptions, setViewType: (newValue: ViewTypeOptions) => void }) {
// Keyboard shortcut
const dropdownContainerRef = useRef<HTMLDivElement>(null);

View File

@@ -42,8 +42,11 @@ export default function NoteIcon() {
setIcon(note?.getIcon());
}, [ note, iconClass, workspaceIconClass ]);
const isDisabled = viewScope?.viewMode !== "default"
|| note?.isMetadataReadOnly;
if (isMobile()) {
return <MobileNoteIconSwitcher note={note} icon={icon} />;
return <MobileNoteIconSwitcher note={note} icon={icon} disabled={isDisabled} />;
}
return (
@@ -55,16 +58,17 @@ export default function NoteIcon() {
dropdownOptions={{ autoClose: "outside" }}
buttonClassName={`note-icon tn-focusable-button ${icon ?? "bx bx-empty"}`}
hideToggleArrow
disabled={viewScope?.viewMode !== "default"}
disabled={isDisabled}
>
{ note && <NoteIconList note={note} onHide={() => dropdownRef?.current?.hide()} columnCount={12} /> }
</Dropdown>
);
}
function MobileNoteIconSwitcher({ note, icon }: {
function MobileNoteIconSwitcher({ note, icon, disabled }: {
note: FNote | null | undefined;
icon: string | null | undefined;
disabled?: boolean;
}) {
const [ modalShown, setModalShown ] = useState(false);
const { windowWidth } = useWindowSize();
@@ -76,6 +80,7 @@ function MobileNoteIconSwitcher({ note, icon }: {
icon={icon ?? "bx bx-empty"}
text={t("note_icon.change_note_icon")}
onClick={() => setModalShown(true)}
disabled={disabled}
/>
{createPortal((

View File

@@ -1,5 +1,5 @@
.note-detail-note-map {
height: 100%;
height: 100%;
overflow: hidden;
}
@@ -54,4 +54,4 @@
width: 10px;
}
/* End of styling the slider */
/* End of styling the slider */

View File

@@ -12,11 +12,15 @@ import { t } from "../../services/i18n";
import { getEffectiveThemeStyle } from "../../services/theme";
import ActionButton from "../react/ActionButton";
import { useElementSize, useNoteLabel } from "../react/hooks";
import NoItems from "../react/NoItems";
import Slider from "../react/Slider";
import { loadNotesAndRelations, NoteMapLinkObject, NoteMapNodeObject, NotesAndRelationsData } from "./data";
import { CssData, setupRendering } from "./rendering";
import { MapType, NoteMapWidgetMode, rgb2hex } from "./utils";
/** Maximum number of notes to render in the note map before showing a warning. */
const MAX_NOTES_THRESHOLD = 1_000;
interface NoteMapProps {
note: FNote;
widgetMode: NoteMapWidgetMode;
@@ -34,6 +38,7 @@ export default function NoteMap({ note, widgetMode, parentRef }: NoteMapProps) {
const containerSize = useElementSize(parentRef);
const [ fixNodes, setFixNodes ] = useState(false);
const [ linkDistance, setLinkDistance ] = useState(40);
const [ tooManyNotes, setTooManyNotes ] = useState<number | null>(null);
const notesAndRelationsRef = useRef<NotesAndRelationsData>();
const mapRootId = useMemo(() => {
@@ -61,6 +66,14 @@ export default function NoteMap({ note, widgetMode, parentRef }: NoteMapProps) {
const includeRelations = labelValues("mapIncludeRelation");
loadNotesAndRelations(mapRootId, excludeRelations, includeRelations, mapType).then((notesAndRelations) => {
if (!containerRef.current || !styleResolverRef.current) return;
// Guard against rendering too many notes which would freeze the browser.
if (notesAndRelations.nodes.length > MAX_NOTES_THRESHOLD) {
setTooManyNotes(notesAndRelations.nodes.length);
return;
}
setTooManyNotes(null);
const cssData = getCssData(containerRef.current, styleResolverRef.current);
// Configure rendering properties.
@@ -119,6 +132,12 @@ export default function NoteMap({ note, widgetMode, parentRef }: NoteMapProps) {
});
}, [ fixNodes, mapType ]);
if (tooManyNotes) {
return (
<NoItems icon="bx bx-error-circle" text={t("note_map.too-many-notes", { count: tooManyNotes, max: MAX_NOTES_THRESHOLD })} />
);
}
return (
<div className="note-map-widget">
<div className="btn-group btn-group-sm map-type-switcher content-floating-buttons top-left" role="group">

View File

@@ -1,15 +1,16 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { t } from "../services/i18n";
import FormTextBox from "./react/FormTextBox";
import { useNoteContext, useNoteProperty, useSpacedUpdate, useTriliumEvent, useTriliumEvents } from "./react/hooks";
import protected_session_holder from "../services/protected_session_holder";
import server from "../services/server";
import "./note_title.css";
import { isLaunchBarConfig } from "../services/utils";
import clsx from "clsx";
import { useEffect, useRef, useState } from "preact/hooks";
import appContext from "../components/app_context";
import branches from "../services/branches";
import { t } from "../services/i18n";
import protected_session_holder from "../services/protected_session_holder";
import server from "../services/server";
import { isIMEComposing } from "../services/shortcuts";
import clsx from "clsx";
import FormTextBox from "./react/FormTextBox";
import { useNoteContext, useNoteProperty, useSpacedUpdate, useTriliumEvent, useTriliumEvents } from "./react/hooks";
export default function NoteTitleWidget(props: {className?: string}) {
const { note, noteId, componentId, viewScope, noteContext, parentComponent } = useNoteContext();
@@ -25,8 +26,7 @@ export default function NoteTitleWidget(props: {className?: string}) {
const isReadOnly = note === null
|| note === undefined
|| (note.isProtected && !protected_session_holder.isProtectedSessionAvailable())
|| isLaunchBarConfig(note.noteId)
|| note.noteId.startsWith("_help_")
|| note.isMetadataReadOnly
|| viewScope?.viewMode !== "default";
setReadOnly(isReadOnly);
}, [ note, note?.noteId, note?.isProtected, viewScope?.viewMode ]);
@@ -58,11 +58,29 @@ export default function NoteTitleWidget(props: {className?: string}) {
// Manage focus.
const textBoxRef = useRef<HTMLInputElement>(null);
const isNewNote = useRef<boolean>();
const pendingSelect = useRef<boolean>(false);
// Re-apply selection when title changes if we have a pending select.
// This handles the case where the server sends back entity changes after we've
// already called select(), which causes the controlled input to re-render and lose selection.
useEffect(() => {
if (pendingSelect.current && textBoxRef.current && document.activeElement === textBoxRef.current) {
textBoxRef.current.select();
pendingSelect.current = false;
}
}, [title]);
useTriliumEvents([ "focusOnTitle", "focusAndSelectTitle" ], (e, eventName) => {
if (noteContext?.isActive() && textBoxRef.current) {
// In the new layout, there are two NoteTitleWidget instances. Only handle if visible.
if (!textBoxRef.current.checkVisibility({ checkOpacity: true })) {
return;
}
textBoxRef.current.focus();
if (eventName === "focusAndSelectTitle") {
textBoxRef.current.select();
pendingSelect.current = true;
}
isNewNote.current = ("isNewNote" in e ? e.isNewNote : false);
}
@@ -83,6 +101,9 @@ export default function NoteTitleWidget(props: {className?: string}) {
spacedUpdate.scheduleUpdate();
}}
onKeyDown={(e) => {
// User started typing, stop re-applying selection
pendingSelect.current = false;
// Skip processing if IME is composing to prevent interference
// with text input in CJK languages
if (isIMEComposing(e)) {
@@ -101,6 +122,7 @@ export default function NoteTitleWidget(props: {className?: string}) {
}
}}
onBlur={() => {
pendingSelect.current = false;
spacedUpdate.updateNowIfNecessary();
isNewNote.current = false;
}}

View File

@@ -1,13 +1,14 @@
import BasicWidget from "./basic_widget.js";
import server from "../services/server.js";
import linkService from "../services/link.js";
import froca from "../services/froca.js";
import utils, { handleRightToLeftPlacement } from "../services/utils.js";
import appContext from "../components/app_context.js";
import shortcutService, { isIMEComposing } from "../services/shortcuts.js";
import { t } from "../services/i18n.js";
import { Dropdown, Tooltip } from "bootstrap";
import appContext from "../components/app_context.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import linkService from "../services/link.js";
import server from "../services/server.js";
import shortcutService, { isIMEComposing } from "../services/shortcuts.js";
import utils, { handleRightToLeftPlacement } from "../services/utils.js";
import BasicWidget from "./basic_widget.js";
const TPL = /*html*/`
<div class="quick-search input-group input-group-sm">
<style>
@@ -245,7 +246,7 @@ export default class QuickSearchWidget extends BasicWidget {
const { searchResultNoteIds, searchResults, error } = await server.get<QuickSearchResponse>(`quick-search/${encodeURIComponent(searchString)}`);
if (error) {
let tooltip = new Tooltip(this.$searchString[0], {
const tooltip = new Tooltip(this.$searchString[0], {
trigger: "manual",
title: `Search error: ${error}`,
placement: handleRightToLeftPlacement("right")
@@ -289,10 +290,9 @@ export default class QuickSearchWidget extends BasicWidget {
const resultsToDisplay = this.allSearchResults.slice(startIndex, endIndex);
for (const result of resultsToDisplay) {
const noteId = result.notePath.split("/").pop();
if (!noteId) continue;
if (!result.notePath) continue;
const $item = $('<a class="dropdown-item" tabindex="0" href="javascript:">');
const $item = $(`<a class="dropdown-item" tabindex="0" href="#${result.notePath}">`);
// Build the display HTML with content snippet below the title
let itemHtml = `<div class="quick-search-item">
@@ -317,23 +317,13 @@ export default class QuickSearchWidget extends BasicWidget {
$item.html(itemHtml);
$item.on("click", (e) => {
$item.on("click auxclick", () => {
this.dropdown.hide();
e.preventDefault();
const activeContext = appContext.tabManager.getActiveContext();
if (activeContext) {
activeContext.setNote(noteId);
}
});
shortcutService.bindElShortcut($item, "return", () => {
this.dropdown.hide();
const activeContext = appContext.tabManager.getActiveContext();
if (activeContext) {
activeContext.setNote(noteId);
}
$item[0].click();
});
this.$dropdownMenu.append($item);
@@ -350,24 +340,18 @@ export default class QuickSearchWidget extends BasicWidget {
const $link = await linkService.createLink(note.noteId, { showNotePath: true, showNoteIcon: true });
$link.addClass("dropdown-item");
$link.attr("tabIndex", "0");
$link.on("click", (e) => {
$link.on("click auxclick", (e) => {
this.dropdown.hide();
if (!e.target || e.target.nodeName !== "A") {
// click on the link is handled by link handling, but we want the whole item clickable
const activeContext = appContext.tabManager.getActiveContext();
if (activeContext) {
activeContext.setNote(note.noteId);
}
if (!e.target || (e.target as HTMLElement).nodeName !== "A") {
// click on the <a> is handled by the global goToLink handler,
// but we want the whole item clickable
$link.find("a")[0]?.dispatchEvent(new MouseEvent(e.type, e.originalEvent as MouseEventInit));
}
});
shortcutService.bindElShortcut($link, "return", () => {
this.dropdown.hide();
const activeContext = appContext.tabManager.getActiveContext();
if (activeContext) {
activeContext.setNote(note.noteId);
}
$link.find("a")[0]?.click();
});
this.$dropdownMenu.append($link);

View File

@@ -35,6 +35,14 @@
flex-direction: column;
gap: var(--card-section-gap);
.tn-card-section.tn-no-padding {
padding: 0;
& .table {
margin-bottom: 0;
}
}
.tn-card-section {
&:first-of-type {
border-top-left-radius: var(--card-border-radius);

View File

@@ -50,6 +50,7 @@ export interface CardSectionProps {
subSectionsVisible?: boolean;
highlightOnHover?: boolean;
onAction?: () => void;
noPadding?: boolean;
}
interface CardSectionContextType {
@@ -65,7 +66,8 @@ export function CardSection(props: {children: ComponentChildren} & CardSectionPr
return <>
<section className={clsx("tn-card-section", props.className, {
"tn-card-section-nested": nestingLevel > 0,
"tn-card-highlight-on-hover": props.highlightOnHover || props.onAction
"tn-card-highlight-on-hover": props.highlightOnHover || props.onAction,
"tn-no-padding": props.noPadding
})}
style={{"--tn-card-section-nesting-level": (nestingLevel) ? nestingLevel : null}}
onClick={props.onAction}>

View File

@@ -7,17 +7,22 @@ import { ComponentChildren } from "preact";
interface FormToggleProps {
currentValue: boolean | null;
onChange(newValue: boolean): void;
switchOnName: string;
/** Label shown when toggle is off. If omitted along with switchOffName, no label is shown. */
switchOnName?: string;
switchOnTooltip?: string;
switchOffName: string;
/** Label shown when toggle is on. If omitted along with switchOnName, no label is shown. */
switchOffName?: string;
switchOffTooltip?: string;
helpPage?: string;
disabled?: boolean;
afterName?: ComponentChildren;
/** ID for the input element, useful for accessibility with external labels */
id?: string;
}
export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled, afterName }: FormToggleProps) {
export default function FormToggle({ currentValue, helpPage, switchOnName, switchOnTooltip, switchOffName, switchOffTooltip, onChange, disabled, afterName, id }: FormToggleProps) {
const [ disableTransition, setDisableTransition ] = useState(true);
const hasLabel = switchOnName || switchOffName;
useEffect(() => {
const timeout = setTimeout(() => {
@@ -28,7 +33,7 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc
return (
<div className="switch-widget">
<span className="switch-name">{ currentValue ? switchOffName : switchOnName }</span>
{hasLabel && <span className="switch-name">{ currentValue ? switchOffName : switchOnName }</span>}
{ afterName }
<label>
@@ -37,6 +42,7 @@ export default function FormToggle({ currentValue, helpPage, switchOnName, switc
title={currentValue ? switchOffTooltip : switchOnTooltip }
>
<input
id={id}
className="switch-toggle"
type="checkbox"
checked={currentValue === true}

View File

@@ -15,6 +15,7 @@ import attributes from "../../services/attributes";
import froca from "../../services/froca";
import keyboard_actions from "../../services/keyboard_actions";
import { ViewScope } from "../../services/link";
import math from "../../services/math";
import options, { type OptionValue } from "../../services/options";
import protected_session_holder from "../../services/protected_session_holder";
import server from "../../services/server";
@@ -825,13 +826,43 @@ export function useWindowSize() {
return size;
}
// Workaround for https://github.com/twbs/bootstrap/issues/37474
// Bootstrap's dispose() sets ALL properties to null. But pending animation callbacks
// (scheduled via setTimeout) can still fire and crash when accessing null properties.
// We patch dispose() to set safe placeholder values instead of null.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const TooltipProto = Tooltip.prototype as any;
const originalDispose = TooltipProto.dispose;
const disposedTooltipPlaceholder = {
activeTrigger: {},
element: document.createElement("noscript")
};
TooltipProto.dispose = function () {
originalDispose.call(this);
// After disposal, set safe values so pending callbacks don't crash
this._activeTrigger = disposedTooltipPlaceholder.activeTrigger;
this._element = disposedTooltipPlaceholder.element;
};
export function useTooltip(elRef: RefObject<HTMLElement>, config: Partial<Tooltip.Options>) {
useEffect(() => {
if (!elRef?.current) return;
const $el = $(elRef.current);
$el.tooltip("dispose");
const element = elRef.current;
const $el = $(element);
// Dispose any existing tooltip before creating a new one
Tooltip.getInstance(element)?.dispose();
$el.tooltip(config);
// Capture the tooltip instance now, since elRef.current may be null during cleanup.
const tooltip = Tooltip.getInstance(element);
return () => {
if (element.isConnected) {
tooltip?.dispose();
}
};
}, [ elRef, config ]);
const showTooltip = useCallback(() => {
@@ -866,8 +897,14 @@ export function useStaticTooltip(elRef: RefObject<Element>, config?: Partial<Too
const hasTooltip = config?.title || elRef.current?.getAttribute("title");
if (!elRef?.current || !hasTooltip) return;
const tooltip = Tooltip.getOrCreateInstance(elRef.current, config);
elRef.current.addEventListener("show.bs.tooltip", () => {
// Capture element now, since elRef.current may be null during cleanup.
const element = elRef.current;
// Dispose any existing tooltip before creating a new one
Tooltip.getInstance(element)?.dispose();
const tooltip = new Tooltip(element, config);
element.addEventListener("show.bs.tooltip", () => {
// Hide all the other tooltips.
for (const otherTooltip of tooltips) {
if (otherTooltip === tooltip) continue;
@@ -878,12 +915,11 @@ export function useStaticTooltip(elRef: RefObject<Element>, config?: Partial<Too
return () => {
tooltips.delete(tooltip);
tooltip.dispose();
// workaround for https://github.com/twbs/bootstrap/issues/37474
(tooltip as any)._activeTrigger = {};
(tooltip as any)._element = document.createElement('noscript'); // placeholder with no behavior
if (element.isConnected) {
tooltip.dispose();
}
// Remove *all* tooltip elements from the DOM
// Remove any lingering tooltip popup elements from the DOM.
document
.querySelectorAll('.tooltip')
.forEach(t => t.remove());
@@ -1400,3 +1436,38 @@ export function useColorScheme() {
return prefersDark ? "dark" : "light";
}
/**
* Renders math equations within elements that have the `.math-tex` class.
* Used by sidebar widgets like Table of Contents and Highlights list to display math content.
*
* @param containerRef - Ref to the container element that may contain math elements
* @param deps - Dependencies that trigger re-rendering (e.g., text content)
*/
export function useMathRendering(containerRef: RefObject<HTMLElement>, deps: unknown[]) {
useEffect(() => {
if (!containerRef.current) return;
// Support both read-only (.math-tex) and CKEditor editing view (.ck-math-tex) classes
const mathElements = containerRef.current.querySelectorAll(".math-tex, .ck-math-tex");
for (const mathEl of mathElements) {
// Skip if already rendered by KaTeX
if (mathEl.querySelector(".katex")) continue;
try {
let equation = mathEl.textContent || "";
// CKEditor widgets store equation without delimiters, add them for KaTeX
if (mathEl.classList.contains("ck-math-tex")) {
// Check if it's display mode or inline
const isDisplay = mathEl.classList.contains("ck-math-tex-display");
equation = isDisplay ? `\\[${equation}\\]` : `\\(${equation}\\)`;
}
math.render(equation, mathEl as HTMLElement);
} catch (e) {
console.warn("Failed to render math:", e);
}
}
}, deps); // eslint-disable-line react-hooks/exhaustive-deps
}

View File

@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import { extractHighlightsFromStaticHtml } from "./HighlightsList.js";
describe("extractHighlightsFromStaticHtml", () => {
it("extracts a single highlight containing text and math equation together", () => {
const container = document.createElement("div");
container.innerHTML = `<p>
<span style="background-color:hsl(30,75%,60%);">
Highlighted&nbsp;
<span class="math-tex">
\\(e=mc^2\\)
</span>
&nbsp;math
</span>
</p>`;
document.body.appendChild(container);
const highlights = extractHighlightsFromStaticHtml(container);
// Should extract 1 combined highlight, not 3 separate ones
expect(highlights.length).toBe(1);
// The highlight should contain the full innerHTML of the styled span
const highlight = highlights[0];
expect(highlight.text).toContain("Highlighted");
expect(highlight.text).toContain("math-tex");
expect(highlight.text).toContain("e=mc^2");
expect(highlight.text).toContain("math");
expect(highlight.attrs.background).toBeTruthy();
document.body.removeChild(container);
});
it("extracts separate highlights for differently styled spans", () => {
const container = document.createElement("div");
container.innerHTML = `<p>
<span style="background-color:yellow;">Yellow text</span>
normal text
<span style="background-color:red;">Red text</span>
</p>`;
document.body.appendChild(container);
const highlights = extractHighlightsFromStaticHtml(container);
// Should extract 2 separate highlights (yellow and red)
expect(highlights.length).toBe(2);
expect(highlights[0].text).toBe("Yellow text");
expect(highlights[1].text).toBe("Red text");
document.body.removeChild(container);
});
});

View File

@@ -1,11 +1,12 @@
import { CKTextEditor, ModelText } from "@triliumnext/ckeditor5";
import { createPortal } from "preact/compat";
import { useCallback, useEffect, useState } from "preact/hooks";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import { randomString } from "../../services/utils";
import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor, useTriliumOptionJson } from "../react/hooks";
import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useMathRendering, useNoteProperty, useTextEditor, useTriliumOptionJson } from "../react/hooks";
import Modal from "../react/Modal";
import RawHtml from "../react/RawHtml";
import { HighlightsListOptions } from "../type_widgets/options/text_notes";
import RightPanelWidget from "./RightPanelWidget";
@@ -84,20 +85,11 @@ function AbstractHighlightsList<T extends RawHighlight>({ highlights, scrollToHi
{filteredHighlights.length > 0 ? (
<ol>
{filteredHighlights.map(highlight => (
<li
<HighlightItem
key={highlight.id}
highlight={highlight}
onClick={() => scrollToHighlight(highlight)}
>
<span
style={{
fontWeight: highlight.attrs.bold ? "700" : undefined,
fontStyle: highlight.attrs.italic ? "italic" : undefined,
textDecoration: highlight.attrs.underline ? "underline" : undefined,
color: highlight.attrs.color,
backgroundColor: highlight.attrs.background
}}
>{highlight.text}</span>
</li>
/>
))}
</ol>
) : (
@@ -112,6 +104,31 @@ function AbstractHighlightsList<T extends RawHighlight>({ highlights, scrollToHi
);
}
function HighlightItem<T extends RawHighlight>({ highlight, onClick }: {
highlight: T;
onClick(): void;
}) {
const contentRef = useRef<HTMLElement>(null);
useMathRendering(contentRef, [highlight.text]);
return (
<li onClick={onClick}>
<RawHtml
containerRef={contentRef}
style={{
fontWeight: highlight.attrs.bold ? "700" : undefined,
fontStyle: highlight.attrs.italic ? "italic" : undefined,
textDecoration: highlight.attrs.underline ? "underline" : undefined,
color: highlight.attrs.color,
backgroundColor: highlight.attrs.background
}}
html={highlight.text}
/>
</li>
);
}
//#region Editable text (CKEditor)
interface CKHighlight extends RawHighlight {
textNode: ModelText;
@@ -201,9 +218,24 @@ function extractHighlightsFromTextEditor(editor: CKTextEditor) {
};
if (Object.values(attrs).some(Boolean)) {
// Get HTML content from DOM (includes nested elements like math)
let html = item.data;
try {
const modelPos = editor.model.createPositionAt(item.textNode, "before");
const viewPos = editor.editing.mapper.toViewPosition(modelPos);
const domPos = editor.editing.view.domConverter.viewPositionToDom(viewPos);
if (domPos?.parent instanceof HTMLElement) {
// Get the formatting span's innerHTML (includes math elements)
html = domPos.parent.innerHTML;
}
} catch {
// During change:data events, the view may not be fully synchronized with the model.
// Fall back to using the raw text data.
}
result.push({
id: randomString(),
text: item.data,
text: html,
attrs,
textNode: item.textNode,
offset: item.startOffset
@@ -235,47 +267,65 @@ function ReadOnlyTextHighlightsList() {
/>;
}
function extractHighlightsFromStaticHtml(el: HTMLElement | null) {
export function extractHighlightsFromStaticHtml(el: HTMLElement | null) {
if (!el) return [];
const { color: defaultColor, backgroundColor: defaultBackgroundColor } = getComputedStyle(el);
const walker = document.createTreeWalker(
el,
NodeFilter.SHOW_TEXT,
null
);
const highlights: DomHighlight[] = [];
const processedElements = new Set<Element>();
let node: Node | null;
while ((node = walker.nextNode())) {
const el = node.parentElement;
if (!el || !node.textContent?.trim()) continue;
// Find all elements with inline background-color or color styles
const styledElements = el.querySelectorAll<HTMLElement>('[style*="background-color"], [style*="color"]');
const style = getComputedStyle(el);
for (const styledEl of styledElements) {
if (processedElements.has(styledEl)) continue;
if (!styledEl.textContent?.trim()) continue;
if (
el.closest('strong, em, u') ||
style.color !== defaultColor ||
style.backgroundColor !== defaultBackgroundColor
) {
const attrs: RawHighlight["attrs"] = {
bold: !!el.closest("strong"),
italic: !!el.closest("em"),
underline: !!el.closest("u"),
background: el.style.backgroundColor,
color: el.style.color
};
const attrs: RawHighlight["attrs"] = {
bold: !!styledEl.closest("strong"),
italic: !!styledEl.closest("em"),
underline: !!styledEl.closest("u"),
background: styledEl.style.backgroundColor,
color: styledEl.style.color
};
if (Object.values(attrs).some(Boolean)) {
highlights.push({
id: randomString(),
text: node.textContent,
element: el,
attrs
});
}
if (Object.values(attrs).some(Boolean)) {
processedElements.add(styledEl);
highlights.push({
id: randomString(),
text: styledEl.innerHTML,
element: styledEl,
attrs
});
}
}
// Also find bold, italic, underline elements
const formattingElements = el.querySelectorAll<HTMLElement>("strong, em, u, b, i");
for (const formattedEl of formattingElements) {
// Skip if already processed or inside a processed element
if (processedElements.has(formattedEl)) continue;
if (Array.from(processedElements).some(processed => processed.contains(formattedEl))) continue;
if (!formattedEl.textContent?.trim()) continue;
const attrs: RawHighlight["attrs"] = {
bold: formattedEl.matches("strong, b"),
italic: formattedEl.matches("em, i"),
underline: formattedEl.matches("u"),
background: formattedEl.style.backgroundColor,
color: formattedEl.style.color
};
if (Object.values(attrs).some(Boolean)) {
processedElements.add(formattedEl);
highlights.push({
id: randomString(),
text: formattedEl.innerHTML,
element: formattedEl,
attrs
});
}
}

View File

@@ -5,9 +5,8 @@ import clsx from "clsx";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import math from "../../services/math";
import { randomString } from "../../services/utils";
import { useActiveNoteContext, useContentElement, useGetContextData, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks";
import { useActiveNoteContext, useContentElement, useGetContextData, useIsNoteReadOnly, useMathRendering, useNoteProperty, useTextEditor } from "../react/hooks";
import Icon from "../react/Icon";
import RawHtml from "../react/RawHtml";
import RightPanelWidget from "./RightPanelWidget";
@@ -84,19 +83,7 @@ function TableOfContentsHeading({ heading, scrollToHeading, activeHeadingId }: {
const isActive = heading.id === activeHeadingId;
const contentRef = useRef<HTMLElement>(null);
// Render math equations after component mounts/updates
useEffect(() => {
if (!contentRef.current) return;
const mathElements = contentRef.current.querySelectorAll(".ck-math-tex");
for (const mathEl of mathElements ?? []) {
try {
math.render(mathEl.textContent || "", mathEl as HTMLElement);
} catch (e) {
console.warn("Failed to render math in TOC:", e);
}
}
}, [heading.text]);
useMathRendering(contentRef, [heading.text]);
return (
<>
@@ -273,7 +260,7 @@ function extractTocFromStaticHtml(el: HTMLElement | null) {
headings.push({
id: randomString(),
level: parseInt(headingEl.tagName.substring(1), 10),
text: headingEl.textContent,
text: headingEl.innerHTML,
element: headingEl
});
}

View File

@@ -903,7 +903,7 @@ export default class TabRowWidget extends BasicWidget {
loadResults.isNoteReloaded(noteContext.noteId) ||
loadResults
.getAttributeRows()
.find((attr) => ["workspace", "workspaceIconClass", "workspaceTabBackgroundColor"].includes(attr.name || "") && attributeService.isAffecting(attr, noteContext.note))
.find((attr) => ["workspace", "iconClass", "workspaceIconClass", "workspaceTabBackgroundColor"].includes(attr.name || "") && attributeService.isAffecting(attr, noteContext.note))
) {
const $tab = this.getTabById(noteContext.ntxId);

View File

@@ -3,6 +3,7 @@ import "./appearance.css";
import { FontFamily, OptionNames } from "@triliumnext/commons";
import { useEffect, useState } from "preact/hooks";
import zoomService from "../../../components/zoom";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import { isElectron, isMobile, reloadFrontendApp, restartDesktopApp } from "../../../services/utils";
@@ -14,9 +15,10 @@ import FormGroup from "../../react/FormGroup";
import FormRadioGroup from "../../react/FormRadioGroup";
import FormSelect, { FormSelectWithGroups } from "../../react/FormSelect";
import FormText from "../../react/FormText";
import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
import { FormTextBoxWithUnit } from "../../react/FormTextBox";
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import Icon from "../../react/Icon";
import OptionsRow from "./components/OptionsRow";
import OptionsSection from "./components/OptionsSection";
import PlatformIndicator from "./components/PlatformIndicator";
import RadioWithIllustration from "./components/RadioWithIllustration";
@@ -333,20 +335,23 @@ function Font({ title, fontFamilyOption, fontSizeOption }: { title: string, font
}
function ElectronIntegration() {
const [ zoomFactor, setZoomFactor ] = useTriliumOption("zoomFactor");
const [ zoomFactor ] = useTriliumOption("zoomFactor");
const [ nativeTitleBarVisible, setNativeTitleBarVisible ] = useTriliumOptionBool("nativeTitleBarVisible");
const [ backgroundEffects, setBackgroundEffects ] = useTriliumOptionBool("backgroundEffects");
const zoomPercentage = Math.round(parseFloat(zoomFactor || "1") * 100);
return (
<OptionsSection title={t("electron_integration.desktop-application")}>
<FormGroup name="zoom-factor" label={t("electron_integration.zoom-factor")} description={t("zoom_factor.description")}>
<FormTextBox
<OptionsRow name="zoom-factor" label={t("electron_integration.zoom-factor")} description={t("zoom_factor.description")}>
<FormTextBoxWithUnit
type="number"
min="0.3" max="2.0" step="0.1"
currentValue={zoomFactor} onChange={setZoomFactor}
min={50} max={200} step={10}
currentValue={String(zoomPercentage)}
onChange={(v) => zoomService.setZoomFactorAndSave(parseInt(v, 10) / 100)}
unit={t("units.percentage")}
/>
</FormGroup>
<hr/>
</OptionsRow>
<FormGroup name="native-title-bar" description={t("electron_integration.native-title-bar-description")}>
<FormCheckbox

View File

@@ -1,15 +1,16 @@
import { BackupDatabaseNowResponse, DatabaseBackup } from "@triliumnext/commons";
import { useCallback, useEffect, useState } from "preact/hooks";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast";
import { formatDateTime } from "../../../utils/formatters";
import Button from "../../react/Button";
import FormCheckbox from "../../react/FormCheckbox";
import { FormMultiGroup } from "../../react/FormGroup";
import FormText from "../../react/FormText";
import { useTriliumOptionBool } from "../../react/hooks";
import OptionsSection from "./components/OptionsSection";
import { useCallback, useEffect, useState } from "preact/hooks";
import { formatDateTime } from "../../../utils/formatters";
export default function BackupSettings() {
const [ backups, setBackups ] = useState<DatabaseBackup[]>([]);
@@ -35,7 +36,7 @@ export default function BackupSettings() {
<BackupNow refreshCallback={refreshBackups} />
<BackupList backups={backups} />
</>
)
);
}
export function AutomaticBackup() {
@@ -67,7 +68,7 @@ export function AutomaticBackup() {
<FormText>{t("backup.backup_recommendation")}</FormText>
</OptionsSection>
)
);
}
export function BackupNow({ refreshCallback }: { refreshCallback: () => void }) {
@@ -82,7 +83,7 @@ export function BackupNow({ refreshCallback }: { refreshCallback: () => void })
}}
/>
</OptionsSection>
)
);
}
export function BackupList({ backups }: { backups: DatabaseBackup[] }) {
@@ -92,11 +93,13 @@ export function BackupList({ backups }: { backups: DatabaseBackup[] }) {
<colgroup>
<col width="33%" />
<col />
<col width="1%" />
</colgroup>
<thead>
<tr>
<th>{t("backup.date-and-time")}</th>
<th>{t("backup.path")}</th>
<th />
</tr>
</thead>
<tbody>
@@ -105,15 +108,20 @@ export function BackupList({ backups }: { backups: DatabaseBackup[] }) {
<tr>
<td>{mtime ? formatDateTime(mtime) : "-"}</td>
<td className="selectable-text">{filePath}</td>
<td>
<a href={`api/database/backup/download?filePath=${encodeURIComponent(filePath)}`} download>
<Button text={t("backup.download")} />
</a>
</td>
</tr>
))
) : (
<tr>
<td className="empty-table-placeholder" colspan={2}>{t("backup.no_backup_yet")}</td>
<td className="empty-table-placeholder" colspan={3}>{t("backup.no_backup_yet")}</td>
</tr>
)}
</tbody>
</table>
</OptionsSection>
);
}
);
}

View File

@@ -45,3 +45,25 @@
.option-row.centered {
justify-content: center;
}
.option-row.stacked {
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.option-row.stacked .option-row-input {
width: 100%;
}
.option-row-link.use-tn-links {
text-decoration: none;
color: inherit;
margin-inline: calc(-1 * var(--options-card-padding, 15px));
padding-inline: var(--options-card-padding, 15px);
transition: background-color 250ms ease-in-out;
}
.option-row-link:hover {
background: var(--hover-item-background-color);
}

View File

@@ -1,5 +1,7 @@
import { cloneElement, VNode } from "preact";
import "./OptionsRow.css";
import { cloneElement, VNode } from "preact";
import { useUniqueName } from "../../../react/hooks";
interface OptionsRowProps {
@@ -8,14 +10,18 @@ interface OptionsRowProps {
description?: string;
children: VNode;
centered?: boolean;
/** When true, stacks label above input with full-width input */
stacked?: boolean;
}
export default function OptionsRow({ name, label, description, children, centered }: OptionsRowProps) {
export default function OptionsRow({ name, label, description, children, centered, stacked }: OptionsRowProps) {
const id = useUniqueName(name);
const childWithId = cloneElement(children, { id });
const className = `option-row ${centered ? "centered" : ""} ${stacked ? "stacked" : ""}`;
return (
<div className={`option-row ${centered ? "centered" : ""}`}>
<div className={className}>
<div className="option-row-label">
{label && <label for={id}>{label}</label>}
{description && <small className="option-row-description">{description}</small>}
@@ -25,4 +31,24 @@ export default function OptionsRow({ name, label, description, children, centere
</div>
</div>
);
}
}
interface OptionsRowLinkProps {
label: string;
description?: string;
href: string;
}
export function OptionsRowLink({ label, description, href }: OptionsRowLinkProps) {
return (
<a href={href} className="option-row option-row-link use-tn-links no-tooltip-preview">
<div className="option-row-label">
<label style={{ cursor: "pointer" }}>{label}</label>
{description && <small className="option-row-description">{description}</small>}
</div>
<div className="option-row-input">
<span className="bx bx-chevron-right" />
</div>
</a>
);
}

View File

@@ -1,24 +1,36 @@
import OptionsSection from "./OptionsSection";
import type { OptionPages } from "../../ContentWidget";
import { t } from "../../../../services/i18n";
import type { OptionPages } from "../../ContentWidget";
import { OptionsRowLink } from "./OptionsRow";
import OptionsSection from "./OptionsSection";
interface RelatedSettingsItem {
title: string;
description?: string;
targetPage: OptionPages;
enabled?: boolean;
}
interface RelatedSettingsProps {
items: {
title: string;
targetPage: OptionPages;
}[];
items: RelatedSettingsItem[];
}
export default function RelatedSettings({ items }: RelatedSettingsProps) {
const filteredItems = items.filter(item => item.enabled !== false);
if (filteredItems.length === 0) {
return null;
}
return (
<OptionsSection title={t("settings.related_settings")}>
<nav className="use-tn-links" style={{ padding: 0, margin: 0, listStyleType: "none" }}>
{items.map(item => (
<li>
<a href={`#root/_hidden/_options/${item.targetPage}`}>{item.title}</a>
</li>
))}
</nav>
{filteredItems.map((item) => (
<OptionsRowLink
key={item.targetPage}
label={item.title}
description={item.description}
href={`#root/_hidden/_options/${item.targetPage}`}
/>
))}
</OptionsSection>
);
}

View File

@@ -5,13 +5,14 @@ import OptionsRow from "./components/OptionsRow";
import OptionsSection from "./components/OptionsSection";
import { useTriliumOption, useTriliumOptionJson } from "../../react/hooks";
import type { Locale } from "@triliumnext/commons";
import { restartDesktopApp } from "../../../services/utils";
import { isElectron, restartDesktopApp } from "../../../services/utils";
import FormRadioGroup from "../../react/FormRadioGroup";
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";
import RelatedSettings from "./components/RelatedSettings";
import { LocaleSelector } from "./components/LocaleSelector";
export default function InternationalizationOptions() {
@@ -19,8 +20,17 @@ export default function InternationalizationOptions() {
<>
<LocalizationOptions />
<ContentLanguages />
{isElectron() && (
<RelatedSettings items={[
{
title: t("spellcheck.title"),
description: t("spellcheck.related_description"),
targetPage: "_optionsSpellcheck"
}
]} />
)}
</>
)
);
}
function LocalizationOptions() {

View File

@@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast";
import { isElectron } from "../../../services/utils";
import { FormTextBoxWithUnit } from "../../react/FormTextBox";
import FormToggle from "../../react/FormToggle";
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
@@ -93,7 +94,8 @@ function OcrSettings() {
<RelatedSettings items={[
{
title: t("images.ocr_related_content_languages"),
targetPage: "_optionsLocalization"
targetPage: "_optionsLocalization",
enabled: isElectron(), // This setting is only relevant for desktop, as web browsers use their own native OCR which doesn't support language selection.
}
]} />
</>

View File

@@ -1,63 +1,132 @@
import { useMemo } from "preact/hooks";
import { useCallback, useMemo } from "preact/hooks";
import appContext from "../../../components/app_context";
import { t } from "../../../services/i18n";
import FormCheckbox from "../../react/FormCheckbox";
import FormGroup from "../../react/FormGroup";
import { dynamicRequire, isElectron, restartDesktopApp } from "../../../services/utils";
import Button from "../../react/Button";
import FormText from "../../react/FormText";
import FormTextBox from "../../react/FormTextBox";
import FormToggle from "../../react/FormToggle";
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
import NoItems from "../../react/NoItems";
import CheckboxList from "./components/CheckboxList";
import OptionsRow from "./components/OptionsRow";
import OptionsSection from "./components/OptionsSection";
import { dynamicRequire, isElectron } from "../../../services/utils";
export default function SpellcheckSettings() {
if (isElectron()) {
return <ElectronSpellcheckSettings />
} else {
return <WebSpellcheckSettings />
return <ElectronSpellcheckSettings />;
}
return <WebSpellcheckSettings />;
}
interface SpellcheckLanguage {
code: string;
name: string;
}
function ElectronSpellcheckSettings() {
const [ spellCheckEnabled, setSpellCheckEnabled ] = useTriliumOptionBool("spellCheckEnabled");
return (
<>
<OptionsSection title={t("spellcheck.title")}>
<FormText>{t("spellcheck.restart-required")}</FormText>
<OptionsRow name="spell-check-enabled" label={t("spellcheck.enable")}>
<FormToggle
switchOnName="" switchOffName=""
currentValue={spellCheckEnabled}
onChange={setSpellCheckEnabled}
/>
</OptionsRow>
<OptionsRow name="restart" centered>
<Button
name="restart-app-button"
text={t("electron_integration.restart-app-button")}
size="micro"
onClick={restartDesktopApp}
/>
</OptionsRow>
</OptionsSection>
{spellCheckEnabled && <SpellcheckLanguages />}
{spellCheckEnabled && <CustomDictionary />}
</>
);
}
function SpellcheckLanguages() {
const [ spellCheckLanguageCode, setSpellCheckLanguageCode ] = useTriliumOption("spellCheckLanguageCode");
const availableLanguageCodes = useMemo(() => {
const selectedCodes = useMemo(() =>
(spellCheckLanguageCode ?? "")
.split(",")
.map((c) => c.trim())
.filter((c) => c.length > 0),
[spellCheckLanguageCode]
);
const setSelectedCodes = useCallback((codes: string[]) => {
setSpellCheckLanguageCode(codes.join(", "));
}, [setSpellCheckLanguageCode]);
const availableLanguages = useMemo<SpellcheckLanguage[]>(() => {
if (!isElectron()) {
return [];
}
const { webContents } = dynamicRequire("@electron/remote").getCurrentWindow();
return webContents.session.availableSpellCheckerLanguages as string[];
}, [])
const { webContents } = dynamicRequire("@electron/remote").getCurrentWindow();
const codes = webContents.session.availableSpellCheckerLanguages as string[];
const displayNames = new Intl.DisplayNames([navigator.language], { type: "language" });
return codes.map((code) => ({
code,
name: displayNames.of(code) ?? code
})).sort((a, b) => a.name.localeCompare(b.name));
}, []);
return (
<OptionsSection title={t("spellcheck.title")}>
<FormText>{t("spellcheck.restart-required")}</FormText>
<FormCheckbox
name="spell-check-enabled"
label={t("spellcheck.enable")}
currentValue={spellCheckEnabled} onChange={setSpellCheckEnabled}
<OptionsSection title={t("spellcheck.language_code_label")}>
<CheckboxList
values={availableLanguages}
keyProperty="code" titleProperty="name"
currentValue={selectedCodes}
onChange={setSelectedCodes}
columnWidth="200px"
/>
<FormGroup name="spell-check-languages" label={t("spellcheck.language_code_label")} description={t("spellcheck.multiple_languages_info")}>
<FormTextBox
placeholder={t("spellcheck.language_code_placeholder")}
currentValue={spellCheckLanguageCode} onChange={setSpellCheckLanguageCode}
/>
</FormGroup>
<FormText>
<strong>{t("spellcheck.available_language_codes_label")} </strong>
{availableLanguageCodes.join(", ")}
</FormText>
</OptionsSection>
)
);
}
function CustomDictionary() {
function openDictionary() {
appContext.triggerCommand("openInPopup", { noteIdOrPath: "_customDictionary" });
}
return (
<OptionsSection title={t("spellcheck.custom_dictionary_title")}>
<FormText>{t("spellcheck.custom_dictionary_description")}</FormText>
<OptionsRow name="custom-dictionary" label={t("spellcheck.custom_dictionary_edit")} description={t("spellcheck.custom_dictionary_edit_description")}>
<Button
name="open-custom-dictionary"
text={t("spellcheck.custom_dictionary_open")}
icon="bx bx-edit"
onClick={openDictionary}
/>
</OptionsRow>
</OptionsSection>
);
}
function WebSpellcheckSettings() {
return (
<OptionsSection title={t("spellcheck.title")}>
<p>{t("spellcheck.description")}</p>
<OptionsSection>
<NoItems
text={t("spellcheck.description")}
icon="bx bx-check-double"
/>
</OptionsSection>
)
}
);
}

View File

@@ -1,16 +1,19 @@
import { SyncTestResponse } from "@triliumnext/commons";
import { useRef } from "preact/hooks";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast";
import { openInAppHelpFromUrl } from "../../../services/utils";
import Button from "../../react/Button";
import FormGroup from "../../react/FormGroup";
import FormTextBox, { FormTextBoxWithUnit } from "../../react/FormTextBox";
import RawHtml from "../../react/RawHtml";
import OptionsSection from "./components/OptionsSection";
import { useTriliumOptions } from "../../react/hooks";
import FormText from "../../react/FormText";
import server from "../../../services/server";
import toast from "../../../services/toast";
import { SyncTestResponse } from "@triliumnext/commons";
import FormTextBox from "../../react/FormTextBox";
import { useTriliumOptions } from "../../react/hooks";
import RawHtml from "../../react/RawHtml";
import OptionsRow from "./components/OptionsRow";
import OptionsSection from "./components/OptionsSection";
import TimeSelector from "./components/TimeSelector";
export default function SyncOptions() {
return (
@@ -18,13 +21,12 @@ export default function SyncOptions() {
<SyncConfiguration />
<SyncTest />
</>
)
);
}
export function SyncConfiguration() {
const [ options, setOptions ] = useTriliumOptions("syncServerHost", "syncServerTimeout", "syncProxy");
const [ options, setOptions ] = useTriliumOptions("syncServerHost", "syncProxy");
const syncServerHost = useRef(options.syncServerHost);
const syncServerTimeout = useRef(options.syncServerTimeout);
const syncProxy = useRef(options.syncProxy);
return (
@@ -32,13 +34,12 @@ export function SyncConfiguration() {
<form onSubmit={(e) => {
setOptions({
syncServerHost: syncServerHost.current,
syncServerTimeout: syncServerTimeout.current,
syncProxy: syncProxy.current
});
e.preventDefault();
}}>
<FormGroup name="sync-server-host" label={t("sync_2.server_address")}>
<FormTextBox
<FormTextBox
placeholder="https://<host>:<port>"
currentValue={syncServerHost.current} onChange={(newValue) => syncServerHost.current = newValue}
/>
@@ -50,27 +51,30 @@ export function SyncConfiguration() {
<RawHtml html={t("sync_2.special_value_description")} />
</>}
>
<FormTextBox
<FormTextBox
placeholder="https://<host>:<port>"
currentValue={syncProxy.current} onChange={(newValue) => syncProxy.current = newValue}
/>
</FormGroup>
<FormGroup name="sync-server-timeout" label={t("sync_2.timeout")}>
<FormTextBoxWithUnit
min={1} max={10000000} type="number"
unit={t("sync_2.timeout_unit")}
currentValue={syncServerTimeout.current} onChange={(newValue) => syncServerTimeout.current = newValue}
/>
</FormGroup>
<div style={{ display: "flex", justifyContent: "spaceBetween"}}>
<Button text={t("sync_2.save")} kind="primary" />
<Button text={t("sync_2.help")} onClick={() => openInAppHelpFromUrl("cbkrhQjrkKrh")} />
</div>
</form>
<hr/>
<OptionsRow name="sync-server-timeout" label={t("sync_2.timeout")} description={t("sync_2.timeout_description")}>
<TimeSelector
name="sync-server-timeout"
optionValueId="syncServerTimeout"
optionTimeScaleId="syncServerTimeoutTimeScale"
minimumSeconds={1}
/>
</OptionsRow>
</OptionsSection>
)
);
}
export function SyncTest() {
@@ -90,5 +94,5 @@ export function SyncTest() {
}}
/>
</OptionsSection>
)
}
);
}

View File

@@ -8,13 +8,11 @@ import { HTMLProps } from "preact/compat";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import FNote from "../../../entities/fnote";
import attribute_autocomplete from "../../../services/attribute_autocomplete";
import dialog from "../../../services/dialog";
import { isExperimentalFeatureEnabled } from "../../../services/experimental_features";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast";
import utils from "../../../services/utils";
import ActionButton from "../../react/ActionButton";
import { useEditorSpacedUpdate, useTriliumEvent, useTriliumEvents } from "../../react/hooks";
import { TypeWidgetProps } from "../type_widget";
@@ -23,7 +21,7 @@ import { buildRelationContextMenuHandler } from "./context_menu";
import { JsPlumb } from "./jsplumb";
import { NoteBox } from "./NoteBox";
import setupOverlays, { uniDirectionalOverlays } from "./overlays";
import { getMousePosition, getZoom, idToNoteId, noteIdToId } from "./utils";
import { getMousePosition, getZoom, idToNoteId, noteIdToId, promptForRelationName } from "./utils";
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
@@ -415,27 +413,7 @@ function useRelationCreation({ mapApiRef, jsPlumbApiRef }: { mapApiRef: RefObjec
// if there's no event, then this has been triggered programmatically
if (!originalEvent || !mapApiRef.current) return;
const name = await dialog.prompt({
message: t("relation_map.specify_new_relation_name"),
shown: ({ $answer }) => {
if (!$answer) {
return;
}
$answer.on("keyup", () => {
// invalid characters are simply ignored (from user perspective they are not even entered)
const attrName = utils.filterAttributeName($answer.val() as string);
$answer.val(attrName);
});
attribute_autocomplete.initAttributeNameAutocomplete({
$el: $answer,
attributeType: "relation",
open: true
});
}
});
const name = await promptForRelationName();
// Delete the newly created connection if the dialog was dismissed.
if (!name || !name.trim()) {

View File

@@ -75,6 +75,29 @@ export default class RelationMapApi {
this.onDataChange(true);
}
async renameRelation(connection: Connection, newName: string) {
newName = utils.filterAttributeName(newName);
const relation = this.relations.find((rel) => rel.attributeId === connection.id);
if (!relation) return false;
// Check if a relation with the new name already exists between these notes.
const exists = this.relations.some(
(rel) => rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId && rel.name === newName
);
if (exists) return false;
await server.put(`notes/${relation.sourceNoteId}/relations/${newName}/to/${relation.targetNoteId}`);
await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`);
this.onDataChange(true);
return true;
}
getRelationName(connection: Connection): string | undefined {
const relation = this.relations.find((rel) => rel.attributeId === connection.id);
return relation?.name;
}
cleanupOtherNotes(noteIds: string[]) {
const filteredNotes = this.data.notes.filter((note) => noteIds.includes(note.noteId));
if (filteredNotes.length === this.data.notes.length) return;

View File

@@ -9,6 +9,7 @@ import dialog from "../../../services/dialog";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import RelationMapApi from "./api";
import { promptForRelationName } from "./utils";
export function buildNoteContextMenuHandler(note: FNote | null | undefined, mapApiRef: RefObject<RelationMapApi>) {
return (e: MouseEvent) => {
@@ -73,9 +74,25 @@ export function buildRelationContextMenuHandler(connection: Connection, mapApiRe
contextMenu.show({
x: event.pageX,
y: event.pageY,
items: [{ title: t("relation_map.remove_relation"), command: "remove", uiIcon: "bx bx-trash" }],
items: [
{ title: t("relation_map.rename_relation"), command: "rename", uiIcon: "bx bx-pencil" },
{ kind: "separator" },
{ title: t("relation_map.remove_relation"), command: "remove", uiIcon: "bx bx-trash" }
],
selectMenuItemHandler: async ({ command }) => {
if (command === "remove") {
if (command === "rename") {
const currentName = mapApiRef.current?.getRelationName(connection) ?? "";
const newName = await promptForRelationName(currentName);
if (!newName?.trim() || newName === currentName) {
return;
}
const result = await mapApiRef.current?.renameRelation(connection, newName);
if (!result) {
await dialog.info(t("relation_map.connection_exists", { name: newName }));
}
} else if (command === "remove") {
if (!(await dialog.confirm(t("relation_map.confirm_remove_relation")))) {
return;
}

View File

@@ -1,4 +1,7 @@
import attribute_autocomplete from "../../../services/attribute_autocomplete";
import dialog from "../../../services/dialog";
import { t } from "../../../services/i18n";
import utils from "../../../services/utils";
export function noteIdToId(noteId: string) {
return `rel-map-note-${noteId}`;
@@ -32,3 +35,26 @@ export function getMousePosition(evt: MouseEvent, container: HTMLDivElement, zoo
y: ((evt.clientY ?? 0) - rect.top) / zoom
};
}
export function promptForRelationName(defaultValue?: string): Promise<string | null> {
return dialog.prompt({
message: t("relation_map.specify_new_relation_name"),
defaultValue,
shown: ({ $answer }) => {
if (!$answer) {
return;
}
$answer.on("keyup", () => {
const attrName = utils.filterAttributeName($answer.val() as string);
$answer.val(attrName);
});
attribute_autocomplete.initAttributeNameAutocomplete({
$el: $answer,
attributeType: "relation",
open: true
});
}
});
}

View File

@@ -7,7 +7,7 @@ import link from "../../../services/link";
import { useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteContext, useSyncedRef, useTriliumOption } from "../../react/hooks";
import { buildConfig, BuildEditorOptions } from "./config";
export type BoxSize = "small" | "medium" | "full";
export type BoxSize = "small" | "medium" | "full" | "expandable";
export interface CKEditorApi {
/** returns true if user selected some text, false if there's no selection */

View File

@@ -55,4 +55,14 @@ body.mobile .note-detail-readonly-text {
.edit-text-note-button:hover {
border-color: var(--button-border-color);
}
/* Inline code click-to-copy */
.note-detail-readonly-text-content code.copyable-inline-code {
cursor: pointer;
transition: background-color 0.15s ease;
}
.note-detail-readonly-text-content code.copyable-inline-code:hover {
background-color: var(--accented-background-color);
}

View File

@@ -182,9 +182,21 @@ export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfi
marker: "@",
feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
itemRenderer: (item) => {
const suggestion = item as Suggestion;
const itemElement = document.createElement("button");
itemElement.innerHTML = `${(item as Suggestion).highlightedNotePathTitle} `;
const iconElement = document.createElement("span");
// Choose appropriate icon based on action
let iconClass = suggestion.icon ?? "bx bx-note";
if (suggestion.action === "create-note") {
iconClass = "bx bx-plus";
}
iconElement.className = iconClass;
itemElement.append(iconElement, document.createTextNode(" "));
const titleContainer = document.createElement("span");
titleContainer.innerHTML = suggestion.highlightedNotePathTitle ?? "";
itemElement.append(...titleContainer.childNodes, document.createTextNode(" "));
return itemElement;
},

View File

@@ -8,17 +8,77 @@ export async function loadIncludedNote(noteId: string, $el: JQuery<HTMLElement>)
const note = await froca.getNote(noteId);
if (!note) return;
// Get the box size from the parent section element
const $section = $el.closest('section.include-note');
const boxSize = $section.attr('data-box-size');
const isExpandable = boxSize === 'expandable';
const $wrapper = $('<div class="include-note-wrapper">');
const $link = await link.createLink(note.noteId, {
showTooltip: false
});
$wrapper.empty().append($('<h4 class="include-note-title">').append($link));
if (isExpandable) {
// Create expandable structure with toggle
const $titleRow = $('<div class="include-note-title-row">');
const $toggle = $('<button class="include-note-toggle bx bx-chevron-right" aria-expanded="false">');
const $title = $('<h4 class="include-note-title">').append($link);
const { $renderedContent, type } = await content_renderer.getRenderedContent(note);
$wrapper.append($(`<div class="include-note-content type-${type}">`).append($renderedContent));
$titleRow.append($toggle, $title);
$wrapper.append($titleRow);
const { $renderedContent, type } = await content_renderer.getRenderedContent(note);
const $content = $(`<div class="include-note-content type-${type}" style="display: none;">`).append($renderedContent);
$wrapper.append($content);
// Add toggle functionality
$toggle.on('click', (e) => {
e.stopPropagation();
const isExpanded = $toggle.attr('aria-expanded') === 'true';
$toggle.attr('aria-expanded', String(!isExpanded));
$toggle.toggleClass('expanded');
$content.slideToggle(200);
});
} else {
// Standard display
$wrapper.append($('<h4 class="include-note-title">').append($link));
const { $renderedContent, type } = await content_renderer.getRenderedContent(note);
$wrapper.append($(`<div class="include-note-content type-${type}">`).append($renderedContent));
}
$el.empty().append($wrapper);
// Watch for box-size attribute changes and re-render
setupBoxSizeObserver($section[0], noteId, $el);
}
// Track observers to avoid duplicates
const boxSizeObservers = new WeakMap<Element, MutationObserver>();
function setupBoxSizeObserver(section: Element, noteId: string, $el: JQuery<HTMLElement>) {
// Clean up existing observer if any
const existingObserver = boxSizeObservers.get(section);
if (existingObserver) {
existingObserver.disconnect();
}
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'data-box-size') {
// Re-render the included note with the new box size
loadIncludedNote(noteId, $el);
break;
}
}
});
observer.observe(section, {
attributes: true,
attributeFilter: ['data-box-size']
});
boxSizeObservers.set(section, observer);
}
export function refreshIncludedNote(container: HTMLDivElement, noteId: string) {

File diff suppressed because it is too large Load Diff

View File

@@ -538,6 +538,9 @@
<li><a href="root/Trilium%20Demo/Scripting%20examples/Statistics/Attribute%20count/template/js/renderPieChart.js"
target="detail">renderPieChart</a>
<ul>
<li><a href="root/Trilium%20Demo/Scripting%20examples/Weight%20Tracker/Implementation/JS%20code/chart.js"
target="detail">chart.js</a>
</li>
<li><a href="root/Trilium%20Demo/Scripting%20examples/Statistics/Attribute%20count/template/js/renderPieChart/chartjs-plugin-datalabe.min.js"
target="detail">chartjs-plugin-datalabels.min.js</a>
</li>

View File

@@ -25,9 +25,7 @@
You can play with it, and modify the note content and tree structure as
you wish.</p>
<p>If you need any help, visit <a href="https://triliumnotes.org">triliumnotes.org</a> or
our <a href="https://github.com/TriliumNext">GitHub repository</a>
</p>
our <a href="https://github.com/TriliumNext">GitHub repository</a>.</p>
<h2>Cleanup</h2>
<p>Once you're finished with experimenting and want to cleanup these pages,
@@ -35,7 +33,7 @@
<h2>Formatting</h2>
<p>Trilium supports classic formatting like <em>italic</em>, <strong>bold</strong>, <em><strong>bold and italic</strong></em>.
You can add links pointing to <a href="https://triliumnotes.org/">external pages</a> or&nbsp;
You can add links pointing to <a href="https://triliumnotes.org/">external pages</a> or&nbsp;&nbsp;
<a
class="reference-link" href="Trilium%20Demo/Formatting%20examples">Formatting examples</a>.</p>
<h3>Lists</h3>
@@ -75,9 +73,8 @@
<hr>
<p>See also other examples like <a href="Trilium%20Demo/Formatting%20examples/School%20schedule.html">tables</a>,
<a
href="Trilium%20Demo/Formatting%20examples/Checkbox%20lists.html">checkbox lists,</a> <a href="Trilium%20Demo/Formatting%20examples/Highlighting.html">highlighting</a>, <a href="Trilium%20Demo/Formatting%20examples/Code%20blocks.html">code blocks</a>and
<a
href="Trilium%20Demo/Formatting%20examples/Math.html">math examples</a>.</p>
href="Trilium%20Demo/Formatting%20examples/Checkbox%20lists.html">checkbox lists</a>, <a href="Trilium%20Demo/Formatting%20examples/Highlighting.html">highlighting</a>, <a href="Trilium%20Demo/Formatting%20examples/Code%20blocks.html">code blocks</a>,
and <a href="Trilium%20Demo/Formatting%20examples/Math.html">math examples</a>.</p>
</div>
</div>
</body>

View File

@@ -31,7 +31,7 @@
<h2>Similar books</h2>
<ul>
<li></li>
<li data-list-item-id="eebd9f297d5dc97dfc46579ba1f25d7bf"></li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../../../../../../../style.css">
<base target="_parent">
<title data-trilium-title>chart.js</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>chart.js</h1>
<div class="ck-content">
<p>This is a clone of a note. Go to its <a href="../../../../../Weight%20Tracker/Implementation/JS%20code/chart.js">primary location</a>.</p>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,11 @@
import { extractZip, importData, initializeDatabase, startElectron } from "./utils.js";
import { createZipFromDirectory, extractZip, importData, initializeDatabase, startElectron } from "./utils.js";
import { initializeTranslations } from "@triliumnext/server/src/services/i18n.js";
import debounce from "@triliumnext/client/src/services/debounce.js";
import fs from "fs/promises";
import { join } from "path";
import cls from "@triliumnext/server/src/services/cls.js";
import type { NoteMetaFile } from "@triliumnext/server/src/services/meta/note_meta.js";
import type NoteMeta from "@triliumnext/server/src/services/meta/note_meta.js";
// Paths are relative to apps/edit-docs/dist.
const DEMO_ZIP_PATH = join(__dirname, "../../server/src/assets/db/demo.zip");
@@ -17,20 +19,29 @@ async function main() {
await initializeTranslations();
await initializeDatabase(true);
// Wait for becca to be loaded before importing data
const beccaLoader = await import("@triliumnext/server/src/becca/becca_loader.js");
await beccaLoader.beccaLoaded;
cls.init(async () => {
await importData(DEMO_ZIP_DIR_PATH);
setOptions();
initializedPromise.resolve();
});
initializedPromise.resolve();
}
async function setOptions() {
const optionsService = (await import("@triliumnext/server/src/services/options.js")).default;
const sql = (await import("@triliumnext/server/src/services/sql.js")).default;
optionsService.setOption("eraseUnusedAttachmentsAfterSeconds", 10);
optionsService.setOption("eraseUnusedAttachmentsAfterTimeScale", 60);
optionsService.setOption("compressImages", "false");
// Set initial note to the first visible child of root (not _hidden)
const startNoteId = sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 AND noteId != '_hidden' ORDER BY notePosition") || "root";
optionsService.setOption("openNoteContexts", JSON.stringify([{ notePath: startNoteId, active: true }]));
}
async function registerHandlers() {
@@ -41,8 +52,10 @@ async function registerHandlers() {
eraseService.eraseUnusedAttachmentsNow();
await exportData();
await fs.rmdir(DEMO_ZIP_DIR_PATH, { recursive: true }).catch(() => {});
await fs.rm(DEMO_ZIP_DIR_PATH, { recursive: true }).catch(() => {});
await extractZip(DEMO_ZIP_PATH, DEMO_ZIP_DIR_PATH);
await cleanUpMeta(DEMO_ZIP_DIR_PATH);
await createZipFromDirectory(DEMO_ZIP_DIR_PATH, DEMO_ZIP_PATH);
}, 10_000);
events.subscribe(events.ENTITY_CHANGED, async (e) => {
if (e.entityName === "options") {
@@ -59,4 +72,28 @@ async function exportData() {
await exportToZipFile("root", "html", DEMO_ZIP_PATH);
}
const EXPANDED_NOTE_IDS = new Set([
"root",
"rvaX6hEaQlmk" // Trilium Demo
]);
async function cleanUpMeta(dirPath: string) {
const metaPath = join(dirPath, "!!!meta.json");
const meta = JSON.parse(await fs.readFile(metaPath, "utf-8")) as NoteMetaFile;
for (const file of meta.files) {
file.notePosition = 1;
traverse(file);
}
function traverse(el: NoteMeta) {
el.isExpanded = EXPANDED_NOTE_IDS.has(el.noteId);
for (const child of el.children || []) {
traverse(child);
}
}
await fs.writeFile(metaPath, JSON.stringify(meta, null, 4));
}
main();

View File

@@ -141,9 +141,15 @@ async function main() {
async function setOptions() {
const optionsService = (await import("@triliumnext/server/src/services/options.js")).default;
const sql = (await import("@triliumnext/server/src/services/sql.js")).default;
optionsService.setOption("eraseUnusedAttachmentsAfterSeconds", 10);
optionsService.setOption("eraseUnusedAttachmentsAfterTimeScale", 60);
optionsService.setOption("compressImages", "false");
// Set initial note to the first visible child of root (not _hidden)
const startNoteId = sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 AND noteId != '_hidden' ORDER BY notePosition") || "root";
optionsService.setOption("openNoteContexts", JSON.stringify([{ notePath: startNoteId, active: true }]));
}
async function exportData(noteId: string, format: ExportFormat, outputPath: string, ignoredFiles?: Set<string>) {

View File

@@ -103,6 +103,14 @@ function waitForEnd(archive: Archiver, stream: WriteStream) {
});
}
export async function createZipFromDirectory(dirPath: string, zipPath: string) {
const archive = archiver("zip", { zlib: { level: 5 } });
const outputStream = fsExtra.createWriteStream(zipPath);
archive.directory(dirPath, false);
archive.pipe(outputStream);
await waitForEnd(archive, outputStream);
}
export async function extractZip(zipFilePath: string, outputPath: string, ignoredFiles?: Set<string>) {
const promise = deferred<void>();
setTimeout(async () => {

View File

@@ -6,6 +6,6 @@
"e2e": "playwright test"
},
"devDependencies": {
"dotenv": "17.4.0"
"dotenv": "17.4.1"
}
}

View File

@@ -1,4 +1,5 @@
import test, { expect } from "@playwright/test";
import App from "./support/app";
test("Native Title Bar not displayed on web", async ({ page, context }) => {
@@ -18,8 +19,6 @@ test("Tray settings not displayed on web", async ({ page, context }) => {
test("Spellcheck settings not displayed on web", async ({ page, context }) => {
const app = new App(page, context);
await app.goto({ url: "http://localhost:8082/#root/_hidden/_options/_optionsSpellcheck" });
await expect(app.currentNoteSplitContent.getByRole("heading", { name: "Spell Check" })).toBeVisible();
await expect(app.currentNoteSplitContent.getByRole("heading", { name: "Tray" })).toBeHidden();
await expect(app.currentNoteSplitContent.getByText("These options apply only for desktop builds")).toBeVisible();
await expect(app.currentNoteSplitContent.getByText("Enable spellcheck")).toBeHidden();
await expect(app.currentNoteSplitContent.getByText("Check spelling")).toBeHidden();
});

View File

@@ -30,11 +30,11 @@
"proxy-nginx-subdir": "docker run --name trilium-nginx-subdir --rm --network=host -v ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro nginx:latest"
},
"dependencies": {
"@ai-sdk/anthropic": "3.0.66",
"@ai-sdk/google": "3.0.58",
"@ai-sdk/openai": "3.0.50",
"@ai-sdk/anthropic": "3.0.68",
"@ai-sdk/google": "3.0.60",
"@ai-sdk/openai": "3.0.52",
"@modelcontextprotocol/sdk": "^1.12.1",
"ai": "6.0.146",
"ai": "6.0.153",
"better-sqlite3": "12.8.0",
"html-to-text": "9.0.5",
"js-yaml": "4.1.1",
@@ -78,7 +78,6 @@
"@types/xml2js": "0.4.14",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"axios": "1.14.0",
"chardet": "2.1.1",
"cheerio": "1.2.0",
"chokidar": "5.0.0",
@@ -110,8 +109,8 @@
"ini": "6.0.0",
"is-animated": "2.0.2",
"is-svg": "6.1.0",
"jimp": "1.6.0",
"marked": "17.0.5",
"jimp": "1.6.1",
"marked": "18.0.0",
"mime-types": "3.0.2",
"multer": "2.1.1",
"normalize-strings": "1.1.1",
@@ -131,7 +130,7 @@
"tmp": "0.2.5",
"turnish": "1.8.0",
"unescape": "1.0.1",
"vite": "8.0.5",
"vite": "8.0.7",
"ws": "8.20.0",
"xml2js": "0.6.2",
"yauzl": "3.3.0"

View File

@@ -51,7 +51,8 @@ VERSION=`jq -r ".version" package.json`
ARCHIVE_NAME="TriliumNotes-Server-${VERSION}-linux-${ARCH}"
echo "Creating Archive $ARCHIVE_NAME..."
mkdir $DIST_DIR
rm -rf $DIST_DIR
mkdir -p $DIST_DIR
cp -r "$BUILD_DIR" "$DIST_DIR/$ARCHIVE_NAME"
cd $DIST_DIR
tar cJf "$ARCHIVE_NAME.tar.xz" "$ARCHIVE_NAME"

Some files were not shown because too many files have changed in this diff Show More