Compare commits

..

2 Commits

Author SHA1 Message Date
Elian Doran
47a7fb5708 feat(client/mermaid): integrate same pan/zoom 2026-01-05 22:11:07 +02:00
Elian Doran
a7c0c52610 feat(client/image): integrate a better image viewer 2026-01-05 22:00:47 +02:00
385 changed files with 10590 additions and 29931 deletions

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Check if PRs have conflicts
uses: eps1lon/actions-label-merge-conflict@v3
if: ${{ github.repository == vars.REPO_MAIN }}
if: github.repository == ${{ vars.REPO_MAIN }}
with:
dirtyLabel: "merge-conflicts"
repoToken: "${{ secrets.MERGE_CONFLICT_LABEL_PAT }}"

View File

@@ -67,7 +67,7 @@ jobs:
- name: Deploy
uses: ./.github/actions/deploy-to-cloudflare-pages
if: ${{ github.repository == vars.REPO_MAIN }}
if: github.repository == ${{ vars.REPO_MAIN }}
with:
project_name: "trilium-docs"
comment_body: "📚 Documentation preview is ready"

View File

@@ -1,30 +0,0 @@
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

View File

@@ -271,16 +271,13 @@ jobs:
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
# Create and push the manifest list with both the branch/tag name and the commit SHA
# Note: Images are only pushed to GHCR during build, so we always reference GHCR sources
# and copy to DockerHub using imagetools create
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Copy from GHCR to DockerHub (source digests only exist on GHCR)
docker buildx imagetools create \
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# If the ref is a tag, also tag the image as stable as this is part of a 'release'
# and only go in the `if` if there is NOT a `-` in the tag's name, due to tagging of `-alpha`, `-beta`, etc...
@@ -290,10 +287,9 @@ jobs:
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Copy stable tag from GHCR to DockerHub
docker buildx imagetools create \
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Small delay to ensure stable tag is fully propagated
sleep 5
@@ -305,7 +301,7 @@ jobs:
docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
fi

View File

@@ -26,7 +26,7 @@ permissions:
jobs:
nightly-electron:
if: ${{ github.repository == vars.REPO_MAIN }}
if: github.repository == ${{ vars.REPO_MAIN }}
name: Deploy nightly
strategy:
fail-fast: false
@@ -109,7 +109,7 @@ jobs:
path: apps/desktop/upload
nightly-server:
if: ${{ github.repository == vars.REPO_MAIN }}
if: github.repository == ${{ vars.REPO_MAIN }}
name: Deploy server nightly
strategy:
fail-fast: false

View File

@@ -11,14 +11,6 @@ concurrency:
cancel-in-progress: true
jobs:
sanity-check:
name: Sanity Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check version consistency
run: pnpm tsx ${{ github.workspace }}/scripts/check-version-consistency.ts ${{ github.ref_name }}
make-electron:
name: Make Electron
strategy:

View File

@@ -1,69 +0,0 @@
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 --ignore-scripts
run: pnpm install --filter website --frozen-lockfile
- name: Build the website
run: pnpm website:build

2
.gitignore vendored
View File

@@ -51,4 +51,4 @@ upload
# docs
site/
apps/*/coverage
scripts/translation/.language*.json
scripts/translation/.language*.json

2
.nvmrc
View File

@@ -1 +1 @@
24.13.0
24.12.0

View File

@@ -165,17 +165,6 @@ 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,14 +9,14 @@
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.28.2",
"packageManager": "pnpm@10.27.0",
"devDependencies": {
"@redocly/cli": "2.15.0",
"@redocly/cli": "2.14.3",
"archiver": "7.0.1",
"fs-extra": "11.3.3",
"react": "19.2.4",
"react-dom": "19.2.4",
"typedoc": "0.28.16",
"react": "19.2.3",
"react-dom": "19.2.3",
"typedoc": "0.28.15",
"typedoc-plugin-missing-exports": "4.1.2"
}
}

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
<title>Trilium Notes</title>
</head>
<body id="trilium-app">
<noscript>Trilium requires JavaScript to be enabled.</noscript>
<div id="context-menu-cover"></div>
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
<!-- Required to match the PWA's top bar color with the theme -->
<!-- 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="./src/index.ts" type="module"></script>
<!-- Required for correct loading of scripts in Electron -->
<script>
if (typeof module === 'object') {window.module = module; module = undefined;}
</script>
</body>
</html>

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/client",
"version": "0.101.3",
"version": "0.101.1",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true,
"license": "AGPL-3.0-only",
@@ -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.6.2",
"@preact/signals": "2.5.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.2",
"@zumer/snapdom": "2.0.1",
"autocomplete.js": "0.38.1",
"bootstrap": "5.3.8",
"boxicons": "2.1.4",
@@ -43,25 +43,26 @@
"debounce": "3.0.0",
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"globals": "17.2.0",
"i18next": "25.8.0",
"globals": "17.0.0",
"i18next": "25.7.3",
"i18next-http-backend": "3.0.2",
"jquery": "4.0.0",
"jquery": "3.7.1",
"jquery.fancytree": "2.38.5",
"jsplumb": "2.15.6",
"katex": "0.16.28",
"katex": "0.16.27",
"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.6.1",
"mind-elixir": "5.4.0",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.28.2",
"react-i18next": "16.5.4",
"react-window": "2.2.5",
"preact": "10.28.1",
"react-i18next": "16.5.1",
"react-window": "2.2.3",
"react-zoom-pan-pinch": "3.7.0",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
@@ -69,7 +70,7 @@
},
"devDependencies": {
"@ckeditor/ckeditor5-inspector": "5.0.0",
"@prefresh/vite": "2.4.11",
"@preact/preset-vite": "2.10.2",
"@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.33",
"@types/leaflet": "1.9.21",
@@ -78,9 +79,8 @@
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "20.4.0",
"lightningcss": "1.31.1",
"happy-dom": "20.0.11",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.2.0"
"vite-plugin-static-copy": "3.1.4"
}
}

View File

@@ -1,6 +1,6 @@
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror";
import { SqlExecuteResponse } from "@triliumnext/commons";
import { SqlExecuteResults } from "@triliumnext/commons";
import type { NativeImage, TouchBar } from "electron";
import { ColumnComponent } from "tabulator-tables";
@@ -154,7 +154,6 @@ export type CommandMappings = {
};
openInTab: ContextMenuCommandData;
openNoteInSplit: ContextMenuCommandData;
openNoteInWindow: ContextMenuCommandData;
openNoteInPopup: ContextMenuCommandData;
toggleNoteHoisting: ContextMenuCommandData;
insertNoteAfter: ContextMenuCommandData;
@@ -410,7 +409,7 @@ type EventMappings = {
addNewLabel: CommandData;
addNewRelation: CommandData;
sqlQueryResults: CommandData & {
response: SqlExecuteResponse;
results: SqlExecuteResults;
};
readOnlyTemporarilyDisabled: {
noteContext: NoteContext;

View File

@@ -1,17 +1,16 @@
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
import bundleService from "../services/bundle.js";
import utils from "../services/utils.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() {
@@ -188,8 +187,13 @@ 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 response = await server.post<SqlExecuteResponse>(`sql/execute/${note.noteId}`);
await appContext.triggerEvent("sqlQueryResults", { ntxId, response });
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 });
}
toastService.showMessage(t("entrypoints.note-executed"));

View File

@@ -99,22 +99,15 @@ function initFullScreenDetection(currentWindow: Electron.BrowserWindow) {
}
function initTransparencyEffects(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) {
const material = style.getPropertyValue("--background-material").trim();
if (window.glob.platform === "win32") {
const material = style.getPropertyValue("--background-material");
// TriliumNextTODO: find a nicer way to make TypeScript happy unfortunately TS did not like Array.includes here
const bgMaterialOptions = ["auto", "none", "mica", "acrylic", "tabbed"] as const;
const foundBgMaterialOption = bgMaterialOptions.find((bgMaterialOption) => material === bgMaterialOption);
if (foundBgMaterialOption) {
currentWindow.setBackgroundMaterial(foundBgMaterialOption);
}
}
if (window.glob.platform === "darwin") {
const bgMaterialOptions = [ "popover", "tooltip", "titlebar", "selection", "menu", "sidebar", "header", "sheet", "window", "hud", "fullscreen-ui", "content", "under-window", "under-page" ] as const;
const foundBgMaterialOption = bgMaterialOptions.find((bgMaterialOption) => material === bgMaterialOption);
if (foundBgMaterialOption) {
currentWindow.setVibrancy(foundBgMaterialOption);
}
}
}
/**

View File

@@ -1,4 +1,4 @@
import { getNoteIcon } from "@triliumnext/commons";
import { MIME_TYPES_DICT } from "@triliumnext/commons";
import cssClassManager from "../services/css_class_manager.js";
import type { Froca } from "../services/froca-interface.js";
@@ -13,6 +13,25 @@ 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
@@ -563,18 +582,32 @@ export default class FNote {
}
getIcon() {
return `tn-icon ${this.#getIconInternal()}`;
}
#getIconInternal() {
const iconClassLabels = this.getLabels("iconClass");
const workspaceIconClass = this.getWorkspaceIconClass();
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}`;
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];
}
getColorClass() {
@@ -583,9 +616,7 @@ export default class FNote {
}
isFolder() {
if (this.isLabelTruthy("subtreeHidden")) return false;
if (this.type === "search") return true;
return this.getFilteredChildBranches().length > 0;
return this.type === "search" || this.getFilteredChildBranches().length > 0;
}
getFilteredChildBranches() {

View File

@@ -1,131 +0,0 @@
async function bootstrap() {
showSplash();
await setupGlob();
await Promise.all([
initJQuery(),
loadBootstrapCss()
]);
loadStylesheets();
loadIcons();
setBodyAttributes();
await loadScripts();
hideSplash();
}
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 json = await response.json();
window.global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
window.glob = {
...json,
activeDialog: null
};
}
async function loadBootstrapCss() {
// We have to selectively import Bootstrap CSS based on text direction.
if (glob.isRtl) {
await import("bootstrap/dist/css/bootstrap.rtl.min.css");
} else {
await import("bootstrap/dist/css/bootstrap.min.css");
}
}
function loadStylesheets() {
const { device, assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
const cssToLoad: string[] = [];
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`);
}
for (const href of cssToLoad) {
const linkEl = document.createElement("link");
linkEl.href = href;
linkEl.rel = "stylesheet";
document.head.appendChild(linkEl);
}
}
function loadIcons() {
const styleEl = document.createElement("style");
styleEl.innerText = window.glob.iconPackCss;
document.head.appendChild(styleEl);
}
function setBodyAttributes() {
const { device, headingStyle, layoutOrientation, platform, isElectron, hasNativeTitleBar, hasBackgroundEffects, currentLocale } = window.glob;
const classesToSet = [
device,
`heading-style-${headingStyle}`,
`layout-${layoutOrientation}`,
`platform-${platform}`,
isElectron && "electron",
hasNativeTitleBar && "native-titlebar",
hasBackgroundEffects && "background-effects"
].filter(Boolean) as string[];
for (const classToSet of classesToSet) {
document.body.classList.add(classToSet);
}
document.body.lang = currentLocale.id;
document.body.dir = currentLocale.rtl ? "rtl" : "ltr";
}
async function loadScripts() {
switch (glob.device) {
case "mobile":
await import("./mobile.js");
break;
case "print":
await import("./print.js");
break;
case "desktop":
default:
await import("./desktop.js");
break;
}
}
function showSplash() {
// hide body to reduce flickering on the startup. This is done through JS and not CSS to not hide <noscript>
document.body.style.display = "none";
}
function hideSplash() {
document.body.style.display = "block";
}
bootstrap();

View File

@@ -46,6 +46,8 @@ 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";
@@ -161,9 +163,11 @@ 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

@@ -179,6 +179,7 @@ export default class MobileLayout {
new FlexContainer("column")
.contentSized()
.id("mobile-bottom-bar")
.child(new TabRowWidget().css("height", "40px"))
.child(new FlexContainer("row")
.class("horizontal")
.css("height", "53px")

View File

@@ -1,9 +1,8 @@
import { KeyboardActionNames } from "@triliumnext/commons";
import { h, JSX, render } from "preact";
import keyboardActionService, { getActionSync } from "../services/keyboard_actions.js";
import note_tooltip from "../services/note_tooltip.js";
import utils from "../services/utils.js";
import { h, JSX, render } from "preact";
export interface ContextMenuOptions<T> {
x: number;
@@ -63,17 +62,17 @@ export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEve
class ContextMenu {
private $widget: JQuery<HTMLElement>;
private $cover?: JQuery<HTMLElement>;
private $cover: JQuery<HTMLElement>;
private options?: ContextMenuOptions<any>;
private isMobile: boolean;
constructor() {
this.$widget = $("#context-menu-container");
this.$cover = $("#context-menu-cover");
this.$widget.addClass("dropend");
this.isMobile = utils.isMobile();
if (this.isMobile) {
this.$cover = $("#context-menu-cover");
this.$cover.on("click", () => this.hide());
} else {
$(document).on("click", (e) => this.hide());
@@ -92,7 +91,7 @@ class ContextMenu {
}
this.$widget.toggleClass("mobile-bottom-menu", !this.options.forcePositionOnMobile);
this.$cover?.addClass("show");
this.$cover.addClass("show");
$("body").addClass("context-menu-shown");
this.$widget.empty();
@@ -141,14 +140,16 @@ class ContextMenu {
} else {
left = this.options.x - contextMenuWidth + CONTEXT_MENU_OFFSET;
}
} else if (contextMenuWidth && this.options.x + contextMenuWidth - CONTEXT_MENU_OFFSET > clientWidth - CONTEXT_MENU_PADDING) {
// Overflow: right
left = clientWidth - contextMenuWidth - CONTEXT_MENU_PADDING;
} else if (this.options.x - CONTEXT_MENU_OFFSET < CONTEXT_MENU_PADDING) {
// Overflow: left
left = CONTEXT_MENU_PADDING;
} else {
left = this.options.x - CONTEXT_MENU_OFFSET;
if (contextMenuWidth && this.options.x + contextMenuWidth - CONTEXT_MENU_OFFSET > clientWidth - CONTEXT_MENU_PADDING) {
// Overflow: right
left = clientWidth - contextMenuWidth - CONTEXT_MENU_PADDING;
} else if (this.options.x - CONTEXT_MENU_OFFSET < CONTEXT_MENU_PADDING) {
// Overflow: left
left = CONTEXT_MENU_PADDING;
} else {
left = this.options.x - CONTEXT_MENU_OFFSET;
}
}
this.$widget
@@ -260,7 +261,7 @@ class ContextMenu {
.append(item.title);
if ("badges" in item && item.badges) {
for (const badge of item.badges) {
for (let badge of item.badges) {
const badgeElement = $(`<span class="badge">`).text(badge.title);
if (badge.className) {
@@ -351,7 +352,7 @@ class ContextMenu {
async hide() {
this.options?.onHide?.();
this.$widget.removeClass("show");
this.$cover?.removeClass("show");
this.$cover.removeClass("show");
$("body").removeClass("context-menu-shown");
this.$widget.hide();
}

View File

@@ -1,21 +1,21 @@
import appContext, { type ContextMenuCommandData, type FilteredCommandNames } from "../components/app_context.js";
import type { SelectMenuItemEventListener } from "../components/events.js";
import type FAttachment from "../entities/fattachment.js";
import attributes from "../services/attributes.js";
import { executeBulkActions } from "../services/bulk_action.js";
import clipboard from "../services/clipboard.js";
import dialogService from "../services/dialog.js";
import NoteColorPicker from "./custom-items/NoteColorPicker.jsx";
import treeService from "../services/tree.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import clipboard from "../services/clipboard.js";
import noteCreateService from "../services/note_create.js";
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
import appContext, { type ContextMenuCommandData, type FilteredCommandNames } from "../components/app_context.js";
import noteTypesService from "../services/note_types.js";
import server from "../services/server.js";
import toastService from "../services/toast.js";
import treeService from "../services/tree.js";
import utils from "../services/utils.js";
import dialogService from "../services/dialog.js";
import { t } from "../services/i18n.js";
import type NoteTreeWidget from "../widgets/note_tree.js";
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
import NoteColorPicker from "./custom-items/NoteColorPicker.jsx";
import type FAttachment from "../entities/fattachment.js";
import type { SelectMenuItemEventListener } from "../components/events.js";
import utils from "../services/utils.js";
import attributes from "../services/attributes.js";
import { executeBulkActions } from "../services/bulk_action.js";
// TODO: Deduplicate once client/server is well split.
interface ConvertToAttachmentResponse {
@@ -72,8 +72,6 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
const noSelectedNotes = selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node);
const notSearch = note?.type !== "search";
const hasSubtreeHidden = note?.isLabelTruthy("subtreeHidden") ?? false;
const isSpotlighted = this.node.extraClasses.includes("spotlighted-node");
const notOptionsOrHelp = !note?.noteId.startsWith("_options") && !note?.noteId.startsWith("_help");
const parentNotSearch = !parentNote || parentNote.type !== "search";
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
@@ -81,18 +79,17 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
const items: (MenuItem<TreeCommandNames> | null)[] = [
{ title: t("tree-context-menu.open-in-a-new-tab"), command: "openInTab", shortcut: "Ctrl+Click", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-a-new-window"), command: "openNoteInWindow", uiIcon: "bx bx-window-open", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes },
isHoisted
? null
: {
title: `${t("tree-context-menu.hoist-note")}`,
command: "toggleNoteHoisting",
keyboardShortcut: "toggleNoteHoisting",
uiIcon: "bx bxs-chevrons-up",
enabled: noSelectedNotes && notSearch
},
title: `${t("tree-context-menu.hoist-note")}`,
command: "toggleNoteHoisting",
keyboardShortcut: "toggleNoteHoisting",
uiIcon: "bx bxs-chevrons-up",
enabled: noSelectedNotes && notSearch
},
!isHoisted || !isNotRoot
? null
: { title: t("tree-context-menu.unhoist-note"), command: "toggleNoteHoisting", keyboardShortcut: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
@@ -115,7 +112,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
keyboardShortcut: "createNoteInto",
uiIcon: "bx bx-plus",
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
enabled: notSearch && noSelectedNotes && notOptionsOrHelp && !hasSubtreeHidden && !isSpotlighted,
enabled: notSearch && noSelectedNotes && notOptionsOrHelp,
columns: 2
},
@@ -153,17 +150,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
{ kind: "separator" },
!hasSubtreeHidden && { title: t("tree-context-menu.expand-subtree"), command: "expandSubtree", keyboardShortcut: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
!hasSubtreeHidden && { title: t("tree-context-menu.collapse-subtree"), command: "collapseSubtree", keyboardShortcut: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{
title: hasSubtreeHidden ? t("tree-context-menu.show-subtree") : t("tree-context-menu.hide-subtree"),
uiIcon: "bx bx-show",
handler: async () => {
const note = await froca.getNote(this.node.data.noteId);
if (!note) return;
attributes.setBooleanWithInheritance(note, "subtreeHidden", !hasSubtreeHidden);
}
},
{ title: t("tree-context-menu.expand-subtree"), command: "expandSubtree", keyboardShortcut: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: t("tree-context-menu.collapse-subtree"), command: "collapseSubtree", keyboardShortcut: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{
title: t("tree-context-menu.sort-by"),
command: "sortChildNotes",
@@ -176,7 +164,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
{ title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true },
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptionsOrHelp }
].filter(Boolean) as MenuItem<TreeCommandNames>[]
]
},
{ kind: "separator" },
@@ -304,30 +292,25 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
noteCreateService.createNote(parentNotePath, {
target: "after",
targetBranchId: this.node.data.branchId,
type,
isProtected,
templateNoteId
type: type,
isProtected: isProtected,
templateNoteId: templateNoteId
});
} else if (command === "insertChildNote") {
const parentNotePath = treeService.getNotePath(this.node);
noteCreateService.createNote(parentNotePath, {
type,
type: type,
isProtected: this.node.data.isProtected,
templateNoteId
templateNoteId: templateNoteId
});
} else if (command === "openNoteInSplit") {
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
const { ntxId } = subContexts?.[subContexts.length - 1] ?? {};
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
} else if (command === "openNoteInWindow") {
appContext.triggerCommand("openInWindow", {
notePath,
hoistedNoteId: appContext.tabManager.getActiveContext()?.hoistedNoteId
});
} else if (command === "openNoteInPopup") {
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
} else if (command === "convertNoteToAttachment") {
if (!(await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm")))) {
return;
@@ -349,11 +332,11 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted }));
} else if (command === "copyNotePathToClipboard") {
navigator.clipboard.writeText(`#${ notePath}`);
navigator.clipboard.writeText("#" + notePath);
} else if (command) {
this.treeWidget.triggerCommand<TreeCommandNames>(command, {
node: this.node,
notePath,
notePath: notePath,
noteId: this.node.data.noteId,
selectedOrActiveBranchIds: this.treeWidget.getSelectedOrActiveBranchIds(this.node),
selectedOrActiveNoteIds: this.treeWidget.getSelectedOrActiveNoteIds(this.node)

View File

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

View File

@@ -8,17 +8,6 @@ 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

@@ -1,139 +0,0 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { buildNote } from "../test/easy-froca";
import { setBooleanWithInheritance } from "./attributes";
import froca from "./froca";
import server from "./server.js";
// Spy on server methods to track calls
// @ts-expect-error the generic typing is causing issues here
server.put = vi.fn(async <T> (url: string, data?: T) => ({} as T));
// @ts-expect-error the generic typing is causing issues here
server.remove = vi.fn(async <T> (url: string) => ({} as T));
describe("Set boolean with inheritance", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("doesn't call server if value matches directly", async () => {
const noteWithLabel = buildNote({
title: "New note",
"#foo": ""
});
const noteWithoutLabel = buildNote({
title: "New note"
});
await setBooleanWithInheritance(noteWithLabel, "foo", true);
await setBooleanWithInheritance(noteWithoutLabel, "foo", false);
expect(server.put).not.toHaveBeenCalled();
expect(server.remove).not.toHaveBeenCalled();
});
it("sets boolean normally without inheritance", async () => {
const standaloneNote = buildNote({
title: "New note"
});
await setBooleanWithInheritance(standaloneNote, "foo", true);
expect(server.put).toHaveBeenCalledWith(`notes/${standaloneNote.noteId}/set-attribute`, {
type: "label",
name: "foo",
value: "",
isInheritable: false
}, undefined);
});
it("removes boolean normally without inheritance", async () => {
const standaloneNote = buildNote({
title: "New note",
"#foo": ""
});
const attributeId = standaloneNote.getLabel("foo")!.attributeId;
await setBooleanWithInheritance(standaloneNote, "foo", false);
expect(server.remove).toHaveBeenCalledWith(`notes/${standaloneNote.noteId}/attributes/${attributeId}`);
});
it("doesn't call server if value matches inherited", async () => {
const parentNote = buildNote({
title: "Parent note",
"#foo(inheritable)": "",
"children": [
{
title: "Child note"
}
]
});
const childNote = froca.getNoteFromCache(parentNote.children[0])!;
expect(childNote.isLabelTruthy("foo")).toBe(true);
await setBooleanWithInheritance(childNote, "foo", true);
expect(server.put).not.toHaveBeenCalled();
expect(server.remove).not.toHaveBeenCalled();
});
it("overrides boolean with inheritance", async () => {
const parentNote = buildNote({
title: "Parent note",
"#foo(inheritable)": "",
"children": [
{
title: "Child note"
}
]
});
const childNote = froca.getNoteFromCache(parentNote.children[0])!;
expect(childNote.isLabelTruthy("foo")).toBe(true);
await setBooleanWithInheritance(childNote, "foo", false);
expect(server.put).toHaveBeenCalledWith(`notes/${childNote.noteId}/set-attribute`, {
type: "label",
name: "foo",
value: "false",
isInheritable: false
}, undefined);
});
it("overrides boolean with inherited false", async () => {
const parentNote = buildNote({
title: "Parent note",
"#foo(inheritable)": "false",
"children": [
{
title: "Child note"
}
]
});
const childNote = froca.getNoteFromCache(parentNote.children[0])!;
expect(childNote.isLabelTruthy("foo")).toBe(false);
await setBooleanWithInheritance(childNote, "foo", true);
expect(server.put).toHaveBeenCalledWith(`notes/${childNote.noteId}/set-attribute`, {
type: "label",
name: "foo",
value: "",
isInheritable: false
}, undefined);
});
it("deletes override boolean with inherited false with already existing value", async () => {
const parentNote = buildNote({
title: "Parent note",
"#foo(inheritable)": "false",
"children": [
{
title: "Child note",
"#foo": "false",
}
]
});
const childNote = froca.getNoteFromCache(parentNote.children[0])!;
expect(childNote.isLabelTruthy("foo")).toBe(false);
await setBooleanWithInheritance(childNote, "foo", true);
expect(server.put).toBeCalledWith(`notes/${childNote.noteId}/set-attribute`, {
type: "label",
name: "foo",
value: "",
isInheritable: false
}, undefined);
});
});

View File

@@ -1,67 +1,36 @@
import { AttributeType } from "@triliumnext/commons";
import type FNote from "../entities/fnote.js";
import froca from "./froca.js";
import type { AttributeRow } from "./load_results.js";
import server from "./server.js";
import froca from "./froca.js";
import type FNote from "../entities/fnote.js";
import type { AttributeRow } from "./load_results.js";
import { AttributeType } from "@triliumnext/commons";
async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
await server.put(`notes/${noteId}/attribute`, {
type: "label",
name,
value,
name: name,
value: value,
isInheritable
});
}
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false, componentId?: string) {
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
await server.put(`notes/${noteId}/set-attribute`, {
type: "label",
name,
value,
isInheritable,
}, componentId);
name: name,
value: value,
isInheritable
});
}
export async function setRelation(noteId: string, name: string, value: string = "", isInheritable = false) {
await server.put(`notes/${noteId}/set-attribute`, {
type: "relation",
name,
value,
name: name,
value: value,
isInheritable
});
}
/**
* Sets a boolean label on the given note, taking inheritance into account. If the desired value matches the inherited
* value, any owned label will be removed to allow the inherited value to take effect. If the desired value differs
* from the inherited value, an owned label will be created or updated to reflect the desired value.
*
* When checking if the boolean value is set, don't use `note.hasLabel`; instead use `note.isLabelTruthy`.
*
* @param note the note on which to set the boolean label.
* @param labelName the name of the label to set.
* @param value the boolean value to set for the label.
*/
export async function setBooleanWithInheritance(note: FNote, labelName: string, value: boolean) {
const actualValue = note.isLabelTruthy(labelName);
if (actualValue === value) return;
const hasInheritedValue = !note.hasOwnedLabel(labelName) && note.hasLabel(labelName);
if (hasInheritedValue) {
if (value) {
setLabel(note.noteId, labelName, "");
} else {
// Label is inherited - override to false.
setLabel(note.noteId, labelName, "false");
}
} else if (value) {
setLabel(note.noteId, labelName, "");
} else {
removeOwnedLabelByName(note, labelName);
}
}
async function removeAttributeById(noteId: string, attributeId: string) {
await server.remove(`notes/${noteId}/attributes/${attributeId}`);
}
@@ -117,15 +86,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, componentId?: string) {
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
if (value !== null && value !== undefined) {
// Create or update the attribute.
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value }, componentId);
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
} 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}`, componentId);
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`);
}
}
}
@@ -173,7 +142,6 @@ export default {
setLabel,
setRelation,
setAttribute,
setBooleanWithInheritance,
removeAttributeById,
removeOwnedLabelByName,
removeOwnedRelationByName,

View File

@@ -1,12 +1,12 @@
import appContext from "../components/app_context.js";
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import froca from "./froca.js";
import hoistedNoteService from "./hoisted_note.js";
import { t } from "./i18n.js";
import utils from "./utils.js";
import server from "./server.js";
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
import utils from "./utils.js";
import froca from "./froca.js";
import hoistedNoteService from "./hoisted_note.js";
import ws from "./ws.js";
import appContext from "../components/app_context.js";
import { t } from "./i18n.js";
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
// TODO: Deduplicate type with server
interface Response {
@@ -66,7 +66,7 @@ async function moveAfterBranch(branchIdsToMove: string[], afterBranchId: string)
}
}
async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: string, componentId?: string) {
async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: string) {
const newParentBranch = froca.getBranch(newParentBranchId);
if (!newParentBranch) {
return;
@@ -86,7 +86,7 @@ async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: st
continue;
}
const resp = await server.put<Response>(`branches/${branchIdToMove}/move-to/${newParentBranchId}`, undefined, componentId);
const resp = await server.put<Response>(`branches/${branchIdToMove}/move-to/${newParentBranchId}`);
if (!resp.success) {
toastService.showError(resp.message);
@@ -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, componentId?: string) {
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true) {
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}`, componentId);
await server.remove(`notes/${branch.noteId}${query}`);
} else {
await server.remove(`branches/${branchIdToDelete}${query}`, componentId);
await server.remove(`branches/${branchIdToDelete}${query}`);
}
}

View File

@@ -23,12 +23,6 @@ export interface RenderOptions {
imageHasZoom?: boolean;
/** If enabled, it will prevent the default behavior in which an empty note would display a list of children. */
noChildrenList?: boolean;
/** If enabled, it will prevent rendering of included notes. */
noIncludedNotes?: boolean;
/** If enabled, it will include archived notes when rendering children list. */
includeArchivedNotes?: boolean;
/** Set of note IDs that have already been seen during rendering to prevent infinite recursion. */
seenNoteIds?: Set<string>;
}
const CODE_MIME_TYPES = new Set(["application/json"]);

View File

@@ -1,132 +0,0 @@
import { trimIndentation } from "@triliumnext/commons";
import { describe, expect, it } from "vitest";
import { buildNote } from "../test/easy-froca";
import renderText from "./content_renderer_text";
describe("Text content renderer", () => {
it("renders included note", async () => {
const contentEl = document.createElement("div");
const includedNote = buildNote({
title: "Included note",
content: "<p>This is the included note.</p>"
});
const note = buildNote({
title: "New note",
content: trimIndentation`
<p>
Hi there
</p>
<section class="include-note" data-note-id="${includedNote.noteId}" data-box-size="medium">
&nbsp;
</section>
`
});
await renderText(note, $(contentEl));
expect(contentEl.querySelectorAll("section.include-note").length).toBe(1);
expect(contentEl.querySelectorAll("section.include-note p").length).toBe(1);
});
it("skips rendering included note", async () => {
const contentEl = document.createElement("div");
const includedNote = buildNote({
title: "Included note",
content: "<p>This is the included note.</p>"
});
const note = buildNote({
title: "New note",
content: trimIndentation`
<p>
Hi there
</p>
<section class="include-note" data-note-id="${includedNote.noteId}" data-box-size="medium">
&nbsp;
</section>
`
});
await renderText(note, $(contentEl), { noIncludedNotes: true });
expect(contentEl.querySelectorAll("section.include-note").length).toBe(0);
});
it("doesn't enter infinite loop on direct recursion", async () => {
const contentEl = document.createElement("div");
const note = buildNote({
title: "New note",
id: "Y7mBwmRjQyb4",
content: trimIndentation`
<p>
Hi there
</p>
<section class="include-note" data-note-id="Y7mBwmRjQyb4" data-box-size="medium">
&nbsp;
</section>
<section class="include-note" data-note-id="Y7mBwmRjQyb4" data-box-size="medium">
&nbsp;
</section>
`
});
await renderText(note, $(contentEl));
expect(contentEl.querySelectorAll("section.include-note").length).toBe(0);
});
it("doesn't enter infinite loop on indirect recursion", async () => {
const contentEl = document.createElement("div");
buildNote({
id: "first",
title: "Included note",
content: trimIndentation`\
<p>This is the included note.</p>
<section class="include-note" data-note-id="second" data-box-size="medium">
&nbsp;
</section>
`
});
const note = buildNote({
id: "second",
title: "New note",
content: trimIndentation`
<p>
Hi there
</p>
<section class="include-note" data-note-id="first" data-box-size="medium">
&nbsp;
</section>
`
});
await renderText(note, $(contentEl));
expect(contentEl.querySelectorAll("section.include-note").length).toBe(1);
});
it("renders children list when note is empty", async () => {
const contentEl = document.createElement("div");
const parentNote = buildNote({
title: "Parent note",
children: [
{ title: "Child note 1" },
{ title: "Child note 2" }
]
});
await renderText(parentNote, $(contentEl));
const items = contentEl.querySelectorAll("a");
expect(items.length).toBe(2);
expect(items[0].textContent).toBe("Child note 1");
expect(items[1].textContent).toBe("Child note 2");
});
it("skips archived notes in children list", async () => {
const contentEl = document.createElement("div");
const parentNote = buildNote({
title: "Parent note",
children: [
{ title: "Child note 1" },
{ title: "Child note 2", "#archived": "" },
{ title: "Child note 3" }
]
});
await renderText(parentNote, $(contentEl));
const items = contentEl.querySelectorAll("a");
expect(items.length).toBe(2);
expect(items[0].textContent).toBe("Child note 1");
expect(items[1].textContent).toBe("Child note 3");
});
});

View File

@@ -15,14 +15,7 @@ export default async function renderText(note: FNote | FAttachment, $renderedCon
if (blob && !isHtmlEmpty(blob.content)) {
$renderedContent.append($('<div class="ck-content">').html(blob.content));
const seenNoteIds = options.seenNoteIds ?? new Set<string>();
seenNoteIds.add("noteId" in note ? note.noteId : note.attachmentId);
if (!options.noIncludedNotes) {
await renderIncludedNotes($renderedContent[0], seenNoteIds);
} else {
$renderedContent.find("section.include-note").remove();
}
await renderIncludedNotes($renderedContent[0]);
if ($renderedContent.find("span.math-tex").length > 0) {
renderMathInElement($renderedContent[0], { trust: true });
@@ -42,11 +35,11 @@ export default async function renderText(note: FNote | FAttachment, $renderedCon
await rewriteMermaidDiagramsInContainer($renderedContent[0] as HTMLDivElement);
await formatCodeBlocks($renderedContent);
} else if (note instanceof FNote && !options.noChildrenList) {
await renderChildrenList($renderedContent, note, options.includeArchivedNotes ?? false);
await renderChildrenList($renderedContent, note);
}
}
async function renderIncludedNotes(contentEl: HTMLElement, seenNoteIds: Set<string>) {
async function renderIncludedNotes(contentEl: HTMLElement) {
// TODO: Consider duplicating with server's share/content_renderer.ts.
const includeNoteEls = contentEl.querySelectorAll("section.include-note");
@@ -73,15 +66,7 @@ async function renderIncludedNotes(contentEl: HTMLElement, seenNoteIds: Set<stri
continue;
}
if (seenNoteIds.has(noteId)) {
console.warn(`Skipping inclusion of ${noteId} to avoid circular reference.`);
includeNoteEl.remove();
continue;
}
const renderedContent = (await content_renderer.getRenderedContent(note, {
seenNoteIds
})).$renderedContent;
const renderedContent = (await content_renderer.getRenderedContent(note)).$renderedContent;
includeNoteEl.replaceChildren(...renderedContent);
}
}
@@ -113,7 +98,7 @@ export async function applyInlineMermaid(container: HTMLDivElement) {
}
}
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: FNote, includeArchivedNotes: boolean) {
async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: FNote) {
let childNoteIds = note.getChildNoteIds();
if (!childNoteIds.length) {
@@ -123,16 +108,14 @@ async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: F
$renderedContent.css("padding", "10px");
$renderedContent.addClass("text-with-ellipsis");
// just load the first 10 child notes
if (childNoteIds.length > 10) {
childNoteIds = childNoteIds.slice(0, 10);
}
// just load the first 10 child notes
const childNotes = await froca.getNotes(childNoteIds);
for (const childNote of childNotes) {
if (childNote.isArchived && !includeArchivedNotes) continue;
$renderedContent.append(
await link.createLink(`${note.noteId}/${childNote.noteId}`, {
showTooltip: false,

View File

@@ -49,7 +49,7 @@ function createClassForColor(colorString: string | null) {
return clsx("use-note-color", className, colorsWithHue.has(className) && "with-hue");
}
export function parseColor(color: string) {
function parseColor(color: string) {
try {
return Color(color.toLowerCase());
} catch (ex) {
@@ -77,7 +77,7 @@ function adjustColorLightness(color: ColorInstance, lightThemeMaxLightness: numb
}
/** Returns the hue of the specified color, or undefined if the color is grayscale. */
export function getHue(color: ColorInstance) {
function getHue(color: ColorInstance) {
const hslColor = color.hsl();
if (hslColor.saturationl() > 0) {
return hslColor.hue();

View File

@@ -1,5 +1,4 @@
import type { AttachmentRow, EtapiTokenRow, NoteType, OptionNames } from "@triliumnext/commons";
import type { AttributeType } from "../entities/fattribute.js";
import type { EntityChange } from "../server_types.js";
@@ -136,14 +135,7 @@ export default class LoadResults {
}
getBranchRows() {
return this.branchRows.map((row) => {
const branch = this.getEntityRow("branches", row.branchId);
if (branch) {
// Merge the componentId from the tracked row with the entity data
return { ...branch, componentId: row.componentId };
}
return null;
}).filter((branch) => !!branch) as BranchRow[];
return this.branchRows.map((row) => this.getEntityRow("branches", row.branchId)).filter((branch) => !!branch);
}
addNoteReordering(parentNoteId: string, componentId: string) {
@@ -161,14 +153,7 @@ export default class LoadResults {
getAttributeRows(componentId = "none"): AttributeRow[] {
return this.attributeRows
.filter((row) => row.componentId !== componentId)
.map((row) => {
const attr = this.getEntityRow("attributes", row.attributeId);
if (attr) {
// Merge the componentId from the tracked row with the entity data
return { ...attr, componentId: row.componentId };
}
return null;
})
.map((row) => this.getEntityRow("attributes", row.attributeId))
.filter((attr) => !!attr) as AttributeRow[];
}

View File

@@ -1,6 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import shortcuts, { isIMEComposing, keyMatches, matchesShortcut } from "./shortcuts.js";
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
// Mock utils module
vi.mock("./utils.js", () => ({
@@ -62,10 +61,9 @@ describe("shortcuts", () => {
});
describe("keyMatches", () => {
const createKeyboardEvent = (key: string, code?: string, extraProps: Partial<KeyboardEvent> = {}) => ({
const createKeyboardEvent = (key: string, code?: string) => ({
key,
code: code || `Key${key.toUpperCase()}`,
...extraProps
code: code || `Key${key.toUpperCase()}`
} as KeyboardEvent);
it("should match regular letter keys using key code", () => {
@@ -102,26 +100,6 @@ 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", () => {
@@ -222,42 +200,6 @@ 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,13 +213,7 @@ 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,11 +1,10 @@
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 { ensureMimeTypes, highlight, highlightAuto, loadTheme, Themes, type AutoHighlightResult, type HighlightResult, type Theme } from "@triliumnext/highlightjs";
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;
@@ -77,15 +76,13 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery<HTMLEle
}
export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
if (!mimeTypeHint && highlightingLoaded) {
if (highlightingLoaded) {
return;
}
// Load theme.
if (!highlightingLoaded) {
const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName);
}
const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName);
// Load mime types.
let mimeTypes: MimeType[];
@@ -97,7 +94,7 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
enabled: true,
mime: mimeTypeHint.replace("-", "/")
}
];
]
} else {
mimeTypes = mime_types.getMimeTypes();
}
@@ -127,9 +124,9 @@ export function isSyntaxHighlightEnabled() {
if (!isShare) {
const theme = options.get("codeBlockTheme");
return !!theme && theme !== "none";
} else {
return true;
}
return true;
}
/**

View File

@@ -224,6 +224,10 @@ body.mobile .modal .modal-dialog {
width: 100%;
}
body.mobile .modal .modal-content {
border-radius: var(--bs-modal-border-radius) var(--bs-modal-border-radius) 0 0;
}
.component {
contain: size;
}
@@ -1251,7 +1255,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
z-index: 2500;
z-index: 1000;
background: rgba(0, 0, 0, 0.1);
}
@@ -1610,7 +1614,6 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
body.mobile .modal-content {
overflow-y: auto;
border-radius: var(--bs-modal-border-radius) var(--bs-modal-border-radius) 0 0;
}
body.mobile .modal-footer {
@@ -1666,15 +1669,6 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
#detail-container {
background: var(--main-background-color);
}
.modal-dialog {
margin: var(--bs-modal-margin);
max-width: 80%;
}
.modal-content {
height: 100%;
}
}
@media (max-width: 991px) {

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,42 +40,10 @@
border-bottom: var(--col-header-bottom-border);
background: var(--col-header-background-color);
color: var(--col-header-text-color);
font-weight: normal;
}
.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;
}
}
}
.tabulator .tabulator-col-content {
padding: 8px 4px !important;
}
@media (hover: hover) and (pointer: fine) {
@@ -112,6 +80,7 @@
.tabulator-tableholder {
padding-top: 10px;
height: unset !important; /* Don't extend on the full height */
}
/* Rows */
@@ -130,14 +99,6 @@
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 {
@@ -159,14 +120,11 @@
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);
}
@@ -216,6 +174,10 @@
margin: 0;
}
.tabulator .tabulator-footer {
color: var(--main-text-color);
}
/* Context menus */
.tabulator-popup-container {
@@ -230,27 +192,8 @@
}
/* Footer */
:root .tabulator .tabulator-footer {
background: transparent;
color: var(--main-text-color);
border-top: 1px solid var(--main-border-color);
border-top: unset;
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

@@ -40,30 +40,13 @@ body.mobile {
/* #region Mica */
/* Quirk: --background-material is read before "theme-supports-background-effects" class
* is applied. Apply the matterial even if the theme doesn't support it. */
body.background-effects.platform-win32 {
&.layout-vertical {
--background-material: mica;
}
&.layout-horizontal {
--background-material: tabbed;
}
/* Quirk: --background-material is read before "theme-supports-background-effects" class
* is applied. Apply the matterial even if the theme doesn't support it. */
--background-material: tabbed;
}
body.background-effects.platform-darwin {
/** Reference: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc **/
&.layout-vertical {
--background-material: under-window;
}
&.layout-horizontal {
--background-material: hud;
}
}
body.background-effects.theme-supports-background-effects {
body.background-effects.theme-supports-background-effects.platform-win32 {
--launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
@@ -73,29 +56,33 @@ body.background-effects.theme-supports-background-effects {
--root-background: transparent;
}
body.background-effects.theme-supports-background-effects.layout-vertical {
body.background-effects.platform-win32.layout-vertical {
--background-material: mica;
}
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical {
--left-pane-background-color: var(--window-background-color-bgfx);
--center-pane-background-color-bgfx: var(--center-pane-vert-layout-background-color-bgfx);
--right-pane-background-color: var(--right-pane-background-color-bgfx);
}
body.background-effects.theme-supports-background-effects.layout-horizontal {
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal {
--center-pane-background-color-bgfx: var(--center-pane-horiz-layout-background-color-bgfx);
--gutter-color: var(--left-pane-background-color);
}
body.background-effects.theme-supports-background-effects,
body.background-effects.theme-supports-background-effects #root-widget {
body.background-effects.theme-supports-background-effects.platform-win32,
body.background-effects.theme-supports-background-effects.platform-win32 #root-widget {
background: var(--window-background-color-bgfx) !important;
}
body.background-effects.theme-supports-background-effects.layout-horizontal #horizontal-main-container,
body.background-effects.theme-supports-background-effects.layout-vertical #vertical-main-container {
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal #horizontal-main-container,
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical #vertical-main-container {
background-color: var(--root-background);
}
/* Note split with background effects */
body.background-effects.theme-supports-background-effects #center-pane .note-split.bgfx {
body.background-effects.theme-supports-background-effects.platform-win32 #center-pane .note-split.bgfx {
--note-split-background-color: var(--center-pane-background-color-bgfx);
}
@@ -1067,7 +1054,7 @@ body.layout-horizontal .tab-row-widget-container {
overflow: hidden;
}
body.desktop:not(.background-effects) #root-widget.horizontal-layout {
body.desktop:not(.background-effects.platform-win32) #root-widget.horizontal-layout {
background-color: var(--root-background) !important;
}

View File

@@ -69,6 +69,24 @@ export function buildNote(noteDef: NoteDefinition) {
});
note.getBlob = async () => blob;
// Manage children.
if (noteDef.children) {
for (const childDef of noteDef.children) {
const childNote = buildNote(childDef);
const branchId = `${note.noteId}_${childNote.noteId}`;
const branch = new FBranch(froca, {
branchId,
noteId: childNote.noteId,
parentNoteId: note.noteId,
notePosition: childNotePosition,
fromSearchNote: false
});
froca.branches[branchId] = branch;
note.addChild(childNote.noteId, branchId, false);
childNotePosition += 10;
}
}
let position = 0;
for (const [ key, value ] of Object.entries(noteDef)) {
const attributeId = utils.randomString(12);
@@ -118,25 +136,5 @@ export function buildNote(noteDef: NoteDefinition) {
}
noteAttributeCache.attributes[note.noteId].push(attribute);
}
// Manage children.
if (noteDef.children) {
for (const childDef of noteDef.children) {
const childNote = buildNote(childDef);
const branchId = `${note.noteId}_${childNote.noteId}`;
const branch = new FBranch(froca, {
branchId,
noteId: childNote.noteId,
parentNoteId: note.noteId,
notePosition: childNotePosition,
fromSearchNote: false
});
froca.branches[branchId] = branch;
note.addChild(childNote.noteId, branchId, false);
childNote.addParent(note.noteId, branchId, false);
childNotePosition += 10;
}
}
return note;
}

View File

@@ -798,8 +798,7 @@
"expand_tooltip": "展开此集合的直接子代(单层深度)。点击右方箭头以查看更多选项。",
"expand_first_level": "展开直接子代",
"expand_nth_level": "展开 {{depth}} 层",
"expand_all_levels": "展开所有层级",
"hide_child_notes": "隐藏树中的子笔记"
"expand_all_levels": "展开所有层级"
},
"edited_notes": {
"no_edited_notes_found": "今天还没有编辑过的笔记...",
@@ -1506,10 +1505,7 @@
"duplicate": "复制",
"open-in-popup": "快速编辑",
"archive": "归档",
"unarchive": "解压",
"open-in-a-new-window": "在新窗口中打开",
"hide-subtree": "隐藏子树",
"show-subtree": "显示子树"
"unarchive": "解压"
},
"shared_info": {
"help_link": "访问 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a> 获取帮助。",
@@ -1602,11 +1598,7 @@
"shared-indicator-tooltip": "此笔记已公开分享",
"shared-indicator-tooltip-with-url": "此笔记已公开分享至:{{- url}}",
"clone-indicator-tooltip": "此笔记有 {{- count}} 个父级: {{- parents}}",
"clone-indicator-tooltip-single": "此笔记已克隆1 个额外的父级:{{- parent}}",
"subtree-hidden-tooltip_other": "从树中隐藏的 {{count}} 篇子笔记",
"subtree-hidden-moved-title": "已添加到 {{title}}",
"subtree-hidden-moved-description-collection": "此集合隐藏其树中的子笔记。",
"subtree-hidden-moved-description-other": "子笔记隐藏于此笔记的树中。"
"clone-indicator-tooltip-single": "此笔记已克隆1 个额外的父级:{{- parent}}"
},
"title_bar_buttons": {
"window-on-top": "保持此窗口置顶"
@@ -1639,11 +1631,7 @@
"configure_launchbar": "配置启动栏"
},
"sql_result": {
"no_rows": "此查询没有返回任何数据",
"not_executed": "查询尚未执行。",
"failed": "SQL 查询执行失败",
"execute_now": "立即执行",
"statement_result": "执行结果"
"no_rows": "此查询没有返回任何数据"
},
"sql_table_schemas": {
"tables": "表"

View File

@@ -1,6 +1,6 @@
{
"about": {
"title": "Über Trilium Notizen",
"title": "Über Trilium Notes",
"homepage": "Startseite:",
"app_version": "App-Version:",
"db_version": "DB-Version:",
@@ -25,13 +25,7 @@
},
"widget-list-error": {
"title": "Abruf der Liste von Widgets vom Server ist fehlgeschlagen"
},
"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",
@@ -129,7 +123,7 @@
"scrollToActiveNote": "Scrolle zur aktiven Notiz",
"jumpToParentNote": "Zur übergeordneten Notiz springen",
"collapseWholeTree": "Reduziere den gesamten Notizbaum",
"collapseSubTree": "Zweig einklappen",
"collapseSubTree": "Teilbaum einklappen",
"tabShortcuts": "Tab-Tastenkürzel",
"newTabNoteLink": "auf den Notizlink öffnet die Notiz in einem neuen Tab",
"onlyInDesktop": "Nur im Desktop (Electron Build)",
@@ -214,8 +208,7 @@
"info": {
"modalTitle": "Infonachricht",
"closeButton": "Schließen",
"okButton": "OK",
"copy_to_clipboard": "In Zwischenablage kopieren"
"okButton": "OK"
},
"jump_to_note": {
"search_button": "Suche im Volltext",
@@ -230,7 +223,7 @@
"move_to": {
"dialog_title": "Notizen verschieben nach ...",
"notes_to_move": "Notizen zum Verschieben",
"target_parent_note": "Übergeordnete Notiz bestimmen",
"target_parent_note": "Ziel-Elternnotiz",
"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.",
@@ -333,8 +326,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": "Hervorgehoben",
"promoted_alias_title": "Der Name, der in der Benutzeroberfläche für hervorgehobene Attribute angezeigt werden soll.",
"promoted": "Gefördert",
"promoted_alias_title": "Der Name, der in der Benutzeroberfläche für heraufgestufte 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",
@@ -367,7 +360,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 im Notizexport inkludiert",
"exclude_from_export": "Notizen (mit ihrem Unterbaum) werden nicht in den Notizexport einbezogen",
"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.",
@@ -376,7 +369,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": "Hervorgehobene Attribute für diese Notiz ausblenden",
"hide_promoted_attributes": "Heraufgestufte 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.",
@@ -416,13 +409,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 Zweig nicht angezeigt werden kann.",
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Unterbaum 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 „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.",
"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.",
"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).",
@@ -433,8 +426,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 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.",
"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.",
"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“.",
@@ -632,7 +625,7 @@
"show_toc": "Inhaltsverzeichnis anzeigen"
},
"show_highlights_list_widget_button": {
"show_highlights_list": "Markierungsliste anzeigen"
"show_highlights_list": "Hervorhebungen anzeigen"
},
"global_menu": {
"menu": "Menü",
@@ -645,8 +638,8 @@
"zoom_out": "Herauszoomen",
"reset_zoom_level": "Zoomstufe zurücksetzen",
"zoom_in": "Hineinzoomen",
"configure_launchbar": "Konfiguriere die Starterleiste",
"show_shared_notes_subtree": "Zweig „Freigegebene Notizen“ anzeigen",
"configure_launchbar": "Konfiguriere die Launchbar",
"show_shared_notes_subtree": "Unterbaum „Freigegebene Notizen“ anzeigen",
"advanced": "Erweitert",
"open_dev_tools": "Öffne die Entwicklungstools",
"open_sql_console": "Öffne die SQL-Konsole",
@@ -655,7 +648,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 Zweig anzeigen",
"show_hidden_subtree": "Versteckten Teilbaum anzeigen",
"show_help": "Hilfe anzeigen",
"about": "Über Trilium Notes",
"logout": "Abmelden",
@@ -702,9 +695,7 @@
"export_as_image": "Als Bild exportieren",
"export_as_image_png": "PNG (Raster)",
"export_as_image_svg": "SVG (Vektor)",
"note_map": "Notizen Karte",
"view_revisions": "Notizrevisionen...",
"advanced": "Erweitert"
"note_map": "Notizen Karte"
},
"onclick_button": {
"no_click_handler": "Das Schaltflächen-Widget „{{componentId}}“ hat keinen definierten Klick-Handler"
@@ -720,7 +711,7 @@
"update_available": "Update verfügbar"
},
"note_launcher": {
"this_launcher_doesnt_define_target_note": "Dieser Starter definiert keine Zielnotiz."
"this_launcher_doesnt_define_target_note": "Dieser Launcher definiert keine Zielnotiz."
},
"code_buttons": {
"execute_button_title": "Skript ausführen",
@@ -763,8 +754,8 @@
"change_note_icon": "Notiz-Icon ändern",
"search": "Suche:",
"reset-default": "Standard wiederherstellen",
"search_placeholder_one": "Suche {{number}} Symbole über {{count}} Pakete",
"search_placeholder_other": "Suche {{number}} Symbole über {{count}} Pakete",
"search_placeholder_one": "Suche {{number}} Icons über {{count}} Pakete",
"search_placeholder_other": "Suche {{number}} Icons über {{count}} Pakete",
"search_placeholder_filtered": "Suche {{number}} Icons in {{name}}",
"filter": "Filter",
"filter-none": "Alle Icons",
@@ -797,8 +788,7 @@
"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",
"hide_child_notes": "Unternotizen im Baum ausblenden"
"expand_nth_level": "{{depth}} Ebenen erweitern"
},
"edited_notes": {
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
@@ -811,7 +801,7 @@
"file_type": "Dateityp",
"file_size": "Dateigröße",
"download": "Herunterladen",
"open": "Extern öffnen",
"open": "Offen",
"upload_new_revision": "Neue Revision hochladen",
"upload_success": "Neue Dateirevision wurde hochgeladen.",
"upload_failed": "Das Hochladen einer neuen Dateirevision ist fehlgeschlagen.",
@@ -842,7 +832,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": "(Zweiggröße: {{size}} in {{count}} Notizen)",
"subtree_size": "(Teilbaumgröße: {{size}} in {{count}} Notizen)",
"title": "Notizinfo",
"mime": "MIME Typ",
"show_similar_notes": "Zeige ähnliche Notizen"
@@ -871,7 +861,7 @@
"owned_attributes": "Eigene Attribute"
},
"promoted_attributes": {
"promoted_attributes": "Hervorgehobene Attribute",
"promoted_attributes": "Übergebene Attribute",
"url_placeholder": "http://website...",
"open_external_link": "Externen Link öffnen",
"unknown_label_type": "Unbekannter Labeltyp „{{type}}“",
@@ -909,7 +899,7 @@
"unknown_search_option": "Unbekannte Suchoption {{searchOptionName}}",
"search_note_saved": "Suchnotiz wurde in {{-notePathTitle}} gespeichert",
"actions_executed": "Aktionen wurden ausgeführt.",
"view_options": "Optionen anzeigen:"
"view_options": "Anzeigeoptionen:"
},
"similar_notes": {
"title": "Ähnliche Notizen",
@@ -1015,9 +1005,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 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_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_details_title": "Technische Informationen"
},
"empty": {
@@ -1115,7 +1105,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": "Datenbank aufräumen",
"button_text": "Vakuumdatenbank",
"vacuuming_database": "Datenbank wird geleert...",
"database_vacuumed": "Die Datenbank wurde geleert"
},
@@ -1156,7 +1146,7 @@
},
"ribbon": {
"widgets": "Multifunktionsleisten-Widgets",
"promoted_attributes_message": "Die „Hervorgehobene Attribute“-Leiste wird automatisch geöffnet, wenn in der Notiz hervorgehobene Attribute vorhanden sind",
"promoted_attributes_message": "Die Multifunktionsleisten-Registerkarte „Heraufgestufte Attribute“ wird automatisch geöffnet, wenn in der Notiz heraufgestufte Attribute vorhanden sind",
"edited_notes_message": "Die Multifunktionsleisten-Registerkarte „Bearbeitete Notizen“ wird bei Tagesnotizen automatisch geöffnet"
},
"theme": {
@@ -1169,7 +1159,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)",
@@ -1177,7 +1167,7 @@
},
"zoom_factor": {
"title": "Zoomfaktor (nur Desktop-Build)",
"description": "Das Zoomen kann auch mit den Tastenkombinationen Strg+- und Strg+= gesteuert werden."
"description": "Das Zoomen kann auch mit den Tastenkombinationen STRG+- und STRG+u003d gesteuert werden."
},
"code_auto_read_only_size": {
"title": "Automatische schreibgeschützte Größe",
@@ -1266,16 +1256,16 @@
"markdown": "Markdown-Stil"
},
"highlights_list": {
"title": "Markierungsliste",
"description": "Du kannst die im rechten Bereich angezeigte Markierungsliste anpassen:",
"title": "Highlights-Liste",
"description": "Du kannst die im rechten Bereich angezeigte Highlights-Liste anpassen:",
"bold": "Fettgedruckter Text",
"italic": "Kursiver Text",
"underline": "Unterstrichener Text",
"color": "Farbiger Text",
"bg_color": "Text mit Hintergrundfarbe",
"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“)."
"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“)."
},
"table_of_contents": {
"title": "Inhaltsverzeichnis",
@@ -1418,7 +1408,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}}, MIME: {{- mimeType}}",
"role_and_size": "Rolle: {{role}}, Größe: {{size}}",
"link_copied": "Anhangslink in die Zwischenablage kopiert.",
"unrecognized_role": "Unbekannte Anhangsrolle „{{role}}“."
},
@@ -1445,19 +1435,19 @@
"insert-note-after": "Notiz dahinter einfügen",
"insert-child-note": "Unternotiz einfügen",
"delete": "Löschen",
"search-in-subtree": "Im Zweig suchen",
"search-in-subtree": "Im Notizbaum suchen",
"hoist-note": "Notiz-Fokus setzen",
"unhoist-note": "Notiz-Fokus aufheben",
"edit-branch-prefix": "Zweig-Präfix bearbeiten",
"advanced": "Erweitert",
"expand-subtree": "Zweig aufklappen",
"collapse-subtree": "Zweig einklappen",
"expand-subtree": "Unterzweig aufklappen",
"collapse-subtree": "Notizbaum einklappen",
"sort-by": "Sortieren nach...",
"recent-changes-in-subtree": "Kürzliche Änderungen im Zweig",
"recent-changes-in-subtree": "Kürzliche Änderungen im Notizbaum",
"convert-to-attachment": "Als Anhang konvertieren",
"copy-note-path-to-clipboard": "Notiz-Pfad in die Zwischenablage kopieren",
"protect-subtree": "Zweig schützen",
"unprotect-subtree": "Zweig-Schutz aufheben",
"protect-subtree": "Notizbaum schützen",
"unprotect-subtree": "Notizenbaum-Schutz aufheben",
"copy-clone": "Kopieren / Klonen",
"clone-to": "Klonen nach...",
"cut": "Ausschneiden",
@@ -1469,17 +1459,14 @@
"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? Diese Operation wird nur auf Bildnotizes angewandt. Andere Notizen werden übersprungen.",
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest?",
"open-in-popup": "Schnellbearbeitung",
"archive": "Archiviere",
"unarchive": "Entarchivieren",
"open-in-a-new-window": "In neuem Fenster öffnen",
"hide-subtree": "Zweig ausblenden",
"show-subtree": "Zweig anzeigen"
"unarchive": "Entarchivieren"
},
"shared_info": {
"shared_publicly": "Diese Notiz ist öffentlich freigegeben über {{- link}}.",
"shared_locally": "Diese Notiz ist lokal freigegeben über {{- link}}.",
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}.",
"help_link": "Für Hilfe besuche <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"note_types": {
@@ -1490,12 +1477,12 @@
"note-map": "Notizkarte",
"render-note": "Render Notiz",
"mermaid-diagram": "Mermaid Diagramm",
"canvas": "Leinwand",
"canvas": "Canvas",
"web-view": "Webansicht",
"mind-map": "Mind Map",
"file": "Datei",
"image": "Bild",
"launcher": "Starter",
"launcher": "Launcher",
"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?",
@@ -1514,10 +1501,10 @@
"toggle-off-hint": "Notiz ist geschützt, klicken, um den Schutz aufzuheben"
},
"shared_switch": {
"shared": "Freigegeben",
"toggle-on-title": "Notiz freigeben",
"shared": "Teilen",
"toggle-on-title": "Notiz teilen",
"toggle-off-title": "Notiz-Freigabe aufheben",
"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?",
"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?",
"inherited": "Die Notiz kann hier nicht von der Freigabe entfernt werden, da sie über Vererbung von einer übergeordneten Notiz geteilt wird."
},
"template_switch": {
@@ -1535,13 +1522,13 @@
"replace_all": "Alle Ersetzen"
},
"highlights_list_2": {
"title": "Markierungsliste",
"title": "Hervorhebungs-Liste",
"options": "Optionen",
"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."
"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."
},
"quick-search": {
"placeholder": "Schnellsuche",
@@ -1565,16 +1552,7 @@
"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.",
"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."
"dropping-not-allowed": "Ablegen von Notizen an dieser Stelle ist nicht zulässig."
},
"title_bar_buttons": {
"window-on-top": "Dieses Fenster immer oben halten"
@@ -1585,9 +1563,7 @@
"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_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."
"print_report_collection_details_ignored_notes": "Ignorierte Notizen"
},
"note_title": {
"placeholder": "Titel der Notiz hier eingeben…",
@@ -1595,7 +1571,7 @@
"last_modified": "Bearbeitet am <Value />",
"note_type_switcher_label": "Ändere von {{type}} zu:",
"note_type_switcher_others": "Andere Notizart",
"note_type_switcher_templates": "Vorlage",
"note_type_switcher_templates": "Template",
"note_type_switcher_collection": "Sammlung",
"edited_notes": "Notizen, bearbeitet an diesem Tag",
"promoted_attributes": "Hervorgehobene Attribute"
@@ -1605,14 +1581,10 @@
"search_not_executed": "Die Suche wurde noch nicht ausgeführt. Klicke oben auf „Suchen“, um die Ergebnisse anzuzeigen."
},
"spacer": {
"configure_launchbar": "Starterleiste konfigurieren"
"configure_launchbar": "Startleiste konfigurieren"
},
"sql_result": {
"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"
"no_rows": "Es wurden keine Zeilen für diese Abfrage zurückgegeben"
},
"sql_table_schemas": {
"tables": "Tabellen"
@@ -1683,16 +1655,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 Starter wird an seinen ursprünglichen Standort zurückgesetzt.",
"add-note-launcher": "Notiz-Starter hinzufügen",
"add-script-launcher": "Skript-Starter 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 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",
"add-custom-widget": "Benutzerdefiniertes Widget hinzufügen",
"add-spacer": "Abstandhalter hinzufügen",
"add-spacer": "Spacer hinzufügen",
"delete": "Löschen <kbd data-command=\"deleteNotes\"></kbd>",
"reset": "Zurücksetzen",
"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\">"
"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\">"
},
"highlighting": {
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
@@ -1701,7 +1673,7 @@
},
"code_block": {
"word_wrapping": "Wortumbruch",
"theme_none": "Keine Syntaxhervorhebung",
"theme_none": "Keine Syntax-Hervorhebung",
"theme_group_light": "Helle Themen",
"theme_group_dark": "Dunkle Themen",
"copy_title": "Kopiere in Zwischenablage"
@@ -1744,15 +1716,14 @@
"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_other_split": "Notiz in neuer Spalte öffnen"
"open_note_in_popup": "Schnellbearbeitung"
},
"electron_integration": {
"desktop-application": "Desktop Anwendung",
"native-title-bar": "Native Anwendungsleiste",
"native-title-bar-description": "In Windows und macOS, sorgt das Deaktivieren der nativen Anwendungsleiste für ein kompakteres Aussehen. Unter Linux, sorgt das Aktivieren der nativen Anwendungsleiste für eine bessere Integration mit anderen Teilen des Systems.",
"background-effects": "Hintergrundeffekte aktivieren",
"background-effects-description": "Fügt einen unscharfen, stylischen Hintergrund in das Anwendungsfenstern ein. Dies erzeugt Tiefe und ein modernes Auftreten. \"Native Titelleiste\" muss deaktiviert sein.",
"background-effects": "Hintergrundeffekte aktivieren (nur Windows 11)",
"background-effects-description": "Der Mica Effekt fügt einen unscharfen, stylischen Hintergrund in Anwendungsfenstern ein. Dieser erzeugt Tiefe und ein modernes Auftreten. \"Native Titelleiste\" muss deaktiviert sein.",
"restart-app-button": "Anwendung neustarten um Änderungen anzuwenden",
"zoom-factor": "Zoomfaktor"
},
@@ -2000,7 +1971,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 freigegeben"
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht geteilt"
},
"tasks": {
"due": {
@@ -2013,9 +1984,8 @@
"unknown_widget": "Unbekanntes Widget für '{{id}}'."
},
"note_language": {
"not_set": "Keine Sprache ausgewählt",
"configure-languages": "Konfiguriere Sprachen...",
"help-on-languages": "Zu Übersetzungen beitragen..."
"not_set": "Nicht gesetzt",
"configure-languages": "Konfiguriere Sprachen..."
},
"content_language": {
"title": "Inhaltssprachen",
@@ -2033,8 +2003,7 @@
"button_title": "Exportiere Diagramm als PNG"
},
"svg": {
"export_to_png": "Das Diagramm konnte als PNG nicht exportiert werden.",
"export_to_svg": "Das Diagramm konnte nicht als SVG exportiert werden."
"export_to_png": "Das Diagramm konnte als PNG nicht exportiert werden."
},
"code_theme": {
"title": "Aussehen",
@@ -2082,7 +2051,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)",
@@ -2118,8 +2087,8 @@
"show_attachments_description": "Notizanhänge anzeigen",
"search_notes_title": "Suche Notiz",
"search_notes_description": "Öffne erweiterte Suche",
"search_subtree_title": "Im Zweig suchen",
"search_subtree_description": "Im aktuellen Zweig suchen",
"search_subtree_title": "Im Unterzweig suchen",
"search_subtree_description": "Im aktuellen Unterzweig suchen",
"search_history_title": "Zeige Suchhistorie",
"search_history_description": "Zeige vorherige Suchen",
"configure_launch_bar_title": "Startleiste anpassen",
@@ -2133,22 +2102,16 @@
"next_theme_message": "Es wird aktuell das alte Design verwendet. Möchten Sie das neue Design ausprobieren?",
"next_theme_button": "Teste das neue Design",
"background_effects_title": "Hintergrundeffekte sind jetzt zuverlässig nutzbar",
"background_effects_message": "Auf Windows- und macOS-Geräten sind die Hintergrundeffekte nun stabil. Die Hintergrundeffekte verleihen der Benutzeroberfläche einen Farbakzent, indem der Hintergrund dahinter weichgezeichnet wird.",
"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",
"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"
"dismiss": "Ablehnen"
},
"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",
"ui": "Benutzeroberfläche",
"ui_old_layout": "Altes Layout",
"ui_new_layout": "Neues Layout"
"related_code_notes": "Farbschema für Code-Notizen"
},
"units": {
"percentage": "%"
@@ -2184,91 +2147,6 @@
"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_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..."
},
"platform_indicator": {
"available_on": "Verfügbar auf {{platform}}"
"new_layout_name": "Neues Layout"
}
}

View File

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

View File

@@ -800,8 +800,7 @@
"geo-map": "Geo Map",
"board": "Board",
"presentation": "Presentation",
"include_archived_notes": "Show archived notes",
"hide_child_notes": "Hide child notes in tree"
"include_archived_notes": "Show archived notes"
},
"edited_notes": {
"no_edited_notes_found": "No edited notes on this day yet...",
@@ -1644,7 +1643,6 @@
"tree-context-menu": {
"open-in-a-new-tab": "Open in a new tab",
"open-in-a-new-split": "Open in a new split",
"open-in-a-new-window": "Open in a new window",
"insert-note-after": "Insert note after",
"insert-child-note": "Insert child note",
"archive": "Archive",
@@ -1657,8 +1655,6 @@
"advanced": "Advanced",
"expand-subtree": "Expand subtree",
"collapse-subtree": "Collapse subtree",
"hide-subtree": "Hide subtree",
"show-subtree": "Show subtree",
"sort-by": "Sort by...",
"recent-changes-in-subtree": "Recent changes in subtree",
"convert-to-attachment": "Convert to attachment",
@@ -1776,12 +1772,7 @@
"clone-indicator-tooltip": "This note has {{- count}} parents: {{- parents}}",
"clone-indicator-tooltip-single": "This note is cloned (1 additional parent: {{- parent}})",
"shared-indicator-tooltip": "This note is shared publicly",
"shared-indicator-tooltip-with-url": "This note is shared publicly at: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} child note that is hidden from the tree",
"subtree-hidden-tooltip_other": "{{count}} child notes that are hidden from the tree",
"subtree-hidden-moved-title": "Added to {{title}}",
"subtree-hidden-moved-description-collection": "This collection hides its child notes in the tree.",
"subtree-hidden-moved-description-other": "Child notes are hidden in the tree for this note."
"shared-indicator-tooltip-with-url": "This note is shared publicly at: {{- url}}"
},
"title_bar_buttons": {
"window-on-top": "Keep Window on Top"
@@ -1815,11 +1806,7 @@
"configure_launchbar": "Configure Launchbar"
},
"sql_result": {
"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"
"no_rows": "No rows have been returned for this query"
},
"sql_table_schemas": {
"tables": "Tables"
@@ -1958,8 +1945,8 @@
"desktop-application": "Desktop Application",
"native-title-bar": "Native title bar",
"native-title-bar-description": "For Windows and macOS, keeping the native title bar off makes the application look more compact. On Linux, keeping the native title bar on integrates better with the rest of the system.",
"background-effects": "Enable background effects",
"background-effects-description": "Adds a blurred, stylish background to app windows, creating depth and a modern look. \"Native title bar\" must be disabled.",
"background-effects": "Enable background effects (Windows 11 only)",
"background-effects-description": "The Mica effect adds a blurred, stylish background to app windows, creating depth and a modern look. \"Native title bar\" must be disabled.",
"restart-app-button": "Restart the application to view the changes",
"zoom-factor": "Zoom factor"
},
@@ -1977,7 +1964,6 @@
},
"geo-map": {
"create-child-note-title": "Create a new child note and add it to the map",
"create-child-note-text": "Add marker",
"create-child-note-instruction": "Click on the map to create a new note at that location or press Escape to dismiss.",
"unable-to-load-map": "Unable to load map."
},
@@ -2153,7 +2139,7 @@
"next_theme_message": "You are currently using the legacy theme, would you like to try the new theme?",
"next_theme_button": "Try the new theme",
"background_effects_title": "Background effects are now stable",
"background_effects_message": "On Windows and macOS devices, background effects are now stable. The background effects adds a touch of color to the user interface by blurring the background behind it.",
"background_effects_message": "On Windows devices, background effects are now fully stable. The background effects adds a touch of color to the user interface by blurring the background behind it. This technique is also used in other applications such as Windows Explorer.",
"background_effects_button": "Enable background effects",
"new_layout_title": "New layout",
"new_layout_message": "Weve introduced a modernized layout for Trilium. The ribbon has been removed and seamlessly integrated into the main interface, with a new status bar and expandable sections (such as promoted attributes) taking over key functions.\n\nThe new layout is enabled by default, and can be temporarily disabled via Options → Appearance.",
@@ -2268,13 +2254,5 @@
"pages_other": "{{count}} pages",
"pages_alt": "Page {{pageNumber}}",
"pages_loading": "Loading..."
},
"platform_indicator": {
"available_on": "Available on {{platform}}"
},
"mobile_tab_switcher": {
"title_one": "{{count}} tab",
"title_other": "{{count}} tabs",
"more_options": "More options"
}
}

View File

@@ -21,17 +21,8 @@
},
"bundle-error": {
"title": "Hubo un fallo al cargar un script personalizado",
"message": "El script no pudo ser ejecutado debido a:\n\n{{message}}"
},
"widget-list-error": {
"title": "Hubo un fallo al obtener la lista de widgets del servidor"
},
"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}}"
"message": "El script de la nota con ID \"{{id}}\", titulado \"{{title}}\" no pudo ser ejecutado debido a:\n\n{{message}}"
}
},
"add_link": {
"add_link": "Agregar enlace",
@@ -171,8 +162,7 @@
"other": "Otro",
"quickSearch": "centrarse en la entrada de búsqueda rápida",
"inPageSearch": "búsqueda en la página",
"title": "Hoja de ayuda",
"editShortcuts": "Editar atajos de teclado"
"title": "Hoja de ayuda"
},
"import": {
"importIntoNote": "Importar a nota",
@@ -214,8 +204,7 @@
"info": {
"modalTitle": "Mensaje informativo",
"closeButton": "Cerrar",
"okButton": "Aceptar",
"copy_to_clipboard": "Copiar al portapapeles"
"okButton": "Aceptar"
},
"jump_to_note": {
"search_button": "Buscar en texto completo",
@@ -701,13 +690,7 @@
"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",
"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"
"open_note_on_server": "Abrir nota en el servidor"
},
"onclick_button": {
"no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido"
@@ -753,7 +736,7 @@
"zpetne_odkazy": {
"relation": "relación",
"backlink_one": "{{count}} Vínculo de retroceso",
"backlink_many": "{{count}} Vínculos de retroceso",
"backlink_many": "",
"backlink_other": "{{count}} vínculos de retroceso"
},
"mobile_detail_menu": {
@@ -766,16 +749,7 @@
"note_icon": {
"change_note_icon": "Cambiar icono de nota",
"search": "Búsqueda:",
"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_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."
"reset-default": "Restablecer a icono por defecto"
},
"basic_properties": {
"note_type": "Tipo de nota",
@@ -799,11 +773,10 @@
"board": "Tablero",
"include_archived_notes": "Mostrar notas archivadas",
"presentation": "Presentación",
"expand_tooltip": "Expande las subnotas inmediatas de esta colección (un nivel). Para más opciones, pulsa la flecha a la derecha.",
"expand_tooltip": "Expande las notas hijas 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",
"hide_child_notes": "Ocultar subnotas en el árbol"
"expand_all_levels": "Expandir todos los niveles"
},
"edited_notes": {
"no_edited_notes_found": "Aún no hay notas editadas en este día...",
@@ -816,7 +789,7 @@
"file_type": "Tipo de archivo",
"file_size": "Tamaño del archivo",
"download": "Descargar",
"open": "Abrir externamente",
"open": "Abrir",
"upload_new_revision": "Subir nueva revisión",
"upload_success": "Se ha subido una nueva revisión de archivo.",
"upload_failed": "Error al cargar una nueva revisión de archivo.",
@@ -836,8 +809,7 @@
},
"inherited_attribute_list": {
"title": "Atributos heredados",
"no_inherited_attributes": "Sin atributos heredados.",
"none": "ninguno"
"no_inherited_attributes": "Sin atributos heredados."
},
"note_info_widget": {
"note_id": "ID de nota",
@@ -848,9 +820,7 @@
"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",
"mime": "Tipo MIME",
"show_similar_notes": "Mostrar notas similares"
"title": "Información de nota"
},
"note_map": {
"open_full": "Ampliar al máximo",
@@ -913,8 +883,7 @@
"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.",
"view_options": "Ver opciones:"
"actions_executed": "Las acciones han sido ejecutadas."
},
"similar_notes": {
"title": "Notas similares",
@@ -1017,13 +986,7 @@
},
"editable_text": {
"placeholder": "Escribe aquí el contenido de tu nota...",
"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."
"auto-detect-language": "Detectado automáticamente"
},
"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.",
@@ -1339,11 +1302,11 @@
"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_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",
"use_vim_keybindings_in_code_notes": "Atajos de teclas de Vim",
"enable_vim_keybindings": "Habilitar los atajos de teclas de Vim en la notas de código (no es modo ex)"
},
"wrap_lines": {
@@ -1417,16 +1380,16 @@
"markdown": "Estilo Markdown"
},
"highlights_list": {
"title": "Lista de puntos destacados",
"description": "Puede personalizar la lista de puntos destacados que se muestra en el panel derecho:",
"title": "Lista de aspectos destacados",
"description": "Puede personalizar la lista de aspectos 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 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')."
"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')."
},
"table_of_contents": {
"title": "Tabla de contenido",
@@ -1608,13 +1571,13 @@
"will_be_deleted_in": "Este archivo adjunto se eliminará automáticamente en {{time}}",
"will_be_deleted_soon": "Este archivo adjunto se eliminará automáticamente pronto",
"deletion_reason": ", porque el archivo adjunto no está vinculado en el contenido de la nota. Para evitar la eliminación, vuelva a agregar el enlace del archivo adjunto al contenido o convierta el archivo adjunto en una nota.",
"role_and_size": "Rol: {{role}}, tamaño: {{size}}, MIME: {{- mimeType}}",
"role_and_size": "Rol: {{role}}, Tamaño: {{size}}",
"link_copied": "Enlace del archivo adjunto copiado al portapapeles.",
"unrecognized_role": "Rol de archivo adjunto no reconocido '{{role}}'."
},
"bookmark_switch": {
"bookmark": "Marcador",
"bookmark_this_note": "Agregar esta nota a marcadores en el panel lateral izquierdo",
"bookmark_this_note": "Añadir esta nota a marcadores en el panel lateral izquierdo",
"remove_bookmark": "Eliminar marcador"
},
"editability_select": {
@@ -1659,13 +1622,10 @@
"import-into-note": "Importar a nota",
"apply-bulk-actions": "Aplicar acciones en lote",
"converted-to-attachments": "{{count}} notas han sido convertidas en archivos adjuntos.",
"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.",
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres?",
"open-in-popup": "Edición rápida",
"archive": "Archivar",
"unarchive": "Desarchivar",
"open-in-a-new-window": "Abrir en una nueva ventana",
"hide-subtree": "Ocultar subárbol",
"show-subtree": "Mostrar subárbol"
"unarchive": "Desarchivar"
},
"shared_info": {
"shared_publicly": "Esta nota está compartida públicamente en {{- link}}.",
@@ -1726,13 +1686,7 @@
},
"highlights_list_2": {
"title": "Lista de destacados",
"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."
"options": "Opciones"
},
"quick-search": {
"placeholder": "Búsqueda rápida",
@@ -1755,18 +1709,7 @@
"refresh-saved-search-results": "Refrescar resultados de búsqueda guardados",
"create-child-note": "Crear subnota",
"unhoist": "Desanclar",
"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."
"toggle-sidebar": "Alternar barra lateral"
},
"title_bar_buttons": {
"window-on-top": "Mantener esta ventana en la parte superior"
@@ -1774,24 +1717,10 @@
"note_detail": {
"could_not_find_typewidget": "No se pudo encontrar typeWidget para el tipo '{{type}}'",
"printing": "Impresión en curso...",
"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_title": "Imprimir informe",
"print_report_collection_details_button": "Ver detalles",
"print_report_collection_details_ignored_notes": "Notas ignoradas"
"printing_pdf": "Exportando a PDF en curso.."
},
"note_title": {
"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"
"placeholder": "escriba el título de la nota aquí..."
},
"search_result": {
"no_notes_found": "No se han encontrado notas para los parámetros de búsqueda dados.",
@@ -1801,11 +1730,7 @@
"configure_launchbar": "Configurar barra de lanzamiento"
},
"sql_result": {
"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"
"no_rows": "No se han devuelto filas para esta consulta"
},
"sql_table_schemas": {
"tables": "Tablas"
@@ -1824,8 +1749,7 @@
},
"toc": {
"table_of_contents": "Tabla de contenido",
"options": "Opciones",
"no_headings": "Sin encabezados."
"options": "Opciones"
},
"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>.",
@@ -1937,15 +1861,14 @@
"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_other_split": "Abrir nota en la otra división"
"open_note_in_popup": "Edición rápida"
},
"electron_integration": {
"desktop-application": "Aplicación de escritorio",
"native-title-bar": "Barra de título nativa",
"native-title-bar-description": "Para Windows y macOS, quitar la barra de título nativa hace que la aplicación se vea más compacta. En Linux, mantener la barra de título nativa hace que se integre mejor con el resto del sistema.",
"background-effects": "Habilitar efectos de fondo",
"background-effects-description": "Agrega un fondo borroso y elegante a las ventanas de la aplicación, creando profundidad y un aspecto moderno. \"Título nativo de la barra\" debe deshabilitarse.",
"background-effects": "Habilitar efectos de fondo (sólo en Windows 11)",
"background-effects-description": "El efecto Mica agrega un fondo borroso y elegante a las ventanas de la aplicación, creando profundidad y un aspecto moderno. \"Título nativo de la barra\" debe deshabilitarse.",
"restart-app-button": "Reiniciar la aplicación para ver los cambios",
"zoom-factor": "Factor de zoom"
},
@@ -1964,8 +1887,7 @@
"geo-map": {
"create-child-note-title": "Crear una nueva subnota y agregarla al mapa",
"create-child-note-instruction": "Dé clic en el mapa para crear una nueva nota en esa ubicación o presione Escape para cancelar.",
"unable-to-load-map": "No se puede cargar el mapa.",
"create-child-note-text": "Agregar marcador"
"unable-to-load-map": "No se puede cargar el mapa."
},
"geo-map-context": {
"open-location": "Abrir ubicación",
@@ -2007,12 +1929,11 @@
"unknown_widget": "Widget desconocido para \"{{id}}\"."
},
"note_language": {
"not_set": "Idioma no establecido",
"configure-languages": "Configurar idiomas...",
"help-on-languages": "Ayuda en idiomas de contenido..."
"not_set": "No establecido",
"configure-languages": "Configurar idiomas..."
},
"content_language": {
"title": "Idiomas de contenido",
"title": "Contenido de idiomas",
"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": {
@@ -2027,8 +1948,7 @@
"button_title": "Exportar diagrama como PNG"
},
"svg": {
"export_to_png": "El diagrama no pudo ser exportado a PNG.",
"export_to_svg": "El diagrama no pudo ser exportado a SVG."
"export_to_png": "El diagrama no pudo ser exportado a PNG."
},
"code_theme": {
"title": "Apariencia",
@@ -2048,7 +1968,7 @@
"hide-weekends": "Ocultar fines de semana",
"show-scale": "Mostrar escala",
"display-week-numbers": "Mostrar números de semana",
"map-style": "Estilo de mapa",
"map-style": "Estilo de mapa:",
"max-nesting-depth": "Máxima profundidad de anidamiento:",
"vector_light": "Vector (claro)",
"vector_dark": "Vector (oscuro)",
@@ -2131,12 +2051,9 @@
"next_theme_message": "Estás usando actualmente el tema heredado. ¿Te gustaría probar el nuevo tema?",
"next_theme_button": "Prueba el nuevo tema",
"background_effects_title": "Los efectos de fondo son ahora estables",
"background_effects_message": "En los dispositivos Windows y macOS, los efectos de fondo ya son estables. Los efectos de fondo añaden un toque de color a la interfaz de usuario difuminando el fondo que hay detrás.",
"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",
"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"
"dismiss": "Desestimar"
},
"ui-performance": {
"title": "Rendimiento",
@@ -2151,10 +2068,7 @@
},
"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",
"ui": "Interfaz de usuario",
"ui_old_layout": "Antiguo diseño",
"ui_new_layout": "Nuevo diseño"
"related_code_notes": "Esquema de colores para notas de código"
},
"units": {
"percentage": "%"
@@ -2183,107 +2097,5 @@
"clear-color": "Borrar color de nota",
"set-color": "Asignar color de nota",
"set-custom-color": "Asignar color de nota personalizado"
},
"status_bar": {
"backlinks_one": "{{count}} vínculo de retroceso",
"backlinks_many": "{{count}} vínculos de retroceso",
"backlinks_other": "{{count}} vínculos de retroceso",
"backlinks_title_one": "Ver vínculo de retroceso",
"backlinks_title_many": "Ver vínculos de retroceso",
"backlinks_title_other": "Ver vínculos de retroceso",
"attachments_one": "{{count}} adjunto",
"attachments_many": "{{count}} adjuntos",
"attachments_other": "{{count}} adjuntos",
"attachments_title_one": "Ver adjunto en una nueva pestaña",
"attachments_title_many": "Ver adjuntos en una nueva pestaña",
"attachments_title_other": "Ver adjuntos en una nueva pestaña",
"attributes_one": "{{count}} atributo",
"attributes_many": "{{count}} atributos",
"attributes_other": "{{count}} atributos",
"note_paths_one": "{{count}} ruta",
"note_paths_many": "{{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",
"attachments_many": "{{count}} adjuntos",
"attachments_other": "{{count}} adjuntos",
"layers_one": "{{count}} capa",
"layers_many": "{{count}} capas",
"layers_other": "{{count}} capas",
"pages_one": "{{count}} página",
"pages_many": "{{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"
},
"platform_indicator": {
"available_on": "Disponible en {{platform}}"
}
}

View File

@@ -21,7 +21,7 @@
},
"bundle-error": {
"title": "Echec du chargement d'un script personnalisé",
"message": "Le script n'a pas pu être exécuté à cause de\n\n{{message}}"
"message": "Le script de la note avec l'ID \"{{id}}\", intitulé \"{{title}}\" n'a pas pu être exécuté à cause de\n\n{{message}}"
},
"widget-list-error": {
"title": "Impossible d'obtenir la liste des widgets depuis le serveur"

View File

@@ -1,10 +1,7 @@
{
"about": {
"title": "ट्रिलियम नोट्स के बारें में",
"build_date": "निर्माण की तारीख:",
"app_version": "ऐप वर्ज़न:",
"db_version": "DB वर्ज़न:",
"build_revision": "बिल्ड रिविज़न:"
"build_date": "निर्माण की तारीख:"
},
"toast": {
"widget-error": {
@@ -34,17 +31,5 @@
},
"add_link": {
"note": "नोट"
},
"bulk_actions": {
"other": "अन्य"
},
"clone_to": {
"search_for_note_by_its_name": "नोट क नाम से नोट खोजें"
},
"confirm": {
"also_delete_note": "नोट भी डिलीट करें"
},
"delete_notes": {
"delete_notes_preview": "नोट्स प्रिव्यू डिलीट करें"
}
}

View File

@@ -21,13 +21,7 @@
},
"bundle-error": {
"title": "Nem sikerült betölteni az egyéni szkriptet",
"message": "A skript nem hajtható végre a következő ok miatt:\n\n{{message}}"
},
"widget-list-error": {
"title": "A Widget-ek letöltése sikertelen volt"
},
"widget-render-error": {
"title": "Nem sikerült renderelni a React widget-et"
"message": "A(z) \"{{id}}\" azonosítójú, \"{{title}}\" című jegyzetből származó szkript nem hajtható végre a következő ok miatt:\n\n{{message}}"
}
},
"add_link": {

View File

@@ -11,8 +11,8 @@
},
"toast": {
"critical-error": {
"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."
"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."
},
"widget-error": {
"title": "Gagal menginisialisasi widget",
@@ -21,65 +21,12 @@
},
"bundle-error": {
"title": "Gagal memuat skrip kustom",
"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}}"
"message": "Skrip dari catatan dengan ID \"{{id}}\", berjudul \"{{title}}\" tidak dapat dijalankan karena:\n\n{{message}}"
}
},
"add_link": {
"add_link": "Tambah tautan",
"help_on_links": "Bantuan pada tautan",
"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…"
"note": "Catatan"
}
}

View File

@@ -325,10 +325,7 @@
"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-a-new-window": "Apri in una nuova finestra",
"hide-subtree": "Nascondi sottostruttura",
"show-subtree": "Mostra sottoalbero"
"open-in-popup": "Modifica rapida"
},
"electron_context_menu": {
"cut": "Taglia",
@@ -1381,8 +1378,7 @@
"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",
"hide_child_notes": "Nascondi note secondarie nell'albero"
"expand_all_levels": "Espandi tutti i livelli"
},
"edited_notes": {
"no_edited_notes_found": "Nessuna nota modificata per questo giorno...",
@@ -1899,17 +1895,7 @@
"create-child-note": "Crea nota figlio",
"unhoist": "Sganciare",
"toggle-sidebar": "Attiva/disattiva la barra laterale",
"dropping-not-allowed": "Non è consentito lasciare appunti in questa posizione.",
"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}}",
"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."
"dropping-not-allowed": "Non è consentito lasciare appunti in questa posizione."
},
"title_bar_buttons": {
"window-on-top": "Mantieni la finestra in primo piano"
@@ -1944,11 +1930,7 @@
"configure_launchbar": "Configura Launchbar"
},
"sql_result": {
"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"
"no_rows": "Nessuna riga è stata restituita per questa query"
},
"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>.",
@@ -2218,14 +2200,7 @@
"execute_sql_description": "Questa nota è una nota SQL. Clicca per eseguire la query SQL.",
"shared_copy_to_clipboard": "Copia link negli appunti",
"shared_open_in_browser": "Apri il link nel browser",
"shared_unshare": "Rimuovi condivisione",
"save_status_saved": "Salvato",
"save_status_saving": "Salvataggio in corso...",
"save_status_unsaved": "Non salvato",
"save_status_error": "Salvataggio non riuscito",
"save_status_saving_tooltip": "Le modifiche sono state salvate.",
"save_status_unsaved_tooltip": "Ci sono modifiche non salvate. Verranno salvate automaticamente tra un attimo.",
"save_status_error_tooltip": "Si è verificato un errore durante il salvataggio della nota. Se possibile, prova a copiare il contenuto della nota altrove e a ricaricare l'applicazione."
"shared_unshare": "Rimuovi condivisione"
},
"breadcrumb": {
"workspace_badge": "Area di lavoro",
@@ -2268,18 +2243,5 @@
"empty_button": "Nascondi il pannello",
"toggle": "Attiva/disattiva pannello destro",
"custom_widget_go_to_source": "Vai al codice sorgente"
},
"pdf": {
"attachments_one": "{{count}} allegato",
"attachments_many": "{{count}} allegati",
"attachments_other": "{{count}} allegati",
"layers_one": "{{count}} livello",
"layers_many": "{{count}} livelli",
"layers_other": "{{count}} livelli",
"pages_one": "{{count}} pagina",
"pages_many": "{{count}} pagine",
"pages_other": "{{count}} pagine",
"pages_alt": "Pagina {{pageNumber}}",
"pages_loading": "Caricamento in corso..."
}
}

View File

@@ -443,10 +443,7 @@
"unhoist-note": "ノートのホイストを解除",
"edit-branch-prefix": "ブランチの接頭辞を編集",
"archive": "アーカイブ",
"unarchive": "アーカイブ解除",
"open-in-a-new-window": "新しいウィンドウで開く",
"hide-subtree": "サブツリーを非表示",
"show-subtree": "サブツリーを表示"
"unarchive": "アーカイブ解除"
},
"zen_mode": {
"button_exit": "禅モードを退出"
@@ -571,8 +568,7 @@
"expand_tooltip": "このコレクションの直下の子1階層下を展開します。その他のオプションについては、右側の矢印を押してください。",
"expand_first_level": "直下の子を展開",
"expand_nth_level": "{{depth}} 階層下まで展開",
"expand_all_levels": "すべての階層を展開",
"hide_child_notes": "ツリー内の子ノートを非表示"
"expand_all_levels": "すべての階層を展開"
},
"note_types": {
"geo-map": "ジオマップ",
@@ -1252,11 +1248,7 @@
"clone-indicator-tooltip": "このノートには {{- count}} 個の親があります: {{- parents}}",
"clone-indicator-tooltip-single": "このノートは複製されています (親が 1 件追加: {{- parent}})",
"shared-indicator-tooltip": "このノートは公開されています",
"shared-indicator-tooltip-with-url": "このノートは以下で公開されています: {{- url}}",
"subtree-hidden-tooltip_other": "{{count}} 個の子ノートがツリーで非表示になっています",
"subtree-hidden-moved-title": "{{title}} に追加されました",
"subtree-hidden-moved-description-collection": "このコレクションはツリー内の子ノートを非表示にします。",
"subtree-hidden-moved-description-other": "このノートのツリーでは子ノートは非表示になっています。"
"shared-indicator-tooltip-with-url": "このノートは以下で公開されています: {{- url}}"
},
"bulk_actions": {
"bulk_actions": "一括操作",
@@ -1288,11 +1280,7 @@
"search_not_executed": "検索はまだ実行されていません。上の「検索」ボタンをクリックすると、検索結果が表示されます。"
},
"sql_result": {
"no_rows": "このクエリでは行が返されませんでした",
"not_executed": "クエリはまだ実行されていません。",
"failed": "SQLクエリの実行に失敗しました",
"statement_result": "ステートメント結果",
"execute_now": "今すぐ実行"
"no_rows": "このクエリでは行が返されませんでした"
},
"sql_table_schemas": {
"tables": "テーブル"
@@ -1757,8 +1745,8 @@
"desktop-application": "デスクトップアプリケーション",
"native-title-bar": "ネイティブタイトルバー",
"native-title-bar-description": "WindowsとmacOSでは、ネイティブタイトルバーをオフにしておくと、アプリケーションがよりコンパクトに見えます。Linuxでは、ネイティブタイトルバーを表示したままの方が、他のシステムとの統一性が高まります。",
"background-effects": "背景効果を有効化",
"background-effects-description": "アプリウィンドウにぼかしの効いたスタイリッシュな背景を追加し、奥行きとモダンな外観を演出します。「ネイティブタイトルバー」を無効にする必要があります。",
"background-effects": "背景効果を有効化Windows 11のみ",
"background-effects-description": "Mica効果は、アプリウィンドウにぼかされたスタイリッシュな背景を追加し、奥行きとモダンな外観を演出します。「ネイティブタイトルバー」を無効にする必要があります。",
"restart-app-button": "アプリケーションを再起動して変更を反映",
"zoom-factor": "ズーム倍率"
},
@@ -2044,7 +2032,7 @@
"next_theme_message": "現在、レガシーテーマを使用しています。新しいテーマを試してみませんか?",
"next_theme_button": "新しいテーマを試す",
"background_effects_title": "背景効果が安定しました",
"background_effects_message": "WindowsおよびmacOSデバイスで、背景効果が安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。",
"background_effects_message": "Windowsデバイスで、背景効果が完全に安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。この技術は、Windowsエクスプローラーなどの他のアプリケーションでも使用されています。",
"background_effects_button": "背景効果を有効にする",
"dismiss": "却下",
"new_layout_title": "新しいレイアウト",
@@ -2253,8 +2241,5 @@
"pages_other": "{{count}} ページ",
"pages_alt": "ページ {{pageNumber}}",
"pages_loading": "読み込み中..."
},
"platform_indicator": {
"available_on": "{{platform}} で利用可能"
}
}

View File

@@ -18,65 +18,5 @@
"zpetne_odkazy": {
"backlink_one": "{{count}} Tilbakelenke",
"backlink_other": "{{count}} Tilbakelenker"
},
"add_link": {
"note": "Notat"
},
"branch_prefix": {
"prefix": "Prefiks : ",
"save": "Lagre"
},
"bulk_actions": {
"labels": "Etiketter",
"relations": "Relasjoner",
"notes": "Notater",
"other": "Andre"
},
"confirm": {
"confirmation": "Bekreftelse",
"cancel": "Avbryt",
"ok": "OK"
},
"delete_notes": {
"close": "Lukk",
"cancel": "Avbryt",
"ok": "OK"
},
"export": {
"close": "Lukk",
"export": "Eksporter"
},
"note_type_chooser": {
"templates": "Maler"
},
"help": {
"title": "Hurtigveiledning",
"troubleshooting": "Feilsøking",
"other": "Andre"
},
"import": {
"options": "Alternativer",
"import": "Importer"
},
"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

@@ -12,7 +12,7 @@
"toast": {
"critical-error": {
"title": "Kritische Error",
"message": "Een kritieke fout heeft plaatsgevonden waardoor de applicatie niet kon opstarten:\n\n{{message}}\n\nDit is waarschijnlijk veroorzaakt door een onverwachte fout in een script. Probeer de applicatie op te starten in veilige modus en het probleem op te lossen."
"message": "Een kritieke fout heeft plaatsgevonden waardoor de cliënt zich aanmeldt vanaf het begin:\n\n84X\n\nDit is waarschijnlijk veroorzaakt door een script dat op een onverwachte manier faalt. Probeer de sollicitatie in veilige modus te starten en de kwestie aan te spreken."
},
"widget-error": {
"title": "Starten widget mislukt",
@@ -22,16 +22,7 @@
"bundle-error": {
"title": "Custom script laden mislukt",
"message": "Script van notitie met ID \"{{id}}\", getiteld \"{{title}}\" kon niet worden uitgevoerd vanwege:\n\n{{message}}"
},
"scripting-error": "Error met script: {{title}}",
"widget-list-error": {
"title": "Kon geen lijst met widgets ophalen van de server"
},
"widget-render-error": {
"title": "React-widget kon niet geladen worden"
},
"widget-missing-parent": "Widget heeft niet het verplichte '{{property}}'-veld gedefinieerd.\n\nAls dit script is bedoeld om zonder interface te draaien, gebruik dan in plaats daarvan '#run=frontendStartup'.",
"open-script-note": "Open scriptnotitie"
}
},
"add_link": {
"add_link": "Voeg link toe",
@@ -50,8 +41,7 @@
"help_on_tree_prefix": "Help bij boomvoorvoegsel",
"prefix": "Voorvoegsel: ",
"edit_branch_prefix_multiple": "Bewerk zijtakvoorvoegsel voor {{count}} zijtakken",
"branch_prefix_saved_multiple": "Vertakkingsvoorvoegsel opgeslagen voor {{count}} vertakkingen.",
"affected_branches": "Aangetaste takken ({{count}}):"
"branch_prefix_saved_multiple": "Vertakkingsvoorvoegsel opgeslagen voor {{count}} vertakkingen."
},
"bulk_actions": {
"bulk_actions": "Bulk acties",
@@ -64,8 +54,7 @@
"labels": "Labels",
"relations": "Relaties",
"notes": "Notities",
"other": "Andere",
"include_descendants": "Tel afstammelingen van de geselecteerde notities mee"
"other": "Andere"
},
"calendar": {
"april": "April",
@@ -89,35 +78,5 @@
},
"show_toc_widget_button": {
"show_toc": "Laat Inhoudsopgave zien"
},
"status_bar": {
"note_paths_one": "{{count}} pad",
"note_paths_other": "{{count}} paden",
"note_paths_title": "Notitiepaden",
"code_note_switcher": "Verander de taalmodus"
},
"attributes_panel": {
"title": "Notitie-attributen"
},
"right_pane": {
"empty_message": "Geen informatie voor deze notitie",
"empty_button": "Verberg dit paneel",
"toggle": "Schakel rechterpaneel in/uit",
"custom_widget_go_to_source": "Go naar de broncode"
},
"pdf": {
"attachments_one": "{{count}} bijlage",
"attachments_other": "{{count}} bijlagen",
"layers_one": "{{count}} laag",
"layers_other": "{{count}} lagen",
"pages_one": "{{count}} pagina",
"pages_other": "{{count}} pagina's",
"pages_alt": "Pagina {{pageNumber}}",
"pages_loading": "Laden..."
},
"clone_to": {
"clone_notes_to": "Kloon de notities naar...",
"help_on_links": "Hulp op links",
"notes_to_clone": "Notities om te klonen"
}
}

View File

@@ -22,16 +22,7 @@
"bundle-error": {
"title": "Falha para carregar o script customizado",
"message": "O script da nota com ID \"{{id}}\", intitulada \"{{title}}\", não pôde ser executado devido a:\n\n{{message}}"
},
"widget-list-error": {
"title": "Falha ao obter a lista de widgets do servidor"
},
"scripting-error": "Erro do script específicado: {{title}}",
"open-script-note": "Abrir script da nota",
"widget-render-error": {
"title": "Falha do renderizar um widget React personalizado"
},
"widget-missing-parent": "Widget adaptado não tem a propriedade '{{property}}' mandatória definida.\n\nSe este script é para ser executado sem um element de UI, usar '#run=frontendStartup'."
}
},
"add_link": {
"add_link": "Adicionar ligação",
@@ -48,10 +39,7 @@
"help_on_tree_prefix": "Ajuda sobre o prefixo da árvore de notas",
"prefix": "Prefixo: ",
"save": "Gravar",
"branch_prefix_saved": "O prefixo de ramificação foi gravado.",
"edit_branch_prefix_multiple": "Editar prefixo para {{count}} branches",
"branch_prefix_saved_multiple": "Prefixo dos branches foi editado para {{count}} branches.",
"affected_branches": "Alterados ({{count}}) branches:"
"branch_prefix_saved": "O prefixo de ramificação foi gravado."
},
"bulk_actions": {
"bulk_actions": "Ações em massa",
@@ -116,8 +104,7 @@
"export_status": "Estado da exportação",
"export_in_progress": "Exportação em andamento: {{progressCount}}",
"export_finished_successfully": "Exportação concluída com sucesso.",
"format_pdf": "PDF para impressão ou compartilhamento.",
"share-format": "HTML para publicação web - usa o mesmo tema que é usado para notas partilhadas, mas pode ser publicado como um site estatico."
"format_pdf": "PDF para impressão ou compartilhamento."
},
"help": {
"title": "Folha de Dicas",
@@ -171,8 +158,7 @@
"showSQLConsole": "mostrar console SQL",
"other": "Outros",
"quickSearch": "focar no campo de pesquisa rápida",
"inPageSearch": "pesquisa na página",
"editShortcuts": "Editar atalhos do teclado"
"inPageSearch": "pesquisa na página"
},
"import": {
"importIntoNote": "Importar para a nota",
@@ -198,8 +184,7 @@
},
"import-status": "Estado da importação",
"in-progress": "Importação em andamento: {{progress}}",
"successful": "Importação concluída com sucesso.",
"importZipRecommendation": "Quando a importar ficheiro ZIP, a hierarquia de notas vai reflectir a estrutura da sub directoria dentro do ficheiro."
"successful": "Importação concluída com sucesso."
},
"include_note": {
"dialog_title": "Incluir nota",
@@ -214,8 +199,7 @@
"info": {
"modalTitle": "Mensagem informativa",
"closeButton": "Fechar",
"okButton": "OK",
"copy_to_clipboard": "Copiar para a área de transferência"
"okButton": "OK"
},
"jump_to_note": {
"search_placeholder": "Pesquise uma nota pelo nome ou digite > para comandos...",
@@ -290,12 +274,7 @@
"download_button": "Descarregar",
"mime": "MIME: ",
"file_size": "Tamanho do ficheiro:",
"preview_not_available": "A visualização não está disponível para este tipo de nota.",
"diff_on": "Mostrar diferenças",
"diff_off": "Mostrar conteúdos",
"diff_on_hint": "Carregar para mostrar diferenças da fonte da nota",
"diff_off_hint": "Carregar para mostrar conteúdos da nota",
"diff_not_available": "Diferenças não disponível."
"preview_not_available": "A visualização não está disponível para este tipo de nota."
},
"sort_child_notes": {
"sort_children_by": "Ordenar notas filhas por...",
@@ -606,18 +585,7 @@
"september": "Setembro",
"october": "Outubro",
"november": "Novembro",
"december": "Dezembro",
"week": "Semana",
"week_previous": "Semana anterior",
"week_next": "Próxima semana",
"month": "Mês",
"month_previous": "Mês anterior",
"month_next": "Próximo mês",
"year": "Ano",
"year_previous": "Ano anterior",
"year_next": "Próximo ano",
"list": "Lista",
"today": "Hoje"
"december": "Dezembro"
},
"close_pane_button": {
"close_this_pane": "Fechar este painel"
@@ -660,9 +628,7 @@
"about": "Sobre o Trilium Notes",
"logout": "Sair",
"show-cheatsheet": "Exibir Cheatsheet",
"toggle-zen-mode": "Modo Zen",
"new-version-available": "Nova actualização disponível",
"download-update": "Obter versão {{latestVersion}}"
"toggle-zen-mode": "Modo Zen"
},
"zen_mode": {
"button_exit": "Sair do Modo Zen"
@@ -700,14 +666,7 @@
"convert_into_attachment_failed": "A conversão da nota '{{title}}' falhou.",
"convert_into_attachment_successful": "A nota '{{title}}' foi convertida para anexo.",
"convert_into_attachment_prompt": "Tem certeza que quer converter a nota '{{title}}' num anexo da nota pai?",
"print_pdf": "Exportar como PDF…",
"open_note_on_server": "Abrir nota no servidor",
"export_as_image": "Exportar como imagem",
"note_map": "Mapa de notas",
"advanced": "Avançadas",
"view_revisions": "Revisões da nota...",
"export_as_image_svg": "SVG (vectorial)",
"export_as_image_png": "PNG (matricial)"
"print_pdf": "Exportar como PDF…"
},
"onclick_button": {
"no_click_handler": "Componente de botão '{{componentId}}' não possui manipulador de clique definido"
@@ -753,29 +712,19 @@
"zpetne_odkazy": {
"relation": "relação",
"backlink_one": "{{count}} Ligação Reversa",
"backlink_many": "{{count}} Ligações Reversas",
"backlink_many": "",
"backlink_other": "{{count}} Ligações Reversas"
},
"mobile_detail_menu": {
"insert_child_note": "Inserir nota filha",
"delete_this_note": "Apagar esta nota",
"error_cannot_get_branch_id": "Não foi possível obter o branchId para o notePath '{{notePath}} '",
"error_unrecognized_command": "Comando não reconhecido {{command}}",
"note_revisions": "Revisões da nota"
"error_unrecognized_command": "Comando não reconhecido {{command}}"
},
"note_icon": {
"change_note_icon": "Alterar ícone da nota",
"search": "Pesquisa:",
"reset-default": "Redefinir para o ícone padrão",
"filter": "Filtrar",
"filter-none": "Todos os icons",
"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}}",
"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"
"reset-default": "Redefinir para o ícone padrão"
},
"basic_properties": {
"note_type": "Tipo da nota",
@@ -796,14 +745,7 @@
"calendar": "Calendário",
"table": "Tabela",
"geo-map": "Mapa geográfico",
"board": "Quadro",
"expand_first_level": "Expandir descendentes directos",
"presentation": "Apresentação",
"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.",
"hide_child_notes": "Esconder notas descendentes na árvore"
"board": "Quadro"
},
"edited_notes": {
"no_edited_notes_found": "Ainda não há nenhuma nota editada neste dia…",
@@ -836,8 +778,7 @@
},
"inherited_attribute_list": {
"title": "Atributos Herdados",
"no_inherited_attributes": "Nenhum atributo herdado.",
"none": "Nenhum"
"no_inherited_attributes": "Nenhum atributo herdado."
},
"note_info_widget": {
"note_id": "ID da Nota",
@@ -848,9 +789,7 @@
"note_size_info": "O tamanho da nota fornece uma estimativa aproximada dos requisitos de armazenamento para esta nota. Leva em conta o conteúdo e o conteúdo das suas revisões de nota.",
"calculate": "calcular",
"subtree_size": "(tamanho da subárvore: {{size}} em {{count}} notas)",
"title": "Informações da nota",
"mime": "Tipo MIME",
"show_similar_notes": "Mostrar notas semelhantes"
"title": "Informações da nota"
},
"note_map": {
"open_full": "Expandir completamente",
@@ -913,8 +852,7 @@
"search_parameters": "Parâmetros de Pesquisa",
"unknown_search_option": "Opção de pesquisa desconhecida {{searchOptionName}}",
"search_note_saved": "Nota de pesquisa foi gravada em {{- notePathTitle}}",
"actions_executed": "As ações foram executadas.",
"view_options": "Ver opções:"
"actions_executed": "As ações foram executadas."
},
"similar_notes": {
"title": "Notas Similares",
@@ -1008,22 +946,14 @@
"no_attachments": "Esta nota não possuí anexos."
},
"book": {
"no_children_help": "Esta coleção não possui nenhum nota filha, então não há nada para exibir. Veja <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> para pormenores.",
"drag_locked_title": "Bloqueado para edição",
"drag_locked_message": "Arrastar não permitida pois a coleção está bloqueada para edição."
"no_children_help": "Esta coleção não possui nenhum nota filha, então não há nada para exibir. Veja <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> para pormenores."
},
"editable_code": {
"placeholder": "Digite o conteúdo da sua nota de código aqui…"
},
"editable_text": {
"placeholder": "Digite o conteúdo da sua nota aqui…",
"auto-detect-language": "Detetado automaticamente",
"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_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."
"auto-detect-language": "Detetado automaticamente"
},
"empty": {
"open_note_instruction": "Abra uma nota a digitar o título da nota no campo abaixo ou escolha uma nota na árvore.",
@@ -1151,8 +1081,7 @@
"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",
"centerContent": "Manter conteúdo centrado"
"max_width_unit": "pixels"
},
"native_title_bar": {
"title": "Barra de Título Nativa (requer recarregar a app)",
@@ -1184,9 +1113,7 @@
"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-smooth-scroll": "Activar deslocamento suave",
"app-restart-required": "(é necessário reiniciar a aplicação para aplicar as alterações)"
"enable-backdrop-effects": "Ativar efeitos de fundo para menus, popups e painéis"
},
"ai_llm": {
"not_started": "Não iniciado",
@@ -1345,10 +1272,7 @@
"title": "Editor"
},
"code_mime_types": {
"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"
"title": "Tipos MIME disponíveis no dropdown"
},
"vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "Atribuições de teclas do Vim",
@@ -1468,13 +1392,7 @@
"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",
"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"
"formatting-locale": "Formato de data e número"
},
"backup": {
"automatic_backup": "Backup automático",
@@ -1567,8 +1485,7 @@
"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_missing_vars": "Configurações em falta: {{-variables}}"
"oauth_user_not_logged_in": "Não está logado!"
},
"shortcuts": {
"keyboard_shortcuts": "Atalhos de Teclado",
@@ -1668,12 +1585,7 @@
"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-a-new-window": "Abrir numa nova janela",
"archive": "Arquivar",
"unarchive": "Retirar do arquivo",
"hide-subtree": "Esconder sub-árvore",
"show-subtree": "Mostrar sub-árvore"
"open-in-popup": "Edição rápida"
},
"shared_info": {
"shared_publicly": "Esta nota é partilhada publicamente em {{- link}}.",
@@ -1734,13 +1646,7 @@
},
"highlights_list_2": {
"title": "Lista de Destaques",
"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"
"options": "Opções"
},
"quick-search": {
"placeholder": "Pesquisa rápida",
@@ -1763,43 +1669,16 @@
"refresh-saved-search-results": "Atualizar resultados de pesquisa gravados",
"create-child-note": "Criar nota filha",
"unhoist": "Desafixar",
"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}})"
"toggle-sidebar": "Alternar barra lateral"
},
"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}}'",
"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."
"could_not_find_typewidget": "Não foi possível encontrar typeWidget para o tipo '{{type}}'"
},
"note_title": {
"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"
"placeholder": "digite o título da nota aqui..."
},
"search_result": {
"no_notes_found": "Nenhuma nota encontrada para os parâmetros de pesquisa digitados.",
@@ -1828,8 +1707,7 @@
},
"toc": {
"table_of_contents": "Tabela de Conteúdos",
"options": "Opções",
"no_headings": "Sem cabeçalhos."
"options": "Opções"
},
"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>.",
@@ -1872,9 +1750,7 @@
"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.",
"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."
"encountered-error": "Encontrado o erro \"{{message}}\", verifique o console."
},
"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?"
@@ -1930,8 +1806,7 @@
"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_in_trilium": "A procurar \"{{term}}\" no Trilium"
"search_online": "Pesquisar por \"{{term}}\" com {{searchEngine}}"
},
"image_context_menu": {
"copy_reference_to_clipboard": "Copiar referência para a área de transferência",
@@ -1941,8 +1816,7 @@
"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_other_split": "Abrir nota noutro separador"
"open_note_in_popup": "Edição rápida"
},
"electron_integration": {
"desktop-application": "Aplicação Desktop",
@@ -1950,8 +1824,7 @@
"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",
"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."
"zoom-factor": "Fator de Zoom"
},
"note_autocomplete": {
"search-for": "Pesquisar por \"{{term}}\"",
@@ -2011,8 +1884,7 @@
},
"note_language": {
"not_set": "Não atribuído",
"configure-languages": "Configurar idiomas...",
"help-on-languages": "Ajuda nas linguagens de conteúdos..."
"configure-languages": "Configurar idiomas..."
},
"content_language": {
"title": "Idiomas do conteúdo",
@@ -2030,8 +1902,7 @@
"button_title": "Exportar diagrama como PNG"
},
"svg": {
"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."
"export_to_png": "O diagrama não pôde ser exportado como PNG."
},
"code_theme": {
"title": "Aparência",
@@ -2050,11 +1921,7 @@
"editorfeatures": {
"title": "Recursos",
"emoji_completion_enabled": "Ativar auto-completar de Emoji",
"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 `/`."
"note_completion_enabled": "Ativar auto-completar de notas"
},
"table_view": {
"new-row": "Nova linha",
@@ -2096,16 +1963,7 @@
"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",
"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."
"add-column": "Adicionar Coluna"
},
"command_palette": {
"tree-action-name": "Árvore: {{name}}",
@@ -2136,146 +1994,16 @@
"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",
"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."
"dismiss": "Dispensar"
},
"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",
"ui": "Interface do utilizador",
"ui_old_layout": "Layout antigo",
"ui_new_layout": "Nova aparência"
"related_code_notes": "Esquema de cores para notas de código"
},
"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}}, MIME: {{- mimeType}}",
"role_and_size": "Rol: {{role}}, dimensiune: {{size}}",
"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,8 +293,7 @@
"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",
"hide_child_notes": "Ascunde subnotițele din arbore"
"expand_all_levels": "Expandează pe toate nivelele"
},
"bookmark_switch": {
"bookmark": "Semn de carte",
@@ -570,7 +569,7 @@
"file_size": "Dimensiunea fișierului",
"file_type": "Tipul fișierului",
"note_id": "ID-ul notiței",
"open": "Deschide în exterior",
"open": "Deschide",
"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.",
@@ -796,8 +795,7 @@
},
"inherited_attribute_list": {
"no_inherited_attributes": "Niciun atribut moștenit.",
"title": "Atribute moștenite",
"none": "niciunul"
"title": "Atribute moștenite"
},
"jump_to_note": {
"search_button": "Caută în întregul conținut",
@@ -882,11 +880,7 @@
"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...",
"export_as_image": "Exportează ca imagine",
"export_as_image_png": "PNG (bitmap)",
"export_as_image_svg": "SVG (vectorial)",
"note_map": "Harta notițelor"
"view_revisions": "Revizii ale notițelor..."
},
"note_erasure_timeout": {
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
@@ -905,9 +899,7 @@
"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",
"mime": "Tip MIME",
"show_similar_notes": "Afișează notițe similare"
"type": "Tip"
},
"note_launcher": {
"this_launcher_doesnt_define_target_note": "Acesată scurtătură nu definește o notiță-destinație."
@@ -1165,8 +1157,7 @@
"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}}”",
"view_options": "Opțiuni de afișare:"
"unknown_search_option": "Opțiune de căutare necunoscută „{{searchOptionName}}”"
},
"search_engine": {
"baidu": "Baidu",
@@ -1318,17 +1309,8 @@
},
"bundle-error": {
"title": "Eroare la încărcarea unui script personalizat",
"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}}"
"message": "Scriptul din notița cu ID-ul „{{id}}”, întitulată „{{title}}” nu a putut fi executată din cauza:\n\n{{message}}"
}
},
"tray": {
"enable_tray": "Activează system tray-ul (este necesară repornirea aplicației pentru a avea efect)",
@@ -1435,10 +1417,7 @@
"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ă",
"open-in-a-new-window": "Deschide în fereastră nouă",
"hide-subtree": "Ascunde subnotițele",
"show-subtree": "Afișează subnotițele"
"unarchive": "Dezarhivează"
},
"shared_info": {
"help_link": "Pentru informații vizitați <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki-ul</a>.",
@@ -1499,27 +1478,12 @@
},
"highlights_list_2": {
"options": "Setări",
"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."
"title": "Listă de evidențieri"
},
"note_icon": {
"change_note_icon": "Schimbă iconița notiței",
"reset-default": "Resetează la iconița implicită",
"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ță."
"search": "Căutare:"
},
"show_highlights_list_widget_button": {
"show_highlights_list": "Afișează lista de evidențieri"
@@ -1557,17 +1521,7 @@
"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.",
"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."
"dropping-not-allowed": "Aici nu este permisă plasarea notițelor."
},
"title_bar_buttons": {
"window-on-top": "Menține fereastra mereu vizibilă"
@@ -1575,24 +1529,12 @@
"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...",
"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"
"printing_pdf": "Exportare ca PDF în curs..."
},
"note_title": {
"placeholder": "introduceți titlul notiței aici...",
"created_on": "Creată 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"
"last_modified": "Modificată la <Value />"
},
"revisions_snapshot_limit": {
"erase_excess_revision_snapshots": "Șterge acum reviziile excesive",
@@ -1613,11 +1555,7 @@
"configure_launchbar": "Configurează bara de lansare"
},
"sql_result": {
"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"
"no_rows": "Nu s-a găsit niciun rând pentru această interogare"
},
"sql_table_schemas": {
"tables": "Tabele"
@@ -1639,8 +1577,7 @@
},
"toc": {
"options": "Setări",
"table_of_contents": "Cuprins",
"no_headings": "Niciun titlu."
"table_of_contents": "Cuprins"
},
"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>.",
@@ -2075,7 +2012,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ă)",
@@ -2132,10 +2069,7 @@
"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",
"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"
"dismiss": "Treci peste"
},
"ui-performance": {
"title": "Setări de performanță",
@@ -2150,10 +2084,7 @@
},
"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",
"ui": "Interfață grafică",
"ui_old_layout": "Aspect vechi",
"ui_new_layout": "Aspect nou"
"related_code_notes": "Tema de culori pentru notițele de tip cod"
},
"units": {
"percentage": "%"
@@ -2209,77 +2140,6 @@
"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_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..."
"shared_locally": "Partajată local"
}
}

View File

@@ -797,8 +797,7 @@
"expand_tooltip": "展開此集合的直接子級(單層深度)。按下右側箭頭以查看更多選項。",
"expand_first_level": "展開直接子級",
"expand_nth_level": "展開 {{depth}} 層",
"expand_all_levels": "展開所有層級",
"hide_child_notes": "隱藏樹中的子筆記"
"expand_all_levels": "展開所有層級"
},
"edited_notes": {
"no_edited_notes_found": "今天還沒有編輯過的筆記...",
@@ -1467,10 +1466,7 @@
"duplicate": "複製副本",
"open-in-popup": "快速編輯",
"archive": "封存",
"unarchive": "解除封存",
"open-in-a-new-window": "在新視窗打開",
"hide-subtree": "隱藏子階層",
"show-subtree": "顯示子階層"
"unarchive": "解除封存"
},
"shared_info": {
"help_link": "如需幫助,請訪問 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>。",
@@ -1564,12 +1560,7 @@
"clone-indicator-tooltip": "此筆記有 {{- count}} 個父級:{{- parents}}",
"clone-indicator-tooltip-single": "此筆記已克隆(新增 1 個父級:{{- parent}}",
"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": "子筆記隱藏於此筆記的樹中。"
"shared-indicator-tooltip-with-url": "此筆記已公開分享至:{{- url}}"
},
"title_bar_buttons": {
"window-on-top": "保持此視窗置頂"
@@ -1603,11 +1594,7 @@
"configure_launchbar": "設定啟動欄"
},
"sql_result": {
"no_rows": "此次查詢沒有返回任何數據",
"not_executed": "查詢尚未執行。",
"failed": "SQL 查詢執行失敗",
"statement_result": "查詢結果",
"execute_now": "立即執行"
"no_rows": "此次查詢沒有返回任何數據"
},
"sql_table_schemas": {
"tables": "表"

View File

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

@@ -69,7 +69,7 @@ declare namespace Fancytree {
debug(msg: any): void;
/** Expand (or collapse) all parent nodes. */
expandAll(flag?: boolean, options?: object): void;
expandAll(flag?: boolean, options?: Object): void;
/** [ext-filter] Dimm or hide whole branches.
* @returns {integer} count
@@ -221,7 +221,6 @@ declare namespace Fancytree {
branchId: string;
isProtected: boolean;
noteType: NoteType;
subtreeHidden: boolean;
}
interface FancytreeNewNode extends FancytreeNodeData {
@@ -370,7 +369,7 @@ declare namespace Fancytree {
* @param mode 'before', 'after', or 'child' (default='child')
* @param init NodeData (or simple title string)
*/
editCreateNode(mode?: string, init?: object): void;
editCreateNode(mode?: string, init?: Object): void;
/** [ext-edit] Stop inline editing.
*
@@ -527,7 +526,7 @@ declare namespace Fancytree {
*
* @param opts passed to `setExpanded()`. Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true}
*/
makeVisible(opts?: object): JQueryPromise<any>;
makeVisible(opts?: Object): JQueryPromise<any>;
/** Move this node to targetNode.
*
@@ -590,25 +589,25 @@ declare namespace Fancytree {
* @param effects animation options.
* @param options {topNode: null, effects: ..., parent: ...} this node will remain visible in any case, even if `this` is outside the scroll pane.
*/
scrollIntoView(effects?: boolean, options?: object): JQueryPromise<any>;
scrollIntoView(effects?: boolean, options?: Object): JQueryPromise<any>;
/**
* @param effects animation options.
* @param options {topNode: null, effects: ..., parent: ...} this node will remain visible in any case, even if `this` is outside the scroll pane.
*/
scrollIntoView(effects?: object, options?: object): JQueryPromise<any>;
scrollIntoView(effects?: Object, options?: Object): JQueryPromise<any>;
/**
* @param flag pass false to deactivate
* @param opts additional options. Defaults to {noEvents: false}
*/
setActive(flag?: boolean, opts?: object): JQueryPromise<any>;
setActive(flag?: boolean, opts?: Object): JQueryPromise<any>;
/**
* @param flag pass false to collapse.
* @param opts additional options. Defaults to {noAnimation:false, noEvents:false}
*/
setExpanded(flag?: boolean, opts?: object): JQueryPromise<any>;
setExpanded(flag?: boolean, opts?: Object): JQueryPromise<any>;
/**
* Set keyboard focus to this node.
@@ -1110,7 +1109,7 @@ declare namespace Fancytree {
/** class names added to the node markup (separate with space) */
extraClasses?: string | undefined;
/** all properties from will be copied to `node.data` */
data?: object | undefined;
data?: Object | undefined;
/** Will be added as title attribute of the node's icon span,thus enabling a tooltip. */
iconTooltip?: string | undefined;
@@ -1161,7 +1160,7 @@ declare namespace Fancytree {
escapeHtml(s: string): string;
getEventTarget(event: Event): object;
getEventTarget(event: Event): Object;
getEventTargetType(event: Event): string;
@@ -1180,7 +1179,7 @@ declare namespace Fancytree {
parseHtml($ul: JQuery): NodeData[];
/** Add Fancytree extension definition to the list of globally available extensions. */
registerExtension(definition: object): void;
registerExtension(definition: Object): void;
unescapeHtml(s: string): string;

View File

@@ -1,4 +1,4 @@
import { IconRegistry, Locale } from "@triliumnext/commons";
import { IconRegistry } from "@triliumnext/commons";
import appContext, { AppContext } from "./components/app_context";
import type FNote from "./entities/fnote";
@@ -47,25 +47,14 @@ interface CustomGlobals {
platform?: typeof process.platform;
linter: typeof lint;
hasNativeTitleBar: boolean;
hasBackgroundEffects: boolean;
isElectron: boolean;
isRtl: boolean;
iconRegistry: IconRegistry;
themeCssUrl: string;
themeUseNextAsBase?: "next" | "next-light" | "next-dark";
iconPackCss: string;
headingStyle: "plain" | "underline" | "markdown";
layoutOrientation: "vertical" | "horizontal";
currentLocale: Locale;
}
type RequireMethod = (moduleName: string) => any;
declare global {
interface Window {
$: JQueryStatic;
jQuery: JQueryStatic;
logError(message: string);
logInfo(message: string);

View File

@@ -7,6 +7,7 @@ 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";
@@ -54,6 +55,7 @@ export const DESKTOP_FLOATING_BUTTONS: FloatingButtonsList = [
OpenTriliumApiDocsButton,
SaveToNoteButton,
RelationMapButtons,
GeoMapButtons,
CopyImageReferenceButton,
ExportImageButtons,
InAppHelpButton,
@@ -97,10 +99,9 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: F
/>;
}
function ToggleReadOnlyButton({ note, isDefaultViewMode }: FloatingButtonContext) {
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const isSavedSqlite = note.isTriliumSqlite() && !note.isHiddenCompletely();
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || isSavedSqlite)
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
&& note.isContentAvailable() && isDefaultViewMode;
return isEnabled && <FloatingButton
@@ -242,6 +243,17 @@ function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingB
);
}
function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonContext) {
const isEnabled = viewType === "geoMap" && !isReadOnly;
return isEnabled && (
<FloatingButton
icon="bx bx-plus-circle"
text={t("geo-map.create-child-note-title")}
onClick={() => triggerEvent("geoMapCreateChildNote")}
/>
);
}
function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonContext) {
const hiddenImageCopyRef = useRef<HTMLDivElement>(null);
const isEnabled = (
@@ -293,7 +305,7 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB
function InAppHelpButton({ note }: FloatingButtonContext) {
const helpUrl = getHelpUrlForNote(note);
const isEnabled = note.type !== "book" && !!helpUrl;
const isEnabled = !!helpUrl;
return isEnabled && (
<FloatingButton

View File

@@ -215,7 +215,7 @@ export default function NoteDetail() {
return (
<div
ref={containerRef}
class={`component note-detail ${isFullHeight ? "full-height" : ""}`}
class={`note-detail ${isFullHeight ? "full-height" : ""}`}
>
{Object.entries(noteTypesToRender).map(([ itemType, Element ]) => {
return <NoteDetailWrapper
@@ -265,13 +265,9 @@ 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);
@@ -322,8 +318,6 @@ 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") {
@@ -348,8 +342,9 @@ 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)
return (!noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
|| noteContext?.viewScope?.viewMode === "attachments"
|| isBackendNote;
}
@@ -363,8 +358,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,7 +217,6 @@ 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 @@
.note-list-widget {
min-height: 0;
max-width: var(--max-content-width); /* Inherited from .note-split */
overflow: auto;
contain: none !important;
}
@@ -11,6 +11,10 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
margin-inline: auto;
}
.note-list-widget .note-list {
padding-block: 10px;
}
.note-list-widget.full-height,
.note-list-widget.full-height .note-list-widget-content {
height: 100%;
@@ -21,12 +25,8 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
}
/* #region Pagination */
.note-list-pager {
font-size: 1rem;
span.current-page {
text-decoration: underline;
font-weight: bold;
}
.note-list-pager span.current-page {
text-decoration: underline;
font-weight: bold;
}
/* #endregion */
/* #endregion */

View File

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

View File

@@ -2,8 +2,7 @@
position: relative;
height: 100%;
user-select: none;
display: flex;
flex-direction: column;
overflow-x: auto;
--card-font-size: 0.9em;
--card-line-height: 1.2;
@@ -20,10 +19,8 @@ body.mobile .board-view {
height: 100%;
display: flex;
gap: 1em;
padding-inline: 12px;
padding-block: 4px;
padding: 1em;
align-items: flex-start;
overflow-x: auto;
}
.board-view-container .board-column {
@@ -355,4 +352,4 @@ body.mobile .board-view-container .board-column {
font-size: 0.9em;
max-width: 200px;
word-wrap: break-word;
}
}

View File

@@ -1,23 +1,20 @@
import "./index.css";
import { createContext, TargetedKeyboardEvent } from "preact";
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import FNote from "../../../entities/fnote";
import { t } from "../../../services/i18n";
import toast from "../../../services/toast";
import CollectionProperties from "../../note_bars/CollectionProperties";
import FormTextArea from "../../react/FormTextArea";
import FormTextBox from "../../react/FormTextBox";
import { ViewModeProps } from "../interface";
import "./index.css";
import { ColumnMap, getBoardData } from "./data";
import { useNoteLabelBoolean, useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
import Icon from "../../react/Icon";
import NoteAutocomplete from "../../react/NoteAutocomplete";
import { onWheelHorizontalScroll } from "../../widget_utils";
import { ViewModeProps } from "../interface";
import { t } from "../../../services/i18n";
import Api from "./api";
import BoardApi from "./api";
import FormTextBox from "../../react/FormTextBox";
import { createContext, TargetedKeyboardEvent } from "preact";
import { onWheelHorizontalScroll } from "../../widget_utils";
import Column from "./column";
import { ColumnMap, getBoardData } from "./data";
import BoardApi from "./api";
import FormTextArea from "../../react/FormTextArea";
import FNote from "../../../entities/fnote";
import NoteAutocomplete from "../../react/NoteAutocomplete";
import toast from "../../../services/toast";
export interface BoardViewData {
columns?: BoardColumnData[];
@@ -148,7 +145,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
const insertBefore = mouseX < columnMiddle;
// Calculate the target position
const targetIndex = insertBefore ? index : index + 1;
let targetIndex = insertBefore ? index : index + 1;
setColumnDropPosition(targetIndex);
}, [draggedColumn]);
@@ -162,14 +159,15 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
}, [draggedColumn, columnDropPosition, handleColumnDrop]);
return (
<div className="board-view">
<CollectionProperties note={parentNote} />
<div
className="board-view"
onWheel={onWheelHorizontalScroll}
>
<BoardViewContext.Provider value={boardViewContext}>
{byColumn && columns && <div
className="board-view-container"
onDragOver={handleColumnDragOver}
onDrop={handleContainerDrop}
onWheel={onWheelHorizontalScroll}
>
{columns.map((column, index) => (
<>
@@ -196,7 +194,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
</div>}
</BoardViewContext.Provider>
</div>
);
)
}
function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMode: boolean }) {
@@ -220,26 +218,26 @@ function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMo
tabIndex={300}
>
{!isCreatingNewColumn
? <>
<Icon icon="bx bx-plus" />{" "}
{t("board_view.add-column")}
</>
: (
<TitleEditor
placeholder={t("board_view.add-column-placeholder")}
save={async (columnName) => {
const created = await api.addNewColumn(columnName);
if (!created) {
toast.showMessage(t("board_view.column-already-exists"), undefined, "bx bx-duplicate");
}
}}
dismiss={() => setIsCreatingNewColumn(false)}
isNewItem
mode={isInRelationMode ? "relation" : "normal"}
/>
)}
? <>
<Icon icon="bx bx-plus" />{" "}
{t("board_view.add-column")}
</>
: (
<TitleEditor
placeholder={t("board_view.add-column-placeholder")}
save={async (columnName) => {
const created = await api.addNewColumn(columnName);
if (!created) {
toast.showMessage(t("board_view.column-already-exists"), undefined, "bx bx-duplicate");
}
}}
dismiss={() => setIsCreatingNewColumn(false)}
isNewItem
mode={isInRelationMode ? "relation" : "normal"}
/>
)}
</div>
);
)
}
export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, isNewItem }: {
@@ -304,26 +302,26 @@ export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, is
onBlur={onBlur}
/>
);
}
return (
<NoteAutocomplete
inputRef={inputRef}
noteId={currentValue ?? ""}
opts={{
hideAllButtons: true,
allowCreatingNotes: true
}}
onKeyDown={(e) => {
if (e.key === "Escape") {
} else {
return (
<NoteAutocomplete
inputRef={inputRef}
noteId={currentValue ?? ""}
opts={{
hideAllButtons: true,
allowCreatingNotes: true
}}
onKeyDown={(e) => {
if (e.key === "Escape") {
dismiss();
}
}}
onBlur={() => dismiss()}
noteIdChanged={(newValue) => {
save(newValue);
dismiss();
}
}}
onBlur={() => dismiss()}
noteIdChanged={(newValue) => {
save(newValue);
dismiss();
}}
/>
);
}}
/>
);
}
}

View File

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

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, componentId?: string) {
export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parentNote: FNote) {
e.preventDefault();
e.stopPropagation();
@@ -30,16 +30,16 @@ export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parent
}
if (branchIdToDelete) {
await branches.deleteNotes([ branchIdToDelete ], false, false, componentId);
await branches.deleteNotes([ branchIdToDelete ], false, false);
}
}
},
{ kind: "separator" },
{
kind: "custom",
componentFn: () => NoteColorPicker({note})
componentFn: () => NoteColorPicker({note: note})
}
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, e, note.noteId),
});
})
}

View File

@@ -1,10 +1,9 @@
import { EventInput, EventSourceFuncArg, EventSourceInput } from "@fullcalendar/core/index.js";
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";
import FNote from "../../../entities/fnote";
import server from "../../../services/server";
import clsx from "clsx";
interface Event {
startDate: string,
@@ -106,8 +105,7 @@ export async function buildEvent(note: FNote, { startDate, endDate, startTime, e
endDate = (endTime ? `${endDate}T${endTime}:00` : endDate);
const eventData: EventInput = {
id: note.noteId,
title,
title: title,
start: startDate,
url: `#${note.noteId}?popup`,
noteId: note.noteId,
@@ -150,12 +148,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

@@ -21,16 +21,7 @@
outline: 0;
height: 100%;
user-select: none;
padding: 0;
@media (max-width: 991px) {
padding: 0;
th {
font-weight: normal;
font-size: 0.9em;
}
}
padding: 10px;
}
.calendar-view a,
@@ -52,7 +43,6 @@
--fc-border-color: var(--main-border-color);
--fc-neutral-bg-color: var(--launcher-pane-background-color);
--fc-list-event-hover-bg-color: var(--left-pane-item-hover-background);
padding: 0 12px;
}
.calendar-container .fc-list-sticky .fc-list-day > * {
@@ -69,43 +59,41 @@
overflow: hidden;
}
.calendar-view .collection-properties {
.center-container {
justify-content: center;
.title {
min-width: 150px;
font-size: 1.2em;
text-align: center;
}
}
@media (max-width: 991px) {
>div {
justify-content: flex-start;
}
.right-container {
flex-grow: 0;
}
.center-container {
.title {
flex-grow: 1;
min-width: 110px;
font-size: 0.95em;
}
}
}
/* #region Header */
.calendar-view .calendar-header {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.calendar-view .calendar-header .btn {
min-width: unset !important;
}
.calendar-view .calendar-header > .title {
flex-grow: 1;
font-size: 1.3rem;
font-weight: normal;
}
body.desktop:not(.zen) .calendar-view .calendar-header {
padding-block-start: 4px;
padding-inline-end: 5em;
}
.search-result-widget-content .calendar-view .calendar-header {
padding-inline-end: unset !important;
}
/* #endregion */
/* #region Events */
/*
* week, month, year views
*/
.calendar-container a.fc-event {
.calendar-container a.fc-event {
text-decoration: none;
}
@@ -138,7 +126,7 @@
.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)
@@ -187,7 +175,7 @@
opacity: .75;
}
/*
/*
* List view
*/
@@ -200,4 +188,4 @@
--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, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import appContext from "../../../components/app_context";
import FNote from "../../../entities/fnote";
@@ -14,13 +14,9 @@ import dialog from "../../../services/dialog";
import froca from "../../../services/froca";
import { t } from "../../../services/i18n";
import { isMobile } from "../../../services/utils";
import CollectionProperties from "../../note_bars/CollectionProperties";
import ActionButton from "../../react/ActionButton";
import Button, { ButtonGroup } from "../../react/Button";
import Dropdown from "../../react/Dropdown";
import { FormListItem } from "../../react/FormList";
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";
@@ -44,28 +40,24 @@ const CALENDAR_VIEWS = [
{
type: "timeGridWeek",
name: t("calendar.week"),
icon: "bx bx-calendar-week",
previousText: t("calendar.week_previous"),
nextText: t("calendar.week_next")
},
{
type: "dayGridMonth",
name: t("calendar.month"),
icon: "bx bx-calendar",
previousText: t("calendar.month_previous"),
nextText: t("calendar.month_next")
},
{
type: "multiMonthYear",
name: t("calendar.year"),
icon: "bx bx-layer",
previousText: t("calendar.year_previous"),
nextText: t("calendar.year_next")
},
{
type: "listMonth",
name: t("calendar.list"),
icon: "bx bx-list-ol",
previousText: t("calendar.month_previous"),
nextText: t("calendar.month_next")
}
@@ -95,7 +87,6 @@ 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);
@@ -114,40 +105,32 @@ 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, parentComponent?.componentId);
const editingProps = useEditing(note, isEditable, isCalendarRoot, parentComponent?.componentId);
const { eventDidMount } = useEventDisplayCustomization(note);
const editingProps = useEditing(note, isEditable, isCalendarRoot);
// React to changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
const api = calendarRef.current;
if (!api) return;
// Subnote attribute change.
if (loadResults.getAttributeRows(parentComponent?.componentId).some((a) => noteIds.includes(a.noteId ?? ""))) {
if (loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) // note title change.
|| loadResults.getAttributeRows().some((a) => noteIds.includes(a.noteId ?? ""))) // subnote change.
{
// Defer execution after the load results are processed so that the event builder has the updated data to work with.
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);
setTimeout(() => {
calendarRef.current?.refetchEvents();
}, 0);
}
});
return (plugins &&
<div className="calendar-view" ref={containerRef} tabIndex={100}>
<CalendarCollectionProperties note={note} calendarRef={calendarRef} />
<CalendarHeader calendarRef={calendarRef} />
<Calendar
events={eventBuilder}
calendarRef={calendarRef}
@@ -176,67 +159,28 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
);
}
function CalendarCollectionProperties({ note, calendarRef }: {
note: FNote;
calendarRef: RefObject<FullCalendar>;
}) {
function CalendarHeader({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
const { title, viewType: currentViewType } = useOnDatesSet(calendarRef);
const currentViewData = CALENDAR_VIEWS.find(v => calendarRef.current && v.type === currentViewType);
const isMobileLocal = isMobile();
return (
<CollectionProperties
note={note}
centerChildren={<>
<ActionButton icon="bx bx-chevron-left" text={currentViewData?.previousText ?? ""} onClick={() => calendarRef.current?.prev()} />
<span className="title">{title}</span>
<ActionButton icon="bx bx-chevron-right" text={currentViewData?.nextText ?? ""} onClick={() => calendarRef.current?.next()} />
<Button text={t("calendar.today")} onClick={() => calendarRef.current?.today()} />
{isMobileLocal && <MobileCalendarViewSwitcher calendarRef={calendarRef} />}
</>}
rightChildren={<>
{!isMobileLocal && <DesktopCalendarViewSwitcher calendarRef={calendarRef} />}
</>}
/>
);
}
function DesktopCalendarViewSwitcher({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
const { viewType: currentViewType } = useOnDatesSet(calendarRef);
return (
<>
<div className="calendar-header">
<span className="title">{title}</span>
<ButtonGroup>
{CALENDAR_VIEWS.map(viewData => (
<Button
key={viewData.type}
text={viewData.name}
text={viewData.name.toLocaleLowerCase()}
className={currentViewType === viewData.type ? "active" : ""}
onClick={() => calendarRef.current?.changeView(viewData.type)}
/>
))}
</ButtonGroup>
</>
);
}
function MobileCalendarViewSwitcher({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
const { viewType: currentViewType } = useOnDatesSet(calendarRef);
const currentViewTypeData = CALENDAR_VIEWS.find(view => view.type === currentViewType);
return (
<Dropdown
text={currentViewTypeData?.name}
>
{CALENDAR_VIEWS.map(viewData => (
<FormListItem
key={viewData.type}
selected={currentViewType === viewData.type}
icon={viewData.icon}
onClick={() => calendarRef.current?.changeView(viewData.type)}
>{viewData.name}</FormListItem>
))}
</Dropdown>
<Button text={t("calendar.today").toLocaleLowerCase()} 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()} />
</ButtonGroup>
</div>
);
}
@@ -263,23 +207,22 @@ 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] ?? LOCALE_MAPPINGS[locale];
const correspondingLocale = LOCALE_MAPPINGS[formattingLocale];
if (correspondingLocale) {
correspondingLocale().then((locale) => setCalendarLocale(locale.default));
} else {
setCalendarLocale(undefined);
}
}, [formattingLocale, locale]);
});
return calendarLocale;
}
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, componentId: string | undefined) {
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
const onCalendarSelection = useCallback(async (e: DateSelectArg) => {
const { startDate, endDate } = parseStartEndDateFromEvent(e);
if (!startDate) return;
@@ -291,8 +234,8 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, c
return;
}
newEvent(note, { title, startDate, endDate, startTime, endTime, componentId });
}, [ note, componentId ]);
newEvent(note, { title, startDate, endDate, startTime, endTime });
}, [ note ]);
const onEventChange = useCallback(async (e: EventChangeArg) => {
const { startDate, endDate } = parseStartEndDateFromEvent(e.event);
@@ -301,8 +244,8 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, c
const { startTime, endTime } = parseStartEndTimeFromEvent(e.event);
const note = await froca.getNote(e.event.extendedProps.noteId);
if (!note) return;
changeEvent(note, { startDate, endDate, startTime, endTime, componentId });
}, [ componentId ]);
changeEvent(note, { startDate, endDate, startTime, endTime });
}, []);
// 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) => {
@@ -321,7 +264,7 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, c
};
}
function useEventDisplayCustomization(parentNote: FNote, componentId: string | undefined) {
function useEventDisplayCustomization(parentNote: FNote) {
const eventDidMount = useCallback((e: EventMountArg) => {
const { iconClass, promotedAttributes } = e.event.extendedProps;
@@ -378,7 +321,7 @@ function useEventDisplayCustomization(parentNote: FNote, componentId: string | u
const note = await froca.getNote(e.event.extendedProps.noteId);
if (!note) return;
openCalendarContextMenu(contextMenuEvent, note, parentNote, componentId);
openCalendarContextMenu(contextMenuEvent, note, parentNote);
}
if (isMobile()) {

View File

@@ -32,11 +32,9 @@ export function formatDateToLocalISO(date: Date | null | undefined) {
return undefined;
}
// Use local date methods directly - no timezone conversion needed
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
const offset = date.getTimezoneOffset();
const localDate = new Date(date.getTime() - offset * 60 * 1000);
return localDate.toISOString().split("T")[0];
}
export function offsetDate(date: Date | string | null | undefined, offset: number) {
@@ -44,15 +42,6 @@ export function offsetDate(date: Date | string | null | undefined, offset: numbe
return undefined;
}
// When given a date string (YYYY-MM-DD), parse as local time rather than UTC
// to avoid timezone-related off-by-one errors
if (typeof date === 'string') {
const [year, month, day] = date.split('-').map(Number);
const newDate = new Date(year, month - 1, day);
newDate.setDate(newDate.getDate() + offset);
return newDate;
}
const newDate = new Date(date);
newDate.setDate(newDate.getDate() + offset);
return newDate;

View File

@@ -2,13 +2,6 @@
overflow: hidden;
position: relative;
height: 100%;
display: flex;
flex-direction: column;
> .collection-properties {
position: relative;
z-index: 2000;
}
}
.geo-map-container {

View File

@@ -1,29 +1,24 @@
import Map from "./map";
import "./index.css";
import { ViewModeProps } from "../interface";
import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTriliumEvent } from "../../react/hooks";
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
import { divIcon, GPXOptions, LatLng, LeafletMouseEvent } from "leaflet";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import Marker, { GpxTrack } from "./marker";
import froca from "../../../services/froca";
import FNote from "../../../entities/fnote";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import appContext from "../../../components/app_context";
import FNote from "../../../entities/fnote";
import branches from "../../../services/branches";
import froca from "../../../services/froca";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast";
import CollectionProperties from "../../note_bars/CollectionProperties";
import ActionButton from "../../react/ActionButton";
import { ButtonOrActionButton } from "../../react/Button";
import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTriliumEvent } from "../../react/hooks";
import { ParentComponent } from "../../react/react_utils";
import TouchBar, { TouchBarButton, TouchBarSlider } from "../../react/TouchBar";
import { ViewModeProps } from "../interface";
import { createNewNote, moveMarker } from "./api";
import openContextMenu, { openMapContextMenu } from "./context_menu";
import Map from "./map";
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
import Marker, { GpxTrack } from "./marker";
import toast from "../../../services/toast";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import branches from "../../../services/branches";
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSlider } from "../../react/TouchBar";
import { ParentComponent } from "../../react/react_utils";
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
const DEFAULT_ZOOM = 2;
@@ -55,7 +50,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
}
}, 5000);
useEffect(() => { froca.getNotes(noteIds).then(setNotes); }, [ noteIds ]);
useEffect(() => { froca.getNotes(noteIds).then(setNotes) }, [ noteIds ]);
useEffect(() => {
if (!note) return;
@@ -65,7 +60,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
// Note creation.
useTriliumEvent("geoMapCreateChildNote", () => {
toast.showPersistent({
toast.showPersistent({
icon: "plus",
id: "geo-new-note",
title: "New note",
@@ -135,19 +130,6 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
return (
<div className={`geo-view ${state === State.NewNote ? "placing-note" : ""}`}>
<CollectionProperties
note={note}
rightChildren={<>
<ToggleReadOnlyButton note={note} />
<ButtonOrActionButton
icon="bx bx-plus"
text={t("geo-map.create-child-note-text")}
title={t("geo-map.create-child-note-title")}
triggerCommand="geoMapCreateChildNote"
disabled={isReadOnly}
/>
</>}
/>
{ coordinates !== undefined && zoom !== undefined && <Map
apiRef={apiRef} containerRef={containerRef}
coordinates={coordinates}
@@ -169,16 +151,6 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
);
}
function ToggleReadOnlyButton({ note }: { note: FNote }) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
return <ActionButton
text={isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")}
icon={isReadOnly ? "bx bx-lock-open-alt" : "bx bx-lock-alt"}
onClick={() => setReadOnly(!isReadOnly)}
/>;
}
function NoteWrapper({ note, isReadOnly }: { note: FNote, isReadOnly: boolean }) {
const mime = useNoteProperty(note, "mime");
const [ location ] = useNoteLabel(note, LOCATION_ATTRIBUTE);
@@ -232,7 +204,7 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
onDragged={editable ? onDragged : undefined}
onClick={!editable ? onClick : undefined}
onContextMenu={onContextMenu}
/>;
/>
}
function NoteGpxTrack({ note }: { note: FNote }) {
@@ -266,7 +238,7 @@ function NoteGpxTrack({ note }: { note: FNote }) {
color: note.getLabelValue("color") ?? "blue"
}
}), [ color, iconClass ]);
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />;
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />
}
function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) {
@@ -320,5 +292,5 @@ function GeoMapTouchBar({ state, map }: { state: State, map: L.Map | null | unde
enabled={state === State.Normal}
/>
</TouchBar>
);
)
}

View File

@@ -7,8 +7,7 @@ import attribute_renderer from "../../../services/attribute_renderer";
import content_renderer from "../../../services/content_renderer";
import { t } from "../../../services/i18n";
import link from "../../../services/link";
import CollectionProperties from "../../note_bars/CollectionProperties";
import { useImperativeSearchHighlighlighting, useNoteLabel, useNoteLabelBoolean, useNoteProperty } from "../../react/hooks";
import { useImperativeSearchHighlighlighting, useNoteLabel, useNoteLabelBoolean } from "../../react/hooks";
import Icon from "../../react/Icon";
import NoteLink from "../../react/NoteLink";
import { ViewModeProps } from "../interface";
@@ -20,18 +19,11 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
const { pageNotes, ...pagination } = usePagination(note, noteIds);
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
const noteType = useNoteProperty(note, "type");
const hasCollectionProperties = [ "book", "search" ].includes(noteType ?? "");
return (
<div class="note-list list-view">
<CollectionProperties
note={note}
centerChildren={<Pager {...pagination} />}
/>
{ noteIds.length > 0 && <div class="note-list-wrapper">
{!hasCollectionProperties && <Pager {...pagination} />}
<Pager {...pagination} />
<div class="note-list-container use-tn-links">
{pageNotes?.map(childNote => (
@@ -52,23 +44,15 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
export function GridView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
const { pageNotes, ...pagination } = usePagination(note, noteIds);
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
const noteType = useNoteProperty(note, "type");
const hasCollectionProperties = [ "book", "search" ].includes(noteType ?? "");
return (
<div class="note-list grid-view">
<CollectionProperties
note={note}
centerChildren={<Pager {...pagination} />}
/>
<div class="note-list-wrapper">
{!hasCollectionProperties && <Pager {...pagination} />}
<Pager {...pagination} />
<div class="note-list-container use-tn-links">
{pageNotes?.map(childNote => (
<GridNoteCard note={childNote} parentNote={note} highlightedTokens={highlightedTokens} includeArchived={includeArchived} />
<GridNoteCard note={childNote} parentNote={note} highlightedTokens={highlightedTokens} />
))}
</div>
@@ -110,16 +94,14 @@ function ListNoteCard({ note, parentNote, highlightedTokens, currentLevel, expan
</h5>
{isExpanded && <>
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList includeArchivedNotes={includeArchived} />
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList />
<NoteChildren note={note} parentNote={parentNote} highlightedTokens={highlightedTokens} currentLevel={currentLevel} expandDepth={expandDepth} includeArchived={includeArchived} />
</>}
</div>
);
}
function GridNoteCard({ note, parentNote, highlightedTokens, includeArchived }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined, includeArchived: boolean }) {
const titleRef = useRef<HTMLSpanElement>(null);
const [ noteTitle, setNoteTitle ] = useState<string>();
function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined }) {
const notePath = getNotePath(parentNote, note);
return (
@@ -138,7 +120,6 @@ function GridNoteCard({ note, parentNote, highlightedTokens, includeArchived }:
note={note}
trim
highlightedTokens={highlightedTokens}
includeArchivedNotes={includeArchived}
/>
</div>
);
@@ -155,22 +136,14 @@ function NoteAttributes({ note }: { note: FNote }) {
return <span className="note-list-attributes" ref={ref} />;
}
export function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: {
note: FNote;
trim?: boolean;
noChildrenList?: boolean;
highlightedTokens: string[] | null | undefined;
includeArchivedNotes: boolean;
}) {
function NoteContent({ note, trim, noChildrenList, highlightedTokens }: { note: FNote, trim?: boolean, noChildrenList?: boolean, highlightedTokens: string[] | null | undefined }) {
const contentRef = useRef<HTMLDivElement>(null);
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
useEffect(() => {
content_renderer.getRenderedContent(note, {
trim,
noChildrenList,
noIncludedNotes: true,
includeArchivedNotes
noChildrenList
})
.then(({ $renderedContent, type }) => {
if (!contentRef.current) return;

View File

@@ -1,7 +1,11 @@
.presentation-view {
display: flex;
flex-direction: column;
height: 100%;
.presentation-button-bar {
position: absolute;
top: 1em;
right: 1em;
.floating-buttons-children {
top: 0;
}
}
.presentation-container {

View File

@@ -1,21 +1,18 @@
import "./index.css";
import { RefObject } from "preact";
import { ViewModeMedia, ViewModeProps } from "../interface";
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
import Reveal from "reveal.js";
import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw";
import { openInCurrentNoteContext } from "../../../components/note_context";
import FNote from "../../../entities/fnote";
import { t } from "../../../services/i18n";
import CollectionProperties from "../../note_bars/CollectionProperties";
import ActionButton from "../../react/ActionButton";
import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
import ShadowDom from "../../react/ShadowDom";
import { ViewModeMedia, ViewModeProps } from "../interface";
import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model";
import slideCustomStylesheet from "./slidejs.css?raw";
import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model";
import ShadowDom from "../../react/ShadowDom";
import ActionButton from "../../react/ActionButton";
import "./index.css";
import { RefObject } from "preact";
import { openInCurrentNoteContext } from "../../../components/note_context";
import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
import { t } from "../../../services/i18n";
import { DEFAULT_THEME, loadPresentationTheme } from "./themes";
import FNote from "../../../entities/fnote";
export default function PresentationView({ note, noteIds, media, onReady, onProgressChanged }: ViewModeProps<{}>) {
const [ presentation, setPresentation ] = useState<PresentationModel>();
@@ -54,17 +51,14 @@ export default function PresentationView({ note, noteIds, media, onReady, onProg
if (media === "screen") {
return (
<div class="presentation-view">
<CollectionProperties
note={note}
rightChildren={<ButtonOverlay containerRef={containerRef} api={api} />}
/>
<>
<ShadowDom
className="presentation-container"
containerRef={containerRef}
>{content}</ShadowDom>
</div>
);
<ButtonOverlay containerRef={containerRef} api={api} />
</>
)
} else if (media === "print") {
// Printing needs a query parameter that is read by Reveal.js.
const url = new URL(window.location.href);
@@ -114,34 +108,42 @@ function ButtonOverlay({ containerRef, api }: { containerRef: RefObject<HTMLDivE
}, [ api ]);
return (
<>
<ActionButton
icon="bx bx-edit"
text={t("presentation_view.edit-slide")}
onClick={e => {
const currentSlide = api?.getCurrentSlide();
const noteId = getNoteIdFromSlide(currentSlide);
<div className="presentation-button-bar">
<div className="floating-buttons-children">
<ActionButton
className="floating-button"
icon="bx bx-edit"
text={t("presentation_view.edit-slide")}
noIconActionClass
onClick={e => {
const currentSlide = api?.getCurrentSlide();
const noteId = getNoteIdFromSlide(currentSlide);
if (noteId) {
openInCurrentNoteContext(e, noteId);
}
}}
/>
if (noteId) {
openInCurrentNoteContext(e, noteId);
}
}}
/>
<ActionButton
icon="bx bx-grid-horizontal"
text={t("presentation_view.slide-overview")}
active={isOverviewActive}
onClick={() => api?.toggleOverview()}
/>
<ActionButton
className="floating-button"
icon="bx bx-grid-horizontal"
text={t("presentation_view.slide-overview")}
active={isOverviewActive}
noIconActionClass
onClick={() => api?.toggleOverview()}
/>
<ActionButton
icon="bx bx-fullscreen"
text={t("presentation_view.start-presentation")}
onClick={() => containerRef.current?.requestFullscreen()}
/>
</>
);
<ActionButton
className="floating-button"
icon="bx bx-fullscreen"
text={t("presentation_view.start-presentation")}
noIconActionClass
onClick={() => containerRef.current?.requestFullscreen()}
/>
</div>
</div>
)
}
function Presentation({ presentation, setApi } : { presentation: PresentationModel, setApi: (api: Reveal.Api | undefined) => void }) {
@@ -177,7 +179,7 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod
api.destroy();
setRevealApi(undefined);
setApi(undefined);
};
}
}, []);
useEffect(() => {
@@ -189,19 +191,19 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod
<div className="slides">
{presentation.slides?.map(slide => {
if (!slide.verticalSlides) {
return <Slide key={slide.noteId} slide={slide} />;
return <Slide key={slide.noteId} slide={slide} />
} else {
return (
<section>
<Slide key={slide.noteId} slide={slide} />
{slide.verticalSlides.map(slide => <Slide key={slide.noteId} slide={slide} /> )}
</section>
);
}
return (
<section>
<Slide key={slide.noteId} slide={slide} />
{slide.verticalSlides.map(slide => <Slide key={slide.noteId} slide={slide} /> )}
</section>
);
})}
</div>
</div>
);
)
}

View File

@@ -3,12 +3,7 @@
position: relative;
height: 100%;
user-select: none;
display: flex;
flex-direction: column;
.tabulator-tableholder {
height: unset !important;
}
padding: 0 5px 0 10px;
}
.table-view-container {
@@ -73,4 +68,4 @@
inset-inline-start: 0;
font-size: 1.5em;
transform: translateY(-50%);
}
}

View File

@@ -1,22 +1,20 @@
import "./index.css";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { DataTreeModule, EditModule, FormatModule, FrozenColumnsModule, InteractionModule, MoveColumnsModule, MoveRowsModule, Options, PersistenceModule, ResizeColumnsModule, RowComponent,SortModule, Tabulator as VanillaTabulator} from 'tabulator-tables';
import { t } from "../../../services/i18n";
import SpacedUpdate from "../../../services/spaced_update";
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
import CollectionProperties from "../../note_bars/CollectionProperties";
import { ButtonOrActionButton } from "../../react/Button";
import { useLegacyWidget } from "../../react/hooks";
import { ParentComponent } from "../../react/react_utils";
import { ViewModeProps } from "../interface";
import useColTableEditing from "./col_editing";
import { useContextMenu } from "./context_menu";
import useData, { TableConfig } from "./data";
import useRowTableEditing from "./row_editing";
import { TableData } from "./rows";
import { useLegacyWidget } from "../../react/hooks";
import Tabulator from "./tabulator";
import { Tabulator as VanillaTabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule, Options, RowComponent} from 'tabulator-tables';
import { useContextMenu } from "./context_menu";
import { ParentComponent } from "../../react/react_utils";
import FNote from "../../../entities/fnote";
import { t } from "../../../services/i18n";
import Button from "../../react/Button";
import "./index.css";
import useRowTableEditing from "./row_editing";
import useColTableEditing from "./col_editing";
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
import SpacedUpdate from "../../../services/spaced_update";
import useData, { TableConfig } from "./data";
export default function TableView({ note, noteIds, notePath, viewConfig, saveConfig }: ViewModeProps<TableConfig>) {
const tabulatorRef = useRef<VanillaTabulator>(null);
@@ -38,7 +36,7 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
dataTreeChildIndent: 20,
dataTreeExpandElement: `<button class="tree-expand"><span class="bx bx-chevron-right"></span></button>`,
dataTreeCollapseElement: `<button class="tree-collapse"><span class="bx bx-chevron-down"></span></button>`
};
}
}, [ hasChildren ]);
const rowFormatter = useCallback((row: RowComponent) => {
@@ -48,16 +46,6 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
return (
<div className="table-view">
<CollectionProperties
note={note}
rightChildren={note.type !== "search" &&
<>
<ButtonOrActionButton triggerCommand="addNewRow" icon="bx bx-plus" text={t("table_view.new-row")} />
<ButtonOrActionButton triggerCommand="addNewTableColumn" icon="bx bx-carousel" text={t("table_view.new-column")} />
</>
}
/>
{rowData !== undefined && persistenceProps && (
<>
<Tabulator
@@ -66,6 +54,7 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
columns={columnDefs ?? []}
data={rowData}
modules={[ SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule ]}
footerElement={<TableFooter note={note} />}
events={{
...contextMenuEvents,
...rowEditingEvents
@@ -78,11 +67,24 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
rowFormatter={rowFormatter}
{...dataTreeProps}
/>
<TableFooter note={note} />
</>
)}
{attributeDetailWidgetEl}
</div>
);
)
}
function TableFooter({ note }: { note: FNote }) {
return (note.type !== "search" &&
<div className="tabulator-footer">
<div className="tabulator-footer-contents">
<Button triggerCommand="addNewRow" icon="bx bx-plus" text={t("table_view.new-row")} />
{" "}
<Button triggerCommand="addNewTableColumn" icon="bx bx-carousel" text={t("table_view.new-column")} />
</div>
</div>
)
}
function usePersistence(viewConfig: TableConfig | null | undefined, saveConfig: (newConfig: TableConfig) => void) {

View File

@@ -1,20 +1,18 @@
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 { 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";
import { JSX } from "preact/jsx-runtime";
import { isValidElement, RefObject } from "preact";
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;
}
@@ -45,9 +43,7 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
tabulator.on("tableBuilt", () => {
tabulatorRef.current = tabulator;
if (externalTabulatorRef) {
externalTabulatorRef.current = tabulator;
}
externalTabulatorRef.current = tabulator;
onReady?.();
});
@@ -66,15 +62,12 @@ 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(() => {
if (!columns) return;
tabulatorRef.current?.setColumns(columns);
}, [ columns ]);
useEffect(() => { tabulatorRef.current?.setData(data) }, [ data ]);
useEffect(() => { columns && tabulatorRef.current?.setColumns(columns)}, [ data]);
return (
<div ref={containerRef} className={className} />

View File

@@ -5,7 +5,7 @@
> .inline-title,
> .note-detail > .note-detail-editable-text,
> .note-list-widget:not(.full-height) .note-list-wrapper {
> .note-list-widget:not(.full-height) {
padding-inline: 24px;
}

View File

@@ -82,10 +82,6 @@ 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%;
@@ -110,4 +106,4 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
margin: 0;
border-radius: 0;
}
}
}

View File

@@ -1,7 +1,7 @@
import appContext from "../../components/app_context";
import { t } from "../../services/i18n";
import options from "../../services/options";
import utils, { isMac } from "../../services/utils";
import utils from "../../services/utils";
/**
* A "call-to-action" is an interactive message for the user, generally to present new features.
@@ -41,6 +41,10 @@ export interface CallToAction {
}[];
}
function isNextTheme() {
return [ "next", "next-light", "next-dark" ].includes(options.get("theme"));
}
const CALL_TO_ACTIONS: CallToAction[] = [
{
id: "new_layout",
@@ -59,7 +63,7 @@ const CALL_TO_ACTIONS: CallToAction[] = [
id: "background_effects",
title: t("call_to_action.background_effects_title"),
message: t("call_to_action.background_effects_message"),
enabled: () => (isMac() && !options.is("backgroundEffects")),
enabled: () => false,
buttons: [
{
text: t("call_to_action.background_effects_button"),
@@ -74,7 +78,7 @@ const CALL_TO_ACTIONS: CallToAction[] = [
id: "next_theme",
title: t("call_to_action.next_theme_title"),
message: t("call_to_action.next_theme_message"),
enabled: () => ![ "next", "next-light", "next-dark" ].includes(options.get("theme")),
enabled: () => !isNextTheme(),
buttons: [
{
text: t("call_to_action.next_theme_button"),

View File

@@ -1,19 +1,17 @@
import { useCallback, useLayoutEffect, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import froca from "../../services/froca";
import { isDesktop, isMobile } from "../../services/utils";
import TabSwitcher from "../mobile_widgets/TabSwitcher";
import CalendarWidget from "./CalendarWidget";
import SpacerWidget from "./SpacerWidget";
import BookmarkButtons from "./BookmarkButtons";
import ProtectedSessionStatusWidget from "./ProtectedSessionStatusWidget";
import SyncStatus from "./SyncStatus";
import HistoryNavigationButton from "./HistoryNavigation";
import { AiChatButton, CommandButton, CustomWidget, NoteLauncher, QuickSearchLauncherWidget, ScriptLauncher, TodayLauncher } from "./LauncherDefinitions";
import { useTriliumEvent } from "../react/hooks";
import { onWheelHorizontalScroll } from "../widget_utils";
import BookmarkButtons from "./BookmarkButtons";
import CalendarWidget from "./CalendarWidget";
import HistoryNavigationButton from "./HistoryNavigation";
import { LaunchBarContext } from "./launch_bar_widgets";
import { AiChatButton, CommandButton, CustomWidget, NoteLauncher, QuickSearchLauncherWidget, ScriptLauncher, TodayLauncher } from "./LauncherDefinitions";
import ProtectedSessionStatusWidget from "./ProtectedSessionStatusWidget";
import SpacerWidget from "./SpacerWidget";
import SyncStatus from "./SyncStatus";
export default function LauncherContainer({ isHorizontalLayout }: { isHorizontalLayout: boolean }) {
const childNotes = useLauncherChildNotes();
@@ -36,19 +34,18 @@ export default function LauncherContainer({ isHorizontalLayout }: { isHorizontal
}}>
{childNotes?.map(childNote => {
if (childNote.type !== "launcher") {
console.warn(`Note '${childNote.noteId}' '${childNote.title}' is not a launcher even though it's in the launcher subtree`);
return false;
throw new Error(`Note '${childNote.noteId}' '${childNote.title}' is not a launcher even though it's in the launcher subtree`);
}
if (!isDesktop() && childNote.isLabelTruthy("desktopOnly")) {
return false;
}
return <Launcher key={childNote.noteId} note={childNote} isHorizontalLayout={isHorizontalLayout} />;
return <Launcher key={childNote.noteId} note={childNote} isHorizontalLayout={isHorizontalLayout} />
})}
</LaunchBarContext.Provider>
</div>
);
)
}
function Launcher({ note, isHorizontalLayout }: { note: FNote, isHorizontalLayout: boolean }) {
@@ -75,7 +72,7 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) {
const builtinWidget = note.getLabelValue("builtinWidget");
switch (builtinWidget) {
case "calendar":
return <CalendarWidget launcherNote={note} />;
return <CalendarWidget launcherNote={note} />
case "spacer":
// || has to be inside since 0 is a valid value
const baseSize = parseInt(note.getLabelValue("baseSize") || "40");
@@ -89,17 +86,15 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) {
case "syncStatus":
return <SyncStatus />;
case "backInHistoryButton":
return <HistoryNavigationButton launcherNote={note} command="backInNoteHistory" />;
return <HistoryNavigationButton launcherNote={note} command="backInNoteHistory" />
case "forwardInHistoryButton":
return <HistoryNavigationButton launcherNote={note} command="forwardInNoteHistory" />;
return <HistoryNavigationButton launcherNote={note} command="forwardInNoteHistory" />
case "todayInJournal":
return <TodayLauncher launcherNote={note} />;
return <TodayLauncher launcherNote={note} />
case "quickSearch":
return <QuickSearchLauncherWidget />;
return <QuickSearchLauncherWidget />
case "aiChatLauncher":
return <AiChatButton launcherNote={note} />;
case "mobileTabSwitcher":
return <TabSwitcher />;
return <AiChatButton launcherNote={note} />
default:
throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`);
}

View File

@@ -1,4 +1,3 @@
import clsx from "clsx";
import { createContext } from "preact";
import { useContext } from "preact/hooks";
@@ -19,12 +18,12 @@ export interface LauncherNoteProps {
launcherNote: FNote;
}
export function LaunchBarActionButton({ className, ...props }: Omit<ActionButtonProps, "noIconActionClass" | "titlePosition">) {
export function LaunchBarActionButton(props: Omit<ActionButtonProps, "className" | "noIconActionClass" | "titlePosition">) {
const { isHorizontalLayout } = useContext(LaunchBarContext);
return (
<ActionButton
className={clsx("button-widget launcher-button", className)}
className="button-widget launcher-button"
noIconActionClass
titlePosition={isHorizontalLayout ? "bottom" : "right"}
{...props}

View File

@@ -5,10 +5,8 @@ import { Tooltip } from "bootstrap";
import clsx from "clsx";
import { ComponentChild } from "preact";
import { useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import type React from "react";
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";
@@ -24,12 +22,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, type, viewScope));
const [ shown, setShown ] = useState(shouldShow(note?.noteId, type, viewScope));
const containerRef = useRef<HTMLDivElement>(null);
const [ titleHidden, setTitleHidden ] = useState(false);
useLayoutEffect(() => {
setShown(shouldShow(note, type, viewScope));
setShown(shouldShow(note?.noteId, type, viewScope));
}, [ note, type, viewScope ]);
useLayoutEffect(() => {
@@ -71,10 +69,9 @@ export default function InlineTitle() {
);
}
function shouldShow(note: FNote | null | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
function shouldShow(noteId: string | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
if (viewScope?.viewMode !== "default") return false;
if (note?.noteId?.startsWith("_options")) return true;
if (note?.isTriliumSqlite()) return false;
if (noteId?.startsWith("_options")) return true;
return type && supportedNoteTypes.has(type);
}

View File

@@ -26,6 +26,7 @@ export default function NoteTitleActions() {
<div className="title-actions">
<PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />
{noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />}
{!isHiddenNote && note && noteType === "book" && <CollectionProperties note={note} />}
<EditedNotes />
<NoteTypeSwitcher />
</div>

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) && !note?.isTriliumSqlite() &&
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
<div
className="note-type-switcher"
onWheel={onWheelHorizontalScroll}

View File

@@ -1,6 +1,6 @@
import "./StatusBar.css";
import { Locale, NOTE_TYPE_ICONS, NoteType } from "@triliumnext/commons";
import { Locale, 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 from "../../entities/fnote";
import FNote, { NOTE_TYPE_ICONS } from "../../entities/fnote";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import { ViewScope } from "../../services/link";
@@ -338,19 +338,19 @@ interface AttributesProps extends StatusBarContext {
function AttributesButton({ note, attributesShown, setAttributesShown }: AttributesProps) {
const [ count, setCount ] = useState(note.attributes.length);
const getAttributeCount = useCallback((note: FNote) => {
const refreshCount = useCallback((note: FNote) => {
return note.getAttributes().filter(a => !a.isAutoLink).length;
}, []);
// React to note changes.
useEffect(() => {
setCount(getAttributeCount(note));
}, [ note, getAttributeCount ]);
setCount(refreshCount(note));
}, [ note, refreshCount ]);
// React to changes in count.
useTriliumEvent("entitiesReloaded", (({loadResults}) => {
if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) {
setCount(getAttributeCount(note));
setCount(refreshCount(note));
}
}));

View File

@@ -1,133 +0,0 @@
#launcher-container .mobile-tab-switcher {
position: relative;
&::after {
content: attr(data-tab-count);
font-family: var(--main-font-family);
font-size: 10px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.modal.tab-bar-modal {
.modal-dialog {
min-height: 85vh;
}
.tabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1em;
@media (min-width: 850px) {
grid-template-columns: 1fr 1fr 1fr;
}
.tab-card {
background: var(--card-background-color);
border-radius: 1em;
min-width: 0;
overflow: hidden;
height: 200px;
display: flex;
flex-direction: column;
&.with-hue {
background-color: hsl(var(--bg-hue), 8.8%, 11.2%);
border-color: hsl(var(--bg-hue), 9.4%, 25.1%);
}
&.active {
outline: 4px solid var(--more-accented-background-color);
background: var(--card-background-hover-color);
.title {
font-weight: bold;
}
}
header {
padding: 0.4em 0.5em;
border-bottom: 1px solid rgba(150, 150, 150, 0.1);
display: flex;
overflow: hidden;
align-items: center;
color: var(--custom-color, inherit);
flex-shrink: 0;
&:not(:first-of-type) {
border-top: 1px solid rgba(150, 150, 150, 0.1);
}
>.tn-icon {
margin-inline-end: 0.4em;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
font-size: 0.9em;
flex-grow: 1;
}
.icon-action {
flex-shrink: 0;
}
}
.tab-preview {
flex-grow: 1;
height: 100%;
overflow: hidden;
font-size: 0.5em;
user-select: none;
pointer-events: none;
&.type-text {
padding: 10px;
}
&.type-book,
&.type-contentWidget,
&.type-search,
&.type-empty {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25em;
color: var(--muted-text-color);
}
.preview-placeholder {
font-size: 500%;
}
p { margin-bottom: 0.2em;}
h2 { font-size: 1.20em; }
h3 { font-size: 1.15em; }
h4 { font-size: 1.10em; }
h5 { font-size: 1.05em}
h6 { font-size: 1em; }
}
&.with-split {
.preview-placeholder {
font-size: 250%;
}
}
}
}
.modal-footer {
.tn-link {
color: var(--main-text-color);
width: 40%;
text-align: center;
text-decoration: none;
}
}
}

View File

@@ -1,240 +0,0 @@
import "./TabSwitcher.css";
import clsx from "clsx";
import { createPortal, Fragment } from "preact/compat";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import appContext, { CommandNames } from "../../components/app_context";
import NoteContext from "../../components/note_context";
import FNote from "../../entities/fnote";
import contextMenu from "../../menus/context_menu";
import { getHue, parseColor } from "../../services/css_class_manager";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import { NoteContent } from "../collections/legacy/ListOrGridView";
import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets";
import { ICON_MAPPINGS } from "../note_bars/CollectionProperties";
import ActionButton from "../react/ActionButton";
import { useActiveNoteContext, useNoteIcon, useTriliumEvents } from "../react/hooks";
import Icon from "../react/Icon";
import LinkButton from "../react/LinkButton";
import Modal from "../react/Modal";
export default function TabSwitcher() {
const [ shown, setShown ] = useState(false);
const mainNoteContexts = useMainNoteContexts();
return (
<>
<LaunchBarActionButton
className="mobile-tab-switcher"
icon="bx bx-rectangle"
text="Tabs"
onClick={() => setShown(true)}
data-tab-count={mainNoteContexts.length > 99 ? "∞" : mainNoteContexts.length}
/>
{createPortal(<TabBarModal mainNoteContexts={mainNoteContexts} shown={shown} setShown={setShown} />, document.body)}
</>
);
}
function TabBarModal({ mainNoteContexts, shown, setShown }: {
mainNoteContexts: NoteContext[];
shown: boolean;
setShown: (newValue: boolean) => void;
}) {
const [ fullyShown, setFullyShown ] = useState(false);
const selectTab = useCallback((noteContextToActivate: NoteContext) => {
appContext.tabManager.activateNoteContext(noteContextToActivate.ntxId);
setShown(false);
}, [ setShown ]);
return (
<Modal
className="tab-bar-modal"
size="xl"
title={t("mobile_tab_switcher.title", { count: mainNoteContexts.length})}
show={shown}
onShown={() => setFullyShown(true)}
customTitleBarButtons={[
{
iconClassName: "bx bx-dots-vertical-rounded",
title: t("mobile_tab_switcher.more_options"),
onClick(e) {
contextMenu.show<CommandNames>({
x: e.pageX,
y: e.pageY,
items: [
{ title: t("tab_row.new_tab"), command: "openNewTab", uiIcon: "bx bx-plus" },
{ title: t("tab_row.reopen_last_tab"), command: "reopenLastTab", uiIcon: "bx bx-undo", enabled: appContext.tabManager.recentlyClosedTabs.length !== 0 },
{ kind: "separator" },
{ title: t("tab_row.close_all_tabs"), command: "closeAllTabs", uiIcon: "bx bx-trash destructive-action-icon" },
],
selectMenuItemHandler: ({ command }) => {
if (command) {
appContext.triggerCommand(command);
}
}
});
},
}
]}
footer={<>
<LinkButton
text={t("tab_row.new_tab")}
onClick={() => {
appContext.triggerCommand("openNewTab");
setShown(false);
}}
/>
</>}
scrollable
onHidden={() => {
setShown(false);
setFullyShown(false);
}}
>
<TabBarModelContent mainNoteContexts={mainNoteContexts} selectTab={selectTab} shown={fullyShown} />
</Modal>
);
}
function TabBarModelContent({ mainNoteContexts, selectTab, shown }: {
mainNoteContexts: NoteContext[];
shown: boolean;
selectTab: (noteContextToActivate: NoteContext) => void;
}) {
const activeNoteContext = useActiveNoteContext();
const tabRefs = useRef<Record<string, HTMLDivElement | null>>({});
// Scroll to active tab.
useEffect(() => {
if (!shown || !activeNoteContext?.ntxId) return;
const correspondingEl = tabRefs.current[activeNoteContext.ntxId];
requestAnimationFrame(() => {
correspondingEl?.scrollIntoView();
});
}, [ activeNoteContext, shown ]);
return (
<div className="tabs">
{mainNoteContexts.map((noteContext) => (
<Tab
key={noteContext.ntxId}
noteContext={noteContext}
activeNtxId={activeNoteContext.ntxId}
selectTab={selectTab}
containerRef={el => (tabRefs.current[noteContext.ntxId ?? ""] = el)}
/>
))}
</div>
);
}
function Tab({ noteContext, containerRef, selectTab, activeNtxId }: {
containerRef: (el: HTMLDivElement | null) => void;
noteContext: NoteContext;
selectTab: (noteContextToActivate: NoteContext) => void;
activeNtxId: string | null | undefined;
}) {
const { note } = noteContext;
const iconClass = useNoteIcon(note);
const colorClass = note?.getColorClass() || '';
const workspaceTabBackgroundColorHue = getWorkspaceTabBackgroundColorHue(noteContext);
const subContexts = noteContext.getSubContexts();
return (
<div
ref={containerRef}
class={clsx("tab-card", {
active: noteContext.ntxId === activeNtxId,
"with-hue": workspaceTabBackgroundColorHue !== undefined,
"with-split": subContexts.length > 1
})}
onClick={() => selectTab(noteContext)}
style={{
"--bg-hue": workspaceTabBackgroundColorHue
}}
>
{subContexts.map(subContext => (
<Fragment key={subContext.ntxId}>
<header className={colorClass}>
{subContext.note && <Icon icon={iconClass} />}
<span className="title">{subContext.note?.title ?? t("tab_row.new_tab")}</span>
{subContext.isMainContext() && <ActionButton
icon="bx bx-x"
text={t("tab_row.close_tab")}
onClick={(e) => {
// We are closing a tab, so we need to prevent propagation for click (activate tab).
e.stopPropagation();
appContext.tabManager.removeNoteContext(subContext.ntxId);
}}
/>}
</header>
<div className={clsx("tab-preview", `type-${subContext.note?.type ?? "empty"}`)}>
<TabPreviewContent note={subContext.note} />
</div>
</Fragment>
))}
</div>
);
}
function TabPreviewContent({ note }: {
note: FNote | null
}) {
if (!note) {
return <PreviewPlaceholder icon="bx bx-plus" />;
}
if (note.type === "book") {
return <PreviewPlaceholder icon={ICON_MAPPINGS[note.getLabelValue("viewType") ?? ""] ?? "bx bx-book"} />;
}
return (
<NoteContent
note={note}
highlightedTokens={undefined}
trim
includeArchivedNotes={false}
/>
);
}
function PreviewPlaceholder({ icon}: {
icon: string;
}) {
return (
<div className="preview-placeholder">
<Icon icon={icon} />
</div>
);
}
function getWorkspaceTabBackgroundColorHue(noteContext: NoteContext) {
if (!noteContext.hoistedNoteId) return;
const hoistedNote = froca.getNoteFromCache(noteContext.hoistedNoteId);
if (!hoistedNote) return;
const workspaceTabBackgroundColor = hoistedNote.getWorkspaceTabBackgroundColor();
if (!workspaceTabBackgroundColor) return;
try {
const parsedColor = parseColor(workspaceTabBackgroundColor);
if (!parsedColor) return;
return getHue(parsedColor);
} catch (e) {
// Colors are non-critical, simply ignore.
console.warn(e);
}
}
function useMainNoteContexts() {
const [ noteContexts, setNoteContexts ] = useState(appContext.tabManager.getMainNoteContexts());
useTriliumEvents([ "newNoteContextCreated", "noteContextRemoved" ] , () => {
setNoteContexts(appContext.tabManager.getMainNoteContexts());
});
return noteContexts;
}

View File

@@ -1,16 +1,13 @@
import { useContext } from "preact/hooks";
import appContext, { CommandMappings } from "../../components/app_context";
import contextMenu, { MenuItem } from "../../menus/context_menu";
import branches from "../../services/branches";
import { t } from "../../services/i18n";
import { getHelpUrlForNote } from "../../services/in_app_help";
import note_create from "../../services/note_create";
import tree from "../../services/tree";
import { openInAppHelpFromUrl } from "../../services/utils";
import BasicWidget from "../basic_widget";
import ActionButton from "../react/ActionButton";
import { ParentComponent } from "../react/react_utils";
import BasicWidget from "../basic_widget";
export default function MobileDetailMenu() {
const parentComponent = useContext(ParentComponent);
@@ -27,7 +24,6 @@ export default function MobileDetailMenu() {
const subContexts = noteContext.getMainContext().getSubContexts();
const isMainContext = noteContext?.isMainContext();
const note = noteContext.note;
const helpUrl = getHelpUrlForNote(note);
const items: (MenuItem<keyof CommandMappings>)[] = [
{ title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" },
@@ -35,12 +31,6 @@ export default function MobileDetailMenu() {
{ kind: "separator" },
{ title: t("mobile_detail_menu.note_revisions"), command: "showRevisions", uiIcon: "bx bx-history" },
{ kind: "separator" },
helpUrl && {
title: t("help-button.title"),
uiIcon: "bx bx-help-circle",
handler: () => openInAppHelpFromUrl(helpUrl)
},
{ kind: "separator" },
subContexts.length < 2 && { title: t("create_pane_button.create_new_split"), command: "openNewNoteSplit", uiIcon: "bx bx-dock-right" },
!isMainContext && { title: t("close_pane_button.close_this_pane"), command: "closeThisNoteSplit", uiIcon: "bx bx-x" }
].filter(i => !!i) as MenuItem<keyof CommandMappings>[];
@@ -80,5 +70,5 @@ export default function MobileDetailMenu() {
});
}}
/>
);
)
}

View File

@@ -1,5 +1,5 @@
.collection-properties {
padding: 0.55em 12px;
padding: 0;
display: flex;
gap: 0.25em;
align-items: center;
@@ -14,31 +14,7 @@
}
}
>div {
display: inline-flex;
align-items: center;
gap: 0.5em;
}
.center-container {
.spacer {
flex-grow: 1;
justify-content: center;
}
.right-container {
justify-content: flex-end;
}
button.btn {
min-width: 0;
}
@media (max-width: 991px) {
flex-wrap: wrap;
padding: 0.55em 1em;
>div {
flex-grow: 1;
}
}
}

View File

@@ -1,22 +1,24 @@
import "./CollectionProperties.css";
import { t } from "i18next";
import { ComponentChildren } from "preact";
import { useContext, useRef } from "preact/hooks";
import { Fragment } from "preact/jsx-runtime";
import FNote from "../../entities/fnote";
import { getHelpUrlForNote } from "../../services/in_app_help";
import { openInAppHelpFromUrl } from "../../services/utils";
import { ViewTypeOptions } from "../collections/interface";
import ActionButton from "../react/ActionButton";
import Dropdown from "../react/Dropdown";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList";
import FormTextBox from "../react/FormTextBox";
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useNoteProperty, useTriliumEvent } from "../react/hooks";
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useTriliumEvent } from "../react/hooks";
import Icon from "../react/Icon";
import { ParentComponent } from "../react/react_utils";
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config";
import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab";
export const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
grid: "bx bxs-grid",
list: "bx bx-list-ul",
calendar: "bx bx-calendar",
@@ -26,26 +28,15 @@ export const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
presentation: "bx bx-rectangle"
};
export default function CollectionProperties({ note, centerChildren, rightChildren }: {
note: FNote;
centerChildren?: ComponentChildren;
rightChildren?: ComponentChildren;
}) {
export default function CollectionProperties({ note }: { note: FNote }) {
const [ viewType, setViewType ] = useViewType(note);
const noteType = useNoteProperty(note, "type");
return ([ "book", "search" ].includes(noteType ?? "") &&
return (
<div className="collection-properties">
<div className="left-container">
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
<ViewOptions note={note} viewType={viewType} />
</div>
<div className="center-container">
{centerChildren}
</div>
<div className="right-container">
{rightChildren}
</div>
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
<ViewOptions note={note} viewType={viewType} />
<div className="spacer" />
<HelpButton note={note} />
</div>
);
}
@@ -91,13 +82,6 @@ function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOption
))}
{properties.length > 0 && <FormDropdownDivider />}
<ViewProperty note={note} property={{
type: "checkbox",
icon: "bx bx-hide",
label: t("book_properties.hide_child_notes"),
bindToLabel: "subtreeHidden"
} as CheckBoxProperty} />
<ViewProperty note={note} property={{
type: "checkbox",
icon: "bx bx-archive",
@@ -231,3 +215,15 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check
/>
);
}
function HelpButton({ note }: { note: FNote }) {
const helpUrl = getHelpUrlForNote(note);
return (helpUrl && (
<ActionButton
icon="bx bx-help-circle"
onClick={(() => openInAppHelpFromUrl(helpUrl))}
text={t("help-button.title")}
/>
));
}

View File

@@ -6,7 +6,6 @@ import clsx from "clsx";
import { t } from "i18next";
import { CSSProperties, RefObject } from "preact";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import type React from "react";
import { CellComponentProps, Grid } from "react-window";
import FNote from "../entities/fnote";
@@ -154,10 +153,10 @@ function NoteIconList({ note, dropdownRef }: {
function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellComponentProps<{
filteredIcons: IconWithName[];
}>) {
}>): React.JSX.Element {
const iconIndex = rowIndex * 12 + columnIndex;
const iconData = filteredIcons[iconIndex] as IconWithName | undefined;
if (!iconData) return <></> as React.ReactElement;
if (!iconData) return <></>;
const { id, terms, iconPack } = iconData;
return (
@@ -167,7 +166,7 @@ function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellCompo
title={t("note_icon.icon_tooltip", { name: terms?.[0] ?? id, iconPack })}
style={style as CSSProperties}
/>
) as React.ReactElement;
);
}
function IconFilterContent({ filterByPrefix, setFilterByPrefix }: {

View File

@@ -1,20 +0,0 @@
#left-pane .tree-wrapper {
.note-indicator-icon.subtree-hidden-badge {
font-family: inherit !important;
margin-inline: 0.5em;
margin-top: 0.3em;
background: var(--left-pane-item-action-button-background);
color: var(--left-pane-item-action-button-color);
padding: 0.1em 0.6em;
border-radius: 0.5em;
font-size: 0.7rem;
font-weight: normal;
float: right;
vertical-align: middle;
}
.spotlighted-node {
opacity: 0.8;
font-style: italic;
}
}

View File

@@ -3,13 +3,13 @@ import "jquery.fancytree/dist/modules/jquery.fancytree.dnd5.js";
import "jquery.fancytree/dist/modules/jquery.fancytree.clones.js";
import "jquery.fancytree/dist/modules/jquery.fancytree.filter.js";
import "../stylesheets/tree.css";
import "./note_tree.css";
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
import type { SetNoteOpts } from "../components/note_context.js";
import type { TouchBarItem } from "../components/touch_bar.js";
import type FBranch from "../entities/fbranch.js";
import type FNote from "../entities/fnote.js";
import type { NoteType } from "../entities/fnote.js";
import contextMenu from "../menus/context_menu.js";
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
import branchService from "../services/branches.js";
@@ -153,7 +153,7 @@ const TPL = /*html*/`
const MAX_SEARCH_RESULTS_IN_TREE = 100;
// this has to be hanged on the actual elements to effectively intercept and stop click event
const cancelClickPropagation: (e: Event) => void = (e) => e.stopPropagation();
const cancelClickPropagation: (e: JQuery.ClickEvent) => void = (e) => e.stopPropagation();
// TODO: Fix once we remove Node.js API from public
type Timeout = NodeJS.Timeout | string | number | undefined;
@@ -190,9 +190,6 @@ export interface DragData {
export const TREE_CLIPBOARD_TYPE = "application/x-fancytree-node";
/** Entity changes below the given threshold will be processed without batching to avoid performance degradation. */
const BATCH_UPDATE_THRESHOLD = 10;
export default class NoteTreeWidget extends NoteContextAwareWidget {
private $tree!: JQuery<HTMLElement>;
private $treeActions!: JQuery<HTMLElement>;
@@ -204,8 +201,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
private treeName: "main";
private autoCollapseTimeoutId?: Timeout;
private lastFilteredHoistedNotePath?: string | null;
private spotlightedNotePath?: string | null;
private spotlightedNode: Fancytree.FancytreeNode | null = null;
private tree!: Fancytree.Fancytree;
constructor() {
@@ -358,7 +353,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
this.$tree.fancytree({
titlesTabbable: true,
keyboard: true,
toggleEffect: options.is("motionEnabled") ? undefined : false,
extensions: ["dnd5", "clones", "filter"],
source: treeData,
scrollOfs: {
@@ -558,7 +552,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
} else if (data.hitMode === "after") {
branchService.moveAfterBranch(selectedBranchIds, node.data.branchId);
} else if (data.hitMode === "over") {
branchService.moveToParentNote(selectedBranchIds, node.data.branchId, this.componentId);
branchService.moveToParentNote(selectedBranchIds, node.data.branchId);
} else {
throw new Error(`Unknown hitMode '${data.hitMode}'`);
}
@@ -604,7 +598,102 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
clones: {
highlightActiveClones: true
},
enhanceTitle: buildEnhanceTitle(),
async enhanceTitle (
event: Event,
data: {
node: Fancytree.FancytreeNode;
noteId: string;
}
) {
const node = data.node;
if (!node.data.noteId) {
// if there's "non-note" node, then don't enhance
// this can happen for e.g. "Load error!" node
return;
}
const note = await froca.getNote(node.data.noteId, true);
if (!note) {
return;
}
const activeNoteContext = appContext.tabManager.getActiveContext();
const $span = $(node.span);
$span.find(".tree-item-button").remove();
$span.find(".note-indicator-icon").remove();
const isHoistedNote = activeNoteContext && activeNoteContext.hoistedNoteId === note.noteId && note.noteId !== "root";
if (note.hasLabel("workspace") && !isHoistedNote) {
const $enterWorkspaceButton = $(`<span class="tree-item-button tn-icon enter-workspace-button bx bx-door-open" title="${t("note_tree.hoist-this-note-workspace")}"></span>`).on(
"click",
cancelClickPropagation
);
$span.append($enterWorkspaceButton);
}
if (note.type === "search") {
const $refreshSearchButton = $(`<span class="tree-item-button tn-icon refresh-search-button bx bx-refresh" title="${t("note_tree.refresh-saved-search-results")}"></span>`).on(
"click",
cancelClickPropagation
);
$span.append($refreshSearchButton);
}
// TODO: Deduplicate with server's notes.ts#getAndValidateParent
if (!["search", "launcher"].includes(note.type)
&& !note.isOptions()
&& !note.isLaunchBarConfig()
&& !note.noteId.startsWith("_help")
) {
const $createChildNoteButton = $(`<span class="tree-item-button tn-icon add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on(
"click",
cancelClickPropagation
);
$span.append($createChildNoteButton);
}
if (isHoistedNote) {
const $unhoistButton = $(`<span class="tree-item-button tn-icon unhoist-button bx bx-door-open" title="${t("note_tree.unhoist")}"></span>`).on("click", cancelClickPropagation);
$span.append($unhoistButton);
}
// Add clone indicator with tooltip if note has multiple parents
const parentNotes = note.getParentNotes();
const realParents = parentNotes.filter(
(parent) => !["_share", "_lbBookmarks"].includes(parent.noteId) && parent.type !== "search"
);
if (realParents.length > 1) {
const parentTitles = realParents.map((p) => p.title).join(", ");
const tooltipText = realParents.length === 2
? t("note_tree.clone-indicator-tooltip-single", { parent: realParents[1].title })
: t("note_tree.clone-indicator-tooltip", { count: realParents.length, parents: parentTitles });
const $cloneIndicator = $(`<span class="note-indicator-icon clone-indicator"></span>`);
$cloneIndicator.attr("title", tooltipText);
$span.find(".fancytree-title").append($cloneIndicator);
}
// Add shared indicator with tooltip if note is shared
if (note.isShared()) {
const shareId = note.getOwnedLabelValue("shareAlias") || note.noteId;
const shareUrl = `${location.origin}${location.pathname}share/${shareId}`;
const tooltipText = t("note_tree.shared-indicator-tooltip-with-url", { url: shareUrl });
const $sharedIndicator = $(`<span class="note-indicator-icon shared-indicator"></span>`);
$sharedIndicator.attr("title", tooltipText);
$span.find(".fancytree-title").append($sharedIndicator);
}
},
// this is done to automatically lazy load all expanded notes after tree load
loadChildren: (event, data) => {
data.node.visit((subNode) => {
@@ -714,23 +803,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
childBranches = childBranches.slice(0, MAX_SEARCH_RESULTS_IN_TREE);
}
if (parentNote.isLabelTruthy("subtreeHidden")) {
// If we have a spotlighted note path, show only the child that leads to it
if (this.spotlightedNotePath) {
const spotlightPathSegments = this.spotlightedNotePath.split('/');
const parentIndex = spotlightPathSegments.indexOf(parentNote.noteId);
if (parentIndex >= 0 && parentIndex < spotlightPathSegments.length - 1) {
const nextNoteIdInPath = spotlightPathSegments[parentIndex + 1];
childBranches = childBranches.filter(branch => branch.noteId === nextNoteIdInPath);
} else {
childBranches = [];
}
} else {
childBranches = [];
}
}
for (const branch of childBranches) {
if (hideArchivedNotes) {
const note = branch.getNoteFromCache();
@@ -802,7 +874,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
lazy: true,
folder: isFolder,
expanded: !!branch.isExpanded && note.type !== "search",
subtreeHidden: note.isLabelTruthy("subtreeHidden"),
key: utils.randomString(12) // this should prevent some "duplicate key" errors
};
@@ -861,10 +932,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
extraClasses.push(...["tinted", colorClass]);
}
if (this.spotlightedNotePath && this.spotlightedNotePath.endsWith(`/${note.noteId}`)) {
extraClasses.push("spotlighted-node");
}
return extraClasses.join(" ");
}
@@ -1015,43 +1082,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
foundChildNode = this.findChildNode(parentNode, childNoteId);
if (!foundChildNode) {
const childNote = await froca.getNote(childNoteId);
if (logErrors) {
// besides real errors, this can be also caused by hiding of e.g. included images
// these are real notes with real notePath, user can display them in a detail,
// but they don't have a node in the tree
if (childNote?.type === "image") return;
const childNote = await froca.getNote(childNoteId);
// The child note can be part of a note with #subtreeHidden, case in which we need to "spotlight" it.
const parentNote = froca.getNoteFromCache(parentNode.data.noteId);
if (parentNote?.isLabelTruthy("subtreeHidden")) {
// Enable spotlight mode and reload the parent to show only the path to this note
this.spotlightedNotePath = notePath;
await parentNode.load(true);
// Try to find the child again after reload
foundChildNode = this.findChildNode(parentNode, childNoteId);
this.spotlightedNode = foundChildNode ?? null;
if (!foundChildNode) {
if (logErrors || !childNote) {
ws.logError(
`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`
);
return;
}
return;
if (!childNote || childNote.type !== "image") {
ws.logError(
`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`
);
}
parentNode = foundChildNode;
continue;
}
// besides real errors, this can be also caused by hiding of e.g. included images
// these are real notes with real notePath, user can display them in a detail,
// but they don't have a node in the tree
if (logErrors || !childNote) {
ws.logError(
`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`
);
return;
}
return;
@@ -1066,7 +1108,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
findChildNode(parentNode: Fancytree.FancytreeNode, childNoteId: string) {
return parentNode.getChildren()?.find((childNode) => childNode.data.noteId === childNoteId);
return parentNode.getChildren().find((childNode) => childNode.data.noteId === childNoteId);
}
async expandToNote(notePath: string, logErrors = true) {
@@ -1107,20 +1149,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
(!treeService.isNotePathInHiddenSubtree(this.noteContext.notePath) || (await hoistedNoteService.isHoistedInHiddenSubtree())) &&
(await this.getNodeFromPath(this.noteContext.notePath));
if (this.spotlightedNode && newActiveNode !== this.spotlightedNode) {
// Can get removed when switching to another note in a spotlighted subtree.
if (this.spotlightedNode.parent) {
this.spotlightedNode.remove();
}
this.spotlightedNode = null;
this.spotlightedNotePath = null;
}
if (newActiveNode !== oldActiveNode) {
let oldActiveNodeFocused = false;
if (oldActiveNode) {
oldActiveNodeFocused = oldActiveNode.hasFocus();
oldActiveNode.setActive(false);
oldActiveNode.setFocus(false);
}
@@ -1223,20 +1257,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const { movedActiveNode, parentsOfAddedNodes } = await this.#processBranchRows(branchRows, refreshCtx);
for (const noteId of loadResults.getNoteIds()) {
const contentReloaded = loadResults.isNoteContentReloaded(noteId);
if (contentReloaded && !loadResults.isNoteReloaded(noteId, contentReloaded.componentId)) {
// Only the note content was reloaded, not the note itself. This would cause a redundant update on every few seconds while editing a note.
continue;
}
refreshCtx.noteIdsToUpdate.add(noteId);
}
const hasNotesToUpdateOrReload = refreshCtx.noteIdsToUpdate.size + refreshCtx.noteIdsToReload.size > 0;
const hasNoteReorderingChange = loadResults.getNoteReorderings().length > 0;
if (hasNotesToUpdateOrReload || hasNoteReorderingChange) {
await this.#executeTreeUpdates(refreshCtx, loadResults);
}
await this.#executeTreeUpdates(refreshCtx, loadResults);
await this.#setActiveNode(activeNotePath, activeNodeFocused, movedActiveNode, parentsOfAddedNodes);
@@ -1256,7 +1280,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
} else {
refreshCtx.noteIdsToUpdate.add(attrRow.noteId);
}
} else if (attrRow.type === "label" && (attrRow.name === "archived" || attrRow.name === "subtreeHidden") && attrRow.noteId) {
} else if (attrRow.type === "label" && attrRow.name === "archived" && attrRow.noteId) {
const note = froca.getNoteFromCache(attrRow.noteId);
if (note) {
@@ -1341,34 +1365,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
} else if (frocaBranch) {
// make sure it's loaded
// we're forcing lazy since it's not clear if the whole required subtree is in froca
if (!parentNode.data.subtreeHidden) {
const newNode = this.prepareNode(frocaBranch, true);
if (newNode) {
parentNode.addChildren([newNode]);
}
if (frocaBranch?.isExpanded && note && note.hasChildren()) {
refreshCtx.noteIdsToReload.add(frocaBranch.noteId);
}
this.sortChildren(parentNode);
} else if (branchRow.componentId === this.componentId) {
// Display the toast and focus to parent note only if we know for sure that the operation comes from the tree.
const parentNote = froca.getNoteFromCache(parentNode.data.noteId || "");
toastService.showPersistent({
id: `subtree-hidden-moved`,
title: t("note_tree.subtree-hidden-moved-title", { title: parentNote?.title }),
message: parentNote?.type === "book"
? t("note_tree.subtree-hidden-moved-description-collection")
: t("note_tree.subtree-hidden-moved-description-other"),
icon: "bx bx-hide",
timeout: 5_000,
});
parentNode.setActive(true);
const newNode = this.prepareNode(frocaBranch, true);
if (newNode) {
parentNode.addChildren([newNode]);
}
if (frocaBranch?.isExpanded && note && note.hasChildren()) {
refreshCtx.noteIdsToReload.add(frocaBranch.noteId);
}
this.sortChildren(parentNode);
// this might be a first child which would force an icon change
// also update the count if the subtree is hidden.
if (branchRow.parentNoteId) {
refreshCtx.noteIdsToUpdate.add(branchRow.parentNoteId);
}
@@ -1384,7 +1392,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
async #executeTreeUpdates(refreshCtx: RefreshContext, loadResults: LoadResults) {
const performUpdates = async () => {
await this.batchUpdate(async () => {
for (const noteId of refreshCtx.noteIdsToReload) {
for (const node of this.getNodesByNoteId(noteId)) {
await node.load(true);
@@ -1395,25 +1403,12 @@ 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);
}
}
}
};
if (refreshCtx.noteIdsToReload.size + refreshCtx.noteIdsToUpdate.size >= BATCH_UPDATE_THRESHOLD) {
/**
* Batch updates are used for large number of updates to prevent multiple re-renders, however in the context of small updates (such as changing a note title)
* it can cause up to 400ms of delay for ~8k notes which is not acceptable. Therefore we use batching only for larger number of updates.
* Without batching, the updates would take a couple of milliseconds.
* We still keep the batching for potential cases where there are many updates, for example in a sync.
*/
await this.batchUpdate(performUpdates);
} else {
await performUpdates();
}
});
// for some reason, node update cannot be in the batchUpdate() block (node is not re-rendered)
for (const noteId of refreshCtx.noteIdsToUpdate) {
@@ -1677,7 +1672,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const toNode = node.getPrevSibling();
if (toNode !== null) {
branchService.moveToParentNote([node.data.branchId], toNode.data.branchId, this.componentId);
branchService.moveToParentNote([node.data.branchId], toNode.data.branchId);
}
}
@@ -1814,12 +1809,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
#moveLaunchers(selectedOrActiveBranchIds: string[], desktopParent: string, mobileParent: string) {
const desktopLaunchersToMove = selectedOrActiveBranchIds.filter((branchId) => !branchId.startsWith("_lbMobile"));
if (desktopLaunchersToMove) {
branchService.moveToParentNote(desktopLaunchersToMove, `_lbRoot_${ desktopParent}`, this.componentId);
branchService.moveToParentNote(desktopLaunchersToMove, `_lbRoot_${ desktopParent}`);
}
const mobileLaunchersToMove = selectedOrActiveBranchIds.filter((branchId) => branchId.startsWith("_lbMobile"));
if (mobileLaunchersToMove) {
branchService.moveToParentNote(mobileLaunchersToMove, `_lbMobileRoot_${mobileParent}`, this.componentId);
branchService.moveToParentNote(mobileLaunchersToMove, `_lbMobileRoot_${ mobileParent}`);
}
}
@@ -1887,112 +1882,3 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return items;
}
}
function buildEnhanceTitle() {
const createChildTemplate = document.createElement("span");
createChildTemplate.className = "tree-item-button tn-icon add-note-button bx bx-plus";
createChildTemplate.title = t("note_tree.create-child-note");
return async function enhanceTitle(event: Event,
data: {
node: Fancytree.FancytreeNode;
noteId: string;
}) {
const node = data.node;
if (!node.data.noteId) {
// if there's "non-note" node, then don't enhance
// this can happen for e.g. "Load error!" node
return;
}
const note = froca.getNoteFromCache(node.data.noteId);
if (!note) return;
const activeNoteContext = appContext.tabManager.getActiveContext();
const $span = $(node.span);
$span.find(".tree-item-button").remove();
$span.find(".note-indicator-icon").remove();
const isHoistedNote = activeNoteContext && activeNoteContext.hoistedNoteId === note.noteId && note.noteId !== "root";
if (note.hasLabel("workspace") && !isHoistedNote) {
const $enterWorkspaceButton = $(`<span class="tree-item-button tn-icon enter-workspace-button bx bx-door-open" title="${t("note_tree.hoist-this-note-workspace")}"></span>`).on(
"click",
cancelClickPropagation
);
$span.append($enterWorkspaceButton);
}
if (note.type === "search") {
const $refreshSearchButton = $(`<span class="tree-item-button tn-icon refresh-search-button bx bx-refresh" title="${t("note_tree.refresh-saved-search-results")}"></span>`).on(
"click",
cancelClickPropagation
);
$span.append($refreshSearchButton);
}
// TODO: Deduplicate with server's notes.ts#getAndValidateParent
const isSubtreeHidden = note.isLabelTruthy("subtreeHidden");
if (!["search", "launcher"].includes(note.type)
&& !note.isOptions()
&& !note.isLaunchBarConfig()
&& !note.noteId.startsWith("_help")
&& !isSubtreeHidden
&& !node.extraClasses.includes("spotlighted-node")
) {
const createChildItem = createChildTemplate.cloneNode();
createChildItem.addEventListener("click", cancelClickPropagation);
node.span.append(createChildItem);
}
if (isHoistedNote) {
const $unhoistButton = $(`<span class="tree-item-button tn-icon unhoist-button bx bx-door-open" title="${t("note_tree.unhoist")}"></span>`).on("click", cancelClickPropagation);
$span.append($unhoistButton);
}
// Add clone indicator with tooltip if note has multiple parents
const parentNotes = note.getParentNotes();
const realParents: FNote[] = [];
for (const parent of parentNotes) {
if (parent.noteId !== "_share" && parent.noteId !== "_lbBookmarks" && parent.type !== "search") {
realParents.push(parent);
}
}
if (realParents.length > 1) {
const parentTitles = realParents.map((p) => p.title).join(", ");
const tooltipText = realParents.length === 2
? t("note_tree.clone-indicator-tooltip-single", { parent: realParents[1].title })
: t("note_tree.clone-indicator-tooltip", { count: realParents.length, parents: parentTitles });
const $cloneIndicator = $(`<span class="note-indicator-icon clone-indicator"></span>`);
$cloneIndicator.attr("title", tooltipText);
$span.find(".fancytree-title").append($cloneIndicator);
}
// Add shared indicator with tooltip if note is shared
if (note.isShared()) {
const shareId = note.getOwnedLabelValue("shareAlias") || note.noteId;
const shareUrl = `${location.origin}${location.pathname}share/${shareId}`;
const tooltipText = t("note_tree.shared-indicator-tooltip-with-url", { url: shareUrl });
const $sharedIndicator = $(`<span class="note-indicator-icon shared-indicator"></span>`);
$sharedIndicator.attr("title", tooltipText);
$span.find(".fancytree-title").append($sharedIndicator);
}
// Add a badge with the number of items if it hides children.
const count = note.getChildNoteIds().length;
if (isSubtreeHidden && count > 0) {
const $badge = $(`<span class="note-indicator-icon subtree-hidden-badge">${count}</span>`);
$badge.attr("title", t("note_tree.subtree-hidden-tooltip", { count }));
$span.find(".fancytree-title").append($badge);
}
};
}

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" | "sqlConsole";
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat";
export type TypeWidget = ((props: TypeWidgetProps) => VNode | JSX.Element | undefined);
type NoteTypeView = () => (Promise<{ default: TypeWidget } | TypeWidget> | TypeWidget);
@@ -140,10 +140,5 @@ 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

@@ -1,11 +1,10 @@
import { HTMLAttributes } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { CommandNames } from "../../components/app_context";
import keyboard_actions from "../../services/keyboard_actions";
import { useStaticTooltip } from "./hooks";
import keyboard_actions from "../../services/keyboard_actions";
import { HTMLAttributes } from "preact";
export interface ActionButtonProps extends Pick<HTMLAttributes<HTMLButtonElement>, "onClick" | "onAuxClick" | "onContextMenu" | "style"> {
export interface ActionButtonProps extends Pick<HTMLAttributes<HTMLButtonElement>, "onClick" | "onAuxClick" | "onContextMenu"> {
text: string;
titlePosition?: "top" | "right" | "bottom" | "left";
icon: string;

View File

@@ -1,11 +1,8 @@
import type { ComponentChildren, RefObject } from "preact";
import type { CSSProperties } from "preact/compat";
import { memo } from "preact/compat";
import { useMemo } from "preact/hooks";
import { memo } from "preact/compat";
import { CommandNames } from "../../components/app_context";
import { isDesktop } from "../../services/utils";
import ActionButton from "./ActionButton";
import Icon from "./Icon";
export interface ButtonProps {
@@ -81,7 +78,7 @@ export function ButtonGroup({ children }: { children: ComponentChildren }) {
<div className="btn-group" role="group">
{children}
</div>
);
)
}
export function SplitButton({ text, icon, children, ...restProps }: {
@@ -106,17 +103,7 @@ export function SplitButton({ text, icon, children, ...restProps }: {
{children}
</ul>
</ButtonGroup>
);
}
export function ButtonOrActionButton(props: {
text: string;
icon: string;
} & Pick<ButtonProps, "onClick" | "triggerCommand" | "disabled" | "title">) {
if (isDesktop()) {
return <Button {...props} />;
}
return <ActionButton {...props} />;
)
}
export default Button;

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