Merge remote-tracking branch 'origin/main' into HEAD

; Conflicts:
;	apps/client/src/index.ts
;	apps/client/src/widgets/sql_table_schemas.tsx
;	apps/server/package.json
;	apps/server/src/app.ts
;	apps/server/src/becca/entities/bnote.ts
;	apps/server/src/services/import/single.ts
;	apps/server/src/services/import/zip.ts
;	apps/server/src/services/note-interface.ts
;	apps/server/src/services/notes.ts
;	apps/server/src/services/tree.ts
;	apps/server/src/services/utils.ts
;	apps/server/src/share/shaca/entities/snote.ts
;	pnpm-lock.yaml
;	scripts/update-nightly-version.ts
;	scripts/update-version.ts
This commit is contained in:
Elian Doran 2026-01-29 21:47:06 +02:00
commit da7a61a8b6
No known key found for this signature in database
225 changed files with 10374 additions and 8054 deletions

30
.github/workflows/i18n.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Internationalization
on:
push:
branches:
- "weblate:*"
workflow_dispatch:
pull_request:
paths:
- "apps/client/src/translations/**"
- ".github/workflows/i18n.yml"
permissions:
contents: read
jobs:
i18n-check:
name: Check i18n translations
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check translations
run: pnpm tsx scripts/translation/check-translation-coverage.ts

69
.github/workflows/web-clipper.yml vendored Normal file
View File

@ -0,0 +1,69 @@
name: Deploy web clipper extension
on:
push:
branches:
- main
paths:
- "apps/web-clipper/**"
tags:
- "web-clipper-v*"
pull_request:
paths:
- "apps/web-clipper/**"
permissions:
contents: write
discussions: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
name: Build web clipper extension
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
node-version: 24
cache: "pnpm"
- name: Install dependencies
run: pnpm install --filter web-clipper --frozen-lockfile --ignore-scripts
- name: Build the web clipper extension
run: |
pnpm --filter web-clipper zip
pnpm --filter web-clipper zip:firefox
- name: Upload build artifacts
uses: actions/upload-artifact@v6
if: ${{ !startsWith(github.ref, 'refs/tags/web-clipper-v') }}
with:
name: web-clipper-extension
path: apps/web-clipper/.output/*.zip
include-hidden-files: true
if-no-files-found: error
compression-level: 0
- name: Release web clipper extension
uses: softprops/action-gh-release@v2.5.0
if: ${{ startsWith(github.ref, 'refs/tags/web-clipper-v') }}
with:
draft: false
fail_on_unmatched_files: true
files: apps/web-clipper/.output/*.zip
discussion_category_name: Releases
make_latest: false
token: ${{ secrets.RELEASE_PAT }}

View File

@ -34,7 +34,7 @@ jobs:
cache: "pnpm"
- name: Install dependencies
run: pnpm install --filter website --frozen-lockfile
run: pnpm install --filter website --frozen-lockfile --ignore-scripts
- name: Build the website
run: pnpm website:build

2
.nvmrc
View File

@ -1 +1 @@
24.12.0
24.13.0

View File

@ -165,6 +165,17 @@ pnpm install
pnpm edit-docs:edit-docs
```
Alternatively, if you have Nix installed:
```shell
# Run directly
nix run .#edit-docs
# Or install to your profile
nix profile install .#edit-docs
trilium-edit-docs
```
### Building the Executable
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
```shell

View File

@ -9,13 +9,13 @@
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.28.0",
"packageManager": "pnpm@10.28.2",
"devDependencies": {
"@redocly/cli": "2.14.4",
"@redocly/cli": "2.14.9",
"archiver": "7.0.1",
"fs-extra": "11.3.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"react": "19.2.4",
"react-dom": "19.2.4",
"typedoc": "0.28.16",
"typedoc-plugin-missing-exports": "4.1.2"
}

View File

@ -19,7 +19,7 @@
<!-- This works even when the user directly changes --root-background in CSS -->
<div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div>
<script src="./index.ts" type="module"></script>
<script src="./src/index.ts" type="module"></script>
<!-- Required for correct loading of scripts in Electron -->
<script>

View File

@ -27,14 +27,14 @@
"@mermaid-js/layout-elk": "0.2.0",
"@mind-elixir/node-menu": "5.0.1",
"@popperjs/core": "2.11.8",
"@preact/signals": "2.5.1",
"@preact/signals": "2.6.1",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
"@triliumnext/commons": "workspace:*",
"@triliumnext/highlightjs": "workspace:*",
"@triliumnext/share-theme": "workspace:*",
"@triliumnext/split.js": "workspace:*",
"@zumer/snapdom": "2.0.1",
"@zumer/snapdom": "2.0.2",
"autocomplete.js": "0.38.1",
"bootstrap": "5.3.8",
"boxicons": "2.1.4",
@ -44,23 +44,23 @@
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"globals": "17.0.0",
"i18next": "25.7.3",
"i18next": "25.8.0",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
"jquery": "4.0.0",
"jquery.fancytree": "2.38.5",
"jsplumb": "2.15.6",
"katex": "0.16.27",
"katex": "0.16.28",
"knockout": "3.5.1",
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "17.0.1",
"mermaid": "11.12.2",
"mind-elixir": "5.5.0",
"mind-elixir": "5.6.1",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.28.2",
"react-i18next": "16.5.1",
"react-i18next": "16.5.4",
"react-window": "2.2.5",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
@ -78,9 +78,9 @@
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "20.0.11",
"lightningcss": "1.30.2",
"happy-dom": "20.4.0",
"lightningcss": "1.31.1",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.1.4"
"vite-plugin-static-copy": "3.2.0"
}
}

View File

@ -1,6 +1,6 @@
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror";
import { SqlExecuteResults } from "@triliumnext/commons";
import { SqlExecuteResponse } from "@triliumnext/commons";
import type { NativeImage, TouchBar } from "electron";
import { ColumnComponent } from "tabulator-tables";
@ -410,7 +410,7 @@ type EventMappings = {
addNewLabel: CommandData;
addNewRelation: CommandData;
sqlQueryResults: CommandData & {
results: SqlExecuteResults;
response: SqlExecuteResponse;
};
readOnlyTemporarilyDisabled: {
noteContext: NoteContext;

View File

@ -1,16 +1,17 @@
import utils from "../services/utils.js";
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
import bundleService from "../services/bundle.js";
import dateNoteService from "../services/date_notes.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import linkService from "../services/link.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import server from "../services/server.js";
import toastService from "../services/toast.js";
import utils from "../services/utils.js";
import ws from "../services/ws.js";
import appContext, { type NoteCommandData } from "./app_context.js";
import Component from "./component.js";
import toastService from "../services/toast.js";
import ws from "../services/ws.js";
import bundleService from "../services/bundle.js";
import froca from "../services/froca.js";
import linkService from "../services/link.js";
import { t } from "../services/i18n.js";
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
export default class Entrypoints extends Component {
constructor() {
@ -187,13 +188,8 @@ export default class Entrypoints extends Component {
} else if (note.mime.endsWith("env=backend")) {
await server.post(`script/run/${note.noteId}`);
} else if (note.mime === "text/x-sqlite;schema=trilium") {
const resp = await server.post<SqlExecuteResponse>(`sql/execute/${note.noteId}`);
if (!resp.success) {
toastService.showError(t("entrypoints.sql-error", { message: resp.error }));
}
await appContext.triggerEvent("sqlQueryResults", { ntxId: ntxId, results: resp.results });
const response = await server.post<SqlExecuteResponse>(`sql/execute/${note.noteId}`);
await appContext.triggerEvent("sqlQueryResults", { ntxId, response });
}
toastService.showMessage(t("entrypoints.note-executed"));

View File

@ -1,4 +1,4 @@
import { MIME_TYPES_DICT } from "@triliumnext/commons";
import { getNoteIcon } from "@triliumnext/commons";
import cssClassManager from "../services/css_class_manager.js";
import type { Froca } from "../services/froca-interface.js";
@ -13,25 +13,6 @@ import type { AttributeType, default as FAttribute } from "./fattribute.js";
const LABEL = "label";
const RELATION = "relation";
export const NOTE_TYPE_ICONS = {
file: "bx bx-file",
image: "bx bx-image",
code: "bx bx-code",
render: "bx bx-extension",
search: "bx bx-file-find",
relationMap: "bx bxs-network-chart",
book: "bx bx-book",
noteMap: "bx bxs-network-chart",
mermaid: "bx bx-selection",
canvas: "bx bx-pen",
webView: "bx bx-globe-alt",
launcher: "bx bx-link",
doc: "bx bxs-file-doc",
contentWidget: "bx bxs-widget",
mindMap: "bx bx-sitemap",
aiChat: "bx bx-bot"
};
/**
* There are many different Note types, some of which are entirely opaque to the
* end user. Those types should be used only for checking against, they are
@ -582,32 +563,18 @@ export default class FNote {
}
getIcon() {
return `tn-icon ${this.#getIconInternal()}`;
}
#getIconInternal() {
const iconClassLabels = this.getLabels("iconClass");
const workspaceIconClass = this.getWorkspaceIconClass();
if (iconClassLabels && iconClassLabels.length > 0) {
return iconClassLabels[0].value;
} else if (workspaceIconClass) {
return workspaceIconClass;
} else if (this.noteId === "root") {
return "bx bx-home-alt-2";
}
if (this.noteId === "_share") {
return "bx bx-share-alt";
} else if (this.type === "text") {
if (this.isFolder()) {
return "bx bx-folder";
}
return "bx bx-note";
} else if (this.type === "code") {
const correspondingMimeType = MIME_TYPES_DICT.find(m => m.mime === this.mime);
return correspondingMimeType?.icon ?? NOTE_TYPE_ICONS.code;
}
return NOTE_TYPE_ICONS[this.type];
const icon = getNoteIcon({
noteId: this.noteId,
type: this.type,
mime: this.mime,
iconClass: iconClassLabels.length > 0 ? iconClassLabels[0].value : undefined,
workspaceIconClass,
isFolder: this.isFolder.bind(this)
});
return `tn-icon ${icon}`;
}
getColorClass() {

View File

@ -16,10 +16,21 @@ async function initJQuery() {
const $ = (await import("jquery")).default;
window.$ = $;
window.jQuery = $;
// Polyfill removed jQuery methods for autocomplete.js compatibility
($ as any).isArray = Array.isArray;
($ as any).isFunction = function(obj: any) { return typeof obj === 'function'; };
($ as any).isPlainObject = function(obj: any) {
if (obj == null || typeof obj !== 'object') { return false; }
const proto = Object.getPrototypeOf(obj);
if (proto === null) { return true; }
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === 'function' && Ctor === Object;
};
}
async function setupGlob() {
const response = await fetch(`/bootstrap${window.location.search}`);
const response = await fetch(`./bootstrap${window.location.search}`);
const json = await response.json();
window.global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
@ -66,22 +77,25 @@ async function loadBootstrapCss() {
}
function loadStylesheets() {
const { assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
const { device, assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
const cssToLoad: string[] = [];
cssToLoad.push(`${assetPath}/stylesheets/ckeditor-theme.css`);
cssToLoad.push(`api/fonts`);
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
if (themeCssUrl) {
cssToLoad.push(themeCssUrl);
if (device !== "print") {
cssToLoad.push(`${assetPath}/stylesheets/ckeditor-theme.css`);
cssToLoad.push(`api/fonts`);
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
if (themeCssUrl) {
cssToLoad.push(themeCssUrl);
}
if (themeUseNextAsBase === "next") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`);
} else if (themeUseNextAsBase === "next-dark") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`);
} else if (themeUseNextAsBase === "next-light") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`);
}
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
}
if (themeUseNextAsBase === "next") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`);
} else if (themeUseNextAsBase === "next-dark") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`);
} else if (themeUseNextAsBase === "next-light") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`);
}
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
for (const href of cssToLoad) {
const linkEl = document.createElement("link");
@ -128,6 +142,7 @@ async function loadScripts() {
case "desktop":
default:
await import("./desktop.js");
break;
}
}

View File

@ -46,8 +46,6 @@ import ScrollPadding from "../widgets/scroll_padding.js";
import SearchResult from "../widgets/search_result.jsx";
import SharedInfo from "../widgets/shared_info.jsx";
import RightPanelContainer from "../widgets/sidebar/RightPanelContainer.jsx";
import SqlResults from "../widgets/sql_result.js";
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
import TabRowWidget from "../widgets/tab_row.js";
import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx";
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
@ -163,11 +161,9 @@ export default class DesktopLayout {
.child(<SharedInfo />)
)
.optChild(!isNewLayout, <PromotedAttributes />)
.child(<SqlTableSchemas />)
.child(<NoteDetail />)
.child(<NoteList media="screen" />)
.child(<SearchResult />)
.child(<SqlResults />)
.child(<ScrollPadding />)
)
.child(<ApiLog />)

View File

@ -29,7 +29,9 @@ async function main() {
const froca = (await import("./services/froca")).default;
const note = await froca.getNote(noteId);
render(<App note={note} noteId={noteId} />, document.body);
const bodyWrapper = document.createElement("div");
render(<App note={note} noteId={noteId} />, bodyWrapper);
document.body.appendChild(bodyWrapper);
}
function App({ note, noteId }: { note: FNote | null | undefined, noteId: string }) {

View File

@ -8,6 +8,17 @@ async function loadBootstrap() {
}
}
// Polyfill removed jQuery methods for autocomplete.js compatibility
($ as any).isArray = Array.isArray;
($ as any).isFunction = function(obj: any) { return typeof obj === 'function'; };
($ as any).isPlainObject = function(obj: any) {
if (obj == null || typeof obj !== 'object') { return false; }
const proto = Object.getPrototypeOf(obj);
if (proto === null) { return true; }
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === 'function' && Ctor === Object;
};
(window as any).$ = $;
(window as any).jQuery = $;
await loadBootstrap();

View File

@ -42,7 +42,7 @@ describe("Set boolean with inheritance", () => {
name: "foo",
value: "",
isInheritable: false
});
}, undefined);
});
it("removes boolean normally without inheritance", async () => {
@ -91,7 +91,7 @@ describe("Set boolean with inheritance", () => {
name: "foo",
value: "false",
isInheritable: false
});
}, undefined);
});
it("overrides boolean with inherited false", async () => {
@ -112,7 +112,7 @@ describe("Set boolean with inheritance", () => {
name: "foo",
value: "",
isInheritable: false
});
}, undefined);
});
it("deletes override boolean with inherited false with already existing value", async () => {
@ -134,6 +134,6 @@ describe("Set boolean with inheritance", () => {
name: "foo",
value: "",
isInheritable: false
});
}, undefined);
});
});

View File

@ -14,13 +14,13 @@ async function addLabel(noteId: string, name: string, value: string = "", isInhe
});
}
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false, componentId?: string) {
await server.put(`notes/${noteId}/set-attribute`, {
type: "label",
name,
value,
isInheritable
});
isInheritable,
}, componentId);
}
export async function setRelation(noteId: string, name: string, value: string = "", isInheritable = false) {
@ -117,15 +117,15 @@ function removeOwnedRelationByName(note: FNote, relationName: string) {
* @param name the name of the attribute to set.
* @param value the value of the attribute to set.
*/
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined, componentId?: string) {
if (value !== null && value !== undefined) {
// Create or update the attribute.
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value }, componentId);
} else {
// Remove the attribute if it exists on the server but we don't define a value for it.
const attributeId = note.getAttribute(type, name)?.attributeId;
if (attributeId) {
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`);
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`, componentId);
}
}
}

View File

@ -103,7 +103,7 @@ async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: st
* @param moveToParent whether to automatically go to the parent note path after a succesful delete. Usually makes sense if deleting the active note(s).
* @returns promise that returns false if the operation was cancelled or there was nothing to delete, true if the operation succeeded.
*/
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true) {
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true, componentId?: string) {
branchIdsToDelete = filterRootNote(branchIdsToDelete);
if (branchIdsToDelete.length === 0) {
@ -139,9 +139,9 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
const branch = froca.getBranch(branchIdToDelete);
if (deleteAllClones && branch) {
await server.remove(`notes/${branch.noteId}${query}`);
await server.remove(`notes/${branch.noteId}${query}`, componentId);
} else {
await server.remove(`branches/${branchIdToDelete}${query}`);
await server.remove(`branches/${branchIdToDelete}${query}`, componentId);
}
}

View File

@ -1,5 +1,6 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import shortcuts, { isIMEComposing, keyMatches, matchesShortcut } from "./shortcuts.js";
// Mock utils module
vi.mock("./utils.js", () => ({
@ -61,9 +62,10 @@ describe("shortcuts", () => {
});
describe("keyMatches", () => {
const createKeyboardEvent = (key: string, code?: string) => ({
const createKeyboardEvent = (key: string, code?: string, extraProps: Partial<KeyboardEvent> = {}) => ({
key,
code: code || `Key${key.toUpperCase()}`
code: code || `Key${key.toUpperCase()}`,
...extraProps
} as KeyboardEvent);
it("should match regular letter keys using key code", () => {
@ -100,6 +102,26 @@ describe("shortcuts", () => {
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
it("should match azerty keys", () => {
const event = createKeyboardEvent("A", "KeyQ");
expect(keyMatches(event, "a")).toBe(true);
expect(keyMatches(event, "q")).toBe(false);
});
it("should match letter keys using code when key is a special character (macOS Alt behavior)", () => {
// On macOS, pressing Option/Alt + A produces 'å' as the key, but code is still 'KeyA'
const macOSAltAEvent = createKeyboardEvent("å", "KeyA", { altKey: true });
expect(keyMatches(macOSAltAEvent, "a")).toBe(true);
// Option + H produces '˙'
const macOSAltHEvent = createKeyboardEvent("˙", "KeyH", { altKey: true });
expect(keyMatches(macOSAltHEvent, "h")).toBe(true);
// Option + S produces 'ß'
const macOSAltSEvent = createKeyboardEvent("ß", "KeyS", { altKey: true });
expect(keyMatches(macOSAltSEvent, "s")).toBe(true);
});
});
describe("matchesShortcut", () => {
@ -200,6 +222,42 @@ describe("shortcuts", () => {
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
it("matches azerty", () => {
const event = createKeyboardEvent({
key: "a",
code: "KeyQ",
ctrlKey: true
});
expect(matchesShortcut(event, "Ctrl+A")).toBe(true);
});
it("should match Alt+letter shortcuts on macOS where key is a special character", () => {
// On macOS, pressing Option/Alt + A produces 'å' but code remains 'KeyA'
const macOSAltAEvent = createKeyboardEvent({
key: "å",
code: "KeyA",
altKey: true
});
expect(matchesShortcut(macOSAltAEvent, "alt+a")).toBe(true);
// Option/Alt + H produces '˙'
const macOSAltHEvent = createKeyboardEvent({
key: "˙",
code: "KeyH",
altKey: true
});
expect(matchesShortcut(macOSAltHEvent, "alt+h")).toBe(true);
// Combined with Ctrl: Ctrl+Alt+S where Alt produces 'ß'
const macOSCtrlAltSEvent = createKeyboardEvent({
key: "ß",
code: "KeyS",
ctrlKey: true,
altKey: true
});
expect(matchesShortcut(macOSCtrlAltSEvent, "ctrl+alt+s")).toBe(true);
});
});
describe("bindGlobalShortcut", () => {

View File

@ -213,7 +213,13 @@ export function keyMatches(e: KeyboardEvent, key: string): boolean {
}
// For letter keys, use the physical key code for consistency
// On macOS, Option/Alt key produces special characters, so we must use e.code
if (key.length === 1 && key >= 'a' && key <= 'z') {
if (e.altKey) {
// e.code is like "KeyA", "KeyB", etc.
const expectedCode = `Key${key.toUpperCase()}`;
return e.code === expectedCode || e.key.toLowerCase() === key.toLowerCase();
}
return e.key.toLowerCase() === key.toLowerCase();
}

View File

@ -1,10 +1,11 @@
import { ensureMimeTypes, highlight, highlightAuto, loadTheme, Themes, type AutoHighlightResult, type HighlightResult, type Theme } from "@triliumnext/highlightjs";
import { MimeType } from "@triliumnext/commons";
import { type AutoHighlightResult, ensureMimeTypes, highlight, highlightAuto, type HighlightResult, loadTheme, type Theme,Themes } from "@triliumnext/highlightjs";
import { copyText, copyTextWithToast } from "./clipboard_ext.js";
import { t } from "./i18n.js";
import mime_types from "./mime_types.js";
import options from "./options.js";
import { t } from "./i18n.js";
import { copyText, copyTextWithToast } from "./clipboard_ext.js";
import { isShare } from "./utils.js";
import { MimeType } from "@triliumnext/commons";
let highlightingLoaded = false;
@ -76,13 +77,15 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery<HTMLEle
}
export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
if (highlightingLoaded) {
if (!mimeTypeHint && highlightingLoaded) {
return;
}
// Load theme.
const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName);
if (!highlightingLoaded) {
const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName);
}
// Load mime types.
let mimeTypes: MimeType[];
@ -94,7 +97,7 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
enabled: true,
mime: mimeTypeHint.replace("-", "/")
}
]
];
} else {
mimeTypes = mime_types.getMimeTypes();
}
@ -124,9 +127,9 @@ export function isSyntaxHighlightEnabled() {
if (!isShare) {
const theme = options.get("codeBlockTheme");
return !!theme && theme !== "none";
} else {
return true;
}
return true;
}
/**

View File

@ -14,13 +14,13 @@
--row-moving-background-color: var(--accented-background-color);
--row-text-color: var(--main-text-color);
--row-delimiter-color: var(--more-accented-background-color);
--cell-horiz-padding-size: 8px;
--cell-vert-padding-size: 8px;
--cell-editable-hover-outline-color: var(--main-border-color);
--cell-read-only-text-color: var(--muted-text-color);
--cell-editing-border-color: var(--main-border-color);
--cell-editing-border-width: 2px;
--cell-editing-background-color: var(--ck-color-selector-focused-cell-background);
@ -40,10 +40,42 @@
border-bottom: var(--col-header-bottom-border);
background: var(--col-header-background-color);
color: var(--col-header-text-color);
}
font-weight: normal;
.tabulator .tabulator-col-content {
padding: 8px 4px !important;
.tabulator-col.tabulator-range-highlight {
background: inherit;
color: inherit;
font-weight: bold;
}
.tabulator-col-content {
padding: 0 !important;
.tabulator-col-title-holder {
padding: 8px 4px;
}
&:has(.tabulator-header-filter) {
.tabulator-col-title-holder {
padding: 4px;
padding-bottom: 0;
}
}
.tabulator-header-filter {
background: var(--main-background-color);
padding: 2px 1px;
input {
background: var(--main-background-color);
color: var(--main-text-color);
border: 1px solid var(--button-border-color);
border-radius: 3px;
outline: none;
padding: 2px;
}
}
}
}
@media (hover: hover) and (pointer: fine) {
@ -80,7 +112,6 @@
.tabulator-tableholder {
padding-top: 10px;
height: unset !important; /* Don't extend on the full height */
}
/* Rows */
@ -99,6 +130,14 @@
border-top: none;
border-bottom: 1px solid var(--row-delimiter-color);
color: var(--row-text-color);
&:last-of-type {
border-bottom: none;
}
&.tabulator-range-highlight > .tabulator-cell.tabulator-frozen {
font-weight: bold;
}
}
.tabulator-row.tabulator-row-odd {
@ -120,11 +159,14 @@
margin-inline-end: var(--cell-editing-border-width);
}
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left,
.tabulator-row .tabulator-cell {
border-inline-end-color: transparent;
}
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
border-inline-end-color: var(--main-border-color);
}
.tabulator-row .tabulator-cell:not(.tabulator-editable) {
color: var(--cell-read-only-text-color);
}
@ -174,10 +216,6 @@
margin: 0;
}
.tabulator .tabulator-footer {
color: var(--main-text-color);
}
/* Context menus */
.tabulator-popup-container {
@ -192,8 +230,27 @@
}
/* Footer */
:root .tabulator .tabulator-footer {
border-top: unset;
background: transparent;
color: var(--main-text-color);
border-top: 1px solid var(--main-border-color);
padding: 10px 0;
}
.tabulator-page {
background: var(--button-background-color);
color: var(--button-text-color);
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
&:hover {
border-color: var(--hover-item-border-color);
color: var(--button-text-color);
}
}
select {
background: var(--button-background-color);
color: var(--input-text-color);
border: 1px solid var(--button-border-color);
}
}

View File

@ -1604,7 +1604,9 @@
"clone-indicator-tooltip": "此笔记有 {{- count}} 个父级: {{- parents}}",
"clone-indicator-tooltip-single": "此笔记已克隆1 个额外的父级:{{- parent}}",
"subtree-hidden-tooltip_other": "从树中隐藏的 {{count}} 篇子笔记",
"subtree-hidden-moved-title": "已添加到 {{title}}"
"subtree-hidden-moved-title": "已添加到 {{title}}",
"subtree-hidden-moved-description-collection": "此集合隐藏其树中的子笔记。",
"subtree-hidden-moved-description-other": "子笔记隐藏于此笔记的树中。"
},
"title_bar_buttons": {
"window-on-top": "保持此窗口置顶"
@ -1637,7 +1639,11 @@
"configure_launchbar": "配置启动栏"
},
"sql_result": {
"no_rows": "此查询没有返回任何数据"
"no_rows": "此查询没有返回任何数据",
"not_executed": "查询尚未执行。",
"failed": "SQL 查询执行失败",
"execute_now": "立即执行",
"statement_result": "执行结果"
},
"sql_table_schemas": {
"tables": "表"

View File

@ -1,6 +1,6 @@
{
"about": {
"title": "Über Trilium Notes",
"title": "Über Trilium Notizen",
"homepage": "Startseite:",
"app_version": "App-Version:",
"db_version": "DB-Version:",
@ -26,7 +26,12 @@
"widget-list-error": {
"title": "Abruf der Liste von Widgets vom Server ist fehlgeschlagen"
},
"open-script-note": "Script-Notiz öffnen"
"open-script-note": "Script-Notiz öffnen",
"widget-render-error": {
"title": "Benutzerdefiniertes React-Widget konnte nicht dargestellt werden"
},
"widget-missing-parent": "Benutzerdefiniertes Widget hat die erforderliche '{{property}}'-Eigenschaft nicht korrekt definiert.\n\nFalls dieses Skript ohne UI-Element ausgeführt werden soll, benutze stattdessen '#run=frontendStartup'.",
"scripting-error": "Benutzerdefinierter Skriptfehler: {{title}}"
},
"add_link": {
"add_link": "Link hinzufügen",
@ -124,7 +129,7 @@
"scrollToActiveNote": "Scrolle zur aktiven Notiz",
"jumpToParentNote": "Zur übergeordneten Notiz springen",
"collapseWholeTree": "Reduziere den gesamten Notizbaum",
"collapseSubTree": "Teilbaum einklappen",
"collapseSubTree": "Zweig einklappen",
"tabShortcuts": "Tab-Tastenkürzel",
"newTabNoteLink": "auf den Notizlink öffnet die Notiz in einem neuen Tab",
"onlyInDesktop": "Nur im Desktop (Electron Build)",
@ -210,7 +215,7 @@
"modalTitle": "Infonachricht",
"closeButton": "Schließen",
"okButton": "OK",
"copy_to_clipboard": "In die Zwischenablage kopieren"
"copy_to_clipboard": "In Zwischenablage kopieren"
},
"jump_to_note": {
"search_button": "Suche im Volltext",
@ -225,7 +230,7 @@
"move_to": {
"dialog_title": "Notizen verschieben nach ...",
"notes_to_move": "Notizen zum Verschieben",
"target_parent_note": "Ziel-Elternnotiz",
"target_parent_note": "Übergeordnete Notiz bestimmen",
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
"move_button": "Zur ausgewählten Notiz wechseln",
"error_no_path": "Kein Weg, auf den man sich bewegen kann.",
@ -328,8 +333,8 @@
"target_note_title": "Eine Beziehung ist eine benannte Verbindung zwischen Quellnotiz und Zielnotiz.",
"target_note": "Zielnotiz",
"promoted_title": "Das heraufgestufte Attribut wird deutlich in der Notiz angezeigt.",
"promoted": "Gefördert",
"promoted_alias_title": "Der Name, der in der Benutzeroberfläche für heraufgestufte Attribute angezeigt werden soll.",
"promoted": "Hervorgehoben",
"promoted_alias_title": "Der Name, der in der Benutzeroberfläche für hervorgehobene Attribute angezeigt werden soll.",
"promoted_alias": "Alias",
"multiplicity_title": "Multiplizität definiert, wie viele Attribute mit demselben Namen erstellt werden können maximal 1 oder mehr als 1.",
"multiplicity": "Vielzahl",
@ -362,7 +367,7 @@
"disable_versioning": "deaktiviert die automatische Versionierung. Nützlich z.B. große, aber unwichtige Notizen z.B. große JS-Bibliotheken, die für die Skripterstellung verwendet werden",
"calendar_root": "Markiert eine Notiz, die als Basis für Tagesnotizen verwendet werden soll. Nur einer sollte als solcher gekennzeichnet sein.",
"archived": "Notizen mit dieser Bezeichnung werden standardmäßig nicht in den Suchergebnissen angezeigt (auch nicht in den Dialogen „Springen zu“, „Link hinzufügen“ usw.).",
"exclude_from_export": "Notizen (mit ihrem Unterbaum) werden nicht in den Notizexport einbezogen",
"exclude_from_export": "Notizen (mit ihrem Unterbaum) werden nicht im Notizexport inkludiert",
"run": "Definiert, bei welchen Ereignissen das Skript ausgeführt werden soll. Mögliche Werte sind:\n<ul>\n<li>frontendStartup - wenn das Trilium-Frontend startet (oder aktualisiert wird), außer auf mobilen Geräten.</li>\n<li>mobileStartup - wenn das Trilium-Frontend auf einem mobilen Gerät startet (oder aktualisiert wird).</li>\n<li>backendStartup - wenn das Trilium-Backend startet</li>\n<li>hourly - einmal pro Stunde ausführen. Du kannst das zusätzliche Label <code>runAtHour</code> verwenden, um die genaue Stunde festzulegen.</li>\n<li>daily - einmal pro Tag ausführen</li>\n</ul>",
"run_on_instance": "Definiere, auf welcher Trilium-Instanz dies ausgeführt werden soll. Standardmäßig alle Instanzen.",
"run_at_hour": "Zu welcher Stunde soll das laufen? Sollte zusammen mit <code>#runu003dhourly</code> verwendet werden. Kann für mehr Läufe im Laufe des Tages mehrfach definiert werden.",
@ -371,7 +376,7 @@
"sort_direction": "ASC (Standard) oder DESC",
"sort_folders_first": "Ordner (Notizen mit Unternotizen) sollten oben sortiert werden",
"top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen)",
"hide_promoted_attributes": "Heraufgestufte Attribute für diese Notiz ausblenden",
"hide_promoted_attributes": "Hervorgehobene Attribute für diese Notiz ausblenden",
"read_only": "Der Editor befindet sich im schreibgeschützten Modus. Funktioniert nur für Text- und Codenotizen.",
"auto_read_only_disabled": "Text-/Codenotizen können automatisch in den Lesemodus versetzt werden, wenn sie zu groß sind. Du kannst dieses Verhalten für jede einzelne Notiz deaktivieren, indem du diese Beschriftung zur Notiz hinzufügst",
"app_css": "markiert CSS-Notizen, die in die Trilium-Anwendung geladen werden und somit zur Änderung des Aussehens von Trilium verwendet werden können.",
@ -411,13 +416,13 @@
"toc": "<code>#toc</code> oder <code>#tocu003dshow</code> erzwingen die Anzeige des Inhaltsverzeichnisses, <code>#tocu003dhide</code> erzwingt das Ausblenden. Wenn die Bezeichnung nicht vorhanden ist, wird die globale Einstellung beachtet",
"color": "Definiert die Farbe der Notiz im Notizbaum, in Links usw. Verwende einen beliebigen gültigen CSS-Farbwert wie „rot“ oder #a13d5f",
"keyboard_shortcut": "Definiert eine Tastenkombination, die sofort zu dieser Notiz springt. Beispiel: „Strg+Alt+E“. Erfordert ein Neuladen des Frontends, damit die Änderung wirksam wird.",
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Unterbaum nicht angezeigt werden kann.",
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Zweig nicht angezeigt werden kann.",
"execute_button": "Titel der Schaltfläche, welche die aktuelle Codenotiz ausführt",
"execute_description": "Längere Beschreibung der aktuellen Codenotiz, die zusammen mit der Schaltfläche „Ausführen“ angezeigt wird",
"exclude_from_note_map": "Notizen mit dieser Bezeichnung werden in der Notizenkarte ausgeblendet",
"new_notes_on_top": "Neue Notizen werden oben in der übergeordneten Notiz erstellt, nicht unten.",
"hide_highlight_widget": "Widget „Hervorhebungsliste“ ausblenden",
"run_on_note_creation": "Wird ausgeführt, wenn eine Notiz im Backend erstellt wird. Verwende diese Beziehung, wenn du das Skript für alle Notizen ausführen möchtest, die unter einer bestimmten Unternotiz erstellt wurden. Erstelle es in diesem Fall auf der Unternotiz-Stammnotiz und mache es vererbbar. Eine neue Notiz, die innerhalb der Unternotiz (beliebige Tiefe) erstellt wird, löst das Skript aus.",
"hide_highlight_widget": "Widget „Markierungsliste“ ausblenden",
"run_on_note_creation": "Wird ausgeführt, wenn eine Notiz im Backend erstellt wird. Verwende diese Beziehung, wenn du das Skript für alle Notizen ausführen möchtest, die unter einem bestimmten Zweig erstellt wurden. Erstelle es in diesem Fall auf der Stammnotiz und mache es vererbbar. Eine neue Notiz, die innerhalb des Zweigs (beliebige Tiefe) erstellt wird, löst das Skript aus.",
"run_on_child_note_creation": "Wird ausgeführt, wenn eine neue Notiz unter der Notiz erstellt wird, in der diese Beziehung definiert ist",
"run_on_note_title_change": "Wird ausgeführt, wenn der Notiztitel geändert wird (einschließlich der Notizerstellung)",
"run_on_note_content_change": "Wird ausgeführt, wenn der Inhalt einer Notiz geändert wird (einschließlich der Erstellung von Notizen).",
@ -428,8 +433,8 @@
"run_on_branch_deletion": "wird ausgeführt, wenn ein Zweig gelöscht wird. Der Zweig ist eine Verknüpfung zwischen der übergeordneten Notiz und der untergeordneten Notiz und wird z. B. gelöscht. beim Verschieben der Notiz (alter Zweig/Link wird gelöscht).",
"run_on_attribute_creation": "wird ausgeführt, wenn für die Notiz ein neues Attribut erstellt wird, das diese Beziehung definiert",
"run_on_attribute_change": " wird ausgeführt, wenn das Attribut einer Notiz geändert wird, die diese Beziehung definiert. Dies wird auch ausgelöst, wenn das Attribut gelöscht wird",
"relation_template": "Die Attribute der Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Der Inhalt und der Unterbaum der Notiz werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
"inherit": "Die Attribute einer Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Ein ähnliches Konzept findest du unter Vorlagenbeziehung. Siehe Attributvererbung in der Dokumentation.",
"relation_template": "Die Attribute der Notiz werden auch ohne eine Hierarchische-Beziehung vererbt. Der Inhalt und der Zweig werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
"inherit": "Die Attribute einer Notiz werden auch ohne eine Hierarchische-Beziehung vererbt. Ein ähnliches Konzept findest du unter Vorlagenbeziehung. Siehe Attributsvererbung in der Dokumentation.",
"render_note": "Notizen vom Typ \"HTML-Notiz rendern\" werden mit einer Code-Notiz (HTML oder Skript) gerendert, und es ist notwendig, über diese Beziehung anzugeben, welche Notiz gerendert werden soll",
"widget_relation": "Das Ziel dieser Beziehung wird ausgeführt und als Widget in der Seitenleiste gerendert",
"share_css": "CSS-Hinweis, der in die Freigabeseite eingefügt wird. Die CSS-Notiz muss sich ebenfalls im gemeinsamen Unterbaum befinden. Erwäge auch die Verwendung von „share_hidden_from_tree“ und „share_omit_default_css“.",
@ -627,7 +632,7 @@
"show_toc": "Inhaltsverzeichnis anzeigen"
},
"show_highlights_list_widget_button": {
"show_highlights_list": "Hervorhebungen anzeigen"
"show_highlights_list": "Markierungsliste anzeigen"
},
"global_menu": {
"menu": "Menü",
@ -640,8 +645,8 @@
"zoom_out": "Herauszoomen",
"reset_zoom_level": "Zoomstufe zurücksetzen",
"zoom_in": "Hineinzoomen",
"configure_launchbar": "Konfiguriere die Launchbar",
"show_shared_notes_subtree": "Unterbaum „Freigegebene Notizen“ anzeigen",
"configure_launchbar": "Konfiguriere die Starterleiste",
"show_shared_notes_subtree": "Zweig „Freigegebene Notizen“ anzeigen",
"advanced": "Erweitert",
"open_dev_tools": "Öffne die Entwicklungstools",
"open_sql_console": "Öffne die SQL-Konsole",
@ -650,7 +655,7 @@
"show_backend_log": "Backend-Protokoll anzeigen",
"reload_hint": "Ein Neuladen kann bei einigen visuellen Störungen Abhilfe schaffen, ohne die gesamte App neu starten zu müssen.",
"reload_frontend": "Frontend neu laden",
"show_hidden_subtree": "Versteckten Teilbaum anzeigen",
"show_hidden_subtree": "Versteckten Zweig anzeigen",
"show_help": "Hilfe anzeigen",
"about": "Über Trilium Notes",
"logout": "Abmelden",
@ -698,7 +703,7 @@
"export_as_image_png": "PNG (Raster)",
"export_as_image_svg": "SVG (Vektor)",
"note_map": "Notizen Karte",
"view_revisions": "Notizrevisionen",
"view_revisions": "Notizrevisionen...",
"advanced": "Erweitert"
},
"onclick_button": {
@ -715,7 +720,7 @@
"update_available": "Update verfügbar"
},
"note_launcher": {
"this_launcher_doesnt_define_target_note": "Dieser Launcher definiert keine Zielnotiz."
"this_launcher_doesnt_define_target_note": "Dieser Starter definiert keine Zielnotiz."
},
"code_buttons": {
"execute_button_title": "Skript ausführen",
@ -758,8 +763,8 @@
"change_note_icon": "Notiz-Icon ändern",
"search": "Suche:",
"reset-default": "Standard wiederherstellen",
"search_placeholder_one": "Suche {{number}} Icons über {{count}} Pakete",
"search_placeholder_other": "Suche {{number}} Icons über {{count}} Pakete",
"search_placeholder_one": "Suche {{number}} Symbole über {{count}} Pakete",
"search_placeholder_other": "Suche {{number}} Symbole über {{count}} Pakete",
"search_placeholder_filtered": "Suche {{number}} Icons in {{name}}",
"filter": "Filter",
"filter-none": "Alle Icons",
@ -792,7 +797,8 @@
"expand_all_levels": "Alle Ebenen erweitern",
"expand_tooltip": "Erweitert die direkten Unterelemente dieser Sammlung (eine Ebene tiefer). Für weitere Optionen auf den Pfeil rechts klicken.",
"expand_first_level": "Direkte Unterelemente erweitern",
"expand_nth_level": "{{depth}} Ebenen erweitern"
"expand_nth_level": "{{depth}} Ebenen erweitern",
"hide_child_notes": "Unternotizen im Baum ausblenden"
},
"edited_notes": {
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
@ -805,7 +811,7 @@
"file_type": "Dateityp",
"file_size": "Dateigröße",
"download": "Herunterladen",
"open": "Offen",
"open": "Extern öffnen",
"upload_new_revision": "Neue Revision hochladen",
"upload_success": "Neue Dateirevision wurde hochgeladen.",
"upload_failed": "Das Hochladen einer neuen Dateirevision ist fehlgeschlagen.",
@ -836,7 +842,7 @@
"note_size": "Notengröße",
"note_size_info": "Die Notizgröße bietet eine grobe Schätzung des Speicherbedarfs für diese Notiz. Es berücksichtigt den Inhalt der Notiz und den Inhalt ihrer Notizrevisionen.",
"calculate": "berechnen",
"subtree_size": "(Teilbaumgröße: {{size}} in {{count}} Notizen)",
"subtree_size": "(Zweiggröße: {{size}} in {{count}} Notizen)",
"title": "Notizinfo",
"mime": "MIME Typ",
"show_similar_notes": "Zeige ähnliche Notizen"
@ -865,7 +871,7 @@
"owned_attributes": "Eigene Attribute"
},
"promoted_attributes": {
"promoted_attributes": "Übergebene Attribute",
"promoted_attributes": "Hervorgehobene Attribute",
"url_placeholder": "http://website...",
"open_external_link": "Externen Link öffnen",
"unknown_label_type": "Unbekannter Labeltyp „{{type}}“",
@ -903,7 +909,7 @@
"unknown_search_option": "Unbekannte Suchoption {{searchOptionName}}",
"search_note_saved": "Suchnotiz wurde in {{-notePathTitle}} gespeichert",
"actions_executed": "Aktionen wurden ausgeführt.",
"view_options": "Anzeigeoptionen:"
"view_options": "Optionen anzeigen:"
},
"similar_notes": {
"title": "Ähnliche Notizen",
@ -1009,9 +1015,9 @@
"auto-detect-language": "Automatisch erkannt",
"keeps-crashing": "Die Bearbeitungskomponente stürzt immer wieder ab. Bitte starten Sie Trilium neu. Wenn das Problem weiterhin besteht, erstellen Sie einen Fehlerbericht.",
"editor_crashed_title": "Der Text Editor ist abgestürzt",
"editor_crashed_content": "Ihr Inhalt wurde erfolgreich wiederhergestellt, aber einzelne Ihrer letzten Änderungen waren möglicherweise noch nicht gespeichert.",
"editor_crashed_details_button": "Zeige mehr Details…",
"editor_crashed_details_intro": "Falls Sie diesen Fehler mehrmals sehen, melden Sie dies auf GitHub mit den folgenden Informationen.",
"editor_crashed_content": "Ihr Inhalt wurde erfolgreich wiederhergestellt, aber kürzlich gemachte Änderungen wurden unter Umständen nicht gespeichert.",
"editor_crashed_details_button": "Mehr Details anzeigen...",
"editor_crashed_details_intro": "Falls dieser Fehler häufiger auftritt, ziehen Sie in Betracht uns diesen über GitHub zu melden, indem Sie die folgenden Informationen bereitstellen.",
"editor_crashed_details_title": "Technische Informationen"
},
"empty": {
@ -1109,7 +1115,7 @@
"vacuum_database": {
"title": "Datenbank aufräumen",
"description": "Dadurch wird die Datenbank neu erstellt, was normalerweise zu einer kleineren Datenbankdatei führt. Es werden keine Daten tatsächlich geändert.",
"button_text": "Vakuumdatenbank",
"button_text": "Datenbank aufräumen",
"vacuuming_database": "Datenbank wird geleert...",
"database_vacuumed": "Die Datenbank wurde geleert"
},
@ -1150,7 +1156,7 @@
},
"ribbon": {
"widgets": "Multifunktionsleisten-Widgets",
"promoted_attributes_message": "Die Multifunktionsleisten-Registerkarte „Heraufgestufte Attribute“ wird automatisch geöffnet, wenn in der Notiz heraufgestufte Attribute vorhanden sind",
"promoted_attributes_message": "Die „Hervorgehobene Attribute“-Leiste wird automatisch geöffnet, wenn in der Notiz hervorgehobene Attribute vorhanden sind",
"edited_notes_message": "Die Multifunktionsleisten-Registerkarte „Bearbeitete Notizen“ wird bei Tagesnotizen automatisch geöffnet"
},
"theme": {
@ -1163,7 +1169,7 @@
"layout": "Layout",
"layout-vertical-title": "Vertikal",
"layout-horizontal-title": "Horizontal",
"layout-vertical-description": "Startleiste ist auf der linken Seite (standard)",
"layout-vertical-description": "Startleiste ist auf der linken Seite (Standard)",
"layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert.",
"auto_theme": "Alt (Folge dem Farbschema des Systems)",
"light_theme": "Alt (Hell)",
@ -1171,7 +1177,7 @@
},
"zoom_factor": {
"title": "Zoomfaktor (nur Desktop-Build)",
"description": "Das Zoomen kann auch mit den Tastenkombinationen STRG+- und STRG+u003d gesteuert werden."
"description": "Das Zoomen kann auch mit den Tastenkombinationen Strg+- und Strg+= gesteuert werden."
},
"code_auto_read_only_size": {
"title": "Automatische schreibgeschützte Größe",
@ -1260,16 +1266,16 @@
"markdown": "Markdown-Stil"
},
"highlights_list": {
"title": "Highlights-Liste",
"description": "Du kannst die im rechten Bereich angezeigte Highlights-Liste anpassen:",
"title": "Markierungsliste",
"description": "Du kannst die im rechten Bereich angezeigte Markierungsliste anpassen:",
"bold": "Fettgedruckter Text",
"italic": "Kursiver Text",
"underline": "Unterstrichener Text",
"color": "Farbiger Text",
"bg_color": "Text mit Hintergrundfarbe",
"visibility_title": "Sichtbarkeit der Highlights-Liste",
"visibility_description": "Du kannst das Hervorhebungs-Widget pro Notiz ausblenden, indem du die Beschriftung #hideHighlightWidget hinzufügst.",
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Hervorhebungen) in den Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“)."
"visibility_title": "Sichtbarkeit der Markierungsliste",
"visibility_description": "Du kannst das Markierungs-Widget pro Notiz ausblenden, indem du die Beschriftung #hideHighlightWidget hinzufügst.",
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Markierungen) in den Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“)."
},
"table_of_contents": {
"title": "Inhaltsverzeichnis",
@ -1412,7 +1418,7 @@
"will_be_deleted_in": "Dieser Anhang wird in {{time}} automatisch gelöscht",
"will_be_deleted_soon": "Dieser Anhang wird bald automatisch gelöscht",
"deletion_reason": ", da der Anhang nicht im Inhalt der Notiz verlinkt ist. Um das Löschen zu verhindern, füge den Anhangslink wieder in den Inhalt ein oder wandel den Anhang in eine Notiz um.",
"role_and_size": "Rolle: {{role}}, Größe: {{size}}",
"role_and_size": "Rolle: {{role}}, Größe: {{size}}, MIME: {{- mimeType}}",
"link_copied": "Anhangslink in die Zwischenablage kopiert.",
"unrecognized_role": "Unbekannte Anhangsrolle „{{role}}“."
},
@ -1439,19 +1445,19 @@
"insert-note-after": "Notiz dahinter einfügen",
"insert-child-note": "Unternotiz einfügen",
"delete": "Löschen",
"search-in-subtree": "Im Notizbaum suchen",
"search-in-subtree": "Im Zweig suchen",
"hoist-note": "Notiz-Fokus setzen",
"unhoist-note": "Notiz-Fokus aufheben",
"edit-branch-prefix": "Zweig-Präfix bearbeiten",
"advanced": "Erweitert",
"expand-subtree": "Unterzweig aufklappen",
"collapse-subtree": "Notizbaum einklappen",
"expand-subtree": "Zweig aufklappen",
"collapse-subtree": "Zweig einklappen",
"sort-by": "Sortieren nach...",
"recent-changes-in-subtree": "Kürzliche Änderungen im Notizbaum",
"recent-changes-in-subtree": "Kürzliche Änderungen im Zweig",
"convert-to-attachment": "Als Anhang konvertieren",
"copy-note-path-to-clipboard": "Notiz-Pfad in die Zwischenablage kopieren",
"protect-subtree": "Notizbaum schützen",
"unprotect-subtree": "Notizenbaum-Schutz aufheben",
"protect-subtree": "Zweig schützen",
"unprotect-subtree": "Zweig-Schutz aufheben",
"copy-clone": "Kopieren / Klonen",
"clone-to": "Klonen nach...",
"cut": "Ausschneiden",
@ -1463,14 +1469,17 @@
"import-into-note": "In Notiz importieren",
"apply-bulk-actions": "Massenaktionen anwenden",
"converted-to-attachments": "{{count}} Notizen wurden als Anhang konvertiert.",
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest?",
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest? Diese Operation wird nur auf Bildnotizes angewandt. Andere Notizen werden übersprungen.",
"open-in-popup": "Schnellbearbeitung",
"archive": "Archiviere",
"unarchive": "Entarchivieren"
"unarchive": "Entarchivieren",
"open-in-a-new-window": "In neuem Fenster öffnen",
"hide-subtree": "Zweig ausblenden",
"show-subtree": "Zweig anzeigen"
},
"shared_info": {
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}.",
"shared_publicly": "Diese Notiz ist öffentlich freigegeben über {{- link}}.",
"shared_locally": "Diese Notiz ist lokal freigegeben über {{- link}}.",
"help_link": "Für Hilfe besuche <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"note_types": {
@ -1481,12 +1490,12 @@
"note-map": "Notizkarte",
"render-note": "Render Notiz",
"mermaid-diagram": "Mermaid Diagramm",
"canvas": "Canvas",
"canvas": "Leinwand",
"web-view": "Webansicht",
"mind-map": "Mind Map",
"file": "Datei",
"image": "Bild",
"launcher": "Launcher",
"launcher": "Starter",
"doc": "Dokument",
"widget": "Widget",
"confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
@ -1505,10 +1514,10 @@
"toggle-off-hint": "Notiz ist geschützt, klicken, um den Schutz aufzuheben"
},
"shared_switch": {
"shared": "Teilen",
"toggle-on-title": "Notiz teilen",
"shared": "Freigegeben",
"toggle-on-title": "Notiz freigeben",
"toggle-off-title": "Notiz-Freigabe aufheben",
"shared-branch": "Diese Notiz existiert nur als geteilte Notiz, das Aufheben der Freigabe würde sie löschen. Möchtest du fortfahren und die Notiz damit löschen?",
"shared-branch": "Diese Notiz existiert nur als freigegebene Notiz, das Aufheben der Freigabe würde sie löschen. Möchtest du fortfahren und die Notiz damit löschen?",
"inherited": "Die Notiz kann hier nicht von der Freigabe entfernt werden, da sie über Vererbung von einer übergeordneten Notiz geteilt wird."
},
"template_switch": {
@ -1526,13 +1535,13 @@
"replace_all": "Alle Ersetzen"
},
"highlights_list_2": {
"title": "Hervorhebungs-Liste",
"title": "Markierungsliste",
"options": "Optionen",
"title_with_count_one": "{{count}} Highlight",
"title_with_count_other": "{{count}} Highlights",
"modal_title": "Highlight Liste konfigurieren",
"menu_configure": "Highlight Liste konfigurieren…",
"no_highlights": "Keine Highlights gefunden."
"title_with_count_one": "{{count}} Markierung",
"title_with_count_other": "{{count}} Markierungen",
"modal_title": "Markierungsliste konfigurieren",
"menu_configure": "Markierungsliste konfigurieren…",
"no_highlights": "Keine Markierungen gefunden."
},
"quick-search": {
"placeholder": "Schnellsuche",
@ -1556,7 +1565,16 @@
"create-child-note": "Unternotiz anlegen",
"unhoist": "Fokus verlassen",
"toggle-sidebar": "Seitenleiste ein-/ausblenden",
"dropping-not-allowed": "Ablegen von Notizen an dieser Stelle ist nicht zulässig."
"dropping-not-allowed": "Ablegen von Notizen an dieser Stelle ist nicht zulässig.",
"clone-indicator-tooltip": "Diese Notiz hat {{- count}} übergeordnete Knoten: {{- parents}}",
"clone-indicator-tooltip-single": "Diese Notiz ist geklont (1 weitere Quelle: {{- parent}})",
"shared-indicator-tooltip": "Diese Notiz ist öffentlich freigegeben",
"shared-indicator-tooltip-with-url": "Diese Notiz ist öffentlich freigegeben unter: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} untergeordnete Notiz, die im Baum ausgeblendet ist",
"subtree-hidden-tooltip_other": "{{count}} untergeordnete Notizen, die im Baum ausgeblendet sind",
"subtree-hidden-moved-title": "Zu {{title}} hinzugefügt",
"subtree-hidden-moved-description-collection": "Diese Sammlung blendet ihre Unternotizen im Baum aus.",
"subtree-hidden-moved-description-other": "Untergeordnete Notizen sind im Baum für diese Notiz ausgeblendet."
},
"title_bar_buttons": {
"window-on-top": "Dieses Fenster immer oben halten"
@ -1567,7 +1585,9 @@
"printing_pdf": "PDF-Export läuft…",
"print_report_title": "Druckreport",
"print_report_collection_details_button": "Details anzeigen",
"print_report_collection_details_ignored_notes": "Ignorierte Notizen"
"print_report_collection_details_ignored_notes": "Ignorierte Notizen",
"print_report_collection_content_one": "{{count}} Notiz in der Sammlung konnte nicht gedruckt werden, weil sie nicht unterstützt oder geschützt ist.",
"print_report_collection_content_other": "{{count}} Notizen in der Sammlung konnten nicht gedruckt werden, weil sie nicht unterstützt oder geschützt sind."
},
"note_title": {
"placeholder": "Titel der Notiz hier eingeben…",
@ -1575,7 +1595,7 @@
"last_modified": "Bearbeitet am <Value />",
"note_type_switcher_label": "Ändere von {{type}} zu:",
"note_type_switcher_others": "Andere Notizart",
"note_type_switcher_templates": "Template",
"note_type_switcher_templates": "Vorlage",
"note_type_switcher_collection": "Sammlung",
"edited_notes": "Notizen, bearbeitet an diesem Tag",
"promoted_attributes": "Hervorgehobene Attribute"
@ -1585,10 +1605,14 @@
"search_not_executed": "Die Suche wurde noch nicht ausgeführt. Klicke oben auf „Suchen“, um die Ergebnisse anzuzeigen."
},
"spacer": {
"configure_launchbar": "Startleiste konfigurieren"
"configure_launchbar": "Starterleiste konfigurieren"
},
"sql_result": {
"no_rows": "Es wurden keine Zeilen für diese Abfrage zurückgegeben"
"no_rows": "Es wurden keine Zeilen für diese Abfrage zurückgegeben",
"not_executed": "Die Abfrage wurde noch nicht ausgeführt.",
"failed": "SQL-Abfrage ist fehlgeschlagen",
"execute_now": "Jetzt ausführen",
"statement_result": "Abfrageergebnis"
},
"sql_table_schemas": {
"tables": "Tabellen"
@ -1659,16 +1683,16 @@
"confirm_unhoisting": "Die angeforderte Notiz {{requestedNote}} befindet sich außerhalb des hoisted Bereichs der Notiz {{hoistedNote}}. Du musst sie unhoisten, um auf die Notiz zuzugreifen. Möchtest du mit dem Unhoisting fortfahren?"
},
"launcher_context_menu": {
"reset_launcher_confirm": "Möchtest du „{{title}}“ wirklich zurücksetzen? Alle Daten / Einstellungen in dieser Notiz (und ihren Unternotizen) gehen verloren und der Launcher wird an seinen ursprünglichen Standort zurückgesetzt.",
"add-note-launcher": "Launcher für Notiz hinzufügen",
"add-script-launcher": "Launcher für Skript hinzufügen",
"reset_launcher_confirm": "Möchtest du „{{title}}“ wirklich zurücksetzen? Alle Daten / Einstellungen in dieser Notiz (und ihren Unternotizen) gehen verloren und der Starter wird an seinen ursprünglichen Standort zurückgesetzt.",
"add-note-launcher": "Notiz-Starter hinzufügen",
"add-script-launcher": "Skript-Starter hinzufügen",
"add-custom-widget": "Benutzerdefiniertes Widget hinzufügen",
"add-spacer": "Spacer hinzufügen",
"add-spacer": "Abstandhalter hinzufügen",
"delete": "Löschen <kbd data-command=\"deleteNotes\"></kbd>",
"reset": "Zurücksetzen",
"move-to-visible-launchers": "Zu sichtbaren Launchern verschieben",
"move-to-available-launchers": "Zu verfügbaren Launchern verschieben",
"duplicate-launcher": "Launcher duplizieren <kbd data-command=\"duplicateSubtree\">"
"move-to-visible-launchers": "Zu sichtbaren Startern verschieben",
"move-to-available-launchers": "Zu verfügbaren Startern verschieben",
"duplicate-launcher": "Starter duplizieren <kbd data-command=\"duplicateSubtree\">"
},
"highlighting": {
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
@ -1677,7 +1701,7 @@
},
"code_block": {
"word_wrapping": "Wortumbruch",
"theme_none": "Keine Syntax-Hervorhebung",
"theme_none": "Keine Syntaxhervorhebung",
"theme_group_light": "Helle Themen",
"theme_group_dark": "Dunkle Themen",
"copy_title": "Kopiere in Zwischenablage"
@ -1720,7 +1744,8 @@
"open_note_in_new_tab": "Notiz in neuen Tab öffnen",
"open_note_in_new_split": "Notiz in neuen geteilten Tab öffnen",
"open_note_in_new_window": "Notiz in neuen Fenster öffnen",
"open_note_in_popup": "Schnellbearbeitung"
"open_note_in_popup": "Schnellbearbeitung",
"open_note_in_other_split": "Notiz in neuer Spalte öffnen"
},
"electron_integration": {
"desktop-application": "Desktop Anwendung",
@ -1975,7 +2000,7 @@
"check_share_root": "Status des Freigabe-Roots prüfen",
"share_root_found": "Freigabe-Root-Notiz '{{noteTitle}}' ist bereit",
"share_root_not_found": "Keine Notiz mit #shareRoot Label gefunden",
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht geteilt"
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht freigegeben"
},
"tasks": {
"due": {
@ -1988,8 +2013,9 @@
"unknown_widget": "Unbekanntes Widget für '{{id}}'."
},
"note_language": {
"not_set": "Nicht gesetzt",
"configure-languages": "Konfiguriere Sprachen..."
"not_set": "Keine Sprache ausgewählt",
"configure-languages": "Konfiguriere Sprachen...",
"help-on-languages": "Zu Übersetzungen beitragen..."
},
"content_language": {
"title": "Inhaltssprachen",
@ -2007,7 +2033,8 @@
"button_title": "Exportiere Diagramm als PNG"
},
"svg": {
"export_to_png": "Das Diagramm konnte als PNG nicht exportiert werden."
"export_to_png": "Das Diagramm konnte als PNG nicht exportiert werden.",
"export_to_svg": "Das Diagramm konnte nicht als SVG exportiert werden."
},
"code_theme": {
"title": "Aussehen",
@ -2055,7 +2082,7 @@
"book_properties_config": {
"hide-weekends": "Wochenenden ausblenden",
"display-week-numbers": "Zeige Kalenderwoche",
"map-style": "Kartenstil:",
"map-style": "Kartenstil",
"max-nesting-depth": "Maximale Verschachtelungstiefe:",
"raster": "Raster",
"vector_light": "Vektor (Hell)",
@ -2091,8 +2118,8 @@
"show_attachments_description": "Notizanhänge anzeigen",
"search_notes_title": "Suche Notiz",
"search_notes_description": "Öffne erweiterte Suche",
"search_subtree_title": "Im Unterzweig suchen",
"search_subtree_description": "Im aktuellen Unterzweig suchen",
"search_subtree_title": "Im Zweig suchen",
"search_subtree_description": "Im aktuellen Zweig suchen",
"search_history_title": "Zeige Suchhistorie",
"search_history_description": "Zeige vorherige Suchen",
"configure_launch_bar_title": "Startleiste anpassen",
@ -2108,14 +2135,20 @@
"background_effects_title": "Hintergrundeffekte sind jetzt zuverlässig nutzbar",
"background_effects_message": "Auf Windows-Geräten sind die Hintergrundeffekte nun vollständig stabil. Die Hintergrundeffekte verleihen der Benutzeroberfläche einen Farbakzent, indem der Hintergrund dahinter weichgezeichnet wird. Diese Technik wird auch in anderen Anwendungen wie dem Windows-Explorer eingesetzt.",
"background_effects_button": "Aktiviere Hintergrundeffekte",
"dismiss": "Ablehnen"
"dismiss": "Ablehnen",
"new_layout_title": "Neues Layout",
"new_layout_message": "Wir haben ein modernisiertes Layout für Trilium eingeführt. Die Multifunktionsleiste wurde entfernt und als neue Statusanzeige und ausklappbaren Sektionen (wie hervorgehobenen Attributen), welche Schlüsselfunktionen übernehmen, nahtlos in das Hauptinterface integriert.\n\nDas neue Layout ist standardmäßig aktiviert und kann temporär in Optionen → Anzeige deaktiviert werden.",
"new_layout_button": "Mehr Informationen"
},
"settings": {
"related_settings": "Ähnliche Einstellungen"
},
"settings_appearance": {
"related_code_blocks": "Farbschema für Code-Blöcke in Textnotizen",
"related_code_notes": "Farbschema für Code-Notizen"
"related_code_notes": "Farbschema für Code-Notizen",
"ui": "Benutzeroberfläche",
"ui_old_layout": "Altes Layout",
"ui_new_layout": "Neues Layout"
},
"units": {
"percentage": "%"
@ -2151,6 +2184,88 @@
"experimental_features": {
"title": "Experimentelle Optionen",
"disclaimer": "Diese Optionen sind experimentell und können Instabilitäten verursachen. Achtsam zu verwenden.",
"new_layout_name": "Neues Layout"
"new_layout_name": "Neues Layout",
"new_layout_description": "Probiere das neue Layout für eine modernere Darstellung und verbesserte Benutzbarkeit aus. Kann sich in Zukunft stark ändern."
},
"server": {
"unknown_http_error_title": "Kommunikationsfehler mit dem Server",
"unknown_http_error_content": "Statuscode: {{statusCode}}\nURL: {{method}} {{url}}\nNachricht: {{message}}",
"traefik_blocks_requests": "Der Traefik Reverse-Proxy hat eine Änderung erfahren, welches die Kommunikation mit dem Server beeinflusst."
},
"tab_history_navigation_buttons": {
"go-back": "Zur vorherigen Notiz zurück kehren",
"go-forward": "Zur nächsten Notiz"
},
"breadcrumb": {
"hoisted_badge": "Gehoben",
"hoisted_badge_title": "Abgesenkt",
"workspace_badge": "Arbeitsfläche",
"scroll_to_top_title": "Zum Anfang der Notiz springen",
"create_new_note": "Neue Unternotiz erstellen",
"empty_hide_archived_notes": "Archivierte Notizen ausblenden"
},
"breadcrumb_badges": {
"read_only_explicit": "Schreibgeschützt",
"read_only_explicit_description": "Diese Notiz wurde händisch schreibgeschützt.\nKlicke hier um sie temporär zu bearbeiten.",
"read_only_auto": "Automatisch schreibgeschützt",
"read_only_auto_description": "Diese Notiz wurde automatisch aus Leistungsgründen als schreibgeschützt markiert. Dieses automatische Limit kann in den Einstellungen angepasst werden.\n\nKlicke hier, um sie temporär zu bearbeiten.",
"read_only_temporarily_disabled": "Temporär bearbeitbar",
"read_only_temporarily_disabled_description": "Diese Notiz ist aktuell bearbeitbar, ist aber normalerweise schreibgeschützt. Sobald du zu einer anderen Notiz navigierst wird diese wieder schreibgeschützt.\n\nKlicke hier, um die Notiz wieder schreibgeschützt zu machen.",
"shared_publicly": "Öffentlich freigegeben",
"shared_locally": "Lokal freigegeben",
"shared_copy_to_clipboard": "Link in die Zwischenablage kopieren",
"shared_open_in_browser": "Link im Browser öffnen",
"shared_unshare": "Freigabe aufheben",
"clipped_note": "Internetschnellverweis",
"clipped_note_description": "Diese Notiz wurde von {{url}} übernommen.\n\nKlicke hier, um zur Quelle zu gehen.",
"execute_script": "Skript ausführen",
"execute_script_description": "Diese Notiz ist eine Skriptnotiz. Klicke hier, um das Skript auszuführen.",
"execute_sql": "SQL ausführen",
"execute_sql_description": "Diese Notiz ist eine SQL-Notiz. Klicke hier, um die SQL-Abfrage auszuführen.",
"save_status_saved": "Gespeichert",
"save_status_saving": "Speichere...",
"save_status_unsaved": "Nicht gespeichert",
"save_status_error": "Speichern fehlgeschlagen",
"save_status_saving_tooltip": "Änderungen werden gespeichert.",
"save_status_unsaved_tooltip": "Es gibt ungespeicherte Änderungen, welche gleich automatisch gespeichert werden.",
"save_status_error_tooltip": "Beim speichern der Notiz ist ein Fehler aufgetreten. Wenn möglich, versuche die Notiz woandershin zu kopieren und die Anwendung neu zu laden."
},
"status_bar": {
"language_title": "Inhaltssprache ändern",
"note_info_title": "Notizinfo anzeigen (z.B.: Datum, Notizgröße)",
"backlinks_one": "{{count}} Rücklink",
"backlinks_other": "{{count}} Rücklinks",
"backlinks_title_one": "Rücklink anzeigen",
"backlinks_title_other": "Rücklinks anzeigen",
"attachments_one": "{{count}} Anhang",
"attachments_other": "{{count}} Anhänge",
"attachments_title_one": "Anhang in einem neuen Tab öffnen",
"attachments_title_other": "Anhänge in einem neuen Tab öffnen",
"attributes_one": "{{count}} Attribut",
"attributes_other": "{{count}} Attribute",
"attributes_title": "Eigene und geerbte Attribute",
"note_paths_one": "{{count}} Pfad",
"note_paths_other": "{{count}} Pfade",
"note_paths_title": "Notizpfade",
"code_note_switcher": "Sprachmodus ändern"
},
"attributes_panel": {
"title": "Notizattribute"
},
"right_pane": {
"empty_message": "Für diese Notiz gibt es nichts anzuzeigen",
"empty_button": "Leiste ausblenden",
"toggle": "Rechte Leiste umschalten",
"custom_widget_go_to_source": "Zum Quellcode"
},
"pdf": {
"attachments_one": "{{count}} Anhang",
"attachments_other": "{{count}} Anhänge",
"layers_one": "{{count}} Ebene",
"layers_other": "{{count}} Ebenen",
"pages_one": "{{count}} Seite",
"pages_other": "{{count}} Seiten",
"pages_alt": "Seite {{pageNumber}}",
"pages_loading": "Lädt..."
}
}

View File

@ -69,5 +69,8 @@
"clear-color": "Clear note colour",
"set-color": "Set note colour",
"set-custom-color": "Set custom note colour"
},
"about": {
"title": "About Trilium Notes"
}
}

View File

@ -1815,7 +1815,11 @@
"configure_launchbar": "Configure Launchbar"
},
"sql_result": {
"no_rows": "No rows have been returned for this query"
"not_executed": "The query has not been executed yet.",
"no_rows": "No rows have been returned for this query",
"failed": "SQL query execution has failed",
"statement_result": "Statement result",
"execute_now": "Execute now"
},
"sql_table_schemas": {
"tables": "Tables"

View File

@ -28,7 +28,10 @@
},
"widget-render-error": {
"title": "Hubo un fallo al renderizar un widget personalizado de React"
}
},
"widget-missing-parent": "El widget personalizado no tiene definida la propiedad obligatoria '{{property}}'.\n\nSi este script está pensado para ejecutarse sin un elemento de interfaz de usuario, utilice '#run=frontendStartup' en su lugar.",
"open-script-note": "Abrir script",
"scripting-error": "Error en script personalizado: {{title}}"
},
"add_link": {
"add_link": "Agregar enlace",
@ -211,7 +214,8 @@
"info": {
"modalTitle": "Mensaje informativo",
"closeButton": "Cerrar",
"okButton": "Aceptar"
"okButton": "Aceptar",
"copy_to_clipboard": "Copiar al portapapeles"
},
"jump_to_note": {
"search_button": "Buscar en texto completo",
@ -697,7 +701,13 @@
"convert_into_attachment_successful": "La nota '{{title}}' ha sido convertida a un archivo adjunto.",
"convert_into_attachment_prompt": "¿Está seguro que desea convertir la nota '{{title}}' en un archivo adjunto de la nota padre?",
"print_pdf": "Exportar como PDF...",
"open_note_on_server": "Abrir nota en servidor"
"open_note_on_server": "Abrir nota en servidor",
"view_revisions": "Revisiones de la nota...",
"advanced": "Avanzado",
"export_as_image": "Exportar como imagen",
"export_as_image_png": "PNG (ráster)",
"export_as_image_svg": "SVG (vectorial)",
"note_map": "Mapa de la nota"
},
"onclick_button": {
"no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido"
@ -759,7 +769,13 @@
"reset-default": "Restablecer a icono por defecto",
"search_placeholder_one": "Buscar {{number}} icono a través de {{count}} paquetes",
"search_placeholder_many": "Buscar {{number}} iconos a través de {{count}} paquetes",
"search_placeholder_other": "Buscar {{number}} iconos a través de {{count}} paquetes"
"search_placeholder_other": "Buscar {{number}} iconos a través de {{count}} paquetes",
"search_placeholder_filtered": "Buscar {{number}} iconos en {{name}}",
"filter": "Filtro",
"filter-none": "Todos los iconos",
"filter-default": "Iconos predeterminados",
"icon_tooltip": "{{name}}\nPaquete de iconos: {{iconPack}}",
"no_results": "No se encontraron iconos."
},
"basic_properties": {
"note_type": "Tipo de nota",
@ -783,10 +799,11 @@
"board": "Tablero",
"include_archived_notes": "Mostrar notas archivadas",
"presentation": "Presentación",
"expand_tooltip": "Expande las notas hijas inmediatas de esta colección (un nivel). Para más opciones, pulsa la flecha a la derecha.",
"expand_tooltip": "Expande las subnotas inmediatas de esta colección (un nivel). Para más opciones, pulsa la flecha a la derecha.",
"expand_first_level": "Expandir hijos inmediatos",
"expand_nth_level": "Expandir {{depth}} niveles",
"expand_all_levels": "Expandir todos los niveles"
"expand_all_levels": "Expandir todos los niveles",
"hide_child_notes": "Ocultar subnotas en el árbol"
},
"edited_notes": {
"no_edited_notes_found": "Aún no hay notas editadas en este día...",
@ -819,7 +836,8 @@
},
"inherited_attribute_list": {
"title": "Atributos heredados",
"no_inherited_attributes": "Sin atributos heredados."
"no_inherited_attributes": "Sin atributos heredados.",
"none": "ninguno"
},
"note_info_widget": {
"note_id": "ID de nota",
@ -830,7 +848,9 @@
"note_size_info": "El tamaño de la nota proporciona una estimación aproximada de los requisitos de almacenamiento para esta nota. Toma en cuenta el contenido de la nota y el contenido de sus revisiones de nota.",
"calculate": "calcular",
"subtree_size": "(tamaño del subárbol: {{size}} en {{count}} notas)",
"title": "Información de nota"
"title": "Información de nota",
"mime": "Tipo MIME",
"show_similar_notes": "Mostrar notas similares"
},
"note_map": {
"open_full": "Ampliar al máximo",
@ -893,7 +913,8 @@
"search_parameters": "Parámetros de búsqueda",
"unknown_search_option": "Opción de búsqueda desconocida {{searchOptionName}}",
"search_note_saved": "La nota de búsqueda se ha guardado en {{- notePathTitle}}",
"actions_executed": "Las acciones han sido ejecutadas."
"actions_executed": "Las acciones han sido ejecutadas.",
"view_options": "Ver opciones:"
},
"similar_notes": {
"title": "Notas similares",
@ -996,7 +1017,13 @@
},
"editable_text": {
"placeholder": "Escribe aquí el contenido de tu nota...",
"auto-detect-language": "Detectado automáticamente"
"auto-detect-language": "Detectado automáticamente",
"editor_crashed_title": "El editor de texto ha dejado de responder",
"editor_crashed_content": "Su contenido ha sido recuperado con éxito, pero puede que algunos de sus cambios más recientes no se hayan guardado.",
"editor_crashed_details_button": "Ver más detalles...",
"editor_crashed_details_intro": "Si experimenta este error varias veces, considere informarlo en GitHub adjuntando la siguiente información.",
"editor_crashed_details_title": "Información técnica",
"keeps-crashing": "El componente de edición sigue fallando. Por favor, intente reiniciar Trilium. Si el problema persiste, considere crear un informe de fallos."
},
"empty": {
"open_note_instruction": "Abra una nota escribiendo el título de la nota en la entrada a continuación o elija una nota en el árbol.",
@ -1312,8 +1339,8 @@
"code_mime_types": {
"title": "Tipos MIME disponibles en el menú desplegable",
"tooltip_syntax_highlighting": "Resaltado de sintaxis",
"tooltip_code_block_syntax": "Bloques de Código en notas de Texto",
"tooltip_code_note_syntax": "Notas de Código"
"tooltip_code_block_syntax": "Bloques de código en Notas de texto",
"tooltip_code_note_syntax": "Notas de código"
},
"vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "Combinaciones de teclas Vim",
@ -1390,16 +1417,16 @@
"markdown": "Estilo Markdown"
},
"highlights_list": {
"title": "Lista de aspectos destacados",
"description": "Puede personalizar la lista de aspectos destacados que se muestra en el panel derecho:",
"title": "Lista de puntos destacados",
"description": "Puede personalizar la lista de puntos destacados que se muestra en el panel derecho:",
"bold": "Texto en negrita",
"italic": "Texto en cursiva",
"underline": "Texto subrayado",
"color": "Texto con color",
"bg_color": "Texto con color de fondo",
"visibility_title": "Visibilidad de la lista de aspectos destacados",
"visibility_description": "Puede ocultar el widget de aspectos destacados por nota agregando una etiqueta #hideHighlightWidget.",
"shortcut_info": "Puede configurar un método abreviado de teclado para alternar rápidamente el panel derecho (incluidos los aspectos destacados) en Opciones -> Atajos (nombre 'toggleRightPane')."
"visibility_title": "Visibilidad de la lista de puntos destacados",
"visibility_description": "Puede ocultar el widget de puntos destacados por nota agregando la etiqueta #hideHighlightWidget.",
"shortcut_info": "Puede configurar un método abreviado de teclado para alternar rápidamente el panel derecho (incluidos los puntos destacados) en Opciones -> Atajos (nombre 'toggleRightPane')."
},
"table_of_contents": {
"title": "Tabla de contenido",
@ -1635,7 +1662,10 @@
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres? Esta operación solo aplica a notas de Imagen, otras notas serán omitidas.",
"open-in-popup": "Edición rápida",
"archive": "Archivar",
"unarchive": "Desarchivar"
"unarchive": "Desarchivar",
"open-in-a-new-window": "Abrir en una nueva ventana",
"hide-subtree": "Ocultar subárbol",
"show-subtree": "Mostrar subárbol"
},
"shared_info": {
"shared_publicly": "Esta nota está compartida públicamente en {{- link}}.",
@ -1696,7 +1726,13 @@
},
"highlights_list_2": {
"title": "Lista de destacados",
"options": "Opciones"
"options": "Opciones",
"title_with_count_one": "{{count}} punto destacado",
"title_with_count_many": "{{count}} puntos destacados",
"title_with_count_other": "{{count}} puntos destacados",
"modal_title": "Configurar la lista de puntos destacados",
"menu_configure": "Configurar la lista de puntos destacados...",
"no_highlights": "Ningún punto destacado encontrado."
},
"quick-search": {
"placeholder": "Búsqueda rápida",
@ -1719,7 +1755,18 @@
"refresh-saved-search-results": "Refrescar resultados de búsqueda guardados",
"create-child-note": "Crear subnota",
"unhoist": "Desanclar",
"toggle-sidebar": "Alternar barra lateral"
"toggle-sidebar": "Alternar barra lateral",
"dropping-not-allowed": "No está permitido soltar notas en esta ubicación.",
"clone-indicator-tooltip": "Esta nota tiene {{- count}} padres: {{- parents}}",
"clone-indicator-tooltip-single": "Esta nota está clonada (1 padre adicional: {{- parent}})",
"shared-indicator-tooltip": "Esta nota está compartida públicamente",
"shared-indicator-tooltip-with-url": "Esta nota está compartida públicamente en: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} subnota que está oculta del árbol",
"subtree-hidden-tooltip_many": "{{count}} subnotas que están ocultas del árbol",
"subtree-hidden-tooltip_other": "{{count}} subnotas que están ocultas del árbol",
"subtree-hidden-moved-title": "Agregado a {{title}}",
"subtree-hidden-moved-description-collection": "Esta colección oculta sus subnotas en el árbol.",
"subtree-hidden-moved-description-other": "Las subnotas están ocultas en el árbol para esta nota."
},
"title_bar_buttons": {
"window-on-top": "Mantener esta ventana en la parte superior"
@ -1730,10 +1777,21 @@
"printing_pdf": "Exportando a PDF en curso..",
"print_report_collection_content_one": "{{count}} nota en la colección no se puede imprimir porque no son compatibles o está protegida.",
"print_report_collection_content_many": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.",
"print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas."
"print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.",
"print_report_title": "Imprimir informe",
"print_report_collection_details_button": "Ver detalles",
"print_report_collection_details_ignored_notes": "Notas ignoradas"
},
"note_title": {
"placeholder": "escriba el título de la nota aquí..."
"placeholder": "escriba el título de la nota aquí...",
"created_on": "Creado en <Value />",
"last_modified": "Modificado en <Value />",
"note_type_switcher_label": "Cambiar de {{type}} a:",
"note_type_switcher_others": "Otro tipo de nota",
"note_type_switcher_templates": "Plantilla",
"note_type_switcher_collection": "Colección",
"edited_notes": "Notas editadas en este día",
"promoted_attributes": "Atributos promovidos"
},
"search_result": {
"no_notes_found": "No se han encontrado notas para los parámetros de búsqueda dados.",
@ -1743,7 +1801,11 @@
"configure_launchbar": "Configurar barra de lanzamiento"
},
"sql_result": {
"no_rows": "No se han devuelto filas para esta consulta"
"no_rows": "No se han devuelto filas para esta consulta",
"not_executed": "La consulta aún no ha sido ejecutada.",
"failed": "La ejecución de la consulta SQL ha fallado",
"statement_result": "Resultado de declaración",
"execute_now": "Ejecutar ahora"
},
"sql_table_schemas": {
"tables": "Tablas"
@ -1762,7 +1824,8 @@
},
"toc": {
"table_of_contents": "Tabla de contenido",
"options": "Opciones"
"options": "Opciones",
"no_headings": "Sin encabezados."
},
"watched_file_update_status": {
"file_last_modified": "Archivo <code class=\"file-path\"></code> ha sido modificado por última vez en<span class=\"file-last-modified\"></span>.",
@ -1874,7 +1937,8 @@
"open_note_in_new_tab": "Abrir nota en una pestaña nueva",
"open_note_in_new_split": "Abrir nota en una nueva división",
"open_note_in_new_window": "Abrir nota en una nueva ventana",
"open_note_in_popup": "Edición rápida"
"open_note_in_popup": "Edición rápida",
"open_note_in_other_split": "Abrir nota en la otra división"
},
"electron_integration": {
"desktop-application": "Aplicación de escritorio",
@ -1943,10 +2007,11 @@
},
"note_language": {
"not_set": "Idioma no establecido",
"configure-languages": "Configurar idiomas..."
"configure-languages": "Configurar idiomas...",
"help-on-languages": "Ayuda en idiomas de contenido..."
},
"content_language": {
"title": "Contenido de idiomas",
"title": "Idiomas de contenido",
"description": "Seleccione uno o más idiomas que deben aparecer en la selección del idioma en la sección Propiedades Básicas de una nota de texto de solo lectura o editable. Esto permitirá características tales como corrección de ortografía o soporte de derecha a izquierda."
},
"switch_layout_button": {
@ -1961,7 +2026,8 @@
"button_title": "Exportar diagrama como PNG"
},
"svg": {
"export_to_png": "El diagrama no pudo ser exportado a PNG."
"export_to_png": "El diagrama no pudo ser exportado a PNG.",
"export_to_svg": "El diagrama no pudo ser exportado a SVG."
},
"code_theme": {
"title": "Apariencia",
@ -2066,7 +2132,10 @@
"background_effects_title": "Los efectos de fondo son ahora estables",
"background_effects_message": "En los dispositivos Windows, los efectos de fondo ya son totalmente estables. Los efectos de fondo añaden un toque de color a la interfaz de usuario difuminando el fondo que hay detrás. Esta técnica también se utiliza en otras aplicaciones como el Explorador de Windows.",
"background_effects_button": "Activar efectos de fondo",
"dismiss": "Desestimar"
"dismiss": "Desestimar",
"new_layout_title": "Nuevo diseño",
"new_layout_message": "Hemos introducido un diseño modernizado para Trilium. La cinta se ha eliminado y se ha integrado perfectamente en la interfaz principal, con una nueva barra de estado y secciones ampliables (como los atributos promovidos) que tienen funciones clave.\n\nEl nuevo diseño está habilitado por defecto, y puede ser deshabilitado temporalmente a través de Opciones → Apariencia.",
"new_layout_button": "Más información"
},
"ui-performance": {
"title": "Rendimiento",
@ -2081,7 +2150,10 @@
},
"settings_appearance": {
"related_code_blocks": "Esquema de colores para bloques de código en notas de texto",
"related_code_notes": "Esquema de colores para notas de código"
"related_code_notes": "Esquema de colores para notas de código",
"ui": "Interfaz de usuario",
"ui_old_layout": "Antiguo diseño",
"ui_new_layout": "Nuevo diseño"
},
"units": {
"percentage": "%"
@ -2129,7 +2201,12 @@
"attributes_other": "{{count}} atributos",
"note_paths_one": "{{count}} ruta",
"note_paths_many": "{{count}} rutas",
"note_paths_other": "{{count}} rutas"
"note_paths_other": "{{count}} rutas",
"language_title": "Cambiar el idioma del contenido",
"note_info_title": "Ver información de la nota (p. e., fechas, tamaño de la nota)",
"attributes_title": "Atributos propios y atributos heredados",
"note_paths_title": "Rutas de nota",
"code_note_switcher": "Cambiar modo de idioma"
},
"pdf": {
"attachments_one": "{{count}} adjunto",
@ -2140,6 +2217,69 @@
"layers_other": "{{count}} capas",
"pages_one": "{{count}} página",
"pages_many": "{{count}} páginas",
"pages_other": "{{count}} páginas"
"pages_other": "{{count}} páginas",
"pages_alt": "Página {{pageNumber}}",
"pages_loading": "Cargando..."
},
"experimental_features": {
"title": "Opciones experimentales",
"disclaimer": "Estas opciones son experimentales y pueden causar inestabilidad. Úselas con precaución.",
"new_layout_name": "Nuevo diseño",
"new_layout_description": "Pruebe el nuevo diseño para tener un aspecto más moderno y usabilidad mejorada. Sujeto a grandes cambios en las próximas versiones."
},
"popup-editor": {
"maximize": "Cambiar a editor completo"
},
"server": {
"unknown_http_error_title": "Error de comunicación con el servidor",
"unknown_http_error_content": "Código de estado: {{statusCode}}\nURL: {{method}} {{url}}\nMensaje: {{message}}",
"traefik_blocks_requests": "Si está usando el proxy inverso Traefik, este introdujo un cambio que afecta la comunicación con el servidor."
},
"tab_history_navigation_buttons": {
"go-back": "Volver a la nota anterior",
"go-forward": "Avanzar a la siguiente nota"
},
"breadcrumb": {
"hoisted_badge": "Anclada",
"hoisted_badge_title": "Desanclar",
"workspace_badge": "Espacio de trabajo",
"scroll_to_top_title": "Saltar al inicio de la nota",
"create_new_note": "Crear nueva subnota",
"empty_hide_archived_notes": "Ocultar notas archivadas"
},
"breadcrumb_badges": {
"read_only_explicit": "Sólo lectura",
"read_only_explicit_description": "Esta nota se ha fijado manualmente como sólo lectura.\nHaga clic para editarla temporalmente.",
"read_only_auto": "Sólo lectura automática",
"read_only_auto_description": "Esta nota se fijó automáticamente con el modo de sólo lectura por razones de rendimiento. Este límite automático es ajustable desde los ajustes.\n\nHaga clic para editarla temporalmente.",
"read_only_temporarily_disabled": "Temporalmente editable",
"read_only_temporarily_disabled_description": "Esta nota actualmente es editable, pero normalmente es de sólo lectura. La nota volverá a ser de sólo lectura tan pronto como navegue a otra nota.\n\nHaga clic para volver a habilitar el modo de sólo lectura.",
"shared_publicly": "Compartida públicamente",
"shared_locally": "Compartida localmente",
"shared_copy_to_clipboard": "Copiar enlace al portapapeles",
"shared_open_in_browser": "Abrir enlace en el navegador",
"shared_unshare": "Eliminar compartido",
"clipped_note_description": "Esta nota fue tomada originalmente de {{url}}.\n\nHaga clic para navegar a la página web de origen.",
"execute_script": "Ejecutar script",
"execute_script_description": "Esta nota es una nota de script. Haga clic para ejecutar el script.",
"execute_sql": "Ejecutar SQL",
"execute_sql_description": "Esta nota es una nota SQL. Haga clic para ejecutar la consulta SQL.",
"save_status_saved": "Guardado",
"save_status_saving": "Guardando...",
"save_status_unsaved": "Sin guardar",
"save_status_error": "Fallo al guardar",
"save_status_saving_tooltip": "Los cambios están siendo guardados.",
"save_status_unsaved_tooltip": "Hay cambios sin guardar. Se guardarán automáticamente en un momento.",
"save_status_error_tooltip": "Se produjo un error al guardar la nota. Si es posible, trate de copiar el contenido de la nota en otro lugar y recargar la aplicación.",
"clipped_note": "Clip web"
},
"attributes_panel": {
"title": "Atributos de nota"
},
"right_pane": {
"empty_message": "Nada que mostrar para esta nota",
"empty_button": "Ocultar el panel",
"toggle": "Alternar panel derecho",
"custom_widget_go_to_source": "Ir al código fuente"
}
}

View File

@ -11,8 +11,8 @@
},
"toast": {
"critical-error": {
"title": "Kesalahan kritis",
"message": "Telah terjadi kesalahan kritis yang mencegah aplikasi klien untuk memulai:\n\n{{message}}\n\nHal ini kemungkinan besar disebabkan oleh skrip yang gagal secara tidak terduga. Coba jalankan aplikasi dalam mode aman dan atasi masalahnya."
"title": "Eror kritikal",
"message": "Telah terjadi eror kritikal yang mencegah aplikasi klien untuk memulai:\n\n{{message}}\n\nHal ini kemungkinan besar disebabkan oleh skrip yang gagal secara tidak terduga. Coba jalankan aplikasi dalam mode aman dan atasi masalahnya."
},
"widget-error": {
"title": "Gagal menginisialisasi widget",
@ -21,12 +21,65 @@
},
"bundle-error": {
"title": "Gagal memuat skrip kustom",
"message": "Skrip dari catatan dengan ID \"{{id}}\", berjudul \"{{title}}\" tidak dapat dijalankan karena:\n\n{{message}}"
}
"message": "Skrip tidak dapat dijalankan:\n\n{{message}}"
},
"widget-list-error": {
"title": "Gagal mendapatkan daftar widget dari server"
},
"open-script-note": "Buka skrip catatan",
"widget-render-error": {
"title": "Gagal render widget React custom"
},
"widget-missing-parent": "Widget custom '{{property}}' tidak terdefinisi.\n\nJika skrip ini bermaksud untuk bisa dijalankan tanpa elemen UI, gunakanlah '#run=frontendStartup'.",
"scripting-error": "Skrip custom eror : {{title}}"
},
"add_link": {
"add_link": "Tambah tautan",
"help_on_links": "Bantuan pada tautan",
"note": "Catatan"
"note": "Catatan",
"search_note": "cari catatan berdasarkan nama",
"link_title_mirrors": "judul tautan mencerminkan judul catatan saat ini",
"link_title_arbitrary": "judul tautan dapat diubah secara bebas",
"link_title": "Judul tautan",
"button_add_link": "Tambah tautan"
},
"branch_prefix": {
"edit_branch_prefix_multiple": "Edit prefiks cabang untuk {{count}} cabang",
"help_on_tree_prefix": "Bantuan pada prefiks pohon catatan",
"prefix": "Prefiks: ",
"save": "Simpan",
"branch_prefix_saved": "Prefiks cabang telah disimpan.",
"branch_prefix_saved_multiple": "Prefix cabang telah disimpan pada {{count}} cabang.",
"affected_branches": "Cabang terdampak ({{count}}):"
},
"bulk_actions": {
"bulk_actions": "Aksi borongan",
"affected_notes": "Catatan terdampak",
"include_descendants": "Sertakan anakan dari catatan yang dipilih",
"available_actions": "Pilihan aksi",
"chosen_actions": "Aksi terpilih",
"execute_bulk_actions": "Eksekusi aksi borongan",
"bulk_actions_executed": "Aksi borongan telah di eksekusi dengan sukses.",
"none_yet": "Belum ada... tambahkan aksi dengan memilih salah satu dari aksi di atas.",
"labels": "Label-label"
},
"confirm": {
"cancel": "Batal",
"ok": "Oke",
"are_you_sure_remove_note": "Apakah anda yakin mau membuang catatan \"{{title}}\" dari peta relasi? ",
"if_you_dont_check": "Jika Anda tidak mencentang ini, catatan hanya akan dihapus dari peta relasi.",
"also_delete_note": "Hapus juga catatannya"
},
"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.",
"erase_notes_warning": "Hapus catatan secara permanen (tidak bisa dikembalikan), termasuk semua duplikat. Aksi akan memaksa aplikasi untuk mengulang kembali.",
"notes_to_be_deleted": "Catatan-catatan berikut akan dihapuskan ({{notesCount}})",
"no_note_to_delete": "Tidak ada Catatan yang akan dihapus (hanya duplikat)."
},
"clone_to": {
"clone_notes_to": "Duplikat catatan ke…"
}
}

View File

@ -325,7 +325,10 @@
"apply-bulk-actions": "Applica azioni in blocco",
"converted-to-attachments": "{{count}} note sono state convertite in allegati.",
"convert-to-attachment-confirm": "Sei sicuro di voler convertire le note selezionate in allegati delle note principali? Questa operazione si applica solo alle note immagine, le altre note verranno ignorate.",
"open-in-popup": "Modifica rapida"
"open-in-popup": "Modifica rapida",
"open-in-a-new-window": "Apri in una nuova finestra",
"hide-subtree": "Nascondi sottostruttura",
"show-subtree": "Mostra sottoalbero"
},
"electron_context_menu": {
"cut": "Taglia",
@ -1378,7 +1381,8 @@
"expand_tooltip": "Espande i figli diretti di questa raccolta (a un livello di profondità). Per ulteriori opzioni, premere la freccia a destra.",
"expand_first_level": "Espandi figli diretti",
"expand_nth_level": "Espandi {{depth}} livelli",
"expand_all_levels": "Espandi tutti i livelli"
"expand_all_levels": "Espandi tutti i livelli",
"hide_child_notes": "Nascondi note secondarie nell'albero"
},
"edited_notes": {
"no_edited_notes_found": "Nessuna nota modificata per questo giorno...",
@ -1899,7 +1903,13 @@
"clone-indicator-tooltip": "Questa nota ha {{- count}} genitori: {{- parents}}",
"clone-indicator-tooltip-single": "Questa nota è stata clonata (1 genitore aggiuntivo: {{- parent}})",
"shared-indicator-tooltip": "Questa nota è condivisa pubblicamente",
"shared-indicator-tooltip-with-url": "Questa nota è condivisa pubblicamente all'indirizzo: {{- url}}"
"shared-indicator-tooltip-with-url": "Questa nota è condivisa pubblicamente all'indirizzo: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} nota secondaria nascosta dall'albero",
"subtree-hidden-tooltip_many": "{{count}} note secondarie nascoste dall'albero",
"subtree-hidden-tooltip_other": "{{count}} note secondarie nascoste dall'albero",
"subtree-hidden-moved-title": "Aggiunto a {{title}}",
"subtree-hidden-moved-description-collection": "Questa raccolta nasconde le sue note secondarie nell'albero.",
"subtree-hidden-moved-description-other": "Le note secondarie sono nascoste nell'albero di questa nota."
},
"title_bar_buttons": {
"window-on-top": "Mantieni la finestra in primo piano"
@ -1934,7 +1944,11 @@
"configure_launchbar": "Configura Launchbar"
},
"sql_result": {
"no_rows": "Nessuna riga è stata restituita per questa query"
"no_rows": "Nessuna riga è stata restituita per questa query",
"not_executed": "La query non è stata ancora eseguita.",
"failed": "Esecuzione query SQL non riuscita",
"statement_result": "Risultato della dichiarazione",
"execute_now": "Esegui ora"
},
"watched_file_update_status": {
"file_last_modified": "Il file <code class=\"file-path\"></code> è stato modificato l'ultima volta il <span class=\"file-last-modified\"></span>.",

View File

@ -1288,7 +1288,11 @@
"search_not_executed": "検索はまだ実行されていません。上の「検索」ボタンをクリックすると、検索結果が表示されます。"
},
"sql_result": {
"no_rows": "このクエリでは行が返されませんでした"
"no_rows": "このクエリでは行が返されませんでした",
"not_executed": "クエリはまだ実行されていません。",
"failed": "SQLクエリの実行に失敗しました",
"statement_result": "ステートメント結果",
"execute_now": "今すぐ実行"
},
"sql_table_schemas": {
"tables": "テーブル"

View File

@ -60,5 +60,23 @@
},
"include_note": {
"label_note": "Notat"
},
"prompt": {
"title": "Ledetekst",
"ok": "OK",
"defaultTitle": "Ledetekst"
},
"info": {
"closeButton": "Lukk",
"okButton": "OK"
},
"markdown_import": {
"import_button": "Importer"
},
"protected_session_password": {
"close_label": "Lukk"
},
"recent_changes": {
"undelete_link": "gjenopprett"
}
}

View File

@ -772,7 +772,10 @@
"filter-default": "Icons default",
"no_results": "Não foram encontrados icons.",
"search_placeholder_filtered": "Procurar {{number}} icons no {{name}}",
"icon_tooltip": "{{name}}\nPacote de icons: {{iconPack}}"
"icon_tooltip": "{{name}}\nPacote de icons: {{iconPack}}",
"search_placeholder_one": "Procurar {{number}} icon nos {{count}} pacotes",
"search_placeholder_many": "Procurar {{number}} icons em {{count}} pacotes",
"search_placeholder_other": "Procurar {{number}} icons nos {{count}} pacotes"
},
"basic_properties": {
"note_type": "Tipo da nota",
@ -799,7 +802,8 @@
"expand_nth_level": "Expandir {{depth}} níveis",
"expand_all_levels": "Expandir todos os níveis",
"include_archived_notes": "Mostrar notas arquivadas",
"expand_tooltip": "Expande a direcção dos descendentes desta colecção (um nível). Para mais opções, carregar na seta à direita."
"expand_tooltip": "Expande a direcção dos descendentes desta colecção (um nível). Para mais opções, carregar na seta à direita.",
"hide_child_notes": "Esconder notas descendentes na árvore"
},
"edited_notes": {
"no_edited_notes_found": "Ainda não há nenhuma nota editada neste dia…",
@ -1017,7 +1021,9 @@
"editor_crashed_title": "O editor de texto quebrou",
"editor_crashed_details_button": "Ver mais detalhes...",
"editor_crashed_details_title": "Informação técnica",
"editor_crashed_details_intro": "Se teve este erro várias vezes, considerer reportar no GitHub disponibilizando a informação abaixo."
"editor_crashed_details_intro": "Se teve este erro várias vezes, considerer reportar no GitHub disponibilizando a informação abaixo.",
"editor_crashed_content": "O seu conteudo foi recuperado com sucesso, mas alguns das alterações mais recentes podem não ter sido gravadas.",
"keeps-crashing": "Componente de edição a rebentar continuamente. Por favor tentar reiniciar Trilium. Se o problema persistir, considere abrir um bug report."
},
"empty": {
"open_note_instruction": "Abra uma nota a digitar o título da nota no campo abaixo ou escolha uma nota na árvore.",
@ -1145,7 +1151,8 @@
"title": "Largura do Conteúdo",
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em ecrãs largos.",
"max_width_label": "Largura máxima do conteúdo",
"max_width_unit": "pixels"
"max_width_unit": "pixels",
"centerContent": "Manter conteúdo centrado"
},
"native_title_bar": {
"title": "Barra de Título Nativa (requer recarregar a app)",
@ -1177,7 +1184,9 @@
"title": "Desempenho",
"enable-motion": "Ativar transições e animações",
"enable-shadows": "Ativar sombras",
"enable-backdrop-effects": "Ativar efeitos de fundo para menus, popups e painéis"
"enable-backdrop-effects": "Ativar efeitos de fundo para menus, popups e painéis",
"enable-smooth-scroll": "Activar deslocamento suave",
"app-restart-required": "(é necessário reiniciar a aplicação para aplicar as alterações)"
},
"ai_llm": {
"not_started": "Não iniciado",
@ -1336,7 +1345,10 @@
"title": "Editor"
},
"code_mime_types": {
"title": "Tipos MIME disponíveis no dropdown"
"title": "Tipos MIME disponíveis no dropdown",
"tooltip_syntax_highlighting": "Destaque de sintaxe",
"tooltip_code_block_syntax": "Blocos de código nas notas de texto",
"tooltip_code_note_syntax": "Notas de código"
},
"vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "Atribuições de teclas do Vim",
@ -1456,7 +1468,13 @@
"min-days-in-first-week": "Mínimo de dias da primeira semana",
"first-week-info": "Primeira semana que contenha a primeira Quinta-feira do ano é baseado na <a href=\"https://en.wikipedia.org/wiki/ISO_week_date#First_week\">ISO 8601</a>.",
"first-week-warning": "Alterar as opções de primeira semana pode causar duplicidade nas Notas Semanais existentes e estas Notas não serão atualizadas de acordo.",
"formatting-locale": "Formato de data e número"
"formatting-locale": "Formato de data e número",
"tuesday": "Terça-feira",
"wednesday": "Quarta-feira",
"thursday": "Quinta-feira",
"friday": "Sexta-feira",
"saturday": "Sábado",
"formatting-locale-auto": "Baseado na linguagem da aplicação"
},
"backup": {
"automatic_backup": "Backup automático",
@ -1549,7 +1567,8 @@
"oauth_description_warning": "Para ativar o OAuth/OpenID, precisa definir a URL base do OAuth/OpenID, o client ID e o client secret no ficheiro config.ini e reiniciar a aplicação. Se quiser configurar via variáveis de ambiente, defina TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID e TRILIUM_OAUTH_CLIENT_SECRET.",
"oauth_user_account": "Conta do Utilizador: ",
"oauth_user_email": "E-mail do Utilizador: ",
"oauth_user_not_logged_in": "Não está logado!"
"oauth_user_not_logged_in": "Não está logado!",
"oauth_missing_vars": "Configurações em falta: {{-variables}}"
},
"shortcuts": {
"keyboard_shortcuts": "Atalhos de Teclado",
@ -1649,7 +1668,12 @@
"apply-bulk-actions": "Aplicar ações em massa",
"converted-to-attachments": "{{count}} notas foram convertidas em anexos.",
"convert-to-attachment-confirm": "Tem certeza que deseja converter as notas selecionadas em anexos das suas notas-pai?",
"open-in-popup": "Edição rápida"
"open-in-popup": "Edição rápida",
"open-in-a-new-window": "Abrir numa nova janela",
"archive": "Arquivar",
"unarchive": "Retirar do arquivo",
"hide-subtree": "Esconder sub-árvore",
"show-subtree": "Mostrar sub-árvore"
},
"shared_info": {
"shared_publicly": "Esta nota é partilhada publicamente em {{- link}}.",
@ -1710,7 +1734,13 @@
},
"highlights_list_2": {
"title": "Lista de Destaques",
"options": "Opções"
"options": "Opções",
"no_highlights": "Sem destaques encontrados.",
"menu_configure": "Configurar lista de destaques...",
"modal_title": "Configurar list de destaques",
"title_with_count_one": "{{count}} destaque",
"title_with_count_many": "{{count}} destaques",
"title_with_count_other": "{{count}} destaques"
},
"quick-search": {
"placeholder": "Pesquisa rápida",
@ -1733,16 +1763,43 @@
"refresh-saved-search-results": "Atualizar resultados de pesquisa gravados",
"create-child-note": "Criar nota filha",
"unhoist": "Desafixar",
"toggle-sidebar": "Alternar barra lateral"
"toggle-sidebar": "Alternar barra lateral",
"dropping-not-allowed": "Largar notas nesta localização não é permitida",
"clone-indicator-tooltip": "Esta nota tem {{- count}} ascendentes: {{- parents}}",
"shared-indicator-tooltip": "Esta nota está partilhada publicamente",
"shared-indicator-tooltip-with-url": "Esta nota está partilhada publicamente em: {{- url}}",
"subtree-hidden-moved-title": "Adicionar ao {{title}}",
"subtree-hidden-moved-description-collection": "Esta colecção esconde as notas descendentes na árvore.",
"subtree-hidden-moved-description-other": "Notas descendentes estão escondidades na árvore para esta nota.",
"subtree-hidden-tooltip_one": "{{count}} nota descendentes escondidas da árvore",
"subtree-hidden-tooltip_many": "{{count}} notas descendentes escondidas da árvore",
"subtree-hidden-tooltip_other": "{{count}} notas descendentes escondidas da árvore",
"clone-indicator-tooltip-single": "Esta nota está clonada (1 additional parent: {{- parent}})"
},
"title_bar_buttons": {
"window-on-top": "Manter Janela no Topo"
},
"note_detail": {
"could_not_find_typewidget": "Não foi possível encontrar typeWidget para o tipo '{{type}}'"
"could_not_find_typewidget": "Não foi possível encontrar typeWidget para o tipo '{{type}}'",
"print_report_collection_details_button": "Ver detalhes",
"printing": "Impressão em progresso...",
"printing_pdf": "Exportação PDF em progresso...",
"print_report_title": "Imprimir relatório",
"print_report_collection_details_ignored_notes": "Ignorar notas",
"print_report_collection_content_one": "{{count}} nota na colecção não pode ser impressa porque não é suportado ou está protegida.",
"print_report_collection_content_many": "{{count}} notas na colecção não podem ser impressas porque não é suportado ou estão protegidas.",
"print_report_collection_content_other": "{{count}} notas na colecção não podem ser impressas porque não é suportado ou estão protegidas."
},
"note_title": {
"placeholder": "digite o título da nota aqui..."
"placeholder": "digite o título da nota aqui...",
"promoted_attributes": "Atributos destacados",
"created_on": "Criado em <Value />",
"last_modified": "Modificado em <Value />",
"note_type_switcher_label": "Alterar de {{type}} para:",
"note_type_switcher_others": "Outro tipo de nota",
"note_type_switcher_templates": "Template",
"note_type_switcher_collection": "Colecção",
"edited_notes": "Notas editadas neste dia"
},
"search_result": {
"no_notes_found": "Nenhuma nota encontrada para os parâmetros de pesquisa digitados.",
@ -1771,7 +1828,8 @@
},
"toc": {
"table_of_contents": "Tabela de Conteúdos",
"options": "Opções"
"options": "Opções",
"no_headings": "Sem cabeçalhos."
},
"watched_file_update_status": {
"file_last_modified": "O ficheiro <code class=\"file-path\"></code> foi modificado pela última vez em <span class=\"file-last-modified\"></span>.",
@ -1814,7 +1872,9 @@
"ws": {
"sync-check-failed": "A verificação de sincronização falhou!",
"consistency-checks-failed": "A verificação de consistência falhou! Veja os logs para pormenores.",
"encountered-error": "Encontrado o erro \"{{message}}\", verifique o console."
"encountered-error": "Encontrado o erro \"{{message}}\", verifique o console.",
"lost-websocket-connection-title": "Perdida conexão com o servidor",
"lost-websocket-connection-message": "Verifique a configuração da proxy inversa (e.g. nginx ou Apache) para assegurar conexões WebSocket estão permitidas e não bloqueadas."
},
"hoisted_note": {
"confirm_unhoisting": "A nota solicitada '{{requestedNote}}' está fora da árvore da nota fixada '{{hoistedNote}}' e precisa desafixar para aceder a nota. Quer prosseguir e desafixar?"
@ -1870,7 +1930,8 @@
"copy-link": "Copiar ligação",
"paste": "Colar",
"paste-as-plain-text": "Colar como texto sem formatação",
"search_online": "Pesquisar por \"{{term}}\" com {{searchEngine}}"
"search_online": "Pesquisar por \"{{term}}\" com {{searchEngine}}",
"search_in_trilium": "A procurar \"{{term}}\" no Trilium"
},
"image_context_menu": {
"copy_reference_to_clipboard": "Copiar referência para a área de transferência",
@ -1880,7 +1941,8 @@
"open_note_in_new_tab": "Abrir nota em nova guia",
"open_note_in_new_split": "Abrir nota em nova divisão",
"open_note_in_new_window": "Abrir nota em nova janela",
"open_note_in_popup": "Edição rápida"
"open_note_in_popup": "Edição rápida",
"open_note_in_other_split": "Abrir nota noutro separador"
},
"electron_integration": {
"desktop-application": "Aplicação Desktop",
@ -1888,7 +1950,8 @@
"native-title-bar-description": "Para Windows e macOS, manter a barra de título nativa desativada faz a aplicação parecer mais compacta. No Linux, manter a barra de título nativa ativada faz a aplicação se integrar melhor com o restante do sistema.",
"background-effects": "Ativar efeitos de fundo (apenas Windows 11)",
"restart-app-button": "Reiniciar a aplicação para ver as alterações",
"zoom-factor": "Fator de Zoom"
"zoom-factor": "Fator de Zoom",
"background-effects-description": "O Mica adiciona um desfoque, fundo estiloso as janelas da aplicação, criando uma profundidade e aspecto moderno. \"Barra de titulo nativa\" deve estar inactiva."
},
"note_autocomplete": {
"search-for": "Pesquisar por \"{{term}}\"",
@ -1948,7 +2011,8 @@
},
"note_language": {
"not_set": "Não atribuído",
"configure-languages": "Configurar idiomas..."
"configure-languages": "Configurar idiomas...",
"help-on-languages": "Ajuda nas linguagens de conteúdos..."
},
"content_language": {
"title": "Idiomas do conteúdo",
@ -1966,7 +2030,8 @@
"button_title": "Exportar diagrama como PNG"
},
"svg": {
"export_to_png": "O diagrama não pôde ser exportado como PNG."
"export_to_png": "O diagrama não pôde ser exportado como PNG.",
"export_to_svg": "O diagrama não pode ser exportado para SVG."
},
"code_theme": {
"title": "Aparência",
@ -1985,7 +2050,11 @@
"editorfeatures": {
"title": "Recursos",
"emoji_completion_enabled": "Ativar auto-completar de Emoji",
"note_completion_enabled": "Ativar auto-completar de notas"
"note_completion_enabled": "Ativar auto-completar de notas",
"emoji_completion_description": "Se activo, emojis podem ser facilmente inseridos em texto ao pressionar `:`, seguido do nome de um emoji.",
"note_completion_description": "Se activo, links para notas podem ser criadas ao escrever `@` seguido do titulo de uma nota.",
"slash_commands_enabled": "Activar comentários simples",
"slash_commands_description": "Se activo, editar comandos como inserir quebras de linha ou cabeçalhos podem ser activado/inactivado ao escrever `/`."
},
"table_view": {
"new-row": "Nova linha",
@ -2027,7 +2096,16 @@
"delete-column": "Apagar coluna",
"delete-column-confirmation": "Tem certeza que deseja apagar esta coluna? O atributo correspondente também será apagado de todas as notas abaixo desta coluna.",
"new-item": "Novo elemento",
"add-column": "Adicionar Coluna"
"add-column": "Adicionar Coluna",
"delete-note": "Apagar nota...",
"remove-from-board": "Remover do quadro",
"archive-note": "Arquivar nota",
"new-item-placeholder": "Inserir titulo da nota...",
"add-column-placeholder": "Inserir nome da coluna...",
"edit-note-title": "Clicar para editar o titulo da nota",
"unarchive-note": "Remover nota do arquivo",
"edit-column-title": "Click para editar titulo da coluna",
"column-already-exists": "Esta coluna já existe no quadro."
},
"command_palette": {
"tree-action-name": "Árvore: {{name}}",
@ -2058,16 +2136,146 @@
"background_effects_title": "Efeitos de fundo estão estáveis agora",
"background_effects_message": "Em dispositivos Windows, efeitos de fundo estão estáveis agora. Os efeitos de fundo adicionam um toque de cor à interface do utilizador borrando o plano de fundo atrás dela. Esta técnica também é usada noutras aplicações como o Windows Explorer.",
"background_effects_button": "Ativar os efeitos de fundo",
"dismiss": "Dispensar"
"dismiss": "Dispensar",
"new_layout_title": "Novo titulo do layout",
"new_layout_button": "Mais informação",
"new_layout_message": "Estamos a introduzir um layout modernizado para o Trilium. A faixa foi removida e está integrada na interface principal, com uma nota barra de estado e secções expansíveis (como as propriedades próprias) a tomar papéis principais.\n\nO novo layout está activo por defeito, e pode ser temporáriamente disabilidade em Opções → Aparência."
},
"settings": {
"related_settings": "Configurações relacionadas"
},
"settings_appearance": {
"related_code_blocks": "Esquema de cores para blocos de código em notas de texto",
"related_code_notes": "Esquema de cores para notas de código"
"related_code_notes": "Esquema de cores para notas de código",
"ui": "Interface do utilizador",
"ui_old_layout": "Layout antigo",
"ui_new_layout": "Nova aparência"
},
"units": {
"percentage": "%"
},
"experimental_features": {
"title": "Opções experimentais",
"new_layout_name": "Novo layout",
"new_layout_description": "Experimente o novo layout para um aspecto moderno e melhor estabilidade. Sujeito a grandes alterações nas próximas publicações.",
"disclaimer": "Estas opções são experimentais e podem causar instabilidade. Usar com cuidado."
},
"read-only-info": {
"read-only-note": "Actualmente a ver em modo de leitura.",
"edit-note": "Editar nota",
"auto-read-only-note": "Esta nota está a ser mostrada em modo de leitura para um carregamento mais rápido."
},
"presentation_view": {
"edit-slide": "Editar este slide",
"start-presentation": "Iniciar apresentação",
"slide-overview": "Alternar visão geral dos slides"
},
"calendar_view": {
"delete_note": "Apagar nota..."
},
"pagination": {
"page_title": "Página {{startIndex}} - {{endIndex}}",
"total_notes": "{{count}} notas"
},
"collections": {
"rendering_error": "Sem possíbilidade de mostrar conteúdos devido a um erro."
},
"note-color": {
"clear-color": "Remover cor da nota",
"set-color": "Atribuir cor da nota",
"set-custom-color": "Afectar cor personalizada da nota"
},
"popup-editor": {
"maximize": "Alterar para editor completo"
},
"server": {
"unknown_http_error_title": "Erro na comunicação com servidor",
"unknown_http_error_content": "Código de estado: {{statusCode}}\nURL: {{method}} {{url}}\nMessagem: {{message}}",
"traefik_blocks_requests": "Se está a usar o Traefik, este introduz uma alteração que afecta a comunicação com o servidor."
},
"tab_history_navigation_buttons": {
"go-back": "Ir para a nota anterior",
"go-forward": "Ir para nota seguinte"
},
"breadcrumb": {
"hoisted_badge": "Içado",
"workspace_badge": "Área de trabalho",
"scroll_to_top_title": "Saltar para o início da nota",
"create_new_note": "Criar nova nota descendente",
"empty_hide_archived_notes": "Esconder notas arquivadas",
"hoisted_badge_title": "Retirar de içado"
},
"breadcrumb_badges": {
"read_only_explicit": "Modo de leitura",
"read_only_auto": "Modo de leitura automático",
"read_only_temporarily_disabled": "Editável temporáriamente",
"read_only_auto_description": "Esta nota foi automaticamente colocada em modo de leitura por razões de performance. Este limite automatico é ajustável nas configurações.\n\nClicar para editar temporáriamente.",
"read_only_temporarily_disabled_description": "Esta nota está editável, mas normalmente está em modo de leitura. A nova vai regressar para mode de leitura assim que navegar para outra nota.\n\nClicar para reactivar o modo de leitura.",
"read_only_explicit_description": "Esta nota foi manualmente colocada em modo de leitura.\nClicar para editar temporáriamente.",
"shared_publicly": "Partilhado publicamente",
"shared_locally": "Partilhado localmente",
"shared_copy_to_clipboard": "Copiar link para a área de transferência",
"shared_open_in_browser": "Abrir link no browser",
"shared_unshare": "Remover partilha",
"clipped_note_description": "Esta nota foi retirar do {{url}}.\n\nClicar para navegar no código fonte da página.",
"clipped_note": "Web clipe",
"execute_script": "Correr script",
"execute_script_description": "Esta nota é uma nota de script. Clicar para executar o script.",
"execute_sql": "Correr SQL",
"execute_sql_description": "Esta nota é uma nota de SQL. Clicar para executar script SQL.",
"save_status_saved": "Guardar",
"save_status_saving": "A guardar...",
"save_status_unsaved": "Não gravado",
"save_status_error": "Gravar falhou",
"save_status_saving_tooltip": "Alterações estão a ser guardadas",
"save_status_unsaved_tooltip": "Existem alterações não guardadas. Serão guardadas automaticamente em breve.",
"save_status_error_tooltip": "Ocorreu um erro ao guardar a nota. Se possível, tente copiar os conteúdos da nota para outro local e reiniciar a aplicação."
},
"status_bar": {
"language_title": "Alterar lingua do conteúdo",
"note_info_title": "Ver informação da nota (e.g., datas, tamanho da nota)",
"backlinks_one": "{{count}} backlink",
"backlinks_many": "{{count}} backlinks",
"backlinks_other": "{{count}} backlinks",
"backlinks_title_one": "Ver backlink",
"backlinks_title_many": "Ver backlinks",
"backlinks_title_other": "Ver backlinks",
"attachments_one": "{{count}} anexo",
"attachments_many": "{{count}} anexos",
"attachments_other": "{{count}} anexos",
"attachments_title_one": "Ver anexo num novo separador",
"attachments_title_many": "Ver anexos num novo separador",
"attachments_title_other": "Ver anexos num novo separador",
"attributes_one": "{{count}} atributo",
"attributes_many": "{{count}} atributos",
"attributes_other": "{{count}} atributos",
"attributes_title": "Atributos próprios e herdados",
"note_paths_one": "{{count}} caminho",
"note_paths_many": "{{count}} caminhos",
"note_paths_other": "{{count}} caminhos",
"note_paths_title": "Caminhos da nota",
"code_note_switcher": "Alterar modo de linguagem"
},
"attributes_panel": {
"title": "Atributos da nota"
},
"right_pane": {
"empty_message": "Nada para mostrar nesta nota",
"empty_button": "Esconder painél",
"toggle": "Alterar painel direito",
"custom_widget_go_to_source": "Ir para código fonte"
},
"pdf": {
"attachments_one": "{{count}} anexo pdf",
"attachments_many": "{{count}} anexos pdf",
"attachments_other": "{{count}} anexos pdf",
"layers_one": "{{count}} camada",
"layers_many": "{{count}} camadas",
"layers_other": "{{count}} camadas",
"pages_one": "{{count}} página",
"pages_many": "{{count}} páginas",
"pages_other": "{{count}} páginas",
"pages_alt": "Página {{pageNumber}}",
"pages_loading": "A carregar..."
}
}

View File

@ -68,7 +68,7 @@
"attachment_detail_2": {
"deletion_reason": ", deoarece nu există o legătură către atașament în conținutul notiței. Pentru a preveni ștergerea, trebuie adăugată înapoi o legătură către atașament în conținut sau atașamentul trebuie convertit în notiță.",
"link_copied": "O legătură către atașament a fost copiată în clipboard.",
"role_and_size": "Rol: {{role}}, dimensiune: {{size}}",
"role_and_size": "Rol: {{role}}, dimensiune: {{size}}, MIME: {{- mimeType}}",
"unrecognized_role": "Rol atașament necunoscut: „{{role}}”.",
"will_be_deleted_in": "Acest atașament va fi șters automat în {{time}}",
"will_be_deleted_soon": "Acest atașament va fi șters automat în curând"
@ -293,7 +293,8 @@
"expand_tooltip": "Expandează subnotițele directe ale acestei colecții (un singur nivel de adâncime). Pentru mai multe opțiuni, apăsați săgeata din dreapta.",
"expand_first_level": "Expandează subnotițele directe",
"expand_nth_level": "Expandează pe {{depth}} nivele",
"expand_all_levels": "Expandează pe toate nivelele"
"expand_all_levels": "Expandează pe toate nivelele",
"hide_child_notes": "Ascunde subnotițele din arbore"
},
"bookmark_switch": {
"bookmark": "Semn de carte",
@ -569,7 +570,7 @@
"file_size": "Dimensiunea fișierului",
"file_type": "Tipul fișierului",
"note_id": "ID-ul notiței",
"open": "Deschide",
"open": "Deschide în exterior",
"original_file_name": "Denumirea originală a fișierului",
"title": "Fișier",
"upload_failed": "Încărcarea a unei noi revizii ale fișierului a eșuat.",
@ -795,7 +796,8 @@
},
"inherited_attribute_list": {
"no_inherited_attributes": "Niciun atribut moștenit.",
"title": "Atribute moștenite"
"title": "Atribute moștenite",
"none": "niciunul"
},
"jump_to_note": {
"search_button": "Caută în întregul conținut",
@ -880,7 +882,11 @@
"convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?",
"print_pdf": "Exportare ca PDF...",
"open_note_on_server": "Deschide notița pe server",
"view_revisions": "Revizii ale notițelor..."
"view_revisions": "Revizii ale notițelor...",
"export_as_image": "Exportează ca imagine",
"export_as_image_png": "PNG (bitmap)",
"export_as_image_svg": "SVG (vectorial)",
"note_map": "Harta notițelor"
},
"note_erasure_timeout": {
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
@ -899,7 +905,9 @@
"note_size_info": "Dimensiunea notiței reprezintă o aproximare a cerințelor de stocare ale acestei notițe. Ia în considerare conținutul notiței dar și ale reviziilor sale.",
"subtree_size": "(dimensiunea sub-arborelui: {{size}} în {{count}} notițe)",
"title": "Informații despre notiță",
"type": "Tip"
"type": "Tip",
"mime": "Tip MIME",
"show_similar_notes": "Afișează notițe similare"
},
"note_launcher": {
"this_launcher_doesnt_define_target_note": "Acesată scurtătură nu definește o notiță-destinație."
@ -1157,7 +1165,8 @@
"search_parameters": "Parametrii de căutare",
"search_script": "script de căutare",
"search_string": "șir de căutat",
"unknown_search_option": "Opțiune de căutare necunoscută „{{searchOptionName}}”"
"unknown_search_option": "Opțiune de căutare necunoscută „{{searchOptionName}}”",
"view_options": "Opțiuni de afișare:"
},
"search_engine": {
"baidu": "Baidu",
@ -1309,8 +1318,17 @@
},
"bundle-error": {
"title": "Eroare la încărcarea unui script personalizat",
"message": "Scriptul din notița cu ID-ul „{{id}}”, întitulată „{{title}}” nu a putut fi executată din cauza:\n\n{{message}}"
}
"message": "Scriptul nu a putut fi executat din cauza:\n\n{{message}}"
},
"widget-list-error": {
"title": "Nu s-a putut obține lista de widget-uri de la server"
},
"widget-render-error": {
"title": "Nu s-a putut randa un widget React"
},
"widget-missing-parent": "Widget-ul personalizat nu are definită proprietatea necesară „{{property}}“.\n\nDacă acest script este menit să ruleze fără interfață grafică, folosiți '#run=frontendStartup'.",
"open-script-note": "Deschide notița scriptului",
"scripting-error": "Eroare script personalizat: {{title}}"
},
"tray": {
"enable_tray": "Activează system tray-ul (este necesară repornirea aplicației pentru a avea efect)",
@ -1417,7 +1435,10 @@
"convert-to-attachment-confirm": "Doriți convertirea notițelor selectate în atașamente ale notiței părinte? Această operațiune se aplică doar notițelor de tip imagine, celelalte vor fi ignorate.",
"open-in-popup": "Editare rapidă",
"archive": "Arhivează",
"unarchive": "Dezarhivează"
"unarchive": "Dezarhivează",
"open-in-a-new-window": "Deschide în fereastră nouă",
"hide-subtree": "Ascunde subnotițele",
"show-subtree": "Afișează subnotițele"
},
"shared_info": {
"help_link": "Pentru informații vizitați <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki-ul</a>.",
@ -1478,12 +1499,27 @@
},
"highlights_list_2": {
"options": "Setări",
"title": "Listă de evidențieri"
"title": "Listă de evidențieri",
"title_with_count_one": "{{count}} evidențiere",
"title_with_count_few": "{{count}} evidențieri",
"title_with_count_other": "{{count}} de evidențieri",
"modal_title": "Configurează lista de evidențieri",
"menu_configure": "Configurează lista de evidențieri...",
"no_highlights": "Nu există nicio evidențiere."
},
"note_icon": {
"change_note_icon": "Schimbă iconița notiței",
"reset-default": "Resetează la iconița implicită",
"search": "Căutare:"
"search": "Căutare:",
"search_placeholder_one": "Caută printre {{number}} iconițe dintr-un pachet",
"search_placeholder_few": "Caută printre {{number}} iconițe din {{count}} pachete",
"search_placeholder_other": "Caută printre {{number}} iconițe din {{count}} de pachete",
"search_placeholder_filtered": "Căutați printre {{number}} iconițe în {{name}}",
"filter": "Filtrează",
"filter-none": "Toate iconițele",
"filter-default": "Iconițele implicite",
"icon_tooltip": "{{name}}\nPachet iconițe: {{iconPack}}",
"no_results": "Nu s-a găsit nicio iconiță."
},
"show_highlights_list_widget_button": {
"show_highlights_list": "Afișează lista de evidențieri"
@ -1521,7 +1557,17 @@
"refresh-saved-search-results": "Reîmprospătează căutarea salvată",
"unhoist": "Defocalizează notița",
"toggle-sidebar": "Comută bara laterală",
"dropping-not-allowed": "Aici nu este permisă plasarea notițelor."
"dropping-not-allowed": "Aici nu este permisă plasarea notițelor.",
"clone-indicator-tooltip": "Această notiță are {{- count}} părinți: {{- parents}}",
"clone-indicator-tooltip-single": "Această notiță este clonată (un singur părinte: {{- parent}})",
"shared-indicator-tooltip": "Această notiță este partajată public",
"shared-indicator-tooltip-with-url": "Această notiță este partajată public la: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} subnotiță ascunsă din arbore",
"subtree-hidden-tooltip_few": "{{count}} subnotițe ascunse din arbore",
"subtree-hidden-tooltip_other": "{{count}} de subnotițe ascunse din arbore",
"subtree-hidden-moved-title": "Adăugat în {{title}}",
"subtree-hidden-moved-description-collection": "Subnotițele din această colecție sunt ascunse din arbore.",
"subtree-hidden-moved-description-other": "Subnotițele din această notiță sunt ascunse."
},
"title_bar_buttons": {
"window-on-top": "Menține fereastra mereu vizibilă"
@ -1529,12 +1575,24 @@
"note_detail": {
"could_not_find_typewidget": "Nu s-a putut găsi widget-ul corespunzător tipului „{{type}}”",
"printing": "Imprimare în curs...",
"printing_pdf": "Exportare ca PDF în curs..."
"printing_pdf": "Exportare ca PDF în curs...",
"print_report_title": "Raport de imprimare",
"print_report_collection_content_one": "{{count}} notiță din colecție nu a putut fi imprimată deoarece nu este suportată sau este protejată.",
"print_report_collection_content_few": "{{count}} notițe din colecție nu au putut fi imprimate deoarece nu sunt suportate sau sunt protejate.",
"print_report_collection_content_other": "{{count}} de notițe din colecție nu au putut fi imprimate deoarece nu sunt suportate sau sunt protejate.",
"print_report_collection_details_button": "Afișează detalii",
"print_report_collection_details_ignored_notes": "Notițe ignorate"
},
"note_title": {
"placeholder": "introduceți titlul notiței aici...",
"created_on": "Creată la <Value />",
"last_modified": "Modificată la <Value />"
"last_modified": "Modificată la <Value />",
"note_type_switcher_label": "Schimbă din {{type}} la:",
"note_type_switcher_others": "Mai multe tipuri de notițe",
"note_type_switcher_templates": "Șablon",
"note_type_switcher_collection": "Colecție",
"edited_notes": "Notițe editate în această zi",
"promoted_attributes": "Atribute promovate"
},
"revisions_snapshot_limit": {
"erase_excess_revision_snapshots": "Șterge acum reviziile excesive",
@ -1555,7 +1613,11 @@
"configure_launchbar": "Configurează bara de lansare"
},
"sql_result": {
"no_rows": "Nu s-a găsit niciun rând pentru această interogare"
"no_rows": "Nu s-a găsit niciun rând pentru această interogare",
"not_executed": "Această interogare nu a fost executată încă.",
"failed": "Interogarea SQL a eșuat",
"statement_result": "Rezultatul comenzii SQL",
"execute_now": "Execută acum"
},
"sql_table_schemas": {
"tables": "Tabele"
@ -1577,7 +1639,8 @@
},
"toc": {
"options": "Setări",
"table_of_contents": "Cuprins"
"table_of_contents": "Cuprins",
"no_headings": "Niciun titlu."
},
"watched_file_update_status": {
"file_last_modified": "Fișierul <code class=\"file-path\"></code> a fost ultima oară modificat la data de <span class=\"file-last-modified\"></span>.",
@ -2012,7 +2075,7 @@
"book_properties_config": {
"hide-weekends": "Ascunde weekend-urile",
"display-week-numbers": "Afișează numărul săptămânii",
"map-style": "Stil hartă:",
"map-style": "Stil hartă",
"max-nesting-depth": "Nivel maxim de imbricare:",
"raster": "Raster",
"vector_light": "Vectorial (culoare deschisă)",
@ -2069,7 +2132,10 @@
"next_theme_title": "Încercați noua temă Trilium",
"next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?",
"next_theme_button": "Testează noua temă",
"dismiss": "Treci peste"
"dismiss": "Treci peste",
"new_layout_title": "Aspect nou",
"new_layout_message": "Am introdus un aspect modernizat pentru Trilium. Panglică a fost integrată în restul interfeței, cu o bară de stare nouă și secțiuni expandabile (precum atributele promovate) ce preiau funcționalitatea de bază.\n\nNoul aspect este activat în mod implicit, și se poate dezactiva momentan din Opțiuni → Aspect.",
"new_layout_button": "Mai multe informații"
},
"ui-performance": {
"title": "Setări de performanță",
@ -2084,7 +2150,10 @@
},
"settings_appearance": {
"related_code_blocks": "Tema de culori pentru blocuri de cod în notițe de tip text",
"related_code_notes": "Tema de culori pentru notițele de tip cod"
"related_code_notes": "Tema de culori pentru notițele de tip cod",
"ui": "Interfață grafică",
"ui_old_layout": "Aspect vechi",
"ui_new_layout": "Aspect nou"
},
"units": {
"percentage": "%"
@ -2140,6 +2209,77 @@
"read_only_temporarily_disabled": "Editabilă temporar",
"read_only_temporarily_disabled_description": "Această notiță se poate modifica, deși în mod normal ea este doar în citire. Notița va reveni la modul doar în citire imediat ce navigați către altă notiță.\n\nClick pentru a re-activa modul doar în citire.",
"shared_publicly": "Partajată public",
"shared_locally": "Partajată local"
"shared_locally": "Partajată local",
"shared_copy_to_clipboard": "Copiază legătură în clipboard",
"shared_open_in_browser": "Deschide legătura în browser",
"shared_unshare": "Înlătură partajarea",
"clipped_note": "Decupare web",
"clipped_note_description": "Această notiță a fost preluată de la {{url}}.\n\nClic pentru a naviga la pagina web sursă.",
"execute_script": "Rulează script",
"execute_script_description": "Această notiță este un script. Clic pentru a executa scriptul.",
"execute_sql": "Rulează SQL",
"execute_sql_description": "Această notiță este de tip SQL. Clic pentru a executa interogarea SQL.",
"save_status_saved": "Salvat",
"save_status_saving": "Se salvează...",
"save_status_unsaved": "Nesalvat",
"save_status_error": "Salvarea a eșuat",
"save_status_saving_tooltip": "Modificările sunt în curs de salvare.",
"save_status_unsaved_tooltip": "Există schimbări ce nu au fost încă salvate. Acestea vor fi salvate automat într-un moment.",
"save_status_error_tooltip": "A intervenit o eroare la salvarea notiței. Dacă este posibil, încercați să copiați conținutul notiței într-un alt loc și să reîmprospătați aplicația."
},
"breadcrumb": {
"hoisted_badge": "Focalizat",
"hoisted_badge_title": "Defocalizează",
"workspace_badge": "Spațiu de lucru",
"scroll_to_top_title": "Sari la începutul notiței",
"create_new_note": "Crează subnotiță",
"empty_hide_archived_notes": "Ascunde notițele arhivate"
},
"status_bar": {
"language_title": "Schimbă limba conținutului",
"note_info_title": "Afișează informații despre notiță precum data modificării și dimensiunea",
"backlinks_one": "{{count}} legătură de retur",
"backlinks_few": "{{count}} legături de retur",
"backlinks_other": "{{count}} de legături de retur",
"backlinks_title_one": "Afișează legătura de retur",
"backlinks_title_few": "Afișează legăturile de retur",
"backlinks_title_other": "Afișează legăturile de retur",
"attachments_one": "{{count}} atașament",
"attachments_few": "{{count}} atașamente",
"attachments_other": "{{count}} de atașamente",
"attachments_title_one": "Deschide atașamentul într-un tab nou",
"attachments_title_few": "Deschide atașamentele într-un tab nou",
"attachments_title_other": "Deschide atașamentele într-un tab nou",
"attributes_one": "{{count}} atribut",
"attributes_few": "{{count}} atribute",
"attributes_other": "{{count}} de atribute",
"attributes_title": "Atribute proprii și moștenite",
"note_paths_one": "O cale",
"note_paths_few": "{{count}} căi",
"note_paths_other": "{{count}} de căi",
"note_paths_title": "Căi ale notiței",
"code_note_switcher": "Schimbă limbajul"
},
"attributes_panel": {
"title": "Atributele notiței"
},
"right_pane": {
"empty_message": "Nimic de afișat pentru această notiță",
"empty_button": "Ascunde panoul",
"toggle": "Comută panoul din dreapta",
"custom_widget_go_to_source": "Mergi la codul sursă"
},
"pdf": {
"attachments_one": "{{count}} atașament",
"attachments_few": "{{count}} atașamente",
"attachments_other": "{{count}} de atașamente",
"layers_one": "{{count}} strat",
"layers_few": "{{count}} straturi",
"layers_other": "{{count}} de straturi",
"pages_one": "{{count}} pagină",
"pages_few": "{{count}} pagini",
"pages_other": "{{count}} de pagini",
"pages_alt": "Pagina {{pageNumber}}",
"pages_loading": "Încărcare..."
}
}

View File

@ -1566,6 +1566,7 @@
"shared-indicator-tooltip": "此筆記已公開分享",
"shared-indicator-tooltip-with-url": "此筆記已公開分享至:{{- url}}",
"subtree-hidden-tooltip_one": "從樹中隱藏的 {{count}} 篇子筆記",
"subtree-hidden-tooltip_other": "",
"subtree-hidden-moved-title": "已新增至 {{title}}",
"subtree-hidden-moved-description-collection": "此集合隱藏其樹中的子筆記。",
"subtree-hidden-moved-description-other": "子筆記隱藏於此筆記的樹中。"
@ -1602,7 +1603,11 @@
"configure_launchbar": "設定啟動欄"
},
"sql_result": {
"no_rows": "此次查詢沒有返回任何數據"
"no_rows": "此次查詢沒有返回任何數據",
"not_executed": "查詢尚未執行。",
"failed": "SQL 查詢執行失敗",
"statement_result": "查詢結果",
"execute_now": "立即執行"
},
"sql_table_schemas": {
"tables": "表"

View File

@ -14,7 +14,10 @@
"edit_branch_prefix": "Редагувати префікс гілки",
"help_on_tree_prefix": "Довідка щодо префіксу дерева",
"prefix": "Префікс: ",
"branch_prefix_saved": "Префікс гілки збережено."
"branch_prefix_saved": "Префікс гілки збережено.",
"edit_branch_prefix_multiple": "Редагувати префікс гілки для {{count}} гілок",
"branch_prefix_saved_multiple": "Префікс гілки збережено для {{count}} гілок.",
"affected_branches": "Уражені гілки ({{count}}):"
},
"about": {
"app_version": "Версія програми:",
@ -70,8 +73,17 @@
},
"bundle-error": {
"title": "Не вдалося завантажити користувацький скрипт",
"message": "Скрипт з нотатки ID \"{{id}}\" з заголовком \"{{title}}\" не вдалося виконати через:\n\n{{message}}"
}
"message": "Скрипт не вдалося виконати через:\n\n{{message}}"
},
"widget-list-error": {
"title": "Не вдалося отримати список віджетів з сервера"
},
"widget-render-error": {
"title": "Не вдалося відобразити користувацький віджет"
},
"widget-missing-parent": "Для власного віджета не визначено {{property}} обов'язкову властивість\n\nЯкщо цей скрипт призначений для запуску без елемента інтерфейсу користувача, використовуйте замість нього '#run=frontendStartup'.",
"open-script-note": "Відкрити нотатку сценарію",
"scripting-error": "Помилка користувацького скрипта: {{title}}"
},
"bulk_actions": {
"bulk_actions": "Масові дії",
@ -199,7 +211,8 @@
"export_status": "Статус експорту",
"export_in_progress": "Триває експорт: {{progressCount}}",
"export_finished_successfully": "Експорт успішно завершено.",
"format_pdf": "PDF для друку або спільного використання."
"format_pdf": "PDF для друку або спільного використання.",
"share-format": "HTML для веб-публікацій використовує ту саму тему, що й для спільних нотаток, але може бути опублікований як статичний веб-сайт."
},
"help": {
"title": "Шпаргалка",
@ -253,7 +266,8 @@
"showSQLConsole": "показати консоль SQL",
"other": "Інше",
"quickSearch": "фокус на швидкому введенні пошуку",
"inPageSearch": "пошук на сторінці"
"inPageSearch": "пошук на сторінці",
"editShortcuts": "Редагувати комбінації клавіш"
},
"import": {
"importIntoNote": "Імпортувати в нотатку",
@ -849,7 +863,10 @@
"note_icon": {
"change_note_icon": "Змінити значок нотатки",
"search": "Пошук:",
"reset-default": "Скинути значок до стандартного значення"
"reset-default": "Скинути значок до стандартного значення",
"search_placeholder_one": "Пошук {{number}} значка у {{count}} пакеті",
"search_placeholder_few": "Пошук {{number}} значків у {{count}} пакетах",
"search_placeholder_many": "Пошук {{number}} значків у {{count}} пакетах"
},
"basic_properties": {
"note_type": "Тип нотатки",
@ -884,7 +901,7 @@
"file_type": "Тип файлу",
"file_size": "Розмір файлу",
"download": "Завантажити",
"open": "Відкрити",
"open": "Відкрити зовні",
"upload_new_revision": "Завантажити нову версію",
"upload_success": "Завантажено нову версію файлу.",
"upload_failed": "Не вдалося завантажити нову версію файлу.",
@ -1589,13 +1606,19 @@
"refresh-saved-search-results": "Оновити збережені результати пошуку",
"create-child-note": "Створити дочірню нотатку",
"unhoist": "Відкріпити",
"toggle-sidebar": "Перемикання бічної панелі"
"toggle-sidebar": "Перемикання бічної панелі",
"subtree-hidden-tooltip_one": "{{count}} дочірня нотатка, прихована від дерев",
"subtree-hidden-tooltip_few": "{{count}} дочірніх нотатки, прихованих від дерев",
"subtree-hidden-tooltip_many": "{{count}} дочірніх нотаток, прихованих від дерев"
},
"title_bar_buttons": {
"window-on-top": "Тримати вікно зверху"
},
"note_detail": {
"could_not_find_typewidget": "Не вдалося знайти typeWidget для типу '{{type}}'"
"could_not_find_typewidget": "Не вдалося знайти typeWidget для типу '{{type}}'",
"print_report_collection_content_one": "{{count}} нотатку з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені.",
"print_report_collection_content_few": "{{count}} нотатки з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені.",
"print_report_collection_content_many": "{{count}} нотаток з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені."
},
"note_title": {
"placeholder": "введіть тут заголовок нотатки..."
@ -1743,7 +1766,7 @@
"unknown_widget": "Невідомий віджет для \"{{id}}\"."
},
"note_language": {
"not_set": "Не встановлено",
"not_set": "Мову не встановлено",
"configure-languages": "Налаштувати мови..."
},
"content_language": {
@ -1810,7 +1833,7 @@
"book_properties_config": {
"hide-weekends": "Приховати вихідні",
"display-week-numbers": "Відображення номерів тижнів",
"map-style": "Стиль карти:",
"map-style": "Стиль карти",
"max-nesting-depth": "Максимальна глибина вкладення:",
"raster": "Растр",
"vector_light": "Вектор (Світла)",
@ -1863,7 +1886,7 @@
"will_be_deleted_in": "Це вкладення буде автоматично видалено через {{time}}",
"will_be_deleted_soon": "Це вкладення незабаром буде автоматично видалено",
"deletion_reason": ", оскільки вкладення не має посилання у вмісті нотатки. Щоб запобігти видаленню, додайте посилання на вкладення назад у вміст або перетворіть вкладення на нотатку.",
"role_and_size": "Роль: {{role}}, Розмір: {{size}}",
"role_and_size": "Роль: {{role}}, розмір: {{size}}, формат даних: {{- mimeType}}",
"link_copied": "Посилання на вкладення скопійовано в буфер обміну.",
"unrecognized_role": "Нерозпізнана роль вкладення '{{role}}'."
},
@ -1914,7 +1937,7 @@
"import-into-note": "Імпортувати в нотатку",
"apply-bulk-actions": "Застосувати масові дії",
"converted-to-attachments": "({{count}}) нотаток перетворено на вкладення.",
"convert-to-attachment-confirm": "Ви впевнені, що хочете конвертувати вибрані нотатки у вкладення до їхніх батьківських нотаток?",
"convert-to-attachment-confirm": "Ви впевнені, що хочете конвертувати вибрані нотатки у вкладення до їхніх батьківських нотаток? Ця операція застосовується лише до нотаток із зображеннями, інші нотатки будуть пропущені.",
"open-in-popup": "Швидке редагування",
"archive": "Архівувати",
"unarchive": "Розархівувати"
@ -1978,7 +2001,10 @@
},
"highlights_list_2": {
"title": "Список основних моментів",
"options": "Параметри"
"options": "Параметри",
"title_with_count_one": "{{count}} виділення",
"title_with_count_few": "{{count}} виділення",
"title_with_count_many": "{{count}} виділень"
},
"table_context_menu": {
"delete_row": "Видалити рядок"
@ -2051,5 +2077,36 @@
},
"collections": {
"rendering_error": "Не вдалося показати вміст через помилку."
},
"status_bar": {
"backlinks_one": "{{count}} зворотне посилання",
"backlinks_few": "{{count}} зворотні посилання",
"backlinks_many": "{{count}} зворотних посилань",
"backlinks_title_one": "Переглянути зворотне посилання",
"backlinks_title_few": "Переглянути зворотні посилання",
"backlinks_title_many": "Переглянути зворотніх посилань",
"attachments_one": "{{count}} вкладення",
"attachments_few": "{{count}} вкладення",
"attachments_many": "{{count}} вкладень",
"attachments_title_one": "Переглянути вкладення в новій вкладці",
"attachments_title_few": "Переглянути вкладення в новій вкладці",
"attachments_title_many": "Переглянути вкладень в новій вкладці",
"attributes_one": "{{count}} атрибут",
"attributes_few": "{{count}} атрибути",
"attributes_many": "{{count}} атрибутів",
"note_paths_one": "{{count}} шлях",
"note_paths_few": "{{count}} шляхи",
"note_paths_many": "{{count}} шляхів"
},
"pdf": {
"attachments_one": "{{count}} вкладення",
"attachments_few": "{{count}} вкладення",
"attachments_many": "{{count}} вкладень",
"layers_one": "{{count}} шар",
"layers_few": "{{count}} шари",
"layers_many": "{{count}} шарів",
"pages_one": "{{count}} сторінка",
"pages_few": "{{count}} сторінки",
"pages_many": "{{count}} сторінок"
}
}

View File

@ -7,7 +7,6 @@ import Component from "../components/component";
import NoteContext from "../components/note_context";
import FNote from "../entities/fnote";
import attributes from "../services/attributes";
import { isExperimentalFeatureEnabled } from "../services/experimental_features";
import froca from "../services/froca";
import { t } from "../services/i18n";
import { copyImageReferenceToClipboard } from "../services/image";
@ -101,7 +100,8 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: F
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
const isSavedSqlite = note.isTriliumSqlite() && !note.isHiddenCompletely();
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap" || isSavedSqlite)
&& note.isContentAvailable() && isDefaultViewMode;
return isEnabled && <FloatingButton

View File

@ -265,9 +265,13 @@ function useNoteInfo() {
const [ note, setNote ] = useState<FNote | null | undefined>();
const [ type, setType ] = useState<ExtendedNoteType>();
const [ mime, setMime ] = useState<string>();
const refreshIdRef = useRef(0);
function refresh() {
const refreshId = ++refreshIdRef.current;
getExtendedWidgetType(actualNote, noteContext).then(type => {
if (refreshId !== refreshIdRef.current) return;
setNote(actualNote);
setType(type);
setMime(actualNote?.mime);
@ -318,6 +322,8 @@ export async function getExtendedWidgetType(note: FNote | null | undefined, note
resultingType = "noteMap";
} else if (type === "text" && (await noteContext?.isReadOnly())) {
resultingType = "readOnlyText";
} else if (note.isTriliumSqlite()) {
resultingType = "sqlConsole";
} else if ((type === "code" || type === "mermaid") && (await noteContext?.isReadOnly())) {
resultingType = "readOnlyCode";
} else if (type === "text") {
@ -342,9 +348,8 @@ export function checkFullHeight(noteContext: NoteContext | undefined, type: Exte
// https://github.com/zadam/trilium/issues/2522
const isBackendNote = noteContext?.noteId === "_backendLog";
const isSqlNote = noteContext.note?.mime === "text/x-sqlite;schema=trilium";
const isFullHeightNoteType = type && TYPE_MAPPINGS[type].isFullHeight;
return (!noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
return (!noteContext?.hasNoteList() && isFullHeightNoteType)
|| noteContext?.viewScope?.viewMode === "attachments"
|| isBackendNote;
}
@ -358,8 +363,8 @@ function showToast(type: "printing" | "exporting_pdf", progress: number = 0) {
});
}
function handlePrintReport(printReport: PrintReport) {
if (printReport.type === "collection" && printReport.ignoredNoteIds.length > 0) {
function handlePrintReport(printReport?: PrintReport) {
if (printReport?.type === "collection" && printReport.ignoredNoteIds.length > 0) {
toast.showPersistent({
id: "print-report",
icon: "bx bx-collection",

View File

@ -217,6 +217,7 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) {
id={inputId}
type={LABEL_MAPPINGS[definition.labelType ?? "text"]}
value={valueAttr.value}
checked={definition.labelType === "boolean" ? valueAttr.value === "true" : undefined}
placeholder={t("promoted_attributes.unset-field-placeholder")}
data-attribute-id={valueAttr.attributeId}
data-attribute-type={valueAttr.type}

View File

@ -1,7 +1,7 @@
import "./NoteList.css";
import { WebSocketMessage } from "@triliumnext/commons";
import { VNode } from "preact";
import { Component, VNode } from "preact";
import { lazy, Suspense } from "preact/compat";
import { useEffect, useRef, useState } from "preact/hooks";
@ -120,7 +120,9 @@ export function CustomNoteList({ note, viewType, isEnabled: shouldEnable, notePa
}
const ComponentToRender = viewType && props && isEnabled && (
props.media === "print" ? ViewComponents[viewType].print : ViewComponents[viewType].normal
props.media === "print"
? ViewComponents[viewType].print ?? ViewComponents[viewType].normal
: ViewComponents[viewType].normal
);
return (

View File

@ -1,8 +1,8 @@
import { CreateChildrenResponse } from "@triliumnext/commons";
import server from "../../../services/server";
import { AttributeRow, CreateChildrenResponse } from "@triliumnext/commons";
import FNote from "../../../entities/fnote";
import { setAttribute, setLabel } from "../../../services/attributes";
import froca from "../../../services/froca";
import server from "../../../services/server";
interface NewEventOpts {
title: string;
@ -10,6 +10,7 @@ interface NewEventOpts {
endDate?: string | null;
startTime?: string | null;
endTime?: string | null;
componentId?: string;
}
interface ChangeEventOpts {
@ -17,30 +18,48 @@ interface ChangeEventOpts {
endDate?: string | null;
startTime?: string | null;
endTime?: string | null;
componentId?: string;
}
export async function newEvent(parentNote: FNote, { title, startDate, endDate, startTime, endTime }: NewEventOpts) {
// Create the note.
const { note } = await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
title,
content: "",
type: "text"
export async function newEvent(parentNote: FNote, { title, startDate, endDate, startTime, endTime, componentId }: NewEventOpts) {
const attributes: Omit<AttributeRow, "noteId" | "attributeId">[] = [];
attributes.push({
type: "label",
name: "startDate",
value: startDate
});
// Set the attributes.
setLabel(note.noteId, "startDate", startDate);
if (endDate) {
setLabel(note.noteId, "endDate", endDate);
attributes.push({
type: "label",
name: "endDate",
value: endDate
});
}
if (startTime) {
setLabel(note.noteId, "startTime", startTime);
attributes.push({
type: "label",
name: "startTime",
value: startTime
});
}
if (endTime) {
setLabel(note.noteId, "endTime", endTime);
attributes.push({
type: "label",
name: "endTime",
value: endTime
});
}
// Create the note.
await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
title,
content: "",
type: "text",
attributes
}, componentId);
}
export async function changeEvent(note: FNote, { startDate, endDate, startTime, endTime }: ChangeEventOpts) {
export async function changeEvent(note: FNote, { startDate, endDate, startTime, endTime, componentId }: ChangeEventOpts) {
// Don't store the end date if it's empty.
if (endDate === startDate) {
endDate = undefined;
@ -52,12 +71,12 @@ export async function changeEvent(note: FNote, { startDate, endDate, startTime,
let endAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:endDate").shift()?.value||"endDate";
const noteId = note.noteId;
setLabel(noteId, startAttribute, startDate);
setAttribute(note, "label", endAttribute, endDate);
setLabel(noteId, startAttribute, startDate, false, componentId);
setAttribute(note, "label", endAttribute, endDate, componentId);
startAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:startTime").shift()?.value||"startTime";
endAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:endTime").shift()?.value||"endTime";
setAttribute(note, "label", startAttribute, startTime);
setAttribute(note, "label", endAttribute, endTime);
setAttribute(note, "label", startAttribute, startTime, componentId);
setAttribute(note, "label", endAttribute, endTime, componentId);
}

View File

@ -1,12 +1,12 @@
import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker";
import FNote from "../../../entities/fnote";
import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu";
import { getArchiveMenuItem } from "../../../menus/context_menu_utils";
import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker";
import link_context_menu from "../../../menus/link_context_menu";
import branches from "../../../services/branches";
import { getArchiveMenuItem } from "../../../menus/context_menu_utils";
import { t } from "../../../services/i18n";
export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parentNote: FNote) {
export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parentNote: FNote, componentId?: string) {
e.preventDefault();
e.stopPropagation();
@ -30,16 +30,16 @@ export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parent
}
if (branchIdToDelete) {
await branches.deleteNotes([ branchIdToDelete ], false, false);
await branches.deleteNotes([ branchIdToDelete ], false, false, componentId);
}
}
},
{ kind: "separator" },
{
kind: "custom",
componentFn: () => NoteColorPicker({note: note})
componentFn: () => NoteColorPicker({note})
}
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, e, note.noteId),
})
});
}

View File

@ -1,10 +1,11 @@
import { EventInput, EventSourceFuncArg, EventSourceInput } from "@fullcalendar/core/index.js";
import froca from "../../../services/froca";
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
import FNote from "../../../entities/fnote";
import server from "../../../services/server";
import clsx from "clsx";
import FNote from "../../../entities/fnote";
import froca from "../../../services/froca";
import server from "../../../services/server";
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
interface Event {
startDate: string,
endDate?: string | null,
@ -105,7 +106,8 @@ export async function buildEvent(note: FNote, { startDate, endDate, startTime, e
endDate = (endTime ? `${endDate}T${endTime}:00` : endDate);
const eventData: EventInput = {
title: title,
id: note.noteId,
title,
start: startDate,
url: `#${note.noteId}?popup`,
noteId: note.noteId,
@ -148,12 +150,12 @@ async function parseCustomTitle(customTitlettributeName: string | null, note: FN
}
async function buildDisplayedAttributes(note: FNote, calendarDisplayedAttributes: string[]) {
const filteredDisplayedAttributes = note.getAttributes().filter((attr): boolean => calendarDisplayedAttributes.includes(attr.name))
const filteredDisplayedAttributes = note.getAttributes().filter((attr): boolean => calendarDisplayedAttributes.includes(attr.name));
const result: Array<[string, string]> = [];
for (const attribute of filteredDisplayedAttributes) {
if (attribute.type === "label") result.push([attribute.name, attribute.value]);
else result.push([attribute.name, (await attribute.getTargetNote())?.title || ""])
else result.push([attribute.name, (await attribute.getTargetNote())?.title || ""]);
}
return result;

View File

@ -77,7 +77,7 @@
font-weight: normal;
}
body.desktop:not(.zen) .calendar-view .calendar-header {
body.desktop:not(.zen):not(.experimental-feature-new-layout) .calendar-view .calendar-header {
padding-block-start: 4px;
padding-inline-end: 5em;
}
@ -126,7 +126,7 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
.calendar-view a.fc-timegrid-event,
.calendar-view a.fc-daygrid-event {
--border-color: transparent;
border: 2px solid;
border-left-width: 4px;
border-color: var(--border-color) var(--border-color) var(--border-color)
@ -175,7 +175,7 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
opacity: .75;
}
/*
/*
* List view
*/
@ -188,4 +188,4 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
--fc-event-border-color: var(--custom-color);
}
/* #endregion */
/* #endregion */

View File

@ -5,7 +5,7 @@ import { DateSelectArg, EventChangeArg, EventMountArg, EventSourceFuncArg, Local
import { DateClickArg } from "@fullcalendar/interaction";
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
import { RefObject } from "preact";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import appContext from "../../../components/app_context";
import FNote from "../../../entities/fnote";
@ -17,6 +17,7 @@ import { isMobile } from "../../../services/utils";
import ActionButton from "../../react/ActionButton";
import Button, { ButtonGroup } from "../../react/Button";
import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTriliumEvent, useTriliumOption, useTriliumOptionInt } from "../../react/hooks";
import { ParentComponent } from "../../react/react_utils";
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar";
import { ViewModeProps } from "../interface";
import { changeEvent, newEvent } from "./api";
@ -87,6 +88,7 @@ export const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, (() => Promise<{ de
};
export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarViewData>) {
const parentComponent = useContext(ParentComponent);
const containerRef = useRef<HTMLDivElement>(null);
const calendarRef = useRef<FullCalendar>(null);
@ -105,26 +107,34 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
const eventBuilder = useMemo(() => {
if (!isCalendarRoot) {
return async () => await buildEvents(noteIds);
}
}
return async (e: EventSourceFuncArg) => await buildEventsForCalendar(note, e);
}, [isCalendarRoot, noteIds]);
const plugins = usePlugins(isEditable, isCalendarRoot);
const locale = useLocale();
const { eventDidMount } = useEventDisplayCustomization(note);
const editingProps = useEditing(note, isEditable, isCalendarRoot);
const { eventDidMount } = useEventDisplayCustomization(note, parentComponent?.componentId);
const editingProps = useEditing(note, isEditable, isCalendarRoot, parentComponent?.componentId);
// React to changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) // note title change.
|| loadResults.getAttributeRows().some((a) => noteIds.includes(a.noteId ?? ""))) // subnote change.
{
const api = calendarRef.current;
if (!api) return;
// Subnote attribute change.
if (loadResults.getAttributeRows(parentComponent?.componentId).some((a) => noteIds.includes(a.noteId ?? ""))) {
// Defer execution after the load results are processed so that the event builder has the updated data to work with.
setTimeout(() => {
calendarRef.current?.refetchEvents();
}, 0);
setTimeout(() => api.refetchEvents(), 0);
return; // early return since we'll refresh the events anyway
}
// Title change.
for (const noteId of loadResults.getNoteIds().filter(noteId => noteIds.includes(noteId))) {
const event = api.getEventById(noteId);
const note = froca.getNoteFromCache(noteId);
if (!event || !note) continue;
event.setProp("title", note.title);
}
});
@ -169,13 +179,13 @@ function CalendarHeader({ calendarRef }: { calendarRef: RefObject<FullCalendar>
<ButtonGroup>
{CALENDAR_VIEWS.map(viewData => (
<Button
text={viewData.name.toLocaleLowerCase()}
text={viewData.name}
className={currentViewType === viewData.type ? "active" : ""}
onClick={() => calendarRef.current?.changeView(viewData.type)}
/>
))}
</ButtonGroup>
<Button text={t("calendar.today").toLocaleLowerCase()} onClick={() => calendarRef.current?.today()} />
<Button text={t("calendar.today")} onClick={() => calendarRef.current?.today()} />
<ButtonGroup>
<ActionButton icon="bx bx-chevron-left" text={currentViewData?.previousText ?? ""} frame onClick={() => calendarRef.current?.prev()} />
<ActionButton icon="bx bx-chevron-right" text={currentViewData?.nextText ?? ""} frame onClick={() => calendarRef.current?.next()} />
@ -207,22 +217,23 @@ function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
}
function useLocale() {
const [ locale ] = useTriliumOption("locale");
const [ formattingLocale ] = useTriliumOption("formattingLocale");
const [ calendarLocale, setCalendarLocale ] = useState<LocaleInput>();
useEffect(() => {
const correspondingLocale = LOCALE_MAPPINGS[formattingLocale];
const correspondingLocale = LOCALE_MAPPINGS[formattingLocale] ?? LOCALE_MAPPINGS[locale];
if (correspondingLocale) {
correspondingLocale().then((locale) => setCalendarLocale(locale.default));
} else {
setCalendarLocale(undefined);
}
});
}, [formattingLocale, locale]);
return calendarLocale;
}
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, componentId: string | undefined) {
const onCalendarSelection = useCallback(async (e: DateSelectArg) => {
const { startDate, endDate } = parseStartEndDateFromEvent(e);
if (!startDate) return;
@ -234,8 +245,8 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
return;
}
newEvent(note, { title, startDate, endDate, startTime, endTime });
}, [ note ]);
newEvent(note, { title, startDate, endDate, startTime, endTime, componentId });
}, [ note, componentId ]);
const onEventChange = useCallback(async (e: EventChangeArg) => {
const { startDate, endDate } = parseStartEndDateFromEvent(e.event);
@ -244,8 +255,8 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
const { startTime, endTime } = parseStartEndTimeFromEvent(e.event);
const note = await froca.getNote(e.event.extendedProps.noteId);
if (!note) return;
changeEvent(note, { startDate, endDate, startTime, endTime });
}, []);
changeEvent(note, { startDate, endDate, startTime, endTime, componentId });
}, [ componentId ]);
// Called upon when clicking the day number in the calendar, opens or creates the day note but only if in a calendar root.
const onDateClick = useCallback(async (e: DateClickArg) => {
@ -264,7 +275,7 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
};
}
function useEventDisplayCustomization(parentNote: FNote) {
function useEventDisplayCustomization(parentNote: FNote, componentId: string | undefined) {
const eventDidMount = useCallback((e: EventMountArg) => {
const { iconClass, promotedAttributes } = e.event.extendedProps;
@ -321,7 +332,7 @@ function useEventDisplayCustomization(parentNote: FNote) {
const note = await froca.getNote(e.event.extendedProps.noteId);
if (!note) return;
openCalendarContextMenu(contextMenuEvent, note, parentNote);
openCalendarContextMenu(contextMenuEvent, note, parentNote, componentId);
}
if (isMobile()) {

View File

@ -4,6 +4,10 @@
height: 100%;
user-select: none;
padding: 0 5px 0 10px;
.tabulator-tableholder {
height: unset !important;
}
}
.table-view-container {
@ -68,4 +72,4 @@
inset-inline-start: 0;
font-size: 1.5em;
transform: translateY(-50%);
}
}

View File

@ -1,18 +1,20 @@
import { useContext, useEffect, useLayoutEffect, useRef } from "preact/hooks";
import { EventCallBackMethods, Module, Options, Tabulator as VanillaTabulator } from "tabulator-tables";
import "tabulator-tables/dist/css/tabulator.css";
import "../../../../src/stylesheets/table.css";
import { ParentComponent, renderReactWidget } from "../../react/react_utils";
import { JSX } from "preact/jsx-runtime";
import { isValidElement, RefObject } from "preact";
import { useContext, useEffect, useLayoutEffect, useRef } from "preact/hooks";
import { JSX } from "preact/jsx-runtime";
import { EventCallBackMethods, Module, Options, Tabulator as VanillaTabulator } from "tabulator-tables";
import { ParentComponent, renderReactWidget } from "../../react/react_utils";
interface TableProps<T> extends Omit<Options, "data" | "footerElement" | "index"> {
tabulatorRef: RefObject<VanillaTabulator>;
tabulatorRef?: RefObject<VanillaTabulator>;
className?: string;
data?: T[];
modules?: (new (table: VanillaTabulator) => Module)[];
events?: Partial<EventCallBackMethods>;
index: keyof T;
index?: keyof T;
footerElement?: string | HTMLElement | JSX.Element;
onReady?: () => void;
}
@ -43,7 +45,9 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
tabulator.on("tableBuilt", () => {
tabulatorRef.current = tabulator;
externalTabulatorRef.current = tabulator;
if (externalTabulatorRef) {
externalTabulatorRef.current = tabulator;
}
onReady?.();
});
@ -62,12 +66,15 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
for (const [ eventName, handler ] of Object.entries(events)) {
tabulator.off(eventName as keyof EventCallBackMethods, handler);
}
}
};
}, Object.values(events ?? {}));
// Change in data.
useEffect(() => { tabulatorRef.current?.setData(data) }, [ data ]);
useEffect(() => { columns && tabulatorRef.current?.setColumns(columns)}, [ data]);
useEffect(() => { tabulatorRef.current?.setData(data); }, [ data ]);
useEffect(() => {
if (!columns) return;
tabulatorRef.current?.setColumns(columns);
}, [ columns ]);
return (
<div ref={containerRef} className={className} />

View File

@ -82,6 +82,10 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
align-items: flex-start;
}
.modal.popup-editor-dialog .note-detail {
width: 100%;
}
.modal.popup-editor-dialog .note-detail.full-height {
flex-grow: 0;
height: 100%;
@ -106,4 +110,4 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
margin: 0;
border-radius: 0;
}
}
}

View File

@ -7,6 +7,7 @@ import { ComponentChild } from "preact";
import { useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { Trans } from "react-i18next";
import FNote from "../../entities/fnote";
import { ViewScope } from "../../services/link";
import { formatDateTime } from "../../utils/formatters";
import NoteIcon from "../note_icon";
@ -22,12 +23,12 @@ const supportedNoteTypes = new Set<NoteType>([
export default function InlineTitle() {
const { note, parentComponent, viewScope } = useNoteContext();
const type = useNoteProperty(note, "type");
const [ shown, setShown ] = useState(shouldShow(note?.noteId, type, viewScope));
const [ shown, setShown ] = useState(shouldShow(note, type, viewScope));
const containerRef = useRef<HTMLDivElement>(null);
const [ titleHidden, setTitleHidden ] = useState(false);
useLayoutEffect(() => {
setShown(shouldShow(note?.noteId, type, viewScope));
setShown(shouldShow(note, type, viewScope));
}, [ note, type, viewScope ]);
useLayoutEffect(() => {
@ -69,9 +70,10 @@ export default function InlineTitle() {
);
}
function shouldShow(noteId: string | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
function shouldShow(note: FNote | null | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
if (viewScope?.viewMode !== "default") return false;
if (noteId?.startsWith("_options")) return true;
if (note?.noteId?.startsWith("_options")) return true;
if (note?.isTriliumSqlite()) return false;
return type && supportedNoteTypes.has(type);
}

View File

@ -39,7 +39,7 @@ export default function NoteTypeSwitcher() {
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
return (currentNoteType && supportedNoteTypes.has(currentNoteType) && !note?.isTriliumSqlite() &&
<div
className="note-type-switcher"
onWheel={onWheelHorizontalScroll}

View File

@ -1,6 +1,6 @@
import "./StatusBar.css";
import { Locale, NoteType } from "@triliumnext/commons";
import { Locale, NOTE_TYPE_ICONS, NoteType } from "@triliumnext/commons";
import { Dropdown as BootstrapDropdown } from "bootstrap";
import clsx from "clsx";
import { type ComponentChildren, RefObject } from "preact";
@ -9,7 +9,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "p
import { CommandNames } from "../../components/app_context";
import NoteContext from "../../components/note_context";
import FNote, { NOTE_TYPE_ICONS } from "../../entities/fnote";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import { ViewScope } from "../../services/link";

View File

@ -1232,7 +1232,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
refreshCtx.noteIdsToUpdate.add(noteId);
}
if (refreshCtx.noteIdsToUpdate.size + refreshCtx.noteIdsToReload.size > 0) {
const hasNotesToUpdateOrReload = refreshCtx.noteIdsToUpdate.size + refreshCtx.noteIdsToReload.size > 0;
const hasNoteReorderingChange = loadResults.getNoteReorderings().length > 0;
if (hasNotesToUpdateOrReload || hasNoteReorderingChange) {
await this.#executeTreeUpdates(refreshCtx, loadResults);
}
@ -1393,6 +1395,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
for (const parentNoteId of loadResults.getNoteReorderings()) {
for (const node of this.getNodesByNoteId(parentNoteId)) {
console.log("Reordering ", node);
if (node.isLoaded()) {
this.sortChildren(node);
}

View File

@ -12,7 +12,7 @@ import { TypeWidgetProps } from "./type_widgets/type_widget";
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
* for protected session or attachment information.
*/
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat";
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat" | "sqlConsole";
export type TypeWidget = ((props: TypeWidgetProps) => VNode | JSX.Element | undefined);
type NoteTypeView = () => (Promise<{ default: TypeWidget } | TypeWidget> | TypeWidget);
@ -140,5 +140,10 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
view: () => import("./type_widgets/AiChat"),
className: "ai-chat-widget-container",
isFullHeight: true
},
sqlConsole: {
view: () => import("./type_widgets/SqlConsole"),
className: "sql-console-widget-container",
isFullHeight: true
}
};

View File

@ -0,0 +1,18 @@
.no-items {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
flex-direction: column;
padding: 0.75em;
color: var(--muted-text-color);
height: 100%;
.tn-icon {
font-size: 3em;
}
button {
margin-top: 1em;
}
}

View File

@ -0,0 +1,21 @@
import "./NoItems.css";
import { ComponentChildren } from "preact";
import Icon from "./Icon";
interface NoItemsProps {
icon: string;
text: string;
children?: ComponentChildren;
}
export default function NoItems({ icon, text, children }: NoItemsProps) {
return (
<div className="no-items">
<Icon icon={icon} />
{text}
{children}
</div>
);
}

View File

@ -184,7 +184,8 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: N
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActionsCustomInnerProps) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
const isSavedSqlite = note.isTriliumSqlite() && !note.isHiddenCompletely();
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap" || isSavedSqlite)
&& note.isContentAvailable() && isDefaultViewMode;
return isEnabled && <ActionButton

View File

@ -1,11 +1,14 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { useNoteContext } from "./react/hooks";
export default function ScrollPadding() {
const { note, parentComponent, ntxId, viewScope } = useNoteContext();
const ref = useRef<HTMLDivElement>(null);
const [height, setHeight] = useState<number>(10);
const isEnabled = ["text", "code"].includes(note?.type ?? "") && viewScope?.viewMode === "default";
const isEnabled = ["text", "code"].includes(note?.type ?? "")
&& viewScope?.viewMode === "default"
&& !note?.isTriliumSqlite();
const refreshHeight = () => {
if (!ref.current) return;
@ -37,6 +40,6 @@ export default function ScrollPadding() {
style={{ height }}
onClick={() => parentComponent.triggerCommand("scrollToEnd", { ntxId })}
/>
: <div></div>
)
: <div />
);
}

View File

@ -40,22 +40,4 @@ body.experimental-feature-new-layout #right-pane {
.gutter-vertical + .card .card-header {
padding-top: 0;
}
.no-items {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
flex-direction: column;
padding: 0.75em;
color: var(--muted-text-color);
.tn-icon {
font-size: 3em;
}
button {
margin-top: 1em;
}
}
}

View File

@ -3,7 +3,7 @@ import "./RightPanelContainer.css";
import Split from "@triliumnext/split.js";
import { VNode } from "preact";
import { useState, useEffect, useRef, useCallback } from "preact/hooks";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context";
import { WidgetsByParent } from "../../services/bundle";
@ -12,7 +12,7 @@ import options from "../../services/options";
import { DEFAULT_GUTTER_SIZE } from "../../services/resizer";
import Button from "../react/Button";
import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumEvent, useTriliumOptionJson } from "../react/hooks";
import Icon from "../react/Icon";
import NoItems from "../react/NoItems";
import LegacyRightPanelWidget from "../right_panel_widget";
import HighlightsList from "./HighlightsList";
import PdfAttachments from "./pdf/PdfAttachments";
@ -47,14 +47,15 @@ export default function RightPanelContainer({ widgetsByParent }: { widgetsByPare
items.length > 0 ? (
items
) : (
<div className="no-items">
<Icon icon="bx bx-sidebar" />
{t("right_pane.empty_message")}
<NoItems
icon="bx bx-sidebar"
text={t("right_pane.empty_message")}
>
<Button
text={t("right_pane.empty_button")}
triggerCommand="toggleRightPane"
/>
</div>
</NoItems>
)
)}
</div>

View File

@ -1,7 +0,0 @@
.sql-result-widget {
padding: 15px;
}
.sql-console-result-container td {
white-space: preserve;
}

View File

@ -1,63 +0,0 @@
import { SqlExecuteResults } from "@triliumnext/commons";
import { useNoteContext, useTriliumEvent } from "./react/hooks";
import "./sql_result.css";
import { useState } from "preact/hooks";
import Alert from "./react/Alert";
import { t } from "../services/i18n";
export default function SqlResults() {
const { note, ntxId } = useNoteContext();
const [ results, setResults ] = useState<SqlExecuteResults>();
useTriliumEvent("sqlQueryResults", ({ ntxId: eventNtxId, results }) => {
if (eventNtxId !== ntxId) return;
setResults(results);
})
const isEnabled = note?.mime === "text/x-sqlite;schema=trilium";
return (
<div className={`sql-result-widget ${!isEnabled ? "hidden-ext" : ""}`}>
{isEnabled && (
results?.length === 1 && Array.isArray(results[0]) && results[0].length === 0 ? (
<Alert type="info">
{t("sql_result.no_rows")}
</Alert>
) : (
<div className="sql-console-result-container selectable-text">
{results?.map(rows => {
// inserts, updates
if (typeof rows === "object" && !Array.isArray(rows)) {
return <pre>{JSON.stringify(rows, null, "\t")}</pre>
}
// selects
return <SqlResultTable rows={rows} />
})}
</div>
)
)}
</div>
)
}
function SqlResultTable({ rows }: { rows: object[] }) {
if (!rows.length) return;
return (
<table className="table table-striped">
<thead>
<tr>
{Object.keys(rows[0]).map(key => <th>{key}</th>)}
</tr>
</thead>
<tbody>
{rows.map(row => (
<tr>
{Object.values(row).map(cell => <td>{cell}</td>)}
</tr>
))}
</tbody>
</table>
)
}

View File

@ -1,43 +0,0 @@
.sql-table-schemas-widget {
padding: 12px;
padding-inline-end: 10%;
contain: none !important;
}
.sql-table-schemas > .dropdown {
display: inline-block !important;
}
.sql-table-schemas button.btn {
padding: 0.25rem 0.4rem;
font-size: 0.875rem;
line-height: 0.5;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
background: var(--button-background-color);
color: var(--button-text-color);
cursor: pointer;
}
.sql-console-result-container {
width: 100%;
font-size: smaller;
margin-top: 10px;
flex-grow: 1;
overflow: auto;
min-height: 0;
}
.table-schema td {
padding: 5px;
}
.dropdown .table-schema {
font-family: var(--monospace-font-family);
font-size: .85em;
}
/* Data type */
.dropdown .table-schema td:nth-child(2) {
color: var(--muted-text-color);
}

View File

@ -1,51 +0,0 @@
import { useEffect, useState } from "preact/hooks";
import { t } from "../services/i18n";
import { useNoteContext } from "./react/hooks";
import "./sql_table_schemas.css";
import { SchemaResponse } from "@triliumnext/commons";
import server from "../services/server";
import Dropdown from "./react/Dropdown";
export default function SqlTableSchemas() {
const { note } = useNoteContext();
const isEnabled = note?.mime === "text/x-sqlite;schema=trilium";
return (
<div className={`sql-table-schemas-widget ${!isEnabled ? "hidden-ext" : ""}`}>
{isEnabled && <SqlTableSchemasContent />}
</div>
)
}
function SqlTableSchemasContent() {
const [ schemas, setSchemas ] = useState<SchemaResponse[]>();
useEffect(() => {
server.get<SchemaResponse[]>("sql/schema").then(setSchemas);
}, []);
return schemas && (
<>
{t("sql_table_schemas.tables")}{": "}
<span className="sql-table-schemas">
{schemas.map(({ name, columns }) => (
<>
<Dropdown text={name} noSelectButtonStyle hideToggleArrow
>
<table className="table-schema">
{columns.map(column => (
<tr>
<td>{column.name}</td>
<td>{column.type}</td>
</tr>
))}
</table>
</Dropdown>
{" "}
</>
))}
</span>
</>
)
}

View File

@ -1,12 +1,15 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { TypeWidgetProps } from "./type_widget";
import render from "../../services/render";
import { refToJQuerySelector } from "../react/react_utils";
import Alert from "../react/Alert";
import "./Render.css";
import { useEffect, useRef, useState } from "preact/hooks";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import RawHtml from "../react/RawHtml";
import render from "../../services/render";
import Alert from "../react/Alert";
import { useTriliumEvent } from "../react/hooks";
import RawHtml from "../react/RawHtml";
import { refToJQuerySelector } from "../react/react_utils";
import { TypeWidgetProps } from "./type_widget";
export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
const contentRef = useRef<HTMLDivElement>(null);
@ -31,6 +34,13 @@ export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
refresh();
});
// Refresh on attribute change.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(a => a.type === "relation" && a.name === "renderNote" && attributes.isAffecting(a, note))) {
refresh();
}
});
// Integration with search.
useTriliumEvent("executeWithContentElement", ({ resolve, ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId) return;

View File

@ -0,0 +1,81 @@
.sql-console-widget-container {
.note-detail-split.split-vertical {
flex-direction: column-reverse;
}
.note-detail-split-preview {
overflow: auto;
}
.gutter {
background-color: var(--accented-background-color) !important;
}
.sql-result-widget {
height: 100%;
> .sql-console-result-container {
width: 100%;
height: 100%;
font-size: smaller;
flex-grow: 1;
overflow: auto;
min-height: 0;
> .tabulator {
--cell-vert-padding-size: 4px;
> .tabulator-tableholder {
padding: 0;
}
> .tabulator-footer,
> .tabulator-footer .tabulator-footer-contents {
padding: 2px 4px;
}
}
}
}
.sql-table-schemas-widget {
padding: 12px;
padding-inline-end: 10%;
contain: none !important;
.sql-table-schemas {
display: flex;
flex-wrap: wrap;
gap: 0.25em;
}
> .dropdown {
display: inline-block !important;
}
button.btn {
padding: 0.25rem 0.4rem;
font-size: 0.875rem;
line-height: 0.5;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
background: var(--button-background-color);
color: var(--button-text-color);
cursor: pointer;
}
.table-schema td {
padding: 5px;
}
.dropdown .table-schema {
font-family: var(--monospace-font-family);
font-size: .85em;
}
/* Data type */
.dropdown .table-schema td:nth-child(2) {
color: var(--muted-text-color);
}
}
}

View File

@ -0,0 +1,176 @@
import "./SqlConsole.css";
import { SchemaResponse, SqlExecuteResponse } from "@triliumnext/commons";
import { useEffect, useState } from "preact/hooks";
import { ClipboardModule, EditModule, ExportModule, FilterModule, FormatModule, FrozenColumnsModule, KeybindingsModule, PageModule, ResizeColumnsModule, SelectRangeModule, SelectRowModule, SortModule } from "tabulator-tables";
import { t } from "../../services/i18n";
import server from "../../services/server";
import Tabulator from "../collections/table/tabulator";
import Button from "../react/Button";
import Dropdown from "../react/Dropdown";
import { useTriliumEvent } from "../react/hooks";
import NoItems from "../react/NoItems";
import SplitEditor from "./helpers/SplitEditor";
import { TypeWidgetProps } from "./type_widget";
export default function SqlConsole(props: TypeWidgetProps) {
return (
<SplitEditor
noteType="code"
{...props}
editorBefore={<SqlTableSchemas {...props} />}
previewContent={<SqlResults key={props.note.noteId} {...props} />}
forceOrientation="vertical"
splitOptions={{
sizes: [ 70, 30 ]
}}
/>
);
}
function SqlResults({ ntxId }: TypeWidgetProps) {
const [ response, setResponse ] = useState<SqlExecuteResponse>();
useTriliumEvent("sqlQueryResults", ({ ntxId: eventNtxId, response }) => {
if (eventNtxId !== ntxId) return;
setResponse(response);
});
// Not yet executed.
if (response === undefined) {
return (
<NoItems
icon="bx bx-data"
text={t("sql_result.not_executed")}
>
<Button
text={t("sql_result.execute_now")}
triggerCommand="runActiveNote"
/>
</NoItems>
);
}
// Executed but failed.
if (response && !response.success) {
return (
<NoItems
icon="bx bx-error"
text={t("sql_result.failed")}
>
<pre className="sql-error-message selectable-text">{response.error}</pre>
</NoItems>
);
}
// Zero results.
if (response?.results.length === 1 && Array.isArray(response.results[0]) && response.results[0].length === 0) {
return (
<NoItems
icon="bx bx-rectangle"
text={t("sql_result.no_rows")}
/>
);
}
return (
<div className="sql-result-widget">
<div className="sql-console-result-container selectable-text">
{response?.results.map((rows, index) => {
// inserts, updates
if (typeof rows === "object" && !Array.isArray(rows)) {
return (
<NoItems
key={index}
icon="bx bx-play"
text={t("sql_result.statement_result")}
>
<pre key={index}>{JSON.stringify(rows, null, "\t")}</pre>
</NoItems>
);
}
// selects
return <SqlResultTable key={index} rows={rows} />;
})}
</div>
</div>
);
}
function SqlResultTable({ rows }: { rows: object[] }) {
if (!rows.length) return;
return (
<Tabulator
layout="fitDataFill"
modules={[ ResizeColumnsModule, SortModule, SelectRangeModule, ClipboardModule, KeybindingsModule, EditModule, ExportModule, SelectRowModule, FormatModule, FrozenColumnsModule, FilterModule, PageModule ]}
selectableRange
clipboard="copy"
clipboardCopyRowRange="range"
clipboardCopyConfig={{
rowHeaders: false,
columnHeaders: false
}}
pagination
paginationSize={15}
paginationSizeSelector
paginationCounter="rows"
height="100%"
columns={[
{
title: "#",
formatter: "rownum",
width: 60,
hozAlign: "right",
frozen: true
},
...Object.keys(rows[0]).map(key => ({
title: key,
field: key,
width: 250,
minWidth: 100,
widthGrow: 1,
resizable: true,
headerFilter: true as const
}))
]}
data={rows}
/>
);
}
export function SqlTableSchemas({ note }: TypeWidgetProps) {
const [ schemas, setSchemas ] = useState<SchemaResponse[]>();
useEffect(() => {
server.get<SchemaResponse[]>("sql/schema").then(setSchemas);
}, []);
const isEnabled = note.isTriliumSqlite() && schemas;
return (
<div className={`sql-table-schemas-widget ${!isEnabled ? "hidden-ext" : ""}`}>
{isEnabled && (
<>
{t("sql_table_schemas.tables")}{": "}
<span class="sql-table-schemas">
{schemas.map(({ name, columns }) => (
<Dropdown key={name} text={name} noSelectButtonStyle hideToggleArrow>
<table className="table-schema">
{columns.map(column => (
<tr key={column.name}>
<td>{column.name}</td>
<td>{column.type}</td>
</tr>
))}
</table>
</Dropdown>
))}
</span>
</>
)}
</div>
);
}

View File

@ -20,6 +20,9 @@ export interface CanvasContent {
appState: Partial<AppState>;
}
/** Subset of the app state that should be persisted whenever they change. This explicitly excludes transient state like the current selection or zoom level. */
type ImportantAppState = Pick<AppState, "gridModeEnabled" | "viewBackgroundColor">;
export default function useCanvasPersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: RefObject<ExcalidrawImperativeAPI>, theme: AppState["theme"], isReadOnly: boolean): Partial<ExcalidrawProps> {
const libraryChanged = useRef(false);
@ -37,6 +40,8 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
const libraryCache = useRef<LibraryItem[]>([]);
const attachmentMetadata = useRef<AttachmentMetadata[]>([]);
const appStateToCompare = useRef<Partial<ImportantAppState>>({});
const spacedUpdate = useEditorSpacedUpdate({
note,
noteContext,
@ -47,7 +52,6 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
libraryCache.current = [];
attachmentMetadata.current = [];
currentSceneVersion.current = -1;
// load saved content into excalidraw canvas
let content: CanvasContent = {
@ -65,6 +69,9 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
loadData(api, content, theme);
// Initialize tracking state after loading to prevent redundant updates from initial onChange events
currentSceneVersion.current = getSceneVersion(api.getSceneElements());
// load the library state
loadLibrary(note).then(({ libraryItems, metadata }) => {
// Update the library and save to independent variables
@ -78,7 +85,7 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
async getData() {
const api = apiRef.current;
if (!api) return;
const { content, svg } = await getData(api);
const { content, svg } = await getData(api, appStateToCompare);
const attachments: SavedData["attachments"] = [{ role: "image", title: "canvas-export.svg", mime: "image/svg+xml", content: svg, position: 0 }];
// libraryChanged is unset in dataSaved()
@ -149,21 +156,47 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
const oldSceneVersion = currentSceneVersion.current;
const newSceneVersion = getSceneVersion(apiRef.current.getSceneElements());
if (newSceneVersion !== oldSceneVersion) {
let hasChanges = (newSceneVersion !== oldSceneVersion);
// There are cases where the scene version does not change, but appState did.
if (!hasChanges) {
const importantAppState = appStateToCompare.current;
const currentAppState = apiRef.current.getAppState();
for (const key in importantAppState) {
if (importantAppState[key as keyof ImportantAppState] !== currentAppState[key as keyof ImportantAppState]) {
hasChanges = true;
break;
}
}
}
if (hasChanges) {
spacedUpdate.resetUpdateTimer();
spacedUpdate.scheduleUpdate();
currentSceneVersion.current = newSceneVersion;
}
},
onLibraryChange: () => {
libraryChanged.current = true;
spacedUpdate.resetUpdateTimer();
spacedUpdate.scheduleUpdate();
onLibraryChange: (libraryItems) => {
if (!apiRef.current || isReadOnly) return;
// Check if library actually changed by comparing with cached state
const hasChanges =
libraryItems.length !== libraryCache.current.length ||
libraryItems.some(item => {
const cachedItem = libraryCache.current.find(cached => cached.id === item.id);
return !cachedItem || cachedItem.name !== item.name;
});
if (hasChanges) {
libraryChanged.current = true;
spacedUpdate.resetUpdateTimer();
spacedUpdate.scheduleUpdate();
}
}
};
}
async function getData(api: ExcalidrawImperativeAPI) {
async function getData(api: ExcalidrawImperativeAPI, appStateToCompare: RefObject<Partial<ImportantAppState>>) {
const elements = api.getSceneElements();
const appState = api.getAppState();
@ -188,6 +221,12 @@ async function getData(api: ExcalidrawImperativeAPI) {
}
});
const importantAppState: ImportantAppState = {
gridModeEnabled: appState.gridModeEnabled,
viewBackgroundColor: appState.viewBackgroundColor
};
appStateToCompare.current = importantAppState;
const content = {
type: "excalidraw",
version: 2,
@ -197,7 +236,7 @@ async function getData(api: ExcalidrawImperativeAPI) {
scrollX: appState.scrollX,
scrollY: appState.scrollY,
zoom: appState.zoom,
gridModeEnabled: appState.gridModeEnabled
...importantAppState
}
};

View File

@ -1,13 +1,15 @@
import { useEffect, useRef } from "preact/hooks";
import utils, { isMobile } from "../../../services/utils";
import Admonition from "../../react/Admonition";
import { useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
import "./SplitEditor.css";
import Split from "@triliumnext/split.js";
import { DEFAULT_GUTTER_SIZE } from "../../../services/resizer";
import { EditableCode, EditableCodeProps } from "../code/Code";
import { ComponentChildren } from "preact";
import { useEffect, useRef } from "preact/hooks";
import { DEFAULT_GUTTER_SIZE } from "../../../services/resizer";
import utils, { isMobile } from "../../../services/utils";
import ActionButton, { ActionButtonProps } from "../../react/ActionButton";
import Admonition from "../../react/Admonition";
import { useNoteBlob, useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
import { EditableCode, EditableCodeProps } from "../code/Code";
export interface SplitEditorProps extends EditableCodeProps {
className?: string;
@ -15,6 +17,8 @@ export interface SplitEditorProps extends EditableCodeProps {
splitOptions?: Split.Options;
previewContent: ComponentChildren;
previewButtons?: ComponentChildren;
editorBefore?: ComponentChildren;
forceOrientation?: "horizontal" | "vertical";
}
/**
@ -26,13 +30,24 @@ export interface SplitEditorProps extends EditableCodeProps {
* - Can display errors to the user via {@link setError}.
* - Horizontal or vertical orientation for the editor/preview split, adjustable via the switch split orientation button floating button.
*/
export default function SplitEditor({ note, error, splitOptions, previewContent, previewButtons, className, ...editorProps }: SplitEditorProps) {
const splitEditorOrientation = useSplitOrientation();
const [ readOnly ] = useNoteLabelBoolean(note, "readOnly");
const containerRef = useRef<HTMLDivElement>(null);
export default function SplitEditor(props: SplitEditorProps) {
const [ readOnly ] = useNoteLabelBoolean(props.note, "readOnly");
const editor = (!readOnly &&
if (readOnly) {
return <ReadOnlyView {...props} />;
}
return <EditorWithSplit {...props} />;
}
function EditorWithSplit({ note, error, splitOptions, previewContent, previewButtons, className, editorBefore, forceOrientation, ...editorProps }: SplitEditorProps) {
const containerRef = useRef<HTMLDivElement>(null);
const splitEditorOrientation = useSplitOrientation(forceOrientation);
const editor = (
<div className="note-detail-split-editor-col">
{editorBefore}
<div className="note-detail-split-editor">
<EditableCode
note={note}
@ -48,19 +63,14 @@ export default function SplitEditor({ note, error, splitOptions, previewContent,
</div>
);
const preview = (
<div className="note-detail-split-preview-col">
<div className={`note-detail-split-preview ${error ? "on-error" : ""}`}>
{previewContent}
</div>
<div className="btn-group btn-group-sm map-type-switcher content-floating-buttons preview-buttons bottom-right" role="group">
{previewButtons}
</div>
</div>
);
const preview = <PreviewContainer
error={error}
previewContent={previewContent}
previewButtons={previewButtons}
/>;
useEffect(() => {
if (!utils.isDesktop() || !containerRef.current || readOnly) return;
if (!utils.isDesktop() || !containerRef.current) return;
const elements = Array.from(containerRef.current?.children) as HTMLElement[];
const splitInstance = Split(elements, {
rtl: glob.isRtl,
@ -71,15 +81,52 @@ export default function SplitEditor({ note, error, splitOptions, previewContent,
});
return () => splitInstance.destroy();
}, [ readOnly, splitEditorOrientation ]);
}, [ splitEditorOrientation ]);
return (
<div ref={containerRef} className={`note-detail-split note-detail-printable ${"split-" + splitEditorOrientation} ${readOnly ? "split-read-only" : ""} ${className ?? ""}`}>
<div ref={containerRef} className={`note-detail-split note-detail-printable ${`split-${splitEditorOrientation}`} ${className ?? ""}`}>
{splitEditorOrientation === "horizontal"
? <>{editor}{preview}</>
: <>{preview}{editor}</>}
? <>{editor}{preview}</>
: <>{preview}{editor}</>}
</div>
)
);
}
function ReadOnlyView({ ...props }: SplitEditorProps) {
const { note, onContentChanged } = props;
const content = useNoteBlob(note);
const onContentChangedRef = useRef(onContentChanged);
useEffect(() => {
onContentChangedRef.current = onContentChanged;
});
useEffect(() => {
onContentChangedRef.current?.(content?.content ?? "");
}, [ content ]);
return (
<div className={`note-detail-split note-detail-printable ${props.className} split-read-only`}>
<PreviewContainer {...props} />
</div>
);
}
function PreviewContainer({ error, previewContent, previewButtons }: {
error?: string | null;
previewContent: ComponentChildren;
previewButtons?: ComponentChildren;
}) {
return (
<div className="note-detail-split-preview-col">
<div className={`note-detail-split-preview ${error ? "on-error" : ""}`}>
{previewContent}
</div>
<div className="btn-group btn-group-sm map-type-switcher content-floating-buttons preview-buttons bottom-right" role="group">
{previewButtons}
</div>
</div>
);
}
export function PreviewButton(props: Omit<ActionButtonProps, "titlePosition">) {
@ -88,11 +135,12 @@ export function PreviewButton(props: Omit<ActionButtonProps, "titlePosition">) {
className="tn-tool-button"
noIconActionClass
titlePosition="top"
/>
/>;
}
function useSplitOrientation() {
function useSplitOrientation(forceOrientation?: "horizontal" | "vertical") {
const [ splitEditorOrientation ] = useTriliumOption("splitEditorOrientation");
if (forceOrientation) return forceOrientation;
if (isMobile()) return "vertical";
if (!splitEditorOrientation) return "horizontal";
return splitEditorOrientation as "horizontal" | "vertical";

View File

@ -1,13 +1,14 @@
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../../services/i18n";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
import { RawHtmlBlock } from "../../react/RawHtml";
import server from "../../../services/server";
import svgPanZoom from "svg-pan-zoom";
import { RefObject } from "preact";
import { useElementSize, useTriliumEvent } from "../../react/hooks";
import utils from "../../../services/utils";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import svgPanZoom from "svg-pan-zoom";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast";
import utils from "../../../services/utils";
import { useElementSize, useTriliumEvent } from "../../react/hooks";
import { RawHtmlBlock } from "../../react/RawHtml";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
/**
@ -144,7 +145,7 @@ export default function SvgSplitEditor({ ntxId, note, attachmentName, renderSvg,
}
{...props}
/>
)
);
}
function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg: string | undefined) {
@ -181,7 +182,7 @@ function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg
lastPanZoom.current = {
pan: zoomInstance.getPan(),
zoom: zoomInstance.getZoom()
}
};
zoomRef.current = undefined;
zoomInstance.destroy();
};

View File

@ -191,7 +191,6 @@ function ExperimentalOptions() {
values={filteredExperimentalFeatures}
keyProperty="id"
titleProperty="name"
descriptionProperty="description"
currentValue={enabledExperimentalFeatures} onChange={setEnabledExperimentalFeatures}
/>
</OptionsSection>

View File

@ -1,17 +1,14 @@
import FormCheckbox from "../../../react/FormCheckbox";
interface CheckboxListProps<T> {
values: T[];
keyProperty: keyof T;
titleProperty?: keyof T;
disabledProperty?: keyof T;
descriptionProperty?: keyof T;
currentValue: string[];
onChange: (newValues: string[]) => void;
columnWidth?: string;
}
export default function CheckboxList<T>({ values, keyProperty, titleProperty, disabledProperty, descriptionProperty, currentValue, onChange, columnWidth }: CheckboxListProps<T>) {
export default function CheckboxList<T>({ values, keyProperty, titleProperty, disabledProperty, currentValue, onChange, columnWidth }: CheckboxListProps<T>) {
function toggleValue(value: string) {
if (currentValue.includes(value)) {
// Already there, needs removing.
@ -25,17 +22,20 @@ export default function CheckboxList<T>({ values, keyProperty, titleProperty, di
return (
<ul style={{ listStyleType: "none", marginBottom: 0, columnWidth: columnWidth ?? "400px" }}>
{values.map(value => (
<li key={String(value[keyProperty])}>
<FormCheckbox
label={String(value[titleProperty ?? keyProperty] ?? value[keyProperty])}
name={String(value[keyProperty])}
currentValue={currentValue.includes(String(value[keyProperty]))}
disabled={!!(disabledProperty && value[disabledProperty])}
hint={value && (descriptionProperty ? String(value[descriptionProperty]) : undefined)}
onChange={() => toggleValue(String(value[keyProperty]))}
/>
<li>
<label className="tn-checkbox">
<input
type="checkbox"
className="form-check-input"
value={String(value[keyProperty])}
checked={currentValue.includes(String(value[keyProperty]))}
disabled={!!(disabledProperty && value[disabledProperty])}
onChange={e => toggleValue((e.target as HTMLInputElement).value)}
/>
{String(value[titleProperty ?? keyProperty] ?? value[keyProperty])}
</label>
</li>
))}
</ul>
);
}
)
}

View File

@ -85,7 +85,7 @@ export default defineConfig(() => ({
sourcemap: false,
rollupOptions: {
input: {
index: join(__dirname, "src", "index.html"),
index: join(__dirname, "index.html"),
login: join(__dirname, "src", "login.ts"),
setup: join(__dirname, "src", "setup.ts"),
set_password: join(__dirname, "src", "set_password.ts"),
@ -93,7 +93,15 @@ export default defineConfig(() => ({
print: join(__dirname, "src", "print.tsx")
},
output: {
entryFileNames: "src/[name].js",
entryFileNames: (chunk) => {
// We enforce a hash in the main index file to avoid caching issues, this only works because we have the HTML entry point.
if (chunk.name === "index") {
return "src/[name]-[hash].js";
}
// For EJS-rendered pages (e.g. login) we need to have a stable name.
return "src/[name].js";
},
chunkFileNames: "src/[name]-[hash].js",
assetFileNames: "src/[name]-[hash].[ext]",
manualChunks: {

View File

@ -5,7 +5,7 @@
"description": "Tool to compare content of Trilium databases. Useful for debugging sync problems.",
"dependencies": {
"colors": "1.4.0",
"diff": "8.0.2",
"diff": "8.0.3",
"sqlite": "5.1.1",
"sqlite3": "5.1.7"
},

1
apps/desktop/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
electron-forge/app-icon/mac

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,9 +1,11 @@
import path, { join } from "path";
import fs from "fs-extra";
import { LOCALES } from "@triliumnext/commons";
import { PRODUCT_NAME } from "../src/app-info.js";
import type { ForgeConfig } from "@electron-forge/shared-types";
import { LOCALES } from "@triliumnext/commons";
import { existsSync } from "fs";
import fs from "fs-extra";
import path, { join } from "path";
import packageJson from "../package.json" assert { type: "json" };
import { PRODUCT_NAME } from "../src/app-info.js";
const ELECTRON_FORGE_DIR = __dirname;
@ -12,12 +14,12 @@ const APP_ICON_PATH = path.join(ELECTRON_FORGE_DIR, "app-icon");
const extraResourcesForPlatform = getExtraResourcesForPlatform();
const baseLinuxMakerConfigOptions = {
name: EXECUTABLE_NAME,
bin: EXECUTABLE_NAME,
productName: PRODUCT_NAME,
icon: path.join(APP_ICON_PATH, "png/128x128.png"),
desktopTemplate: path.resolve(path.join(ELECTRON_FORGE_DIR, "desktop.ejs")),
categories: ["Office", "Utility"]
name: EXECUTABLE_NAME,
bin: EXECUTABLE_NAME,
productName: PRODUCT_NAME,
icon: path.join(APP_ICON_PATH, "png/128x128.png"),
desktopTemplate: path.resolve(path.join(ELECTRON_FORGE_DIR, "desktop.ejs")),
categories: ["Office", "Utility"]
};
const windowsSignConfiguration = process.env.WINDOWS_SIGN_EXECUTABLE ? {
hookModulePath: path.join(ELECTRON_FORGE_DIR, "sign-windows.cjs")
@ -30,6 +32,7 @@ const macosSignConfiguration = process.env.APPLE_ID ? {
teamId: process.env.APPLE_TEAM_ID!
}
} : undefined;
const isNightly = packageJson.version.includes("test");
const config: ForgeConfig = {
outDir: "out",
@ -37,9 +40,10 @@ const config: ForgeConfig = {
packagerConfig: {
executableName: EXECUTABLE_NAME,
name: PRODUCT_NAME,
appVersion: packageJson.version,
overwrite: true,
asar: true,
icon: path.join(APP_ICON_PATH, "icon"),
icon: path.join(APP_ICON_PATH, isNightly ? "icon-dev" : "icon"),
...macosSignConfiguration,
windowsSign: windowsSignConfiguration,
extraResource: [
@ -87,7 +91,7 @@ const config: ForgeConfig = {
...baseLinuxMakerConfigOptions,
desktopTemplate: undefined, // otherwise it would put in the wrong exec
icon: {
"128x128": path.join(APP_ICON_PATH, "png/128x128.png"),
"128x128": path.join(APP_ICON_PATH, isNightly ? "png/128x128-dev.png" : "png/128x128.png"),
},
id: "com.triliumnext.notes",
runtimeVersion: "24.08",
@ -136,24 +140,24 @@ const config: ForgeConfig = {
config: {
name: EXECUTABLE_NAME,
productName: PRODUCT_NAME,
iconUrl: "https://raw.githubusercontent.com/TriliumNext/Trilium/refs/heads/main/apps/desktop/electron-forge/app-icon/icon.ico",
setupIcon: path.join(ELECTRON_FORGE_DIR, "setup-icon/setup.ico"),
loadingGif: path.join(ELECTRON_FORGE_DIR, "setup-icon/setup-banner.gif"),
iconUrl: `https://raw.githubusercontent.com/TriliumNext/Trilium/refs/heads/main/apps/desktop/electron-forge/app-icon/${isNightly ? "icon-dev" : "icon"}.ico`,
setupIcon: path.join(ELECTRON_FORGE_DIR, isNightly ? "setup-icon/setup-dev.ico" : "setup-icon/setup.ico"),
loadingGif: path.join(ELECTRON_FORGE_DIR, isNightly ? "setup-icon/setup-banner-dev.gif" : "setup-icon/setup-banner.gif"),
windowsSign: windowsSignConfiguration
}
},
{
name: "@electron-forge/maker-dmg",
config: {
icon: path.join(APP_ICON_PATH, "icon.icns")
icon: path.join(APP_ICON_PATH, isNightly ? "icon-dev.icns" : "icon.icns")
}
},
{
name: "@electron-forge/maker-zip",
config: {
options: {
iconUrl: "https://raw.githubusercontent.com/TriliumNext/Trilium/refs/heads/main/apps/desktop/electron-forge/app-icon/icon.ico",
icon: path.join(APP_ICON_PATH, "icon.ico")
iconUrl: `https://raw.githubusercontent.com/TriliumNext/Trilium/refs/heads/main/apps/desktop/electron-forge/app-icon/${isNightly ? "icon-dev" : "icon"}.ico`,
icon: path.join(APP_ICON_PATH, isNightly ? "icon-dev.ico" : "icon.ico")
}
}
}
@ -172,7 +176,7 @@ const config: ForgeConfig = {
.filter(locale => !locale.contentOnly)
.map(locale => locale.electronLocale) as string[];
if (!isMac) {
localesToKeep = localesToKeep.map(locale => locale.replace("_", "-"))
localesToKeep = localesToKeep.map(locale => locale.replace("_", "-"));
}
const keptLocales = new Set();
@ -283,11 +287,11 @@ function getExtraResourcesForPlatform() {
const scripts = ["trilium-portable", "trilium-safe-mode", "trilium-no-cert-check"];
const scriptExt = (process.platform === "win32") ? "bat" : "sh";
return scripts.map(script => `electron-forge/${script}.${scriptExt}`);
}
};
switch (process.platform) {
case "win32":
resources.push(...getScriptResources())
resources.push(...getScriptResources());
break;
case "linux":
resources.push(...getScriptResources(), path.join(APP_ICON_PATH, "png/256x256.png"));
@ -300,18 +304,18 @@ function getExtraResourcesForPlatform() {
}
function getELFArch(file: string) {
const buf = fs.readFileSync(file);
const buf = fs.readFileSync(file);
if (buf[0] !== 0x7f || buf[1] !== 0x45 || buf[2] !== 0x4c || buf[3] !== 0x46) {
throw new Error("Not an ELF file");
}
if (buf[0] !== 0x7f || buf[1] !== 0x45 || buf[2] !== 0x4c || buf[3] !== 0x46) {
throw new Error("Not an ELF file");
}
const eiClass = buf[4]; // 1=32-bit, 2=64-bit
const eiMachine = buf[18]; // architecture code
const eiClass = buf[4]; // 1=32-bit, 2=64-bit
const eiMachine = buf[18]; // architecture code
if (eiMachine === 0x3E) return 'x86-64';
if (eiMachine === 0xB7) return 'ARM64';
return 'other';
if (eiMachine === 0x3E) return 'x86-64';
if (eiMachine === 0xB7) return 'ARM64';
return 'other';
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -23,7 +23,7 @@
},
"dependencies": {
"@electron/remote": "2.1.3",
"better-sqlite3": "12.6.0",
"better-sqlite3": "12.6.2",
"electron-debug": "4.1.0",
"electron-dl": "4.0.0",
"electron-squirrel-startup": "1.0.1",
@ -35,15 +35,15 @@
"@triliumnext/commons": "workspace:*",
"@triliumnext/server": "workspace:*",
"copy-webpack-plugin": "13.0.1",
"electron": "39.2.7",
"@electron-forge/cli": "7.10.2",
"@electron-forge/maker-deb": "7.10.2",
"@electron-forge/maker-dmg": "7.10.2",
"@electron-forge/maker-flatpak": "7.10.2",
"@electron-forge/maker-rpm": "7.10.2",
"@electron-forge/maker-squirrel": "7.10.2",
"@electron-forge/maker-zip": "7.10.2",
"@electron-forge/plugin-auto-unpack-natives": "7.10.2",
"electron": "40.0.0",
"@electron-forge/cli": "7.11.1",
"@electron-forge/maker-deb": "7.11.1",
"@electron-forge/maker-dmg": "7.11.1",
"@electron-forge/maker-flatpak": "7.11.1",
"@electron-forge/maker-rpm": "7.11.1",
"@electron-forge/maker-squirrel": "7.11.1",
"@electron-forge/maker-zip": "7.11.1",
"@electron-forge/plugin-auto-unpack-natives": "7.11.1",
"prebuild-install": "7.1.3"
}
}

View File

@ -4,7 +4,7 @@
"description": "Standalone tool to dump contents of Trilium document.db file into a directory tree of notes",
"private": true,
"dependencies": {
"better-sqlite3": "12.6.0",
"better-sqlite3": "12.6.2",
"mime-types": "3.0.2",
"sanitize-filename": "1.6.3",
"tsx": "4.21.0",

View File

@ -1,22 +1,24 @@
{
"name": "@triliumnext/edit-docs",
"version": "0.0.1",
"version": "0.101.3",
"private": true,
"description": "Desktop version of Trilium which imports the demo database (presented to new users at start-up) or the user guide and other documentation and saves the modifications for committing.",
"dependencies": {
"archiver": "7.0.1",
"better-sqlite3": "12.6.0"
"better-sqlite3": "12.6.2"
},
"devDependencies": {
"@triliumnext/client": "workspace:*",
"@triliumnext/desktop": "workspace:*",
"@types/fs-extra": "11.0.4",
"copy-webpack-plugin": "13.0.1",
"electron": "39.2.7",
"electron": "40.0.0",
"fs-extra": "11.3.3"
},
"scripts": {
"edit-docs": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-docs.ts",
"build": "tsx scripts/build.ts",
"test-build": "vitest --config vitest.build.config.mts",
"edit-docs": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store tsx ../../scripts/electron-start.mts src/edit-docs.ts",
"edit-demo": "cross-env TRILIUM_PORT=37744 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-demo.ts"
}
}

View File

@ -0,0 +1,40 @@
import { writeFileSync } from "fs";
import { join } from "path";
import BuildHelper from "../../../scripts/build-utils";
import originalPackageJson from "../package.json" with { type: "json" };
const build = new BuildHelper("apps/edit-docs");
async function main() {
await build.buildBackend(["src/edit-docs.ts", "src/utils.ts"]);
// Copy assets from server (needed for DB initialization)
build.copy("/apps/server/src/assets", "assets/");
build.triggerBuildAndCopyTo("packages/share-theme", "share-theme/assets/");
build.copy("/packages/share-theme/src/templates", "share-theme/templates/");
build.copy("/node_modules/ckeditor5/dist/ckeditor5-content.css", "ckeditor5-content.css");
build.buildFrontend();
// Copy node modules dependencies
build.copyNodeModules(["better-sqlite3", "bindings", "file-uri-to-path", "@electron/remote"]);
generatePackageJson();
}
function generatePackageJson() {
const { version, author, license, description, dependencies, devDependencies } = originalPackageJson;
const packageJson = {
name: "trilium-edit-docs",
main: "edit-docs.cjs",
version,
author,
license,
description,
dependencies: {"better-sqlite3": dependencies["better-sqlite3"]},
devDependencies: {electron: devDependencies.electron},
};
writeFileSync(join(build.outDir, "package.json"), JSON.stringify(packageJson, null, "\t"), "utf-8");
}
main();

View File

@ -0,0 +1,48 @@
import { globSync } from "fs";
import { join } from "path";
import { it, describe, expect } from "vitest";
describe("Check artifacts are present", () => {
const distPath = join(__dirname, "../../dist");
it("has the necessary node modules", async () => {
const paths = [
"node_modules/better-sqlite3",
"node_modules/bindings",
"node_modules/file-uri-to-path",
"node_modules/@electron/remote"
];
ensurePathsExist(paths);
});
it("includes the client", async () => {
const paths = [
"public/assets",
"public/fonts",
"public/node_modules",
"public/src",
"public/stylesheets",
"public/translations"
];
ensurePathsExist(paths);
});
it("includes necessary assets", async () => {
const paths = [
"assets",
"share-theme",
"ckeditor5-content.css"
];
ensurePathsExist(paths);
});
function ensurePathsExist(paths: string[]) {
for (const path of paths) {
const result = globSync(join(distPath, path, "**"));
expect(result, path).not.toHaveLength(0);
}
}
});

View File

@ -1,14 +1,17 @@
import fs from "fs/promises";
import fsExtra from "fs-extra";
import path from "path";
import type { NoteMetaFile } from "@triliumnext/server/src/services/meta/note_meta.js";
import { initializeTranslations } from "@triliumnext/server/src/services/i18n.js";
import debounce from "@triliumnext/client/src/services/debounce.js";
import { extractZip, importData, initializeDatabase, startElectron } from "./utils.js";
import cls from "@triliumnext/server/src/services/cls.js";
import type { AdvancedExportOptions, ExportFormat } from "@triliumnext/server/src/services/export/zip/abstract_provider.js";
import { initializeTranslations } from "@triliumnext/server/src/services/i18n.js";
import { parseNoteMetaFile } from "@triliumnext/server/src/services/in_app_help.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";
import fs from "fs/promises";
import fsExtra from "fs-extra";
import yaml from "js-yaml";
import path from "path";
import packageJson from "../package.json" with { type: "json" };
import { extractZip, importData, initializeDatabase, startElectron } from "./utils.js";
interface NoteMapping {
rootNoteId: string;
@ -18,47 +21,113 @@ interface NoteMapping {
exportOnly?: boolean;
}
const { DOCS_ROOT, USER_GUIDE_ROOT } = process.env;
if (!DOCS_ROOT || !USER_GUIDE_ROOT) {
throw new Error("Missing DOCS_ROOT or USER_GUIDE_ROOT environment variable.");
interface Config {
baseUrl: string;
noteMappings: NoteMapping[];
}
const BASE_URL = "https://docs.triliumnotes.org";
// Parse command-line arguments
function parseArgs() {
const args = process.argv.slice(2);
let configPath: string | undefined;
let showHelp = false;
let showVersion = false;
const NOTE_MAPPINGS: NoteMapping[] = [
{
rootNoteId: "pOsGYCXsbNQG",
path: path.join(__dirname, DOCS_ROOT, "User Guide"),
format: "markdown"
},
{
rootNoteId: "pOsGYCXsbNQG",
path: path.join(__dirname, USER_GUIDE_ROOT),
format: "html",
ignoredFiles: ["index.html", "navigation.html", "style.css", "User Guide.html"],
exportOnly: true
},
{
rootNoteId: "jdjRLhLV3TtI",
path: path.join(__dirname, DOCS_ROOT, "Developer Guide"),
format: "markdown"
},
{
rootNoteId: "hD3V4hiu2VW4",
path: path.join(__dirname, DOCS_ROOT, "Release Notes"),
format: "markdown"
for (let i = 0; i < args.length; i++) {
if (args[i] === '--config' || args[i] === '-c') {
configPath = args[i + 1];
if (!configPath) {
console.error("Error: --config/-c requires a path argument");
process.exit(1);
}
i++; // Skip the next argument as it's the value
} else if (args[i] === '--help' || args[i] === '-h') {
showHelp = true;
} else if (args[i] === '--version' || args[i] === '-v') {
showVersion = true;
}
}
];
return { configPath, showHelp, showVersion };
}
function getVersion(): string {
return packageJson.version;
}
function printHelp() {
const version = getVersion();
console.log(`
Usage: trilium-edit-docs [options]
Options:
-c, --config <path> Path to the configuration file (default: edit-docs-config.yaml in the root)
-h, --help Display this help message
-v, --version Display version information
Version: ${version}
`);
}
function printVersion() {
const version = getVersion();
console.log(version);
}
const { configPath, showHelp, showVersion } = parseArgs();
if (showHelp) {
printHelp();
process.exit(0);
} else if (showVersion) {
printVersion();
process.exit(0);
}
// Configuration variables to be initialized
let BASE_URL: string;
let NOTE_MAPPINGS: NoteMapping[];
// Load configuration from edit-docs-config.yaml
async function loadConfig() {
let CONFIG_PATH = configPath
? path.resolve(configPath)
: path.join(process.cwd(), "edit-docs-config.yaml");
const exists = await fs.access(CONFIG_PATH).then(() => true).catch(() => false);
if (!exists && !configPath) {
// Fallback to project root if running from within a subproject
CONFIG_PATH = path.join(__dirname, "../../../edit-docs-config.yaml");
}
const configContent = await fs.readFile(CONFIG_PATH, "utf-8");
const config = yaml.load(configContent) as Config;
BASE_URL = config.baseUrl;
// Resolve all paths relative to the config file's directory (for flexibility with external configs)
const CONFIG_DIR = path.dirname(CONFIG_PATH);
NOTE_MAPPINGS = config.noteMappings.map((mapping) => ({
...mapping,
path: path.resolve(CONFIG_DIR, mapping.path)
}));
}
async function main() {
await loadConfig();
const initializedPromise = startElectron(() => {
// Wait for the import to be finished and the application to be loaded before we listen to changes.
setTimeout(() => registerHandlers(), 10_000);
setTimeout(() => {
registerHandlers();
}, 10_000);
});
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 () => {
for (const mapping of NOTE_MAPPINGS) {
if (!mapping.exportOnly) {
@ -116,6 +185,9 @@ async function exportData(noteId: string, format: ExportFormat, outputPath: stri
return components.join("/");
});
// Remove data-list-item-id created by CKEditor for lists
content = content.replace(/ data-list-item-id="[^"]*"/g, "");
return content;
function findAttachment(targetAttachmentId: string) {
@ -142,7 +214,7 @@ async function exportData(noteId: string, format: ExportFormat, outputPath: stri
}
}
const minifyMeta = (format === "html");
const minifyMeta = (format === "html" || format === "share");
await cleanUpMeta(outputPath, minifyMeta);
}
@ -195,7 +267,6 @@ async function registerHandlers() {
return;
}
console.log("Got entity changed", e.entityName, e.entity.title);
debouncer();
});
}

View File

@ -1,23 +1,27 @@
import cls from "@triliumnext/server/src/services/cls.js";
import TaskContext from "@triliumnext/server/src/services/task_context.js";
import windowService from "@triliumnext/server/src/services/window.js";
import archiver, { type Archiver } from "archiver";
import electron from "electron";
import type { WriteStream } from "fs";
import fs from "fs/promises";
import fsExtra from "fs-extra";
import path from "path";
import electron from "electron";
import windowService from "@triliumnext/server/src/services/window.js";
import archiver, { type Archiver } from "archiver";
import type { WriteStream } from "fs";
import TaskContext from "@triliumnext/server/src/services/task_context.js";
import { resolve } from "path";
import { deferred, DeferredPromise } from "../../../packages/commons/src";
export function initializeDatabase(skipDemoDb: boolean) {
return new Promise<void>(async (resolve) => {
const sqlInit = (await import("@triliumnext/server/src/services/sql_init.js")).default;
cls.init(async () => {
if (!sqlInit.isDbInitialized()) {
await sqlInit.createInitialDatabase(skipDemoDb);
}
resolve();
import { deferred, type DeferredPromise } from "../../../packages/commons/src/index.js";
export function initializeDatabase(skipDemoDb: boolean): Promise<void> {
return new Promise<void>((resolve) => {
import("@triliumnext/server/src/services/sql_init.js").then((m) => {
const sqlInit = m.default;
cls.init(async () => {
if (!sqlInit.isDbInitialized()) {
sqlInit.createInitialDatabase(skipDemoDb).then(() => resolve());
} else {
sqlInit.dbReady.resolve();
resolve();
}
});
});
});
}
@ -32,10 +36,9 @@ export function initializeDatabase(skipDemoDb: boolean) {
*/
export function startElectron(callback: () => void): DeferredPromise<void> {
const initializedPromise = deferred<void>();
electron.app.on("ready", async () => {
await initializedPromise;
console.log("Electron is ready!");
const readyHandler = async () => {
await initializedPromise;
// Start the server.
const startTriliumServer = (await import("@triliumnext/server/src/www.js")).default;
@ -45,7 +48,15 @@ export function startElectron(callback: () => void): DeferredPromise<void> {
await windowService.createMainWindow(electron.app);
callback();
});
};
// Handle race condition: Electron ready event may have already fired
if (electron.app.isReady()) {
readyHandler();
} else {
electron.app.on("ready", readyHandler);
}
return initializedPromise;
}
@ -70,7 +81,6 @@ async function createImportZip(path: string) {
zlib: { level: 0 }
});
console.log("Archive path is ", resolve(path))
archive.directory(path, "/");
const outputStream = fsExtra.createWriteStream(inputFile);
@ -85,14 +95,16 @@ async function createImportZip(path: string) {
}
function waitForEnd(archive: Archiver, stream: WriteStream) {
return new Promise<void>(async (res, rej) => {
stream.on("finish", () => res());
await archive.finalize();
return new Promise<void>((res, rej) => {
stream.on("finish", res);
stream.on("error", rej);
archive.on("error", rej);
archive.finalize().catch(rej);
});
}
export async function extractZip(zipFilePath: string, outputPath: string, ignoredFiles?: Set<string>) {
const promise = deferred<void>()
const promise = deferred<void>();
setTimeout(async () => {
// Then extract the zip.
const { readZipFile, readContent } = (await import("@triliumnext/server/src/services/import/zip.js"));

View File

@ -0,0 +1,17 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/edit-docs',
plugins: [],
test: {
watch: false,
globals: true,
environment: "node",
include: ['spec/build-checks/**'],
reporters: [
"verbose"
]
},
}));

View File

@ -1,4 +1,4 @@
FROM node:24.12.0-bullseye-slim AS builder
FROM node:24.13.0-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.12.0-bullseye-slim
FROM node:24.13.0-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \

View File

@ -1,4 +1,4 @@
FROM node:24.12.0-alpine AS builder
FROM node:24.13.0-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.12.0-alpine
FROM node:24.13.0-alpine
# Install runtime dependencies
RUN apk add --no-cache su-exec shadow

View File

@ -1,4 +1,4 @@
FROM node:24.12.0-alpine AS builder
FROM node:24.13.0-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.12.0-alpine
FROM node:24.13.0-alpine
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@ -1,4 +1,4 @@
FROM node:24.12.0-bullseye-slim AS builder
FROM node:24.13.0-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.12.0-bullseye-slim
FROM node:24.13.0-bullseye-slim
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@ -1,5 +1,5 @@
{
"dependencies": {
"better-sqlite3": "12.6.0"
"better-sqlite3": "12.6.2"
}
}

View File

@ -29,7 +29,7 @@
"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": {
"better-sqlite3": "12.6.0",
"better-sqlite3": "12.6.2",
"html-to-text": "9.0.5",
"node-html-parser": "7.0.2",
"sucrase": "3.35.1"
@ -67,11 +67,11 @@
"@types/xml2js": "0.4.14",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"axios": "1.13.2",
"axios": "1.13.4",
"bindings": "1.5.0",
"bootstrap": "5.3.8",
"chardet": "2.1.1",
"cheerio": "1.1.2",
"cheerio": "1.2.0",
"chokidar": "5.0.0",
"cls-hooked": "4.2.2",
"compression": "1.8.1",
@ -79,15 +79,15 @@
"csrf-csrf": "3.2.2",
"debounce": "3.0.0",
"debug": "4.4.3",
"ejs": "3.1.10",
"electron": "39.2.7",
"ejs": "4.0.1",
"electron": "40.0.0",
"electron-debug": "4.1.0",
"electron-window-state": "5.0.3",
"express": "5.2.1",
"express-http-proxy": "2.1.2",
"express-openid-connect": "2.19.4",
"express-rate-limit": "8.2.1",
"express-session": "1.18.2",
"express-session": "1.19.0",
"file-uri-to-path": "2.0.0",
"fs-extra": "11.3.3",
"helmet": "8.1.0",
@ -95,7 +95,7 @@
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"i18next": "25.7.3",
"i18next": "25.8.0",
"i18next-fs-backend": "2.6.1",
"image-type": "6.0.0",
"ini": "6.0.0",
@ -107,7 +107,7 @@
"multer": "2.0.2",
"normalize-strings": "1.1.1",
"ollama": "0.6.3",
"openai": "6.16.0",
"openai": "6.17.0",
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sax": "1.4.4",
@ -119,8 +119,7 @@
"swagger-jsdoc": "6.2.8",
"time2fa": "1.4.2",
"tmp": "0.2.5",
"turndown": "7.2.2",
"vite": "7.3.1",
"turnish": "1.8.0",
"ws": "8.19.0",
"xml2js": "0.6.2",
"yauzl": "3.2.0"

View File

@ -3,6 +3,7 @@ import("@triliumnext/core");
import { erase } from "@triliumnext/core";
import compression from "compression";
import cookieParser from "cookie-parser";
import ejs from "ejs";
import express from "express";
import { auth } from "express-openid-connect";
import helmet from "helmet";
@ -33,7 +34,7 @@ export default async function buildApp() {
// view engine setup
app.set("views", path.join(assetsDir, "views"));
app.engine("ejs", (await import("ejs")).renderFile);
app.engine("ejs", (filePath, options, callback) => ejs.renderFile(filePath, options, callback));
app.set("view engine", "ejs");
app.use((req, res, next) => {

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 74 KiB

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