Merge branch 'main' into feat/cleanup-logs

This commit is contained in:
Elian Doran
2025-08-13 13:26:52 +03:00
committed by GitHub
192 changed files with 13814 additions and 11092 deletions

View File

@@ -44,7 +44,7 @@ runs:
steps: steps:
# Checkout branch to compare to [required] # Checkout branch to compare to [required]
- name: Checkout base branch - name: Checkout base branch
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
ref: ${{ inputs.branch }} ref: ${{ inputs.branch }}
path: br-base path: br-base

View File

@@ -57,7 +57,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
# Add any setup steps before running the `github/codeql-action/init` action. # Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node` # This includes steps like installing compilers or runtimes (`actions/setup-node`

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 # needed for https://github.com/marketplace/actions/nx-set-shas fetch-depth: 0 # needed for https://github.com/marketplace/actions/nx-set-shas
@@ -48,7 +48,7 @@ jobs:
- check-affected - check-affected
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4 uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
@@ -68,7 +68,7 @@ jobs:
- test_dev - test_dev
- check-affected - check-affected
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Install dependencies - name: Install dependencies
run: pnpm install --frozen-lockfile run: pnpm install --frozen-lockfile
@@ -103,7 +103,7 @@ jobs:
- dockerfile: Dockerfile - dockerfile: Dockerfile
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4 uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Install dependencies - name: Install dependencies

View File

@@ -32,7 +32,7 @@ jobs:
- dockerfile: Dockerfile - dockerfile: Dockerfile
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Set IMAGE_NAME to lowercase - name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
@@ -141,7 +141,7 @@ jobs:
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v4

View File

@@ -27,6 +27,7 @@ permissions:
jobs: jobs:
nightly-electron: nightly-electron:
if: github.repository == 'TriliumNext/Trilium'
name: Deploy nightly name: Deploy nightly
strategy: strategy:
fail-fast: false fail-fast: false
@@ -47,7 +48,7 @@ jobs:
forge_platform: win32 forge_platform: win32
runs-on: ${{ matrix.os.image }} runs-on: ${{ matrix.os.image }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v4
@@ -96,6 +97,7 @@ jobs:
path: apps/desktop/upload path: apps/desktop/upload
nightly-server: nightly-server:
if: github.repository == 'TriliumNext/Trilium'
name: Deploy server nightly name: Deploy server nightly
strategy: strategy:
fail-fast: false fail-fast: false
@@ -108,7 +110,7 @@ jobs:
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Run the build - name: Run the build
uses: ./.github/actions/build-server uses: ./.github/actions/build-server

View File

@@ -14,7 +14,7 @@ jobs:
main: main:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
filter: tree:0 filter: tree:0
fetch-depth: 0 fetch-depth: 0

View File

@@ -32,7 +32,7 @@ jobs:
forge_platform: win32 forge_platform: win32
runs-on: ${{ matrix.os.image }} runs-on: ${{ matrix.os.image }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
- name: Set up node & dependencies - name: Set up node & dependencies
uses: actions/setup-node@v4 uses: actions/setup-node@v4
@@ -78,7 +78,7 @@ jobs:
runs-on: ubuntu-24.04-arm runs-on: ubuntu-24.04-arm
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Run the build - name: Run the build
uses: ./.github/actions/build-server uses: ./.github/actions/build-server
@@ -101,7 +101,7 @@ jobs:
steps: steps:
- run: mkdir upload - run: mkdir upload
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
sparse-checkout: | sparse-checkout: |
docs/Release Notes docs/Release Notes

View File

@@ -3,6 +3,7 @@
languageIds: languageIds:
- javascript - javascript
- typescript - typescript
- typescriptreact
- html - html
# An array of RegExes to find the key usage. **The key should be captured in the first match group**. # An array of RegExes to find the key usage. **The key should be captured in the first match group**.
@@ -25,9 +26,10 @@ scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
# The "$1" will be replaced by the keypath specified. # The "$1" will be replaced by the keypath specified.
refactorTemplates: refactorTemplates:
- t("$1") - t("$1")
- {t("$1")}
- ${t("$1")} - ${t("$1")}
- <%= t("$1") %> - <%= t("$1") %>
# If set to true, only enables this custom framework (will disable all built-in frameworks) # If set to true, only enables this custom framework (will disable all built-in frameworks)
monopoly: true monopoly: true

View File

@@ -82,7 +82,7 @@ Feel free to join our official conversations. We would love to hear what feature
### Windows / MacOS ### Windows / MacOS
Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the `trilium` executable. Download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package and run the `trilium` executable.
### Linux ### Linux
@@ -90,7 +90,7 @@ If your distribution is listed in the table below, use your distribution's packa
[![Packaging status](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/triliumnext.svg)](https://repology.org/project/triliumnext/versions)
You may also download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run the `trilium` executable. You may also download the binary release for your platform from the [latest release page](https://github.com/TriliumNext/Trilium/releases/latest), unzip the package and run the `trilium` executable.
TriliumNext is also provided as a Flatpak, but not yet published on FlatHub. TriliumNext is also provided as a Flatpak, but not yet published on FlatHub.

View File

@@ -49,8 +49,8 @@
"rcedit": "4.0.1", "rcedit": "4.0.1",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"tslib": "2.8.1", "tslib": "2.8.1",
"typedoc": "0.28.9", "typedoc": "0.28.10",
"typedoc-plugin-missing-exports": "4.0.0" "typedoc-plugin-missing-exports": "4.1.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"appdmg": "0.6.6" "appdmg": "0.6.6"

View File

@@ -12,12 +12,12 @@
"dependencies": { "dependencies": {
"@eslint/js": "9.33.0", "@eslint/js": "9.33.0",
"@excalidraw/excalidraw": "0.18.0", "@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.18", "@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.18", "@fullcalendar/daygrid": "6.1.19",
"@fullcalendar/interaction": "6.1.18", "@fullcalendar/interaction": "6.1.19",
"@fullcalendar/list": "6.1.18", "@fullcalendar/list": "6.1.19",
"@fullcalendar/multimonth": "6.1.18", "@fullcalendar/multimonth": "6.1.19",
"@fullcalendar/timegrid": "6.1.18", "@fullcalendar/timegrid": "6.1.19",
"@maplibre/maplibre-gl-leaflet": "0.1.3", "@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.1.8", "@mermaid-js/layout-elk": "0.1.8",
"@mind-elixir/node-menu": "5.0.0", "@mind-elixir/node-menu": "5.0.0",
@@ -36,7 +36,7 @@
"draggabilly": "3.0.0", "draggabilly": "3.0.0",
"force-graph": "1.50.1", "force-graph": "1.50.1",
"globals": "16.3.0", "globals": "16.3.0",
"i18next": "25.3.2", "i18next": "25.3.4",
"i18next-http-backend": "3.0.2", "i18next-http-backend": "3.0.2",
"jquery": "3.7.1", "jquery": "3.7.1",
"jquery.fancytree": "2.38.5", "jquery.fancytree": "2.38.5",
@@ -65,8 +65,8 @@
"@types/leaflet": "1.9.20", "@types/leaflet": "1.9.20",
"@types/leaflet-gpx": "1.3.7", "@types/leaflet-gpx": "1.3.7",
"@types/mark.js": "8.11.12", "@types/mark.js": "8.11.12",
"@types/tabulator-tables": "6.2.9", "@types/tabulator-tables": "6.2.10",
"copy-webpack-plugin": "13.0.0", "copy-webpack-plugin": "13.0.1",
"happy-dom": "18.0.1", "happy-dom": "18.0.1",
"script-loader": "0.7.2", "script-loader": "0.7.2",
"vite-plugin-static-copy": "3.1.1" "vite-plugin-static-copy": "3.1.1"

View File

@@ -30,6 +30,7 @@ import type CodeMirror from "@triliumnext/codemirror";
import { StartupChecks } from "./startup_checks.js"; import { StartupChecks } from "./startup_checks.js";
import type { CreateNoteOpts } from "../services/note_create.js"; import type { CreateNoteOpts } from "../services/note_create.js";
import { ColumnComponent } from "tabulator-tables"; import { ColumnComponent } from "tabulator-tables";
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
interface Layout { interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget; getRootWidget: (appContext: AppContext) => RootWidget;
@@ -92,7 +93,9 @@ export type CommandMappings = {
closeTocCommand: CommandData; closeTocCommand: CommandData;
closeHlt: CommandData; closeHlt: CommandData;
showLaunchBarSubtree: CommandData; showLaunchBarSubtree: CommandData;
showRevisions: CommandData; showRevisions: CommandData & {
noteId?: string | null;
};
showLlmChat: CommandData; showLlmChat: CommandData;
createAiChat: CommandData; createAiChat: CommandData;
showOptions: CommandData & { showOptions: CommandData & {
@@ -368,6 +371,9 @@ export type CommandMappings = {
}; };
refreshTouchBar: CommandData; refreshTouchBar: CommandData;
reloadTextEditor: CommandData; reloadTextEditor: CommandData;
chooseNoteType: CommandData & {
callback: ChooseNoteTypeCallback
}
}; };
type EventMappings = { type EventMappings = {

View File

@@ -30,6 +30,7 @@ import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolb
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js"; import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
import NoteDetailWidget from "../widgets/note_detail.js"; import NoteDetailWidget from "../widgets/note_detail.js";
import NoteListWidget from "../widgets/note_list.js"; import NoteListWidget from "../widgets/note_list.js";
import { CallToActionDialog } from "../widgets/dialogs/call_to_action.jsx";
export function applyModals(rootContainer: RootContainer) { export function applyModals(rootContainer: RootContainer) {
rootContainer rootContainer
@@ -66,4 +67,5 @@ export function applyModals(rootContainer: RootContainer) {
.child(new PromotedAttributesWidget()) .child(new PromotedAttributesWidget())
.child(new NoteDetailWidget()) .child(new NoteDetailWidget())
.child(new NoteListWidget(true))) .child(new NoteListWidget(true)))
.child(new CallToActionDialog());
} }

View File

@@ -38,8 +38,8 @@ export interface Suggestion {
commandShortcut?: string; commandShortcut?: string;
} }
interface Options { export interface Options {
container?: HTMLElement; container?: HTMLElement | null;
fastSearch?: boolean; fastSearch?: boolean;
allowCreatingNotes?: boolean; allowCreatingNotes?: boolean;
allowJumpToSearchNotes?: boolean; allowJumpToSearchNotes?: boolean;
@@ -82,12 +82,12 @@ async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void
// Check if we're in command mode // Check if we're in command mode
if (options.isCommandPalette && term.startsWith(">")) { if (options.isCommandPalette && term.startsWith(">")) {
const commandQuery = term.substring(1).trim(); const commandQuery = term.substring(1).trim();
// Get commands (all if no query, filtered if query provided) // Get commands (all if no query, filtered if query provided)
const commands = commandQuery.length === 0 const commands = commandQuery.length === 0
? commandRegistry.getAllCommands() ? commandRegistry.getAllCommands()
: commandRegistry.searchCommands(commandQuery); : commandRegistry.searchCommands(commandQuery);
// Convert commands to suggestions // Convert commands to suggestions
const commandSuggestions: Suggestion[] = commands.map(cmd => ({ const commandSuggestions: Suggestion[] = commands.map(cmd => ({
action: "command", action: "command",
@@ -99,7 +99,7 @@ async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void
commandShortcut: cmd.shortcut, commandShortcut: cmd.shortcut,
icon: cmd.icon icon: cmd.icon
})); }));
cb(commandSuggestions); cb(commandSuggestions);
return; return;
} }
@@ -452,6 +452,21 @@ function init() {
}; };
} }
/**
* Convenience function which triggers the display of recent notes in the autocomplete input and focuses it.
*
* @param inputElement - The input element to trigger recent notes on.
*/
export function triggerRecentNotes(inputElement: HTMLInputElement | null | undefined) {
if (!inputElement) {
return;
}
const $el = $(inputElement);
showRecentNotes($el);
$el.trigger("focus").trigger("select");
}
export default { export default {
autocompleteSourceForCKEditor, autocompleteSourceForCKEditor,
initNoteAutocomplete, initNoteAutocomplete,

View File

@@ -109,8 +109,6 @@ async function createNote(parentNotePath: string | undefined, options: CreateNot
async function chooseNoteType() { async function chooseNoteType() {
return new Promise<ChooseNoteTypeResponse>((res) => { return new Promise<ChooseNoteTypeResponse>((res) => {
// TODO: Remove ignore after callback for chooseNoteType is defined in app_context.ts
//@ts-ignore
appContext.triggerCommand("chooseNoteType", { callback: res }); appContext.triggerCommand("chooseNoteType", { callback: res });
}); });
} }

View File

@@ -320,8 +320,3 @@ h6 {
page-break-after: avoid; page-break-after: avoid;
break-after: avoid; break-after: avoid;
} }
figure.table {
/* Workaround for https://github.com/ckeditor/ckeditor5/issues/18903. Remove once official fix is released */
display: table !important;
}

View File

@@ -642,6 +642,10 @@ table.promoted-attributes-in-tooltip th {
z-index: calc(var(--ck-z-panel) - 1) !important; z-index: calc(var(--ck-z-panel) - 1) !important;
} }
.tooltip.tooltip-top {
z-index: 32767 !important;
}
.tooltip-trigger { .tooltip-trigger {
background: transparent; background: transparent;
pointer-events: none; pointer-events: none;
@@ -2251,6 +2255,13 @@ footer.webview-footer button {
padding: 1px 10px 1px 10px; padding: 1px 10px 1px 10px;
} }
/* Search result highlighting */
.search-result-title b,
.search-result-content b {
font-weight: 900;
color: var(--admonition-warning-accent-color);
}
/* Customized icons */ /* Customized icons */
.bx-tn-toc::before { .bx-tn-toc::before {

View File

@@ -233,16 +233,16 @@ div.tn-tool-dialog {
/* Item title link */ /* Item title link */
.recent-changes-content ul li .note-title a { .recent-changes-content ul li a {
color: currentColor; color: currentColor;
} }
.recent-changes-content ul li .note-title a:hover { .recent-changes-content ul li a:hover {
text-decoration: underline; text-decoration: underline;
} }
/* Item title for deleted notes */ /* Item title for deleted notes */
.recent-changes-content ul li.deleted-note .note-title > .note-title { .recent-changes-content ul li.deleted-note .note-title {
text-decoration: line-through; text-decoration: line-through;
} }

View File

@@ -1,31 +1,23 @@
{ {
"about": { "about": {
"title": "Sobre Trilium Notes", "title": "Sobre Trilium Notes",
"close": "Tanca",
"homepage": "Pàgina principal:" "homepage": "Pàgina principal:"
}, },
"add_link": { "add_link": {
"note": "Nota", "note": "Nota"
"close": "Tanca"
}, },
"branch_prefix": { "branch_prefix": {
"close": "Tanca",
"prefix": "Prefix: ", "prefix": "Prefix: ",
"save": "Desa" "save": "Desa"
}, },
"bulk_actions": { "bulk_actions": {
"close": "Tanca",
"labels": "Etiquetes", "labels": "Etiquetes",
"relations": "Relacions", "relations": "Relacions",
"notes": "Notes", "notes": "Notes",
"other": "Altres" "other": "Altres"
}, },
"clone_to": {
"close": "Tanca"
},
"confirm": { "confirm": {
"confirmation": "Confirmació", "confirmation": "Confirmació",
"close": "Tanca",
"cancel": "Cancel·la", "cancel": "Cancel·la",
"ok": "OK" "ok": "OK"
}, },
@@ -39,53 +31,34 @@
"export": "Exporta" "export": "Exporta"
}, },
"help": { "help": {
"close": "Tanca",
"troubleshooting": "Solució de problemes", "troubleshooting": "Solució de problemes",
"other": "Altres" "other": "Altres"
}, },
"import": { "import": {
"close": "Tanca",
"options": "Opcions", "options": "Opcions",
"import": "Importa" "import": "Importa"
}, },
"include_note": { "include_note": {
"close": "Tanca",
"label_note": "Nota" "label_note": "Nota"
}, },
"info": { "info": {
"closeButton": "Tanca", "closeButton": "Tanca",
"okButton": "OK" "okButton": "OK"
}, },
"jump_to_note": {
"close": "Tanca"
},
"markdown_import": {
"close": "Tanca"
},
"move_to": {
"close": "Tanca"
},
"note_type_chooser": { "note_type_chooser": {
"close": "Tanca",
"templates": "Plantilles:" "templates": "Plantilles:"
}, },
"password_not_set": {
"close": "Tanca"
},
"prompt": { "prompt": {
"title": "Sol·licitud", "title": "Sol·licitud",
"close": "Tanca",
"defaultTitle": "Sol·licitud" "defaultTitle": "Sol·licitud"
}, },
"protected_session_password": { "protected_session_password": {
"close_label": "Tanca" "close_label": "Tanca"
}, },
"recent_changes": { "recent_changes": {
"close": "Tanca",
"undelete_link": "recuperar" "undelete_link": "recuperar"
}, },
"revisions": { "revisions": {
"close": "Tanca",
"restore_button": "Restaura", "restore_button": "Restaura",
"delete_button": "Suprimeix", "delete_button": "Suprimeix",
"download_button": "Descarrega", "download_button": "Descarrega",
@@ -93,14 +66,12 @@
"preview": "Vista prèvia:" "preview": "Vista prèvia:"
}, },
"sort_child_notes": { "sort_child_notes": {
"close": "Tanca",
"title": "títol", "title": "títol",
"ascending": "ascendent", "ascending": "ascendent",
"descending": "descendent", "descending": "descendent",
"folders": "Carpetes" "folders": "Carpetes"
}, },
"upload_attachments": { "upload_attachments": {
"close": "Tanca",
"options": "Opcions", "options": "Opcions",
"upload": "Puja" "upload": "Puja"
}, },

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
{ {
"about": { "about": {
"title": "Über Trilium Notes", "title": "Über Trilium Notes",
"close": "Schließen",
"homepage": "Startseite:", "homepage": "Startseite:",
"app_version": "App-Version:", "app_version": "App-Version:",
"db_version": "DB-Version:", "db_version": "DB-Version:",
@@ -28,25 +27,22 @@
"add_link": { "add_link": {
"add_link": "Link hinzufügen", "add_link": "Link hinzufügen",
"help_on_links": "Hilfe zu Links", "help_on_links": "Hilfe zu Links",
"close": "Schließen",
"note": "Notiz", "note": "Notiz",
"search_note": "Suche nach einer Notiz anhand ihres Namens", "search_note": "Suche nach einer Notiz anhand ihres Namens",
"link_title_mirrors": "Der Linktitel spiegelt den aktuellen Titel der Notiz wider", "link_title_mirrors": "Der Linktitel spiegelt den aktuellen Titel der Notiz wider",
"link_title_arbitrary": "Der Linktitel kann beliebig geändert werden", "link_title_arbitrary": "Der Linktitel kann beliebig geändert werden",
"link_title": "Linktitel", "link_title": "Linktitel",
"button_add_link": "Link hinzufügen <kbd>Eingabetaste</kbd>" "button_add_link": "Link hinzufügen"
}, },
"branch_prefix": { "branch_prefix": {
"edit_branch_prefix": "Zweigpräfix bearbeiten", "edit_branch_prefix": "Zweigpräfix bearbeiten",
"help_on_tree_prefix": "Hilfe zum Baumpräfix", "help_on_tree_prefix": "Hilfe zum Baumpräfix",
"close": "Schließen",
"prefix": "Präfix: ", "prefix": "Präfix: ",
"save": "Speichern", "save": "Speichern",
"branch_prefix_saved": "Zweigpräfix wurde gespeichert." "branch_prefix_saved": "Zweigpräfix wurde gespeichert."
}, },
"bulk_actions": { "bulk_actions": {
"bulk_actions": "Massenaktionen", "bulk_actions": "Massenaktionen",
"close": "Schließen",
"affected_notes": "Betroffene Notizen", "affected_notes": "Betroffene Notizen",
"include_descendants": "Unternotizen der ausgewählten Notizen einbeziehen", "include_descendants": "Unternotizen der ausgewählten Notizen einbeziehen",
"available_actions": "Verfügbare Aktionen", "available_actions": "Verfügbare Aktionen",
@@ -61,20 +57,18 @@
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Notizen klonen nach...", "clone_notes_to": "Notizen klonen nach...",
"close": "Schließen",
"help_on_links": "Hilfe zu Links", "help_on_links": "Hilfe zu Links",
"notes_to_clone": "Notizen zum Klonen", "notes_to_clone": "Notizen zum Klonen",
"target_parent_note": "Ziel-Übergeordnetenotiz", "target_parent_note": "Ziel-Übergeordnetenotiz",
"search_for_note_by_its_name": "Suche nach einer Notiz anhand ihres Namens", "search_for_note_by_its_name": "Suche nach einer Notiz anhand ihres Namens",
"cloned_note_prefix_title": "Die geklonte Notiz wird im Notizbaum mit dem angegebenen Präfix angezeigt", "cloned_note_prefix_title": "Die geklonte Notiz wird im Notizbaum mit dem angegebenen Präfix angezeigt",
"prefix_optional": "Präfix (optional)", "prefix_optional": "Präfix (optional)",
"clone_to_selected_note": "Auf ausgewählte Notiz klonen <kbd>Eingabe</kbd>", "clone_to_selected_note": "Auf ausgewählte Notiz klonen",
"no_path_to_clone_to": "Kein Pfad zum Klonen.", "no_path_to_clone_to": "Kein Pfad zum Klonen.",
"note_cloned": "Die Notiz \"{{clonedTitle}}\" wurde in \"{{targetTitle}}\" hinein geklont" "note_cloned": "Die Notiz \"{{clonedTitle}}\" wurde in \"{{targetTitle}}\" hinein geklont"
}, },
"confirm": { "confirm": {
"confirmation": "Bestätigung", "confirmation": "Bestätigung",
"close": "Schließen",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"ok": "OK", "ok": "OK",
"are_you_sure_remove_note": "Bist du sicher, dass du \"{{title}}\" von der Beziehungskarte entfernen möchten? ", "are_you_sure_remove_note": "Bist du sicher, dass du \"{{title}}\" von der Beziehungskarte entfernen möchten? ",
@@ -87,9 +81,9 @@
"delete_all_clones_description": "auch alle Klone löschen (kann bei letzte Änderungen rückgängig gemacht werden)", "delete_all_clones_description": "auch alle Klone löschen (kann bei letzte Änderungen rückgängig gemacht werden)",
"erase_notes_description": "Beim normalen (vorläufigen) Löschen werden die Notizen nur als gelöscht markiert und sie können innerhalb eines bestimmten Zeitraums (im Dialogfeld „Letzte Änderungen“) wiederhergestellt werden. Wenn du diese Option aktivierst, werden die Notizen sofort gelöscht und es ist nicht möglich, die Notizen wiederherzustellen.", "erase_notes_description": "Beim normalen (vorläufigen) Löschen werden die Notizen nur als gelöscht markiert und sie können innerhalb eines bestimmten Zeitraums (im Dialogfeld „Letzte Änderungen“) wiederhergestellt werden. Wenn du diese Option aktivierst, werden die Notizen sofort gelöscht und es ist nicht möglich, die Notizen wiederherzustellen.",
"erase_notes_warning": "Notizen dauerhaft löschen (kann nicht rückgängig gemacht werden), einschließlich aller Klone. Dadurch wird ein Neuladen der Anwendung erzwungen.", "erase_notes_warning": "Notizen dauerhaft löschen (kann nicht rückgängig gemacht werden), einschließlich aller Klone. Dadurch wird ein Neuladen der Anwendung erzwungen.",
"notes_to_be_deleted": "Folgende Notizen werden gelöscht (<span class=\"deleted-notes-count\"></span>)", "notes_to_be_deleted": "Folgende Notizen werden gelöscht ({{notesCount}})",
"no_note_to_delete": "Es werden keine Notizen gelöscht (nur Klone).", "no_note_to_delete": "Es werden keine Notizen gelöscht (nur Klone).",
"broken_relations_to_be_deleted": "Folgende Beziehungen werden gelöst und gelöscht (<span class=\"broke-relations-count\"></span>)", "broken_relations_to_be_deleted": "Folgende Beziehungen werden gelöst und gelöscht ({{ relationCount}})",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"ok": "OK", "ok": "OK",
"deleted_relation_text": "Notiz {{- note}} (soll gelöscht werden) wird von Beziehung {{- relation}} ausgehend von {{- source}} referenziert." "deleted_relation_text": "Notiz {{- note}} (soll gelöscht werden) wird von Beziehung {{- relation}} ausgehend von {{- source}} referenziert."
@@ -113,20 +107,18 @@
"format_pdf": "PDF - für Ausdrucke oder Teilen." "format_pdf": "PDF - für Ausdrucke oder Teilen."
}, },
"help": { "help": {
"fullDocumentation": "Hilfe (gesamte Dokumentation ist <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a> verfügbar)",
"close": "Schließen",
"noteNavigation": "Notiz Navigation", "noteNavigation": "Notiz Navigation",
"goUpDown": "<kbd>Pfeil Hoch</kbd>, <kbd>Pfeil Runter</kbd> - In der Liste der Notizen nach oben/unten gehen", "goUpDown": "In der Liste der Notizen nach oben/unten gehen",
"collapseExpand": "<kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - Knoten reduzieren/erweitern", "collapseExpand": "Knoten reduzieren/erweitern",
"notSet": "nicht eingestellt", "notSet": "nicht eingestellt",
"goBackForwards": "in der Historie zurück/vorwärts gehen", "goBackForwards": "in der Historie zurück/vorwärts gehen",
"showJumpToNoteDialog": "zeige <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Springe zu\" dialog</a>", "showJumpToNoteDialog": "zeige <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Springe zu\" dialog</a>",
"scrollToActiveNote": "Scrolle zur aktiven Notiz", "scrollToActiveNote": "Scrolle zur aktiven Notiz",
"jumpToParentNote": "<kbd>Backspace</kbd> - Zur übergeordneten Notiz springen", "jumpToParentNote": "Zur übergeordneten Notiz springen",
"collapseWholeTree": "Reduziere den gesamten Notizbaum", "collapseWholeTree": "Reduziere den gesamten Notizbaum",
"collapseSubTree": "Teilbaum einklappen", "collapseSubTree": "Teilbaum einklappen",
"tabShortcuts": "Tab-Tastenkürzel", "tabShortcuts": "Tab-Tastenkürzel",
"newTabNoteLink": "<kbd>Strg+Klick</kbd> - (oder <kbd>mittlerer Mausklick</kbd> ) auf den Notizlink öffnet die Notiz in einem neuen Tab", "newTabNoteLink": "auf den Notizlink öffnet die Notiz in einem neuen Tab",
"onlyInDesktop": "Nur im Desktop (Electron Build)", "onlyInDesktop": "Nur im Desktop (Electron Build)",
"openEmptyTab": "Leeren Tab öffnen", "openEmptyTab": "Leeren Tab öffnen",
"closeActiveTab": "Aktiven Tab schließen", "closeActiveTab": "Aktiven Tab schließen",
@@ -141,14 +133,14 @@
"moveNoteUpHierarchy": "Verschiebe die Notiz in der Hierarchie nach oben", "moveNoteUpHierarchy": "Verschiebe die Notiz in der Hierarchie nach oben",
"multiSelectNote": "Mehrfachauswahl von Notizen oben/unten", "multiSelectNote": "Mehrfachauswahl von Notizen oben/unten",
"selectAllNotes": "Wähle alle Notizen in der aktuellen Ebene aus", "selectAllNotes": "Wähle alle Notizen in der aktuellen Ebene aus",
"selectNote": "<kbd>Umschalt+Klick</kbd> - Notiz auswählen", "selectNote": "Notiz auswählen",
"copyNotes": "Kopiere aktive Notiz (oder aktuelle Auswahl) in den Zwischenspeicher (wird genutzt für <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">Klonen</a>)", "copyNotes": "Kopiere aktive Notiz (oder aktuelle Auswahl) in den Zwischenspeicher (wird genutzt für <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">Klonen</a>)",
"cutNotes": "Aktuelle Notiz (oder aktuelle Auswahl) in die Zwischenablage ausschneiden (wird zum Verschieben von Notizen verwendet)", "cutNotes": "Aktuelle Notiz (oder aktuelle Auswahl) in die Zwischenablage ausschneiden (wird zum Verschieben von Notizen verwendet)",
"pasteNotes": "Notiz(en) als Unternotiz in die aktive Notiz einfügen (entweder verschieben oder klonen, je nachdem, ob sie kopiert oder in die Zwischenablag e ausgeschnitten wurde)", "pasteNotes": "Notiz(en) als Unternotiz in die aktive Notiz einfügen (entweder verschieben oder klonen, je nachdem, ob sie kopiert oder in die Zwischenablag e ausgeschnitten wurde)",
"deleteNotes": "Notiz / Unterbaum löschen", "deleteNotes": "Notiz / Unterbaum löschen",
"editingNotes": "Notizen bearbeiten", "editingNotes": "Notizen bearbeiten",
"editNoteTitle": "Im Baumbereich wird vom Baumbereich zum Notiztitel gewechselt. Beim Druck auf Eingabe im Notiztitel, wechselt der Fokus zum Texteditor. <kbd>Strg+.</kbd> wechselt vom Editor zurück zum Baumbereich.", "editNoteTitle": "Im Baumbereich wird vom Baumbereich zum Notiztitel gewechselt. Beim Druck auf Eingabe im Notiztitel, wechselt der Fokus zum Texteditor. <kbd>Strg+.</kbd> wechselt vom Editor zurück zum Baumbereich.",
"createEditLink": "<kbd>Strg+K</kbd> - Externen Link erstellen/bearbeiten", "createEditLink": "Externen Link erstellen/bearbeiten",
"createInternalLink": "Internen Link erstellen", "createInternalLink": "Internen Link erstellen",
"followLink": "Folge dem Link unter dem Cursor", "followLink": "Folge dem Link unter dem Cursor",
"insertDateTime": "Gebe das aktuelle Datum und die aktuelle Uhrzeit an der Einfügemarke ein", "insertDateTime": "Gebe das aktuelle Datum und die aktuelle Uhrzeit an der Einfügemarke ein",
@@ -165,11 +157,11 @@
"other": "Andere", "other": "Andere",
"quickSearch": "Fokus auf schnelle Sucheingabe", "quickSearch": "Fokus auf schnelle Sucheingabe",
"inPageSearch": "Auf-der-Seite-Suche", "inPageSearch": "Auf-der-Seite-Suche",
"newTabWithActivationNoteLink": "<kbd>Strg+Umschalt+Klick</kbd> - (oder <kbd>Umschalt+Mittlere Maustaste</kbd>) auf einen Notiz-Link öffnet und aktiviert die Notiz in einem neuen Tab" "newTabWithActivationNoteLink": "auf einen Notiz-Link öffnet und aktiviert die Notiz in einem neuen Tab",
"title": "Spickzettel"
}, },
"import": { "import": {
"importIntoNote": "In Notiz importieren", "importIntoNote": "In Notiz importieren",
"close": "Schließen",
"chooseImportFile": "Wähle Importdatei aus", "chooseImportFile": "Wähle Importdatei aus",
"importDescription": "Der Inhalt der ausgewählten Datei(en) wird als untergeordnete Notiz(en) importiert", "importDescription": "Der Inhalt der ausgewählten Datei(en) wird als untergeordnete Notiz(en) importiert",
"options": "Optionen", "options": "Optionen",
@@ -196,14 +188,13 @@
}, },
"include_note": { "include_note": {
"dialog_title": "Notiz beifügen", "dialog_title": "Notiz beifügen",
"close": "Schließen",
"label_note": "Notiz", "label_note": "Notiz",
"placeholder_search": "Suche nach einer Notiz anhand ihres Namens", "placeholder_search": "Suche nach einer Notiz anhand ihres Namens",
"box_size_prompt": "Kartongröße des beigelegten Zettels:", "box_size_prompt": "Kartongröße des beigelegten Zettels:",
"box_size_small": "klein (~ 10 Zeilen)", "box_size_small": "klein (~ 10 Zeilen)",
"box_size_medium": "mittel (~ 30 Zeilen)", "box_size_medium": "mittel (~ 30 Zeilen)",
"box_size_full": "vollständig (Feld zeigt vollständigen Text)", "box_size_full": "vollständig (Feld zeigt vollständigen Text)",
"button_include": "Notiz beifügen <kbd>Eingabetaste</kbd>" "button_include": "Notiz beifügen"
}, },
"info": { "info": {
"modalTitle": "Infonachricht", "modalTitle": "Infonachricht",
@@ -211,42 +202,35 @@
"okButton": "OK" "okButton": "OK"
}, },
"jump_to_note": { "jump_to_note": {
"close": "Schließen", "search_button": "Suche im Volltext"
"search_button": "Suche im Volltext: <kbd>Strg+Eingabetaste</kbd>"
}, },
"markdown_import": { "markdown_import": {
"dialog_title": "Markdown-Import", "dialog_title": "Markdown-Import",
"close": "Schließen",
"modal_body_text": "Aufgrund der Browser-Sandbox ist es nicht möglich, die Zwischenablage direkt aus JavaScript zu lesen. Bitte füge den zu importierenden Markdown in den Textbereich unten ein und klicke auf die Schaltfläche „Importieren“.", "modal_body_text": "Aufgrund der Browser-Sandbox ist es nicht möglich, die Zwischenablage direkt aus JavaScript zu lesen. Bitte füge den zu importierenden Markdown in den Textbereich unten ein und klicke auf die Schaltfläche „Importieren“.",
"import_button": "Importieren Strg+Eingabe", "import_button": "Importieren",
"import_success": "Markdown-Inhalt wurde in das Dokument importiert." "import_success": "Markdown-Inhalt wurde in das Dokument importiert."
}, },
"move_to": { "move_to": {
"dialog_title": "Notizen verschieben nach ...", "dialog_title": "Notizen verschieben nach ...",
"close": "Schließen",
"notes_to_move": "Notizen zum Verschieben", "notes_to_move": "Notizen zum Verschieben",
"target_parent_note": "Ziel-Elternnotiz", "target_parent_note": "Ziel-Elternnotiz",
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens", "search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
"move_button": "Zur ausgewählten Notiz wechseln <kbd>Eingabetaste</kbd>", "move_button": "Zur ausgewählten Notiz wechseln",
"error_no_path": "Kein Weg, auf den man sich bewegen kann.", "error_no_path": "Kein Weg, auf den man sich bewegen kann.",
"move_success_message": "Ausgewählte Notizen wurden verschoben" "move_success_message": "Ausgewählte Notizen wurden verschoben"
}, },
"note_type_chooser": { "note_type_chooser": {
"modal_title": "Wähle den Notiztyp aus", "modal_title": "Wähle den Notiztyp aus",
"close": "Schließen",
"modal_body": "Wähle den Notiztyp / die Vorlage der neuen Notiz:", "modal_body": "Wähle den Notiztyp / die Vorlage der neuen Notiz:",
"templates": "Vorlagen:" "templates": "Vorlagen"
}, },
"password_not_set": { "password_not_set": {
"title": "Das Passwort ist nicht festgelegt", "title": "Das Passwort ist nicht festgelegt",
"close": "Schließen", "body1": "Geschützte Notizen werden mit einem Benutzerpasswort verschlüsselt, es wurde jedoch noch kein Passwort festgelegt."
"body1": "Geschützte Notizen werden mit einem Benutzerpasswort verschlüsselt, es wurde jedoch noch kein Passwort festgelegt.",
"body2": "Um Notizen verschlüsseln zu können, klicke <a class=\"open-password-options-button\" href=\"javascript:\">hier</a> um das Optionsmenu zu öffnen und ein Passwort zu setzen."
}, },
"prompt": { "prompt": {
"title": "Prompt", "title": "Prompt",
"close": "Schließen", "ok": "OK",
"ok": "OK <kbd>Eingabe</kbd>",
"defaultTitle": "Prompt" "defaultTitle": "Prompt"
}, },
"protected_session_password": { "protected_session_password": {
@@ -254,12 +238,11 @@
"help_title": "Hilfe zu geschützten Notizen", "help_title": "Hilfe zu geschützten Notizen",
"close_label": "Schließen", "close_label": "Schließen",
"form_label": "Um mit der angeforderten Aktion fortzufahren, musst du eine geschützte Sitzung starten, indem du ein Passwort eingibst:", "form_label": "Um mit der angeforderten Aktion fortzufahren, musst du eine geschützte Sitzung starten, indem du ein Passwort eingibst:",
"start_button": "Geschützte Sitzung starten <kbd>enter</kbd>" "start_button": "Geschützte Sitzung starten"
}, },
"recent_changes": { "recent_changes": {
"title": "Aktuelle Änderungen", "title": "Aktuelle Änderungen",
"erase_notes_button": "Jetzt gelöschte Notizen löschen", "erase_notes_button": "Jetzt gelöschte Notizen löschen",
"close": "Schließen",
"deleted_notes_message": "Gelöschte Notizen wurden gelöscht.", "deleted_notes_message": "Gelöschte Notizen wurden gelöscht.",
"no_changes_message": "Noch keine Änderungen...", "no_changes_message": "Noch keine Änderungen...",
"undelete_link": "Wiederherstellen", "undelete_link": "Wiederherstellen",
@@ -270,7 +253,6 @@
"delete_all_revisions": "Lösche alle Revisionen dieser Notiz", "delete_all_revisions": "Lösche alle Revisionen dieser Notiz",
"delete_all_button": "Alle Revisionen löschen", "delete_all_button": "Alle Revisionen löschen",
"help_title": "Hilfe zu Notizrevisionen", "help_title": "Hilfe zu Notizrevisionen",
"close": "Schließen",
"revision_last_edited": "Diese Revision wurde zuletzt am {{date}} bearbeitet", "revision_last_edited": "Diese Revision wurde zuletzt am {{date}} bearbeitet",
"confirm_delete_all": "Möchtest du alle Revisionen dieser Notiz löschen?", "confirm_delete_all": "Möchtest du alle Revisionen dieser Notiz löschen?",
"no_revisions": "Für diese Notiz gibt es noch keine Revisionen...", "no_revisions": "Für diese Notiz gibt es noch keine Revisionen...",
@@ -290,7 +272,6 @@
}, },
"sort_child_notes": { "sort_child_notes": {
"sort_children_by": "Unternotizen sortieren nach...", "sort_children_by": "Unternotizen sortieren nach...",
"close": "Schließen",
"sorting_criteria": "Sortierkriterien", "sorting_criteria": "Sortierkriterien",
"title": "Titel", "title": "Titel",
"date_created": "Erstellungsdatum", "date_created": "Erstellungsdatum",
@@ -304,13 +285,12 @@
"sort_with_respect_to_different_character_sorting": "Sortierung im Hinblick auf unterschiedliche Sortier- und Sortierregeln für Zeichen in verschiedenen Sprachen oder Regionen.", "sort_with_respect_to_different_character_sorting": "Sortierung im Hinblick auf unterschiedliche Sortier- und Sortierregeln für Zeichen in verschiedenen Sprachen oder Regionen.",
"natural_sort_language": "Natürliche Sortiersprache", "natural_sort_language": "Natürliche Sortiersprache",
"the_language_code_for_natural_sort": "Der Sprachcode für die natürliche Sortierung, z. B. \"de-DE\" für Deutsch.", "the_language_code_for_natural_sort": "Der Sprachcode für die natürliche Sortierung, z. B. \"de-DE\" für Deutsch.",
"sort": "Sortieren <kbd>Eingabetaste</kbd>" "sort": "Sortieren"
}, },
"upload_attachments": { "upload_attachments": {
"upload_attachments_to_note": "Lade Anhänge zur Notiz hoch", "upload_attachments_to_note": "Lade Anhänge zur Notiz hoch",
"close": "Schließen",
"choose_files": "Wähle Dateien aus", "choose_files": "Wähle Dateien aus",
"files_will_be_uploaded": "Dateien werden als Anhänge in hochgeladen", "files_will_be_uploaded": "Dateien werden als Anhänge in hochgeladen {{noteTitle}}",
"options": "Optionen", "options": "Optionen",
"shrink_images": "Bilder verkleinern", "shrink_images": "Bilder verkleinern",
"upload": "Hochladen", "upload": "Hochladen",
@@ -1099,12 +1079,9 @@
"title": "Thema", "title": "Thema",
"theme_label": "Thema", "theme_label": "Thema",
"override_theme_fonts_label": "Theme-Schriftarten überschreiben", "override_theme_fonts_label": "Theme-Schriftarten überschreiben",
"auto_theme": "Auto", "triliumnext": "Trilium (Systemfarbschema folgend)",
"light_theme": "Hell", "triliumnext-light": "Trilium (Hell)",
"dark_theme": "Dunkel", "triliumnext-dark": "Trilium (Dunkel)",
"triliumnext": "TriliumNext Beta (Systemfarbschema folgend)",
"triliumnext-light": "TriliumNext Beta (Hell)",
"triliumnext-dark": "TriliumNext Beta (Dunkel)",
"layout": "Layout", "layout": "Layout",
"layout-vertical-title": "Vertikal", "layout-vertical-title": "Vertikal",
"layout-horizontal-title": "Horizontal", "layout-horizontal-title": "Horizontal",
@@ -1651,5 +1628,8 @@
}, },
"time_selector": { "time_selector": {
"invalid_input": "Die eingegebene Zeit ist keine valide Zahl." "invalid_input": "Die eingegebene Zeit ist keine valide Zahl."
},
"modal": {
"close": "Schließen"
} }
} }

View File

@@ -1,7 +1,6 @@
{ {
"about": { "about": {
"title": "Πληροφορίες για το Trilium Notes", "title": "Πληροφορίες για το Trilium Notes",
"close": "Κλείσιμο",
"homepage": "Αρχική Σελίδα:", "homepage": "Αρχική Σελίδα:",
"app_version": "Έκδοση εφαρμογής:", "app_version": "Έκδοση εφαρμογής:",
"db_version": "Έκδοση βάσης δεδομένων:", "db_version": "Έκδοση βάσης δεδομένων:",

View File

@@ -1,7 +1,6 @@
{ {
"about": { "about": {
"title": "About Trilium Notes", "title": "About Trilium Notes",
"close": "Close",
"homepage": "Homepage:", "homepage": "Homepage:",
"app_version": "App version:", "app_version": "App version:",
"db_version": "DB version:", "db_version": "DB version:",
@@ -28,25 +27,22 @@
"add_link": { "add_link": {
"add_link": "Add link", "add_link": "Add link",
"help_on_links": "Help on links", "help_on_links": "Help on links",
"close": "Close",
"note": "Note", "note": "Note",
"search_note": "search for note by its name", "search_note": "search for note by its name",
"link_title_mirrors": "link title mirrors the note's current title", "link_title_mirrors": "link title mirrors the note's current title",
"link_title_arbitrary": "link title can be changed arbitrarily", "link_title_arbitrary": "link title can be changed arbitrarily",
"link_title": "Link title", "link_title": "Link title",
"button_add_link": "Add link <kbd>enter</kbd>" "button_add_link": "Add link"
}, },
"branch_prefix": { "branch_prefix": {
"edit_branch_prefix": "Edit branch prefix", "edit_branch_prefix": "Edit branch prefix",
"help_on_tree_prefix": "Help on Tree prefix", "help_on_tree_prefix": "Help on Tree prefix",
"close": "Close",
"prefix": "Prefix: ", "prefix": "Prefix: ",
"save": "Save", "save": "Save",
"branch_prefix_saved": "Branch prefix has been saved." "branch_prefix_saved": "Branch prefix has been saved."
}, },
"bulk_actions": { "bulk_actions": {
"bulk_actions": "Bulk actions", "bulk_actions": "Bulk actions",
"close": "Close",
"affected_notes": "Affected notes", "affected_notes": "Affected notes",
"include_descendants": "Include descendants of the selected notes", "include_descendants": "Include descendants of the selected notes",
"available_actions": "Available actions", "available_actions": "Available actions",
@@ -61,20 +57,18 @@
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Clone notes to...", "clone_notes_to": "Clone notes to...",
"close": "Close",
"help_on_links": "Help on links", "help_on_links": "Help on links",
"notes_to_clone": "Notes to clone", "notes_to_clone": "Notes to clone",
"target_parent_note": "Target parent note", "target_parent_note": "Target parent note",
"search_for_note_by_its_name": "search for note by its name", "search_for_note_by_its_name": "search for note by its name",
"cloned_note_prefix_title": "Cloned note will be shown in note tree with given prefix", "cloned_note_prefix_title": "Cloned note will be shown in note tree with given prefix",
"prefix_optional": "Prefix (optional)", "prefix_optional": "Prefix (optional)",
"clone_to_selected_note": "Clone to selected note <kbd>enter</kbd>", "clone_to_selected_note": "Clone to selected note",
"no_path_to_clone_to": "No path to clone to.", "no_path_to_clone_to": "No path to clone to.",
"note_cloned": "Note \"{{clonedTitle}}\" has been cloned into \"{{targetTitle}}\"" "note_cloned": "Note \"{{clonedTitle}}\" has been cloned into \"{{targetTitle}}\""
}, },
"confirm": { "confirm": {
"confirmation": "Confirmation", "confirmation": "Confirmation",
"close": "Close",
"cancel": "Cancel", "cancel": "Cancel",
"ok": "OK", "ok": "OK",
"are_you_sure_remove_note": "Are you sure you want to remove the note \"{{title}}\" from relation map? ", "are_you_sure_remove_note": "Are you sure you want to remove the note \"{{title}}\" from relation map? ",
@@ -87,9 +81,9 @@
"delete_all_clones_description": "Delete also all clones (can be undone in recent changes)", "delete_all_clones_description": "Delete also all clones (can be undone in recent changes)",
"erase_notes_description": "Normal (soft) deletion only marks the notes as deleted and they can be undeleted (in recent changes dialog) within a period of time. Checking this option will erase the notes immediately and it won't be possible to undelete the notes.", "erase_notes_description": "Normal (soft) deletion only marks the notes as deleted and they can be undeleted (in recent changes dialog) within a period of time. Checking this option will erase the notes immediately and it won't be possible to undelete the notes.",
"erase_notes_warning": "Erase notes permanently (can't be undone), including all clones. This will force application reload.", "erase_notes_warning": "Erase notes permanently (can't be undone), including all clones. This will force application reload.",
"notes_to_be_deleted": "Following notes will be deleted ({{- noteCount}})", "notes_to_be_deleted": "Following notes will be deleted ({{notesCount}})",
"no_note_to_delete": "No note will be deleted (only clones).", "no_note_to_delete": "No note will be deleted (only clones).",
"broken_relations_to_be_deleted": "Following relations will be broken and deleted ({{- relationCount}})", "broken_relations_to_be_deleted": "Following relations will be broken and deleted ({{ relationCount}})",
"cancel": "Cancel", "cancel": "Cancel",
"ok": "OK", "ok": "OK",
"deleted_relation_text": "Note {{- note}} (to be deleted) is referenced by relation {{- relation}} originating from {{- source}}." "deleted_relation_text": "Note {{- note}} (to be deleted) is referenced by relation {{- relation}} originating from {{- source}}."
@@ -113,21 +107,20 @@
"format_pdf": "PDF - for printing or sharing purposes." "format_pdf": "PDF - for printing or sharing purposes."
}, },
"help": { "help": {
"fullDocumentation": "Help (full documentation is available <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)", "title": "Cheatsheet",
"close": "Close",
"noteNavigation": "Note navigation", "noteNavigation": "Note navigation",
"goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> - go up/down in the list of notes", "goUpDown": "go up/down in the list of notes",
"collapseExpand": "<kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - collapse/expand node", "collapseExpand": "collapse/expand node",
"notSet": "not set", "notSet": "not set",
"goBackForwards": "go back / forwards in the history", "goBackForwards": "go back / forwards in the history",
"showJumpToNoteDialog": "show <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Jump to\" dialog</a>", "showJumpToNoteDialog": "show <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Jump to\" dialog</a>",
"scrollToActiveNote": "scroll to active note", "scrollToActiveNote": "scroll to active note",
"jumpToParentNote": "<kbd>Backspace</kbd> - jump to parent note", "jumpToParentNote": "jump to parent note",
"collapseWholeTree": "collapse whole note tree", "collapseWholeTree": "collapse whole note tree",
"collapseSubTree": "collapse sub-tree", "collapseSubTree": "collapse sub-tree",
"tabShortcuts": "Tab shortcuts", "tabShortcuts": "Tab shortcuts",
"newTabNoteLink": "<kbd>Ctrl+click</kbd> - (or <kbd>middle mouse click</kbd>) on note link opens note in a new tab", "newTabNoteLink": "on note link opens note in a new tab",
"newTabWithActivationNoteLink": "<kbd>Ctrl+Shift+click</kbd> - (or <kbd>Shift+middle mouse click</kbd>) on note link opens and activates the note in a new tab", "newTabWithActivationNoteLink": "on note link opens and activates the note in a new tab",
"onlyInDesktop": "Only in desktop (Electron build)", "onlyInDesktop": "Only in desktop (Electron build)",
"openEmptyTab": "open empty tab", "openEmptyTab": "open empty tab",
"closeActiveTab": "close active tab", "closeActiveTab": "close active tab",
@@ -142,14 +135,14 @@
"moveNoteUpHierarchy": "move note up in the hierarchy", "moveNoteUpHierarchy": "move note up in the hierarchy",
"multiSelectNote": "multi-select note above/below", "multiSelectNote": "multi-select note above/below",
"selectAllNotes": "select all notes in the current level", "selectAllNotes": "select all notes in the current level",
"selectNote": "<kbd>Shift+click</kbd> - select note", "selectNote": "select note",
"copyNotes": "copy active note (or current selection) into clipboard (used for <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">cloning</a>)", "copyNotes": "copy active note (or current selection) into clipboard (used for <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">cloning</a>)",
"cutNotes": "cut current note (or current selection) into clipboard (used for moving notes)", "cutNotes": "cut current note (or current selection) into clipboard (used for moving notes)",
"pasteNotes": "paste note(s) as sub-note into active note (which is either move or clone depending on whether it was copied or cut into clipboard)", "pasteNotes": "paste note(s) as sub-note into active note (which is either move or clone depending on whether it was copied or cut into clipboard)",
"deleteNotes": "delete note / sub-tree", "deleteNotes": "delete note / sub-tree",
"editingNotes": "Editing notes", "editingNotes": "Editing notes",
"editNoteTitle": "in tree pane will switch from tree pane into note title. Enter from note title will switch focus to text editor. <kbd>Ctrl+.</kbd> will switch back from editor to tree pane.", "editNoteTitle": "in tree pane will switch from tree pane into note title. Enter from note title will switch focus to text editor. <kbd>Ctrl+.</kbd> will switch back from editor to tree pane.",
"createEditLink": "<kbd>Ctrl+K</kbd> - create / edit external link", "createEditLink": "create / edit external link",
"createInternalLink": "create internal link", "createInternalLink": "create internal link",
"followLink": "follow link under cursor", "followLink": "follow link under cursor",
"insertDateTime": "insert current date and time at caret position", "insertDateTime": "insert current date and time at caret position",
@@ -169,7 +162,6 @@
}, },
"import": { "import": {
"importIntoNote": "Import into note", "importIntoNote": "Import into note",
"close": "Close",
"chooseImportFile": "Choose import file", "chooseImportFile": "Choose import file",
"importDescription": "Content of the selected file(s) will be imported as child note(s) into", "importDescription": "Content of the selected file(s) will be imported as child note(s) into",
"options": "Options", "options": "Options",
@@ -196,14 +188,13 @@
}, },
"include_note": { "include_note": {
"dialog_title": "Include note", "dialog_title": "Include note",
"close": "Close",
"label_note": "Note", "label_note": "Note",
"placeholder_search": "search for note by its name", "placeholder_search": "search for note by its name",
"box_size_prompt": "Box size of the included note:", "box_size_prompt": "Box size of the included note:",
"box_size_small": "small (~ 10 lines)", "box_size_small": "small (~ 10 lines)",
"box_size_medium": "medium (~ 30 lines)", "box_size_medium": "medium (~ 30 lines)",
"box_size_full": "full (box shows complete text)", "box_size_full": "full (box shows complete text)",
"button_include": "Include note <kbd>enter</kbd>" "button_include": "Include note"
}, },
"info": { "info": {
"modalTitle": "Info message", "modalTitle": "Info message",
@@ -212,23 +203,20 @@
}, },
"jump_to_note": { "jump_to_note": {
"search_placeholder": "Search for note by its name or type > for commands...", "search_placeholder": "Search for note by its name or type > for commands...",
"close": "Close", "search_button": "Search in full text"
"search_button": "Search in full text <kbd>Ctrl+Enter</kbd>"
}, },
"markdown_import": { "markdown_import": {
"dialog_title": "Markdown import", "dialog_title": "Markdown import",
"close": "Close",
"modal_body_text": "Because of browser sandbox it's not possible to directly read clipboard from JavaScript. Please paste the Markdown to import to textarea below and click on Import button", "modal_body_text": "Because of browser sandbox it's not possible to directly read clipboard from JavaScript. Please paste the Markdown to import to textarea below and click on Import button",
"import_button": "Import Ctrl+Enter", "import_button": "Import",
"import_success": "Markdown content has been imported into the document." "import_success": "Markdown content has been imported into the document."
}, },
"move_to": { "move_to": {
"dialog_title": "Move notes to ...", "dialog_title": "Move notes to ...",
"close": "Close",
"notes_to_move": "Notes to move", "notes_to_move": "Notes to move",
"target_parent_note": "Target parent note", "target_parent_note": "Target parent note",
"search_placeholder": "search for note by its name", "search_placeholder": "search for note by its name",
"move_button": "Move to selected note <kbd>enter</kbd>", "move_button": "Move to selected note",
"error_no_path": "No path to move to.", "error_no_path": "No path to move to.",
"move_success_message": "Selected notes have been moved into " "move_success_message": "Selected notes have been moved into "
}, },
@@ -236,20 +224,19 @@
"change_path_prompt": "Change where to create the new note:", "change_path_prompt": "Change where to create the new note:",
"search_placeholder": "search path by name (default if empty)", "search_placeholder": "search path by name (default if empty)",
"modal_title": "Choose note type", "modal_title": "Choose note type",
"close": "Close",
"modal_body": "Choose note type / template of the new note:", "modal_body": "Choose note type / template of the new note:",
"templates": "Templates:" "templates": "Templates",
"builtin_templates": "Built-in Templates"
}, },
"password_not_set": { "password_not_set": {
"title": "Password is not set", "title": "Password is not set",
"close": "Close",
"body1": "Protected notes are encrypted using a user password, but password has not been set yet.", "body1": "Protected notes are encrypted using a user password, but password has not been set yet.",
"body2": "To be able to protect notes, click <a class=\"open-password-options-button\" href=\"javascript:\">here</a> to open the Options dialog and set your password." "body2": "To be able to protect notes, click the button below to open the Options dialog and set your password.",
"go_to_password_options": "Go to Password options"
}, },
"prompt": { "prompt": {
"title": "Prompt", "title": "Prompt",
"close": "Close", "ok": "OK",
"ok": "OK <kbd>enter</kbd>",
"defaultTitle": "Prompt" "defaultTitle": "Prompt"
}, },
"protected_session_password": { "protected_session_password": {
@@ -257,12 +244,11 @@
"help_title": "Help on Protected notes", "help_title": "Help on Protected notes",
"close_label": "Close", "close_label": "Close",
"form_label": "To proceed with requested action you need to start protected session by entering password:", "form_label": "To proceed with requested action you need to start protected session by entering password:",
"start_button": "Start protected session <kbd>enter</kbd>" "start_button": "Start protected session"
}, },
"recent_changes": { "recent_changes": {
"title": "Recent changes", "title": "Recent changes",
"erase_notes_button": "Erase deleted notes now", "erase_notes_button": "Erase deleted notes now",
"close": "Close",
"deleted_notes_message": "Deleted notes have been erased.", "deleted_notes_message": "Deleted notes have been erased.",
"no_changes_message": "No changes yet...", "no_changes_message": "No changes yet...",
"undelete_link": "undelete", "undelete_link": "undelete",
@@ -273,7 +259,6 @@
"delete_all_revisions": "Delete all revisions of this note", "delete_all_revisions": "Delete all revisions of this note",
"delete_all_button": "Delete all revisions", "delete_all_button": "Delete all revisions",
"help_title": "Help on Note Revisions", "help_title": "Help on Note Revisions",
"close": "Close",
"revision_last_edited": "This revision was last edited on {{date}}", "revision_last_edited": "This revision was last edited on {{date}}",
"confirm_delete_all": "Do you want to delete all revisions of this note?", "confirm_delete_all": "Do you want to delete all revisions of this note?",
"no_revisions": "No revisions for this note yet...", "no_revisions": "No revisions for this note yet...",
@@ -295,7 +280,6 @@
}, },
"sort_child_notes": { "sort_child_notes": {
"sort_children_by": "Sort children by...", "sort_children_by": "Sort children by...",
"close": "Close",
"sorting_criteria": "Sorting criteria", "sorting_criteria": "Sorting criteria",
"title": "title", "title": "title",
"date_created": "date created", "date_created": "date created",
@@ -309,13 +293,12 @@
"sort_with_respect_to_different_character_sorting": "sort with respect to different character sorting and collation rules in different languages or regions.", "sort_with_respect_to_different_character_sorting": "sort with respect to different character sorting and collation rules in different languages or regions.",
"natural_sort_language": "Natural sort language", "natural_sort_language": "Natural sort language",
"the_language_code_for_natural_sort": "The language code for natural sort, e.g. \"zh-CN\" for Chinese.", "the_language_code_for_natural_sort": "The language code for natural sort, e.g. \"zh-CN\" for Chinese.",
"sort": "Sort <kbd>enter</kbd>" "sort": "Sort"
}, },
"upload_attachments": { "upload_attachments": {
"upload_attachments_to_note": "Upload attachments to note", "upload_attachments_to_note": "Upload attachments to note",
"close": "Close",
"choose_files": "Choose files", "choose_files": "Choose files",
"files_will_be_uploaded": "Files will be uploaded as attachments into", "files_will_be_uploaded": "Files will be uploaded as attachments into {{noteTitle}}",
"options": "Options", "options": "Options",
"shrink_images": "Shrink images", "shrink_images": "Shrink images",
"upload": "Upload", "upload": "Upload",
@@ -1118,12 +1101,12 @@
"title": "Application Theme", "title": "Application Theme",
"theme_label": "Theme", "theme_label": "Theme",
"override_theme_fonts_label": "Override theme fonts", "override_theme_fonts_label": "Override theme fonts",
"auto_theme": "Auto", "auto_theme": "Legacy (Follow system color scheme)",
"light_theme": "Light", "light_theme": "Legacy (Light)",
"dark_theme": "Dark", "dark_theme": "Legacy (Dark)",
"triliumnext": "TriliumNext Beta (Follow system color scheme)", "triliumnext": "Trilium (Follow system color scheme)",
"triliumnext-light": "TriliumNext Beta (Light)", "triliumnext-light": "Trilium (Light)",
"triliumnext-dark": "TriliumNext Beta (Dark)", "triliumnext-dark": "Trilium (Dark)",
"layout": "Layout", "layout": "Layout",
"layout-vertical-title": "Vertical", "layout-vertical-title": "Vertical",
"layout-horizontal-title": "Horizontal", "layout-horizontal-title": "Horizontal",
@@ -2007,6 +1990,15 @@
"open_externally": "Open externally" "open_externally": "Open externally"
}, },
"modal": { "modal": {
"close": "Close" "close": "Close",
"help_title": "Display more information about this screen"
},
"call_to_action": {
"next_theme_title": "The new Trilium theme is now stable",
"next_theme_message": "For a while now, we've been working on a new theme to give the application a more modern look.",
"next_theme_button": "Switch to the new Trilium theme",
"background_effects_title": "Background effects are now stable",
"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"
} }
} }

View File

@@ -1,7 +1,6 @@
{ {
"about": { "about": {
"title": "Acerca de Trilium Notes", "title": "Acerca de Trilium Notes",
"close": "Cerrar",
"homepage": "Página principal:", "homepage": "Página principal:",
"app_version": "Versión de la aplicación:", "app_version": "Versión de la aplicación:",
"db_version": "Versión de base de datos:", "db_version": "Versión de base de datos:",
@@ -28,25 +27,22 @@
"add_link": { "add_link": {
"add_link": "Agregar enlace", "add_link": "Agregar enlace",
"help_on_links": "Ayuda sobre enlaces", "help_on_links": "Ayuda sobre enlaces",
"close": "Cerrar",
"note": "Nota", "note": "Nota",
"search_note": "buscar nota por su nombre", "search_note": "buscar nota por su nombre",
"link_title_mirrors": "el título del enlace replica el título actual de la nota", "link_title_mirrors": "el título del enlace replica el título actual de la nota",
"link_title_arbitrary": "el título del enlace se puede cambiar arbitrariamente", "link_title_arbitrary": "el título del enlace se puede cambiar arbitrariamente",
"link_title": "Título del enlace", "link_title": "Título del enlace",
"button_add_link": "Agregar enlace <kbd>Enter</kbd>" "button_add_link": "Agregar enlace"
}, },
"branch_prefix": { "branch_prefix": {
"edit_branch_prefix": "Editar prefijo de rama", "edit_branch_prefix": "Editar prefijo de rama",
"help_on_tree_prefix": "Ayuda sobre el prefijo del árbol", "help_on_tree_prefix": "Ayuda sobre el prefijo del árbol",
"close": "Cerrar",
"prefix": "Prefijo: ", "prefix": "Prefijo: ",
"save": "Guardar", "save": "Guardar",
"branch_prefix_saved": "Se ha guardado el prefijo de rama." "branch_prefix_saved": "Se ha guardado el prefijo de rama."
}, },
"bulk_actions": { "bulk_actions": {
"bulk_actions": "Acciones en bloque", "bulk_actions": "Acciones en bloque",
"close": "Cerrar",
"affected_notes": "Notas afectadas", "affected_notes": "Notas afectadas",
"include_descendants": "Incluir descendientes de las notas seleccionadas", "include_descendants": "Incluir descendientes de las notas seleccionadas",
"available_actions": "Acciones disponibles", "available_actions": "Acciones disponibles",
@@ -61,20 +57,18 @@
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Clonar notas a...", "clone_notes_to": "Clonar notas a...",
"close": "Cerrar",
"help_on_links": "Ayuda sobre enlaces", "help_on_links": "Ayuda sobre enlaces",
"notes_to_clone": "Notas a clonar", "notes_to_clone": "Notas a clonar",
"target_parent_note": "Nota padre de destino", "target_parent_note": "Nota padre de destino",
"search_for_note_by_its_name": "buscar nota por su nombre", "search_for_note_by_its_name": "buscar nota por su nombre",
"cloned_note_prefix_title": "La nota clonada se mostrará en el árbol de notas con el prefijo dado", "cloned_note_prefix_title": "La nota clonada se mostrará en el árbol de notas con el prefijo dado",
"prefix_optional": "Prefijo (opcional)", "prefix_optional": "Prefijo (opcional)",
"clone_to_selected_note": "Clonar a nota seleccionada <kbd>enter</kbd>", "clone_to_selected_note": "Clonar a nota seleccionada",
"no_path_to_clone_to": "No hay ruta para clonar.", "no_path_to_clone_to": "No hay ruta para clonar.",
"note_cloned": "La nota \"{{clonedTitle}}\" a sido clonada en \"{{targetTitle}}\"" "note_cloned": "La nota \"{{clonedTitle}}\" a sido clonada en \"{{targetTitle}}\""
}, },
"confirm": { "confirm": {
"confirmation": "Confirmación", "confirmation": "Confirmación",
"close": "Cerrar",
"cancel": "Cancelar", "cancel": "Cancelar",
"ok": "Aceptar", "ok": "Aceptar",
"are_you_sure_remove_note": "¿Está seguro que desea eliminar la nota \"{{title}}\" del mapa de relaciones? ", "are_you_sure_remove_note": "¿Está seguro que desea eliminar la nota \"{{title}}\" del mapa de relaciones? ",
@@ -87,9 +81,9 @@
"delete_all_clones_description": "Eliminar también todos los clones (se puede deshacer en cambios recientes)", "delete_all_clones_description": "Eliminar también todos los clones (se puede deshacer en cambios recientes)",
"erase_notes_description": "La eliminación normal (suave) solo marca las notas como eliminadas y se pueden recuperar (en el cuadro de diálogo de cambios recientes) dentro de un periodo de tiempo. Al marcar esta opción se borrarán las notas inmediatamente y no será posible recuperarlas.", "erase_notes_description": "La eliminación normal (suave) solo marca las notas como eliminadas y se pueden recuperar (en el cuadro de diálogo de cambios recientes) dentro de un periodo de tiempo. Al marcar esta opción se borrarán las notas inmediatamente y no será posible recuperarlas.",
"erase_notes_warning": "Eliminar notas permanentemente (no se puede deshacer), incluidos todos los clones. Esto forzará la recarga de la aplicación.", "erase_notes_warning": "Eliminar notas permanentemente (no se puede deshacer), incluidos todos los clones. Esto forzará la recarga de la aplicación.",
"notes_to_be_deleted": "Las siguientes notas serán eliminadas ({{- noteCount}})", "notes_to_be_deleted": "Las siguientes notas serán eliminadas ({{notesCount}})",
"no_note_to_delete": "No se eliminará ninguna nota (solo clones).", "no_note_to_delete": "No se eliminará ninguna nota (solo clones).",
"broken_relations_to_be_deleted": "Las siguientes relaciones se romperán y serán eliminadas ({{- relationCount}})", "broken_relations_to_be_deleted": "Las siguientes relaciones se romperán y serán eliminadas ({{ relationCount}})",
"cancel": "Cancelar", "cancel": "Cancelar",
"ok": "Aceptar", "ok": "Aceptar",
"deleted_relation_text": "Nota {{- note}} (para ser eliminada) está referenciado por la relación {{- relation}} que se origina en {{- source}}." "deleted_relation_text": "Nota {{- note}} (para ser eliminada) está referenciado por la relación {{- relation}} que se origina en {{- source}}."
@@ -113,21 +107,19 @@
"format_pdf": "PDF - para propósitos de impresión o compartición." "format_pdf": "PDF - para propósitos de impresión o compartición."
}, },
"help": { "help": {
"fullDocumentation": "Ayuda (la documentación completa está disponible <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)",
"close": "Cerrar",
"noteNavigation": "Navegación de notas", "noteNavigation": "Navegación de notas",
"goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> - subir/bajar en la lista de notas", "goUpDown": "subir/bajar en la lista de notas",
"collapseExpand": "<kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - colapsar/expandir nodo", "collapseExpand": "colapsar/expandir nodo",
"notSet": "no establecido", "notSet": "no establecido",
"goBackForwards": "retroceder / avanzar en la historia", "goBackForwards": "retroceder / avanzar en la historia",
"showJumpToNoteDialog": "mostrar <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Saltar a\" diálogo</a>", "showJumpToNoteDialog": "mostrar <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Saltar a\" diálogo</a>",
"scrollToActiveNote": "desplazarse hasta la nota activa", "scrollToActiveNote": "desplazarse hasta la nota activa",
"jumpToParentNote": "<kbd>Backspace</kbd> - saltar a la nota padre", "jumpToParentNote": "saltar a la nota padre",
"collapseWholeTree": "colapsar todo el árbol de notas", "collapseWholeTree": "colapsar todo el árbol de notas",
"collapseSubTree": "colapsar subárbol", "collapseSubTree": "colapsar subárbol",
"tabShortcuts": "Atajos de pestañas", "tabShortcuts": "Atajos de pestañas",
"newTabNoteLink": "<kbd>CTRL+clic</kbd> - (o <kbd>clic central del mouse</kbd>) en el enlace de la nota abre la nota en una nueva pestaña", "newTabNoteLink": "en el enlace de la nota abre la nota en una nueva pestaña",
"newTabWithActivationNoteLink": "<kbd>Ctrl+Shift+clic</kbd> - (o <kbd>Shift+clic de rueda de ratón</kbd>) en el enlace de la nota abre y activa la nota en una nueva pestaña", "newTabWithActivationNoteLink": "en el enlace de la nota abre y activa la nota en una nueva pestaña",
"onlyInDesktop": "Solo en escritorio (compilación con Electron)", "onlyInDesktop": "Solo en escritorio (compilación con Electron)",
"openEmptyTab": "abrir pestaña vacía", "openEmptyTab": "abrir pestaña vacía",
"closeActiveTab": "cerrar pestaña activa", "closeActiveTab": "cerrar pestaña activa",
@@ -142,14 +134,14 @@
"moveNoteUpHierarchy": "mover nota hacia arriba en la jerarquía", "moveNoteUpHierarchy": "mover nota hacia arriba en la jerarquía",
"multiSelectNote": "selección múltiple de nota hacia arriba/abajo", "multiSelectNote": "selección múltiple de nota hacia arriba/abajo",
"selectAllNotes": "seleccionar todas las notas en el nivel actual", "selectAllNotes": "seleccionar todas las notas en el nivel actual",
"selectNote": "<kbd>Shift+click</kbd> - seleccionar nota", "selectNote": "seleccionar nota",
"copyNotes": "copiar nota activa (o selección actual) al portapapeles (usado para <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonar</a>)", "copyNotes": "copiar nota activa (o selección actual) al portapapeles (usado para <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonar</a>)",
"cutNotes": "cortar la nota actual (o la selección actual) en el portapapeles (usado para mover notas)", "cutNotes": "cortar la nota actual (o la selección actual) en el portapapeles (usado para mover notas)",
"pasteNotes": "pegar notas como subnotas en la nota activa (que se puede mover o clonar dependiendo de si se copió o cortó en el portapapeles)", "pasteNotes": "pegar notas como subnotas en la nota activa (que se puede mover o clonar dependiendo de si se copió o cortó en el portapapeles)",
"deleteNotes": "eliminar nota/subárbol", "deleteNotes": "eliminar nota/subárbol",
"editingNotes": "Editando notas", "editingNotes": "Editando notas",
"editNoteTitle": "en el panel de árbol cambiará del panel de árbol al título de la nota. Ingresar desde el título de la nota cambiará el foco al editor de texto. <kbd>Ctrl+.</kbd> cambiará de nuevo del editor al panel de árbol.", "editNoteTitle": "en el panel de árbol cambiará del panel de árbol al título de la nota. Ingresar desde el título de la nota cambiará el foco al editor de texto. <kbd>Ctrl+.</kbd> cambiará de nuevo del editor al panel de árbol.",
"createEditLink": "<kbd>Ctrl+K</kbd> - crear/editar enlace externo", "createEditLink": "crear/editar enlace externo",
"createInternalLink": "crear enlace interno", "createInternalLink": "crear enlace interno",
"followLink": "siga el enlace debajo del cursor", "followLink": "siga el enlace debajo del cursor",
"insertDateTime": "insertar la fecha y hora actuales en la posición del cursor", "insertDateTime": "insertar la fecha y hora actuales en la posición del cursor",
@@ -169,7 +161,6 @@
}, },
"import": { "import": {
"importIntoNote": "Importar a nota", "importIntoNote": "Importar a nota",
"close": "Cerrar",
"chooseImportFile": "Elija el archivo de importación", "chooseImportFile": "Elija el archivo de importación",
"importDescription": "El contenido de los archivos seleccionados se importará como notas secundarias en", "importDescription": "El contenido de los archivos seleccionados se importará como notas secundarias en",
"options": "Opciones", "options": "Opciones",
@@ -196,14 +187,13 @@
}, },
"include_note": { "include_note": {
"dialog_title": "Incluir nota", "dialog_title": "Incluir nota",
"close": "Cerrar",
"label_note": "Nota", "label_note": "Nota",
"placeholder_search": "buscar nota por su nombre", "placeholder_search": "buscar nota por su nombre",
"box_size_prompt": "Tamaño de caja de la nota incluida:", "box_size_prompt": "Tamaño de caja de la nota incluida:",
"box_size_small": "pequeño (~ 10 líneas)", "box_size_small": "pequeño (~ 10 líneas)",
"box_size_medium": "medio (~ 30 líneas)", "box_size_medium": "medio (~ 30 líneas)",
"box_size_full": "completo (el cuadro muestra el texto completo)", "box_size_full": "completo (el cuadro muestra el texto completo)",
"button_include": "Incluir nota <kbd>Enter</kbd>" "button_include": "Incluir nota"
}, },
"info": { "info": {
"modalTitle": "Mensaje informativo", "modalTitle": "Mensaje informativo",
@@ -211,24 +201,21 @@
"okButton": "Aceptar" "okButton": "Aceptar"
}, },
"jump_to_note": { "jump_to_note": {
"close": "Cerrar", "search_button": "Buscar en texto completo",
"search_button": "Buscar en texto completo <kbd>Ctrl+Enter</kbd>",
"search_placeholder": "Busque nota por su nombre o escriba > para comandos..." "search_placeholder": "Busque nota por su nombre o escriba > para comandos..."
}, },
"markdown_import": { "markdown_import": {
"dialog_title": "Importación de Markdown", "dialog_title": "Importación de Markdown",
"close": "Cerrar",
"modal_body_text": "Debido al entorno limitado del navegador, no es posible leer directamente el portapapeles desde JavaScript. Por favor, pegue el código Markdown para importar en el área de texto a continuación y haga clic en el botón Importar", "modal_body_text": "Debido al entorno limitado del navegador, no es posible leer directamente el portapapeles desde JavaScript. Por favor, pegue el código Markdown para importar en el área de texto a continuación y haga clic en el botón Importar",
"import_button": "Importar Ctrl+Enter", "import_button": "Importar",
"import_success": "El contenido de Markdown se ha importado al documento." "import_success": "El contenido de Markdown se ha importado al documento."
}, },
"move_to": { "move_to": {
"dialog_title": "Mover notas a...", "dialog_title": "Mover notas a...",
"close": "Cerrar",
"notes_to_move": "Notas a mover", "notes_to_move": "Notas a mover",
"target_parent_note": "Nota padre de destino", "target_parent_note": "Nota padre de destino",
"search_placeholder": "buscar nota por su nombre", "search_placeholder": "buscar nota por su nombre",
"move_button": "Mover a la nota seleccionada <kbd>enter</kbd>", "move_button": "Mover a la nota seleccionada",
"error_no_path": "No hay ruta a donde mover.", "error_no_path": "No hay ruta a donde mover.",
"move_success_message": "Las notas seleccionadas se han movido a " "move_success_message": "Las notas seleccionadas se han movido a "
}, },
@@ -236,20 +223,16 @@
"change_path_prompt": "Cambiar donde se creará la nueva nota:", "change_path_prompt": "Cambiar donde se creará la nueva nota:",
"search_placeholder": "ruta de búsqueda por nombre (por defecto si está vacío)", "search_placeholder": "ruta de búsqueda por nombre (por defecto si está vacío)",
"modal_title": "Elija el tipo de nota", "modal_title": "Elija el tipo de nota",
"close": "Cerrar",
"modal_body": "Elija el tipo de nota/plantilla de la nueva nota:", "modal_body": "Elija el tipo de nota/plantilla de la nueva nota:",
"templates": "Plantillas:" "templates": "Plantillas"
}, },
"password_not_set": { "password_not_set": {
"title": "La contraseña no está establecida", "title": "La contraseña no está establecida",
"close": "Cerrar", "body1": "Las notas protegidas se cifran mediante una contraseña de usuario, pero la contraseña aún no se ha establecido."
"body1": "Las notas protegidas se cifran mediante una contraseña de usuario, pero la contraseña aún no se ha establecido.",
"body2": "Para poder proteger notas, dé clic <a class=\"open-password-options-button\" href=\"javascript:\">aquí</a> para abrir el diálogo de Opciones y establecer tu contraseña."
}, },
"prompt": { "prompt": {
"title": "Aviso", "title": "Aviso",
"close": "Cerrar", "ok": "Aceptar",
"ok": "Aceptar <kbd>enter</kbd>",
"defaultTitle": "Aviso" "defaultTitle": "Aviso"
}, },
"protected_session_password": { "protected_session_password": {
@@ -257,12 +240,11 @@
"help_title": "Ayuda sobre notas protegidas", "help_title": "Ayuda sobre notas protegidas",
"close_label": "Cerrar", "close_label": "Cerrar",
"form_label": "Para continuar con la acción solicitada, debe iniciar en la sesión protegida ingresando la contraseña:", "form_label": "Para continuar con la acción solicitada, debe iniciar en la sesión protegida ingresando la contraseña:",
"start_button": "Iniciar sesión protegida <kbd>entrar</kbd>" "start_button": "Iniciar sesión protegida"
}, },
"recent_changes": { "recent_changes": {
"title": "Cambios recientes", "title": "Cambios recientes",
"erase_notes_button": "Borrar notas eliminadas ahora", "erase_notes_button": "Borrar notas eliminadas ahora",
"close": "Cerrar",
"deleted_notes_message": "Las notas eliminadas han sido borradas.", "deleted_notes_message": "Las notas eliminadas han sido borradas.",
"no_changes_message": "Aún no hay cambios...", "no_changes_message": "Aún no hay cambios...",
"undelete_link": "recuperar", "undelete_link": "recuperar",
@@ -273,7 +255,6 @@
"delete_all_revisions": "Eliminar todas las revisiones de esta nota", "delete_all_revisions": "Eliminar todas las revisiones de esta nota",
"delete_all_button": "Eliminar todas las revisiones", "delete_all_button": "Eliminar todas las revisiones",
"help_title": "Ayuda sobre revisiones de notas", "help_title": "Ayuda sobre revisiones de notas",
"close": "Cerrar",
"revision_last_edited": "Esta revisión se editó por última vez en {{date}}", "revision_last_edited": "Esta revisión se editó por última vez en {{date}}",
"confirm_delete_all": "¿Quiere eliminar todas las revisiones de esta nota?", "confirm_delete_all": "¿Quiere eliminar todas las revisiones de esta nota?",
"no_revisions": "Aún no hay revisiones para esta nota...", "no_revisions": "Aún no hay revisiones para esta nota...",
@@ -295,7 +276,6 @@
}, },
"sort_child_notes": { "sort_child_notes": {
"sort_children_by": "Ordenar hijos por...", "sort_children_by": "Ordenar hijos por...",
"close": "Cerrar",
"sorting_criteria": "Criterios de ordenamiento", "sorting_criteria": "Criterios de ordenamiento",
"title": "título", "title": "título",
"date_created": "fecha de creación", "date_created": "fecha de creación",
@@ -309,13 +289,12 @@
"sort_with_respect_to_different_character_sorting": "ordenar con respecto a diferentes reglas de ordenamiento y clasificación de caracteres en diferentes idiomas o regiones.", "sort_with_respect_to_different_character_sorting": "ordenar con respecto a diferentes reglas de ordenamiento y clasificación de caracteres en diferentes idiomas o regiones.",
"natural_sort_language": "Idioma de clasificación natural", "natural_sort_language": "Idioma de clasificación natural",
"the_language_code_for_natural_sort": "El código del idioma para el ordenamiento natural, ej. \"zh-CN\" para Chino.", "the_language_code_for_natural_sort": "El código del idioma para el ordenamiento natural, ej. \"zh-CN\" para Chino.",
"sort": "Ordenar <kbd>Enter</kbd>" "sort": "Ordenar"
}, },
"upload_attachments": { "upload_attachments": {
"upload_attachments_to_note": "Cargar archivos adjuntos a nota", "upload_attachments_to_note": "Cargar archivos adjuntos a nota",
"close": "Cerrar",
"choose_files": "Elija los archivos", "choose_files": "Elija los archivos",
"files_will_be_uploaded": "Los archivos se cargarán como archivos adjuntos en", "files_will_be_uploaded": "Los archivos se cargarán como archivos adjuntos en {{noteTitle}}",
"options": "Opciones", "options": "Opciones",
"shrink_images": "Reducir imágenes", "shrink_images": "Reducir imágenes",
"upload": "Subir", "upload": "Subir",
@@ -1118,12 +1097,9 @@
"title": "Tema", "title": "Tema",
"theme_label": "Tema", "theme_label": "Tema",
"override_theme_fonts_label": "Sobreescribir fuentes de tema", "override_theme_fonts_label": "Sobreescribir fuentes de tema",
"auto_theme": "Automático", "triliumnext": "Trilium (Sigue el esquema de color del sistema)",
"light_theme": "Claro", "triliumnext-light": "Trilium (Claro)",
"dark_theme": "Oscuro", "triliumnext-dark": "Trilium (Oscuro)",
"triliumnext": "TriliumNext Beta (Sigue el esquema de color del sistema)",
"triliumnext-light": "TriliumNext Beta (Claro)",
"triliumnext-dark": "TriliumNext Beta (Oscuro)",
"layout": "Disposición", "layout": "Disposición",
"layout-vertical-title": "Vertical", "layout-vertical-title": "Vertical",
"layout-horizontal-title": "Horizontal", "layout-horizontal-title": "Horizontal",

View File

@@ -1,7 +1,6 @@
{ {
"about": { "about": {
"title": "À propos de Trilium Notes", "title": "À propos de Trilium Notes",
"close": "Fermer",
"homepage": "Page d'accueil :", "homepage": "Page d'accueil :",
"app_version": "Version de l'application :", "app_version": "Version de l'application :",
"db_version": "Version de la base de données :", "db_version": "Version de la base de données :",
@@ -28,25 +27,22 @@
"add_link": { "add_link": {
"add_link": "Ajouter un lien", "add_link": "Ajouter un lien",
"help_on_links": "Aide sur les liens", "help_on_links": "Aide sur les liens",
"close": "Fermer",
"note": "Note", "note": "Note",
"search_note": "rechercher une note par son nom", "search_note": "rechercher une note par son nom",
"link_title_mirrors": "le titre du lien reflète le titre actuel de la note", "link_title_mirrors": "le titre du lien reflète le titre actuel de la note",
"link_title_arbitrary": "le titre du lien peut être modifié arbitrairement", "link_title_arbitrary": "le titre du lien peut être modifié arbitrairement",
"link_title": "Titre du lien", "link_title": "Titre du lien",
"button_add_link": "Ajouter un lien <kbd>Entrée</kbd>" "button_add_link": "Ajouter un lien"
}, },
"branch_prefix": { "branch_prefix": {
"edit_branch_prefix": "Modifier le préfixe de branche", "edit_branch_prefix": "Modifier le préfixe de branche",
"help_on_tree_prefix": "Aide sur le préfixe de l'arbre", "help_on_tree_prefix": "Aide sur le préfixe de l'arbre",
"close": "Fermer",
"prefix": "Préfixe : ", "prefix": "Préfixe : ",
"save": "Sauvegarder", "save": "Sauvegarder",
"branch_prefix_saved": "Le préfixe de la branche a été enregistré." "branch_prefix_saved": "Le préfixe de la branche a été enregistré."
}, },
"bulk_actions": { "bulk_actions": {
"bulk_actions": "Actions groupées", "bulk_actions": "Actions groupées",
"close": "Fermer",
"affected_notes": "Notes concernées", "affected_notes": "Notes concernées",
"include_descendants": "Inclure les descendants des notes sélectionnées", "include_descendants": "Inclure les descendants des notes sélectionnées",
"available_actions": "Actions disponibles", "available_actions": "Actions disponibles",
@@ -61,20 +57,18 @@
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Cloner les notes dans...", "clone_notes_to": "Cloner les notes dans...",
"close": "Fermer",
"help_on_links": "Aide sur les liens", "help_on_links": "Aide sur les liens",
"notes_to_clone": "Notes à cloner", "notes_to_clone": "Notes à cloner",
"target_parent_note": "Note parent cible", "target_parent_note": "Note parent cible",
"search_for_note_by_its_name": "rechercher une note par son nom", "search_for_note_by_its_name": "rechercher une note par son nom",
"cloned_note_prefix_title": "La note clonée sera affichée dans l'arbre des notes avec le préfixe donné", "cloned_note_prefix_title": "La note clonée sera affichée dans l'arbre des notes avec le préfixe donné",
"prefix_optional": "Préfixe (facultatif)", "prefix_optional": "Préfixe (facultatif)",
"clone_to_selected_note": "Cloner vers la note sélectionnée <kbd>entrer</kbd>", "clone_to_selected_note": "Cloner vers la note sélectionnée",
"no_path_to_clone_to": "Aucun chemin vers lequel cloner.", "no_path_to_clone_to": "Aucun chemin vers lequel cloner.",
"note_cloned": "La note \"{{clonedTitle}}\" a été clonée dans \"{{targetTitle}}\"" "note_cloned": "La note \"{{clonedTitle}}\" a été clonée dans \"{{targetTitle}}\""
}, },
"confirm": { "confirm": {
"confirmation": "Confirmation", "confirmation": "Confirmation",
"close": "Fermer",
"cancel": "Annuler", "cancel": "Annuler",
"ok": "OK", "ok": "OK",
"are_you_sure_remove_note": "Voulez-vous vraiment supprimer la note « {{title}} » de la carte des relations ? ", "are_you_sure_remove_note": "Voulez-vous vraiment supprimer la note « {{title}} » de la carte des relations ? ",
@@ -87,9 +81,9 @@
"delete_all_clones_description": "Supprimer aussi les clones (peut être annulé dans des modifications récentes)", "delete_all_clones_description": "Supprimer aussi les clones (peut être annulé dans des modifications récentes)",
"erase_notes_description": "La suppression normale (douce) marque uniquement les notes comme supprimées et elles peuvent être restaurées (dans la boîte de dialogue des Modifications récentes) dans un délai donné. Cocher cette option effacera les notes immédiatement et il ne sera pas possible de les restaurer.", "erase_notes_description": "La suppression normale (douce) marque uniquement les notes comme supprimées et elles peuvent être restaurées (dans la boîte de dialogue des Modifications récentes) dans un délai donné. Cocher cette option effacera les notes immédiatement et il ne sera pas possible de les restaurer.",
"erase_notes_warning": "Efface les notes de manière permanente (ne peut pas être annulée), y compris les clones. L'application va être rechargée.", "erase_notes_warning": "Efface les notes de manière permanente (ne peut pas être annulée), y compris les clones. L'application va être rechargée.",
"notes_to_be_deleted": "Les notes suivantes seront supprimées ({{- noteCount}})", "notes_to_be_deleted": "Les notes suivantes seront supprimées ({{notesCount}})",
"no_note_to_delete": "Aucune note ne sera supprimée (uniquement les clones).", "no_note_to_delete": "Aucune note ne sera supprimée (uniquement les clones).",
"broken_relations_to_be_deleted": "Les relations suivantes seront rompues et supprimées ({{- relationCount}})", "broken_relations_to_be_deleted": "Les relations suivantes seront rompues et supprimées ({{ relationCount}})",
"cancel": "Annuler", "cancel": "Annuler",
"ok": "OK", "ok": "OK",
"deleted_relation_text": "Note {{- note}} (à supprimer) est référencée dans la relation {{- relation}} provenant de {{- source}}." "deleted_relation_text": "Note {{- note}} (à supprimer) est référencée dans la relation {{- relation}} provenant de {{- source}}."
@@ -113,20 +107,18 @@
"format_pdf": "PDF - pour l'impression ou le partage de documents." "format_pdf": "PDF - pour l'impression ou le partage de documents."
}, },
"help": { "help": {
"fullDocumentation": "Aide (la documentation complète est disponible <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">en ligne</a>)",
"close": "Fermer",
"noteNavigation": "Navigation dans les notes", "noteNavigation": "Navigation dans les notes",
"goUpDown": "<kbd>HAUT</kbd>, <kbd>BAS</kbd> - aller vers le haut/bas dans la liste des notes", "goUpDown": "aller vers le haut/bas dans la liste des notes",
"collapseExpand": "<kbd>GAUCHE</kbd>, <kbd>DROITE</kbd> - réduire/développer le nœud", "collapseExpand": "réduire/développer le nœud",
"notSet": "non défini", "notSet": "non défini",
"goBackForwards": "reculer/avancer dans l'historique", "goBackForwards": "reculer/avancer dans l'historique",
"showJumpToNoteDialog": "afficher la <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">boîte de dialogue \"Aller à la note\"</a>", "showJumpToNoteDialog": "afficher la <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">boîte de dialogue \"Aller à la note\"</a>",
"scrollToActiveNote": "faire défiler jusqu'à la note active", "scrollToActiveNote": "faire défiler jusqu'à la note active",
"jumpToParentNote": "<kbd>Retour arrière</kbd> - aller à la note parent", "jumpToParentNote": "aller à la note parent",
"collapseWholeTree": "réduire tout l'arbre des notes", "collapseWholeTree": "réduire tout l'arbre des notes",
"collapseSubTree": "réduire le sous-arbre", "collapseSubTree": "réduire le sous-arbre",
"tabShortcuts": "Raccourcis des onglets", "tabShortcuts": "Raccourcis des onglets",
"newTabNoteLink": "<kbd>CTRL+clic</kbd> - (ou clic central de la souris) sur le lien de la note ouvre la note dans un nouvel onglet", "newTabNoteLink": "sur le lien de la note ouvre la note dans un nouvel onglet",
"onlyInDesktop": "Uniquement sur ordinateur (version Electron)", "onlyInDesktop": "Uniquement sur ordinateur (version Electron)",
"openEmptyTab": "ouvrir un onglet vide", "openEmptyTab": "ouvrir un onglet vide",
"closeActiveTab": "fermer l'onglet actif", "closeActiveTab": "fermer l'onglet actif",
@@ -141,14 +133,14 @@
"moveNoteUpHierarchy": "déplacer la note vers le haut dans la hiérarchie", "moveNoteUpHierarchy": "déplacer la note vers le haut dans la hiérarchie",
"multiSelectNote": "sélectionner plusieurs notes au-dessus/au-dessous", "multiSelectNote": "sélectionner plusieurs notes au-dessus/au-dessous",
"selectAllNotes": "sélectionner toutes les notes du niveau actuel", "selectAllNotes": "sélectionner toutes les notes du niveau actuel",
"selectNote": "<kbd>Shift+clic</kbd> - sélectionner une note", "selectNote": "sélectionner une note",
"copyNotes": "copier la note active (ou la sélection actuelle) dans le presse-papiers (utilisé pour le <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonage</a>)", "copyNotes": "copier la note active (ou la sélection actuelle) dans le presse-papiers (utilisé pour le <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonage</a>)",
"cutNotes": "couper la note actuelle (ou la sélection actuelle) dans le presse-papiers (utilisé pour déplacer les notes)", "cutNotes": "couper la note actuelle (ou la sélection actuelle) dans le presse-papiers (utilisé pour déplacer les notes)",
"pasteNotes": "coller la ou les notes en tant que sous-note dans la note active (qui est soit déplacée, soit clonée selon qu'elle a été copiée ou coupée dans le presse-papiers)", "pasteNotes": "coller la ou les notes en tant que sous-note dans la note active (qui est soit déplacée, soit clonée selon qu'elle a été copiée ou coupée dans le presse-papiers)",
"deleteNotes": "supprimer une note / un sous-arbre", "deleteNotes": "supprimer une note / un sous-arbre",
"editingNotes": "Édition des notes", "editingNotes": "Édition des notes",
"editNoteTitle": "dans le volet de l'arborescence, basculera du volet au titre de la note. Presser Entrer à partir du titre de la note basculera vers léditeur de texte. <kbd>Ctrl+.</kbd> bascule de l'éditeur au volet arborescent.", "editNoteTitle": "dans le volet de l'arborescence, basculera du volet au titre de la note. Presser Entrer à partir du titre de la note basculera vers léditeur de texte. <kbd>Ctrl+.</kbd> bascule de l'éditeur au volet arborescent.",
"createEditLink": "<kbd>Ctrl+K</kbd> - créer/éditer un lien externe", "createEditLink": "créer/éditer un lien externe",
"createInternalLink": "créer un lien interne", "createInternalLink": "créer un lien interne",
"followLink": "suivre le lien sous le curseur", "followLink": "suivre le lien sous le curseur",
"insertDateTime": "insérer la date et l'heure courante à la position du curseur", "insertDateTime": "insérer la date et l'heure courante à la position du curseur",
@@ -168,7 +160,6 @@
}, },
"import": { "import": {
"importIntoNote": "Importer dans la note", "importIntoNote": "Importer dans la note",
"close": "Fermer",
"chooseImportFile": "Choisissez le fichier à importer", "chooseImportFile": "Choisissez le fichier à importer",
"importDescription": "Le contenu du ou des fichiers sélectionnés sera importé en tant que note(s) enfant dans", "importDescription": "Le contenu du ou des fichiers sélectionnés sera importé en tant que note(s) enfant dans",
"options": "Options", "options": "Options",
@@ -195,14 +186,13 @@
}, },
"include_note": { "include_note": {
"dialog_title": "Inclure une note", "dialog_title": "Inclure une note",
"close": "Fermer",
"label_note": "Note", "label_note": "Note",
"placeholder_search": "rechercher une note par son nom", "placeholder_search": "rechercher une note par son nom",
"box_size_prompt": "Taille de la boîte de la note incluse :", "box_size_prompt": "Taille de la boîte de la note incluse :",
"box_size_small": "petit (~ 10 lignes)", "box_size_small": "petit (~ 10 lignes)",
"box_size_medium": "moyen (~ 30 lignes)", "box_size_medium": "moyen (~ 30 lignes)",
"box_size_full": "complet (la boîte affiche le texte complet)", "box_size_full": "complet (la boîte affiche le texte complet)",
"button_include": "Inclure une note <kbd>Entrée</kbd>" "button_include": "Inclure une note"
}, },
"info": { "info": {
"modalTitle": "Message d'information", "modalTitle": "Message d'information",
@@ -210,42 +200,35 @@
"okButton": "OK" "okButton": "OK"
}, },
"jump_to_note": { "jump_to_note": {
"close": "Fermer", "search_button": "Rechercher dans le texte intégral"
"search_button": "Rechercher dans le texte intégral <kbd>Ctrl+Entrée</kbd>"
}, },
"markdown_import": { "markdown_import": {
"dialog_title": "Importation Markdown", "dialog_title": "Importation Markdown",
"close": "Fermer",
"modal_body_text": "En raison du bac à sable du navigateur, il n'est pas possible de lire directement le presse-papiers à partir de JavaScript. Veuillez coller le Markdown à importer dans la zone de texte ci-dessous et cliquez sur le bouton Importer", "modal_body_text": "En raison du bac à sable du navigateur, il n'est pas possible de lire directement le presse-papiers à partir de JavaScript. Veuillez coller le Markdown à importer dans la zone de texte ci-dessous et cliquez sur le bouton Importer",
"import_button": "Importer Ctrl+Entrée", "import_button": "Importer",
"import_success": "Le contenu Markdown a été importé dans le document." "import_success": "Le contenu Markdown a été importé dans le document."
}, },
"move_to": { "move_to": {
"dialog_title": "Déplacer les notes vers...", "dialog_title": "Déplacer les notes vers...",
"close": "Fermer",
"notes_to_move": "Notes à déplacer", "notes_to_move": "Notes à déplacer",
"target_parent_note": "Note parent cible", "target_parent_note": "Note parent cible",
"search_placeholder": "rechercher une note par son nom", "search_placeholder": "rechercher une note par son nom",
"move_button": "Déplacer vers la note sélectionnée <kbd>entrer</kbd>", "move_button": "Déplacer vers la note sélectionnée",
"error_no_path": "Aucun chemin vers lequel déplacer.", "error_no_path": "Aucun chemin vers lequel déplacer.",
"move_success_message": "Les notes sélectionnées ont été déplacées dans " "move_success_message": "Les notes sélectionnées ont été déplacées dans "
}, },
"note_type_chooser": { "note_type_chooser": {
"modal_title": "Choisissez le type de note", "modal_title": "Choisissez le type de note",
"close": "Fermer",
"modal_body": "Choisissez le type de note/le modèle de la nouvelle note :", "modal_body": "Choisissez le type de note/le modèle de la nouvelle note :",
"templates": "Modèles :" "templates": "Modèles"
}, },
"password_not_set": { "password_not_set": {
"title": "Le mot de passe n'est pas défini", "title": "Le mot de passe n'est pas défini",
"close": "Fermer", "body1": "Les notes protégées sont cryptées à l'aide d'un mot de passe utilisateur, mais le mot de passe n'a pas encore été défini."
"body1": "Les notes protégées sont cryptées à l'aide d'un mot de passe utilisateur, mais le mot de passe n'a pas encore été défini.",
"body2": "Pour pouvoir protéger les notes, cliquez <a class=\"open-password-options-button\" href=\"javascript:\">ici</a> pour ouvrir les Options et définir votre mot de passe."
}, },
"prompt": { "prompt": {
"title": "Prompt", "title": "Prompt",
"close": "Fermer", "ok": "OK",
"ok": "OK <kbd>entrer</kbd>",
"defaultTitle": "Prompt" "defaultTitle": "Prompt"
}, },
"protected_session_password": { "protected_session_password": {
@@ -253,12 +236,11 @@
"help_title": "Aide sur les notes protégées", "help_title": "Aide sur les notes protégées",
"close_label": "Fermer", "close_label": "Fermer",
"form_label": "Pour procéder à l'action demandée, vous devez démarrer une session protégée en saisissant le mot de passe :", "form_label": "Pour procéder à l'action demandée, vous devez démarrer une session protégée en saisissant le mot de passe :",
"start_button": "Démarrer une session protégée <kbd>entrer</kbd>" "start_button": "Démarrer une session protégée"
}, },
"recent_changes": { "recent_changes": {
"title": "Modifications récentes", "title": "Modifications récentes",
"erase_notes_button": "Effacer les notes supprimées maintenant", "erase_notes_button": "Effacer les notes supprimées maintenant",
"close": "Fermer",
"deleted_notes_message": "Les notes supprimées ont été effacées.", "deleted_notes_message": "Les notes supprimées ont été effacées.",
"no_changes_message": "Aucun changement pour l'instant...", "no_changes_message": "Aucun changement pour l'instant...",
"undelete_link": "annuler la suppression", "undelete_link": "annuler la suppression",
@@ -269,7 +251,6 @@
"delete_all_revisions": "Supprimer toutes les versions de cette note", "delete_all_revisions": "Supprimer toutes les versions de cette note",
"delete_all_button": "Supprimer toutes les versions", "delete_all_button": "Supprimer toutes les versions",
"help_title": "Aide sur les versions de notes", "help_title": "Aide sur les versions de notes",
"close": "Fermer",
"revision_last_edited": "Cette version a été modifiée pour la dernière fois le {{date}}", "revision_last_edited": "Cette version a été modifiée pour la dernière fois le {{date}}",
"confirm_delete_all": "Voulez-vous supprimer toutes les versions de cette note ?", "confirm_delete_all": "Voulez-vous supprimer toutes les versions de cette note ?",
"no_revisions": "Aucune version pour cette note pour l'instant...", "no_revisions": "Aucune version pour cette note pour l'instant...",
@@ -289,7 +270,6 @@
}, },
"sort_child_notes": { "sort_child_notes": {
"sort_children_by": "Trier les enfants par...", "sort_children_by": "Trier les enfants par...",
"close": "Fermer",
"sorting_criteria": "Critères de tri", "sorting_criteria": "Critères de tri",
"title": "titre", "title": "titre",
"date_created": "date de création", "date_created": "date de création",
@@ -303,13 +283,12 @@
"sort_with_respect_to_different_character_sorting": "trier en fonction de différentes règles de tri et de classement des caractères dans différentes langues ou régions.", "sort_with_respect_to_different_character_sorting": "trier en fonction de différentes règles de tri et de classement des caractères dans différentes langues ou régions.",
"natural_sort_language": "Langage de tri naturel", "natural_sort_language": "Langage de tri naturel",
"the_language_code_for_natural_sort": "Le code de langue pour le tri naturel, par ex. \"zh-CN\" pour le chinois.", "the_language_code_for_natural_sort": "Le code de langue pour le tri naturel, par ex. \"zh-CN\" pour le chinois.",
"sort": "Trier <kbd>Entrée</kbd>" "sort": "Trier"
}, },
"upload_attachments": { "upload_attachments": {
"upload_attachments_to_note": "Téléverser des pièces jointes à la note", "upload_attachments_to_note": "Téléverser des pièces jointes à la note",
"close": "Fermer",
"choose_files": "Choisir des fichiers", "choose_files": "Choisir des fichiers",
"files_will_be_uploaded": "Les fichiers seront téléversés sous forme de pièces jointes dans", "files_will_be_uploaded": "Les fichiers seront téléversés sous forme de pièces jointes dans {{noteTitle}}",
"options": "Options", "options": "Options",
"shrink_images": "Réduire les images", "shrink_images": "Réduire les images",
"upload": "Téléverser", "upload": "Téléverser",
@@ -1103,12 +1082,9 @@
"title": "Thème de l'application", "title": "Thème de l'application",
"theme_label": "Thème", "theme_label": "Thème",
"override_theme_fonts_label": "Remplacer les polices du thème", "override_theme_fonts_label": "Remplacer les polices du thème",
"auto_theme": "Auto", "triliumnext": "Trilium (Suit le thème du système)",
"light_theme": "Lumière", "triliumnext-light": "Trilium (Clair)",
"dark_theme": "Sombre", "triliumnext-dark": "Trilium (sombre)",
"triliumnext": "TriliumNext Beta (Suit le thème du système)",
"triliumnext-light": "TriliumNext Beta (Clair)",
"triliumnext-dark": "TriliumNext Beta (sombre)",
"layout": "Disposition", "layout": "Disposition",
"layout-vertical-title": "Vertical", "layout-vertical-title": "Vertical",
"layout-horizontal-title": "Horizontal", "layout-horizontal-title": "Horizontal",
@@ -1669,5 +1645,8 @@
}, },
"multi_factor_authentication": { "multi_factor_authentication": {
"oauth_user_email": "Courriel de l'utilisateur : " "oauth_user_email": "Courriel de l'utilisateur : "
},
"modal": {
"close": "Fermer"
} }
} }

View File

@@ -1,6 +1,5 @@
{ {
"about": { "about": {
"close": "Chiudi",
"app_version": "Versione dell'app:", "app_version": "Versione dell'app:",
"db_version": "Versione DB:", "db_version": "Versione DB:",
"sync_version": "Versione Sync:", "sync_version": "Versione Sync:",
@@ -27,7 +26,6 @@
}, },
"add_link": { "add_link": {
"add_link": "Aggiungi un collegamento", "add_link": "Aggiungi un collegamento",
"close": "Chiudi",
"note": "Nota", "note": "Nota",
"search_note": "cerca una nota per nome", "search_note": "cerca una nota per nome",
"link_title_mirrors": "il titolo del collegamento rispecchia il titolo della nota corrente", "link_title_mirrors": "il titolo del collegamento rispecchia il titolo della nota corrente",
@@ -39,14 +37,12 @@
"branch_prefix": { "branch_prefix": {
"edit_branch_prefix": "Modifica il prefisso del ramo", "edit_branch_prefix": "Modifica il prefisso del ramo",
"help_on_tree_prefix": "Aiuto sui prefissi dell'Albero", "help_on_tree_prefix": "Aiuto sui prefissi dell'Albero",
"close": "Chiudi",
"prefix": "Prefisso: ", "prefix": "Prefisso: ",
"save": "Salva", "save": "Salva",
"branch_prefix_saved": "Il prefisso del ramo è stato salvato." "branch_prefix_saved": "Il prefisso del ramo è stato salvato."
}, },
"bulk_actions": { "bulk_actions": {
"bulk_actions": "Azioni massive", "bulk_actions": "Azioni massive",
"close": "Chiudi",
"affected_notes": "Note influenzate", "affected_notes": "Note influenzate",
"include_descendants": "Includi i discendenti della nota selezionata", "include_descendants": "Includi i discendenti della nota selezionata",
"available_actions": "Azioni disponibili", "available_actions": "Azioni disponibili",
@@ -61,7 +57,6 @@
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Clona note in...", "clone_notes_to": "Clona note in...",
"close": "Chiudi",
"help_on_links": "Aiuto sui collegamenti", "help_on_links": "Aiuto sui collegamenti",
"notes_to_clone": "Note da clonare", "notes_to_clone": "Note da clonare",
"target_parent_note": "Nodo padre obiettivo", "target_parent_note": "Nodo padre obiettivo",
@@ -73,7 +68,6 @@
"note_cloned": "La nota \"{{clonedTitle}}\" è stata clonata in \"{{targetTitle}}\"" "note_cloned": "La nota \"{{clonedTitle}}\" è stata clonata in \"{{targetTitle}}\""
}, },
"confirm": { "confirm": {
"close": "Chiudi",
"cancel": "Annulla", "cancel": "Annulla",
"ok": "OK", "ok": "OK",
"confirmation": "Conferma", "confirmation": "Conferma",
@@ -106,47 +100,19 @@
"choose_export_type": "Scegli prima il tipo di esportazione, per favore", "choose_export_type": "Scegli prima il tipo di esportazione, per favore",
"export_in_progress": "Esportazione in corso: {{progressCount}}", "export_in_progress": "Esportazione in corso: {{progressCount}}",
"export_finished_successfully": "Esportazione terminata con successo.", "export_finished_successfully": "Esportazione terminata con successo.",
"format_pdf": "PDF- allo scopo di stampa o esportazione." "format_pdf": "PDF- allo scopo di stampa o esportazione.",
}, "export_type_subtree": "Questa nota e tutti i suoi discendenti",
"help": { "format_html": "HTML - raccomandato in quanto mantiene tutti i formati",
"close": "Chiudi", "format_html_zip": "HTML in archivio ZIP - questo è raccomandato in quanto conserva tutta la formattazione.",
"fullDocumentation": "Aiuto (la documentazione completa è disponibile <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)" "format_markdown": "MArkdown - questo conserva la maggior parte della formattazione."
},
"import": {
"close": "Chiudi"
},
"include_note": {
"close": "Chiudi"
},
"jump_to_note": {
"close": "Chiudi"
},
"markdown_import": {
"close": "Chiudi"
},
"move_to": {
"close": "Chiudi"
},
"note_type_chooser": {
"close": "Chiudi"
}, },
"password_not_set": { "password_not_set": {
"close": "Chiudi",
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.", "body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",
"body2": "Per proteggere le note, fare clic su <a class=\"open-password-options-button\" href=\"javascript:\">qui</a> per aprire la finestra di dialogo Opzioni e impostare la password." "body2": "Per proteggere le note, fare clic su <a class=\"open-password-options-button\" href=\"javascript:\">qui</a> per aprire la finestra di dialogo Opzioni e impostare la password."
}, },
"protected_session_password": { "protected_session_password": {
"close_label": "Chiudi" "close_label": "Chiudi"
}, },
"prompt": {
"close": "Chiudi"
},
"recent_changes": {
"close": "Chiudi"
},
"revisions": {
"close": "Chiudi"
},
"abstract_bulk_action": { "abstract_bulk_action": {
"remove_this_search_action": "Rimuovi questa azione di ricerca" "remove_this_search_action": "Rimuovi questa azione di ricerca"
}, },

View File

@@ -1,23 +1,176 @@
{ {
"about": { "about": {
"title": "Trilium Notesについて", "title": "Trilium Notesについて",
"close": "閉じる", "homepage": "ホームページ:",
"homepage": "ホームページ:", "app_version": "アプリのバージョン:",
"app_version": "アプリのヴァージョン:", "db_version": "データベースのバージョン:",
"db_version": "データベースのヴァージョン:", "sync_version": "同期のバージョン:",
"sync_version": "同期のヴァージョン:", "build_date": "Build の日時:",
"build_date": "Build の日時:", "build_revision": "Build のバージョン:",
"build_revision": "Build のヴァージョン:", "data_directory": "データの場所:"
"data_directory": "データの場所:" },
"toast": {
"critical-error": {
"title": "致命的なエラー",
"message": "致命的なエラーのせいでアプリをスタートできません:\n\n{{message}}\n\nおそらくスクリプトが予期しないバグを含んでいると思われます。アプリをセーフモードでスタートしてみて下さい。"
}, },
"toast": { "widget-error": {
"critical-error": { "title": "ウィジェットを初期化できませんでした",
"title": "致命的なエラー", "message-custom": "ートID”{{id}}”, ノートタイトル “{{title}}” のカスタムウィジェットを初期化できませんでした:\n\n{{message}}",
"message": "致命的なエラーのせいでアプリをスタートできません:\n\n{{message}}\n\nおそらくスクリプトが予期しないバグを含んでいると思われます。アプリをセーフモードでスタートしてみて下さい。" "message-unknown": "不明なウィジェットが初期化できませんでした。理由は以下の通りです:\n\n{{message}}"
}, },
"widget-error": { "bundle-error": {
"title": "ウィジェットを初期化できませんでした", "title": "カスタムスクリプトの読み込みに失敗しました",
"message-custom": "ートID”{{id}}”, ノートタイトル “{{title}}” のカスタムウィジェットを初期化できませんでした:\n\n{{message}}" "message": "ートID”{{id}}”, ノートタイトル “{{title}}” のスクリプトを実行できませんでした。理由は以下の通りです:\n\n{{message}}"
}
} }
},
"add_link": {
"add_link": "リンクを追加",
"note": "ノート",
"link_title": "リンクタイトル",
"button_add_link": "リンクを追加",
"help_on_links": "リンクに関するヘルプ",
"search_note": "ノート名で検索",
"link_title_mirrors": "リンクタイトルはノートタイトルの変更を反映します",
"link_title_arbitrary": "リンクタイトルは自由に変更可能"
},
"branch_prefix": {
"save": "保存"
},
"global_menu": {
"menu": "メニュー",
"options": "オプション",
"open_new_window": "新しいウィンドウを開く",
"zoom": "ズーム",
"toggle_fullscreen": "フルスクリーンの切り替え",
"reset_zoom_level": "ズームレベルのリセット",
"open_dev_tools": "開発者ツールを開く",
"open_sql_console": "SQLコンソールを開く",
"open_sql_console_history": "SQLコンソールの履歴を開く",
"open_search_history": "検索履歴を開く",
"show_backend_log": "バックエンドログの表示",
"reload_hint": "リロードは、アプリ全体を再起動することなく、視覚的な不具合を解消することができます。",
"reload_frontend": "フロントエンドをリロード",
"show_hidden_subtree": "隠れたサブツリーを表示",
"show_help": "ヘルプを表示",
"about": "Trilium Notesについて",
"logout": "ログアウト",
"show-cheatsheet": "チートシートを表示"
},
"left_pane_toggle": {
"show_panel": "パネルを表示",
"hide_panel": "パネルを隠す"
},
"move_pane_button": {
"move_left": "左に移動",
"move_right": "右に移動"
},
"clone_to": {
"notes_to_clone": "複製するノート",
"target_parent_note": "ターゲットの親ノート",
"search_for_note_by_its_name": "ノート名で検索",
"cloned_note_prefix_title": "複製されたノートは、指定された接頭辞を付けてノートツリーに表示されます",
"prefix_optional": "接頭辞(任意)",
"clone_to_selected_note": "選択したノートに複製",
"no_path_to_clone_to": "複製先のパスが存在しません。",
"note_cloned": "ノート \"{{clonedTitle}}\" は \"{{targetTitle}}\" に複製されました"
},
"delete_notes": {
"delete_all_clones_description": "すべての複製も削除する(最近の変更では元に戻すことができる)",
"erase_notes_description": "通常の(ソフト)削除では、ノートは削除されたものとしてマークされ、一定期間内に(最近の変更で)削除を取り消すことができます。このオプションをオンにすると、ノートは即座に削除され、削除を取り消すことはできません。",
"erase_notes_warning": "すべての複製を含め、ノートを完全に消去します(元に戻せません)。これにより、アプリケーションは強制的にリロードされます。",
"notes_to_be_deleted": "以下のノートが削除されます ({{notesCount}})",
"no_note_to_delete": "ノートは削除されません(複製のみ)。",
"cancel": "キャンセル",
"ok": "OK"
},
"calendar": {
"mon": "月",
"tue": "火",
"wed": "水",
"thu": "木",
"fri": "金",
"sat": "土",
"sun": "日",
"january": "1月",
"febuary": "2月",
"march": "3月",
"april": "4月",
"may": "5月",
"june": "6月",
"july": "7月",
"august": "8月",
"september": "9月",
"october": "10月",
"november": "11月",
"december": "12月"
},
"note_icon": {
"change_note_icon": "ノートアイコンの変更",
"category": "カテゴリー:",
"search": "検索:",
"reset-default": "アイコンをデフォルトに戻す"
},
"basic_properties": {
"note_type": "ノートタイプ",
"editable": "編集可能"
},
"i18n": {
"title": "ローカライゼーション",
"language": "言語",
"first-day-of-the-week": "週の最初",
"sunday": "日曜日",
"monday": "月曜日"
},
"tab_row": {
"close_tab": "タブを閉じる",
"add_new_tab": "新しいタブ",
"close": "閉じる",
"close_other_tabs": "他のタブを閉じる",
"close_right_tabs": "右側のタブをすべて閉じる",
"close_all_tabs": "すべてのタブを閉じる",
"reopen_last_tab": "最後に閉じたタブを開く",
"move_tab_to_new_window": "このタブを新しいウィンドウに移動する",
"copy_tab_to_new_window": "このタブを新しいウィンドウにコピーする"
},
"tasks": {
"due": {
"today": "今日",
"tomorrow": "明日",
"yesterday": "昨日"
}
},
"classic_editor_toolbar": {
"title": "書式設定"
},
"search_definition": {
"add_search_option": "検索オプションを追加:",
"search_string": "文字列検索",
"search_script": "スクリプト検索",
"fast_search": "高速検索",
"fast_search_description": "高速検索オプションは、ノートの全文検索を無効にし、大規模データベースでの検索を高速化します。",
"include_archived": "アーカイブを含む",
"include_archived_notes_description": "アーカイブされたノートはデフォルトで検索結果から除外されますが、このオプションを使用すると含まれるようになります。",
"order_by": "並べ替え",
"limit": "リミット",
"limit_description": "検索結果の数を制限する",
"debug": "デバッグ",
"debug_description": "デバッグは複雑なクエリのデバッグを支援するために、追加のデバッグ情報をコンソールに出力します",
"action": "アクション",
"search_button": "検索 <kbd>Enter</kbd>",
"search_execute": "検索とアクションの実行",
"save_to_note": "ノートに保存"
},
"shortcuts": {
"multiple_shortcuts": "同じアクションに対して複数のショートカットを設定する場合、カンマで区切ることができます。",
"electron_documentation": "使用可能な修飾キーとキーコードについては、 <a href=\"https://www.electronjs.org/docs/latest/api/accelerator\">Electronのドキュメント</a>を参照してください。",
"type_text_to_filter": "テキストを入力してショートカットを絞り込む...",
"action_name": "アクション名",
"shortcuts": "ショートカット",
"default_shortcuts": "デフォルトのショートカットキー",
"description": "説明",
"reload_app": "リロードして変更を適用する",
"set_all_to_default": "すべてのショートカットをデフォルトに戻す",
"confirm_reset": "キーボードショートカットをすべてデフォルトにリセットしますか?"
}
} }

View File

@@ -1,431 +1,414 @@
{ {
"code_block": { "code_block": {
"theme_none": "Sem destaque de sintaxe", "theme_none": "Sem destaque de sintaxe",
"theme_group_light": "Temas claros", "theme_group_light": "Temas claros",
"theme_group_dark": "Temas escuros" "theme_group_dark": "Temas escuros"
},
"about": {
"title": "Sobre o Trilium Notes",
"homepage": "Página inicial:",
"app_version": "Versão do App:",
"db_version": "Versão do db:",
"sync_version": "Versão de sincronização:",
"build_date": "Data de compilação:",
"build_revision": "Revisão da compilação:",
"data_directory": "Diretório de dados:"
},
"toast": {
"critical-error": {
"title": "Erro crítico",
"message": "Ocorreu um erro crítico que impede a inicialização do aplicativo cliente:\n\n{{message}}\n\nIsso provavelmente foi causado por um script que falhou de maneira inesperada. Tente iniciar o aplicativo no modo seguro e resolva o problema."
}, },
"about": { "widget-error": {
"title": "Sobre o Trilium Notes", "title": "Falha ao inicializar um widget",
"close": "Fechar", "message-custom": "O widget personalizado da nota com ID \"{{id}}\", intitulada \"{{title}}\", não pôde ser inicializado devido a:\n\n{{message}}",
"homepage": "Página inicial:", "message-unknown": "Widget desconhecido não pôde ser inicializado devido a:\n\n{{message}}"
"app_version": "Versão do App:",
"db_version": "Versão do db:",
"sync_version": "Versão de sincronização:",
"build_date": "Data de compilação:",
"build_revision": "Revisão da compilação:",
"data_directory": "Diretório de dados:"
}, },
"toast": { "bundle-error": {
"critical-error": { "title": "Falha para carregar o script customizado",
"title": "Erro crítico", "message": "O script da nota com ID \"{{id}}\", intitulada \"{{title}}\", não pôde ser executado devido a:\n\n{{message}}"
"message": "Ocorreu um erro crítico que impede a inicialização do aplicativo cliente:\n\n{{message}}\n\nIsso provavelmente foi causado por um script que falhou de maneira inesperada. Tente iniciar o aplicativo no modo seguro e resolva o problema."
},
"widget-error": {
"title": "Falha ao inicializar um widget",
"message-custom": "O widget personalizado da nota com ID \"{{id}}\", intitulada \"{{title}}\", não pôde ser inicializado devido a:\n\n{{message}}",
"message-unknown": "Widget desconhecido não pôde ser inicializado devido a:\n\n{{message}}"
},
"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}}"
}
},
"add_link": {
"add_link": "Adicionar link",
"help_on_links": "Ajuda sobre links",
"close": "Fechar",
"note": "Nota",
"search_note": "pesquisar nota pelo nome",
"link_title_mirrors": "o título do link reflete o título atual da nota",
"link_title_arbitrary": "o título do link pode ser alterado livremente",
"link_title": "Titulo do link",
"button_add_link": "Adicionar link <kbd>enter</kbd>"
},
"branch_prefix": {
"close": "Fechar",
"prefix": "Prefixo: ",
"save": "Salvar",
"edit_branch_prefix": "Editar Prefixo do Branch",
"help_on_tree_prefix": "Ajuda sobre o prefixo da árvore de notas",
"branch_prefix_saved": "O prefixo de ramificação foi salvo."
},
"bulk_actions": {
"bulk_actions": "Ações em massa",
"close": "Fechar",
"affected_notes": "Notas Afetadas",
"include_descendants": "Incluir notas filhas das notas selecionadas",
"available_actions": "Ações disponíveis",
"chosen_actions": "Ações selecionadas",
"execute_bulk_actions": "Executar ações em massa",
"bulk_actions_executed": "As ações em massa foram concluídas com sucesso.",
"none_yet": "Nenhuma até agora... adicione uma ação clicando em uma das disponíveis acima.",
"labels": "Etiquetas",
"relations": "Relações",
"notes": "Notas",
"other": "Outros"
},
"clone_to": {
"clone_notes_to": "Clonar notas para...",
"close": "Fechar",
"help_on_links": "Ajuda sobre links",
"notes_to_clone": "Notas para clonar",
"search_for_note_by_its_name": "pesquisar nota pelo nome",
"cloned_note_prefix_title": "A nota clonada aparecerá na árvore de notas com o prefixo fornecido",
"prefix_optional": "Prefixo (opcional)",
"no_path_to_clone_to": "Nenhum caminho para clonar.",
"target_parent_note": "Nota pai-alvo",
"clone_to_selected_note": "Clonar para a nota selecionada <kbd>enter</kbd>",
"note_cloned": "A nota \"{{clonedTitle}}\" foi clonada para \"{{targetTitle}}\""
},
"ai_llm": {
"n_notes_queued_0": "{{ count }} nota enfileirada para indexação",
"n_notes_queued_1": "{{ count }} notas enfileiradas para indexação",
"n_notes_queued_2": "{{ count }} notas enfileiradas para indexação",
"notes_indexed_0": "{{ count }} nota indexada",
"notes_indexed_1": "{{ count }} notas indexadas",
"notes_indexed_2": "{{ count }} notas indexadas"
},
"confirm": {
"confirmation": "Confirmação",
"close": "Fechar",
"cancel": "Cancelar",
"ok": "OK",
"are_you_sure_remove_note": "Tem certeza de que deseja remover a nota '{{title}}' do mapa de relações? ",
"if_you_dont_check": "Se você não marcar isso, a nota será removida apenas do mapa de relações.",
"also_delete_note": "Também excluir a nota"
},
"delete_notes": {
"delete_notes_preview": "Excluir pré-visualização de notas",
"close": "Fechar",
"delete_all_clones_description": "Excluir também todos os clones (pode ser desfeito em alterações recentes)",
"erase_notes_description": "A exclusão normal (suave) apenas marca as notas como excluídas, permitindo que sejam recuperadas (no diálogo de alterações recentes) dentro de um período de tempo. Se esta opção for marcada, as notas serão apagadas imediatamente e não será possível restaurá-las.",
"erase_notes_warning": "Apagar notas permanentemente (não pode ser desfeito), incluindo todos os clones. Isso forçará o recarregamento do aplicativo.",
"notes_to_be_deleted": "As seguintes notas serão excluídas ({{- noteCount}})",
"no_note_to_delete": "Nenhuma nota será excluída (apenas os clones).",
"broken_relations_to_be_deleted": "As seguintes relações serão quebradas e excluídas ({{- relationCount}})",
"cancel": "Cancelar",
"ok": "OK",
"deleted_relation_text": "A nota {{- note}} (a ser excluída) está referenciada pela relação {{- relation}} originada de {{- source}}."
},
"export": {
"export_note_title": "Exportar nota",
"close": "Fechar",
"export_type_subtree": "Esta nota e todos os seus descendentes",
"format_html": "HTML recomendado, pois mantém toda a formatação",
"format_html_zip": "HTML em arquivo ZIP recomendado, pois isso preserva toda a formatação.",
"format_markdown": "Markdown isso preserva a maior parte da formatação.",
"format_opml": "OPML - formato de intercâmbio de outliners apenas para texto. Formatação, imagens e arquivos não estão incluídos.",
"opml_version_1": "OPML v1.0 apenas texto simples",
"opml_version_2": "OPML v2.0 permite também HTML",
"export_type_single": "Apenas esta nota, sem seus descendentes",
"export": "Exportar",
"choose_export_type": "Por favor, escolha primeiro o tipo de exportação",
"export_status": "Status 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."
},
"help": {
"fullDocumentation": "Ajuda (a documentação completa está disponível <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)",
"close": "Fechar",
"noteNavigation": "Navegação de notas",
"goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> subir/descer na lista de notas",
"collapseExpand": "<kbd>ESQUERDA</kbd>, <kbd>DIREITA</kbd> recolher/expandir nó",
"notSet": "não definido",
"goBackForwards": "voltar / avançar no histórico",
"showJumpToNoteDialog": "mostrar diálogo <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Ir para\"</a>",
"scrollToActiveNote": "rolar até a nota atual",
"jumpToParentNote": "<kbd>Backspace</kbd> ir para a nota pai",
"collapseWholeTree": "recolher toda a árvore de notas",
"collapseSubTree": "recolher subárvore",
"tabShortcuts": "Atalhos de abas",
"newTabNoteLink": "<kbd>Ctrl+clique</kbd> (ou <kbd>clique com o botão do meio do mouse</kbd>) em um link de nota abre a nota em uma nova aba",
"newTabWithActivationNoteLink": "<kbd>Ctrl+Shift+clique</kbd> (ou <kbd>Shift+clique com o botão do meio do mouse</kbd>) em um link de nota abre e ativa a nota em uma nova aba",
"onlyInDesktop": "Apenas na versão para desktop (compilação Electron)",
"openEmptyTab": "abrir aba vazia",
"closeActiveTab": "fechar aba ativa",
"activateNextTab": "ativar próxima aba",
"activatePreviousTab": "ativar aba anterior",
"creatingNotes": "Criando notas",
"createNoteAfter": "criar nova nota após a nota atual",
"createNoteInto": "criar nova subnota dentro da nota atual",
"editBranchPrefix": "editar <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefixo</a> do clone da nota ativa",
"movingCloningNotes": "Movendo / clonando notas",
"moveNoteUpDown": "mover nota para cima/baixo na lista de notas",
"moveNoteUpHierarchy": "mover nota para cima na hierarquia",
"multiSelectNote": "selecionar múltiplas notas acima/abaixo",
"selectAllNotes": "selecionar todas as notas no nível atual",
"selectNote": "<kbd>Shift+clique</kbd> - selecionar nota",
"copyNotes": "copiar nota ativa (ou seleção atual) para a área de transferência (usado para <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonar</a>)",
"cutNotes": "recortar nota atual (ou seleção atual) para a área de transferência (usado para mover notas)",
"pasteNotes": "colar nota(s) como subnota dentro da nota ativa (o que pode ser mover ou clonar dependendo se foi copiado ou recortado para a área de transferência)",
"deleteNotes": "excluir nota / subárvore",
"editingNotes": "Edição de notas",
"editNoteTitle": "no painel de árvore, a navegação mudará do painel de árvore para o título da nota. Pressionar Enter no título da nota mudará o foco para o editor de texto. <kbd>Ctrl+.</kbd> mudará o foco de volta do editor para o painel de árvore.",
"createEditLink": "<kbd>Ctrl+K</kbd> - criar / editar link externo",
"createInternalLink": "criar link interno",
"followLink": "seguir link sob o cursor",
"insertDateTime": "inserir data e hora atual na posição do cursor",
"jumpToTreePane": "ir para a árvore de notas e rolar até a nota ativa",
"markdownAutoformat": "Autoformatação estilo Markdown",
"headings": "<code>##</code>, <code>###</code>, <code>####</code> etc. seguidos de espaço para títulos",
"bulletList": "<code>*</code> ou <code>-</code> seguidos de espaço para lista com marcadores",
"numberedList": "<code>1.</code> ou <code>1)</code> seguidos de espaço para lista numerada",
"blockQuote": "comece uma linha com <code>></code> seguido de espaço para citação em bloco",
"troubleshooting": "Solução de problemas",
"reloadFrontend": "recarregar o frontend do Trilium",
"showDevTools": "mostrar ferramentas de desenvolvedor",
"showSQLConsole": "mostrar console SQL",
"other": "Outros",
"quickSearch": "focar no campo de pesquisa rápida",
"inPageSearch": "pesquisa na página"
},
"import": {
"importIntoNote": "Importar para a nota",
"close": "Fechar",
"chooseImportFile": "Escolher arquivo para importar",
"importDescription": "O conteúdo do(s) arquivo(s) selecionado(s) será importado como nota(s) filha(s) em",
"options": "Opções",
"safeImportTooltip": "Arquivos de exportação Trilium<code> .zip</code> podem conter scripts executáveis que podem apresentar comportamentos prejudiciais. A importação segura desativará a execução automática de todos os scripts importados. Desmarque “Importação segura” apenas se o arquivo de importação contiver scripts executáveis e você confiar totalmente no conteúdo do arquivo importado.",
"safeImport": "Importação segura",
"explodeArchivesTooltip": "Se esta opção estiver marcada, o Trilium irá ler arquivos <code>.zip</code>, <code>.enex</code> e <code>.opml</code> e criar notas a partir dos arquivos contidos nesses arquivos compactados. Se estiver desmarcada, o Trilium irá anexar os próprios arquivos compactados à nota.",
"explodeArchives": "Ler conteúdos de arquivos <code>.zip</code>, <code>.enex</code> e <code>.opml</code>.",
"shrinkImagesTooltip": "<p>Se você marcar esta opção, o Trilium tentará reduzir o tamanho das imagens importadas por meio de escala e otimização, o que pode afetar a qualidade visual das imagens. Se desmarcada, as imagens serão importadas sem alterações.</p><p>Isso não se aplica a importações de arquivos <code>.zip</code> com metadados, pois presume-se que esses arquivos já estejam otimizados.</p>",
"shrinkImages": "Reduzir imagens",
"textImportedAsText": "Importar arquivos HTML, Markdown e TXT como notas de texto caso não esteja claro pelos metadados",
"codeImportedAsCode": "Importar arquivos de código reconhecidos (por exemplo, <code>.json</code>) como notas de código caso não esteja claro pelos metadados",
"replaceUnderscoresWithSpaces": "Substituir sublinhados por espaços nos nomes das notas importadas",
"import": "Importar",
"failed": "Falha na importação: {{message}}.",
"html_import_tags": {
"title": "Tags de importação HTML",
"description": "Configurar quais tags HTML devem ser preservadas ao importar notas. As tags que não estiverem nesta lista serão removidas durante a importação. Algumas tags (como 'script') são sempre removidas por motivos de segurança.",
"placeholder": "Digite as tags HTML, uma por linha",
"reset_button": "Redefinir para lista padrão"
},
"import-status": "Status da importação",
"in-progress": "Importação em andamento: {{progress}}",
"successful": "Importação concluída com sucesso."
},
"include_note": {
"dialog_title": "Incluir nota",
"close": "Fechar",
"label_note": "Nota",
"placeholder_search": "pesquisar nota pelo nome",
"box_size_prompt": "Dimensão da caixa da nota incluída:",
"box_size_small": "pequeno (~ 10 linhas)",
"box_size_medium": "médio (~ 30 linhas)",
"box_size_full": "completo (a caixa exibe o texto completo)",
"button_include": "Incluir nota <kbd>enter</kbd>"
},
"info": {
"modalTitle": "Mensagem informativa",
"closeButton": "Fechar",
"okButton": "OK"
},
"jump_to_note": {
"search_placeholder": "Pesquise uma nota pelo nome ou digite > para comandos...",
"close": "Fechar",
"search_button": "Pesquisar em texto completo <kbd>Ctrl+Enter</kbd>"
},
"markdown_import": {
"dialog_title": "Importar Markdown",
"close": "Fechar",
"modal_body_text": "Por motivos de segurança (sandbox do navegador), o JavaScript não pode acessar diretamente a área de transferência. Por favor, cole o conteúdo Markdown na área de texto abaixo e clique em Importar",
"import_button": "Importar Ctrl+Enter",
"import_success": "O conteúdo Markdown foi importado para o documento."
},
"move_to": {
"dialog_title": "Mover notas para...",
"close": "Fechar",
"notes_to_move": "Notas para mover",
"target_parent_note": "Nota pai-alvo",
"search_placeholder": "pesquisar nota pelo nome",
"move_button": "Mover para a nota selecionada <kbd>enter</kbd>",
"error_no_path": "Nenhum caminho para mover.",
"move_success_message": "As notas selecionadas foram movidas para "
},
"note_type_chooser": {
"change_path_prompt": "Alterar onde criar a nova nota:",
"search_placeholder": "buscar caminho pelo nome (valor padrão se não for preenchido)",
"modal_title": "Escolher tipo de nota",
"close": "Fechar",
"modal_body": "Escolha o tipo/modelo da nova nota:",
"templates": "Modelos:"
},
"password_not_set": {
"title": "A senha não está definida",
"close": "Fechar",
"body1": "Notas protegidas são criptografadas usando uma senha do usuário, mas a senha ainda não foi definida.",
"body2": "Para poder proteger notas, clique <a class=\"open-password-options-button\" href=\"javascript:\">aqui</a> para abrir a caixa de diálogo de Opções e definir sua senha."
},
"prompt": {
"title": "Prompt",
"close": "Fechar",
"ok": "OK <kbd>enter</kbd>",
"defaultTitle": "Prompt"
},
"protected_session_password": {
"modal_title": "Sessão Protegida",
"help_title": "Ajuda sobre notas protegidas",
"close_label": "Fechar",
"form_label": "Para prosseguir com a ação solicitada, você precisa iniciar uma sessão protegida digitando a senha:",
"start_button": "Iniciar sessão protegida <kbd>enter</kbd>"
},
"recent_changes": {
"title": "Alterações recentes",
"erase_notes_button": "Remover permanentemente as notas excluídas agora",
"close": "Fechar",
"deleted_notes_message": "As notas excluídas foram removidas permanentemente.",
"no_changes_message": "Nenhuma alteração ainda...",
"undelete_link": "Restaurar",
"confirm_undelete": "Você deseja restaurar esta nota e suas subnotas?"
},
"revisions": {
"note_revisions": "Versões da nota",
"delete_all_revisions": "Excluir todas as versões desta nota",
"delete_all_button": "Excluir todas as versões",
"help_title": "Ajuda sobre as versões da nota",
"close": "Fechar",
"revision_last_edited": "Esta versão foi editada pela última vez em {{date}}",
"confirm_delete_all": "Você quer excluir todas as versões desta nota?",
"no_revisions": "Ainda não há versões para esta nota...",
"restore_button": "Recuperar",
"confirm_restore": "Deseja restaurar esta versão? Isso irá substituir o título e o conteúdo atuais da nota por esta versão.",
"delete_button": "Excluir",
"confirm_delete": "Deseja excluir esta versão?",
"revisions_deleted": "As versões da nota foram removidas.",
"revision_restored": "A versão da nota foi restaurada.",
"revision_deleted": "A versão da nota foi excluída.",
"snapshot_interval": "Intervalo de captura das versões da nota: {{seconds}}s.",
"maximum_revisions": "Limite de capturas das versões da nota: {{number}}.",
"settings": "Configurações de versões da nota",
"download_button": "Download",
"mime": "MIME: ",
"file_size": "Tamanho do arquivo:",
"preview": "Visualizar:",
"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...",
"close": "Fechar",
"sorting_criteria": "Critérios de ordenação",
"title": "título",
"date_created": "data de criação",
"date_modified": "data de modificação",
"sorting_direction": "Direção de ordenação",
"ascending": "crescente",
"descending": "decrescente",
"folders": "Pastas",
"sort_folders_at_top": "colocar pastas no topo",
"natural_sort": "Ordenação Natural",
"sort_with_respect_to_different_character_sorting": "classificar de acordo com diferentes regras de ordenação de caracteres e colação em diferentes idiomas ou regiões.",
"natural_sort_language": "Linguagem da ordenação natural",
"the_language_code_for_natural_sort": "O código do idioma para ordenação natural, por exemplo, \"zh-CN\" para chinês.",
"sort": "Ordenar <kbd>enter</kbd>"
},
"upload_attachments": {
"upload_attachments_to_note": "Enviar anexos para a nota",
"close": "Fechar",
"choose_files": "Escolher arquivos",
"files_will_be_uploaded": "Os arquivos serão enviados como anexos para",
"options": "Opções",
"shrink_images": "Reduzir imagens",
"upload": "Enviar",
"tooltip": "Se você marcar esta opção, o Trilium tentará reduzir as imagens enviadas redimensionando e otimizando, o que pode afetar a qualidade visual percebida. Se desmarcada, as imagens serão enviadas sem alterações."
},
"attribute_detail": {
"attr_detail_title": "Título Detalhado do Atributo",
"close_button_title": "Cancelar alterações e fechar",
"attr_is_owned_by": "O atributo pertence a",
"attr_name_title": "O nome do atributo pode ser composto apenas por caracteres alfanuméricos, dois-pontos e sublinhado",
"name": "Nome",
"value": "Valor",
"target_note_title": "Relação é uma conexão nomeada entre a nota de origem e a nota de destino.",
"target_note": "Nota de destino",
"promoted_title": "O atributo promovido é exibido de forma destacada na nota.",
"promoted": "Promovido",
"promoted_alias_title": "O nome a ser exibido na interface de atributos promovidos.",
"promoted_alias": "Alias",
"multiplicity_title": "Multiplicidade define quantos atributos com o mesmo nome podem ser criados — no máximo 1 ou mais de 1.",
"multiplicity": "Multiplicidade",
"single_value": "Valor único",
"multi_value": "Valor múltiplo",
"label_type_title": "O tipo do rótulo ajudará o Trilium a escolher a interface adequada para inserir o valor do rótulo.",
"label_type": "Tipo",
"text": "Texto",
"number": "Número",
"boolean": "Booleano",
"date": "Data",
"date_time": "Data e Hora",
"time": "Hora",
"url": "URL",
"precision_title": "Qual número de dígitos após o ponto decimal deve estar disponível na interface de configuração de valor.",
"precision": "Precisão",
"digits": "dígitos",
"inverse_relation_title": "Configuração opcional para definir a qual relação esta é oposta. Exemplo: Pai - Filho são relações inversas entre si.",
"inverse_relation": "Relação inversa",
"inheritable_title": "O atributo herdável será transmitido para todos os descendentes deste ramo.",
"inheritable": "Herdável",
"save_and_close": "Salvar e fechar <kbd>Ctrl+Enter</kbd>",
"delete": "Excluir",
"related_notes_title": "Outras notas com este rótulo",
"more_notes": "Mais notas",
"label": "Detalhe do rótulo",
"label_definition": "Detalhe da definição do rótulo",
"relation": "Detalhe da relação",
"relation_definition": "Detalhe da definição da relação",
"disable_versioning": "desativa a versão automática. Útil, por exemplo, para notas grandes, mas sem importância como grandes bibliotecas JS usadas para scripts",
"calendar_root": "marca a nota que deve ser usada como raiz para notas diárias. Apenas uma deve ser marcada assim.",
"archived": "notas com este rótulo não serão exibidas por padrão nos resultados de busca (também nos diálogos Ir para, Adicionar link, etc).",
"exclude_from_export": "notas (junto com sua subárvore) não serão incluídas em nenhuma exportação de notas",
"run": "define em quais eventos o script deve ser executado. Os valores possíveis são:\n<ul>\n<li>frontendStartup - quando o frontend do Trilium inicia (ou é atualizado), mas não no celular.</li>\n<li>mobileStartup - quando o frontend do Trilium inicia (ou é atualizado), no celular.</li>\n<li>backendStartup - quando o backend do Trilium inicia</li>\n<li>hourly - executa uma vez por hora. Você pode usar o rótulo adicional <code>runAtHour</code> para especificar em qual hora.</li>\n<li>daily - executa uma vez por dia</li>\n</ul>",
"run_on_instance": "Define em qual instância do Trilium isso deve ser executado. Por padrão, todas as instâncias.",
"run_at_hour": "Em qual hora isso deve ser executado. Deve ser usado junto com <code>#run=hourly</code>. Pode ser definido várias vezes para executar mais de uma vez ao dia.",
"disable_inclusion": "scripts com este rótulo não serão incluídos na execução do script pai.",
"sorted": "mantém as notas filhas ordenadas alfabeticamente pelo título",
"sort_direction": "ASC (padrão) ou DESC",
"sort_folders_first": "Pastas (notas com filhos) devem ser ordenadas no topo",
"top": "mantenha a nota fornecida no topo em seu pai (aplica-se apenas a pais ordenados)",
"hide_promoted_attributes": "Ocultar atributos promovidos nesta nota",
"read_only": "o editor está em modo somente leitura. Funciona apenas para notas de texto e código.",
"auto_read_only_disabled": "notas de texto/código podem ser automaticamente configuradas para modo de leitura quando são muito grandes. Você pode desabilitar esse comportamento por nota adicionando este rótulo à nota",
"app_css": "marca notas CSS que são carregadas no aplicativo Trilium e, portanto, podem ser usadas para modificar a aparência do Trilium.",
"app_theme": "marca notas CSS que são temas completos do Trilium e, portanto, estão disponíveis nas opções do Trilium.",
"app_theme_base": "defina como \"next\", \"next-light\" ou \"next-dark\" para usar o tema TriliumNext correspondente (auto, claro ou escuro) como base para um tema personalizado, em vez do tema legado.",
"css_class": "o valor deste rótulo é então adicionado como classe CSS ao nó que representa a nota específica na árvore. Isso pode ser útil para temas avançados. Pode ser usado em notas de modelo.",
"icon_class": "o valor deste rótulo é adicionado como uma classe CSS ao ícone na árvore, o que pode ajudar a distinguir visualmente as notas na árvore. Um exemplo pode ser bx bx-home os ícones são retirados do boxicons. Pode ser usado em notas de modelo.",
"page_size": "número de itens por página na listagem de notas",
"custom_request_handler": "veja <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Manipulador de requisição personalizada</a>",
"custom_resource_provider": "veja <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Manipulador de requisição personalizada</a>",
"widget": "marca esta nota como um widget personalizado que será adicionado à árvore de componentes do Trilium",
"workspace": "marca esta nota como um espaço de trabalho, o que permite fácil hoisting",
"workspace_icon_class": "define a classe CSS do ícone box que será usada na aba quando esta nota for hoisted",
"workspace_tab_background_color": "cor CSS usada na aba da nota quando esta nota é hoisted",
"workspace_calendar_root": "Define a raiz do calendário por espaço de trabalho",
"workspace_template": "Esta nota aparecerá na seleção de modelos disponíveis ao criar uma nova nota, mas apenas quando estiver destacada em um espaço de trabalho que contenha este modelo",
"search_home": "novas notas de pesquisa serão criadas como filhas desta nota",
"workspace_search_home": "novas notas de pesquisa serão criadas como filhas desta nota quando ela for destacada para algum ancestral desta nota de área de trabalho",
"inbox": "localização padrão da caixa de entrada para novas notas quando você cria uma nota usando o botão \"nova nota\" na barra lateral, as notas serão criadas como notas filhas na nota marcada com o rótulo <code>#inbox</code>.",
"workspace_inbox": "local padrão da caixa de entrada para novas notas quando esta nota for destacada para algum ancestral desta nota de área de trabalho",
"sql_console_home": "localização padrão das notas do console SQL",
"bookmark_folder": "nota com este rótulo aparecerá nos favoritos como uma pasta (permitindo acesso aos seus filhos)",
"share_hidden_from_tree": "esta nota está oculta na árvore de navegação à esquerda, mas ainda pode ser acessada via sua URL",
"share_external_link": "a nota funcionará como um link para um site externo na árvore de compartilhamento",
"share_alias": "defina um alias por meio do qual a nota ficará disponível em https://your_trilium_host/share/[your_alias]",
"share_omit_default_css": "o CSS padrão da página de compartilhamento será omitido. Use quando você fizer alterações extensas de estilo.",
"share_root": "marca a nota que é servida na raiz de /share.",
"share_description": "defina o texto a ser adicionado à meta tag HTML \"description\"",
"share_raw": "a nota será servida em seu formato bruto, sem o wrapper HTML",
"share_disallow_robot_indexing": "impedirá que robôs indexem esta nota por meio do cabeçalho <code>X-Robots-Tag: noindex</code>",
"share_credentials": "exigir credenciais para acessar esta nota compartilhada. O valor deve estar no formato 'usuário:senha'. Não se esqueça de tornar esta configuração herdável para que seja aplicada às notas-filhas/imagens.",
"share_index": "notas com este rótulo irão listar todas as raízes das notas compartilhadas",
"display_relations": "nomes das relações separados por vírgula que devem ser exibidos. Todas as outras serão ocultadas.",
"hide_relations": "nomes das relações separados por vírgula que devem ser ocultados. Todas as outras serão exibidas.",
"title_template": "Título padrão das notas criadas como filhas desta nota. O valor é avaliado como uma string JavaScript e pode ser enriquecido com conteúdo dinâmico usando as variáveis injetadas <code>now</code> e <code>parentNote</code>. Exemplos:\n\n<ul>\n <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li>\n <li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n</ul>\n\nVeja a <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki com detalhes</a>, a documentação da API para <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> e para <a href=\"https://day.js.org/docs/en/display/format\">now</a> para mais informações.",
"template": "Esta nota aparecerá na seleção de modelos disponíveis ao criar uma nova nota",
"toc": "<code>#toc</code> ou <code>#toc=show</code> irá forçar a exibição do Sumário, <code>#toc=hide</code> irá forçar que ele fique oculto. Se o rótulo não existir, será considerado o ajuste global",
"color": "define a cor da nota na árvore de notas, links etc. Use qualquer valor de cor CSS válido, como 'red' ou #a13d5f",
"keyboard_shortcut": "Define um atalho de teclado que irá pular imediatamente para esta nota. Exemplo: 'ctrl+alt+e'. É necessário recarregar o frontend para que a alteração tenha efeito."
} }
},
"add_link": {
"add_link": "Adicionar link",
"help_on_links": "Ajuda sobre links",
"note": "Nota",
"search_note": "pesquisar nota pelo nome",
"link_title_mirrors": "o título do link reflete o título atual da nota",
"link_title_arbitrary": "o título do link pode ser alterado livremente",
"link_title": "Titulo do link",
"button_add_link": "Adicionar link"
},
"branch_prefix": {
"prefix": "Prefixo: ",
"save": "Salvar",
"edit_branch_prefix": "Editar Prefixo do Branch",
"help_on_tree_prefix": "Ajuda sobre o prefixo da árvore de notas",
"branch_prefix_saved": "O prefixo de ramificação foi salvo."
},
"bulk_actions": {
"bulk_actions": "Ações em massa",
"affected_notes": "Notas Afetadas",
"include_descendants": "Incluir notas filhas das notas selecionadas",
"available_actions": "Ações disponíveis",
"chosen_actions": "Ações selecionadas",
"execute_bulk_actions": "Executar ações em massa",
"bulk_actions_executed": "As ações em massa foram concluídas com sucesso.",
"none_yet": "Nenhuma até agora... adicione uma ação clicando em uma das disponíveis acima.",
"labels": "Etiquetas",
"relations": "Relações",
"notes": "Notas",
"other": "Outros"
},
"clone_to": {
"clone_notes_to": "Clonar notas para...",
"help_on_links": "Ajuda sobre links",
"notes_to_clone": "Notas para clonar",
"search_for_note_by_its_name": "pesquisar nota pelo nome",
"cloned_note_prefix_title": "A nota clonada aparecerá na árvore de notas com o prefixo fornecido",
"prefix_optional": "Prefixo (opcional)",
"no_path_to_clone_to": "Nenhum caminho para clonar.",
"target_parent_note": "Nota pai-alvo",
"clone_to_selected_note": "Clonar para a nota selecionada",
"note_cloned": "A nota \"{{clonedTitle}}\" foi clonada para \"{{targetTitle}}\""
},
"ai_llm": {
"n_notes_queued_0": "{{ count }} nota enfileirada para indexação",
"n_notes_queued_1": "{{ count }} notas enfileiradas para indexação",
"n_notes_queued_2": "{{ count }} notas enfileiradas para indexação",
"notes_indexed_0": "{{ count }} nota indexada",
"notes_indexed_1": "{{ count }} notas indexadas",
"notes_indexed_2": "{{ count }} notas indexadas"
},
"confirm": {
"confirmation": "Confirmação",
"cancel": "Cancelar",
"ok": "OK",
"are_you_sure_remove_note": "Tem certeza de que deseja remover a nota '{{title}}' do mapa de relações? ",
"if_you_dont_check": "Se você não marcar isso, a nota será removida apenas do mapa de relações.",
"also_delete_note": "Também excluir a nota"
},
"delete_notes": {
"delete_notes_preview": "Excluir pré-visualização de notas",
"close": "Fechar",
"delete_all_clones_description": "Excluir também todos os clones (pode ser desfeito em alterações recentes)",
"erase_notes_description": "A exclusão normal (suave) apenas marca as notas como excluídas, permitindo que sejam recuperadas (no diálogo de alterações recentes) dentro de um período de tempo. Se esta opção for marcada, as notas serão apagadas imediatamente e não será possível restaurá-las.",
"erase_notes_warning": "Apagar notas permanentemente (não pode ser desfeito), incluindo todos os clones. Isso forçará o recarregamento do aplicativo.",
"notes_to_be_deleted": "As seguintes notas serão excluídas ({{notesCount}})",
"no_note_to_delete": "Nenhuma nota será excluída (apenas os clones).",
"broken_relations_to_be_deleted": "As seguintes relações serão quebradas e excluídas ({{ relationCount}})",
"cancel": "Cancelar",
"ok": "OK",
"deleted_relation_text": "A nota {{- note}} (a ser excluída) está referenciada pela relação {{- relation}} originada de {{- source}}."
},
"export": {
"export_note_title": "Exportar nota",
"close": "Fechar",
"export_type_subtree": "Esta nota e todos os seus descendentes",
"format_html": "HTML recomendado, pois mantém toda a formatação",
"format_html_zip": "HTML em arquivo ZIP recomendado, pois isso preserva toda a formatação.",
"format_markdown": "Markdown isso preserva a maior parte da formatação.",
"format_opml": "OPML - formato de intercâmbio de outliners apenas para texto. Formatação, imagens e arquivos não estão incluídos.",
"opml_version_1": "OPML v1.0 apenas texto simples",
"opml_version_2": "OPML v2.0 permite também HTML",
"export_type_single": "Apenas esta nota, sem seus descendentes",
"export": "Exportar",
"choose_export_type": "Por favor, escolha primeiro o tipo de exportação",
"export_status": "Status 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."
},
"help": {
"noteNavigation": "Navegação de notas",
"goUpDown": "subir/descer na lista de notas",
"collapseExpand": "recolher/expandir nó",
"notSet": "não definido",
"goBackForwards": "voltar / avançar no histórico",
"showJumpToNoteDialog": "mostrar diálogo <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Ir para\"</a>",
"scrollToActiveNote": "rolar até a nota atual",
"jumpToParentNote": "ir para a nota pai",
"collapseWholeTree": "recolher toda a árvore de notas",
"collapseSubTree": "recolher subárvore",
"tabShortcuts": "Atalhos de abas",
"newTabNoteLink": "em um link de nota abre a nota em uma nova aba",
"newTabWithActivationNoteLink": "em um link de nota abre e ativa a nota em uma nova aba",
"onlyInDesktop": "Apenas na versão para desktop (compilação Electron)",
"openEmptyTab": "abrir aba vazia",
"closeActiveTab": "fechar aba ativa",
"activateNextTab": "ativar próxima aba",
"activatePreviousTab": "ativar aba anterior",
"creatingNotes": "Criando notas",
"createNoteAfter": "criar nova nota após a nota atual",
"createNoteInto": "criar nova subnota dentro da nota atual",
"editBranchPrefix": "editar <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefixo</a> do clone da nota ativa",
"movingCloningNotes": "Movendo / clonando notas",
"moveNoteUpDown": "mover nota para cima/baixo na lista de notas",
"moveNoteUpHierarchy": "mover nota para cima na hierarquia",
"multiSelectNote": "selecionar múltiplas notas acima/abaixo",
"selectAllNotes": "selecionar todas as notas no nível atual",
"selectNote": "selecionar nota",
"copyNotes": "copiar nota ativa (ou seleção atual) para a área de transferência (usado para <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonar</a>)",
"cutNotes": "recortar nota atual (ou seleção atual) para a área de transferência (usado para mover notas)",
"pasteNotes": "colar nota(s) como subnota dentro da nota ativa (o que pode ser mover ou clonar dependendo se foi copiado ou recortado para a área de transferência)",
"deleteNotes": "excluir nota / subárvore",
"editingNotes": "Edição de notas",
"editNoteTitle": "no painel de árvore, a navegação mudará do painel de árvore para o título da nota. Pressionar Enter no título da nota mudará o foco para o editor de texto. <kbd>Ctrl+.</kbd> mudará o foco de volta do editor para o painel de árvore.",
"createEditLink": "criar / editar link externo",
"createInternalLink": "criar link interno",
"followLink": "seguir link sob o cursor",
"insertDateTime": "inserir data e hora atual na posição do cursor",
"jumpToTreePane": "ir para a árvore de notas e rolar até a nota ativa",
"markdownAutoformat": "Autoformatação estilo Markdown",
"headings": "<code>##</code>, <code>###</code>, <code>####</code> etc. seguidos de espaço para títulos",
"bulletList": "<code>*</code> ou <code>-</code> seguidos de espaço para lista com marcadores",
"numberedList": "<code>1.</code> ou <code>1)</code> seguidos de espaço para lista numerada",
"blockQuote": "comece uma linha com <code>></code> seguido de espaço para citação em bloco",
"troubleshooting": "Solução de problemas",
"reloadFrontend": "recarregar o frontend do Trilium",
"showDevTools": "mostrar ferramentas de desenvolvedor",
"showSQLConsole": "mostrar console SQL",
"other": "Outros",
"quickSearch": "focar no campo de pesquisa rápida",
"inPageSearch": "pesquisa na página",
"title": "Folha de Dicas"
},
"import": {
"importIntoNote": "Importar para a nota",
"chooseImportFile": "Escolher arquivo para importar",
"importDescription": "O conteúdo do(s) arquivo(s) selecionado(s) será importado como nota(s) filha(s) em",
"options": "Opções",
"safeImportTooltip": "Arquivos de exportação Trilium<code> .zip</code> podem conter scripts executáveis que podem apresentar comportamentos prejudiciais. A importação segura desativará a execução automática de todos os scripts importados. Desmarque “Importação segura” apenas se o arquivo de importação contiver scripts executáveis e você confiar totalmente no conteúdo do arquivo importado.",
"safeImport": "Importação segura",
"explodeArchivesTooltip": "Se esta opção estiver marcada, o Trilium irá ler arquivos <code>.zip</code>, <code>.enex</code> e <code>.opml</code> e criar notas a partir dos arquivos contidos nesses arquivos compactados. Se estiver desmarcada, o Trilium irá anexar os próprios arquivos compactados à nota.",
"explodeArchives": "Ler conteúdos de arquivos <code>.zip</code>, <code>.enex</code> e <code>.opml</code>.",
"shrinkImagesTooltip": "<p>Se você marcar esta opção, o Trilium tentará reduzir o tamanho das imagens importadas por meio de escala e otimização, o que pode afetar a qualidade visual das imagens. Se desmarcada, as imagens serão importadas sem alterações.</p><p>Isso não se aplica a importações de arquivos <code>.zip</code> com metadados, pois presume-se que esses arquivos já estejam otimizados.</p>",
"shrinkImages": "Reduzir imagens",
"textImportedAsText": "Importar arquivos HTML, Markdown e TXT como notas de texto caso não esteja claro pelos metadados",
"codeImportedAsCode": "Importar arquivos de código reconhecidos (por exemplo, <code>.json</code>) como notas de código caso não esteja claro pelos metadados",
"replaceUnderscoresWithSpaces": "Substituir sublinhados por espaços nos nomes das notas importadas",
"import": "Importar",
"failed": "Falha na importação: {{message}}.",
"html_import_tags": {
"title": "Tags de importação HTML",
"description": "Configurar quais tags HTML devem ser preservadas ao importar notas. As tags que não estiverem nesta lista serão removidas durante a importação. Algumas tags (como 'script') são sempre removidas por motivos de segurança.",
"placeholder": "Digite as tags HTML, uma por linha",
"reset_button": "Redefinir para lista padrão"
},
"import-status": "Status da importação",
"in-progress": "Importação em andamento: {{progress}}",
"successful": "Importação concluída com sucesso."
},
"include_note": {
"dialog_title": "Incluir nota",
"label_note": "Nota",
"placeholder_search": "pesquisar nota pelo nome",
"box_size_prompt": "Dimensão da caixa da nota incluída:",
"box_size_small": "pequeno (~ 10 linhas)",
"box_size_medium": "médio (~ 30 linhas)",
"box_size_full": "completo (a caixa exibe o texto completo)",
"button_include": "Incluir nota"
},
"info": {
"modalTitle": "Mensagem informativa",
"closeButton": "Fechar",
"okButton": "OK"
},
"jump_to_note": {
"search_placeholder": "Pesquise uma nota pelo nome ou digite > para comandos...",
"search_button": "Pesquisar em texto completo"
},
"markdown_import": {
"dialog_title": "Importar Markdown",
"modal_body_text": "Por motivos de segurança (sandbox do navegador), o JavaScript não pode acessar diretamente a área de transferência. Por favor, cole o conteúdo Markdown na área de texto abaixo e clique em Importar",
"import_button": "Importar",
"import_success": "O conteúdo Markdown foi importado para o documento."
},
"move_to": {
"dialog_title": "Mover notas para...",
"notes_to_move": "Notas para mover",
"target_parent_note": "Nota pai-alvo",
"search_placeholder": "pesquisar nota pelo nome",
"move_button": "Mover para a nota selecionada",
"error_no_path": "Nenhum caminho para mover.",
"move_success_message": "As notas selecionadas foram movidas para "
},
"note_type_chooser": {
"change_path_prompt": "Alterar onde criar a nova nota:",
"search_placeholder": "buscar caminho pelo nome (valor padrão se não for preenchido)",
"modal_title": "Escolher tipo de nota",
"modal_body": "Escolha o tipo/modelo da nova nota:",
"templates": "Modelos",
"builtin_templates": "Modelos Incorporados"
},
"password_not_set": {
"title": "A senha não está definida",
"body1": "Notas protegidas são criptografadas usando uma senha do usuário, mas a senha ainda não foi definida.",
"body2": "Para poder proteger notas, clique no botão abaixo para abrir a caixa de diálogo de Opções e definir sua senha.",
"go_to_password_options": "Ir para opções de Senha"
},
"prompt": {
"title": "Prompt",
"ok": "OK <kbd>enter</kbd>",
"defaultTitle": "Prompt"
},
"protected_session_password": {
"modal_title": "Sessão Protegida",
"help_title": "Ajuda sobre notas protegidas",
"close_label": "Fechar",
"form_label": "Para prosseguir com a ação solicitada, você precisa iniciar uma sessão protegida digitando a senha:",
"start_button": "Iniciar sessão protegida <kbd>enter</kbd>"
},
"recent_changes": {
"title": "Alterações recentes",
"erase_notes_button": "Remover permanentemente as notas excluídas agora",
"deleted_notes_message": "As notas excluídas foram removidas permanentemente.",
"no_changes_message": "Nenhuma alteração ainda...",
"undelete_link": "Restaurar",
"confirm_undelete": "Você deseja restaurar esta nota e suas subnotas?"
},
"revisions": {
"note_revisions": "Versões da nota",
"delete_all_revisions": "Excluir todas as versões desta nota",
"delete_all_button": "Excluir todas as versões",
"help_title": "Ajuda sobre as versões da nota",
"revision_last_edited": "Esta versão foi editada pela última vez em {{date}}",
"confirm_delete_all": "Você quer excluir todas as versões desta nota?",
"no_revisions": "Ainda não há versões para esta nota...",
"restore_button": "Recuperar",
"confirm_restore": "Deseja restaurar esta versão? Isso irá substituir o título e o conteúdo atuais da nota por esta versão.",
"delete_button": "Excluir",
"confirm_delete": "Deseja excluir esta versão?",
"revisions_deleted": "As versões da nota foram removidas.",
"revision_restored": "A versão da nota foi restaurada.",
"revision_deleted": "A versão da nota foi excluída.",
"snapshot_interval": "Intervalo de captura das versões da nota: {{seconds}}s.",
"maximum_revisions": "Limite de capturas das versões da nota: {{number}}.",
"settings": "Configurações de versões da nota",
"download_button": "Download",
"mime": "MIME: ",
"file_size": "Tamanho do arquivo:",
"preview": "Visualizar:",
"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...",
"sorting_criteria": "Critérios de ordenação",
"title": "título",
"date_created": "data de criação",
"date_modified": "data de modificação",
"sorting_direction": "Direção de ordenação",
"ascending": "crescente",
"descending": "decrescente",
"folders": "Pastas",
"sort_folders_at_top": "colocar pastas no topo",
"natural_sort": "Ordenação Natural",
"sort_with_respect_to_different_character_sorting": "classificar de acordo com diferentes regras de ordenação de caracteres e colação em diferentes idiomas ou regiões.",
"natural_sort_language": "Linguagem da ordenação natural",
"the_language_code_for_natural_sort": "O código do idioma para ordenação natural, por exemplo, \"zh-CN\" para chinês.",
"sort": "Ordenar <kbd>enter</kbd>"
},
"upload_attachments": {
"upload_attachments_to_note": "Enviar anexos para a nota",
"choose_files": "Escolher arquivos",
"files_will_be_uploaded": "Os arquivos serão enviados como anexos para",
"options": "Opções",
"shrink_images": "Reduzir imagens",
"upload": "Enviar",
"tooltip": "Se você marcar esta opção, o Trilium tentará reduzir as imagens enviadas redimensionando e otimizando, o que pode afetar a qualidade visual percebida. Se desmarcada, as imagens serão enviadas sem alterações."
},
"attribute_detail": {
"attr_detail_title": "Título Detalhado do Atributo",
"close_button_title": "Cancelar alterações e fechar",
"attr_is_owned_by": "O atributo pertence a",
"attr_name_title": "O nome do atributo pode ser composto apenas por caracteres alfanuméricos, dois-pontos e sublinhado",
"name": "Nome",
"value": "Valor",
"target_note_title": "Relação é uma conexão nomeada entre a nota de origem e a nota de destino.",
"target_note": "Nota de destino",
"promoted_title": "O atributo promovido é exibido de forma destacada na nota.",
"promoted": "Promovido",
"promoted_alias_title": "O nome a ser exibido na interface de atributos promovidos.",
"promoted_alias": "Alias",
"multiplicity_title": "Multiplicidade define quantos atributos com o mesmo nome podem ser criados — no máximo 1 ou mais de 1.",
"multiplicity": "Multiplicidade",
"single_value": "Valor único",
"multi_value": "Valor múltiplo",
"label_type_title": "O tipo do rótulo ajudará o Trilium a escolher a interface adequada para inserir o valor do rótulo.",
"label_type": "Tipo",
"text": "Texto",
"number": "Número",
"boolean": "Booleano",
"date": "Data",
"date_time": "Data e Hora",
"time": "Hora",
"url": "URL",
"precision_title": "Qual número de dígitos após o ponto decimal deve estar disponível na interface de configuração de valor.",
"precision": "Precisão",
"digits": "dígitos",
"inverse_relation_title": "Configuração opcional para definir a qual relação esta é oposta. Exemplo: Pai - Filho são relações inversas entre si.",
"inverse_relation": "Relação inversa",
"inheritable_title": "O atributo herdável será transmitido para todos os descendentes deste ramo.",
"inheritable": "Herdável",
"save_and_close": "Salvar e fechar <kbd>Ctrl+Enter</kbd>",
"delete": "Excluir",
"related_notes_title": "Outras notas com este rótulo",
"more_notes": "Mais notas",
"label": "Detalhe do rótulo",
"label_definition": "Detalhe da definição do rótulo",
"relation": "Detalhe da relação",
"relation_definition": "Detalhe da definição da relação",
"disable_versioning": "desativa a versão automática. Útil, por exemplo, para notas grandes, mas sem importância como grandes bibliotecas JS usadas para scripts",
"calendar_root": "marca a nota que deve ser usada como raiz para notas diárias. Apenas uma deve ser marcada assim.",
"archived": "notas com este rótulo não serão exibidas por padrão nos resultados de busca (também nos diálogos Ir para, Adicionar link, etc).",
"exclude_from_export": "notas (junto com sua subárvore) não serão incluídas em nenhuma exportação de notas",
"run": "define em quais eventos o script deve ser executado. Os valores possíveis são:\n<ul>\n<li>frontendStartup - quando o frontend do Trilium inicia (ou é atualizado), mas não no celular.</li>\n<li>mobileStartup - quando o frontend do Trilium inicia (ou é atualizado), no celular.</li>\n<li>backendStartup - quando o backend do Trilium inicia</li>\n<li>hourly - executa uma vez por hora. Você pode usar o rótulo adicional <code>runAtHour</code> para especificar em qual hora.</li>\n<li>daily - executa uma vez por dia</li>\n</ul>",
"run_on_instance": "Define em qual instância do Trilium isso deve ser executado. Por padrão, todas as instâncias.",
"run_at_hour": "Em qual hora isso deve ser executado. Deve ser usado junto com <code>#run=hourly</code>. Pode ser definido várias vezes para executar mais de uma vez ao dia.",
"disable_inclusion": "scripts com este rótulo não serão incluídos na execução do script pai.",
"sorted": "mantém as notas filhas ordenadas alfabeticamente pelo título",
"sort_direction": "ASC (padrão) ou DESC",
"sort_folders_first": "Pastas (notas com filhos) devem ser ordenadas no topo",
"top": "mantenha a nota fornecida no topo em seu pai (aplica-se apenas a pais ordenados)",
"hide_promoted_attributes": "Ocultar atributos promovidos nesta nota",
"read_only": "o editor está em modo somente leitura. Funciona apenas para notas de texto e código.",
"auto_read_only_disabled": "notas de texto/código podem ser automaticamente configuradas para modo de leitura quando são muito grandes. Você pode desabilitar esse comportamento por nota adicionando este rótulo à nota",
"app_css": "marca notas CSS que são carregadas no aplicativo Trilium e, portanto, podem ser usadas para modificar a aparência do Trilium.",
"app_theme": "marca notas CSS que são temas completos do Trilium e, portanto, estão disponíveis nas opções do Trilium.",
"app_theme_base": "defina como \"next\", \"next-light\" ou \"next-dark\" para usar o tema TriliumNext correspondente (auto, claro ou escuro) como base para um tema personalizado, em vez do tema legado.",
"css_class": "o valor deste rótulo é então adicionado como classe CSS ao nó que representa a nota específica na árvore. Isso pode ser útil para temas avançados. Pode ser usado em notas de modelo.",
"icon_class": "o valor deste rótulo é adicionado como uma classe CSS ao ícone na árvore, o que pode ajudar a distinguir visualmente as notas na árvore. Um exemplo pode ser bx bx-home os ícones são retirados do boxicons. Pode ser usado em notas de modelo.",
"page_size": "número de itens por página na listagem de notas",
"custom_request_handler": "veja <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Manipulador de requisição personalizada</a>",
"custom_resource_provider": "veja <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Manipulador de requisição personalizada</a>",
"widget": "marca esta nota como um widget personalizado que será adicionado à árvore de componentes do Trilium",
"workspace": "marca esta nota como um espaço de trabalho, o que permite fácil hoisting",
"workspace_icon_class": "define a classe CSS do ícone box que será usada na aba quando esta nota for hoisted",
"workspace_tab_background_color": "cor CSS usada na aba da nota quando esta nota é hoisted",
"workspace_calendar_root": "Define a raiz do calendário por espaço de trabalho",
"workspace_template": "Esta nota aparecerá na seleção de modelos disponíveis ao criar uma nova nota, mas apenas quando estiver destacada em um espaço de trabalho que contenha este modelo",
"search_home": "novas notas de pesquisa serão criadas como filhas desta nota",
"workspace_search_home": "novas notas de pesquisa serão criadas como filhas desta nota quando ela for destacada para algum ancestral desta nota de área de trabalho",
"inbox": "localização padrão da caixa de entrada para novas notas quando você cria uma nota usando o botão \"nova nota\" na barra lateral, as notas serão criadas como notas filhas na nota marcada com o rótulo <code>#inbox</code>.",
"workspace_inbox": "local padrão da caixa de entrada para novas notas quando esta nota for destacada para algum ancestral desta nota de área de trabalho",
"sql_console_home": "localização padrão das notas do console SQL",
"bookmark_folder": "nota com este rótulo aparecerá nos favoritos como uma pasta (permitindo acesso aos seus filhos)",
"share_hidden_from_tree": "esta nota está oculta na árvore de navegação à esquerda, mas ainda pode ser acessada via sua URL",
"share_external_link": "a nota funcionará como um link para um site externo na árvore de compartilhamento",
"share_alias": "defina um alias por meio do qual a nota ficará disponível em https://your_trilium_host/share/[your_alias]",
"share_omit_default_css": "o CSS padrão da página de compartilhamento será omitido. Use quando você fizer alterações extensas de estilo.",
"share_root": "marca a nota que é servida na raiz de /share.",
"share_description": "defina o texto a ser adicionado à meta tag HTML \"description\"",
"share_raw": "a nota será servida em seu formato bruto, sem o wrapper HTML",
"share_disallow_robot_indexing": "impedirá que robôs indexem esta nota por meio do cabeçalho <code>X-Robots-Tag: noindex</code>",
"share_credentials": "exigir credenciais para acessar esta nota compartilhada. O valor deve estar no formato 'usuário:senha'. Não se esqueça de tornar esta configuração herdável para que seja aplicada às notas-filhas/imagens.",
"share_index": "notas com este rótulo irão listar todas as raízes das notas compartilhadas",
"display_relations": "nomes das relações separados por vírgula que devem ser exibidos. Todas as outras serão ocultadas.",
"hide_relations": "nomes das relações separados por vírgula que devem ser ocultados. Todas as outras serão exibidas.",
"title_template": "Título padrão das notas criadas como filhas desta nota. O valor é avaliado como uma string JavaScript e pode ser enriquecido com conteúdo dinâmico usando as variáveis injetadas <code>now</code> e <code>parentNote</code>. Exemplos:\n\n<ul>\n <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li>\n <li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n</ul>\n\nVeja a <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki com detalhes</a>, a documentação da API para <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> e para <a href=\"https://day.js.org/docs/en/display/format\">now</a> para mais informações.",
"template": "Esta nota aparecerá na seleção de modelos disponíveis ao criar uma nova nota",
"toc": "<code>#toc</code> ou <code>#toc=show</code> irá forçar a exibição do Sumário, <code>#toc=hide</code> irá forçar que ele fique oculto. Se o rótulo não existir, será considerado o ajuste global",
"color": "define a cor da nota na árvore de notas, links etc. Use qualquer valor de cor CSS válido, como 'red' ou #a13d5f",
"keyboard_shortcut": "Define um atalho de teclado que irá pular imediatamente para esta nota. Exemplo: 'ctrl+alt+e'. É necessário recarregar o frontend para que a alteração tenha efeito."
}
} }

View File

@@ -7,8 +7,7 @@
"sync_version": "Versiune sincronizare:", "sync_version": "Versiune sincronizare:",
"build_date": "Data compilării:", "build_date": "Data compilării:",
"build_revision": "Revizia compilării:", "build_revision": "Revizia compilării:",
"data_directory": "Directorul de date:", "data_directory": "Directorul de date:"
"close": "Închide"
}, },
"abstract_bulk_action": { "abstract_bulk_action": {
"remove_this_search_action": "Înlătură acesată acțiune la căutare" "remove_this_search_action": "Înlătură acesată acțiune la căutare"
@@ -30,14 +29,13 @@
}, },
"add_link": { "add_link": {
"add_link": "Adaugă legătură", "add_link": "Adaugă legătură",
"close": "Închide",
"help_on_links": "Informații despre legături", "help_on_links": "Informații despre legături",
"link_title": "Titlu legătură", "link_title": "Titlu legătură",
"link_title_arbitrary": "titlul legăturii poate fi schimbat în mod arbitrar", "link_title_arbitrary": "titlul legăturii poate fi schimbat în mod arbitrar",
"link_title_mirrors": "titlul legăturii corespunde titlul curent al notiței", "link_title_mirrors": "titlul legăturii corespunde titlul curent al notiței",
"note": "Notiță", "note": "Notiță",
"search_note": "căutați notița după nume", "search_note": "căutați notița după nume",
"button_add_link": "Adaugă legătură <kbd>Enter</kbd>" "button_add_link": "Adaugă legătură"
}, },
"add_relation": { "add_relation": {
"add_relation": "Adaugă relație", "add_relation": "Adaugă relație",
@@ -296,7 +294,6 @@
}, },
"branch_prefix": { "branch_prefix": {
"branch_prefix_saved": "Prefixul ramurii a fost salvat.", "branch_prefix_saved": "Prefixul ramurii a fost salvat.",
"close": "Închide",
"edit_branch_prefix": "Editează prefixul ramurii", "edit_branch_prefix": "Editează prefixul ramurii",
"help_on_tree_prefix": "Informații despre prefixe de ierarhie", "help_on_tree_prefix": "Informații despre prefixe de ierarhie",
"prefix": "Prefix:", "prefix": "Prefix:",
@@ -308,7 +305,6 @@
"bulk_actions": "Acțiuni în masă", "bulk_actions": "Acțiuni în masă",
"bulk_actions_executed": "Acțiunile în masă au fost executate cu succes.", "bulk_actions_executed": "Acțiunile în masă au fost executate cu succes.",
"chosen_actions": "Acțiuni selectate", "chosen_actions": "Acțiuni selectate",
"close": "Închide",
"execute_bulk_actions": "Execută acțiunile în masă", "execute_bulk_actions": "Execută acțiunile în masă",
"include_descendants": "Include descendenții notiței selectate", "include_descendants": "Include descendenții notiței selectate",
"none_yet": "Nicio acțiune... adăugați una printr-un click pe cele disponibile mai jos.", "none_yet": "Nicio acțiune... adăugați una printr-un click pe cele disponibile mai jos.",
@@ -342,7 +338,7 @@
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Clonează notițele către...", "clone_notes_to": "Clonează notițele către...",
"clone_to_selected_note": "Clonează notița selectată <kbd>enter</kbd>", "clone_to_selected_note": "Clonează notița selectată",
"cloned_note_prefix_title": "Notița clonată va fi afișată în ierarhia notiței utilizând prefixul dat", "cloned_note_prefix_title": "Notița clonată va fi afișată în ierarhia notiței utilizând prefixul dat",
"help_on_links": "Informații despre legături", "help_on_links": "Informații despre legături",
"no_path_to_clone_to": "Nicio cale de clonat.", "no_path_to_clone_to": "Nicio cale de clonat.",
@@ -350,8 +346,7 @@
"notes_to_clone": "Notițe de clonat", "notes_to_clone": "Notițe de clonat",
"prefix_optional": "Prefix (opțional)", "prefix_optional": "Prefix (opțional)",
"search_for_note_by_its_name": "căutați notița după nume acesteia", "search_for_note_by_its_name": "căutați notița după nume acesteia",
"target_parent_note": "Notița părinte țintă", "target_parent_note": "Notița părinte țintă"
"close": "Închide"
}, },
"close_pane_button": { "close_pane_button": {
"close_this_pane": "Închide acest panou" "close_this_pane": "Închide acest panou"
@@ -378,8 +373,7 @@
"cancel": "Anulează", "cancel": "Anulează",
"confirmation": "Confirm", "confirmation": "Confirm",
"if_you_dont_check": "Dacă această opțiune nu este bifată, notița va fi ștearsă doar din harta de relații.", "if_you_dont_check": "Dacă această opțiune nu este bifată, notița va fi ștearsă doar din harta de relații.",
"ok": "OK", "ok": "OK"
"close": "Închide"
}, },
"consistency_checks": { "consistency_checks": {
"find_and_fix_button": "Caută și repară probleme de consistență", "find_and_fix_button": "Caută și repară probleme de consistență",
@@ -436,14 +430,14 @@
"undelete_notes_instruction": "După ștergere, se pot recupera din ecranul Schimbări recente." "undelete_notes_instruction": "După ștergere, se pot recupera din ecranul Schimbări recente."
}, },
"delete_notes": { "delete_notes": {
"broken_relations_to_be_deleted": "Următoarele relații vor fi întrerupte și șterse ({{- relationCount}})", "broken_relations_to_be_deleted": "Următoarele relații vor fi întrerupte și șterse ({{ relationCount}})",
"cancel": "Anulează", "cancel": "Anulează",
"delete_all_clones_description": "Șterge și toate clonele (se pot recupera în ecranul Schimbări recente)", "delete_all_clones_description": "Șterge și toate clonele (se pot recupera în ecranul Schimbări recente)",
"delete_notes_preview": "Previzualizare ștergerea notițelor", "delete_notes_preview": "Previzualizare ștergerea notițelor",
"erase_notes_description": "Ștergerea obișnuită doar marchează notițele ca fiind șterse și pot fi recuperate (în ecranul Schimbări recente) pentru o perioadă de timp. Dacă se bifează această opțiune, notițele vor fi șterse imediat fără posibilitatea de a le recupera.", "erase_notes_description": "Ștergerea obișnuită doar marchează notițele ca fiind șterse și pot fi recuperate (în ecranul Schimbări recente) pentru o perioadă de timp. Dacă se bifează această opțiune, notițele vor fi șterse imediat fără posibilitatea de a le recupera.",
"erase_notes_warning": "Șterge notițele permanent (nu se mai pot recupera), incluzând toate clonele. Va forța reîncărcarea aplicației.", "erase_notes_warning": "Șterge notițele permanent (nu se mai pot recupera), incluzând toate clonele. Va forța reîncărcarea aplicației.",
"no_note_to_delete": "Nicio notiță nu va fi ștearsă (doar clonele).", "no_note_to_delete": "Nicio notiță nu va fi ștearsă (doar clonele).",
"notes_to_be_deleted": "Următoarele notițe vor fi șterse ({{- noteCount}})", "notes_to_be_deleted": "Următoarele notițe vor fi șterse ({{notesCount}})",
"ok": "OK", "ok": "OK",
"deleted_relation_text": "Notița {{- note}} ce va fi ștearsă este referențiată de relația {{- relation}}, originând din {{- source}}.", "deleted_relation_text": "Notița {{- note}} ce va fi ștearsă este referențiată de relația {{- relation}}, originând din {{- source}}.",
"close": "Închide" "close": "Închide"
@@ -615,13 +609,12 @@
"activatePreviousTab": "activează tabul anterior", "activatePreviousTab": "activează tabul anterior",
"blockQuote": "începeți un rând cu <code>></code> urmat de spațiu pentru un bloc de citat", "blockQuote": "începeți un rând cu <code>></code> urmat de spațiu pentru un bloc de citat",
"bulletList": "<code>*</code> sau <code>-</code> urmat de spațiu pentru o listă punctată", "bulletList": "<code>*</code> sau <code>-</code> urmat de spațiu pentru o listă punctată",
"close": "Închide",
"closeActiveTab": "închide tabul activ", "closeActiveTab": "închide tabul activ",
"collapseExpand": "<kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - minimizează/expandează nodul", "collapseExpand": "minimizează/expandează nodul",
"collapseSubTree": "minimizează subarborele", "collapseSubTree": "minimizează subarborele",
"collapseWholeTree": "minimizează întregul arbore de notițe", "collapseWholeTree": "minimizează întregul arbore de notițe",
"copyNotes": "copiază notița activă (sau selecția curentă) în clipboard (utilizat pentru <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonare</a>)", "copyNotes": "copiază notița activă (sau selecția curentă) în clipboard (utilizat pentru <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonare</a>)",
"createEditLink": "<kbd>Ctrl+K</kbd> - crează/editează legătură externă", "createEditLink": "crează/editează legătură externă",
"createInternalLink": "crează legătură internă", "createInternalLink": "crează legătură internă",
"createNoteAfter": "crează o nouă notiță după notița activă", "createNoteAfter": "crează o nouă notiță după notița activă",
"createNoteInto": "crează o subnotiță în notița activă", "createNoteInto": "crează o subnotiță în notița activă",
@@ -632,20 +625,19 @@
"editNoteTitle": "va sări de la arborele de notițe către titlul notiței. Enter de la titlul notiței va sări către editorul de text. <kbd>Ctrl+.</kbd> va sări înapoi de la editor către arborele de notițe.", "editNoteTitle": "va sări de la arborele de notițe către titlul notiței. Enter de la titlul notiței va sări către editorul de text. <kbd>Ctrl+.</kbd> va sări înapoi de la editor către arborele de notițe.",
"editingNotes": "Editarea notițelor", "editingNotes": "Editarea notițelor",
"followLink": "urmărește link-ul sub cursor", "followLink": "urmărește link-ul sub cursor",
"fullDocumentation": "Instrucțiuni (documentația completă se regăsește <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)",
"goBackForwards": "mergi înapoi/înainte în istoric", "goBackForwards": "mergi înapoi/înainte în istoric",
"goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> - mergi sus/jos în lista de notițe", "goUpDown": "mergi sus/jos în lista de notițe",
"headings": "<code>##</code>, <code>###</code>, <code>####</code> etc. urmat de spațiu pentru titluri", "headings": "<code>##</code>, <code>###</code>, <code>####</code> etc. urmat de spațiu pentru titluri",
"inPageSearch": "caută în interiorul paginii", "inPageSearch": "caută în interiorul paginii",
"insertDateTime": "inserează data și timpul curente la poziția cursorului", "insertDateTime": "inserează data și timpul curente la poziția cursorului",
"jumpToParentNote": "<kbd>Backspace</kbd> - sari la pagina părinte", "jumpToParentNote": "sari la pagina părinte",
"jumpToTreePane": "sari către arborele de notițe și scrolează către notița activă", "jumpToTreePane": "sari către arborele de notițe și scrolează către notița activă",
"markdownAutoformat": "Formatare în stil Markdown", "markdownAutoformat": "Formatare în stil Markdown",
"moveNoteUpDown": "mută notița sus/jos în lista de notițe", "moveNoteUpDown": "mută notița sus/jos în lista de notițe",
"moveNoteUpHierarchy": "mută notița mai sus în ierarhie", "moveNoteUpHierarchy": "mută notița mai sus în ierarhie",
"movingCloningNotes": "Mutarea/clonarea notițelor", "movingCloningNotes": "Mutarea/clonarea notițelor",
"multiSelectNote": "selectează multiplu notița de sus/jos", "multiSelectNote": "selectează multiplu notița de sus/jos",
"newTabNoteLink": "<kbd>CTRL+clic</kbd> - (sau clic mijlociu) pe o legătură către o notiță va deschide notița într-un tab nou", "newTabNoteLink": "pe o legătură către o notiță va deschide notița într-un tab nou",
"notSet": "nesetat", "notSet": "nesetat",
"noteNavigation": "Navigarea printre notițe", "noteNavigation": "Navigarea printre notițe",
"numberedList": "<kbd>1.</code> sau <code>1)</code> urmat de spațiu pentru o listă numerotată", "numberedList": "<kbd>1.</code> sau <code>1)</code> urmat de spațiu pentru o listă numerotată",
@@ -657,13 +649,13 @@
"reloadFrontend": "reîncarcă interfața Trilium", "reloadFrontend": "reîncarcă interfața Trilium",
"scrollToActiveNote": "scrolează la notița activă", "scrollToActiveNote": "scrolează la notița activă",
"selectAllNotes": "selectează toate notițele din nivelul curent", "selectAllNotes": "selectează toate notițele din nivelul curent",
"selectNote": "<kbd>Shift+Click</kbd> - selectează notița", "selectNote": "selectează notița",
"showDevTools": "afișează instrumentele de dezvoltatori", "showDevTools": "afișează instrumentele de dezvoltatori",
"showJumpToNoteDialog": "afișează <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">ecranul „Sari la”</a>", "showJumpToNoteDialog": "afișează <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">ecranul „Sari la”</a>",
"showSQLConsole": "afișează consola SQL", "showSQLConsole": "afișează consola SQL",
"tabShortcuts": "Scurtături pentru tab-uri", "tabShortcuts": "Scurtături pentru tab-uri",
"troubleshooting": "Unelte pentru depanare", "troubleshooting": "Unelte pentru depanare",
"newTabWithActivationNoteLink": "<kbd>Ctrl+Shift+click</kbd> - (sau <kbd>Shift+click mouse mijlociu</kbd>) pe o legătură către o notiță deschide și activează notița într-un tab nou" "newTabWithActivationNoteLink": "pe o legătură către o notiță deschide și activează notița într-un tab nou"
}, },
"hide_floating_buttons_button": { "hide_floating_buttons_button": {
"button_title": "Ascunde butoanele" "button_title": "Ascunde butoanele"
@@ -718,7 +710,6 @@
}, },
"import": { "import": {
"chooseImportFile": "Selectați fișierul de importat", "chooseImportFile": "Selectați fișierul de importat",
"close": "Închide",
"codeImportedAsCode": "Importă fișiere identificate drept cod sursă (e.g. <code>.json</code>) drept notițe de tip cod dacă nu este clar din metainformații", "codeImportedAsCode": "Importă fișiere identificate drept cod sursă (e.g. <code>.json</code>) drept notițe de tip cod dacă nu este clar din metainformații",
"explodeArchives": "Citește conținutul arhivelor <code>.zip</code>, <code>.enex</code> și <code>.opml</code>.", "explodeArchives": "Citește conținutul arhivelor <code>.zip</code>, <code>.enex</code> și <code>.opml</code>.",
"explodeArchivesTooltip": "Dacă această opțiune este bifată atunci Trilium va citi fișiere de tip <code>.zip</code>, <code>.enex</code> și <code>.opml</code> și va crea notițe din fișierele din interiorul acestor arhive. Dacă este nebifat, atunci Trilium va atașa arhiva propriu-zisă la notiță.", "explodeArchivesTooltip": "Dacă această opțiune este bifată atunci Trilium va citi fișiere de tip <code>.zip</code>, <code>.enex</code> și <code>.opml</code> și va crea notițe din fișierele din interiorul acestor arhive. Dacă este nebifat, atunci Trilium va atașa arhiva propriu-zisă la notiță.",
@@ -751,11 +742,10 @@
"box_size_medium": "mediu (~ 30 de rânduri)", "box_size_medium": "mediu (~ 30 de rânduri)",
"box_size_prompt": "Dimensiunea căsuței notiței incluse:", "box_size_prompt": "Dimensiunea căsuței notiței incluse:",
"box_size_small": "mică (~ 10 rânduri)", "box_size_small": "mică (~ 10 rânduri)",
"button_include": "Include notița <kbd>Enter</kbd>", "button_include": "Include notița",
"dialog_title": "Includere notița", "dialog_title": "Includere notița",
"label_note": "Notiță", "label_note": "Notiță",
"placeholder_search": "căutați notița după denumirea ei", "placeholder_search": "căutați notița după denumirea ei"
"close": "Închide"
}, },
"info": { "info": {
"closeButton": "Închide", "closeButton": "Închide",
@@ -767,8 +757,7 @@
"title": "Atribute moștenite" "title": "Atribute moștenite"
}, },
"jump_to_note": { "jump_to_note": {
"search_button": "Caută în întregul conținut <kbd>Ctrl+Enter</kbd>", "search_button": "Caută în întregul conținut",
"close": "Închide",
"search_placeholder": "Căutați notițe după nume sau tastați > pentru comenzi..." "search_placeholder": "Căutați notițe după nume sau tastați > pentru comenzi..."
}, },
"left_pane_toggle": { "left_pane_toggle": {
@@ -781,10 +770,9 @@
}, },
"markdown_import": { "markdown_import": {
"dialog_title": "Importă Markdown", "dialog_title": "Importă Markdown",
"import_button": "Importă Ctrl+Enter", "import_button": "Importă",
"import_success": "Conținutul Markdown a fost importat în document.", "import_success": "Conținutul Markdown a fost importat în document.",
"modal_body_text": "Din cauza limitărilor la nivel de navigator, nu este posibilă citirea clipboard-ului din JavaScript. Inserați Markdown-ul pentru a-l importa în caseta de mai jos și dați clic pe butonul Import", "modal_body_text": "Din cauza limitărilor la nivel de navigator, nu este posibilă citirea clipboard-ului din JavaScript. Inserați Markdown-ul pentru a-l importa în caseta de mai jos și dați clic pe butonul Import"
"close": "Închide"
}, },
"max_content_width": { "max_content_width": {
"apply_changes_description": "Pentru a aplica schimbările de lățime a conținutului, dați click pe", "apply_changes_description": "Pentru a aplica schimbările de lățime a conținutului, dați click pe",
@@ -817,12 +805,11 @@
"move_to": { "move_to": {
"dialog_title": "Mută notițele în...", "dialog_title": "Mută notițele în...",
"error_no_path": "Nicio cale la care să poată fi mutate.", "error_no_path": "Nicio cale la care să poată fi mutate.",
"move_button": "Mută la notița selectată <kbd>enter</kbd>", "move_button": "Mută la notița selectată",
"move_success_message": "Notițele selectate au fost mutate în", "move_success_message": "Notițele selectate au fost mutate în",
"notes_to_move": "Notițe de mutat", "notes_to_move": "Notițe de mutat",
"search_placeholder": "căutați notița după denumirea ei", "search_placeholder": "căutați notița după denumirea ei",
"target_parent_note": "Notița părinte destinație", "target_parent_note": "Notița părinte destinație"
"close": "Închide"
}, },
"native_title_bar": { "native_title_bar": {
"disabled": "dezactivată", "disabled": "dezactivată",
@@ -897,8 +884,7 @@
"note_type_chooser": { "note_type_chooser": {
"modal_body": "Selectați tipul notiței/șablonul pentru noua notiță:", "modal_body": "Selectați tipul notiței/șablonul pentru noua notiță:",
"modal_title": "Selectați tipul notiței", "modal_title": "Selectați tipul notiței",
"templates": "Șabloane:", "templates": "Șabloane",
"close": "Închide",
"change_path_prompt": "Selectați locul unde să se creeze noua notiță:", "change_path_prompt": "Selectați locul unde să se creeze noua notiță:",
"search_placeholder": "căutare cale notiță după nume (cea implicită dacă este necompletat)" "search_placeholder": "căutare cale notiță după nume (cea implicită dacă este necompletat)"
}, },
@@ -954,9 +940,7 @@
}, },
"password_not_set": { "password_not_set": {
"body1": "Notițele protejate sunt criptate utilizând parola de utilizator, dar nu a fost setată nicio parolă.", "body1": "Notițele protejate sunt criptate utilizând parola de utilizator, dar nu a fost setată nicio parolă.",
"body2": "Pentru a putea să protejați notițe, clic <a class=\"open-password-options-button\" href=\"javascript:\">aici</a> pentru a deschide ecranul de opțiuni și pentru a seta parola.", "title": "Parola nu este setată"
"title": "Parola nu este setată",
"close": "Închide"
}, },
"promoted_attributes": { "promoted_attributes": {
"add_new_attribute": "Adaugă un nou atribut", "add_new_attribute": "Adaugă un nou atribut",
@@ -971,9 +955,8 @@
}, },
"prompt": { "prompt": {
"defaultTitle": "Aviz", "defaultTitle": "Aviz",
"ok": "OK <kbd>enter</kbd>", "ok": "OK",
"title": "Aviz", "title": "Aviz"
"close": "Închide"
}, },
"protected_session": { "protected_session": {
"enter_password_instruction": "Afișarea notițelor protejate necesită introducerea parolei:", "enter_password_instruction": "Afișarea notițelor protejate necesită introducerea parolei:",
@@ -992,7 +975,7 @@
"form_label": "Pentru a putea continua cu acțiunea cerută este nevoie să fie pornită sesiunea protejată prin introducerea parolei:", "form_label": "Pentru a putea continua cu acțiunea cerută este nevoie să fie pornită sesiunea protejată prin introducerea parolei:",
"help_title": "Informații despre notițe protejate", "help_title": "Informații despre notițe protejate",
"modal_title": "Sesiune protejată", "modal_title": "Sesiune protejată",
"start_button": "Pornește sesiunea protejată <kbd>enter</kbd>" "start_button": "Pornește sesiunea protejată"
}, },
"protected_session_status": { "protected_session_status": {
"active": "Sesiunea protejată este activă. Clic pentru a închide sesiunea protejată.", "active": "Sesiunea protejată este activă. Clic pentru a închide sesiunea protejată.",
@@ -1004,8 +987,7 @@
"erase_notes_button": "Elimină notițele șterse", "erase_notes_button": "Elimină notițele șterse",
"no_changes_message": "Încă nicio schimbare...", "no_changes_message": "Încă nicio schimbare...",
"title": "Modificări recente", "title": "Modificări recente",
"undelete_link": "restaurare", "undelete_link": "restaurare"
"close": "Închide"
}, },
"relation_map": { "relation_map": {
"cannot_match_transform": "Nu s-a putut identifica transformarea: {{transform}}", "cannot_match_transform": "Nu s-a putut identifica transformarea: {{transform}}",
@@ -1084,8 +1066,7 @@
"revisions_deleted": "Notița reviziei a fost ștearsă.", "revisions_deleted": "Notița reviziei a fost ștearsă.",
"maximum_revisions": "Numărul maxim de revizii pentru notița curentă: {{number}}.", "maximum_revisions": "Numărul maxim de revizii pentru notița curentă: {{number}}.",
"settings": "Setări revizii ale notițelor", "settings": "Setări revizii ale notițelor",
"snapshot_interval": "Intervalul de creare a reviziilor pentru notițe: {{seconds}}s.", "snapshot_interval": "Intervalul de creare a reviziilor pentru notițe: {{seconds}}s."
"close": "Închide"
}, },
"revisions_button": { "revisions_button": {
"note_revisions": "Revizii ale notiței" "note_revisions": "Revizii ale notiței"
@@ -1193,15 +1174,14 @@
"folders": "Dosare", "folders": "Dosare",
"natural_sort": "Ordonare naturală", "natural_sort": "Ordonare naturală",
"natural_sort_language": "Limba pentru ordonare naturală", "natural_sort_language": "Limba pentru ordonare naturală",
"sort": "Ordonare <kbd>Enter</kbd>", "sort": "Ordonare",
"sort_children_by": "Ordonează subnotițele după...", "sort_children_by": "Ordonează subnotițele după...",
"sort_folders_at_top": "ordonează dosarele primele", "sort_folders_at_top": "ordonează dosarele primele",
"sort_with_respect_to_different_character_sorting": "ordonează respectând regulile de sortare și clasificare diferite în funcție de limbă și regiune.", "sort_with_respect_to_different_character_sorting": "ordonează respectând regulile de sortare și clasificare diferite în funcție de limbă și regiune.",
"sorting_criteria": "Criterii de ordonare", "sorting_criteria": "Criterii de ordonare",
"sorting_direction": "Direcția de ordonare", "sorting_direction": "Direcția de ordonare",
"the_language_code_for_natural_sort": "Codul limbii pentru ordonarea naturală, e.g. „zn-CN” pentru chineză.", "the_language_code_for_natural_sort": "Codul limbii pentru ordonarea naturală, e.g. „zn-CN” pentru chineză.",
"title": "titlu", "title": "titlu"
"close": "Închide"
}, },
"spellcheck": { "spellcheck": {
"available_language_codes_label": "Coduri de limbă disponibile:", "available_language_codes_label": "Coduri de limbă disponibile:",
@@ -1253,12 +1233,9 @@
"unit": "caractere" "unit": "caractere"
}, },
"theme": { "theme": {
"auto_theme": "Temă auto (se adaptează la schema de culori a sistemului)", "triliumnext": "Trilium (se adaptează la schema de culori a sistemului)",
"dark_theme": "Temă întunecată", "triliumnext-light": "Trilium (luminoasă)",
"light_theme": "Temă luminoasă", "triliumnext-dark": "Trilium (întunecată)",
"triliumnext": "TriliumNext Beta (se adaptează la schema de culori a sistemului)",
"triliumnext-light": "TriliumNext Beta (luminoasă)",
"triliumnext-dark": "TriliumNext Beta (întunecată)",
"override_theme_fonts_label": "Suprascrie fonturile temei", "override_theme_fonts_label": "Suprascrie fonturile temei",
"theme_label": "Temă", "theme_label": "Temă",
"title": "Tema aplicației", "title": "Tema aplicației",
@@ -1311,13 +1288,12 @@
}, },
"upload_attachments": { "upload_attachments": {
"choose_files": "Selectați fișierele", "choose_files": "Selectați fișierele",
"files_will_be_uploaded": "Fișierele vor fi încărcate ca atașamente în", "files_will_be_uploaded": "Fișierele vor fi încărcate ca atașamente în {{noteTitle}}",
"options": "Opțuni", "options": "Opțuni",
"shrink_images": "Micșorează imaginile", "shrink_images": "Micșorează imaginile",
"tooltip": "Dacă această opțiune este bifată, Trilium va încerca micșorarea imaginilor încărcate prin scalarea și optimizarea lor, aspect ce va putea afecta calitatea imaginilor. Dacă nu este bifată, imaginile vor fi încărcate fără nicio schimbare.", "tooltip": "Dacă această opțiune este bifată, Trilium va încerca micșorarea imaginilor încărcate prin scalarea și optimizarea lor, aspect ce va putea afecta calitatea imaginilor. Dacă nu este bifată, imaginile vor fi încărcate fără nicio schimbare.",
"upload": "Încărcare", "upload": "Încărcare",
"upload_attachments_to_note": "Încarcă atașamentele la notiță", "upload_attachments_to_note": "Încarcă atașamentele la notiță"
"close": "Închide"
}, },
"vacuum_database": { "vacuum_database": {
"button_text": "Compactează baza de date", "button_text": "Compactează baza de date",
@@ -1673,10 +1649,6 @@
"note-has-been-deleted": "Notița a fost ștearsă.", "note-has-been-deleted": "Notița a fost ștearsă.",
"quick-edit": "Editare rapidă" "quick-edit": "Editare rapidă"
}, },
"notes": {
"duplicate-note-suffix": "(dupl.)",
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
},
"geo-map-context": { "geo-map-context": {
"open-location": "Deschide locația", "open-location": "Deschide locația",
"remove-from-map": "Înlătură de pe hartă", "remove-from-map": "Înlătură de pe hartă",
@@ -1731,9 +1703,6 @@
"description": "Selectați una sau mai multe limbi ce vor apărea în selecția limbii din cadrul secțiunii „Proprietăți de bază” pentru notițele de tip text (editabile sau doar în citire).", "description": "Selectați una sau mai multe limbi ce vor apărea în selecția limbii din cadrul secțiunii „Proprietăți de bază” pentru notițele de tip text (editabile sau doar în citire).",
"title": "Limbi pentru conținutul notițelor" "title": "Limbi pentru conținutul notițelor"
}, },
"hidden-subtree": {
"localization": "Limbă și regiune"
},
"note_language": { "note_language": {
"configure-languages": "Configurează limbile...", "configure-languages": "Configurează limbile...",
"not_set": "Nedefinită" "not_set": "Nedefinită"
@@ -2014,5 +1983,8 @@
}, },
"content_renderer": { "content_renderer": {
"open_externally": "Deschide în afara programului" "open_externally": "Deschide în afara programului"
},
"modal": {
"close": "Închide"
} }
} }

View File

@@ -1,6 +1,5 @@
{ {
"about": { "about": {
"close": "Закрыть",
"app_version": "Версия приложения:", "app_version": "Версия приложения:",
"db_version": "Версия базы данных:", "db_version": "Версия базы данных:",
"sync_version": "Версия синхронизации:", "sync_version": "Версия синхронизации:",
@@ -27,7 +26,6 @@
}, },
"add_link": { "add_link": {
"add_link": "Добавить ссылку", "add_link": "Добавить ссылку",
"close": "Закрыть",
"note": "Заметка", "note": "Заметка",
"link_title": "Заголовок ссылки", "link_title": "Заголовок ссылки",
"link_title_arbitrary": "заголовок ссылки может быть изменен произвольно", "link_title_arbitrary": "заголовок ссылки может быть изменен произвольно",
@@ -37,7 +35,6 @@
"link_title_mirrors": "название ссылки отражает текущий заголовок заметки" "link_title_mirrors": "название ссылки отражает текущий заголовок заметки"
}, },
"branch_prefix": { "branch_prefix": {
"close": "Закрыть",
"save": "Сохранить", "save": "Сохранить",
"edit_branch_prefix": "Редактировать префикс ветки", "edit_branch_prefix": "Редактировать префикс ветки",
"prefix": "Префикс: ", "prefix": "Префикс: ",
@@ -50,7 +47,6 @@
"relations": "Связи", "relations": "Связи",
"notes": "Заметки", "notes": "Заметки",
"other": "Прочее", "other": "Прочее",
"close": "Закрыть",
"affected_notes": "Затронутые заметки", "affected_notes": "Затронутые заметки",
"include_descendants": "Включать потомков выбранных заметок", "include_descendants": "Включать потомков выбранных заметок",
"execute_bulk_actions": "Выполнить массовые действия", "execute_bulk_actions": "Выполнить массовые действия",
@@ -61,7 +57,6 @@
}, },
"confirm": { "confirm": {
"confirmation": "Подтверждение", "confirmation": "Подтверждение",
"close": "Закрыть",
"cancel": "Отмена", "cancel": "Отмена",
"ok": "ОК", "ok": "ОК",
"are_you_sure_remove_note": "Вы уверены, что хотите удалить заметку \"{{title}}\" из карты связей? ", "are_you_sure_remove_note": "Вы уверены, что хотите удалить заметку \"{{title}}\" из карты связей? ",
@@ -111,12 +106,9 @@
"theme": { "theme": {
"theme_label": "Тема", "theme_label": "Тема",
"override_theme_fonts_label": "Переопределить шрифты темы", "override_theme_fonts_label": "Переопределить шрифты темы",
"auto_theme": "Авто", "triliumnext": "Trilium (следует системной цветовой схеме)",
"light_theme": "Светлая", "triliumnext-light": "Trilium (Светлая)",
"dark_theme": "Темная", "triliumnext-dark": "Trilium (Темная)",
"triliumnext": "TriliumNext Beta (следует системной цветовой схеме)",
"triliumnext-light": "TriliumNext Beta (Светлая)",
"triliumnext-dark": "TriliumNext Beta (Темная)",
"title": "Тема приложения", "title": "Тема приложения",
"layout": "Макет", "layout": "Макет",
"layout-vertical-title": "Вертикальный", "layout-vertical-title": "Вертикальный",
@@ -182,7 +174,6 @@
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Клонировать заметки в...", "clone_notes_to": "Клонировать заметки в...",
"close": "Закрыть",
"notes_to_clone": "Заметки для клонирования", "notes_to_clone": "Заметки для клонирования",
"target_parent_note": "Целевая родительская заметка", "target_parent_note": "Целевая родительская заметка",
"search_for_note_by_its_name": "поиск заметки по ее названию", "search_for_note_by_its_name": "поиск заметки по ее названию",
@@ -212,8 +203,6 @@
"format_pdf": "PDF - для печати или обмена." "format_pdf": "PDF - для печати или обмена."
}, },
"help": { "help": {
"fullDocumentation": "Помощь (полная документация доступна <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">онлайн</a>)",
"close": "Закрыть",
"noteNavigation": "Навигация по заметке", "noteNavigation": "Навигация по заметке",
"goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> - вверх/вниз в списке заметок", "goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> - вверх/вниз в списке заметок",
"collapseExpand": "<kbd> LEFT</kbd>, <kbd>RIGHT</kbd> - свернуть/развернуть узел", "collapseExpand": "<kbd> LEFT</kbd>, <kbd>RIGHT</kbd> - свернуть/развернуть узел",
@@ -287,7 +276,6 @@
"import-status": "Статус импорта", "import-status": "Статус импорта",
"in-progress": "Импорт в процессе: {{progress}}", "in-progress": "Импорт в процессе: {{progress}}",
"successful": "Импорт успешно завершен.", "successful": "Импорт успешно завершен.",
"close": "Закрыть",
"options": "Опции", "options": "Опции",
"importDescription": "Содержимое выбранных файлов будет импортировано как дочерние заметки в", "importDescription": "Содержимое выбранных файлов будет импортировано как дочерние заметки в",
"safeImportTooltip": "Файлы экспорта Trilium <code>.zip</code> могут содержать исполняемые скрипты, которые могут быть вредоносными. Безопасный импорт отключит автоматическое выполнение всех импортированных скриптов. Снимите флажок «Безопасный импорт» только в том случае, если импортируемый архив должен содержать исполняемые скрипты, и вы полностью доверяете содержимому импортируемого файла.", "safeImportTooltip": "Файлы экспорта Trilium <code>.zip</code> могут содержать исполняемые скрипты, которые могут быть вредоносными. Безопасный импорт отключит автоматическое выполнение всех импортированных скриптов. Снимите флажок «Безопасный импорт» только в том случае, если импортируемый архив должен содержать исполняемые скрипты, и вы полностью доверяете содержимому импортируемого файла.",
@@ -299,7 +287,6 @@
"markdown_import": { "markdown_import": {
"dialog_title": "Импорт Markdown", "dialog_title": "Импорт Markdown",
"modal_body_text": "Из-за особенностей браузера песочница не позволяет напрямую читать буфер обмена из JavaScript. Вставьте разметку Markdown для импорта в текстовую область ниже и нажмите кнопку «Импорт»", "modal_body_text": "Из-за особенностей браузера песочница не позволяет напрямую читать буфер обмена из JavaScript. Вставьте разметку Markdown для импорта в текстовую область ниже и нажмите кнопку «Импорт»",
"close": "Закрыть",
"import_button": "Импорт Ctrl+Enter", "import_button": "Импорт Ctrl+Enter",
"import_success": "Содержимое Markdown импортировано в документ." "import_success": "Содержимое Markdown импортировано в документ."
}, },
@@ -307,13 +294,11 @@
"modal_title": "Выберите тип заметки", "modal_title": "Выберите тип заметки",
"modal_body": "Выберите тип / шаблон новой заметки:", "modal_body": "Выберите тип / шаблон новой заметки:",
"templates": "Шаблоны:", "templates": "Шаблоны:",
"close": "Закрыть",
"change_path_prompt": "Изменить место создания новой заметки:", "change_path_prompt": "Изменить место создания новой заметки:",
"search_placeholder": "поиск пути по имени (по умолчанию, если пусто)" "search_placeholder": "поиск пути по имени (по умолчанию, если пусто)"
}, },
"password_not_set": { "password_not_set": {
"title": "Пароль не установлен", "title": "Пароль не установлен",
"close": "Закрыть",
"body1": "Защищенные заметки шифруются с помощью пароля пользователя, но пароль еще не установлен.", "body1": "Защищенные заметки шифруются с помощью пароля пользователя, но пароль еще не установлен.",
"body2": "Чтобы иметь возможность защищать заметки, нажмите <a class=\"open-password-options-button\" href=\"javascript:\">здесь</a>, чтобы установить пароль." "body2": "Чтобы иметь возможность защищать заметки, нажмите <a class=\"open-password-options-button\" href=\"javascript:\">здесь</a>, чтобы установить пароль."
}, },
@@ -328,7 +313,6 @@
"title": "Последние изменения", "title": "Последние изменения",
"erase_notes_button": "Удалить заметки, помеченные на удаление сейчас", "erase_notes_button": "Удалить заметки, помеченные на удаление сейчас",
"undelete_link": "восстановить", "undelete_link": "восстановить",
"close": "Закрыть",
"no_changes_message": "Еще нет изменений...", "no_changes_message": "Еще нет изменений...",
"deleted_notes_message": "Удаленные заметки были стерты окончательно.", "deleted_notes_message": "Удаленные заметки были стерты окончательно.",
"confirm_undelete": "Вы хотите восстановить эту заметку и ее подзаметки?" "confirm_undelete": "Вы хотите восстановить эту заметку и ее подзаметки?"
@@ -340,7 +324,6 @@
"delete_all_revisions": "Удалить все версии этой заметки", "delete_all_revisions": "Удалить все версии этой заметки",
"delete_all_button": "Удалить все версии", "delete_all_button": "Удалить все версии",
"help_title": "Помощь по версиям заметок", "help_title": "Помощь по версиям заметок",
"close": "Закрыть",
"confirm_delete_all": "Вы хотите удалить все версии этой заметки?", "confirm_delete_all": "Вы хотите удалить все версии этой заметки?",
"revision_last_edited": "Эта версия последний раз редактировалась {{date}}", "revision_last_edited": "Эта версия последний раз редактировалась {{date}}",
"confirm_restore": "Хотите восстановить эту версию? Текущее название и содержание заметки будут перезаписаны этой версией.", "confirm_restore": "Хотите восстановить эту версию? Текущее название и содержание заметки будут перезаписаны этой версией.",
@@ -360,7 +343,6 @@
}, },
"sort_child_notes": { "sort_child_notes": {
"sort_children_by": "Сортировать дочерние заметки по...", "sort_children_by": "Сортировать дочерние заметки по...",
"close": "Закрыть",
"sorting_criteria": "Критерии сортировки", "sorting_criteria": "Критерии сортировки",
"title": "наименованию", "title": "наименованию",
"date_created": "дате создания", "date_created": "дате создания",
@@ -378,7 +360,6 @@
}, },
"upload_attachments": { "upload_attachments": {
"upload_attachments_to_note": "Загрузить вложения к заметке", "upload_attachments_to_note": "Загрузить вложения к заметке",
"close": "Закрыть",
"choose_files": "Выберите файлы", "choose_files": "Выберите файлы",
"files_will_be_uploaded": "Файлы будут загружены как приложения в", "files_will_be_uploaded": "Файлы будут загружены как приложения в",
"options": "Параметры", "options": "Параметры",
@@ -659,12 +640,10 @@
"modalTitle": "Информация" "modalTitle": "Информация"
}, },
"jump_to_note": { "jump_to_note": {
"close": "Закрыть",
"search_placeholder": "Найдите заметку по ее названию или введите > для команд...", "search_placeholder": "Найдите заметку по ее названию или введите > для команд...",
"search_button": "Поиск по всему тексту <kbd>Ctrl+Enter</kbd>" "search_button": "Поиск по всему тексту <kbd>Ctrl+Enter</kbd>"
}, },
"move_to": { "move_to": {
"close": "Закрыть",
"target_parent_note": "Целевая родительская заметка", "target_parent_note": "Целевая родительская заметка",
"notes_to_move": "Заметки к переносу", "notes_to_move": "Заметки к переносу",
"dialog_title": "Переместить заметки в ...", "dialog_title": "Переместить заметки в ...",
@@ -675,7 +654,6 @@
}, },
"prompt": { "prompt": {
"title": "Запрос", "title": "Запрос",
"close": "Закрыть",
"defaultTitle": "Запрос", "defaultTitle": "Запрос",
"ok": "OK <kbd>enter</kbd>" "ok": "OK <kbd>enter</kbd>"
}, },
@@ -1063,7 +1041,6 @@
}, },
"include_note": { "include_note": {
"dialog_title": "Вставить заметку", "dialog_title": "Вставить заметку",
"close": "Закрыть",
"label_note": "Заметка", "label_note": "Заметка",
"button_include": "Вставить заметку <kbd>enter</kbd>", "button_include": "Вставить заметку <kbd>enter</kbd>",
"placeholder_search": "поиск заметки по ее названию", "placeholder_search": "поиск заметки по ее названию",

View File

@@ -0,0 +1,9 @@
{
"about": {
"title": "Podrobnosti Trilium Notes",
"homepage": "Domača stran:",
"app_version": "Verzija aplikacije:",
"db_version": "Verzija DB:",
"sync_version": "Verzija Sync:"
}
}

View File

@@ -1,7 +1,6 @@
{ {
"about": { "about": {
"title": "O Trilium Belеškama", "title": "O Trilium Belеškama",
"close": "Zatvori",
"homepage": "Početna stranica:", "homepage": "Početna stranica:",
"app_version": "Verzija aplikacije:", "app_version": "Verzija aplikacije:",
"db_version": "Verzija baze podataka:", "db_version": "Verzija baze podataka:",
@@ -28,7 +27,6 @@
"add_link": { "add_link": {
"add_link": "Dodaj link", "add_link": "Dodaj link",
"help_on_links": "Pomoć na linkovima", "help_on_links": "Pomoć na linkovima",
"close": "Zatvori",
"note": "Beleška", "note": "Beleška",
"search_note": "potražite belešku po njenom imenu", "search_note": "potražite belešku po njenom imenu",
"link_title_mirrors": "naziv linka preslikava trenutan naziv beleške", "link_title_mirrors": "naziv linka preslikava trenutan naziv beleške",
@@ -39,14 +37,12 @@
"branch_prefix": { "branch_prefix": {
"edit_branch_prefix": "Izmeni prefiks grane", "edit_branch_prefix": "Izmeni prefiks grane",
"help_on_tree_prefix": "Pomoć na prefiksu Drveta", "help_on_tree_prefix": "Pomoć na prefiksu Drveta",
"close": "Zatvori",
"prefix": "Prefiks: ", "prefix": "Prefiks: ",
"save": "Sačuvaj", "save": "Sačuvaj",
"branch_prefix_saved": "Prefiks grane je sačuvan." "branch_prefix_saved": "Prefiks grane je sačuvan."
}, },
"bulk_actions": { "bulk_actions": {
"bulk_actions": "Grupne akcije", "bulk_actions": "Grupne akcije",
"close": "Zatvori",
"affected_notes": "Pogođene beleške", "affected_notes": "Pogođene beleške",
"include_descendants": "Obuhvati potomke izabranih beleški", "include_descendants": "Obuhvati potomke izabranih beleški",
"available_actions": "Dostupne akcije", "available_actions": "Dostupne akcije",
@@ -61,7 +57,6 @@
}, },
"clone_to": { "clone_to": {
"clone_notes_to": "Klonirajte beleške u...", "clone_notes_to": "Klonirajte beleške u...",
"close": "Zatvori",
"help_on_links": "Pomoć na linkovima", "help_on_links": "Pomoć na linkovima",
"notes_to_clone": "Beleške za kloniranje", "notes_to_clone": "Beleške za kloniranje",
"target_parent_note": "Ciljna nadređena beleška", "target_parent_note": "Ciljna nadređena beleška",
@@ -74,7 +69,6 @@
}, },
"confirm": { "confirm": {
"confirmation": "Potvrda", "confirmation": "Potvrda",
"close": "Zatvori",
"cancel": "Otkaži", "cancel": "Otkaži",
"ok": "U redu", "ok": "U redu",
"are_you_sure_remove_note": "Da li ste sigurni da želite da uklonite belešku \"{{title}}\" iz mape odnosa? ", "are_you_sure_remove_note": "Da li ste sigurni da želite da uklonite belešku \"{{title}}\" iz mape odnosa? ",
@@ -113,8 +107,6 @@
"format_pdf": "PDF - za namene štampanja ili deljenja." "format_pdf": "PDF - za namene štampanja ili deljenja."
}, },
"help": { "help": {
"fullDocumentation": "Pomoć (puna dokumentacija je dostupna <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)",
"close": "Zatvori",
"noteNavigation": "Navigacija beleški", "noteNavigation": "Navigacija beleški",
"goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> - kretanje gore/dole u listi sa beleškama", "goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> - kretanje gore/dole u listi sa beleškama",
"collapseExpand": "<kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - sakupi/proširi čvor", "collapseExpand": "<kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - sakupi/proširi čvor",
@@ -122,12 +114,12 @@
"goBackForwards": "idi u nazad/napred kroz istoriju", "goBackForwards": "idi u nazad/napred kroz istoriju",
"showJumpToNoteDialog": "prikaži <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Idi na\" dijalog</a>", "showJumpToNoteDialog": "prikaži <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Idi na\" dijalog</a>",
"scrollToActiveNote": "skroluj do aktivne beleške", "scrollToActiveNote": "skroluj do aktivne beleške",
"jumpToParentNote": "<kbd>Backspace</kbd> - idi do nadređene beleške", "jumpToParentNote": "idi do nadređene beleške",
"collapseWholeTree": "sakupi celo drvo beleški", "collapseWholeTree": "sakupi celo drvo beleški",
"collapseSubTree": "sakupi pod-drvo", "collapseSubTree": "sakupi pod-drvo",
"tabShortcuts": "Prečice na karticama", "tabShortcuts": "Prečice na karticama",
"newTabNoteLink": "<kbd>Ctrl+click</kbd> - (ili <kbd>middle mouse click</kbd>) na link beleške otvara belešku u novoj kartici", "newTabNoteLink": "na link beleške otvara belešku u novoj kartici",
"newTabWithActivationNoteLink": "<kbd>Ctrl+Shift+click</kbd> - (ili <kbd>Shift+middle mouse click</kbd>) na link beleške otvara i aktivira belešku u novoj kartici", "newTabWithActivationNoteLink": "na link beleške otvara i aktivira belešku u novoj kartici",
"onlyInDesktop": "Samo na dektop-u (Electron verzija)", "onlyInDesktop": "Samo na dektop-u (Electron verzija)",
"openEmptyTab": "otvori praznu karticu", "openEmptyTab": "otvori praznu karticu",
"closeActiveTab": "zatvori aktivnu karticu", "closeActiveTab": "zatvori aktivnu karticu",
@@ -142,14 +134,14 @@
"moveNoteUpHierarchy": "pomeri belešku na gore u hijerarhiji", "moveNoteUpHierarchy": "pomeri belešku na gore u hijerarhiji",
"multiSelectNote": "višestruki izbor beleški iznad/ispod", "multiSelectNote": "višestruki izbor beleški iznad/ispod",
"selectAllNotes": "izaberi sve beleške u trenutnom nivou", "selectAllNotes": "izaberi sve beleške u trenutnom nivou",
"selectNote": "<kbd>Shift+click</kbd> - izaberi belešku", "selectNote": "izaberi belešku",
"copyNotes": "kopiraj aktivnu belešku (ili trenutni izbor) u privremenu memoriju (koristi se za <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">kloniranje</a>)", "copyNotes": "kopiraj aktivnu belešku (ili trenutni izbor) u privremenu memoriju (koristi se za <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">kloniranje</a>)",
"cutNotes": "iseci trenutnu belešku (ili trenutni izbor) u privremenu memoriju (koristi se za premeštanje beleški)", "cutNotes": "iseci trenutnu belešku (ili trenutni izbor) u privremenu memoriju (koristi se za premeštanje beleški)",
"pasteNotes": "nalepi belešku/e kao podbelešku u aktivnoj belešci (koja se ili premešta ili klonira u zavisnosti od toga da li je beleška kopirana ili isečena u privremenu memoriju)", "pasteNotes": "nalepi belešku/e kao podbelešku u aktivnoj belešci (koja se ili premešta ili klonira u zavisnosti od toga da li je beleška kopirana ili isečena u privremenu memoriju)",
"deleteNotes": "obriši belešku / podstablo", "deleteNotes": "obriši belešku / podstablo",
"editingNotes": "Izmena beleški", "editingNotes": "Izmena beleški",
"editNoteTitle": "u ravni drveta će se prebaciti sa ravni drveta na naslov beleške. Ulaz sa naslova beleške će prebaciti fokus na uređivač teksta. <kbd>Ctrl+.</kbd> će se vratiti sa uređivača na ravan drveta.", "editNoteTitle": "u ravni drveta će se prebaciti sa ravni drveta na naslov beleške. Ulaz sa naslova beleške će prebaciti fokus na uređivač teksta. <kbd>Ctrl+.</kbd> će se vratiti sa uređivača na ravan drveta.",
"createEditLink": "<kbd>Ctrl+K</kbd> - napravi / izmeni spoljašnji link", "createEditLink": "napravi / izmeni spoljašnji link",
"createInternalLink": "napravi unutrašnji link", "createInternalLink": "napravi unutrašnji link",
"followLink": "prati link ispod kursora", "followLink": "prati link ispod kursora",
"insertDateTime": "ubaci trenutan datum i vreme na poziciju kursora", "insertDateTime": "ubaci trenutan datum i vreme na poziciju kursora",
@@ -169,7 +161,6 @@
}, },
"import": { "import": {
"importIntoNote": "Uvezi u belešku", "importIntoNote": "Uvezi u belešku",
"close": "Zatvori",
"chooseImportFile": "Izaberi datoteku za uvoz", "chooseImportFile": "Izaberi datoteku za uvoz",
"importDescription": "Sadržaj izabranih datoteka će biti uvezen kao podbeleške u", "importDescription": "Sadržaj izabranih datoteka će biti uvezen kao podbeleške u",
"options": "Opcije", "options": "Opcije",
@@ -196,14 +187,13 @@
}, },
"include_note": { "include_note": {
"dialog_title": "Uključi belešku", "dialog_title": "Uključi belešku",
"close": "Zatvori",
"label_note": "Beleška", "label_note": "Beleška",
"placeholder_search": "pretraži belešku po njenom imenu", "placeholder_search": "pretraži belešku po njenom imenu",
"box_size_prompt": "Veličina kutije priložene beleške:", "box_size_prompt": "Veličina kutije priložene beleške:",
"box_size_small": "mala (~ 10 redova)", "box_size_small": "mala (~ 10 redova)",
"box_size_medium": "srednja (~ 30 redova)", "box_size_medium": "srednja (~ 30 redova)",
"box_size_full": "puna (kutija prikazuje ceo tekst)", "box_size_full": "puna (kutija prikazuje ceo tekst)",
"button_include": "Uključi belešku <kbd>enter</kbd>" "button_include": "Uključi belešku"
}, },
"info": { "info": {
"modalTitle": "Informativna poruka", "modalTitle": "Informativna poruka",
@@ -212,23 +202,20 @@
}, },
"jump_to_note": { "jump_to_note": {
"search_placeholder": "Pretraži belešku po njenom imenu ili unesi > za komande...", "search_placeholder": "Pretraži belešku po njenom imenu ili unesi > za komande...",
"close": "Zatvori",
"search_button": "Pretraga u punom tekstu <kbd>Ctrl+Enter</kbd>" "search_button": "Pretraga u punom tekstu <kbd>Ctrl+Enter</kbd>"
}, },
"markdown_import": { "markdown_import": {
"dialog_title": "Uvoz za Markdown", "dialog_title": "Uvoz za Markdown",
"close": "Zatvori",
"modal_body_text": "Zbog Sandbox-a pretraživača nije moguće direktno učitati privremenu memoriju iz JavaScript-a. Molimo vas da nalepite Markdown za uvoz u tekstualno polje ispod i kliknete na dugme za uvoz", "modal_body_text": "Zbog Sandbox-a pretraživača nije moguće direktno učitati privremenu memoriju iz JavaScript-a. Molimo vas da nalepite Markdown za uvoz u tekstualno polje ispod i kliknete na dugme za uvoz",
"import_button": "Uvoz Ctrl+Enter", "import_button": "Uvoz",
"import_success": "Markdown sadržaj je učitan u dokument." "import_success": "Markdown sadržaj je učitan u dokument."
}, },
"move_to": { "move_to": {
"dialog_title": "Premesti beleške u ...", "dialog_title": "Premesti beleške u ...",
"close": "Zatvori",
"notes_to_move": "Beleške za premeštanje", "notes_to_move": "Beleške za premeštanje",
"target_parent_note": "Ciljana nadbeleška", "target_parent_note": "Ciljana nadbeleška",
"search_placeholder": "potraži belešku po njenom imenu", "search_placeholder": "potraži belešku po njenom imenu",
"move_button": "Pređi na izabranu belešku <kbd>enter</kbd>", "move_button": "Pređi na izabranu belešku",
"error_no_path": "Nema putanje za premeštanje.", "error_no_path": "Nema putanje za premeštanje.",
"move_success_message": "Izabrane beleške su premeštene u " "move_success_message": "Izabrane beleške su premeštene u "
}, },
@@ -236,19 +223,16 @@
"change_path_prompt": "Promenite gde će se napraviti nova beleška:", "change_path_prompt": "Promenite gde će se napraviti nova beleška:",
"search_placeholder": "pretraži putanju po njenom imenu (podrazumevano ako je prazno)", "search_placeholder": "pretraži putanju po njenom imenu (podrazumevano ako je prazno)",
"modal_title": "Izaberite tip beleške", "modal_title": "Izaberite tip beleške",
"close": "Zatvori",
"modal_body": "Izaberite tip beleške / šablon za novu belešku:", "modal_body": "Izaberite tip beleške / šablon za novu belešku:",
"templates": "Šabloni:" "templates": "Šabloni"
}, },
"password_not_set": { "password_not_set": {
"title": "Lozinka nije podešena", "title": "Lozinka nije podešena",
"close": "Zatvori",
"body1": "Zaštićene beleške su enkriptovane sa korisničkom lozinkom, ali lozinka još uvek nije podešena.", "body1": "Zaštićene beleške su enkriptovane sa korisničkom lozinkom, ali lozinka još uvek nije podešena.",
"body2": "Za biste mogli da sačuvate beleške, kliknite <a class=\"open-password-options-button\" href=\"javascript:\">ovde</a> da otvorite dijalog sa Opcijama i podesite svoju lozinku." "body2": "Za biste mogli da sačuvate beleške, kliknite <a class=\"open-password-options-button\" href=\"javascript:\">ovde</a> da otvorite dijalog sa Opcijama i podesite svoju lozinku."
}, },
"prompt": { "prompt": {
"title": "Upit", "title": "Upit",
"close": "Zatvori",
"ok": "U redu <kbd>enter</kbd>", "ok": "U redu <kbd>enter</kbd>",
"defaultTitle": "Upit" "defaultTitle": "Upit"
}, },
@@ -257,12 +241,11 @@
"help_title": "Pomoć za Zaštićene beleške", "help_title": "Pomoć za Zaštićene beleške",
"close_label": "Zatvori", "close_label": "Zatvori",
"form_label": "Da biste nastavili sa traženom akcijom moraćete započeti zaštićenu sesiju tako što ćete uneti lozinku:", "form_label": "Da biste nastavili sa traženom akcijom moraćete započeti zaštićenu sesiju tako što ćete uneti lozinku:",
"start_button": "Započni zaštićenu sesiju <kbd>enter</kbd>" "start_button": "Započni zaštićenu sesiju"
}, },
"recent_changes": { "recent_changes": {
"title": "Nedavne promene", "title": "Nedavne promene",
"erase_notes_button": "Obriši izabrane beleške odmah", "erase_notes_button": "Obriši izabrane beleške odmah",
"close": "Zatvori",
"deleted_notes_message": "Obrisane beleške su uklonjene.", "deleted_notes_message": "Obrisane beleške su uklonjene.",
"no_changes_message": "Još uvek nema izmena...", "no_changes_message": "Još uvek nema izmena...",
"undelete_link": "poništi brisanje", "undelete_link": "poništi brisanje",
@@ -273,7 +256,6 @@
"delete_all_revisions": "Obriši sve revizije ove beleške", "delete_all_revisions": "Obriši sve revizije ove beleške",
"delete_all_button": "Obriši sve revizije", "delete_all_button": "Obriši sve revizije",
"help_title": "Pomoć za Revizije beleški", "help_title": "Pomoć za Revizije beleški",
"close": "Zatvori",
"revision_last_edited": "Ova revizija je poslednji put izmenjena {{date}}", "revision_last_edited": "Ova revizija je poslednji put izmenjena {{date}}",
"confirm_delete_all": "Da li želite da obrišete sve revizije ove beleške?", "confirm_delete_all": "Da li želite da obrišete sve revizije ove beleške?",
"no_revisions": "Još uvek nema revizija za ovu belešku...", "no_revisions": "Još uvek nema revizija za ovu belešku...",
@@ -295,7 +277,6 @@
}, },
"sort_child_notes": { "sort_child_notes": {
"sort_children_by": "Sortiranje podbeleški po...", "sort_children_by": "Sortiranje podbeleški po...",
"close": "Zatvori",
"sorting_criteria": "Kriterijum za sortiranje", "sorting_criteria": "Kriterijum za sortiranje",
"title": "naslov", "title": "naslov",
"date_created": "datum kreiranja", "date_created": "datum kreiranja",
@@ -309,13 +290,12 @@
"sort_with_respect_to_different_character_sorting": "sortiranje sa poštovanjem različitih pravila sortiranja karaktera i kolacija u različitim jezicima ili regionima.", "sort_with_respect_to_different_character_sorting": "sortiranje sa poštovanjem različitih pravila sortiranja karaktera i kolacija u različitim jezicima ili regionima.",
"natural_sort_language": "Jezik za prirodno sortiranje", "natural_sort_language": "Jezik za prirodno sortiranje",
"the_language_code_for_natural_sort": "Kod jezika za prirodno sortiranje, npr. \"zh-CN\" za Kineski.", "the_language_code_for_natural_sort": "Kod jezika za prirodno sortiranje, npr. \"zh-CN\" za Kineski.",
"sort": "Sortiraj <kbd>enter</kbd>" "sort": "Sortiraj"
}, },
"upload_attachments": { "upload_attachments": {
"upload_attachments_to_note": "Otpremite priloge uz belešku", "upload_attachments_to_note": "Otpremite priloge uz belešku",
"close": "Zatvori",
"choose_files": "Izaberite datoteke", "choose_files": "Izaberite datoteke",
"files_will_be_uploaded": "Datoteke će biti otpremljene kao prilozi u", "files_will_be_uploaded": "Datoteke će biti otpremljene kao prilozi u {{noteTitle}}",
"options": "Opcije", "options": "Opcije",
"shrink_images": "Smanji slike", "shrink_images": "Smanji slike",
"upload": "Otpremi", "upload": "Otpremi",

View File

@@ -1,71 +1,26 @@
{ {
"about": { "about": {
"close": "Kapat",
"homepage": "Giriş sayfası:", "homepage": "Giriş sayfası:",
"app_version": "Uygulama versiyonu:", "app_version": "Uygulama versiyonu:",
"db_version": "Veritabanı versiyonu:" "db_version": "Veritabanı versiyonu:"
}, },
"add_link": {
"close": "Kapat"
},
"branch_prefix": { "branch_prefix": {
"close": "Kapat",
"save": "Kaydet" "save": "Kaydet"
}, },
"bulk_actions": {
"close": "Kapat"
},
"clone_to": {
"close": "Kapat"
},
"confirm": {
"close": "Kapat"
},
"recent_changes": {
"close": "Kapat"
},
"delete_notes": { "delete_notes": {
"close": "Kapat" "close": "Kapat"
}, },
"export": { "export": {
"close": "Kapat" "close": "Kapat"
}, },
"help": {
"close": "Kapat"
},
"include_note": {
"close": "Kapat"
},
"import": { "import": {
"close": "Kapat",
"chooseImportFile": "İçe aktarım dosyası", "chooseImportFile": "İçe aktarım dosyası",
"importDescription": "Seçilen dosya(lar) alt not olarak içe aktarılacaktır" "importDescription": "Seçilen dosya(lar) alt not olarak içe aktarılacaktır"
}, },
"info": { "info": {
"closeButton": "Kapat" "closeButton": "Kapat"
}, },
"jump_to_note": {
"close": "Kapat"
},
"markdown_import": {
"close": "Kapat"
},
"move_to": {
"close": "Kapat"
},
"note_type_chooser": {
"close": "Kapat"
},
"password_not_set": {
"close": "Kapat"
},
"prompt": {
"close": "Kapat"
},
"protected_session_password": { "protected_session_password": {
"close_label": "Kapat" "close_label": "Kapat"
},
"revisions": {
"close": "Kapat"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,35 +1,32 @@
{ {
"about": { "about": {
"close": "Đóng",
"homepage": "Trang chủ:", "homepage": "Trang chủ:",
"title": "Về Trilium Notes" "title": "Về Trilium Notes"
}, },
"add_link": { "add_link": {
"close": "Đóng" "add_link": "Thêm liên kết",
"button_add_link": "Thêm liên kết"
}, },
"bulk_actions": { "bulk_actions": {
"close": "Đóng" "other": "Khác"
}, },
"branch_prefix": { "branch_prefix": {
"close": "Đóng",
"save": "Lưu" "save": "Lưu"
}, },
"clone_to": {
"close": "Đóng"
},
"confirm": { "confirm": {
"close": "Đóng", "ok": "OK",
"ok": "OK" "cancel": "Huỷ"
}, },
"delete_notes": { "delete_notes": {
"close": "Đóng", "close": "Đóng",
"ok": "OK" "ok": "OK",
"cancel": "Huỷ"
}, },
"export": { "export": {
"close": "Đóng" "close": "Đóng"
}, },
"help": { "help": {
"close": "Đóng" "other": "Khác"
}, },
"toast": { "toast": {
"critical-error": { "critical-error": {
@@ -37,7 +34,36 @@
} }
}, },
"import": { "import": {
"close": "Đóng",
"options": "Tuỳ chọn" "options": "Tuỳ chọn"
},
"info": {
"okButton": "OK",
"closeButton": "Đóng"
},
"move_to": {
"dialog_title": "Chuyển ghi chép tới..."
},
"prompt": {
"ok": "OK"
},
"protected_session_password": {
"close_label": "Đóng"
},
"revisions": {
"restore_button": "Khôi phục",
"delete_button": "Xoá"
},
"upload_attachments": {
"options": "Tuỳ chọn"
},
"attribute_detail": {
"name": "Tên",
"value": "Giá trị",
"text": "Văn bản",
"number": "Số",
"delete": "Xoá"
},
"rename_note": {
"rename_note": "Đổi tên ghi chép"
} }
} }

View File

@@ -0,0 +1,54 @@
import { ComponentChildren } from "preact";
import { memo } from "preact/compat";
import AbstractBulkAction from "./abstract_bulk_action";
interface BulkActionProps {
label: string | ComponentChildren;
children?: ComponentChildren;
helpText?: ComponentChildren;
bulkAction: AbstractBulkAction;
}
// Define styles as constants to prevent recreation
const flexContainerStyle = { display: "flex", alignItems: "center" } as const;
const labelStyle = { marginRight: "10px" } as const;
const textStyle = { marginRight: "10px", marginLeft: "10px" } as const;
const BulkAction = memo(({ label, children, helpText, bulkAction }: BulkActionProps) => {
return (
<tr>
<td colSpan={2}>
<div style={flexContainerStyle}>
<div style={labelStyle} className="text-nowrap">{label}</div>
{children}
</div>
</td>
<td className="button-column">
{helpText && <div className="dropdown help-dropdown">
<span className="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div className="dropdown-menu dropdown-menu-right p-4">
{helpText}
</div>
</div>}
<span
className="bx bx-x icon-action action-conf-del"
onClick={() => bulkAction?.deleteAction()}
/>
</td>
</tr>
);
});
export default BulkAction;
export const BulkActionText = memo(({ text }: { text: string }) => {
return (
<div
style={textStyle}
className="text-nowrap">
{text}
</div>
);
});

View File

@@ -1,10 +1,9 @@
import { t } from "../../services/i18n.js";
import server from "../../services/server.js"; import server from "../../services/server.js";
import ws from "../../services/ws.js"; import ws from "../../services/ws.js";
import utils from "../../services/utils.js";
import type FAttribute from "../../entities/fattribute.js"; import type FAttribute from "../../entities/fattribute.js";
import { VNode } from "preact";
interface ActionDefinition { export interface ActionDefinition {
script: string; script: string;
relationName: string; relationName: string;
targetNoteId: string; targetNoteId: string;
@@ -27,26 +26,9 @@ export default abstract class AbstractBulkAction {
this.actionDef = actionDef; this.actionDef = actionDef;
} }
render() {
try {
const $rendered = this.doRender();
$rendered
.find(".action-conf-del")
.on("click", () => this.deleteAction())
.attr("title", t("abstract_bulk_action.remove_this_search_action"));
utils.initHelpDropdown($rendered);
return $rendered;
} catch (e: any) {
logError(`Failed rendering search action: ${JSON.stringify(this.attribute.dto)} with error: ${e.message} ${e.stack}`);
return null;
}
}
// to be overridden // to be overridden
abstract doRender(): JQuery<HTMLElement>; abstract doRender(): VNode;
static get actionName() { static get actionName() {
return ""; return "";
} }
@@ -66,9 +48,6 @@ export default abstract class AbstractBulkAction {
async deleteAction() { async deleteAction() {
await server.remove(`notes/${this.attribute.noteId}/attributes/${this.attribute.attributeId}`); await server.remove(`notes/${this.attribute.noteId}/attributes/${this.attribute.attributeId}`);
await ws.waitForMaxKnownEntityChangeId(); await ws.waitForMaxKnownEntityChangeId();
//await this.triggerCommand('refreshSearchDefinition');
} }
} }

View File

@@ -1,58 +0,0 @@
import { t } from "../../services/i18n.js";
import SpacedUpdate from "../../services/spaced_update.js";
import AbstractBulkAction from "./abstract_bulk_action.js";
const TPL = /*html*/`
<tr>
<td>
${t("execute_script.execute_script")}
</td>
<td>
<input type="text"
class="form-control script"
placeholder="note.title = note.title + '- suffix';"/>
</td>
<td class="button-column">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t("execute_script.help_text")}
${t("execute_script.example_1")}
<pre>note.title = note.title + ' - suffix';</pre>
${t("execute_script.example_2")}
<pre>for (const attr of note.getOwnedAttributes) { attr.markAsDeleted(); }</pre>
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</div>
</td>
</tr>`;
export default class ExecuteScriptBulkAction extends AbstractBulkAction {
static get actionName() {
return "executeScript";
}
static get actionTitle() {
return t("execute_script.execute_script");
}
doRender() {
const $action = $(TPL);
const $script = $action.find(".script");
$script.val(this.actionDef.script || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({ script: $script.val() });
}, 1000);
$script.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,50 @@
import { useEffect, useState } from "preact/hooks";
import { t } from "../../services/i18n.js";
import FormTextBox from "../react/FormTextBox.jsx";
import AbstractBulkAction, { ActionDefinition } from "./abstract_bulk_action.js";
import BulkAction from "./BulkAction.jsx";
import { useSpacedUpdate } from "../react/hooks.jsx";
function ExecuteScriptBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
const [ script, setScript ] = useState(actionDef.script);
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ script }));
useEffect(() => spacedUpdate.scheduleUpdate(), [ script ]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("execute_script.execute_script")}
helpText={<>
{t("execute_script.help_text")}
{t("execute_script.example_1")}
<pre>note.title = note.title + ' - suffix';</pre>
{t("execute_script.example_2")}
<pre>{"for (const attr of note.getOwnedAttributes) { attr.markAsDeleted(); }"}</pre>
</>}
>
<FormTextBox
placeholder="note.title = note.title + '- suffix';"
currentValue={script} onChange={setScript}
/>
</BulkAction>
);
}
export default class ExecuteScriptBulkAction extends AbstractBulkAction {
static get actionName() {
return "executeScript";
}
static get actionTitle() {
return t("execute_script.execute_script");
}
doRender() {
return <ExecuteScriptBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,70 +0,0 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">${t("add_label.add_label")}</div>
<input type="text"
class="form-control label-name"
placeholder="${t("add_label.label_name_placeholder")}"
pattern="[\\p{L}\\p{N}_:]+"
title="${t("add_label.label_name_title")}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("add_label.to_value")}</div>
<input type="text" class="form-control label-value" placeholder="${t("add_label.new_value_placeholder")}"/>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("add_label.help_text")}</p>
<ul>
<li>${t("add_label.help_text_item1")}</li>
<li>${t("add_label.help_text_item2")}</li>
</ul>
${t("add_label.help_text_note")}
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class AddLabelBulkAction extends AbstractBulkAction {
static get actionName() {
return "addLabel";
}
static get actionTitle() {
return t("add_label.add_label");
}
doRender() {
const $action = $(TPL);
const $labelName = $action.find(".label-name");
$labelName.val(this.actionDef.labelName || "");
const $labelValue = $action.find(".label-value");
$labelValue.val(this.actionDef.labelValue || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
labelName: $labelName.val(),
labelValue: $labelValue.val()
});
}, 1000);
$labelName.on("input", () => spacedUpdate.scheduleUpdate());
$labelValue.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,57 @@
import { useEffect, useState } from "preact/hooks";
import { t } from "../../../services/i18n";
import FormTextBox from "../../react/FormTextBox";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action";
import BulkAction, { BulkActionText } from "../BulkAction";
import { useSpacedUpdate } from "../../react/hooks";
function AddLabelBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
const [ labelName, setLabelName ] = useState<string>(actionDef.labelName ?? "");
const [ labelValue, setLabelValue ] = useState<string>(actionDef.labelValue ?? "");
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ labelName, labelValue }));
useEffect(() => spacedUpdate.scheduleUpdate(), [labelName, labelValue]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("add_label.add_label")}
helpText={<>
<p>{t("add_label.help_text")}</p>
<ul>
<li>{t("add_label.help_text_item1")}</li>
<li>{t("add_label.help_text_item2")}</li>
</ul>
{t("add_label.help_text_note")}
</>}
>
<FormTextBox
placeholder={t("add_label.label_name_placeholder")}
pattern="[\\p{L}\\p{N}_:]+"
title={t("add_label.label_name_title")}
currentValue={labelName} onChange={setLabelName}
/>
<BulkActionText text={t("add_label.to_value")} />
<FormTextBox
placeholder={t("add_label.new_value_placeholder")}
currentValue={labelValue} onChange={setLabelValue}
/>
</BulkAction>
)
}
export default class AddLabelBulkAction extends AbstractBulkAction {
doRender() {
return <AddLabelBulkActionComponent bulkAction={this} actionDef={this.actionDef} />;
}
static get actionName() {
return "addLabel";
}
static get actionTitle() {
return t("add_label.add_label");
}
}

View File

@@ -1,43 +0,0 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = /*html*/`
<tr>
<td>
${t("delete_label.delete_label")}
</td>
<td>
<input type="text"
class="form-control label-name"
pattern="[\\p{L}\\p{N}_:]+"
title="${t("delete_label.label_name_title")}"
placeholder="${t("delete_label.label_name_placeholder")}"/>
</td>
<td class="button-column">
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class DeleteLabelBulkAction extends AbstractBulkAction {
static get actionName() {
return "deleteLabel";
}
static get actionTitle() {
return t("delete_label.delete_label");
}
doRender() {
const $action = $(TPL);
const $labelName = $action.find(".label-name");
$labelName.val(this.actionDef.labelName || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({ labelName: $labelName.val() });
}, 1000);
$labelName.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,39 @@
import { useEffect, useState } from "preact/hooks";
import { t } from "../../../services/i18n.js";
import FormTextBox from "../../react/FormTextBox.jsx";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import { useSpacedUpdate } from "../../react/hooks.jsx";
import BulkAction from "../BulkAction.jsx";
function DeleteLabelBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition}) {
const [ labelName, setLabelName ] = useState<string>(actionDef.labelName ?? "");
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ labelName }));
useEffect(() => spacedUpdate.scheduleUpdate(), [labelName]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("delete_label.delete_label")}
>
<FormTextBox
pattern="[\\p{L}\\p{N}_:]+"
title={t("delete_label.label_name_title")}
placeholder={t("delete_label.label_name_placeholder")}
currentValue={labelName} onChange={setLabelName}
/>
</BulkAction>
);
}
export default class DeleteLabelBulkAction extends AbstractBulkAction {
static get actionName() {
return "deleteLabel";
}
static get actionTitle() {
return t("delete_label.delete_label");
}
doRender() {
return <DeleteLabelBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,60 +0,0 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px; flex-shrink: 0;">${t("rename_label.rename_label_from")}</div>
<input type="text"
class="form-control old-label-name"
placeholder="${t("rename_label.old_name_placeholder")}"
pattern="[\\p{L}\\p{N}_:]+"
title="${t("rename_label.name_title")}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("rename_label.to")}</div>
<input type="text"
class="form-control new-label-name"
placeholder="${t("rename_label.new_name_placeholder")}"
pattern="[\\p{L}\\p{N}_:]+"
title="${t("rename_label.name_title")}"/>
</div>
</td>
<td class="button-column">
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class RenameLabelBulkAction extends AbstractBulkAction {
static get actionName() {
return "renameLabel";
}
static get actionTitle() {
return t("rename_label.rename_label");
}
doRender() {
const $action = $(TPL);
const $oldLabelName = $action.find(".old-label-name");
$oldLabelName.val(this.actionDef.oldLabelName || "");
const $newLabelName = $action.find(".new-label-name");
$newLabelName.val(this.actionDef.newLabelName || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
oldLabelName: $oldLabelName.val(),
newLabelName: $newLabelName.val()
});
}, 1000);
$oldLabelName.on("input", () => spacedUpdate.scheduleUpdate());
$newLabelName.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,49 @@
import { useEffect, useState } from "preact/hooks";
import { t } from "../../../services/i18n.js";
import FormTextBox from "../../react/FormTextBox.jsx";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
import { useSpacedUpdate } from "../../react/hooks.jsx";
function RenameLabelBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition}) {
const [ oldLabelName, setOldLabelName ] = useState(actionDef.oldLabelName);
const [ newLabelName, setNewLabelName ] = useState(actionDef.newLabelName);
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ oldLabelName, newLabelName }));
useEffect(() => spacedUpdate.scheduleUpdate(), [ oldLabelName, newLabelName ]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("rename_label.rename_label_from")}
>
<FormTextBox
placeholder={t("rename_label.old_name_placeholder")}
pattern="[\\p{L}\\p{N}_:]+"
title={t("rename_label.name_title")}
currentValue={oldLabelName} onChange={setOldLabelName}
/>
<BulkActionText text={t("rename_label.to")} />
<FormTextBox
placeholder={t("rename_label.new_name_placeholder")}
pattern="[\\p{L}\\p{N}_:]+"
title={t("rename_label.name_title")}
currentValue={newLabelName} onChange={setNewLabelName}
/>
</BulkAction>
)
}
export default class RenameLabelBulkAction extends AbstractBulkAction {
static get actionName() {
return "renameLabel";
}
static get actionTitle() {
return t("rename_label.rename_label");
}
doRender() {
return <RenameLabelBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,65 +0,0 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">${t("update_label_value.update_label_value")}</div>
<input type="text"
class="form-control label-name"
placeholder="${t("update_label_value.label_name_placeholder")}"
pattern="[\\p{L}\\p{N}_:]+"
title="${t("update_label_value.label_name_title")}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("update_label_value.to_value")}</div>
<input type="text" class="form-control label-value" placeholder="${t("update_label_value.new_value_placeholder")}"/>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("update_label_value.help_text")}</p>
${t("update_label_value.help_text_note")}
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class UpdateLabelValueBulkAction extends AbstractBulkAction {
static get actionName() {
return "updateLabelValue";
}
static get actionTitle() {
return t("update_label_value.update_label_value");
}
doRender() {
const $action = $(TPL);
const $labelName = $action.find(".label-name");
$labelName.val(this.actionDef.labelName || "");
const $labelValue = $action.find(".label-value");
$labelValue.val(this.actionDef.labelValue || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
labelName: $labelName.val(),
labelValue: $labelValue.val()
});
}, 1000);
$labelName.on("input", () => spacedUpdate.scheduleUpdate());
$labelValue.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,50 @@
import { t } from "../../../services/i18n.js";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import FormTextBox from "../../react/FormTextBox.jsx";
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
import { useSpacedUpdate } from "../../react/hooks.jsx";
import { useEffect, useState } from "preact/hooks";
function UpdateLabelValueComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition}) {
const [ labelName, setLabelName ] = useState<string>(actionDef.labelName ?? "");
const [ labelValue, setLabelValue ] = useState<string>(actionDef.labelValue ?? "");
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ labelName, labelValue }));
useEffect(() => spacedUpdate.scheduleUpdate(), [labelName, labelValue]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("update_label_value.update_label_value")}
helpText={<>
<p>{t("update_label_value.help_text")}</p>
{t("update_label_value.help_text_note")}
</>}
>
<FormTextBox
placeholder={t("update_label_value.label_name_placeholder")}
pattern="[\\p{L}\\p{N}_:]+"
title={t("update_label_value.label_name_title")}
currentValue={labelName} onChange={setLabelName}
/>
<BulkActionText text={t("update_label_value.to_value")} />
<FormTextBox
placeholder={t("update_label_value.new_value_placeholder")}
currentValue={labelValue} onChange={setLabelValue}
/>
</BulkAction>
)
}
export default class UpdateLabelValueBulkAction extends AbstractBulkAction {
static get actionName() {
return "updateLabelValue";
}
static get actionTitle() {
return t("update_label_value.update_label_value");
}
doRender() {
return <UpdateLabelValueComponent bulkAction={this} actionDef={this.actionDef} />;
}
}

View File

@@ -1,38 +0,0 @@
import { t } from "../../../services/i18n.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<span class="bx bx-trash"></span>
${t("delete_note.delete_matched_notes")}
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("delete_note.delete_matched_notes_description")}</p>
<p>${t("delete_note.undelete_notes_instruction")}</p>
${t("delete_note.erase_notes_instruction")}
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class DeleteNoteBulkAction extends AbstractBulkAction {
static get actionName() {
return "deleteNote";
}
static get actionTitle() {
return t("delete_note.delete_note");
}
doRender() {
return $(TPL);
}
}

View File

@@ -0,0 +1,33 @@
import { t } from "../../../services/i18n.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import BulkAction from "../BulkAction.jsx";
import Icon from "../../react/Icon.jsx";
function DeleteNoteBulkActionComponent({ bulkAction }: { bulkAction: AbstractBulkAction }) {
return (
<BulkAction
bulkAction={bulkAction}
label={<><Icon icon="bx bx-trash" /> {t("delete_note.delete_matched_notes")}</>}
helpText={<>
<p>{t("delete_note.delete_matched_notes_description")}</p>
<p>{t("delete_note.undelete_notes_instruction")}</p>
{t("delete_note.erase_notes_instruction")}
</>}
/>
);
}
export default class DeleteNoteBulkAction extends AbstractBulkAction {
static get actionName() {
return "deleteNote";
}
static get actionTitle() {
return t("delete_note.delete_note");
}
doRender() {
return <DeleteNoteBulkActionComponent bulkAction={this} />
}
}

View File

@@ -1,32 +0,0 @@
import { t } from "../../../services/i18n.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<span class="bx bx-trash"></span>
${t("delete_revisions.delete_note_revisions")}
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t("delete_revisions.all_past_note_revisions")}
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class DeleteRevisionsBulkAction extends AbstractBulkAction {
static get actionName() {
return "deleteRevisions";
}
static get actionTitle() {
return t("delete_revisions.delete_note_revisions");
}
doRender() {
return $(TPL);
}
}

View File

@@ -0,0 +1,28 @@
import { t } from "../../../services/i18n.js";
import Icon from "../../react/Icon.jsx";
import AbstractBulkAction from "../abstract_bulk_action.js";
import BulkAction from "../BulkAction.jsx";
function DeleteRevisionsBulkActionComponent({ bulkAction }: { bulkAction: AbstractBulkAction }) {
return (
<BulkAction
bulkAction={bulkAction}
label={<><Icon icon="bx bx-trash" /> {t("delete_revisions.delete_note_revisions")}</>}
helpText={t("delete_revisions.all_past_note_revisions")}
/>
)
}
export default class DeleteRevisionsBulkAction extends AbstractBulkAction {
static get actionName() {
return "deleteRevisions";
}
static get actionTitle() {
return t("delete_revisions.delete_note_revisions");
}
doRender() {
return <DeleteRevisionsBulkActionComponent bulkAction={this} />
}
}

View File

@@ -1,64 +0,0 @@
import { t } from "../../../services/i18n.js";
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import noteAutocompleteService from "../../../services/note_autocomplete.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">${t("move_note.move_note")}</div>
<div style="margin-right: 10px;" class="text-nowrap">${t("move_note.to")}</div>
<div class="input-group">
<input type="text" class="form-control target-parent-note" placeholder="${t("move_note.target_parent_note")}"/>
</div>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("move_note.on_all_matched_notes")}:</p>
<ul style="margin-bottom: 0;">
<li>${t("move_note.move_note_new_parent")}</li>
<li>${t("move_note.clone_note_new_parent")}</li>
<li>${t("move_note.nothing_will_happen")}</li>
</ul>
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class MoveNoteBulkAction extends AbstractBulkAction {
static get actionName() {
return "moveNote";
}
static get actionTitle() {
return t("move_note.move_note");
}
doRender() {
const $action = $(TPL);
const $targetParentNote = $action.find(".target-parent-note");
noteAutocompleteService.initNoteAutocomplete($targetParentNote);
$targetParentNote.setNote(this.actionDef.targetParentNoteId);
$targetParentNote.on("autocomplete:closed", () => spacedUpdate.scheduleUpdate());
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
targetParentNoteId: $targetParentNote.getSelectedNoteId()
});
}, 1000);
$targetParentNote.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,50 @@
import { t } from "../../../services/i18n.js";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";
import { useEffect, useState } from "preact/hooks";
import { useSpacedUpdate } from "../../react/hooks.jsx";
function MoveNoteBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
const [ targetParentNoteId, setTargetParentNoteId ] = useState<string>();
const spacedUpdate = useSpacedUpdate(() => {
return bulkAction.saveAction({ targetParentNoteId: targetParentNoteId })
});
useEffect(() => spacedUpdate.scheduleUpdate(), [ targetParentNoteId ]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("move_note.move_note")}
helpText={<>
<p>{t("move_note.on_all_matched_notes")}:</p>
<ul style="margin-bottom: 0;">
<li>{t("move_note.move_note_new_parent")}</li>
<li>{t("move_note.clone_note_new_parent")}</li>
<li>{t("move_note.nothing_will_happen")}</li>
</ul>
</>}
>
<BulkActionText text={t("move_note.to")} />
<NoteAutocomplete
placeholder={t("move_note.target_parent_note")}
noteId={targetParentNoteId} noteIdChanged={setTargetParentNoteId}
/>
</BulkAction>
)
}
export default class MoveNoteBulkAction extends AbstractBulkAction {
static get actionName() {
return "moveNote";
}
static get actionTitle() {
return t("move_note.move_note");
}
doRender() {
return <MoveNoteBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,61 +0,0 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px; flex-shrink: 0;">${t("rename_note.rename_note_title_to")}</div>
<input type="text"
class="form-control new-title"
placeholder="${t("rename_note.new_note_title")}"
title="${t("rename_note.click_help_icon")}"/>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("rename_note.evaluated_as_js_string")}</p>
<ul>
<li>${t("rename_note.example_note")}</li>
<li>${t("rename_note.example_new_title")}</li>
<li>${t("rename_note.example_date_prefix")}</li>
</ul>
${t("rename_note.api_docs")}
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class RenameNoteBulkAction extends AbstractBulkAction {
static get actionName() {
return "renameNote";
}
static get actionTitle() {
return t("rename_note.rename_note");
}
doRender() {
const $action = $(TPL);
const $newTitle = $action.find(".new-title");
$newTitle.val(this.actionDef.newTitle || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
newTitle: $newTitle.val()
});
}, 1000);
$newTitle.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,53 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
import BulkAction from "../BulkAction.jsx";
import FormTextBox from "../../react/FormTextBox.jsx";
import { useEffect, useState } from "preact/hooks";
import { useSpacedUpdate } from "../../react/hooks.jsx";
import RawHtml from "../../react/RawHtml.jsx";
function RenameNoteBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition}) {
const [ newTitle, setNewTitle ] = useState<string>(actionDef.newTitle ?? "");
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ newTitle }));
useEffect(() => spacedUpdate.scheduleUpdate(), [ newTitle ]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("rename_note.rename_note_title_to")}
helpText={<>
<p>{t("rename_note.evaluated_as_js_string")}</p>
<ul>
<li><RawHtml html={t("rename_note.example_note")} /></li>
<li><RawHtml html={t("rename_note.example_new_title")} /></li>
<li><RawHtml html={t("rename_note.example_date_prefix")} /></li>
</ul>
<RawHtml html={t("rename_note.api_docs")} />
</>}
>
<FormTextBox
placeholder={t("rename_note.new_note_title")}
title={("rename_note.click_help_icon")}
currentValue={newTitle} onChange={setNewTitle}
/>
</BulkAction>
)
}
export default class RenameNoteBulkAction extends AbstractBulkAction {
static get actionName() {
return "renameNote";
}
static get actionTitle() {
return t("rename_note.rename_note");
}
doRender() {
return <RenameNoteBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,70 +0,0 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import noteAutocompleteService from "../../../services/note_autocomplete.js";
import { t } from "../../../services/i18n.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">${t("add_relation.add_relation")}</div>
<input type="text"
class="form-control relation-name"
placeholder="${t("add_relation.relation_name")}"
pattern="[\\p{L}\\p{N}_:]+"
style="flex-shrink: 3"
title="${t("add_relation.allowed_characters")}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("add_relation.to")}</div>
<div class="input-group" style="flex-shrink: 2">
<input type="text" class="form-control target-note" placeholder="${t("add_relation.target_note")}"/>
</div>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t("add_relation.create_relation_on_all_matched_notes")}
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class AddRelationBulkAction extends AbstractBulkAction {
static get actionName() {
return "addRelation";
}
static get actionTitle() {
return t("add_relation.add_relation");
}
doRender() {
const $action = $(TPL);
const $relationName = $action.find(".relation-name");
$relationName.val(this.actionDef.relationName || "");
const $targetNote = $action.find(".target-note");
noteAutocompleteService.initNoteAutocomplete($targetNote);
$targetNote.setNote(this.actionDef.targetNoteId);
$targetNote.on("autocomplete:closed", () => spacedUpdate.scheduleUpdate());
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
relationName: $relationName.val(),
targetNoteId: $targetNote.getSelectedNoteId()
});
}, 1000);
$relationName.on("input", () => spacedUpdate.scheduleUpdate());
$targetNote.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,53 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import noteAutocompleteService from "../../../services/note_autocomplete.js";
import { t } from "../../../services/i18n.js";
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";
import FormTextBox from "../../react/FormTextBox.jsx";
import { useEffect, useState } from "preact/hooks";
import { useSpacedUpdate } from "../../react/hooks.jsx";
function AddRelationBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
const [ relationName, setRelationName ] = useState<string>(actionDef.relationName);
const [ targetNoteId, setTargetNoteId ] = useState<string>(actionDef.targetNoteId);
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ relationName, targetNoteId }));
useEffect(() => spacedUpdate.scheduleUpdate(), [ relationName, targetNoteId ]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("add_relation.add_relation")}
helpText={t("add_relation.create_relation_on_all_matched_notes")}
>
<FormTextBox
placeholder={t("add_relation.relation_name")}
pattern="[\\p{L}\\p{N}_:]+"
style={{ flexShrink: 3 }}
title={t("add_relation.allowed_characters")}
currentValue={relationName} onChange={setRelationName}
/>
<BulkActionText text={t("add_relation.to")} />
<NoteAutocomplete
placeholder={t("add_relation.target_note")}
noteId={targetNoteId} noteIdChanged={setTargetNoteId}
/>
</BulkAction>
)
}
export default class AddRelationBulkAction extends AbstractBulkAction {
static get actionName() {
return "addRelation";
}
static get actionTitle() {
return t("add_relation.add_relation");
}
doRender() {
return <AddRelationBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,45 +0,0 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
const TPL = /*html*/`
<tr>
<td>
${t("delete_relation.delete_relation")}
</td>
<td>
<div style="display: flex; align-items: center">
<input type="text"
class="form-control relation-name"
pattern="[\\p{L}\\p{N}_:]+"
placeholder="${t("delete_relation.relation_name")}"
title="${t("delete_relation.allowed_characters")}"/>
</div>
</td>
<td class="button-column">
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class DeleteRelationBulkAction extends AbstractBulkAction {
static get actionName() {
return "deleteRelation";
}
static get actionTitle() {
return t("delete_relation.delete_relation");
}
doRender() {
const $action = $(TPL);
const $relationName = $action.find(".relation-name");
$relationName.val(this.actionDef.relationName || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({ relationName: $relationName.val() });
}, 1000);
$relationName.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,41 @@
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
import BulkAction from "../BulkAction.jsx";
import FormTextBox from "../../react/FormTextBox.jsx";
import { useEffect, useState } from "preact/hooks";
import { useSpacedUpdate } from "../../react/hooks.jsx";
function DeleteRelationBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
const [ relationName, setRelationName ] = useState(actionDef.relationName);
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ relationName }));
useEffect(() => spacedUpdate.scheduleUpdate(), [ relationName ]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("delete_relation.delete_relation")}
>
<FormTextBox
pattern="[\\p{L}\\p{N}_:]+"
placeholder={t("delete_relation.relation_name")}
title={t("delete_relation.allowed_characters")}
currentValue={relationName} onChange={setRelationName}
/>
</BulkAction>
)
}
export default class DeleteRelationBulkAction extends AbstractBulkAction {
static get actionName() {
return "deleteRelation";
}
static get actionTitle() {
return t("delete_relation.delete_relation");
}
doRender() {
return <DeleteRelationBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,60 +0,0 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px; flex-shrink: 0;">${t("rename_relation.rename_relation_from")}</div>
<input type="text"
class="form-control old-relation-name"
placeholder="${t("rename_relation.old_name")}"
pattern="[\\p{L}\\p{N}_:]+"
title="${t("rename_relation.allowed_characters")}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("rename_relation.to")}</div>
<input type="text"
class="form-control new-relation-name"
placeholder="${t("rename_relation.new_name")}"
pattern="[\\p{L}\\p{N}_:]+"
title="${t("rename_relation.allowed_characters")}"/>
</div>
</td>
<td class="button-column">
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class RenameRelationBulkAction extends AbstractBulkAction {
static get actionName() {
return "renameRelation";
}
static get actionTitle() {
return t("rename_relation.rename_relation");
}
doRender() {
const $action = $(TPL);
const $oldRelationName = $action.find(".old-relation-name");
$oldRelationName.val(this.actionDef.oldRelationName || "");
const $newRelationName = $action.find(".new-relation-name");
$newRelationName.val(this.actionDef.newRelationName || "");
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
oldRelationName: $oldRelationName.val(),
newRelationName: $newRelationName.val()
});
}, 1000);
$oldRelationName.on("input", () => spacedUpdate.scheduleUpdate());
$newRelationName.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,49 @@
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
import FormTextBox from "../../react/FormTextBox.jsx";
import { useEffect, useState } from "preact/hooks";
import { useSpacedUpdate } from "../../react/hooks.jsx";
function RenameRelationBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
const [ oldRelationName, setOldRelationName ] = useState(actionDef.oldRelationName);
const [ newRelationName, setNewRelationName ] = useState(actionDef.newRelationName);
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ oldRelationName, newRelationName }));
useEffect(() => spacedUpdate.scheduleUpdate(), [ oldRelationName, newRelationName ]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("rename_relation.rename_relation_from")}
>
<FormTextBox
placeholder={t("rename_relation.old_name")}
pattern="[\\p{L}\\p{N}_:]+"
title={t("rename_relation.allowed_characters")}
currentValue={oldRelationName} onChange={setOldRelationName}
/>
<BulkActionText text={t("rename_relation.to")} />
<FormTextBox
placeholder={t("rename_relation.new_name")}
pattern="[\\p{L}\\p{N}_:]+"
title={t("rename_relation.allowed_characters")}
currentValue={newRelationName} onChange={setNewRelationName}
/>
</BulkAction>
)
}
export default class RenameRelationBulkAction extends AbstractBulkAction {
static get actionName() {
return "renameRelation";
}
static get actionTitle() {
return t("rename_relation.rename_relation");
}
doRender() {
return <RenameRelationBulkActionComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,74 +0,0 @@
import SpacedUpdate from "../../../services/spaced_update.js";
import AbstractBulkAction from "../abstract_bulk_action.js";
import noteAutocompleteService from "../../../services/note_autocomplete.js";
import { t } from "../../../services/i18n.js";
const TPL = /*html*/`
<tr>
<td colspan="2">
<div style="display: flex; align-items: center">
<div style="margin-right: 10px;" class="text-nowrap">${t("update_relation_target.update_relation")}</div>
<input type="text"
class="form-control relation-name"
placeholder="${t("update_relation_target.relation_name")}"
pattern="[\\p{L}\\p{N}_:]+"
style="flex-shrink: 3"
title="${t("update_relation_target.allowed_characters")}"/>
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("update_relation_target.to")}</div>
<div class="input-group" style="flex-shrink: 2">
<input type="text" class="form-control target-note" placeholder="${t("update_relation_target.target_note")}"/>
</div>
</div>
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("update_relation_target.on_all_matched_notes")}:</p>
<ul style="margin-bottom: 0;">
<li>${t("update_relation_target.change_target_note")}</li>
</ul>
</div>
</div>
<span class="bx bx-x icon-action action-conf-del"></span>
</td>
</tr>`;
export default class UpdateRelationTargetBulkAction extends AbstractBulkAction {
static get actionName() {
return "updateRelationTarget";
}
static get actionTitle() {
return t("update_relation_target.update_relation_target");
}
doRender() {
const $action = $(TPL);
const $relationName = $action.find(".relation-name");
$relationName.val(this.actionDef.relationName || "");
const $targetNote = $action.find(".target-note");
noteAutocompleteService.initNoteAutocomplete($targetNote);
$targetNote.setNote(this.actionDef.targetNoteId);
$targetNote.on("autocomplete:closed", () => spacedUpdate.scheduleUpdate());
const spacedUpdate = new SpacedUpdate(async () => {
await this.saveAction({
relationName: $relationName.val(),
targetNoteId: $targetNote.getSelectedNoteId()
});
}, 1000);
$relationName.on("input", () => spacedUpdate.scheduleUpdate());
$targetNote.on("input", () => spacedUpdate.scheduleUpdate());
return $action;
}
}

View File

@@ -0,0 +1,60 @@
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action.js";
import { t } from "../../../services/i18n.js";
import BulkAction, { BulkActionText } from "../BulkAction.jsx";
import FormTextBox from "../../react/FormTextBox.jsx";
import NoteAutocomplete from "../../react/NoteAutocomplete.jsx";
import { useEffect, useState } from "preact/hooks";
import { useSpacedUpdate } from "../../react/hooks.jsx";
function UpdateRelationTargetComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
const [ relationName, setRelationName ] = useState(actionDef.relationName);
const [ targetNoteId, setTargetNoteId ] = useState(actionDef.targetNoteId);
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ relationName, targetNoteId }));
useEffect(() => spacedUpdate.scheduleUpdate(), [ relationName, targetNoteId ]);
return (
<BulkAction
bulkAction={bulkAction}
label={t("update_relation_target.update_relation")}
helpText={<>
<p>{t("update_relation_target.on_all_matched_notes")}:</p>
<ul style="margin-bottom: 0;">
<li>{t("update_relation_target.change_target_note")}</li>
</ul>
</>}
>
<FormTextBox
placeholder={t("update_relation_target.relation_name")}
pattern="[\\p{L}\\p{N}_:]+"
style={{ flexShrink: 3 }}
title={t("update_relation_target.allowed_characters")}
currentValue={relationName} onChange={setRelationName}
/>
<BulkActionText text={t("update_relation_target.to")} />
<NoteAutocomplete
placeholder={t("update_relation_target.target_note")}
containerStyle={{ flexShrink: 2 }}
noteId={targetNoteId} noteIdChanged={setTargetNoteId}
/>
</BulkAction>
)
}
export default class UpdateRelationTargetBulkAction extends AbstractBulkAction {
static get actionName() {
return "updateRelationTarget";
}
static get actionTitle() {
return t("update_relation_target.update_relation_target");
}
doRender() {
return <UpdateRelationTargetComponent bulkAction={this} actionDef={this.actionDef} />
}
}

View File

@@ -1,4 +1,3 @@
import { openDialog } from "../../services/dialog.js";
import ReactBasicWidget from "../react/ReactBasicWidget.js"; import ReactBasicWidget from "../react/ReactBasicWidget.js";
import Modal from "../react/Modal.js"; import Modal from "../react/Modal.js";
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
@@ -9,59 +8,64 @@ import openService from "../../services/open.js";
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import type { CSSProperties } from "preact/compat"; import type { CSSProperties } from "preact/compat";
import type { AppInfo } from "@triliumnext/commons"; import type { AppInfo } from "@triliumnext/commons";
import useTriliumEvent from "../react/hooks.jsx";
function AboutDialogComponent() { function AboutDialogComponent() {
let [appInfo, setAppInfo] = useState<AppInfo | null>(null); let [appInfo, setAppInfo] = useState<AppInfo | null>(null);
let [shown, setShown] = useState(false);
async function onShown() {
const appInfo = await server.get<AppInfo>("app-info");
setAppInfo(appInfo);
}
const forceWordBreak: CSSProperties = { wordBreak: "break-all" }; const forceWordBreak: CSSProperties = { wordBreak: "break-all" };
useTriliumEvent("openAboutDialog", () => setShown(true));
return ( return (
<Modal className="about-dialog" size="lg" title={t("about.title")} onShown={onShown}> <Modal className="about-dialog"
{(appInfo !== null) ? ( size="lg"
<table className="table table-borderless"> title={t("about.title")}
<tbody> show={shown}
<tr> onShown={async () => {
<th>{t("about.homepage")}</th> const appInfo = await server.get<AppInfo>("app-info");
<td><a className="tn-link external" href="https://github.com/TriliumNext/Trilium" style={forceWordBreak}>https://github.com/TriliumNext/Trilium</a></td> setAppInfo(appInfo);
</tr> }}
<tr> onHidden={() => setShown(false)}
<th>{t("about.app_version")}</th> >
<td className="app-version">{appInfo.appVersion}</td> <table className="table table-borderless">
</tr> <tbody>
<tr> <tr>
<th>{t("about.db_version")}</th> <th>{t("about.homepage")}</th>
<td className="db-version">{appInfo.dbVersion}</td> <td><a className="tn-link external" href="https://github.com/TriliumNext/Trilium" style={forceWordBreak}>https://github.com/TriliumNext/Trilium</a></td>
</tr> </tr>
<tr> <tr>
<th>{t("about.sync_version")}</th> <th>{t("about.app_version")}</th>
<td className="sync-version">{appInfo.syncVersion}</td> <td className="app-version">{appInfo?.appVersion}</td>
</tr> </tr>
<tr> <tr>
<th>{t("about.build_date")}</th> <th>{t("about.db_version")}</th>
<td className="build-date">{formatDateTime(appInfo.buildDate)}</td> <td className="db-version">{appInfo?.dbVersion}</td>
</tr> </tr>
<tr> <tr>
<th>{t("about.build_revision")}</th> <th>{t("about.sync_version")}</th>
<td> <td className="sync-version">{appInfo?.syncVersion}</td>
<a className="tn-link build-revision external" href={`https://github.com/TriliumNext/Trilium/commit/${appInfo.buildRevision}`} target="_blank" style={forceWordBreak}>{appInfo.buildRevision}</a> </tr>
</td> <tr>
</tr> <th>{t("about.build_date")}</th>
<tr> <td className="build-date">
<th>{t("about.data_directory")}</th> {appInfo?.buildDate ? formatDateTime(appInfo.buildDate) : ""}
<td className="data-directory"> </td>
<DirectoryLink directory={appInfo.dataDirectory} style={forceWordBreak} /> </tr>
</td> <tr>
</tr> <th>{t("about.build_revision")}</th>
</tbody> <td>
</table> {appInfo?.buildRevision && <a className="tn-link build-revision external" href={`https://github.com/TriliumNext/Trilium/commit/${appInfo.buildRevision}`} target="_blank" style={forceWordBreak}>{appInfo.buildRevision}</a>}
) : ( </td>
<div className="loading-spinner"></div> </tr>
)} <tr>
<th>{t("about.data_directory")}</th>
<td className="data-directory">
{appInfo?.dataDirectory && (<DirectoryLink directory={appInfo.dataDirectory} style={forceWordBreak} />)}
</td>
</tr>
</tbody>
</table>
</Modal> </Modal>
); );
} }
@@ -85,7 +89,4 @@ export default class AboutDialog extends ReactBasicWidget {
return <AboutDialogComponent />; return <AboutDialogComponent />;
} }
async openAboutDialogEvent() {
openDialog(this.$widget);
}
} }

View File

@@ -1,188 +0,0 @@
import { t } from "../../services/i18n.js";
import treeService from "../../services/tree.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import BasicWidget from "../basic_widget.js";
import type { Suggestion } from "../../services/note_autocomplete.js";
import type { default as TextTypeWidget } from "../type_widgets/editable_text.js";
import type { EventData } from "../../components/app_context.js";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="add-link-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title flex-grow-1">${t("add_link.add_link")}</h5>
<button type="button" class="help-button" title="${t("add_link.help_on_links")}" data-help-page="links.html">?</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("add_link.close")}"></button>
</div>
<form class="add-link-form">
<div class="modal-body">
<div class="form-group">
<label for="add-link-note-autocomplete">${t("add_link.note")}</label>
<div class="input-group">
<input class="add-link-note-autocomplete form-control" placeholder="${t("add_link.search_note")}">
</div>
</div>
<div class="add-link-title-settings">
<div class="add-link-title-radios form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="link-type" value="reference-link" checked>
${t("add_link.link_title_mirrors")}
</label>
</div>
<div class="add-link-title-radios form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="link-type" value="hyper-link">
${t("add_link.link_title_arbitrary")}
</label>
</div>
<div class="add-link-title-form-group form-group">
<br/>
<label>
${t("add_link.link_title")}
<input class="link-title form-control" style="width: 100%;">
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">${t("add_link.button_add_link")}</button>
</div>
</form>
</div>
</div>
</div>`;
export default class AddLinkDialog extends BasicWidget {
private $form!: JQuery<HTMLElement>;
private $autoComplete!: JQuery<HTMLElement>;
private $linkTitle!: JQuery<HTMLElement>;
private $addLinkTitleSettings!: JQuery<HTMLElement>;
private $addLinkTitleRadios!: JQuery<HTMLElement>;
private $addLinkTitleFormGroup!: JQuery<HTMLElement>;
private textTypeWidget: TextTypeWidget | null = null;
doRender() {
this.$widget = $(TPL);
this.$form = this.$widget.find(".add-link-form");
this.$autoComplete = this.$widget.find(".add-link-note-autocomplete");
this.$linkTitle = this.$widget.find(".link-title");
this.$addLinkTitleSettings = this.$widget.find(".add-link-title-settings");
this.$addLinkTitleRadios = this.$widget.find(".add-link-title-radios");
this.$addLinkTitleFormGroup = this.$widget.find(".add-link-title-form-group");
this.$form.on("submit", () => {
if (this.$autoComplete.getSelectedNotePath()) {
this.$widget.modal("hide");
const linkTitle = this.getLinkType() === "reference-link" ? null : this.$linkTitle.val() as string;
this.textTypeWidget?.addLink(this.$autoComplete.getSelectedNotePath()!, linkTitle);
} else if (this.$autoComplete.getSelectedExternalLink()) {
this.$widget.modal("hide");
this.textTypeWidget?.addLink(this.$autoComplete.getSelectedExternalLink()!, this.$linkTitle.val() as string, true);
} else {
logError("No link to add.");
}
return false;
});
}
async showAddLinkDialogEvent({ textTypeWidget, text = "" }: EventData<"showAddLinkDialog">) {
this.textTypeWidget = textTypeWidget;
this.$addLinkTitleSettings.toggle(!this.textTypeWidget.hasSelection());
this.$addLinkTitleSettings.find("input[type=radio]").on("change", () => this.updateTitleSettingsVisibility());
// with selection hyperlink is implied
if (this.textTypeWidget.hasSelection()) {
this.$addLinkTitleSettings.find("input[value='hyper-link']").prop("checked", true);
} else {
this.$addLinkTitleSettings.find("input[value='reference-link']").prop("checked", true);
}
this.updateTitleSettingsVisibility();
await openDialog(this.$widget);
this.$autoComplete.val("");
this.$linkTitle.val("");
const setDefaultLinkTitle = async (noteId: string) => {
const noteTitle = await treeService.getNoteTitle(noteId);
this.$linkTitle.val(noteTitle);
};
noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, {
allowExternalLinks: true,
allowCreatingNotes: true
});
this.$autoComplete.on("autocomplete:noteselected", (event: JQuery.Event, suggestion: Suggestion) => {
if (!suggestion.notePath) {
return false;
}
this.updateTitleSettingsVisibility();
const noteId = treeService.getNoteIdFromUrl(suggestion.notePath);
if (noteId) {
setDefaultLinkTitle(noteId);
}
});
this.$autoComplete.on("autocomplete:externallinkselected", (event: JQuery.Event, suggestion: Suggestion) => {
if (!suggestion.externalLink) {
return false;
}
this.updateTitleSettingsVisibility();
this.$linkTitle.val(suggestion.externalLink);
});
this.$autoComplete.on("autocomplete:cursorchanged", (event: JQuery.Event, suggestion: Suggestion) => {
if (suggestion.externalLink) {
this.$linkTitle.val(suggestion.externalLink);
} else {
const noteId = treeService.getNoteIdFromUrl(suggestion.notePath!);
if (noteId) {
setDefaultLinkTitle(noteId);
}
}
});
if (text && text.trim()) {
noteAutocompleteService.setText(this.$autoComplete, text);
} else {
noteAutocompleteService.showRecentNotes(this.$autoComplete);
}
this.$autoComplete.trigger("focus").trigger("select"); // to be able to quickly remove entered text
}
private getLinkType() {
if (this.$autoComplete.getSelectedExternalLink()) {
return "external-link";
}
return this.$addLinkTitleSettings.find("input[type=radio]:checked").val();
}
private updateTitleSettingsVisibility() {
const linkType = this.getLinkType();
this.$addLinkTitleFormGroup.toggle(linkType !== "reference-link");
this.$addLinkTitleRadios.toggle(linkType !== "external-link");
}
}

View File

@@ -0,0 +1,162 @@
import { t } from "../../services/i18n";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
import Button from "../react/Button";
import FormRadioGroup from "../react/FormRadioGroup";
import NoteAutocomplete from "../react/NoteAutocomplete";
import { useRef, useState, useEffect } from "preact/hooks";
import tree from "../../services/tree";
import note_autocomplete, { Suggestion } from "../../services/note_autocomplete";
import { default as TextTypeWidget } from "../type_widgets/editable_text.js";
import { logError } from "../../services/ws";
import FormGroup from "../react/FormGroup.js";
import { refToJQuerySelector } from "../react/react_utils";
import useTriliumEvent from "../react/hooks";
type LinkType = "reference-link" | "external-link" | "hyper-link";
function AddLinkDialogComponent() {
const [ textTypeWidget, setTextTypeWidget ] = useState<TextTypeWidget>();
const initialText = useRef<string>();
const [ linkTitle, setLinkTitle ] = useState("");
const hasSelection = textTypeWidget?.hasSelection();
const [ linkType, setLinkType ] = useState<LinkType>(hasSelection ? "hyper-link" : "reference-link");
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
const [ shown, setShown ] = useState(false);
useTriliumEvent("showAddLinkDialog", ( { textTypeWidget, text }) => {
setTextTypeWidget(textTypeWidget);
initialText.current = text;
setShown(true);
});
async function setDefaultLinkTitle(noteId: string) {
const noteTitle = await tree.getNoteTitle(noteId);
setLinkTitle(noteTitle);
}
function resetExternalLink() {
if (linkType === "external-link") {
setLinkType("reference-link");
}
}
useEffect(() => {
if (!suggestion) {
resetExternalLink();
return;
}
if (suggestion.notePath) {
const noteId = tree.getNoteIdFromUrl(suggestion.notePath);
if (noteId) {
setDefaultLinkTitle(noteId);
}
resetExternalLink();
}
if (suggestion.externalLink) {
setLinkTitle(suggestion.externalLink);
setLinkType("external-link");
}
}, [suggestion]);
function onShown() {
const $autocompleteEl = refToJQuerySelector(autocompleteRef);
if (!initialText.current) {
note_autocomplete.showRecentNotes($autocompleteEl);
} else {
note_autocomplete.setText($autocompleteEl, initialText.current);
}
// to be able to quickly remove entered text
$autocompleteEl
.trigger("focus")
.trigger("select");
}
function onSubmit() {
if (suggestion?.notePath) {
// Handle note link
setShown(false);
textTypeWidget?.addLink(suggestion.notePath, linkType === "reference-link" ? null : linkTitle);
} else if (suggestion?.externalLink) {
// Handle external link
setShown(false);
textTypeWidget?.addLink(suggestion.externalLink, linkTitle, true);
} else {
logError("No link to add.");
}
}
const autocompleteRef = useRef<HTMLInputElement>(null);
return (
<Modal
className="add-link-dialog"
size="lg"
maxWidth={1000}
title={t("add_link.add_link")}
helpPageId="QEAPj01N5f7w"
footer={<Button text={t("add_link.button_add_link")} keyboardShortcut="Enter" />}
onSubmit={onSubmit}
onShown={onShown}
onHidden={() => {
setSuggestion(null);
setShown(false);
}}
show={shown}
>
<FormGroup label={t("add_link.note")}>
<NoteAutocomplete
inputRef={autocompleteRef}
onChange={setSuggestion}
opts={{
allowExternalLinks: true,
allowCreatingNotes: true
}}
/>
</FormGroup>
{!hasSelection && (
<div className="add-link-title-settings">
{(linkType !== "external-link") && (
<>
<FormRadioGroup
name="link-type"
currentValue={linkType}
values={[
{ value: "reference-link", label: t("add_link.link_title_mirrors") },
{ value: "hyper-link", label: t("add_link.link_title_arbitrary") }
]}
onChange={(newValue) => setLinkType(newValue as LinkType)}
/>
</>
)}
{(linkType !== "reference-link" && (
<div className="add-link-title-form-group form-group">
<br/>
<label>
{t("add_link.link_title")}
<input className="link-title form-control" style={{ width: "100%" }}
value={linkTitle}
onInput={e => setLinkTitle((e.target as HTMLInputElement)?.value ?? "")}
/>
</label>
</div>
))}
</div>
)}
</Modal>
);
}
export default class AddLinkDialog extends ReactBasicWidget {
get component() {
return <AddLinkDialogComponent />;
}
}

View File

@@ -1,108 +0,0 @@
import treeService from "../../services/tree.js";
import server from "../../services/server.js";
import froca from "../../services/froca.js";
import toastService from "../../services/toast.js";
import BasicWidget from "../basic_widget.js";
import appContext from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
import { Modal } from "bootstrap";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<form class="branch-prefix-form">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title flex-grow-1">${t("branch_prefix.edit_branch_prefix")}</h5>
<button class="help-button" type="button" data-help-page="tree-concepts.html#prefix" title="${t("branch_prefix.help_on_tree_prefix")}">?</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("branch_prefix.close")}"></button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="branch-prefix-input">${t("branch_prefix.prefix")}</label> &nbsp;
<div class="input-group">
<input class="branch-prefix-input form-control">
<div class="branch-prefix-note-title input-group-text"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary btn-sm">${t("branch_prefix.save")}</button>
</div>
</div>
</form>
</div>
</div>`;
export default class BranchPrefixDialog extends BasicWidget {
private modal!: Modal;
private $form!: JQuery<HTMLElement>;
private $treePrefixInput!: JQuery<HTMLElement>;
private $noteTitle!: JQuery<HTMLElement>;
private branchId: string | null = null;
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$form = this.$widget.find(".branch-prefix-form");
this.$treePrefixInput = this.$widget.find(".branch-prefix-input");
this.$noteTitle = this.$widget.find(".branch-prefix-note-title");
this.$form.on("submit", () => {
this.savePrefix();
return false;
});
this.$widget.on("shown.bs.modal", () => this.$treePrefixInput.trigger("focus"));
}
async refresh(notePath: string) {
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) {
return;
}
const newBranchId = await froca.getBranchId(parentNoteId, noteId);
if (!newBranchId) {
return;
}
this.branchId = newBranchId;
const branch = froca.getBranch(this.branchId);
if (!branch || branch.noteId === "root") {
return;
}
const parentNote = await froca.getNote(branch.parentNoteId);
if (!parentNote || parentNote.type === "search") {
return;
}
this.$treePrefixInput.val(branch.prefix || "");
const noteTitle = await treeService.getNoteTitle(noteId);
this.$noteTitle.text(` - ${noteTitle}`);
}
async editBranchPrefixEvent() {
const notePath = appContext.tabManager.getActiveContextNotePath();
if (!notePath) {
return;
}
await this.refresh(notePath);
openDialog(this.$widget);
}
async savePrefix() {
const prefix = this.$treePrefixInput.val();
await server.put(`branches/${this.branchId}/set-prefix`, { prefix: prefix });
this.modal.hide();
toastService.showMessage(t("branch_prefix.branch_prefix_saved"));
}
}

View File

@@ -0,0 +1,89 @@
import { useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
import server from "../../services/server.js";
import toast from "../../services/toast.js";
import Modal from "../react/Modal.jsx";
import ReactBasicWidget from "../react/ReactBasicWidget.js";
import froca from "../../services/froca.js";
import tree from "../../services/tree.js";
import Button from "../react/Button.jsx";
import FormGroup from "../react/FormGroup.js";
import useTriliumEvent from "../react/hooks.jsx";
import FBranch from "../../entities/fbranch.js";
function BranchPrefixDialogComponent() {
const [ shown, setShown ] = useState(false);
const [ branch, setBranch ] = useState<FBranch>();
const [ prefix, setPrefix ] = useState(branch?.prefix ?? "");
const branchInput = useRef<HTMLInputElement>(null);
useTriliumEvent("editBranchPrefix", async () => {
const notePath = appContext.tabManager.getActiveContextNotePath();
if (!notePath) {
return;
}
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) {
return;
}
const newBranchId = await froca.getBranchId(parentNoteId, noteId);
if (!newBranchId) {
return;
}
const parentNote = await froca.getNote(parentNoteId);
if (!parentNote || parentNote.type === "search") {
return;
}
setBranch(froca.getBranch(newBranchId));
setShown(true);
});
async function onSubmit() {
if (!branch) {
return;
}
savePrefix(branch.branchId, prefix);
setShown(false);
}
return (
<Modal
className="branch-prefix-dialog"
title={t("branch_prefix.edit_branch_prefix")}
size="lg"
onShown={() => branchInput.current?.focus()}
onHidden={() => setShown(false)}
onSubmit={onSubmit}
helpPageId="TBwsyfadTA18"
footer={<Button text={t("branch_prefix.save")} />}
show={shown}
>
<FormGroup label={t("branch_prefix.prefix")}>
<div class="input-group">
<input class="branch-prefix-input form-control" value={prefix} ref={branchInput}
onChange={(e) => setPrefix((e.target as HTMLInputElement).value)} />
<div class="branch-prefix-note-title input-group-text"> - {branch && branch.getNoteFromCache().title}</div>
</div>
</FormGroup>
</Modal>
);
}
export default class BranchPrefixDialog extends ReactBasicWidget {
get component() {
return <BranchPrefixDialogComponent />;
}
}
async function savePrefix(branchId: string, prefix: string) {
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
toast.showMessage(t("branch_prefix.branch_prefix_saved"));
}

View File

@@ -0,0 +1,24 @@
.bulk-actions-dialog .modal-body h4:not(:first-child) {
margin-top: 20px;
}
.bulk-actions-dialog .bulk-available-action-list button {
padding: 2px 7px;
margin-right: 10px;
margin-bottom: 5px;
}
.bulk-actions-dialog .bulk-existing-action-list {
width: 100%;
}
.bulk-actions-dialog .bulk-existing-action-list td {
padding: 7px;
}
.bulk-actions-dialog .bulk-existing-action-list .button-column {
/* minimal width so that table remains static sized and most space remains for middle column with settings */
width: 50px;
white-space: nowrap;
text-align: right;
}

View File

@@ -1,175 +0,0 @@
import BasicWidget from "../basic_widget.js";
import froca from "../../services/froca.js";
import bulkActionService from "../../services/bulk_action.js";
import server from "../../services/server.js";
import toastService from "../../services/toast.js";
import { t } from "../../services/i18n.js";
import type { EventData } from "../../components/app_context.js";
import { closeActiveDialog, openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="bulk-actions-dialog modal mx-auto" tabindex="-1" role="dialog">
<style>
.bulk-actions-dialog .modal-body h4:not(:first-child) {
margin-top: 20px;
}
.bulk-actions-dialog .bulk-available-action-list button {
padding: 2px 7px;
margin-right: 10px;
margin-bottom: 5px;
}
.bulk-actions-dialog .bulk-existing-action-list {
width: 100%;
}
.bulk-actions-dialog .bulk-existing-action-list td {
padding: 7px;
}
.bulk-actions-dialog .bulk-existing-action-list .button-column {
/* minimal width so that table remains static sized and most space remains for middle column with settings */
width: 50px;
white-space: nowrap;
text-align: right;
}
</style>
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("bulk_actions.bulk_actions")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("bulk_actions.close")}"></button>
</div>
<div class="modal-body">
<h4>${t("bulk_actions.affected_notes")}: <span class="affected-note-count">0</span></h4>
<div class="form-check">
<label for="include-descendants" class="form-check-label tn-checkbox">
<input id="include-descendants" class="include-descendants form-check-input" type="checkbox" value="">
${t("bulk_actions.include_descendants")}
</label>
</div>
<h4>${t("bulk_actions.available_actions")}</h4>
<table class="bulk-available-action-list"></table>
<h4>${t("bulk_actions.chosen_actions")}</h4>
<table class="bulk-existing-action-list"></table>
</div>
<div class="modal-footer">
<button type="submit" class="execute-bulk-actions btn btn-primary">${t("bulk_actions.execute_bulk_actions")}</button>
</div>
</div>
</div>
</div>`;
export default class BulkActionsDialog extends BasicWidget {
private $includeDescendants!: JQuery<HTMLElement>;
private $affectedNoteCount!: JQuery<HTMLElement>;
private $availableActionList!: JQuery<HTMLElement>;
private $existingActionList!: JQuery<HTMLElement>;
private $executeButton!: JQuery<HTMLElement>;
private selectedOrActiveNoteIds: string[] | null = null;
doRender() {
this.$widget = $(TPL);
this.$includeDescendants = this.$widget.find(".include-descendants");
this.$includeDescendants.on("change", () => this.refresh());
this.$affectedNoteCount = this.$widget.find(".affected-note-count");
this.$availableActionList = this.$widget.find(".bulk-available-action-list");
this.$existingActionList = this.$widget.find(".bulk-existing-action-list");
this.$widget.on("click", "[data-action-add]", async (event) => {
const actionName = $(event.target).attr("data-action-add");
if (!actionName) {
return;
}
await bulkActionService.addAction("_bulkAction", actionName);
await this.refresh();
});
this.$executeButton = this.$widget.find(".execute-bulk-actions");
this.$executeButton.on("click", async () => {
await server.post("bulk-action/execute", {
noteIds: this.selectedOrActiveNoteIds,
includeDescendants: this.$includeDescendants.is(":checked")
});
toastService.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
closeActiveDialog();
});
}
async refresh() {
this.renderAvailableActions();
if (!this.selectedOrActiveNoteIds) {
return;
}
const { affectedNoteCount } = await server.post("bulk-action/affected-notes", {
noteIds: this.selectedOrActiveNoteIds,
includeDescendants: this.$includeDescendants.is(":checked")
}) as { affectedNoteCount: number };
this.$affectedNoteCount.text(affectedNoteCount);
const bulkActionNote = await froca.getNote("_bulkAction");
if (!bulkActionNote) {
return;
}
const actions = bulkActionService.parseActions(bulkActionNote);
this.$existingActionList.empty();
if (actions.length > 0) {
this.$existingActionList.append(...actions.map((action) => action.render()).filter((action) => action !== null));
} else {
this.$existingActionList.append($("<p>").text(t("bulk_actions.none_yet")));
}
}
renderAvailableActions() {
this.$availableActionList.empty();
for (const actionGroup of bulkActionService.ACTION_GROUPS) {
const $actionGroupList = $("<td>");
const $actionGroup = $("<tr>")
.append($("<td>").text(`${actionGroup.title}: `))
.append($actionGroupList);
for (const action of actionGroup.actions) {
$actionGroupList.append($('<button class="btn btn-sm">').attr("data-action-add", action.actionName).text(action.actionTitle));
}
this.$availableActionList.append($actionGroup);
}
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
// only refreshing deleted attrs, otherwise components update themselves
if (loadResults.getAttributeRows().find((row) => row.type === "label" && row.name === "action" && row.noteId === "_bulkAction" && row.isDeleted)) {
// this may be triggered from e.g., sync without open widget, then no need to refresh the widget
if (this.selectedOrActiveNoteIds && this.$widget.is(":visible")) {
this.refresh();
}
}
}
async openBulkActionsDialogEvent({ selectedOrActiveNoteIds }: EventData<"openBulkActionsDialog">) {
this.selectedOrActiveNoteIds = selectedOrActiveNoteIds;
this.$includeDescendants.prop("checked", false);
await this.refresh();
openDialog(this.$widget);
}
}

View File

@@ -0,0 +1,126 @@
import { useEffect, useState, useCallback } from "preact/hooks";
import { t } from "../../services/i18n";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
import "./bulk_actions.css";
import { BulkActionAffectedNotes } from "@triliumnext/commons";
import server from "../../services/server";
import FormCheckbox from "../react/FormCheckbox";
import Button from "../react/Button";
import bulk_action from "../../services/bulk_action";
import toast from "../../services/toast";
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
import FNote from "../../entities/fnote";
import froca from "../../services/froca";
import useTriliumEvent from "../react/hooks";
function BulkActionComponent() {
const [ selectedOrActiveNoteIds, setSelectedOrActiveNoteIds ] = useState<string[]>();
const [ bulkActionNote, setBulkActionNote ] = useState<FNote | null>();
const [ includeDescendants, setIncludeDescendants ] = useState(false);
const [ affectedNoteCount, setAffectedNoteCount ] = useState(0);
const [ existingActions, setExistingActions ] = useState<RenameNoteBulkAction[]>([]);
const [ shown, setShown ] = useState(false);
useTriliumEvent("openBulkActionsDialog", async ({ selectedOrActiveNoteIds }) => {
setSelectedOrActiveNoteIds(selectedOrActiveNoteIds);
setBulkActionNote(await froca.getNote("_bulkAction"));
setShown(true);
});
useEffect(() => {
if (!selectedOrActiveNoteIds || !bulkActionNote) return;
server.post<BulkActionAffectedNotes>("bulk-action/affected-notes", {
noteIds: selectedOrActiveNoteIds,
includeDescendants
}).then(({ affectedNoteCount }) => setAffectedNoteCount(affectedNoteCount));
}, [ selectedOrActiveNoteIds, includeDescendants, bulkActionNote ]);
const refreshExistingActions = useCallback(() => {
if (!bulkActionNote) return;
setExistingActions(bulk_action.parseActions(bulkActionNote));
}, [bulkActionNote]);
useEffect(() => {
refreshExistingActions();
}, [refreshExistingActions]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().find((row) =>
row.type === "label" && row.name === "action" && row.noteId === "_bulkAction")) {
refreshExistingActions();
}
}, shown);
return (
<Modal
className="bulk-actions-dialog"
size="xl"
title={t("bulk_actions.bulk_actions")}
footer={<Button text={t("bulk_actions.execute_bulk_actions")} primary />}
show={shown}
onSubmit={async () => {
await server.post("bulk-action/execute", {
noteIds: selectedOrActiveNoteIds,
includeDescendants
});
toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
setShown(false);
}}
onHidden={() => setShown(false)}
>
<h4>{t("bulk_actions.affected_notes")}: <span>{affectedNoteCount}</span></h4>
<FormCheckbox
name="include-descendants" label={t("bulk_actions.include_descendants")}
currentValue={includeDescendants} onChange={setIncludeDescendants}
/>
<h4>{t("bulk_actions.available_actions")}</h4>
<AvailableActionsList />
<h4>{t("bulk_actions.chosen_actions")}</h4>
<ExistingActionsList existingActions={existingActions} />
</Modal>
)
}
function AvailableActionsList() {
return <table class="bulk-available-action-list">
{bulk_action.ACTION_GROUPS.map((actionGroup) => {
return (
<tr>
<td>{ actionGroup.title }:</td>
{actionGroup.actions.map(({ actionName, actionTitle }) =>
<Button
small text={actionTitle}
onClick={() => bulk_action.addAction("_bulkAction", actionName)}
/>
)}
</tr>
);
})}
</table>;
}
function ExistingActionsList({ existingActions }: { existingActions?: RenameNoteBulkAction[] }) {
return (
<table class="bulk-existing-action-list">
{ existingActions
? existingActions
.map(action => action.doRender())
.filter(renderedAction => renderedAction !== null)
: <p>{t("bulk_actions.none_yet")}</p>
}
</table>
);
}
export default class BulkActionsDialog extends ReactBasicWidget {
get component() {
return <BulkActionComponent />
}
}

View File

@@ -0,0 +1,58 @@
import { useState } from "preact/hooks";
import Button from "../react/Button";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
import { CallToAction, dismissCallToAction, getCallToActions } from "./call_to_action_definitions";
function CallToActionDialogComponent({ activeCallToActions }: { activeCallToActions: CallToAction[] }) {
if (!activeCallToActions.length) {
return <></>;
}
const [ activeIndex, setActiveIndex ] = useState(0);
const [ shown, setShown ] = useState(true);
const activeItem = activeCallToActions[activeIndex];
function goToNext() {
if (activeIndex + 1 < activeCallToActions.length) {
setActiveIndex(activeIndex + 1);
} else {
setShown(false);
}
}
return (
<Modal
className="call-to-action"
size="md"
title="New features"
show={shown}
onHidden={() => setShown(false)}
footerAlignment="between"
footer={<>
<Button text="Dismiss" onClick={async () => {
await dismissCallToAction(activeItem.id);
goToNext();
}} />
{activeItem.buttons.map((button) =>
<Button text={button.text} onClick={async () => {
await dismissCallToAction(activeItem.id);
await button.onClick();
goToNext();
}}/>
)}
</>}
>
<h4>{activeItem.title}</h4>
<p>{activeItem.message}</p>
</Modal>
)
}
export class CallToActionDialog extends ReactBasicWidget {
get component() {
return <CallToActionDialogComponent activeCallToActions={getCallToActions()} />
}
}

View File

@@ -0,0 +1,116 @@
import utils from "../../services/utils";
import options from "../../services/options";
import { t } from "../../services/i18n";
/**
* A "call-to-action" is an interactive message for the user, generally to present new features.
*/
export interface CallToAction {
/**
* A unique identifier to allow the call-to-action to be dismissed by the user and then never shown again.
*/
id: string;
/**
* The title of the call-to-action, displayed as a heading in the message.
*/
title: string;
/**
* The message body of the call-to-action.
*/
message: string;
/**
* Function that determines whether the call-to-action can be displayed to the user. The check can be based on options or
* the platform of the user. If the user already dismissed this call-to-action, the value of this function doesn't matter.
*
* @returns whether to allow this call-to-action or to skip it, based on the user's environment.
*/
enabled: () => boolean;
/**
* A list of buttons to display in the footer of the modal.
*/
buttons: {
/**
* The text displayed on the button.
*/
text: string;
/**
* The listener that will be called when the button is pressed. Async methods are supported and will be awaited before proceeding to the next step.
*/
onClick: () => (void | Promise<void>);
}[];
}
function isNextTheme() {
return [ "next", "next-light", "next-dark" ].includes(options.get("theme"));
}
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: () => !isNextTheme(),
buttons: [
{
text: t("call_to_action.next_theme_button"),
async onClick() {
await options.save("theme", "next");
await options.save("backgroundEffects", "true");
utils.reloadFrontendApp("call-to-action");
}
}
]
},
{
id: "background_effects",
title: t("call_to_action.background_effects_title"),
message: t("call_to_action.background_effects_message"),
enabled: () => utils.isElectron() && window.glob.platform === "win32" && isNextTheme() && !options.is("backgroundEffects"),
buttons: [
{
text: t("call_to_action.background_effects_button"),
async onClick() {
await options.save("backgroundEffects", "true");
utils.restartDesktopApp();
}
}
]
}
];
/**
* Obtains the list of available call-to-actions, meaning those that are enabled based on the user's environment but also with
* without the call-to-actions already dismissed by the user.
*
* @returns a list iof call to actions to display to the user.
*/
export function getCallToActions() {
const seenCallToActions = new Set(getSeenCallToActions());
return CALL_TO_ACTIONS.filter((callToAction) =>
!seenCallToActions.has(callToAction.id) && callToAction.enabled());
}
/**
* Marks the call-to-action as dismissed by the user, meaning that {@link getCallToActions()} will no longer list this particular action.
*
* @param id the corresponding ID of the call to action.
* @returns a promise with the option saving. Generally it's best to wait for the promise to resolve before refreshing the page.
*/
export async function dismissCallToAction(id: string) {
const seenCallToActions = getSeenCallToActions();
if (seenCallToActions.find(seenId => seenId === id)) {
return;
}
seenCallToActions.push(id);
await options.save("seenCallToActions", JSON.stringify(seenCallToActions));
}
function getSeenCallToActions() {
try {
return JSON.parse(options.get("seenCallToActions")) as string[];
} catch (e) {
return [];
}
}

View File

@@ -1,140 +0,0 @@
import noteAutocompleteService from "../../services/note_autocomplete.js";
import treeService from "../../services/tree.js";
import toastService from "../../services/toast.js";
import froca from "../../services/froca.js";
import branchService from "../../services/branches.js";
import appContext from "../../components/app_context.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import type { EventData } from "../../components/app_context.js";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="clone-to-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title flex-grow-1">${t("clone_to.clone_notes_to")}</h5>
<button type="button" class="help-button" title="${t("clone_to.help_on_links")}" data-help-page="cloning-notes.html">?</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("clone_to.close")}"></button>
</div>
<form class="clone-to-form">
<div class="modal-body">
<h5>${t("clone_to.notes_to_clone")}</h5>
<ul class="clone-to-note-list" style="max-height: 200px; overflow: auto;"></ul>
<div class="form-group">
<label style="width: 100%">
${t("clone_to.target_parent_note")}
<div class="input-group">
<input class="clone-to-note-autocomplete form-control" placeholder="${t("clone_to.search_for_note_by_its_name")}">
</div>
</label>
</div>
<div class="form-group" title="${t("clone_to.cloned_note_prefix_title")}">
<label style="width: 100%">
${t("clone_to.prefix_optional")}
<input class="clone-prefix form-control" style="width: 100%;">
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">${t("clone_to.clone_to_selected_note")}</button>
</div>
</form>
</div>
</div>
</div>`;
export default class CloneToDialog extends BasicWidget {
private $form!: JQuery<HTMLElement>;
private $noteAutoComplete!: JQuery<HTMLElement>;
private $clonePrefix!: JQuery<HTMLElement>;
private $noteList!: JQuery<HTMLElement>;
private clonedNoteIds: string[] | null = null;
constructor() {
super();
}
doRender() {
this.$widget = $(TPL);
this.$form = this.$widget.find(".clone-to-form");
this.$noteAutoComplete = this.$widget.find(".clone-to-note-autocomplete");
this.$clonePrefix = this.$widget.find(".clone-prefix");
this.$noteList = this.$widget.find(".clone-to-note-list");
this.$form.on("submit", () => {
const notePath = this.$noteAutoComplete.getSelectedNotePath();
if (notePath) {
this.$widget.modal("hide");
this.cloneNotesTo(notePath);
} else {
logError(t("clone_to.no_path_to_clone_to"));
}
return false;
});
}
async cloneNoteIdsToEvent({ noteIds }: EventData<"cloneNoteIdsTo">) {
if (!noteIds || noteIds.length === 0) {
noteIds = [appContext.tabManager.getActiveContextNoteId() ?? ""];
}
this.clonedNoteIds = [];
for (const noteId of noteIds) {
if (!this.clonedNoteIds.includes(noteId)) {
this.clonedNoteIds.push(noteId);
}
}
openDialog(this.$widget);
this.$noteAutoComplete.val("").trigger("focus");
this.$noteList.empty();
for (const noteId of this.clonedNoteIds) {
const note = await froca.getNote(noteId);
if (!note) {
continue;
}
this.$noteList.append($("<li>").text(note.title));
}
noteAutocompleteService.initNoteAutocomplete(this.$noteAutoComplete);
noteAutocompleteService.showRecentNotes(this.$noteAutoComplete);
}
async cloneNotesTo(notePath: string) {
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) {
return;
}
const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
if (!targetBranchId || !this.clonedNoteIds) {
return;
}
for (const cloneNoteId of this.clonedNoteIds) {
await branchService.cloneNoteToBranch(cloneNoteId, targetBranchId, this.$clonePrefix.val() as string);
const clonedNote = await froca.getNote(cloneNoteId);
const targetBranch = froca.getBranch(targetBranchId);
if (!clonedNote || !targetBranch) {
continue;
}
const targetNote = await targetBranch.getNote();
if (!targetNote) {
continue;
}
toastService.showMessage(t("clone_to.note_cloned", { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
}
}
}

View File

@@ -0,0 +1,120 @@
import { useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context";
import { t } from "../../services/i18n";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
import NoteAutocomplete from "../react/NoteAutocomplete";
import froca from "../../services/froca";
import FormGroup from "../react/FormGroup";
import FormTextBox from "../react/FormTextBox";
import Button from "../react/Button";
import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete";
import { logError } from "../../services/ws";
import tree from "../../services/tree";
import branches from "../../services/branches";
import toast from "../../services/toast";
import NoteList from "../react/NoteList";
import useTriliumEvent from "../react/hooks";
function CloneToDialogComponent() {
const [ clonedNoteIds, setClonedNoteIds ] = useState<string[]>();
const [ prefix, setPrefix ] = useState("");
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
const [ shown, setShown ] = useState(false);
const autoCompleteRef = useRef<HTMLInputElement>(null);
useTriliumEvent("cloneNoteIdsTo", ({ noteIds }) => {
if (!noteIds || noteIds.length === 0) {
noteIds = [appContext.tabManager.getActiveContextNoteId() ?? ""];
}
const clonedNoteIds: string[] = [];
for (const noteId of noteIds) {
if (!clonedNoteIds.includes(noteId)) {
clonedNoteIds.push(noteId);
}
}
setClonedNoteIds(clonedNoteIds);
setShown(true);
});
function onSubmit() {
if (!clonedNoteIds) {
return;
}
const notePath = suggestion?.notePath;
if (!notePath) {
logError(t("clone_to.no_path_to_clone_to"));
return;
}
setShown(false);
cloneNotesTo(notePath, clonedNoteIds, prefix);
}
return (
<Modal
className="clone-to-dialog"
title={t("clone_to.clone_notes_to")}
helpPageId="IakOLONlIfGI"
size="lg"
footer={<Button text={t("clone_to.clone_to_selected_note")} keyboardShortcut="Enter" />}
onSubmit={onSubmit}
onShown={() => triggerRecentNotes(autoCompleteRef.current)}
onHidden={() => setShown(false)}
show={shown}
>
<h5>{t("clone_to.notes_to_clone")}</h5>
<NoteList style={{ maxHeight: "200px", overflow: "auto" }} noteIds={clonedNoteIds} />
<FormGroup label={t("clone_to.target_parent_note")}>
<NoteAutocomplete
placeholder={t("clone_to.search_for_note_by_its_name")}
onChange={setSuggestion}
inputRef={autoCompleteRef}
/>
</FormGroup>
<FormGroup label={t("clone_to.prefix_optional")} title={t("clone_to.cloned_note_prefix_title")}>
<FormTextBox name="clone-prefix" onChange={setPrefix} />
</FormGroup>
</Modal>
)
}
export default class CloneToDialog extends ReactBasicWidget {
get component() {
return <CloneToDialogComponent />;
}
}
async function cloneNotesTo(notePath: string, clonedNoteIds: string[], prefix?: string) {
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) {
return;
}
const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
if (!targetBranchId || !clonedNoteIds) {
return;
}
for (const cloneNoteId of clonedNoteIds) {
await branches.cloneNoteToBranch(cloneNoteId, targetBranchId, prefix);
const clonedNote = await froca.getNote(cloneNoteId);
const targetBranch = froca.getBranch(targetBranchId);
if (!clonedNote || !targetBranch) {
continue;
}
const targetNote = await targetBranch.getNote();
if (!targetNote) {
continue;
}
toast.showMessage(t("clone_to.note_cloned", { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
}
}

View File

@@ -1,151 +0,0 @@
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import { Modal } from "bootstrap";
const DELETE_NOTE_BUTTON_CLASS = "confirm-dialog-delete-note";
const TPL = /*html*/`
<div class="confirm-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("confirm.confirmation")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("confirm.close")}"></button>
</div>
<div class="modal-body">
<div class="confirm-dialog-content"></div>
<div class="confirm-dialog-custom"></div>
</div>
<div class="modal-footer">
<button class="confirm-dialog-cancel-button btn btn-sm">${t("confirm.cancel")}</button>
&nbsp;
<button class="confirm-dialog-ok-button btn btn-primary btn-sm">${t("confirm.ok")}</button>
</div>
</div>
</div>
</div>`;
export type ConfirmDialogResult = false | ConfirmDialogOptions;
export type ConfirmDialogCallback = (val?: ConfirmDialogResult) => void;
export interface ConfirmDialogOptions {
confirmed: boolean;
isDeleteNoteChecked: boolean;
}
// For "showConfirmDialog"
export interface ConfirmWithMessageOptions {
message: string | HTMLElement | JQuery<HTMLElement>;
callback: ConfirmDialogCallback;
}
export interface ConfirmWithTitleOptions {
title: string;
callback: ConfirmDialogCallback;
}
export default class ConfirmDialog extends BasicWidget {
private resolve: ConfirmDialogCallback | null;
private modal!: Modal;
private $originallyFocused!: JQuery<HTMLElement> | null;
private $confirmContent!: JQuery<HTMLElement>;
private $okButton!: JQuery<HTMLElement>;
private $cancelButton!: JQuery<HTMLElement>;
private $custom!: JQuery<HTMLElement>;
constructor() {
super();
this.resolve = null;
this.$originallyFocused = null; // element focused before the dialog was opened, so we can return to it afterward
}
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$confirmContent = this.$widget.find(".confirm-dialog-content");
this.$okButton = this.$widget.find(".confirm-dialog-ok-button");
this.$cancelButton = this.$widget.find(".confirm-dialog-cancel-button");
this.$custom = this.$widget.find(".confirm-dialog-custom");
this.$widget.on("shown.bs.modal", () => this.$okButton.trigger("focus"));
this.$widget.on("hidden.bs.modal", () => {
if (this.resolve) {
this.resolve(false);
}
if (this.$originallyFocused) {
this.$originallyFocused.trigger("focus");
this.$originallyFocused = null;
}
});
this.$cancelButton.on("click", () => this.doResolve(false));
this.$okButton.on("click", () => this.doResolve(true));
}
showConfirmDialogEvent({ message, callback }: ConfirmWithMessageOptions) {
this.$originallyFocused = $(":focus");
this.$custom.hide();
glob.activeDialog = this.$widget;
if (typeof message === "string") {
message = $("<div>").text(message);
}
this.$confirmContent.empty().append(message);
this.modal.show();
this.resolve = callback;
}
showConfirmDeleteNoteBoxWithNoteDialogEvent({ title, callback }: ConfirmWithTitleOptions) {
glob.activeDialog = this.$widget;
this.$confirmContent.text(`${t("confirm.are_you_sure_remove_note", { title: title })}`);
this.$custom
.empty()
.append("<br/>")
.append(
$("<div>")
.addClass("form-check")
.append(
$("<label>")
.addClass("form-check-label")
.attr("style", "text-decoration: underline dotted var(--main-text-color)")
.attr("title", `${t("confirm.if_you_dont_check")}`)
.append($("<input>").attr("type", "checkbox").addClass(`form-check-input ${DELETE_NOTE_BUTTON_CLASS}`))
.append(`${t("confirm.also_delete_note")}`)
)
);
this.$custom.show();
this.modal.show();
this.resolve = callback;
}
doResolve(ret: boolean) {
if (this.resolve) {
this.resolve({
confirmed: ret,
isDeleteNoteChecked: this.$widget.find(`.${DELETE_NOTE_BUTTON_CLASS}:checked`).length > 0
});
}
this.resolve = null;
this.modal.hide();
}
}

View File

@@ -0,0 +1,102 @@
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import Button from "../react/Button";
import { t } from "../../services/i18n";
import { useState } from "preact/hooks";
import FormCheckbox from "../react/FormCheckbox";
import useTriliumEvent from "../react/hooks";
interface ConfirmDialogProps {
title?: string;
message?: string | HTMLElement;
callback?: ConfirmDialogCallback;
isConfirmDeleteNoteBox?: boolean;
}
function ConfirmDialogComponent() {
const [ opts, setOpts ] = useState<ConfirmDialogProps>();
const [ isDeleteNoteChecked, setIsDeleteNoteChecked ] = useState(false);
const [ shown, setShown ] = useState(false);
function showDialog(title: string | null, message: MessageType, callback: ConfirmDialogCallback, isConfirmDeleteNoteBox: boolean) {
setOpts({
title: title ?? undefined,
message: (typeof message === "object" && "length" in message ? message[0] : message),
callback,
isConfirmDeleteNoteBox
});
setShown(true);
}
useTriliumEvent("showConfirmDialog", ({ message, callback }) => showDialog(null, message, callback, false));
useTriliumEvent("showConfirmDeleteNoteBoxWithNoteDialog", ({ title, callback }) => showDialog(title, t("confirm.are_you_sure_remove_note", { title: title }), callback, true));
return (
<Modal
className="confirm-dialog"
title={opts?.title ?? t("confirm.confirmation")}
size="md"
zIndex={2000}
scrollable={true}
onHidden={() => {
opts?.callback?.({
confirmed: false,
isDeleteNoteChecked
});
setShown(false);
}}
footer={<>
<Button text={t("confirm.cancel")} onClick={() => setShown(false)} />
<Button text={t("confirm.ok")} onClick={() => {
opts?.callback?.({
confirmed: true,
isDeleteNoteChecked
});
setShown(false);
}} />
</>}
show={shown}
stackable
>
{!opts?.message || typeof opts?.message === "string"
? <div>{(opts?.message as string) ?? ""}</div>
: <div dangerouslySetInnerHTML={{ __html: opts?.message.outerHTML ?? "" }} />}
{opts?.isConfirmDeleteNoteBox && (
<FormCheckbox
name="confirm-dialog-delete-note"
label={t("confirm.also_delete_note")}
hint={t("confirm.if_you_dont_check")}
currentValue={isDeleteNoteChecked} onChange={setIsDeleteNoteChecked} />
)}
</Modal>
);
}
export type ConfirmDialogResult = false | ConfirmDialogOptions;
export type ConfirmDialogCallback = (val?: ConfirmDialogResult) => void;
type MessageType = string | HTMLElement | JQuery<HTMLElement>;
export interface ConfirmDialogOptions {
confirmed: boolean;
isDeleteNoteChecked: boolean;
}
export interface ConfirmWithMessageOptions {
message: MessageType;
callback: ConfirmDialogCallback;
}
// For "showConfirmDialog"
export interface ConfirmWithTitleOptions {
title: string;
callback: ConfirmDialogCallback;
}
export default class ConfirmDialog extends ReactBasicWidget {
get component() {
return <ConfirmDialogComponent />;
}
}

View File

@@ -1,198 +0,0 @@
import server from "../../services/server.js";
import froca from "../../services/froca.js";
import linkService from "../../services/link.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import type { FAttributeRow } from "../../entities/fattribute.js";
import { closeActiveDialog, openDialog } from "../../services/dialog.js";
// TODO: Use common with server.
interface Response {
noteIdsToBeDeleted: string[];
brokenRelations: FAttributeRow[];
}
export interface ResolveOptions {
proceed: boolean;
deleteAllClones?: boolean;
eraseNotes?: boolean;
}
interface ShowDeleteNotesDialogOpts {
branchIdsToDelete: string[];
callback: (opts: ResolveOptions) => void;
forceDeleteAllClones: boolean;
}
const TPL = /*html*/`
<div class="delete-notes-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">${t("delete_notes.delete_notes_preview")}</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("delete_notes.close")}"></button>
</div>
<div class="modal-body">
<div class="form-checkbox">
<label for="delete-all-clones" class="form-check-label tn-checkbox">
<input id="delete-all-clones" class="delete-all-clones form-check-input" value="1" type="checkbox">
${t("delete_notes.delete_all_clones_description")}
</label>
</div>
<div class="form-checkbox" style="margin-bottom: 1rem">
<label for="erase-notes" class="form-check-label tn-checkbox">
<input id="erase-notes" class="erase-notes form-check-input" value="1" type="checkbox">
${t("delete_notes.erase_notes_warning")}
</label>
</div>
<div class="delete-notes-list-wrapper">
<h4>${t("delete_notes.notes_to_be_deleted", { noteCount: '<span class="deleted-notes-count"></span>' })}</h4>
<ul class="delete-notes-list" style="max-height: 200px; overflow: auto;"></ul>
</div>
<div class="no-note-to-delete-wrapper alert alert-info">
${t("delete_notes.no_note_to_delete")}
</div>
<div class="broken-relations-wrapper">
<div class="alert alert-danger">
<h4>${t("delete_notes.broken_relations_to_be_deleted", { relationCount: '<span class="broke-relations-count"></span>' })}</h4>
<ul class="broken-relations-list" style="max-height: 200px; overflow: auto;"></ul>
</div>
</div>
</div>
<div class="modal-footer">
<button class="delete-notes-dialog-cancel-button btn btn-sm">${t("delete_notes.cancel")}</button>
&nbsp;
<button class="delete-notes-dialog-ok-button btn btn-primary btn-sm">${t("delete_notes.ok")}</button>
</div>
</div>
</div>
</div>`;
export default class DeleteNotesDialog extends BasicWidget {
private branchIds: string[] | null;
private resolve!: (options: ResolveOptions) => void;
private $content!: JQuery<HTMLElement>;
private $okButton!: JQuery<HTMLElement>;
private $cancelButton!: JQuery<HTMLElement>;
private $deleteNotesList!: JQuery<HTMLElement>;
private $brokenRelationsList!: JQuery<HTMLElement>;
private $deletedNotesCount!: JQuery<HTMLElement>;
private $noNoteToDeleteWrapper!: JQuery<HTMLElement>;
private $deleteNotesListWrapper!: JQuery<HTMLElement>;
private $brokenRelationsListWrapper!: JQuery<HTMLElement>;
private $brokenRelationsCount!: JQuery<HTMLElement>;
private $deleteAllClones!: JQuery<HTMLElement>;
private $eraseNotes!: JQuery<HTMLElement>;
private forceDeleteAllClones?: boolean;
constructor() {
super();
this.branchIds = null;
}
doRender() {
this.$widget = $(TPL);
this.$content = this.$widget.find(".recent-changes-content");
this.$okButton = this.$widget.find(".delete-notes-dialog-ok-button");
this.$cancelButton = this.$widget.find(".delete-notes-dialog-cancel-button");
this.$deleteNotesList = this.$widget.find(".delete-notes-list");
this.$brokenRelationsList = this.$widget.find(".broken-relations-list");
this.$deletedNotesCount = this.$widget.find(".deleted-notes-count");
this.$noNoteToDeleteWrapper = this.$widget.find(".no-note-to-delete-wrapper");
this.$deleteNotesListWrapper = this.$widget.find(".delete-notes-list-wrapper");
this.$brokenRelationsListWrapper = this.$widget.find(".broken-relations-wrapper");
this.$brokenRelationsCount = this.$widget.find(".broke-relations-count");
this.$deleteAllClones = this.$widget.find(".delete-all-clones");
this.$eraseNotes = this.$widget.find(".erase-notes");
this.$widget.on("shown.bs.modal", () => this.$okButton.trigger("focus"));
this.$cancelButton.on("click", () => {
closeActiveDialog();
this.resolve({ proceed: false });
});
this.$okButton.on("click", () => {
closeActiveDialog();
this.resolve({
proceed: true,
deleteAllClones: this.forceDeleteAllClones || this.isDeleteAllClonesChecked(),
eraseNotes: this.isEraseNotesChecked()
});
});
this.$deleteAllClones.on("click", () => this.renderDeletePreview());
}
async renderDeletePreview() {
const response = await server.post<Response>("delete-notes-preview", {
branchIdsToDelete: this.branchIds,
deleteAllClones: this.forceDeleteAllClones || this.isDeleteAllClonesChecked()
});
this.$deleteNotesList.empty();
this.$brokenRelationsList.empty();
this.$deleteNotesListWrapper.toggle(response.noteIdsToBeDeleted.length > 0);
this.$noNoteToDeleteWrapper.toggle(response.noteIdsToBeDeleted.length === 0);
for (const note of await froca.getNotes(response.noteIdsToBeDeleted)) {
this.$deleteNotesList.append($("<li>").append(await linkService.createLink(note.noteId, { showNotePath: true })));
}
this.$deletedNotesCount.text(response.noteIdsToBeDeleted.length);
this.$brokenRelationsListWrapper.toggle(response.brokenRelations.length > 0);
this.$brokenRelationsCount.text(response.brokenRelations.length);
await froca.getNotes(response.brokenRelations.map((br) => br.noteId));
for (const attr of response.brokenRelations) {
this.$brokenRelationsList.append(
$("<li>").html(
t("delete_notes.deleted_relation_text", {
note: (await linkService.createLink(attr.value)).html(),
relation: `<code>${attr.name}</code>`,
source: (await linkService.createLink(attr.noteId)).html()
})
)
);
}
}
async showDeleteNotesDialogEvent({ branchIdsToDelete, callback, forceDeleteAllClones }: ShowDeleteNotesDialogOpts) {
this.branchIds = branchIdsToDelete;
this.forceDeleteAllClones = forceDeleteAllClones;
await this.renderDeletePreview();
openDialog(this.$widget);
this.$deleteAllClones.prop("checked", !!forceDeleteAllClones).prop("disabled", !!forceDeleteAllClones);
this.$eraseNotes.prop("checked", false);
this.resolve = callback;
}
isDeleteAllClonesChecked() {
return this.$deleteAllClones.is(":checked");
}
isEraseNotesChecked() {
return this.$eraseNotes.is(":checked");
}
}

View File

@@ -0,0 +1,181 @@
import { useRef, useState, useEffect } from "preact/hooks";
import { t } from "../../services/i18n.js";
import FormCheckbox from "../react/FormCheckbox.js";
import Modal from "../react/Modal.js";
import ReactBasicWidget from "../react/ReactBasicWidget.js";
import type { DeleteNotesPreview } from "@triliumnext/commons";
import server from "../../services/server.js";
import froca from "../../services/froca.js";
import FNote from "../../entities/fnote.js";
import link from "../../services/link.js";
import Button from "../react/Button.jsx";
import Alert from "../react/Alert.jsx";
import useTriliumEvent from "../react/hooks.jsx";
export interface ResolveOptions {
proceed: boolean;
deleteAllClones?: boolean;
eraseNotes?: boolean;
}
interface ShowDeleteNotesDialogOpts {
branchIdsToDelete?: string[];
callback?: (opts: ResolveOptions) => void;
forceDeleteAllClones?: boolean;
}
interface BrokenRelationData {
note: string;
relation: string;
source: string;
}
function DeleteNotesDialogComponent() {
const [ opts, setOpts ] = useState<ShowDeleteNotesDialogOpts>({});
const [ deleteAllClones, setDeleteAllClones ] = useState(false);
const [ eraseNotes, setEraseNotes ] = useState(!!opts.forceDeleteAllClones);
const [ brokenRelations, setBrokenRelations ] = useState<DeleteNotesPreview["brokenRelations"]>([]);
const [ noteIdsToBeDeleted, setNoteIdsToBeDeleted ] = useState<DeleteNotesPreview["noteIdsToBeDeleted"]>([]);
const [ shown, setShown ] = useState(false);
const okButtonRef = useRef<HTMLButtonElement>(null);
useTriliumEvent("showDeleteNotesDialog", (opts) => {
setOpts(opts);
setShown(true);
})
useEffect(() => {
const { branchIdsToDelete, forceDeleteAllClones } = opts;
if (!branchIdsToDelete || branchIdsToDelete.length === 0) {
return;
}
server.post<DeleteNotesPreview>("delete-notes-preview", {
branchIdsToDelete,
deleteAllClones: forceDeleteAllClones || deleteAllClones
}).then(response => {
setBrokenRelations(response.brokenRelations);
setNoteIdsToBeDeleted(response.noteIdsToBeDeleted);
});
}, [ opts, deleteAllClones ]);
return (
<Modal
className="delete-notes-dialog"
size="xl"
scrollable
title={t("delete_notes.delete_notes_preview")}
onShown={() => okButtonRef.current?.focus()}
onHidden={() => {
opts.callback?.({ proceed: false })
setShown(false);
}}
footer={<>
<Button text={t("delete_notes.cancel")}
onClick={() => setShown(false)} />
<Button text={t("delete_notes.ok")} primary
buttonRef={okButtonRef}
onClick={() => {
opts.callback?.({ proceed: true, deleteAllClones, eraseNotes });
setShown(false);
}} />
</>}
show={shown}
>
<FormCheckbox name="delete-all-clones" label={t("delete_notes.delete_all_clones_description")}
currentValue={deleteAllClones} onChange={setDeleteAllClones}
/>
<FormCheckbox
name="erase-notes" label={t("delete_notes.erase_notes_warning")}
disabled={opts.forceDeleteAllClones}
currentValue={eraseNotes} onChange={setEraseNotes}
/>
<DeletedNotes noteIdsToBeDeleted={noteIdsToBeDeleted} />
<BrokenRelations brokenRelations={brokenRelations} />
</Modal>
);
}
function DeletedNotes({ noteIdsToBeDeleted }: { noteIdsToBeDeleted: DeleteNotesPreview["noteIdsToBeDeleted"] }) {
const [ noteLinks, setNoteLinks ] = useState<string[]>([]);
useEffect(() => {
froca.getNotes(noteIdsToBeDeleted).then(async (notes: FNote[]) => {
const noteLinks: string[] = [];
for (const note of notes) {
noteLinks.push((await link.createLink(note.noteId, { showNotePath: true })).html());
}
setNoteLinks(noteLinks);
});
}, [noteIdsToBeDeleted]);
if (noteIdsToBeDeleted.length) {
return (
<div className="delete-notes-list-wrapper">
<h4>{t("delete_notes.notes_to_be_deleted", { notesCount: noteIdsToBeDeleted.length })}</h4>
<ul className="delete-notes-list" style={{ maxHeight: "200px", overflow: "auto" }}>
{noteLinks.map((link, index) => (
<li key={index} dangerouslySetInnerHTML={{ __html: link }} />
))}
</ul>
</div>
);
} else {
return (
<Alert type="info">
{t("delete_notes.no_note_to_delete")}
</Alert>
)
}
}
function BrokenRelations({ brokenRelations }: { brokenRelations: DeleteNotesPreview["brokenRelations"] }) {
const [ notesWithBrokenRelations, setNotesWithBrokenRelations ] = useState<BrokenRelationData[]>([]);
useEffect(() => {
const noteIds = brokenRelations
.map(relation => relation.noteId)
.filter(noteId => noteId) as string[];
froca.getNotes(noteIds).then(async (notes) => {
const notesWithBrokenRelations: BrokenRelationData[] = [];
for (const attr of brokenRelations) {
notesWithBrokenRelations.push({
note: (await link.createLink(attr.value)).html(),
relation: `<code>${attr.name}</code>`,
source: (await link.createLink(attr.noteId)).html()
});
}
setNotesWithBrokenRelations(notesWithBrokenRelations);
});
}, [brokenRelations]);
if (brokenRelations.length) {
return (
<Alert type="danger" title={t("delete_notes.broken_relations_to_be_deleted", { relationCount: brokenRelations.length })}>
<ul className="broken-relations-list" style={{ maxHeight: "200px", overflow: "auto" }}>
{brokenRelations.map((_, index) => {
return (
<li key={index}>
<span dangerouslySetInnerHTML={{ __html: t("delete_notes.deleted_relation_text", notesWithBrokenRelations[index] as unknown as Record<string, string>) }} />
</li>
);
})}
</ul>
</Alert>
);
} else {
return <></>;
}
}
export default class DeleteNotesDialog extends ReactBasicWidget {
get component() {
return <DeleteNotesDialogComponent />;
}
}

View File

@@ -0,0 +1,16 @@
.export-dialog form .form-check {
padding-top: 10px;
padding-bottom: 10px;
}
.export-dialog form .format-choice {
padding-left: 40px;
}
.export-dialog form .opml-versions {
padding-left: 60px;
}
.export-dialog form .form-check-label {
padding: 2px;
}

View File

@@ -1,264 +0,0 @@
import treeService from "../../services/tree.js";
import utils from "../../services/utils.js";
import ws from "../../services/ws.js";
import toastService, { type ToastOptions } from "../../services/toast.js";
import froca from "../../services/froca.js";
import openService from "../../services/open.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import type { EventData } from "../../components/app_context.js";
import { Modal } from "bootstrap";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="export-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<style>
.export-dialog .export-form .form-check {
padding-top: 10px;
padding-bottom: 10px;
}
.export-dialog .export-form .format-choice {
padding-left: 40px;
display: none;
}
.export-dialog .export-form .opml-versions {
padding-left: 60px;
display: none;
}
.export-dialog .export-form .form-check-label {
padding: 2px;
}
</style>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("export.export_note_title")} <span class="export-note-title"></span></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("export.close")}"></button>
</div>
<form class="export-form">
<div class="modal-body">
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="export-type-subtree form-check-input" type="radio" name="export-type" value="subtree">
${t("export.export_type_subtree")}
</label>
</div>
<div class="export-subtree-formats format-choice">
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="export-subtree-format" value="html">
${t("export.format_html_zip")}
</label>
</div>
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="export-subtree-format" value="markdown">
${t("export.format_markdown")}
</label>
</div>
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="export-subtree-format" value="opml">
${t("export.format_opml")}
</label>
</div>
<div class="opml-versions">
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="opml-version" value="1.0">
${t("export.opml_version_1")}
</label>
</div>
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="opml-version" value="2.0">
${t("export.opml_version_2")}
</label>
</div>
</div>
</div>
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="export-type" value="single">
${t("export.export_type_single")}
</label>
</div>
<div class="export-single-formats format-choice">
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="export-single-format" value="html">
${t("export.format_html")}
</label>
</div>
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="export-single-format" value="markdown">
${t("export.format_markdown")}
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="export-button btn btn-primary">${t("export.export")}</button>
</div>
</form>
</div>
</div>
</div>`;
export default class ExportDialog extends BasicWidget {
private taskId: string;
private branchId: string | null;
private modal?: Modal;
private $form!: JQuery<HTMLElement>;
private $noteTitle!: JQuery<HTMLElement>;
private $subtreeFormats!: JQuery<HTMLElement>;
private $singleFormats!: JQuery<HTMLElement>;
private $subtreeType!: JQuery<HTMLElement>;
private $singleType!: JQuery<HTMLElement>;
private $exportButton!: JQuery<HTMLElement>;
private $opmlVersions!: JQuery<HTMLElement>;
constructor() {
super();
this.taskId = "";
this.branchId = null;
}
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$form = this.$widget.find(".export-form");
this.$noteTitle = this.$widget.find(".export-note-title");
this.$subtreeFormats = this.$widget.find(".export-subtree-formats");
this.$singleFormats = this.$widget.find(".export-single-formats");
this.$subtreeType = this.$widget.find(".export-type-subtree");
this.$singleType = this.$widget.find(".export-type-single");
this.$exportButton = this.$widget.find(".export-button");
this.$opmlVersions = this.$widget.find(".opml-versions");
this.$form.on("submit", () => {
this.modal?.hide();
const exportType = this.$widget.find("input[name='export-type']:checked").val();
if (!exportType) {
toastService.showError(t("export.choose_export_type"));
return;
}
const exportFormat = exportType === "subtree" ? this.$widget.find("input[name=export-subtree-format]:checked").val() : this.$widget.find("input[name=export-single-format]:checked").val();
const exportVersion = exportFormat === "opml" ? this.$widget.find("input[name='opml-version']:checked").val() : "1.0";
if (this.branchId) {
this.exportBranch(this.branchId, String(exportType), String(exportFormat), String(exportVersion));
}
return false;
});
this.$widget.find("input[name=export-type]").on("change", (e) => {
if ((e.currentTarget as HTMLInputElement).value === "subtree") {
if (this.$widget.find("input[name=export-subtree-format]:checked").length === 0) {
this.$widget.find("input[name=export-subtree-format]:first").prop("checked", true);
}
this.$subtreeFormats.slideDown();
this.$singleFormats.slideUp();
} else {
if (this.$widget.find("input[name=export-single-format]:checked").length === 0) {
this.$widget.find("input[name=export-single-format]:first").prop("checked", true);
}
this.$subtreeFormats.slideUp();
this.$singleFormats.slideDown();
}
});
this.$widget.find("input[name=export-subtree-format]").on("change", (e) => {
if ((e.currentTarget as HTMLInputElement).value === "opml") {
this.$opmlVersions.slideDown();
} else {
this.$opmlVersions.slideUp();
}
});
}
async showExportDialogEvent({ notePath, defaultType }: EventData<"showExportDialog">) {
this.taskId = "";
this.$exportButton.removeAttr("disabled");
if (defaultType === "subtree") {
this.$subtreeType.prop("checked", true).trigger("change");
this.$widget.find("input[name=export-subtree-format]:checked").trigger("change");
} else if (defaultType === "single") {
this.$singleType.prop("checked", true).trigger("change");
} else {
throw new Error(`Unrecognized type '${defaultType}'`);
}
this.$widget.find(".opml-v2").prop("checked", true); // setting default
openDialog(this.$widget);
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
if (parentNoteId) {
this.branchId = await froca.getBranchId(parentNoteId, noteId);
}
if (noteId) {
this.$noteTitle.text(await treeService.getNoteTitle(noteId));
}
}
exportBranch(branchId: string, type: string, format: string, version: string) {
this.taskId = utils.randomString(10);
const url = openService.getUrlForDownload(`api/branches/${branchId}/export/${type}/${format}/${version}/${this.taskId}`);
openService.download(url);
}
}
ws.subscribeToMessages(async (message) => {
function makeToast(id: string, message: string): ToastOptions {
return {
id: id,
title: t("export.export_status"),
message: message,
icon: "arrow-square-up-right"
};
}
if (message.taskType !== "export") {
return;
}
if (message.type === "taskError") {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === "taskProgressCount") {
toastService.showPersistent(makeToast(message.taskId, t("export.export_in_progress", { progressCount: message.progressCount })));
} else if (message.type === "taskSucceeded") {
const toast = makeToast(message.taskId, t("export.export_finished_successfully"));
toast.closeAfter = 5000;
toastService.showPersistent(toast);
}
});

View File

@@ -0,0 +1,167 @@
import { useState } from "preact/hooks";
import { t } from "../../services/i18n";
import tree from "../../services/tree";
import Button from "../react/Button";
import FormRadioGroup from "../react/FormRadioGroup";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
import "./export.css";
import ws from "../../services/ws";
import toastService, { ToastOptions } from "../../services/toast";
import utils from "../../services/utils";
import open from "../../services/open";
import froca from "../../services/froca";
import useTriliumEvent from "../react/hooks";
interface ExportDialogProps {
branchId?: string | null;
noteTitle?: string;
defaultType?: "subtree" | "single";
}
function ExportDialogComponent() {
const [ opts, setOpts ] = useState<ExportDialogProps>();
const [ exportType, setExportType ] = useState<string>(opts?.defaultType ?? "subtree");
const [ subtreeFormat, setSubtreeFormat ] = useState("html");
const [ singleFormat, setSingleFormat ] = useState("html");
const [ opmlVersion, setOpmlVersion ] = useState("2.0");
const [ shown, setShown ] = useState(false);
useTriliumEvent("showExportDialog", async ({ notePath, defaultType }) => {
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
if (!parentNoteId) {
return;
}
const branchId = await froca.getBranchId(parentNoteId, noteId);
setOpts({
noteTitle: noteId && await tree.getNoteTitle(noteId),
defaultType,
branchId
});
setShown(true);
});
return (
<Modal
className="export-dialog"
title={`${t("export.export_note_title")} ${opts?.noteTitle ?? ""}`}
size="lg"
onSubmit={() => {
if (!opts || !opts.branchId) {
return;
}
const format = (exportType === "subtree" ? subtreeFormat : singleFormat);
const version = (format === "opml" ? opmlVersion : "1.0");
exportBranch(opts.branchId, exportType, format, version);
setShown(false);
}}
onHidden={() => setShown(false)}
footer={<Button className="export-button" text={t("export.export")} primary />}
show={shown}
>
<FormRadioGroup
name="export-type"
currentValue={exportType} onChange={setExportType}
values={[{
value: "subtree",
label: t("export.export_type_subtree")
}]}
/>
{ exportType === "subtree" &&
<div className="export-subtree-formats format-choice">
<FormRadioGroup
name="export-subtree-format"
currentValue={subtreeFormat} onChange={setSubtreeFormat}
values={[
{ value: "html", label: t("export.format_html_zip") },
{ value: "markdown", label: t("export.format_markdown") },
{ value: "opml", label: t("export.format_opml") }
]}
/>
{ subtreeFormat === "opml" &&
<div className="opml-versions">
<FormRadioGroup
name="opml-version"
currentValue={opmlVersion} onChange={setOpmlVersion}
values={[
{ value: "1.0", label: t("export.opml_version_1") },
{ value: "2.0", label: t("export.opml_version_2") }
]}
/>
</div>
}
</div>
}
<FormRadioGroup
name="export-type"
currentValue={exportType} onChange={setExportType}
values={[{
value: "single",
label: t("export.export_type_single")
}]}
/>
{ exportType === "single" &&
<div class="export-single-formats format-choice">
<FormRadioGroup
name="export-single-format"
currentValue={singleFormat} onChange={setSingleFormat}
values={[
{ value: "html", label: t("export.format_html") },
{ value: "markdown", label: t("export.format_markdown") }
]}
/>
</div>
}
</Modal>
);
}
export default class ExportDialog extends ReactBasicWidget {
get component() {
return <ExportDialogComponent />
}
}
function exportBranch(branchId: string, type: string, format: string, version: string) {
const taskId = utils.randomString(10);
const url = open.getUrlForDownload(`api/branches/${branchId}/export/${type}/${format}/${version}/${taskId}`);
open.download(url);
}
ws.subscribeToMessages(async (message) => {
function makeToast(id: string, message: string): ToastOptions {
return {
id: id,
title: t("export.export_status"),
message: message,
icon: "arrow-square-up-right"
};
}
if (message.taskType !== "export") {
return;
}
if (message.type === "taskError") {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === "taskProgressCount") {
toastService.showPersistent(makeToast(message.taskId, t("export.export_in_progress", { progressCount: message.progressCount })));
} else if (message.type === "taskSucceeded") {
const toast = makeToast(message.taskId, t("export.export_finished_successfully"));
toast.closeAfter = 5000;
toastService.showPersistent(toast);
}
});

View File

@@ -1,160 +0,0 @@
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="help-dialog modal use-tn-links" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document" style="min-width: 90%;">
<div class="modal-content" style="height: auto;">
<div class="modal-header">
<h5 class="modal-title">${t("help.fullDocumentation")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("help.close")}"></button>
</div>
<div class="modal-body" style="overflow: auto;">
<div class="help-cards row row-cols-md-3 g-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">${t("help.noteNavigation")}</h5>
<p class="card-text">
<ul>
<li>${t("help.goUpDown")}</li>
<li>${t("help.collapseExpand")}</li>
<li><kbd data-command="backInNoteHistory">${t("help.notSet")}</kbd>, <kbd data-command="forwardInNoteHistory">${t("help.notSet")}</kbd> - ${t("help.goBackForwards")}</li>
<li><kbd data-command="jumpToNote">${t("help.notSet")}</kbd> - ${t("help.showJumpToNoteDialog")}</li>
<li><kbd data-command="scrollToActiveNote">${t("help.notSet")}</kbd> - ${t("help.scrollToActiveNote")}</li>
<li>${t("help.jumpToParentNote")}</li>
<li><kbd data-command="collapseTree">${t("help.notSet")}</kbd> - ${t("help.collapseWholeTree")}</li>
<li><kbd data-command="collapseSubtree">${t("help.notSet")}</kbd> - ${t("help.collapseSubTree")}</li>
</ul>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">${t("help.tabShortcuts")}</h5>
<p class="card-text">
<ul>
<li>${t("help.newTabNoteLink")}</li>
<li>${t("help.newTabWithActivationNoteLink")}</li>
</ul>
<h6>${t("help.onlyInDesktop")}:</h6>
<ul>
<li><kbd data-command="openNewTab">${t("help.notSet")}</kbd> ${t("help.openEmptyTab")}</li>
<li><kbd data-command="closeActiveTab">${t("help.notSet")}</kbd> ${t("help.closeActiveTab")}</li>
<li><kbd data-command="activateNextTab">${t("help.notSet")}</kbd> ${t("help.activateNextTab")}</li>
<li><kbd data-command="activatePreviousTab">${t("help.notSet")}</kbd> ${t("help.activatePreviousTab")}</li>
</ul>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">${t("help.creatingNotes")}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="createNoteAfter">${t("help.notSet")}</kbd> - ${t("help.createNoteAfter")}</li>
<li><kbd data-command="createNoteInto">${t("help.notSet")}</kbd> - ${t("help.createNoteInto")}</li>
<li><kbd data-command="editBranchPrefix">${t("help.notSet")}</kbd> - ${t("help.editBranchPrefix")}</li>
</ul>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">${t("help.movingCloningNotes")}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="moveNoteUp">${t("help.notSet")}</kbd>, <kbd data-command="moveNoteDown">${t("help.notSet")}</kbd> - ${t("help.moveNoteUpDown")}</li>
<li><kbd data-command="moveNoteUpInHierarchy">${t("help.notSet")}</kbd>, <kbd data-command="moveNoteDownInHierarchy">${t("help.notSet")}</kbd> - ${t("help.moveNoteUpHierarchy")}</li>
<li><kbd data-command="addNoteAboveToSelection">${t("help.notSet")}</kbd>, <kbd data-command="addNoteBelowToSelection">${t("help.notSet")}</kbd> - ${t("help.multiSelectNote")}</li>
<li><kbd data-command="selectAllNotesInParent">${t("help.notSet")}</kbd> - ${t("help.selectAllNotes")}</li>
<li>${t("help.selectNote")}</li>
<li><kbd data-command="copyNotesToClipboard">${t("help.notSet")}</kbd> - ${t("help.copyNotes")}</li>
<li><kbd data-command="cutNotesToClipboard">${t("help.notSet")}</kbd> - ${t("help.cutNotes")}</li>
<li><kbd data-command="pasteNotesFromClipboard">${t("help.notSet")}</kbd> - ${t("help.pasteNotes")}</li>
<li><kbd data-command="deleteNotes">${t("help.notSet")}</kbd> - ${t("help.deleteNotes")}</li>
</ul>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">${t("help.editingNotes")}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="editNoteTitle">${t("help.notSet")}</kbd> ${t("help.editNoteTitle")}</li>
<li>${t("help.createEditLink")}</li>
<li><kbd data-command="addLinkToText">${t("help.notSet")}</kbd> - ${t("help.createInternalLink")}</li>
<li><kbd data-command="followLinkUnderCursor">${t("help.notSet")}</kbd> - ${t("help.followLink")}</li>
<li><kbd data-command="insertDateTimeToText">${t("help.notSet")}</kbd> - ${t("help.insertDateTime")}</li>
<li><kbd data-command="scrollToActiveNote">${t("help.notSet")}</kbd> - ${t("help.jumpToTreePane")}</li>
</ul>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title"><a class="external" href="https://triliumnext.github.io/Docs/Wiki/text-notes.html#markdown--autoformat">${t("help.markdownAutoformat")}</a></h5>
<p class="card-text">
<ul>
<li>${t("help.headings")}</li>
<li>${t("help.bulletList")}</li>
<li>${t("help.numberedList")}</li>
<li>${t("help.blockQuote")}</li>
</ul>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">${t("help.troubleshooting")}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="reloadFrontendApp">${t("help.notSet")}</kbd> - ${t("help.reloadFrontend")}</li>
<li><kbd data-command="openDevTools">${t("help.notSet")}</kbd> - ${t("help.showDevTools")}</li>
<li><kbd data-command="showSQLConsole">${t("help.notSet")}</kbd> - ${t("help.showSQLConsole")}</li>
</ul>
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h5 class="card-title">${t("help.other")}</h5>
<p class="card-text">
<ul>
<li><kbd data-command="quickSearch">${t("help.notSet")}</kbd> - ${t("help.quickSearch")}</li>
<li><kbd data-command="findInText">${t("help.notSet")}</kbd> - ${t("help.inPageSearch")}</li>
</ul>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>`;
export default class HelpDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
}
showCheatsheetEvent() {
openDialog(this.$widget);
}
}

View File

@@ -0,0 +1,171 @@
import ReactBasicWidget from "../react/ReactBasicWidget.js";
import Modal from "../react/Modal.jsx";
import { t } from "../../services/i18n.js";
import { ComponentChildren } from "preact";
import { CommandNames } from "../../components/app_context.js";
import RawHtml from "../react/RawHtml.jsx";
import { useEffect, useState } from "preact/hooks";
import keyboard_actions from "../../services/keyboard_actions.js";
import useTriliumEvent from "../react/hooks.jsx";
function HelpDialogComponent() {
const [ shown, setShown ] = useState(false);
useTriliumEvent("showCheatsheet", () => setShown(true));
return (
<Modal
title={t("help.title")} className="help-dialog use-tn-links" minWidth="90%" size="lg" scrollable
onHidden={() => setShown(false)}
show={shown}
>
<div className="help-cards row row-cols-md-3 g-3">
<Card title={t("help.noteNavigation")}>
<ul>
<FixedKeyboardShortcut keys={["Up", "Down"]} description={t("help.goUpDown")} />
<FixedKeyboardShortcut keys={["Left", "Right"]} description={t("help.collapseExpand")} />
<KeyboardShortcut commands="backInNoteHistory" description={t("help.goBackForwards")} />
<KeyboardShortcut commands="jumpToNote" description={t("help.showJumpToNoteDialog")} />
<KeyboardShortcut commands="scrollToActiveNote" description={t("help.scrollToActiveNote")} />
<FixedKeyboardShortcut keys={["Backspace"]} description={t("help.jumpToParentNote")} />
<KeyboardShortcut commands="collapseTree" description={t("help.collapseWholeTree")} />
<KeyboardShortcut commands="collapseSubtree" description={t("help.collapseSubTree")} />
</ul>
</Card>
<Card title={t("help.tabShortcuts")}>
<ul>
<FixedKeyboardShortcut keys={["Ctrl+Click", "Ctrl+middle click"]} description={t("help.newTabNoteLink")} />
<FixedKeyboardShortcut keys={["Ctrl+Shift+Click", "Shift+middle click"]} description={t("help.newTabWithActivationNoteLink")} />
</ul>
<h6>{t("help.onlyInDesktop")}</h6>
<ul>
<KeyboardShortcut commands="openNewTab" description={t("help.openEmptyTab")} />
<KeyboardShortcut commands="closeActiveTab" description={t("help.closeActiveTab")} />
<KeyboardShortcut commands="activateNextTab" description={t("help.activateNextTab")} />
<KeyboardShortcut commands="activatePreviousTab" description={t("help.activatePreviousTab")} />
</ul>
</Card>
<Card title={t("help.creatingNotes")}>
<ul>
<KeyboardShortcut commands="createNoteAfter" description={t("help.createNoteAfter")} />
<KeyboardShortcut commands="createNoteInto" description={t("help.createNoteInto")} />
<KeyboardShortcut commands="editBranchPrefix" description={t("help.editBranchPrefix")} />
</ul>
</Card>
<Card title={t("help.movingCloningNotes")}>
<ul>
<KeyboardShortcut commands={["moveNoteUp", "moveNoteDown"]} description={t("help.moveNoteUpDown")} />
<KeyboardShortcut commands={["moveNoteUpInHierarchy", "moveNoteDownInHierarchy"]} description={t("help.moveNoteUpHierarchy")} />
<KeyboardShortcut commands={["addNoteAboveToSelection", "addNoteBelowToSelection"]} description={t("help.multiSelectNote")} />
<KeyboardShortcut commands="selectAllNotesInParent" description={t("help.selectAllNotes")} />
<FixedKeyboardShortcut keys={["Shift+Click"]} description={t("help.selectNote")} />
<KeyboardShortcut commands="copyNotesToClipboard" description={t("help.copyNotes")} />
<KeyboardShortcut commands="cutNotesToClipboard" description={t("help.cutNotes")} />
<KeyboardShortcut commands="pasteNotesFromClipboard" description={t("help.pasteNotes")} />
<KeyboardShortcut commands="deleteNotes" description={t("help.deleteNotes")} />
</ul>
</Card>
<Card title={t("help.editingNotes")}>
<ul>
<KeyboardShortcut commands="editNoteTitle" description={t("help.editNoteTitle")} />
<FixedKeyboardShortcut keys={["Ctrl+K"]} description={t("help.createEditLink")} />
<KeyboardShortcut commands="addLinkToText" description={t("help.createInternalLink")} />
<KeyboardShortcut commands="followLinkUnderCursor" description={t("help.followLink")} />
<KeyboardShortcut commands="insertDateTimeToText" description={t("help.insertDateTime")} />
<KeyboardShortcut commands="scrollToActiveNote" description={t("help.jumpToTreePane")} />
</ul>
</Card>
<Card title={t("help.markdownAutoformat")}>
<ul>
<li><RawHtml html={t("help.headings")} /></li>
<li><RawHtml html={t("help.bulletList")} /></li>
<li><RawHtml html={t("help.numberedList")} /></li>
<li><RawHtml html={t("help.blockQuote")} /></li>
</ul>
</Card>
<Card title={t("help.troubleshooting")}>
<ul>
<KeyboardShortcut commands="reloadFrontendApp" description={t("help.reloadFrontend")} />
<KeyboardShortcut commands="openDevTools" description={t("help.showDevTools")} />
<KeyboardShortcut commands="showSQLConsole" description={t("help.showSQLConsole")} />
</ul>
</Card>
<Card title={t("help.other")}>
<ul>
<KeyboardShortcut commands="quickSearch" description={t("help.quickSearch")} />
<KeyboardShortcut commands="findInText" description={t("help.inPageSearch")} />
</ul>
</Card>
</div>
</Modal>
);
}
function KeyboardShortcut({ commands, description }: { commands: CommandNames | CommandNames[], description: string }) {
const [ shortcuts, setShortcuts ] = useState<string[]>([]);
useEffect(() => {
(async () => {
const shortcuts: string[] = [];
for (const command of Array.isArray(commands) ? commands : [commands]) {
const action = await keyboard_actions.getAction(command);
if (action) {
shortcuts.push(...(action.effectiveShortcuts ?? []));
}
}
if (shortcuts.length === 0) {
shortcuts.push(t("help.notSet"));
}
setShortcuts(shortcuts);
})();
}, [commands]);
return FixedKeyboardShortcut({
keys: shortcuts,
description
});
}
function FixedKeyboardShortcut({ keys, description }: { keys?: string[], description: string }) {
return (
<li>
{keys && keys.map((key, index) =>
<>
<kbd key={index}>{key}</kbd>
{index < keys.length - 1 ? ", " : "" }
</>
)} - <RawHtml html={description} />
</li>
);
}
function Card({ title, children }: { title: string, children: ComponentChildren }) {
return (
<div className="card">
<div className="card-body">
<h5 className="card-title">{title}</h5>
<p className="card-text">
{children}
</p>
</div>
</div>
)
}
export default class HelpDialog extends ReactBasicWidget {
get component() {
return <HelpDialogComponent />;
}
}

View File

@@ -1,180 +0,0 @@
import { escapeQuotes } from "../../services/utils.js";
import treeService from "../../services/tree.js";
import importService, { type UploadFilesOptions } from "../../services/import.js";
import options from "../../services/options.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import { Modal, Tooltip } from "bootstrap";
import type { EventData } from "../../components/app_context.js";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="import-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("import.importIntoNote")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("import.close")}"></button>
</div>
<form class="import-form">
<div class="modal-body">
<div class="form-group">
<label for="import-file-upload-input"><strong>${t("import.chooseImportFile")}</strong></label>
<label class="tn-file-input tn-input-field">
<input type="file" class="import-file-upload-input form-control-file" multiple />
</label>
<p>${t("import.importDescription")} <strong class="import-note-title"></strong>.
</div>
<div class="form-group">
<strong>${t("import.options")}:</strong>
<div class="checkbox">
<label class="tn-checkbox" data-bs-toggle="tooltip" title="${escapeQuotes(t("import.safeImportTooltip"))}">
<input class="safe-import-checkbox" value="1" type="checkbox" checked>
<span>${t("import.safeImport")}</span>
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox" data-bs-toggle="tooltip" title="${escapeQuotes(t("import.explodeArchivesTooltip"))}">
<input class="explode-archives-checkbox" value="1" type="checkbox" checked>
<span>${t("import.explodeArchives")}</span>
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox" data-bs-toggle="tooltip" title="${escapeQuotes(t("import.shrinkImagesTooltip"))}">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t("import.shrinkImages")}</span>
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox">
<input class="text-imported-as-text-checkbox" value="1" type="checkbox" checked>
${t("import.textImportedAsText")}
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox">
<input class="code-imported-as-code-checkbox" value="1" type="checkbox" checked> ${t("import.codeImportedAsCode")}
</label>
</div>
<div class="checkbox">
<label class="tn-checkbox">
<input class="replace-underscores-with-spaces-checkbox" value="1" type="checkbox" checked>
${t("import.replaceUnderscoresWithSpaces")}
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button class="import-button btn btn-primary">${t("import.import")}</button>
</div>
</form>
</div>
</div>
</div>`;
export default class ImportDialog extends BasicWidget {
private parentNoteId: string | null;
private $form!: JQuery<HTMLElement>;
private $noteTitle!: JQuery<HTMLElement>;
private $fileUploadInput!: JQuery<HTMLInputElement>;
private $importButton!: JQuery<HTMLElement>;
private $safeImportCheckbox!: JQuery<HTMLElement>;
private $shrinkImagesCheckbox!: JQuery<HTMLElement>;
private $textImportedAsTextCheckbox!: JQuery<HTMLElement>;
private $codeImportedAsCodeCheckbox!: JQuery<HTMLElement>;
private $explodeArchivesCheckbox!: JQuery<HTMLElement>;
private $replaceUnderscoresWithSpacesCheckbox!: JQuery<HTMLElement>;
constructor() {
super();
this.parentNoteId = null;
}
doRender() {
this.$widget = $(TPL);
Modal.getOrCreateInstance(this.$widget[0]);
this.$form = this.$widget.find(".import-form");
this.$noteTitle = this.$widget.find(".import-note-title");
this.$fileUploadInput = this.$widget.find(".import-file-upload-input");
this.$importButton = this.$widget.find(".import-button");
this.$safeImportCheckbox = this.$widget.find(".safe-import-checkbox");
this.$shrinkImagesCheckbox = this.$widget.find(".shrink-images-checkbox");
this.$textImportedAsTextCheckbox = this.$widget.find(".text-imported-as-text-checkbox");
this.$codeImportedAsCodeCheckbox = this.$widget.find(".code-imported-as-code-checkbox");
this.$explodeArchivesCheckbox = this.$widget.find(".explode-archives-checkbox");
this.$replaceUnderscoresWithSpacesCheckbox = this.$widget.find(".replace-underscores-with-spaces-checkbox");
this.$form.on("submit", () => {
// disabling so that import is not triggered again.
this.$importButton.attr("disabled", "disabled");
if (this.parentNoteId) {
this.importIntoNote(this.parentNoteId);
}
return false;
});
this.$fileUploadInput.on("change", () => {
if (this.$fileUploadInput.val()) {
this.$importButton.removeAttr("disabled");
} else {
this.$importButton.attr("disabled", "disabled");
}
});
let _ = [...this.$widget.find('[data-bs-toggle="tooltip"]')].forEach((element) => {
Tooltip.getOrCreateInstance(element, {
html: true
});
});
}
async showImportDialogEvent({ noteId }: EventData<"showImportDialog">) {
this.parentNoteId = noteId;
this.$fileUploadInput.val("").trigger("change"); // to trigger Import button disabling listener below
this.$safeImportCheckbox.prop("checked", true);
this.$shrinkImagesCheckbox.prop("checked", options.is("compressImages"));
this.$textImportedAsTextCheckbox.prop("checked", true);
this.$codeImportedAsCodeCheckbox.prop("checked", true);
this.$explodeArchivesCheckbox.prop("checked", true);
this.$replaceUnderscoresWithSpacesCheckbox.prop("checked", true);
this.$noteTitle.text(await treeService.getNoteTitle(this.parentNoteId));
openDialog(this.$widget);
}
async importIntoNote(parentNoteId: string) {
const files = Array.from(this.$fileUploadInput[0].files ?? []); // shallow copy since we're resetting the upload button below
const boolToString = ($el: JQuery<HTMLElement>) => ($el.is(":checked") ? "true" : "false");
const options: UploadFilesOptions = {
safeImport: boolToString(this.$safeImportCheckbox),
shrinkImages: boolToString(this.$shrinkImagesCheckbox),
textImportedAsText: boolToString(this.$textImportedAsTextCheckbox),
codeImportedAsCode: boolToString(this.$codeImportedAsCodeCheckbox),
explodeArchives: boolToString(this.$explodeArchivesCheckbox),
replaceUnderscoresWithSpaces: boolToString(this.$replaceUnderscoresWithSpacesCheckbox)
};
this.$widget.modal("hide");
await importService.uploadFiles("notes", parentNoteId, files, options);
}
}

View File

@@ -0,0 +1,102 @@
import { useState } from "preact/hooks";
import { t } from "../../services/i18n";
import tree from "../../services/tree";
import Button from "../react/Button";
import FormCheckbox from "../react/FormCheckbox";
import FormFileUpload from "../react/FormFileUpload";
import FormGroup from "../react/FormGroup";
import Modal from "../react/Modal";
import RawHtml from "../react/RawHtml";
import ReactBasicWidget from "../react/ReactBasicWidget";
import importService, { UploadFilesOptions } from "../../services/import";
import useTriliumEvent from "../react/hooks";
function ImportDialogComponent() {
const [ parentNoteId, setParentNoteId ] = useState<string>();
const [ noteTitle, setNoteTitle ] = useState<string>();
const [ files, setFiles ] = useState<FileList | null>(null);
const [ safeImport, setSafeImport ] = useState(true);
const [ explodeArchives, setExplodeArchives ] = useState(true);
const [ shrinkImages, setShrinkImages ] = useState(true);
const [ textImportedAsText, setTextImportedAsText ] = useState(true);
const [ codeImportedAsCode, setCodeImportedAsCode ] = useState(true);
const [ replaceUnderscoresWithSpaces, setReplaceUnderscoresWithSpaces ] = useState(true);
const [ shown, setShown ] = useState(false);
useTriliumEvent("showImportDialog", ({ noteId }) => {
setParentNoteId(noteId);
tree.getNoteTitle(noteId).then(setNoteTitle);
setShown(true);
});
return (
<Modal
className="import-dialog"
size="lg"
title={t("import.importIntoNote")}
onSubmit={async () => {
if (!files || !parentNoteId) {
return;
}
const options: UploadFilesOptions = {
safeImport: boolToString(safeImport),
shrinkImages: boolToString(shrinkImages),
textImportedAsText: boolToString(textImportedAsText),
codeImportedAsCode: boolToString(codeImportedAsCode),
explodeArchives: boolToString(explodeArchives),
replaceUnderscoresWithSpaces: boolToString(replaceUnderscoresWithSpaces)
};
setShown(false);
await importService.uploadFiles("notes", parentNoteId, Array.from(files), options);
}}
onHidden={() => setShown(false)}
footer={<Button text={t("import.import")} primary disabled={!files} />}
show={shown}
>
<FormGroup label={t("import.chooseImportFile")} description={<>{t("import.importDescription")} <strong>{ noteTitle }</strong></>}>
<FormFileUpload multiple onChange={setFiles} />
</FormGroup>
<FormGroup label={t("import.options")}>
<FormCheckbox
name="safe-import" hint={t("import.safeImportTooltip")} label={t("import.safeImport")}
currentValue={safeImport} onChange={setSafeImport}
/>
<FormCheckbox
name="explode-archives" hint={t("import.explodeArchivesTooltip")} label={<RawHtml html={t("import.explodeArchives")} />}
currentValue={explodeArchives} onChange={setExplodeArchives}
/>
<FormCheckbox
name="shrink-images" hint={t("import.shrinkImagesTooltip")} label={t("import.shrinkImages")}
currentValue={shrinkImages} onChange={setShrinkImages}
/>
<FormCheckbox
name="text-imported-as-text" label={t("import.textImportedAsText")}
currentValue={textImportedAsText} onChange={setTextImportedAsText}
/>
<FormCheckbox
name="code-imported-as-code" label={<RawHtml html={t("import.codeImportedAsCode")} />}
currentValue={codeImportedAsCode} onChange={setCodeImportedAsCode}
/>
<FormCheckbox
name="replace-underscores-with-spaces" label={t("import.replaceUnderscoresWithSpaces")}
currentValue={replaceUnderscoresWithSpaces} onChange={setReplaceUnderscoresWithSpaces}
/>
</FormGroup>
</Modal>
);
}
export default class ImportDialog extends ReactBasicWidget {
get component() {
return <ImportDialogComponent />
}
}
function boolToString(value: boolean) {
return value ? "true" : "false";
}

View File

@@ -1,116 +0,0 @@
import { t } from "../../services/i18n.js";
import treeService from "../../services/tree.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import froca from "../../services/froca.js";
import BasicWidget from "../basic_widget.js";
import { Modal } from "bootstrap";
import type { EventData } from "../../components/app_context.js";
import type EditableTextTypeWidget from "../type_widgets/editable_text.js";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="include-note-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("include_note.dialog_title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("include_note.close")}"></button>
</div>
<form class="include-note-form">
<div class="modal-body">
<div class="form-group">
<label for="include-note-autocomplete">${t("include_note.label_note")}</label>
<div class="input-group">
<input class="include-note-autocomplete form-control" placeholder="${t("include_note.placeholder_search")}">
</div>
</div>
${t("include_note.box_size_prompt")}
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
${t("include_note.box_size_small")}
</label>
</div>
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
${t("include_note.box_size_medium")}
</label>
</div>
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
${t("include_note.box_size_full")}
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">${t("include_note.button_include")}</button>
</div>
</form>
</div>
</div>
</div>`;
export default class IncludeNoteDialog extends BasicWidget {
private modal!: bootstrap.Modal;
private $form!: JQuery<HTMLElement>;
private $autoComplete!: JQuery<HTMLElement>;
private textTypeWidget?: EditableTextTypeWidget;
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$form = this.$widget.find(".include-note-form");
this.$autoComplete = this.$widget.find(".include-note-autocomplete");
this.$form.on("submit", () => {
const notePath = this.$autoComplete.getSelectedNotePath();
if (notePath) {
this.modal.hide();
this.includeNote(notePath);
} else {
logError("No noteId to include.");
}
return false;
});
}
async showIncludeNoteDialogEvent({ textTypeWidget }: EventData<"showIncludeDialog">) {
this.textTypeWidget = textTypeWidget;
await this.refresh();
openDialog(this.$widget);
this.$autoComplete.trigger("focus").trigger("select"); // to be able to quickly remove entered text
}
async refresh() {
this.$autoComplete.val("");
noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, {
hideGoToSelectedNoteButton: true,
allowCreatingNotes: true
});
noteAutocompleteService.showRecentNotes(this.$autoComplete);
}
async includeNote(notePath: string) {
const noteId = treeService.getNoteIdFromUrl(notePath);
if (!noteId) {
return;
}
const note = await froca.getNote(noteId);
const boxSize = $("input[name='include-note-box-size']:checked").val() as string;
if (["image", "canvas", "mermaid"].includes(note?.type ?? "")) {
// there's no benefit to use insert note functionlity for images,
// so we'll just add an IMG tag
this.textTypeWidget?.addImage(noteId);
} else {
this.textTypeWidget?.addIncludeNote(noteId, boxSize);
}
}
}

View File

@@ -0,0 +1,95 @@
import { useRef, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import FormGroup from "../react/FormGroup";
import FormRadioGroup from "../react/FormRadioGroup";
import Modal from "../react/Modal";
import NoteAutocomplete from "../react/NoteAutocomplete";
import ReactBasicWidget from "../react/ReactBasicWidget";
import Button from "../react/Button";
import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete";
import tree from "../../services/tree";
import froca from "../../services/froca";
import EditableTextTypeWidget from "../type_widgets/editable_text";
import useTriliumEvent from "../react/hooks";
function IncludeNoteDialogComponent() {
const [textTypeWidget, setTextTypeWidget] = useState<EditableTextTypeWidget>();
const [suggestion, setSuggestion] = useState<Suggestion | null>(null);
const [boxSize, setBoxSize] = useState("medium");
const [shown, setShown] = useState(false);
useTriliumEvent("showIncludeNoteDialog", ({ textTypeWidget }) => {
setTextTypeWidget(textTypeWidget);
setShown(true);
});
const autoCompleteRef = useRef<HTMLInputElement>(null);
return (
<Modal
className="include-note-dialog"
title={t("include_note.dialog_title")}
size="lg"
onShown={() => triggerRecentNotes(autoCompleteRef.current)}
onHidden={() => setShown(false)}
onSubmit={() => {
if (!suggestion?.notePath || !textTypeWidget) {
return;
}
setShown(false);
includeNote(suggestion.notePath, textTypeWidget);
}}
footer={<Button text={t("include_note.button_include")} keyboardShortcut="Enter" />}
show={shown}
>
<FormGroup label={t("include_note.label_note")}>
<NoteAutocomplete
placeholder={t("include_note.placeholder_search")}
onChange={setSuggestion}
inputRef={autoCompleteRef}
opts={{
hideGoToSelectedNoteButton: true,
allowCreatingNotes: true
}}
/>
</FormGroup>
<FormGroup label={t("include_note.box_size_prompt")}>
<FormRadioGroup name="include-note-box-size"
currentValue={boxSize} onChange={setBoxSize}
values={[
{ label: t("include_note.box_size_small"), value: "small" },
{ label: t("include_note.box_size_medium"), value: "medium" },
{ label: t("include_note.box_size_full"), value: "full" },
]}
/>
</FormGroup>
</Modal>
)
}
export default class IncludeNoteDialog extends ReactBasicWidget {
get component() {
return <IncludeNoteDialogComponent />;
}
}
async function includeNote(notePath: string, textTypeWidget: EditableTextTypeWidget) {
const noteId = tree.getNoteIdFromUrl(notePath);
if (!noteId) {
return;
}
const note = await froca.getNote(noteId);
const boxSize = $("input[name='include-note-box-size']:checked").val() as string;
if (["image", "canvas", "mermaid"].includes(note?.type ?? "")) {
// there's no benefit to use insert note functionlity for images,
// so we'll just add an IMG tag
textTypeWidget.addImage(noteId);
} else {
textTypeWidget.addIncludeNote(noteId, boxSize);
}
}

View File

@@ -1,59 +0,0 @@
import BasicWidget from "../basic_widget.js";
import { Modal } from "bootstrap";
import utils from "../../services/utils.js";
import { t } from "../../services/i18n.js";
const TPL = /*html*/`
<div class="cpu-arch-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("cpu_arch_warning.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>${utils.isMac() ? t("cpu_arch_warning.message_macos") : t("cpu_arch_warning.message_windows")}</p>
<p>${t("cpu_arch_warning.recommendation")}</p>
</div>
<div class="modal-footer d-flex justify-content-between align-items-center">
<button class="download-correct-version-button btn btn-primary btn-lg me-2">
<span class="bx bx-download"></span>
${t("cpu_arch_warning.download_link")}
</button>
<button class="btn btn-secondary" data-bs-dismiss="modal">${t("cpu_arch_warning.continue_anyway")}</button>
</div>
</div>
</div>
</div>`;
export default class IncorrectCpuArchDialog extends BasicWidget {
private modal!: Modal;
private $downloadButton!: JQuery<HTMLElement>;
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$downloadButton = this.$widget.find(".download-correct-version-button");
this.$downloadButton.on("click", () => {
// Open the releases page where users can download the correct version
if (utils.isElectron()) {
const { shell } = utils.dynamicRequire("electron");
shell.openExternal("https://github.com/TriliumNext/Trilium/releases/latest");
} else {
window.open("https://github.com/TriliumNext/Trilium/releases/latest", "_blank");
}
});
// Auto-focus the download button when shown
this.$widget.on("shown.bs.modal", () => {
this.$downloadButton.trigger("focus");
});
}
showCpuArchWarningEvent() {
this.modal.show();
}
}

View File

@@ -0,0 +1,54 @@
import { useRef } from "preact/hooks";
import { t } from "../../services/i18n.js";
import utils from "../../services/utils.js";
import Button from "../react/Button.js";
import Modal from "../react/Modal.js";
import ReactBasicWidget from "../react/ReactBasicWidget.js";
import { useState } from "preact/hooks";
import useTriliumEvent from "../react/hooks.jsx";
function IncorrectCpuArchDialogComponent() {
const [ shown, setShown ] = useState(false);
const downloadButtonRef = useRef<HTMLButtonElement>(null);
useTriliumEvent("showCpuArchWarning", () => setShown(true));
return (
<Modal
className="cpu-arch-dialog"
size="lg"
title={t("cpu_arch_warning.title")}
onShown={() => downloadButtonRef.current?.focus()}
footerAlignment="between"
footer={<>
<Button
buttonRef={downloadButtonRef}
text={t("cpu_arch_warning.download_link")}
icon="bx bx-download"
onClick={() => {
// Open the releases page where users can download the correct version
if (utils.isElectron()) {
const { shell } = utils.dynamicRequire("electron");
shell.openExternal("https://github.com/TriliumNext/Trilium/releases/latest");
} else {
window.open("https://github.com/TriliumNext/Trilium/releases/latest", "_blank");
}
}}/>
<Button text={t("cpu_arch_warning.continue_anyway")}
onClick={() => setShown(false)} />
</>}
onHidden={() => setShown(false)}
show={shown}
>
<p>{utils.isMac() ? t("cpu_arch_warning.message_macos") : t("cpu_arch_warning.message_windows")}</p>
<p>{t("cpu_arch_warning.recommendation")}</p>
</Modal>
)
}
export default class IncorrectCpuArchDialog extends ReactBasicWidget {
get component() {
return <IncorrectCpuArchDialogComponent />
}
}

View File

@@ -1,79 +0,0 @@
import type { EventData } from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
import { Modal } from "bootstrap";
import type { ConfirmDialogCallback } from "./confirm.js";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="info-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("info.modalTitle")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("info.closeButton")}"></button>
</div>
<div class="modal-body">
<div class="info-dialog-content"></div>
</div>
<div class="modal-footer">
<button class="info-dialog-ok-button btn btn-primary btn-sm">${t("info.okButton")}</button>
</div>
</div>
</div>
</div>`;
export default class InfoDialog extends BasicWidget {
private resolve: ConfirmDialogCallback | null;
private modal!: bootstrap.Modal;
private $originallyFocused!: JQuery<HTMLElement> | null;
private $infoContent!: JQuery<HTMLElement>;
private $okButton!: JQuery<HTMLElement>;
constructor() {
super();
this.resolve = null;
this.$originallyFocused = null; // element focused before the dialog was opened, so we can return to it afterward
}
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$infoContent = this.$widget.find(".info-dialog-content");
this.$okButton = this.$widget.find(".info-dialog-ok-button");
this.$widget.on("shown.bs.modal", () => this.$okButton.trigger("focus"));
this.$widget.on("hidden.bs.modal", () => {
if (this.resolve) {
this.resolve();
}
if (this.$originallyFocused) {
this.$originallyFocused.trigger("focus");
this.$originallyFocused = null;
}
});
this.$okButton.on("click", () => this.modal.hide());
}
showInfoDialogEvent({ message, callback }: EventData<"showInfoDialog">) {
this.$originallyFocused = $(":focus");
if (typeof message === "string") {
this.$infoContent.text(message);
} else if (Array.isArray(message)) {
this.$infoContent.html(message[0]);
} else {
this.$infoContent.html(message as HTMLElement);
}
openDialog(this.$widget);
this.resolve = callback;
}
}

View File

@@ -0,0 +1,47 @@
import { EventData } from "../../components/app_context";
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import { t } from "../../services/i18n";
import Button from "../react/Button";
import { useRef, useState } from "preact/hooks";
import { RawHtmlBlock } from "../react/RawHtml";
import useTriliumEvent from "../react/hooks";
function ShowInfoDialogComponent() {
const [ opts, setOpts ] = useState<EventData<"showInfoDialog">>();
const [ shown, setShown ] = useState(false);
const okButtonRef = useRef<HTMLButtonElement>(null);
useTriliumEvent("showInfoDialog", (opts) => {
setOpts(opts);
setShown(true);
});
return (<Modal
className="info-dialog"
size="sm"
title={t("info.modalTitle")}
onHidden={() => {
opts?.callback?.();
setShown(false);
}}
onShown={() => okButtonRef.current?.focus?.()}
footer={<Button
buttonRef={okButtonRef}
text={t("info.okButton")}
onClick={() => setShown(false)}
/>}
show={shown}
stackable
>
<RawHtmlBlock className="info-dialog-content" html={opts?.message ?? ""} />
</Modal>);
}
export default class InfoDialog extends ReactBasicWidget {
get component() {
return <ShowInfoDialogComponent />;
}
}

View File

@@ -1,205 +0,0 @@
import { t } from "../../services/i18n.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import utils from "../../services/utils.js";
import appContext from "../../components/app_context.js";
import BasicWidget from "../basic_widget.js";
import shortcutService from "../../services/shortcuts.js";
import { Modal } from "bootstrap";
import { openDialog } from "../../services/dialog.js";
import commandRegistry from "../../services/command_registry.js";
const TPL = /*html*/`<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="input-group">
<input class="jump-to-note-autocomplete form-control" placeholder="${t("jump_to_note.search_placeholder")}">
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("jump_to_note.close")}"></button>
</div>
<div class="modal-body">
<div class="algolia-autocomplete-container jump-to-note-results"></div>
</div>
<div class="modal-footer">
<button class="show-in-full-text-button btn btn-sm">${t("jump_to_note.search_button")}</button>
</div>
</div>
</div>
</div>`;
const KEEP_LAST_SEARCH_FOR_X_SECONDS = 120;
export default class JumpToNoteDialog extends BasicWidget {
private lastOpenedTs: number;
private modal!: bootstrap.Modal;
private $autoComplete!: JQuery<HTMLElement>;
private $results!: JQuery<HTMLElement>;
private $modalFooter!: JQuery<HTMLElement>;
private isCommandMode: boolean = false;
constructor() {
super();
this.lastOpenedTs = 0;
}
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$autoComplete = this.$widget.find(".jump-to-note-autocomplete");
this.$results = this.$widget.find(".jump-to-note-results");
this.$modalFooter = this.$widget.find(".modal-footer");
this.$modalFooter.find(".show-in-full-text-button").on("click", (e) => this.showInFullText(e));
shortcutService.bindElShortcut(this.$widget, "ctrl+return", (e) => this.showInFullText(e));
// Monitor input changes to detect command mode switches
this.$autoComplete.on("input", () => {
this.updateCommandModeState();
});
}
private updateCommandModeState() {
const currentValue = String(this.$autoComplete.val() || "");
const newCommandMode = currentValue.startsWith(">");
if (newCommandMode !== this.isCommandMode) {
this.isCommandMode = newCommandMode;
this.updateButtonVisibility();
}
}
private updateButtonVisibility() {
if (this.isCommandMode) {
this.$modalFooter.hide();
} else {
this.$modalFooter.show();
}
}
async jumpToNoteEvent() {
await this.openDialog();
}
async commandPaletteEvent() {
await this.openDialog(true);
}
private async openDialog(commandMode = false) {
const dialogPromise = openDialog(this.$widget);
if (utils.isMobile()) {
dialogPromise.then(($dialog) => {
const el = $dialog.find(">.modal-dialog")[0];
function reposition() {
const offset = 100;
const modalHeight = (window.visualViewport?.height ?? 0) - offset;
const safeAreaInsetBottom = (window.visualViewport?.height ?? 0) - window.innerHeight;
el.style.height = `${modalHeight}px`;
el.style.bottom = `${(window.visualViewport?.height ?? 0) - modalHeight - safeAreaInsetBottom - offset}px`;
}
this.$autoComplete.on("focus", () => {
reposition();
});
window.visualViewport?.addEventListener("resize", () => {
reposition();
});
reposition();
});
}
// first open dialog, then refresh since refresh is doing focus which should be visible
this.refresh(commandMode);
this.lastOpenedTs = Date.now();
}
async refresh(commandMode = false) {
noteAutocompleteService
.initNoteAutocomplete(this.$autoComplete, {
allowCreatingNotes: true,
hideGoToSelectedNoteButton: true,
allowJumpToSearchNotes: true,
container: this.$results[0],
isCommandPalette: true
})
// clear any event listener added in previous invocation of this function
.off("autocomplete:noteselected")
.off("autocomplete:commandselected")
.on("autocomplete:noteselected", function (event, suggestion, dataset) {
if (!suggestion.notePath) {
return false;
}
appContext.tabManager.getActiveContext()?.setNote(suggestion.notePath);
})
.on("autocomplete:commandselected", async (event, suggestion, dataset) => {
if (!suggestion.commandId) {
return false;
}
this.modal.hide();
await commandRegistry.executeCommand(suggestion.commandId);
});
if (commandMode) {
// Start in command mode - manually trigger command search
this.$autoComplete.autocomplete("val", ">");
this.isCommandMode = true;
this.updateButtonVisibility();
// Manually populate with all commands immediately
noteAutocompleteService.showAllCommands(this.$autoComplete);
this.$autoComplete.trigger("focus");
} else {
// if you open the Jump To dialog soon after using it previously, it can often mean that you
// actually want to search for the same thing (e.g., you opened the wrong note at first try)
// so we'll keep the content.
// if it's outside of this time limit, then we assume it's a completely new search and show recent notes instead.
if (Date.now() - this.lastOpenedTs > KEEP_LAST_SEARCH_FOR_X_SECONDS * 1000) {
this.isCommandMode = false;
this.updateButtonVisibility();
noteAutocompleteService.showRecentNotes(this.$autoComplete);
} else {
this.$autoComplete
// hack, the actual search value is stored in <pre> element next to the search input
// this is important because the search input value is replaced with the suggestion note's title
.autocomplete("val", this.$autoComplete.next().text())
.trigger("focus")
.trigger("select");
// Update command mode state based on the restored value
this.updateCommandModeState();
// If we restored a command mode value, manually trigger command display
if (this.isCommandMode) {
// Clear the value first, then set it to ">" to trigger a proper change
this.$autoComplete.autocomplete("val", "");
noteAutocompleteService.showAllCommands(this.$autoComplete);
}
}
}
}
showInFullText(e: JQuery.TriggeredEvent | KeyboardEvent) {
// stop from propagating upwards (dangerous, especially with ctrl+enter executable javascript notes)
e.preventDefault();
e.stopPropagation();
// Don't perform full text search in command mode
if (this.isCommandMode) {
return;
}
const searchString = String(this.$autoComplete.val());
this.triggerCommand("searchNotes", { searchString });
this.modal.hide();
}
}

View File

@@ -0,0 +1,125 @@
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import Button from "../react/Button";
import NoteAutocomplete from "../react/NoteAutocomplete";
import { t } from "../../services/i18n";
import { useRef, useState } from "preact/hooks";
import note_autocomplete, { Suggestion } from "../../services/note_autocomplete";
import appContext from "../../components/app_context";
import commandRegistry from "../../services/command_registry";
import { refToJQuerySelector } from "../react/react_utils";
import useTriliumEvent from "../react/hooks";
const KEEP_LAST_SEARCH_FOR_X_SECONDS = 120;
type Mode = "last-search" | "recent-notes" | "commands";
function JumpToNoteDialogComponent() {
const [ mode, setMode ] = useState<Mode>();
const [ lastOpenedTs, setLastOpenedTs ] = useState<number>(0);
const containerRef = useRef<HTMLDivElement>(null);
const autocompleteRef = useRef<HTMLInputElement>(null);
const [ isCommandMode, setIsCommandMode ] = useState(mode === "commands");
const [ initialText, setInitialText ] = useState(isCommandMode ? "> " : "");
const actualText = useRef<string>(initialText);
const [ shown, setShown ] = useState(false);
async function openDialog(commandMode: boolean) {
let newMode: Mode;
let initialText: string = "";
if (commandMode) {
newMode = "commands";
initialText = ">";
} else if (Date.now() - lastOpenedTs <= KEEP_LAST_SEARCH_FOR_X_SECONDS * 1000 && actualText) {
// if you open the Jump To dialog soon after using it previously, it can often mean that you
// actually want to search for the same thing (e.g., you opened the wrong note at first try)
// so we'll keep the content.
// if it's outside of this time limit, then we assume it's a completely new search and show recent notes instead.
newMode = "last-search";
initialText = actualText.current;
} else {
newMode = "recent-notes";
}
if (mode !== newMode) {
setMode(newMode);
}
setInitialText(initialText);
setShown(true);
setLastOpenedTs(Date.now());
}
useTriliumEvent("jumpToNote", () => openDialog(false));
useTriliumEvent("commandPalette", () => openDialog(true));
async function onItemSelected(suggestion?: Suggestion | null) {
if (!suggestion) {
return;
}
setShown(false);
if (suggestion.notePath) {
appContext.tabManager.getActiveContext()?.setNote(suggestion.notePath);
} else if (suggestion.commandId) {
await commandRegistry.executeCommand(suggestion.commandId);
}
}
function onShown() {
const $autoComplete = refToJQuerySelector(autocompleteRef);
switch (mode) {
case "last-search":
break;
case "recent-notes":
note_autocomplete.showRecentNotes($autoComplete);
break;
case "commands":
note_autocomplete.showAllCommands($autoComplete);
break;
}
$autoComplete
.trigger("focus")
.trigger("select");
}
return (
<Modal
className="jump-to-note-dialog"
size="lg"
title={<NoteAutocomplete
placeholder={t("jump_to_note.search_placeholder")}
inputRef={autocompleteRef}
container={containerRef}
text={initialText}
opts={{
allowCreatingNotes: true,
hideGoToSelectedNoteButton: true,
allowJumpToSearchNotes: true,
isCommandPalette: true
}}
onTextChange={(text) => {
actualText.current = text;
setIsCommandMode(text.startsWith(">"));
}}
onChange={onItemSelected}
/>}
onShown={onShown}
onHidden={() => setShown(false)}
footer={!isCommandMode && <Button className="show-in-full-text-button" text={t("jump_to_note.search_button")} keyboardShortcut="Ctrl+Enter" />}
show={shown}
>
<div className="algolia-autocomplete-container jump-to-note-results" ref={containerRef}></div>
</Modal>
);
}
export default class JumpToNoteDialog extends ReactBasicWidget {
get component() {
return <JumpToNoteDialogComponent />;
}
}

View File

@@ -1,106 +0,0 @@
import { t } from "../../services/i18n.js";
import toastService from "../../services/toast.js";
import utils from "../../services/utils.js";
import appContext from "../../components/app_context.js";
import BasicWidget from "../basic_widget.js";
import shortcutService from "../../services/shortcuts.js";
import server from "../../services/server.js";
import { Modal } from "bootstrap";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="markdown-import-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("markdown_import.dialog_title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("markdown_import.close")}"></button>
</div>
<div class="modal-body">
<p>${t("markdown_import.modal_body_text")}</p>
<textarea class="markdown-import-textarea" style="height: 340px; width: 100%"></textarea>
</div>
<div class="modal-footer">
<button class="markdown-import-button btn btn-primary">${t("markdown_import.import_button")}</button>
</div>
</div>
</div>
</div>`;
interface RenderMarkdownResponse {
htmlContent: string;
}
export default class MarkdownImportDialog extends BasicWidget {
private lastOpenedTs: number;
private modal!: bootstrap.Modal;
private $importTextarea!: JQuery<HTMLElement>;
private $importButton!: JQuery<HTMLElement>;
constructor() {
super();
this.lastOpenedTs = 0;
}
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$importTextarea = this.$widget.find(".markdown-import-textarea");
this.$importButton = this.$widget.find(".markdown-import-button");
this.$importButton.on("click", () => this.sendForm());
this.$widget.on("shown.bs.modal", () => this.$importTextarea.trigger("focus"));
shortcutService.bindElShortcut(this.$widget, "ctrl+return", () => this.sendForm());
}
async convertMarkdownToHtml(markdownContent: string) {
const { htmlContent } = await server.post<RenderMarkdownResponse>("other/render-markdown", { markdownContent });
const textEditor = await appContext.tabManager.getActiveContext()?.getTextEditor();
if (!textEditor) {
return;
}
const viewFragment = textEditor.data.processor.toView(htmlContent);
const modelFragment = textEditor.data.toModel(viewFragment);
textEditor.model.insertContent(modelFragment, textEditor.model.document.selection);
textEditor.editing.view.focus();
toastService.showMessage(t("markdown_import.import_success"));
}
async pasteMarkdownIntoTextEvent() {
await this.importMarkdownInlineEvent(); // BC with keyboard shortcuts command
}
async importMarkdownInlineEvent() {
if (appContext.tabManager.getActiveContextNoteType() !== "text") {
return;
}
if (utils.isElectron()) {
const { clipboard } = utils.dynamicRequire("electron");
const text = clipboard.readText();
this.convertMarkdownToHtml(text);
} else {
openDialog(this.$widget);
}
}
async sendForm() {
const text = String(this.$importTextarea.val());
this.modal.hide();
await this.convertMarkdownToHtml(text);
this.$importTextarea.val("");
}
}

View File

@@ -0,0 +1,90 @@
import { useCallback, useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context";
import { t } from "../../services/i18n";
import server from "../../services/server";
import toast from "../../services/toast";
import utils from "../../services/utils";
import Modal from "../react/Modal";
import ReactBasicWidget from "../react/ReactBasicWidget";
import Button from "../react/Button";
import useTriliumEvent from "../react/hooks";
interface RenderMarkdownResponse {
htmlContent: string;
}
function MarkdownImportDialogComponent() {
const markdownImportTextArea = useRef<HTMLTextAreaElement>(null);
let [ text, setText ] = useState("");
let [ shown, setShown ] = useState(false);
const triggerImport = useCallback(() => {
if (appContext.tabManager.getActiveContextNoteType() !== "text") {
return;
}
if (utils.isElectron()) {
const { clipboard } = utils.dynamicRequire("electron");
const text = clipboard.readText();
convertMarkdownToHtml(text);
} else {
setShown(true);
}
}, []);
useTriliumEvent("importMarkdownInline", triggerImport);
useTriliumEvent("pasteMarkdownIntoText", triggerImport);
async function sendForm() {
await convertMarkdownToHtml(text);
setText("");
setShown(false);
}
return (
<Modal
className="markdown-import-dialog" title={t("markdown_import.dialog_title")} size="lg"
footer={<Button className="markdown-import-button" text={t("markdown_import.import_button")} onClick={sendForm} keyboardShortcut="Ctrl+Space" />}
onShown={() => markdownImportTextArea.current?.focus()}
onHidden={() => setShown(false) }
show={shown}
>
<p>{t("markdown_import.modal_body_text")}</p>
<textarea ref={markdownImportTextArea} value={text}
onInput={(e) => setText(e.currentTarget.value)}
style={{ height: 340, width: "100%" }}
onKeyDown={(e) => {
if (e.key === "Enter" && e.ctrlKey) {
e.preventDefault();
sendForm();
}
}}></textarea>
</Modal>
)
}
export default class MarkdownImportDialog extends ReactBasicWidget {
get component() {
return <MarkdownImportDialogComponent />;
}
}
async function convertMarkdownToHtml(markdownContent: string) {
const { htmlContent } = await server.post<RenderMarkdownResponse>("other/render-markdown", { markdownContent });
const textEditor = await appContext.tabManager.getActiveContext()?.getTextEditor();
if (!textEditor) {
return;
}
const viewFragment = textEditor.data.processor.toView(htmlContent);
const modelFragment = textEditor.data.toModel(viewFragment);
textEditor.model.insertContent(modelFragment, textEditor.model.document.selection);
textEditor.editing.view.focus();
toast.showMessage(t("markdown_import.import_success"));
}

View File

@@ -1,120 +0,0 @@
import noteAutocompleteService from "../../services/note_autocomplete.js";
import toastService from "../../services/toast.js";
import froca from "../../services/froca.js";
import branchService from "../../services/branches.js";
import treeService from "../../services/tree.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import type { EventData } from "../../components/app_context.js";
import { openDialog } from "../../services/dialog.js";
const TPL = /*html*/`
<div class="move-to-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title me-auto">${t("move_to.dialog_title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("move_to.close")}"></button>
</div>
<form class="move-to-form">
<div class="modal-body">
<h5>${t("move_to.notes_to_move")}</h5>
<ul class="move-to-note-list" style="max-height: 200px; overflow: auto;"></ul>
<div class="form-group">
<label style="width: 100%">
${t("move_to.target_parent_note")}
<div class="input-group">
<input class="move-to-note-autocomplete form-control" placeholder="${t("move_to.search_placeholder")}">
</div>
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">${t("move_to.move_button")}</button>
</div>
</form>
</div>
</div>
</div>`;
export default class MoveToDialog extends BasicWidget {
private movedBranchIds: string[] | null;
private $form!: JQuery<HTMLElement>;
private $noteAutoComplete!: JQuery<HTMLElement>;
private $noteList!: JQuery<HTMLElement>;
constructor() {
super();
this.movedBranchIds = null;
}
doRender() {
this.$widget = $(TPL);
this.$form = this.$widget.find(".move-to-form");
this.$noteAutoComplete = this.$widget.find(".move-to-note-autocomplete");
this.$noteList = this.$widget.find(".move-to-note-list");
this.$form.on("submit", () => {
const notePath = this.$noteAutoComplete.getSelectedNotePath();
if (notePath) {
this.$widget.modal("hide");
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
if (parentNoteId) {
froca.getBranchId(parentNoteId, noteId).then((branchId) => {
if (branchId) {
this.moveNotesTo(branchId);
}
});
}
} else {
logError(t("move_to.error_no_path"));
}
return false;
});
}
async moveBranchIdsToEvent({ branchIds }: EventData<"moveBranchIdsTo">) {
this.movedBranchIds = branchIds;
openDialog(this.$widget);
this.$noteAutoComplete.val("").trigger("focus");
this.$noteList.empty();
for (const branchId of this.movedBranchIds) {
const branch = froca.getBranch(branchId);
if (!branch) {
continue;
}
const note = await froca.getNote(branch.noteId);
if (!note) {
continue;
}
this.$noteList.append($("<li>").text(note.title));
}
noteAutocompleteService.initNoteAutocomplete(this.$noteAutoComplete);
noteAutocompleteService.showRecentNotes(this.$noteAutoComplete);
}
async moveNotesTo(parentBranchId: string) {
if (this.movedBranchIds) {
await branchService.moveToParentNote(this.movedBranchIds, parentBranchId);
}
const parentBranch = froca.getBranch(parentBranchId);
const parentNote = await parentBranch?.getNote();
toastService.showMessage(`${t("move_to.move_success_message")} ${parentNote?.title}`);
}
}

View File

@@ -0,0 +1,87 @@
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import { t } from "../../services/i18n";
import NoteList from "../react/NoteList";
import FormGroup from "../react/FormGroup";
import NoteAutocomplete from "../react/NoteAutocomplete";
import Button from "../react/Button";
import { useRef, useState } from "preact/hooks";
import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete";
import tree from "../../services/tree";
import froca from "../../services/froca";
import branches from "../../services/branches";
import toast from "../../services/toast";
import useTriliumEvent from "../react/hooks";
function MoveToDialogComponent() {
const [ movedBranchIds, setMovedBranchIds ] = useState<string[]>();
const [ suggestion, setSuggestion ] = useState<Suggestion | null>(null);
const [ shown, setShown ] = useState(false);
const autoCompleteRef = useRef<HTMLInputElement>(null);
useTriliumEvent("moveBranchIdsTo", ({ branchIds }) => {
setMovedBranchIds(branchIds);
setShown(true);
});
async function onSubmit() {
const notePath = suggestion?.notePath;
if (!notePath) {
logError(t("move_to.error_no_path"));
return;
}
setShown(false);
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
if (!parentNoteId) {
return;
}
const branchId = await froca.getBranchId(parentNoteId, noteId);
if (branchId) {
moveNotesTo(movedBranchIds, branchId);
}
}
return (
<Modal
className="move-to-dialog"
size="lg" maxWidth={1000}
title={t("move_to.dialog_title")}
footer={<Button text={t("move_to.move_button")} keyboardShortcut="Enter" />}
onSubmit={onSubmit}
onShown={() => triggerRecentNotes(autoCompleteRef.current)}
onHidden={() => setShown(false)}
show={shown}
>
<h5>{t("move_to.notes_to_move")}</h5>
<NoteList branchIds={movedBranchIds} />
<FormGroup label={t("move_to.target_parent_note")}>
<NoteAutocomplete
onChange={setSuggestion}
inputRef={autoCompleteRef}
/>
</FormGroup>
</Modal>
)
}
export default class MoveToDialog extends ReactBasicWidget {
get component() {
return <MoveToDialogComponent />;
}
}
async function moveNotesTo(movedBranchIds: string[] | undefined, parentBranchId: string) {
if (movedBranchIds) {
await branches.moveToParentNote(movedBranchIds, parentBranchId);
}
const parentBranch = froca.getBranch(parentBranchId);
const parentNote = await parentBranch?.getNote();
toast.showMessage(`${t("move_to.move_success_message")} ${parentNote?.title}`);
}

View File

@@ -1,204 +0,0 @@
import type { CommandNames } from "../../components/app_context.js";
import type { MenuCommandItem } from "../../menus/context_menu.js";
import { t } from "../../services/i18n.js";
import noteTypesService from "../../services/note_types.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
import BasicWidget from "../basic_widget.js";
import { Dropdown, Modal } from "bootstrap";
const TPL = /*html*/`
<div class="note-type-chooser-dialog modal mx-auto" tabindex="-1" role="dialog">
<style>
.note-type-chooser-dialog {
/* note type chooser needs to be higher than other dialogs from which it is triggered, e.g. "add link"*/
z-index: 1100 !important;
}
.note-type-chooser-dialog .input-group {
margin-top: 15px;
margin-bottom: 15px;
}
.note-type-chooser-dialog .note-type-dropdown {
position: relative;
font-size: large;
padding: 20px;
width: 100%;
margin-top: 15px;
max-height: 80vh;
overflow: auto;
}
</style>
<div class="modal-dialog" style="max-width: 500px;" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("note_type_chooser.modal_title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("note_type_chooser.close")}"></button>
</div>
<div class="modal-body">
${t("note_type_chooser.change_path_prompt")}
<div class="input-group">
<input class="choose-note-path form-control" placeholder="${t("note_type_chooser.search_placeholder")}">
</div>
${t("note_type_chooser.modal_body")}
<div class="dropdown" style="display: flex;">
<button class="note-type-dropdown-trigger" type="button" style="display: none;"
data-bs-toggle="dropdown" data-bs-display="static">
</button>
<div class="note-type-dropdown dropdown-menu static"></div>
</div>
</div>
</div>
</div>
</div>`;
export interface ChooseNoteTypeResponse {
success: boolean;
noteType?: string;
templateNoteId?: string;
notePath?: string;
}
type Callback = (data: ChooseNoteTypeResponse) => void;
export default class NoteTypeChooserDialog extends BasicWidget {
private resolve: Callback | null;
private dropdown!: Dropdown;
private modal!: Modal;
private $noteTypeDropdown!: JQuery<HTMLElement>;
private $autoComplete!: JQuery<HTMLElement>;
private $originalFocused: JQuery<HTMLElement> | null;
private $originalDialog: JQuery<HTMLElement> | null;
constructor() {
super();
this.resolve = null;
this.$originalFocused = null; // element focused before the dialog was opened, so we can return to it afterward
this.$originalDialog = null;
}
doRender() {
this.$widget = $(TPL);
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
this.$autoComplete = this.$widget.find(".choose-note-path");
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find(".note-type-dropdown-trigger")[0]);
this.$widget.on("hidden.bs.modal", () => {
if (this.resolve) {
this.resolve({ success: false });
}
if (this.$originalFocused) {
this.$originalFocused.trigger("focus");
this.$originalFocused = null;
}
glob.activeDialog = this.$originalDialog;
});
this.$noteTypeDropdown.on("click", ".dropdown-item", (e) => this.doResolve(e));
this.$noteTypeDropdown.on("focus", ".dropdown-item", (e) => {
this.$noteTypeDropdown.find(".dropdown-item").each((i, el) => {
$(el).toggleClass("active", el === e.target);
});
});
this.$noteTypeDropdown.on("keydown", ".dropdown-item", (e) => {
if (e.key === "Enter") {
this.doResolve(e);
e.preventDefault();
return false;
}
});
this.$noteTypeDropdown.parent().on("hide.bs.dropdown", (e) => {
// prevent closing dropdown by clicking outside
// TODO: Check if this actually works.
//@ts-ignore
if (e.clickEvent) {
e.preventDefault();
} else {
this.modal.hide();
}
});
}
async refresh() {
noteAutocompleteService
.initNoteAutocomplete(this.$autoComplete, {
allowCreatingNotes: false,
hideGoToSelectedNoteButton: true,
allowJumpToSearchNotes: false,
})
}
async chooseNoteTypeEvent({ callback }: { callback: Callback }) {
this.$originalFocused = $(":focus");
await this.refresh();
const noteTypes = await noteTypesService.getNoteTypeItems();
this.$noteTypeDropdown.empty();
for (const noteType of noteTypes) {
if (noteType.title === "----") {
this.$noteTypeDropdown.append($('<h6 class="dropdown-header">').append(t("note_type_chooser.templates")));
} else {
const commandItem = noteType as MenuCommandItem<CommandNames>;
const listItem = $('<a class="dropdown-item" tabindex="0">')
.attr("data-note-type", commandItem.type || "")
.attr("data-template-note-id", commandItem.templateNoteId || "")
.append($("<span>").addClass(commandItem.uiIcon || ""))
.append(` ${noteType.title}`);
if (commandItem.badges) {
for (let badge of commandItem.badges) {
listItem.append($(`<span class="badge">`)
.addClass(badge.className || "")
.text(badge.title));
}
}
this.$noteTypeDropdown.append(listItem);
}
}
this.dropdown.show();
this.$originalDialog = glob.activeDialog;
glob.activeDialog = this.$widget;
this.modal.show();
this.$noteTypeDropdown.find(".dropdown-item:first").focus();
this.resolve = callback;
}
doResolve(e: JQuery.KeyDownEvent | JQuery.ClickEvent) {
const $item = $(e.target).closest(".dropdown-item");
const noteType = $item.attr("data-note-type");
const templateNoteId = $item.attr("data-template-note-id");
const notePath = this.$autoComplete.getSelectedNotePath() || undefined;
if (this.resolve) {
this.resolve({
success: true,
noteType,
templateNoteId,
notePath
});
}
this.resolve = null;
this.modal.hide();
}
}

View File

@@ -0,0 +1,130 @@
import ReactBasicWidget from "../react/ReactBasicWidget";
import Modal from "../react/Modal";
import { t } from "../../services/i18n";
import FormGroup from "../react/FormGroup";
import NoteAutocomplete from "../react/NoteAutocomplete";
import FormList, { FormListHeader, FormListItem } from "../react/FormList";
import { useEffect, useState } from "preact/hooks";
import note_types from "../../services/note_types";
import { MenuCommandItem, MenuItem } from "../../menus/context_menu";
import { TreeCommandNames } from "../../menus/tree_context_menu";
import { Suggestion } from "../../services/note_autocomplete";
import Badge from "../react/Badge";
import useTriliumEvent from "../react/hooks";
export interface ChooseNoteTypeResponse {
success: boolean;
noteType?: string;
templateNoteId?: string;
notePath?: string;
}
export type ChooseNoteTypeCallback = (data: ChooseNoteTypeResponse) => void;
const SEPARATOR_TITLE_REPLACEMENTS = [
t("note_type_chooser.builtin_templates"),
t("note_type_chooser.templates")
];
function NoteTypeChooserDialogComponent() {
const [ callback, setCallback ] = useState<ChooseNoteTypeCallback>();
const [ shown, setShown ] = useState(false);
const [ parentNote, setParentNote ] = useState<Suggestion | null>();
const [ noteTypes, setNoteTypes ] = useState<MenuItem<TreeCommandNames>[]>([]);
useTriliumEvent("chooseNoteType", ({ callback }) => {
setCallback(() => callback);
setShown(true);
});
if (!noteTypes.length) {
useEffect(() => {
note_types.getNoteTypeItems().then(noteTypes => {
let index = -1;
setNoteTypes((noteTypes ?? []).map((item, _index) => {
if (item.title === "----") {
index++;
return {
title: SEPARATOR_TITLE_REPLACEMENTS[index],
enabled: false
}
}
return item;
}));
});
});
}
function onNoteTypeSelected(value: string) {
const [ noteType, templateNoteId ] = value.split(",");
callback?.({
success: true,
noteType,
templateNoteId,
notePath: parentNote?.notePath
});
setShown(false);
}
return (
<Modal
title={t("note_type_chooser.modal_title")}
className="note-type-chooser-dialog"
size="md"
zIndex={1100} // note type chooser needs to be higher than other dialogs from which it is triggered, e.g. "add link"
scrollable
onHidden={() => {
callback?.({ success: false });
setShown(false);
}}
show={shown}
stackable
>
<FormGroup label={t("note_type_chooser.change_path_prompt")}>
<NoteAutocomplete
onChange={setParentNote}
placeholder={t("note_type_chooser.search_placeholder")}
opts={{
allowCreatingNotes: false,
hideGoToSelectedNoteButton: true,
allowJumpToSearchNotes: false,
}}
/>
</FormGroup>
<FormGroup label={t("note_type_chooser.modal_body")}>
<FormList onSelect={onNoteTypeSelected}>
{noteTypes.map((_item) => {
if (_item.title === "----") {
return;
}
const item = _item as MenuCommandItem<TreeCommandNames>;
if (item.enabled === false) {
return <FormListHeader text={item.title} />
} else {
return <FormListItem
value={[ item.type, item.templateNoteId ].join(",") }
icon={item.uiIcon}>
{item.title}
{item.badges && item.badges.map((badge) => <Badge {...badge} />)}
</FormListItem>;
}
})}
</FormList>
</FormGroup>
</Modal>
);
}
export default class NoteTypeChooserDialog extends ReactBasicWidget {
get component() {
return <NoteTypeChooserDialogComponent />
}
}

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