Compare commits

..

1 Commits

Author SHA1 Message Date
perf3ct
66074ddbc9 fix(docs): try to fix more docs links... 2025-09-07 22:30:10 -07:00
364 changed files with 10452 additions and 13946 deletions

View File

@@ -1,6 +1,6 @@
root = true
[*.{js,ts,tsx}]
[*.{js,ts,.tsx}]
charset = utf-8
end_of_line = lf
indent_size = 4

View File

@@ -74,7 +74,7 @@ runs:
- name: Update build info
shell: ${{ inputs.shell }}
run: pnpm run chore:update-build-info
run: npm run chore:update-build-info
# Critical debugging configuration
- name: Run electron-forge build with enhanced logging
@@ -86,7 +86,6 @@ runs:
APPLE_ID_PASSWORD: ${{ env.APPLE_ID_PASSWORD }}
WINDOWS_SIGN_EXECUTABLE: ${{ env.WINDOWS_SIGN_EXECUTABLE }}
TRILIUM_ARTIFACT_NAME_HINT: TriliumNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}
TARGET_ARCH: ${{ inputs.arch }}
run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}
# Add DMG signing step

View File

@@ -16,6 +16,7 @@ on:
- 'requirements-docs.txt'
- '.github/workflows/deploy-docs.yml'
- 'scripts/fix-mkdocs-structure.ts'
- 'validate-docs-links.ts'
# Allow manual triggering from Actions tab
workflow_dispatch:
@@ -32,6 +33,7 @@ on:
- 'requirements-docs.txt'
- '.github/workflows/deploy-docs.yml'
- 'scripts/fix-mkdocs-structure.ts'
- 'validate-docs-links.ts'
jobs:
build-and-deploy:
@@ -102,12 +104,7 @@ jobs:
exit $EXIT_CODE
fi
}
- name: Fix HTML Links
run: |
# Remove .md extensions from links in generated HTML
pnpm tsx ./scripts/fix-html-links.ts site
- name: Validate Built Site
run: |
# Basic validation that important files exist
@@ -116,6 +113,11 @@ jobs:
test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1)
echo "✅ Site validation passed"
- name: Validate Documentation Links
run: |
# Run the TypeScript link validation script
pnpm tsx validate-docs-links.ts
# Install wrangler globally to avoid workspace issues
- name: Install Wrangler
run: |

View File

@@ -152,12 +152,12 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Update build info
run: pnpm run chore:update-build-info
- name: Run the TypeScript build
run: pnpm run server:build
- name: Update build info
run: pnpm run chore:update-build-info
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
@@ -211,7 +211,7 @@ jobs:
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }}
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

View File

@@ -30,19 +30,6 @@ jobs:
image: win-signing
shell: cmd
forge_platform: win32
# Exclude ARM64 Linux from default matrix to use native runner
exclude:
- arch: arm64
os:
name: linux
# Add ARM64 Linux with native ubuntu-24.04-arm runner for better-sqlite3 compatibility
include:
- arch: arm64
os:
name: linux
image: ubuntu-24.04-arm
shell: bash
forge_platform: linux
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v5

View File

@@ -13,23 +13,6 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
## 📚 Documentation
**Visit our comprehensive documentation at [docs.triliumnotes.org](https://docs.triliumnotes.org/)**
Our documentation is available in multiple formats:
- **Online Documentation**: Browse the full documentation at [docs.triliumnotes.org](https://docs.triliumnotes.org/)
- **In-App Help**: Press `F1` within Trilium to access the same documentation directly in the application
- **GitHub**: Navigate through the [User Guide](./docs/User%20Guide/User%20Guide/) in this repository
### Quick Links
- [Getting Started Guide](https://docs.triliumnotes.org/)
- [Installation Instructions](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Docker Setup](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
- [Upgrading TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
- [Basic Concepts and Features](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
- [Patterns of Personal Knowledge Base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
## 🎁 Features
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/cloning-notes))
@@ -73,6 +56,19 @@ There are no special migration steps to migrate from a zadam/Trilium instance to
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are compatible with the latest zadam/trilium version of [v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later versions of TriliumNext/Trilium have their sync versions incremented which prevents direct migration.
## 📖 Documentation
We're currently in the progress of moving the documentation to in-app (hit the `F1` key within Trilium). As a result, there may be some missing parts until we've completed the migration. If you'd prefer to navigate through the documentation within GitHub, you can navigate the [User Guide](./docs/User%20Guide/User%20Guide/) documentation.
Below are some quick links for your convenience to navigate the documentation:
- [Server installation](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Docker installation](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
- [Upgrading TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
- [Concepts and Features - Note](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
- [Patterns of personal knowledge base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
Until we finish reorganizing the documentation, you may also want to [browse the old documentation](https://triliumnext.github.io/Docs).
## 💬 Discuss with us
Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have!

View File

@@ -36,12 +36,12 @@
},
"devDependencies": {
"@playwright/test": "1.55.0",
"@stylistic/eslint-plugin": "5.4.0",
"@stylistic/eslint-plugin": "5.3.1",
"@types/express": "5.0.3",
"@types/node": "22.18.6",
"@types/node": "22.18.1",
"@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.2.4",
"eslint": "9.36.0",
"eslint": "9.35.0",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.4",
@@ -49,7 +49,7 @@
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"tslib": "2.8.1",
"typedoc": "0.28.13",
"typedoc": "0.28.12",
"typedoc-plugin-missing-exports": "4.1.0"
},
"optionalDependencies": {

View File

@@ -1,4 +1,5 @@
# The development license key for premium CKEditor features.
# Note: This key must only be used for the Trilium Notes project.
VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3ODcyNzA0MDAsImp0aSI6IjkyMWE1MWNlLTliNDMtNGRlMC1iOTQwLTc5ZjM2MDBkYjg1NyIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOiJ0cmlsaXVtIiwiZmVhdHVyZXMiOlsiVFJJTElVTSJdLCJ2YyI6ImU4YzRhMjBkIn0.hny77p-U4-jTkoqbwPytrEar5ylGCWBN7Ez3SlB8i6_mJCBIeCSTOlVQk_JMiOEq3AGykUMHzWXzjdMFwgniOw
# Expires on: 2025-09-13
VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3NTc3MjE1OTksImp0aSI6ImFiN2E0NjZmLWJlZGMtNDNiYy1iMzU4LTk0NGQ0YWJhY2I3ZiIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOlsic2giLCJkcnVwYWwiXSwid2hpdGVMYWJlbCI6dHJ1ZSwiZmVhdHVyZXMiOlsiRFJVUCIsIkNNVCIsIkRPIiwiRlAiLCJTQyIsIlRPQyIsIlRQTCIsIlBPRSIsIkNDIiwiTUYiLCJTRUUiLCJFQ0giLCJFSVMiXSwidmMiOiI1MzlkOWY5YyJ9.2rvKPql4hmukyXhEtWPZ8MLxKvzPIwzCdykO653g7IxRRZy2QJpeRszElZx9DakKYZKXekVRAwQKgHxwkgbE_w
VITE_CKEDITOR_ENABLE_INSPECTOR=false

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/client",
"version": "0.99.0",
"version": "0.98.1",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true,
"license": "AGPL-3.0-only",
@@ -15,7 +15,7 @@
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
},
"dependencies": {
"@eslint/js": "9.36.0",
"@eslint/js": "9.35.0",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
@@ -40,7 +40,7 @@
"debounce": "2.2.0",
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"globals": "16.4.0",
"globals": "16.3.0",
"i18next": "25.5.2",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
@@ -51,12 +51,12 @@
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "16.3.0",
"mermaid": "11.12.0",
"mind-elixir": "5.2.0",
"marked": "16.2.1",
"mermaid": "11.11.0",
"mind-elixir": "5.1.1",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.2",
"preact": "10.27.1",
"react-i18next": "15.7.3",
"split.js": "1.6.5",
"svg-pan-zoom": "3.6.2",
@@ -71,7 +71,7 @@
"@types/leaflet": "1.9.20",
"@types/leaflet-gpx": "1.3.8",
"@types/mark.js": "8.11.12",
"@types/tabulator-tables": "6.2.11",
"@types/tabulator-tables": "6.2.10",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "18.0.1",
"script-loader": "0.7.2",

View File

@@ -7,9 +7,6 @@
"display": "standalone",
"scope": "/",
"start_url": "/",
"display_override": [
"window-controls-overlay"
],
"icons": [
{
"src": "icon.png",

View File

@@ -116,7 +116,7 @@ export type CommandMappings = {
openedFileUpdated: CommandData & {
entityType: string;
entityId: string;
lastModifiedMs?: number;
lastModifiedMs: number;
filePath: string;
};
focusAndSelectTitle: CommandData & {
@@ -650,7 +650,7 @@ export class AppContext extends Component {
}
getComponentByEl(el: HTMLElement) {
return $(el).closest("[data-component-id]").prop("component");
return $(el).closest(".component").prop("component");
}
addBeforeUnloadListener(obj: BeforeUploadListener | (() => boolean)) {

View File

@@ -433,9 +433,6 @@ export default class TabManager extends Component {
$autocompleteEl.autocomplete("close");
}
// close dangling tooltips
$("body > div.tooltip").remove();
const noteContextsToRemove = noteContextToRemove.getSubContexts();
const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId);
@@ -603,18 +600,18 @@ export default class TabManager extends Component {
}
async moveTabToNewWindowCommand({ ntxId }: { ntxId: string }) {
const { notePath, hoistedNoteId, viewScope } = this.getNoteContextById(ntxId);
const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId);
const removed = await this.removeNoteContext(ntxId);
if (removed) {
this.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
this.triggerCommand("openInWindow", { notePath, hoistedNoteId });
}
}
async copyTabToNewWindowCommand({ ntxId }: { ntxId: string }) {
const { notePath, hoistedNoteId, viewScope } = this.getNoteContextById(ntxId);
this.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId);
this.triggerCommand("openInWindow", { notePath, hoistedNoteId });
}
async reopenLastTabCommand() {

View File

@@ -23,11 +23,11 @@ export default class TouchBarComponent extends Component {
this.$widget = $("<div>");
$(window).on("focusin", async (e) => {
const focusedEl = e.target as unknown as HTMLElement;
const $target = $(focusedEl);
const $target = $(e.target);
this.$activeModal = $target.closest(".modal-dialog");
this.lastFocusedComponent = appContext.getComponentByEl(focusedEl);
const parentComponentEl = $target.closest(".component");
this.lastFocusedComponent = appContext.getComponentByEl(parentComponentEl[0]);
this.#refreshTouchBar();
});
}

View File

@@ -45,10 +45,6 @@ if (utils.isElectron()) {
electronContextMenu.setupContextMenu();
}
if (utils.isPWA()) {
initPWATopbarColor();
}
function initOnElectron() {
const electron: typeof Electron = utils.dynamicRequire("electron");
electron.ipcRenderer.on("globalShortcut", async (event, actionName) => appContext.triggerCommand(actionName));
@@ -117,20 +113,3 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) {
const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote;
nativeTheme.themeSource = themeSource;
}
function initPWATopbarColor() {
const tracker = $("#background-color-tracker");
if (tracker.length) {
const applyThemeColor = () => {
let meta = $("meta[name='theme-color']");
if (!meta.length) {
meta = $(`<meta name="theme-color">`).appendTo($("head"));
}
meta.attr("content", tracker.css("color"));
};
tracker.on("transitionend", applyThemeColor);
applyThemeColor();
}
}

View File

@@ -256,20 +256,18 @@ export default class FNote {
return this.children;
}
async getSubtreeNoteIds(includeArchived = false) {
async getSubtreeNoteIds() {
let noteIds: (string | string[])[] = [];
for (const child of await this.getChildNotes()) {
if (child.isArchived && !includeArchived) continue;
noteIds.push(child.noteId);
noteIds.push(await child.getSubtreeNoteIds(includeArchived));
noteIds.push(await child.getSubtreeNoteIds());
}
return noteIds.flat();
}
async getSubtreeNotes() {
const noteIds = await this.getSubtreeNoteIds();
return (await this.froca.getNotes(noteIds));
return this.froca.getNotes(noteIds);
}
async getChildNotes() {
@@ -907,8 +905,8 @@ export default class FNote {
return this.getBlob();
}
getBlob() {
return this.froca.getBlob("notes", this.noteId);
async getBlob() {
return await this.froca.getBlob("notes", this.noteId);
}
toString() {

View File

@@ -5,6 +5,7 @@ import NoteTreeWidget from "../widgets/note_tree.js";
import NoteTitleWidget from "../widgets/note_title.jsx";
import NoteDetailWidget from "../widgets/note_detail.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import NoteListWidget from "../widgets/note_list.js";
import NoteIconWidget from "../widgets/note_icon.jsx";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import RootContainer from "../widgets/containers/root_container.js";
@@ -41,7 +42,6 @@ import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
import ApiLog from "../widgets/api_log.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
import SharedInfo from "../widgets/shared_info.jsx";
import NoteList from "../widgets/collections/NoteList.jsx";
export default class DesktopLayout {
@@ -123,10 +123,10 @@ export default class DesktopLayout {
.child(<NoteIconWidget />)
.child(<NoteTitleWidget />)
.child(new SpacerWidget(0, 1))
.child(<MovePaneButton direction="left" />)
.child(<MovePaneButton direction="right" />)
.child(<ClosePaneButton />)
.child(<CreatePaneButton />)
.child(new MovePaneButton(true))
.child(new MovePaneButton(false))
.child(new ClosePaneButton())
.child(new CreatePaneButton())
)
.child(<Ribbon />)
.child(<SharedInfo />)
@@ -138,7 +138,7 @@ export default class DesktopLayout {
.child(new PromotedAttributesWidget())
.child(<SqlTableSchemas />)
.child(new NoteDetailWidget())
.child(<NoteList />)
.child(new NoteListWidget(false))
.child(<SearchResult />)
.child(<SqlResults />)
.child(<ScrollPadding />)

View File

@@ -27,10 +27,10 @@ import FlexContainer from "../widgets/containers/flex_container.js";
import NoteIconWidget from "../widgets/note_icon";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import NoteListWidget from "../widgets/note_list.js";
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
import NoteTitleWidget from "../widgets/note_title.jsx";
import { PopupEditorFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.js";
import NoteList from "../widgets/collections/NoteList.jsx";
export function applyModals(rootContainer: RootContainer) {
rootContainer
@@ -66,6 +66,6 @@ export function applyModals(rootContainer: RootContainer) {
.child(<PopupEditorFormattingToolbar />)
.child(new PromotedAttributesWidget())
.child(new NoteDetailWidget())
.child(<NoteList displayOnlyCollections />))
.child(new NoteListWidget(true)))
.child(<CallToActionDialog />);
}

View File

@@ -5,6 +5,7 @@ import QuickSearchWidget from "../widgets/quick_search.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import NoteListWidget from "../widgets/note_list.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import RootContainer from "../widgets/containers/root_container.js";
@@ -23,7 +24,6 @@ import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button
import CloseZenModeButton from "../widgets/close_zen_button.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
import NoteList from "../widgets/collections/NoteList.jsx";
const MOBILE_CSS = `
<style>
@@ -154,7 +154,7 @@ export default class MobileLayout {
.filling()
.contentSized()
.child(new NoteDetailWidget())
.child(<NoteList />)
.child(new NoteListWidget(false))
.child(<FilePropertiesWrapper />)
)
.child(<MobileEditorToolbar />)
@@ -187,4 +187,4 @@ function FilePropertiesWrapper() {
{note?.type === "file" && <FilePropertiesTab note={note} />}
</div>
);
}
}

View File

@@ -1,8 +1,6 @@
import { KeyboardActionNames } from "@triliumnext/commons";
import keyboardActionService, { getActionSync } from "../services/keyboard_actions.js";
import keyboardActionService from "../services/keyboard_actions.js";
import note_tooltip from "../services/note_tooltip.js";
import utils from "../services/utils.js";
import { should } from "vitest";
export interface ContextMenuOptions<T> {
x: number;
@@ -15,13 +13,8 @@ export interface ContextMenuOptions<T> {
onHide?: () => void;
}
export interface MenuSeparatorItem {
kind: "separator";
}
export interface MenuHeader {
title: string;
kind: "header";
interface MenuSeparatorItem {
title: "----";
}
export interface MenuItemBadge {
@@ -45,13 +38,12 @@ export interface MenuCommandItem<T> {
handler?: MenuHandler<T>;
items?: MenuItem<T>[] | null;
shortcut?: string;
keyboardShortcut?: KeyboardActionNames;
spellingSuggestion?: string;
checked?: boolean;
columns?: number;
}
export type MenuItem<T> = MenuCommandItem<T> | MenuSeparatorItem | MenuHeader;
export type MenuItem<T> = MenuCommandItem<T> | MenuSeparatorItem;
export type MenuHandler<T> = (item: MenuCommandItem<T>, e: JQuery.MouseDownEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => void;
export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent;
@@ -156,51 +148,14 @@ class ContextMenu {
.addClass("show");
}
addItems($parent: JQuery<HTMLElement>, items: MenuItem<any>[], multicolumn = false) {
let $group = $parent; // The current group or parent element to which items are being appended
let shouldStartNewGroup = false; // If true, the next item will start a new group
let shouldResetGroup = false; // If true, the next item will be the last one from the group
for (let index = 0; index < items.length; index++) {
const item = items[index];
addItems($parent: JQuery<HTMLElement>, items: MenuItem<any>[]) {
for (const item of items) {
if (!item) {
continue;
}
// If the current item is a header, start a new group. This group will contain the
// header and the next item that follows the header.
if ("kind" in item && item.kind === "header") {
if (multicolumn && !shouldResetGroup) {
shouldStartNewGroup = true;
}
}
// If the next item is a separator, start a new group. This group will contain the
// current item, the separator, and the next item after the separator.
const nextItem = (index < items.length - 1) ? items[index + 1] : null;
if (multicolumn && nextItem && "kind" in nextItem && nextItem.kind === "separator") {
if (!shouldResetGroup) {
shouldStartNewGroup = true;
} else {
shouldResetGroup = true; // Continue the current group
}
}
// Create a new group to avoid column breaks before and after the seaparator / header.
// This is a workaround for Firefox not supporting break-before / break-after: avoid
// for columns.
if (shouldStartNewGroup) {
$group = $("<div class='dropdown-no-break'>");
$parent.append($group);
shouldStartNewGroup = false;
}
if ("kind" in item && item.kind === "separator") {
$group.append($("<div>").addClass("dropdown-divider"));
shouldResetGroup = true; // End the group after the next item
} else if ("kind" in item && item.kind === "header") {
$group.append($("<h6>").addClass("dropdown-header").text(item.title));
shouldResetGroup = true;
if (item.title === "----") {
$parent.append($("<div>").addClass("dropdown-divider"));
} else {
const $icon = $("<span>");
@@ -230,23 +185,7 @@ class ContextMenu {
}
}
if ("keyboardShortcut" in item && item.keyboardShortcut) {
const shortcuts = getActionSync(item.keyboardShortcut).effectiveShortcuts;
if (shortcuts) {
const allShortcuts: string[] = [];
for (const effectiveShortcut of shortcuts) {
allShortcuts.push(effectiveShortcut.split("+")
.map(key => `<kbd>${key}</kbd>`)
.join("+"));
}
if (allShortcuts.length) {
const container = $("<span>").addClass("keyboard-shortcut");
container.append($(allShortcuts.join(",")));
$link.append(container);
}
}
} else if ("shortcut" in item && item.shortcut) {
if ("shortcut" in item && item.shortcut) {
$link.append($("<kbd>").text(item.shortcut));
}
@@ -302,24 +241,16 @@ class ContextMenu {
$link.addClass("dropdown-toggle");
const $subMenu = $("<ul>").addClass("dropdown-menu");
const hasColumns = !!item.columns && item.columns > 1;
if (!this.isMobile && hasColumns) {
$subMenu.css("column-count", item.columns!);
if (!this.isMobile && item.columns) {
$subMenu.css("column-count", item.columns);
}
this.addItems($subMenu, item.items, hasColumns);
this.addItems($subMenu, item.items);
$item.append($subMenu);
}
$group.append($item);
// After adding a menu item, if the previous item was a separator or header,
// reset the group so that the next item will be appended directly to the parent.
if (shouldResetGroup) {
$group = $parent;
shouldResetGroup = false;
};
$parent.append($item);
}
}
}

View File

@@ -37,7 +37,7 @@ function setupContextMenu() {
handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord)
});
items.push({ kind: "separator" });
items.push({ title: `----` });
}
if (params.isEditable) {
@@ -112,7 +112,7 @@ function setupContextMenu() {
// Replace the placeholder with the real search keyword.
let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText));
items.push({ kind: "separator" });
items.push({ title: "----" });
items.push({
title: t("electron_context_menu.search_online", { term: shortenedSelection, searchEngine: searchEngineName }),

View File

@@ -45,16 +45,16 @@ export default class LauncherContextMenu implements SelectMenuItemEventListener<
isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-script-launcher"), command: "addScriptLauncher", uiIcon: "bx bx-code-curly" } : null,
isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-custom-widget"), command: "addWidgetLauncher", uiIcon: "bx bx-customize" } : null,
isVisibleRoot || isAvailableRoot ? { title: t("launcher_context_menu.add-spacer"), command: "addSpacerLauncher", uiIcon: "bx bx-dots-horizontal" } : null,
isVisibleRoot || isAvailableRoot ? { kind: "separator" } : null,
isVisibleRoot || isAvailableRoot ? { title: "----" } : null,
isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null,
isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null,
isVisibleItem || isAvailableItem ? { kind: "separator" } : null,
isVisibleItem || isAvailableItem ? { title: "----" } : null,
{ title: `${t("launcher_context_menu.duplicate-launcher")}`, command: "duplicateSubtree", uiIcon: "bx bx-outline", enabled: isItem },
{ title: `${t("launcher_context_menu.delete")}`, command: "deleteNotes", uiIcon: "bx bx-trash destructive-action-icon", enabled: canBeDeleted },
{ kind: "separator" },
{ title: "----" },
{ title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-reset destructive-action-icon", enabled: canBeReset }
];

View File

@@ -13,8 +13,6 @@ import type NoteTreeWidget from "../widgets/note_tree.js";
import type FAttachment from "../entities/fattachment.js";
import type { SelectMenuItemEventListener } from "../components/events.js";
import utils from "../services/utils.js";
import attributes from "../services/attributes.js";
import { executeBulkActions } from "../services/bulk_action.js";
// TODO: Deduplicate once client/server is well split.
interface ConvertToAttachmentResponse {
@@ -63,11 +61,6 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
// the only exception is when the only selected note is the one that was right-clicked, then
// it's clear what the user meant to do.
const selNodes = this.treeWidget.getSelectedNodes();
const selectedNotes = await froca.getNotes(selNodes.map(node => node.data.noteId));
if (note && !selectedNotes.includes(note)) selectedNotes.push(note);
const isArchived = selectedNotes.every(note => note.isArchived);
const canToggleArchived = !selectedNotes.some(note => note.isArchived !== isArchived);
const noSelectedNotes = selNodes.length === 0 || (selNodes.length === 1 && selNodes[0] === this.node);
const notSearch = note?.type !== "search";
@@ -76,29 +69,27 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
const items: (MenuItem<TreeCommandNames> | null)[] = [
{ title: t("tree-context-menu.open-in-a-new-tab"), command: "openInTab", shortcut: "Ctrl+Click", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
{ title: `${t("tree-context-menu.open-in-a-new-tab")}`, command: "openInTab", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes },
isHoisted
? null
: {
title: `${t("tree-context-menu.hoist-note")}`,
title: `${t("tree-context-menu.hoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`,
command: "toggleNoteHoisting",
keyboardShortcut: "toggleNoteHoisting",
uiIcon: "bx bxs-chevrons-up",
enabled: noSelectedNotes && notSearch
},
!isHoisted || !isNotRoot
? null
: { title: t("tree-context-menu.unhoist-note"), command: "toggleNoteHoisting", keyboardShortcut: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
: { title: `${t("tree-context-menu.unhoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
{ kind: "separator" },
{ title: "----" },
{
title: t("tree-context-menu.insert-note-after"),
title: `${t("tree-context-menu.insert-note-after")}<kbd data-command="createNoteAfter"></kbd>`,
command: "insertNoteAfter",
keyboardShortcut: "createNoteAfter",
uiIcon: "bx bx-plus",
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptionsOrHelp,
@@ -106,22 +97,21 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
},
{
title: t("tree-context-menu.insert-child-note"),
title: `${t("tree-context-menu.insert-child-note")}<kbd data-command="createNoteInto"></kbd>`,
command: "insertChildNote",
keyboardShortcut: "createNoteInto",
uiIcon: "bx bx-plus",
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
enabled: notSearch && noSelectedNotes && notOptionsOrHelp,
columns: 2
},
{ kind: "separator" },
{ title: "----" },
{ title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
{ title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
{ kind: "separator" },
{ title: "----" },
{
title: t("tree-context-menu.advanced"),
@@ -130,52 +120,48 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
items: [
{ title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", enabled: true },
{ kind: "separator" },
{ title: "----" },
{
title: t("tree-context-menu.edit-branch-prefix"),
title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`,
command: "editBranchPrefix",
keyboardShortcut: "editBranchPrefix",
uiIcon: "bx bx-rename",
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp
},
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp },
{ kind: "separator" },
{ title: "----" },
{ title: t("tree-context-menu.expand-subtree"), command: "expandSubtree", keyboardShortcut: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: t("tree-context-menu.collapse-subtree"), command: "collapseSubtree", keyboardShortcut: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{ title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: `${t("tree-context-menu.collapse-subtree")} <kbd data-command="collapseSubtree"></kbd>`, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{
title: t("tree-context-menu.sort-by"),
title: `${t("tree-context-menu.sort-by")} <kbd data-command="sortChildNotes"></kbd>`,
command: "sortChildNotes",
keyboardShortcut: "sortChildNotes",
uiIcon: "bx bx-sort-down",
enabled: noSelectedNotes && notSearch
},
{ kind: "separator" },
{ title: "----" },
{ title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true },
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptionsOrHelp }
]
},
{ kind: "separator" },
{ title: "----" },
{
title: t("tree-context-menu.cut"),
title: `${t("tree-context-menu.cut")} <kbd data-command="cutNotesToClipboard"></kbd>`,
command: "cutNotesToClipboard",
keyboardShortcut: "cutNotesToClipboard",
uiIcon: "bx bx-cut",
enabled: isNotRoot && !isHoisted && parentNotSearch
},
{ title: t("tree-context-menu.copy-clone"), command: "copyNotesToClipboard", keyboardShortcut: "copyNotesToClipboard", uiIcon: "bx bx-copy", enabled: isNotRoot && !isHoisted },
{ title: `${t("tree-context-menu.copy-clone")} <kbd data-command="copyNotesToClipboard"></kbd>`, command: "copyNotesToClipboard", uiIcon: "bx bx-copy", enabled: isNotRoot && !isHoisted },
{
title: t("tree-context-menu.paste-into"),
title: `${t("tree-context-menu.paste-into")} <kbd data-command="pasteNotesFromClipboard"></kbd>`,
command: "pasteNotesFromClipboard",
keyboardShortcut: "pasteNotesFromClipboard",
uiIcon: "bx bx-paste",
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes
},
@@ -188,71 +174,39 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
},
{
title: t("tree-context-menu.move-to"),
title: `${t("tree-context-menu.move-to")} <kbd data-command="moveNotesTo"></kbd>`,
command: "moveNotesTo",
keyboardShortcut: "moveNotesTo",
uiIcon: "bx bx-transfer",
enabled: isNotRoot && !isHoisted && parentNotSearch
},
{ title: t("tree-context-menu.clone-to"), command: "cloneNotesTo", keyboardShortcut: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
{ title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
{
title: t("tree-context-menu.duplicate"),
title: `${t("tree-context-menu.duplicate")} <kbd data-command="duplicateSubtree">`,
command: "duplicateSubtree",
keyboardShortcut: "duplicateSubtree",
uiIcon: "bx bx-outline",
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
},
{
title: !isArchived ? t("tree-context-menu.archive") : t("tree-context-menu.unarchive"),
uiIcon: !isArchived ? "bx bx-archive" : "bx bx-archive-out",
enabled: canToggleArchived,
handler: () => {
if (!selectedNotes.length) return;
if (selectedNotes.length == 1) {
const note = selectedNotes[0];
if (!isArchived) {
attributes.addLabel(note.noteId, "archived");
} else {
attributes.removeOwnedLabelByName(note, "archived");
}
} else {
const noteIds = selectedNotes.map(note => note.noteId);
if (!isArchived) {
executeBulkActions(noteIds, [{
name: "addLabel", labelName: "archived"
}]);
} else {
executeBulkActions(noteIds, [{
name: "deleteLabel", labelName: "archived"
}]);
}
}
}
},
{
title: t("tree-context-menu.delete"),
title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`,
command: "deleteNotes",
keyboardShortcut: "deleteNotes",
uiIcon: "bx bx-trash destructive-action-icon",
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptionsOrHelp
},
{ kind: "separator" },
{ title: "----" },
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-export", enabled: notSearch && noSelectedNotes && notOptionsOrHelp },
{ kind: "separator" },
{ title: "----" },
{
title: t("tree-context-menu.search-in-subtree"),
title: `${t("tree-context-menu.search-in-subtree")} <kbd data-command="searchInSubtree"></kbd>`,
command: "searchInSubtree",
keyboardShortcut: "searchInSubtree",
uiIcon: "bx bx-search",
enabled: notSearch && noSelectedNotes
}

View File

@@ -210,7 +210,7 @@ function makeToast(id: string, message: string): ToastOptions {
}
ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "deleteNotes") {
if (message.taskType !== "deleteNotes") {
return;
}
@@ -228,7 +228,7 @@ ws.subscribeToMessages(async (message) => {
});
ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "undeleteNotes") {
if (message.taskType !== "undeleteNotes") {
return;
}

View File

@@ -256,19 +256,8 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
</button>
`);
$downloadButton.on("click", (e) => {
e.stopPropagation();
openService.downloadFileNote(entity.noteId)
});
$openButton.on("click", async (e) => {
const iconEl = $openButton.find("> .bx");
iconEl.removeClass("bx bx-link-external");
iconEl.addClass("bx bx-loader spin");
e.stopPropagation();
await openService.openNoteExternally(entity.noteId, entity.mime)
iconEl.removeClass("bx bx-loader spin");
iconEl.addClass("bx bx-link-external");
});
$downloadButton.on("click", () => openService.downloadFileNote(entity.noteId));
$openButton.on("click", () => openService.openNoteExternally(entity.noteId, entity.mime));
// open doesn't work for protected notes since it works through a browser which isn't in protected session
$openButton.toggle(!entity.isProtected);

View File

@@ -60,7 +60,7 @@ async function confirmDeleteNoteBoxWithNote(title: string) {
return new Promise<ConfirmDialogResult | undefined>((res) => appContext.triggerCommand("showConfirmDeleteNoteBoxWithNoteDialog", { title, callback: res }));
}
export async function prompt(props: PromptDialogOptions) {
async function prompt(props: PromptDialogOptions) {
return new Promise<string | null>((res) => appContext.triggerCommand("showPromptDialog", { ...props, callback: res }));
}

View File

@@ -1,8 +1,16 @@
import ws from "./ws.js";
import appContext from "../components/app_context.js";
import { OpenedFileUpdateStatus } from "@triliumnext/commons";
const fileModificationStatus: Record<string, Record<string, OpenedFileUpdateStatus>> = {
// TODO: Deduplicate
interface Message {
type: string;
entityType: string;
entityId: string;
lastModifiedMs: number;
filePath: string;
}
const fileModificationStatus: Record<string, Record<string, Message>> = {
notes: {},
attachments: {}
};
@@ -31,7 +39,7 @@ function ignoreModification(entityType: string, entityId: string) {
delete fileModificationStatus[entityType][entityId];
}
ws.subscribeToMessages(async message => {
ws.subscribeToMessages(async (message: Message) => {
if (message.type !== "openedFileUpdated") {
return;
}

View File

@@ -8,7 +8,6 @@ import FAttribute, { type FAttributeRow } from "../entities/fattribute.js";
import FAttachment, { type FAttachmentRow } from "../entities/fattachment.js";
import type { default as FNote, FNoteRow } from "../entities/fnote.js";
import type { EntityChange } from "../server_types.js";
import type { OptionNames } from "@triliumnext/commons";
async function processEntityChanges(entityChanges: EntityChange[]) {
const loadResults = new LoadResults(entityChanges);
@@ -31,8 +30,9 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
continue; // only noise
}
options.set(attributeEntity.name as OptionNames, attributeEntity.value);
loadResults.addOption(attributeEntity.name as OptionNames);
options.set(attributeEntity.name, attributeEntity.value);
loadResults.addOption(attributeEntity.name);
} else if (ec.entityName === "attachments") {
processAttachment(loadResults, ec);
} else if (ec.entityName === "blobs") {

View File

@@ -4,7 +4,6 @@ import ws from "./ws.js";
import utils from "./utils.js";
import appContext from "../components/app_context.js";
import { t } from "./i18n.js";
import { WebSocketMessage } from "@triliumnext/commons";
type BooleanLike = boolean | "true" | "false";
@@ -67,7 +66,7 @@ function makeToast(id: string, message: string): ToastOptions {
}
ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "importNotes") {
if (message.taskType !== "importNotes") {
return;
}
@@ -88,8 +87,8 @@ ws.subscribeToMessages(async (message) => {
}
});
ws.subscribeToMessages(async (message: WebSocketMessage) => {
if (!("taskType" in message) || message.taskType !== "importAttachments") {
ws.subscribeToMessages(async (message) => {
if (message.taskType !== "importAttachments") {
return;
}

View File

@@ -1,6 +1,6 @@
import { NoteType } from "@triliumnext/commons";
import { ViewTypeOptions } from "./note_list_renderer";
import FNote from "../entities/fnote";
import { ViewTypeOptions } from "../widgets/collections/interface";
export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
canvas: null,

View File

@@ -1,4 +1,4 @@
import type { AttachmentRow, EtapiTokenRow, OptionNames } from "@triliumnext/commons";
import type { AttachmentRow, EtapiTokenRow } from "@triliumnext/commons";
import type { AttributeType } from "../entities/fattribute.js";
import type { EntityChange } from "../server_types.js";
@@ -67,7 +67,7 @@ export default class LoadResults {
private revisionRows: RevisionRow[];
private noteReorderings: string[];
private contentNoteIdToComponentId: ContentNoteIdToComponentIdRow[];
private optionNames: OptionNames[];
private optionNames: string[];
private attachmentRows: AttachmentRow[];
public hasEtapiTokenChanges: boolean = false;
@@ -180,11 +180,11 @@ export default class LoadResults {
return this.contentNoteIdToComponentId.find((l) => l.noteId === noteId && l.componentId !== componentId);
}
addOption(name: OptionNames) {
addOption(name: string) {
this.optionNames.push(name);
}
isOptionReloaded(name: OptionNames) {
isOptionReloaded(name: string) {
return this.optionNames.includes(name);
}

View File

@@ -0,0 +1,71 @@
import type FNote from "../entities/fnote.js";
import BoardView from "../widgets/view_widgets/board_view/index.js";
import CalendarView from "../widgets/view_widgets/calendar_view.js";
import GeoView from "../widgets/view_widgets/geo_view/index.js";
import ListOrGridView from "../widgets/view_widgets/list_or_grid_view.js";
import TableView from "../widgets/view_widgets/table_view/index.js";
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
import type ViewMode from "../widgets/view_widgets/view_mode.js";
const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board"] as const;
export type ArgsWithoutNoteId = Omit<ViewModeArgs, "noteIds">;
export type ViewTypeOptions = typeof allViewTypes[number];
export default class NoteListRenderer {
private viewType: ViewTypeOptions;
private args: ArgsWithoutNoteId;
public viewMode?: ViewMode<any>;
constructor(args: ArgsWithoutNoteId) {
this.args = args;
this.viewType = this.#getViewType(args.parentNote);
}
#getViewType(parentNote: FNote): ViewTypeOptions {
const viewType = parentNote.getLabelValue("viewType");
if (!(allViewTypes as readonly string[]).includes(viewType || "")) {
// when not explicitly set, decide based on the note type
return parentNote.type === "search" ? "list" : "grid";
} else {
return viewType as ViewTypeOptions;
}
}
get isFullHeight() {
switch (this.viewType) {
case "list":
case "grid":
return false;
default:
return true;
}
}
async renderList() {
const args = this.args;
const viewMode = this.#buildViewMode(args);
this.viewMode = viewMode;
await viewMode.beforeRender();
return await viewMode.renderList();
}
#buildViewMode(args: ViewModeArgs) {
switch (this.viewType) {
case "calendar":
return new CalendarView(args);
case "table":
return new TableView(args);
case "geoMap":
return new GeoView(args);
case "board":
return new BoardView(args);
case "list":
case "grid":
default:
return new ListOrGridView(this.viewType, args);
}
}
}

View File

@@ -1,7 +1,7 @@
import { t } from "./i18n.js";
import froca from "./froca.js";
import server from "./server.js";
import type { MenuCommandItem, MenuItem, MenuItemBadge, MenuSeparatorItem } from "../menus/context_menu.js";
import type { MenuCommandItem, MenuItem, MenuItemBadge } from "../menus/context_menu.js";
import type { NoteType } from "../entities/fnote.js";
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
@@ -73,7 +73,7 @@ const BETA_BADGE = {
title: t("note_types.beta-feature")
};
const SEPARATOR: MenuSeparatorItem = { kind: "separator" };
const SEPARATOR = { title: "----" };
const creationDateCache = new Map<string, Date>();
let rootCreationDate: Date | undefined;
@@ -81,8 +81,8 @@ let rootCreationDate: Date | undefined;
async function getNoteTypeItems(command?: TreeCommandNames) {
const items: MenuItem<TreeCommandNames>[] = [
...getBlankNoteTypes(command),
...await getBuiltInTemplates(null, command, false),
...await getBuiltInTemplates(t("note_types.collections"), command, true),
...await getBuiltInTemplates(null, command, false),
...await getUserTemplates(command)
];
@@ -121,10 +121,7 @@ async function getUserTemplates(command?: TreeCommandNames) {
}
const items: MenuItem<TreeCommandNames>[] = [
{
title: t("note_type_chooser.templates"),
kind: "header"
}
SEPARATOR
];
for (const templateNote of templateNotes) {
@@ -161,7 +158,8 @@ async function getBuiltInTemplates(title: string | null, command: TreeCommandNam
if (title) {
items.push({
title: title,
kind: "header"
enabled: false,
uiIcon: "bx bx-empty"
});
} else {
items.push(SEPARATOR);

View File

@@ -20,7 +20,7 @@ class Options {
this.arr = arr;
}
get(key: OptionNames) {
get(key: string) {
return this.arr?.[key] as string;
}
@@ -40,7 +40,7 @@ class Options {
}
}
getInt(key: OptionNames) {
getInt(key: string) {
const value = this.arr?.[key];
if (typeof value === "number") {
return value;
@@ -52,7 +52,7 @@ class Options {
return null;
}
getFloat(key: OptionNames) {
getFloat(key: string) {
const value = this.arr?.[key];
if (typeof value !== "string") {
return null;
@@ -60,15 +60,15 @@ class Options {
return parseFloat(value);
}
is(key: OptionNames) {
is(key: string) {
return this.arr[key] === "true";
}
set(key: OptionNames, value: OptionValue) {
set(key: string, value: OptionValue) {
this.arr[key] = value;
}
async save(key: OptionNames, value: OptionValue) {
async save(key: string, value: OptionValue) {
this.set(key, value);
const payload: Record<string, OptionValue> = {};
@@ -85,7 +85,7 @@ class Options {
await server.put<void>("options", newValues);
}
async toggle(key: OptionNames) {
async toggle(key: string) {
await this.save(key, (!this.is(key)).toString());
}
}

View File

@@ -107,11 +107,11 @@ function makeToast(message: Message, title: string, text: string): ToastOptions
}
ws.subscribeToMessages(async (message) => {
if (!("taskType" in message) || message.taskType !== "protectNotes") {
if (message.taskType !== "protectNotes") {
return;
}
const isProtecting = message.data?.protect;
const isProtecting = message.data.protect;
const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title");
if (message.type === "taskError") {

View File

@@ -47,6 +47,27 @@ function parseDate(str: string) {
}
}
// Source: https://stackoverflow.com/a/30465299/4898894
function getMonthsInDateRange(startDate: string, endDate: string) {
const start = startDate.split("-");
const end = endDate.split("-");
const startYear = parseInt(start[0]);
const endYear = parseInt(end[0]);
const dates: string[] = [];
for (let i = startYear; i <= endYear; i++) {
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
const startMon = i === startYear ? parseInt(start[1]) - 1 : 0;
for (let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j + 1) {
const month = j + 1;
const displayMonth = month < 10 ? "0" + month : month;
dates.push([i, displayMonth].join("-"));
}
}
return dates;
}
function padNum(num: number) {
return `${num <= 9 ? "0" : ""}${num}`;
}
@@ -128,18 +149,6 @@ export function isElectron() {
return !!(window && window.process && window.process.type);
}
/**
* Returns `true` if the client is running as a PWA, otherwise `false`.
*/
export function isPWA() {
return (
window.matchMedia('(display-mode: standalone)').matches
|| window.matchMedia('(display-mode: window-controls-overlay)').matches
|| window.navigator.standalone
|| window.navigator.windowControlsOverlay
);
}
export function isMac() {
return navigator.platform.indexOf("Mac") > -1;
}
@@ -487,7 +496,7 @@ function sleep(time_ms: number) {
});
}
export function escapeRegExp(str: string) {
function escapeRegExp(str: string) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
@@ -874,6 +883,7 @@ export default {
restartDesktopApp,
reloadTray,
parseDate,
getMonthsInDateRange,
formatDateISO,
formatDateTime,
formatTimeInterval,
@@ -881,7 +891,6 @@ export default {
localNowDateTime,
now,
isElectron,
isPWA,
isMac,
isCtrlKey,
assertArguments,

View File

@@ -6,10 +6,9 @@ import frocaUpdater from "./froca_updater.js";
import appContext from "../components/app_context.js";
import { t } from "./i18n.js";
import type { EntityChange } from "../server_types.js";
import { WebSocketMessage } from "@triliumnext/commons";
type MessageHandler = (message: WebSocketMessage) => void;
let messageHandlers: MessageHandler[] = [];
type MessageHandler = (message: any) => void;
const messageHandlers: MessageHandler[] = [];
let ws: WebSocket;
let lastAcceptedEntityChangeId = window.glob.maxEntityChangeIdAtLoad;
@@ -48,14 +47,10 @@ function logInfo(message: string) {
window.logError = logError;
window.logInfo = logInfo;
export function subscribeToMessages(messageHandler: MessageHandler) {
function subscribeToMessages(messageHandler: MessageHandler) {
messageHandlers.push(messageHandler);
}
export function unsubscribeToMessage(messageHandler: MessageHandler) {
messageHandlers = messageHandlers.filter(handler => handler !== messageHandler);
}
// used to serialize frontend update operations
let consumeQueuePromise: Promise<void> | null = null;

View File

@@ -161,8 +161,7 @@ textarea,
color: var(--muted-text-color);
}
.form-group.disabled,
.form-checkbox.disabled {
.form-group.disabled {
opacity: 0.5;
pointer-events: none;
}
@@ -252,6 +251,10 @@ button.close:hover {
color: var(--main-text-color) !important;
}
.note-title[readonly] {
background: inherit;
}
.tdialog {
display: none;
}
@@ -290,11 +293,6 @@ button.close:hover {
pointer-events: none;
}
.icon-action.btn {
padding: 0 8px;
min-width: unset !important;
}
.ui-widget-content a:not(.ui-tabs-anchor) {
color: #337ab7 !important;
}
@@ -363,17 +361,21 @@ button kbd {
.tabulator-popup-container {
color: var(--menu-text-color) !important;
font-size: inherit;
background: var(--menu-background-color) !important;
background-color: var(--menu-background-color) !important;
user-select: none;
-webkit-user-select: none;
--bs-dropdown-zindex: 999;
--bs-dropdown-link-active-bg: var(--active-item-background-color) !important;
}
.dropdown-menu .dropdown-divider {
break-before: avoid;
break-after: avoid;
}
body.desktop .dropdown-menu,
body.desktop .tabulator-popup-container {
border: 1px solid var(--dropdown-border-color);
column-rule: 1px solid var(--dropdown-border-color);
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
animation: dropdown-menu-opening 100ms ease-in;
}
@@ -415,7 +417,7 @@ body.desktop .tabulator-popup-container {
}
.dropdown-menu a:hover:not(.disabled),
.dropdown-item:hover:not(.disabled, .dropdown-container-item),
.dropdown-item:hover:not(.disabled, .dropdown-item-container),
.tabulator-menu-item:hover {
color: var(--hover-item-text-color) !important;
background-color: var(--hover-item-background-color) !important;
@@ -423,9 +425,9 @@ body.desktop .tabulator-popup-container {
cursor: pointer;
}
.dropdown-container-item,
.dropdown-item.dropdown-container-item:hover,
.dropdown-container-item:active {
.dropdown-item-container,
.dropdown-item-container:hover,
.dropdown-item-container:active {
background: transparent;
cursor: default;
}
@@ -440,11 +442,9 @@ body #context-menu-container .dropdown-item > span {
align-items: center;
}
.dropdown-item span.keyboard-shortcut,
.dropdown-item *:not(.keyboard-shortcut) > kbd {
.dropdown-item span.keyboard-shortcut {
flex-grow: 1;
text-align: right;
padding-inline-start: 12px;
}
.dropdown-menu kbd {
@@ -454,6 +454,8 @@ body #context-menu-container .dropdown-item > span {
box-shadow: none;
padding-bottom: 0;
padding: 0;
flex-grow: 1;
text-align: right;
}
.dropdown-item,
@@ -462,12 +464,6 @@ body #context-menu-container .dropdown-item > span {
border: 1px solid transparent !important;
}
/* This is a workaround for Firefox not supporting break-before / break-after: avoid on columns.
* It usually wraps a menu item followed by a separator / header and another menu item. */
.dropdown-no-break {
break-inside: avoid;
}
.dropdown-item.disabled,
.dropdown-item.disabled kbd {
color: #aaa !important;
@@ -475,9 +471,9 @@ body #context-menu-container .dropdown-item > span {
.dropdown-item.active,
.dropdown-item:focus {
color: var(--active-item-text-color);
background-color: var(--active-item-background-color);
border-color: var(--active-item-border-color);
color: var(--active-item-text-color) !important;
background-color: var(--active-item-background-color) !important;
border-color: var(--active-item-border-color) !important;
outline: none;
}
@@ -1876,6 +1872,11 @@ textarea {
width: 100%;
}
.jump-to-note-results .aa-dropdown-menu .aa-suggestion:hover,
.jump-to-note-results .aa-dropdown-menu .aa-cursor {
background-color: var(--hover-item-background-color, #f8f9fa);
}
/* Command palette styling */
.jump-to-note-dialog .command-suggestion {
display: flex;

View File

@@ -1,16 +1,16 @@
:root {
--theme-style: dark;
--main-font-family: Montserrat, sans-serif;
--main-font-family: Montserrat;
--main-font-size: normal;
--tree-font-family: Montserrat, sans-serif;
--tree-font-family: Montserrat;
--tree-font-size: normal;
--detail-font-family: Montserrat, sans-serif;
--detail-font-family: Montserrat;
--detail-font-size: normal;
--monospace-font-family: JetBrainsLight, monospace;
--monospace-font-family: JetBrainsLight;
--monospace-font-size: normal;
--main-background-color: #333;

View File

@@ -5,16 +5,16 @@ html {
/* either light or dark, colored theme with darker tones are also dark, used e.g. for note map node colors */
--theme-style: light;
--main-font-family: Montserrat, sans-serif;
--main-font-family: Montserrat;
--main-font-size: normal;
--tree-font-family: Montserrat, sans-serif;
--tree-font-family: Montserrat;
--tree-font-size: normal;
--detail-font-family: Montserrat, sans-serif;
--detail-font-family: Montserrat;
--detail-font-size: normal;
--monospace-font-family: JetBrainsLight, monospace;
--monospace-font-family: JetBrainsLight;
--monospace-font-size: normal;
--main-background-color: white;

View File

@@ -152,7 +152,7 @@
--launcher-pane-horiz-border-color: rgb(22, 22, 22);
--launcher-pane-horiz-background-color: #282828;
--launcher-pane-horiz-text-color: #b8b8b8;
--launcher-pane-horiz-text-color: #909090;
--launcher-pane-horiz-button-hover-color: #ffffff;
--launcher-pane-horiz-button-hover-background: #ffffff1c;
--launcher-pane-horiz-button-hover-shadow: unset;
@@ -172,10 +172,9 @@
--tab-close-button-hover-background: #a45353;
--tab-close-button-hover-color: white;
--active-tab-background-color: #ffffff1c;
--active-tab-hover-background-color: var(--active-tab-background-color);
--active-tab-icon-color: #a9a9a9;
--active-tab-text-color: #ffffffcd;
--active-tab-shadow: 3px 3px 6px rgba(0, 0, 0, 0.2), -1px -1px 3px rgba(0, 0, 0, 0.4);
--active-tab-dragging-shadow: var(--active-tab-shadow), 0 0 20px rgba(0, 0, 0, 0.4);

View File

@@ -165,10 +165,9 @@
--tab-close-button-hover-background: #c95a5a;
--tab-close-button-hover-color: white;
--active-tab-background-color: white;
--active-tab-hover-background-color: var(--active-tab-background-color);
--active-tab-icon-color: gray;
--active-tab-text-color: black;
--active-tab-shadow: 3px 3px 6px rgba(0, 0, 0, 0.1), -1px -1px 3px rgba(0, 0, 0, 0.05);
--active-tab-dragging-shadow: var(--active-tab-shadow), 0 0 20px rgba(0, 0, 0, 0.1);

View File

@@ -26,7 +26,7 @@
--detail-font-family: var(--main-font-family);
--detail-font-size: normal;
--monospace-font-family: JetBrainsLight, monospace;
--monospace-font-family: JetBrainsLight;
--monospace-font-size: normal;
--left-pane-item-selected-shadow-size: 2px;
@@ -102,6 +102,10 @@ body.backdrop-effects-disabled {
font-size: 0.9rem !important;
}
.dropdown-menu {
--scrollbar-background-color: var(--menu-background-color);
}
body.mobile .dropdown-menu {
backdrop-filter: var(--dropdown-backdrop-filter);
border-radius: var(--dropdown-border-radius);
@@ -150,22 +154,12 @@ body.desktop .dropdown-submenu .dropdown-menu {
.dropdown-item,
body.mobile .dropdown-submenu .dropdown-toggle {
padding: 2px 2px 2px 8px !important;
padding-inline-end: 22px !important;
padding-inline-end: 16px !important;
/* Note: the right padding should also accommodate the submenu arrow. */
border-radius: 6px;
cursor: default !important;
}
:root .dropdown-item:focus-visible {
outline: 2px solid var(--input-focus-outline-color) !important;
background-color: transparent;
color: unset;
}
:root .dropdown-item:active {
background: unset;
}
body.mobile .dropdown-submenu {
padding: 0 !important;
}
@@ -295,20 +289,6 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
transform: rotate(270deg);
}
/* Dropdown item button (used in zoom buttons in global menu) */
li.dropdown-item a.dropdown-item-button {
border: unset;
}
li.dropdown-item a.dropdown-item-button.bx {
color: var(--menu-text-color) !important;
}
li.dropdown-item a.dropdown-item-button:focus-visible {
outline: 2px solid var(--input-focus-outline-color) !important;
}
/*
* TOASTS
*/

View File

@@ -84,7 +84,7 @@ button.btn.btn-success kbd {
*/
:root .icon-action:not(.global-menu-button),
:root .tn-tool-button,
:root .btn.tn-tool-button,
:root .btn-group .tn-tool-button:not(:last-child),
:root .btn-group .tn-tool-button:last-child {
width: var(--icon-button-size);
@@ -96,11 +96,6 @@ button.btn.btn-success kbd {
color: var(--icon-button-color);
}
:root .btn-group .icon-action:last-child {
border-top-left-radius: unset !important;
border-bottom-left-radius: unset !important;
}
.btn-group .tn-tool-button + .tn-tool-button {
margin-left: 4px !important;
}
@@ -197,8 +192,8 @@ input[type="password"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="datetime-local"]:focus,
:root input.ck.ck-input-text:not([readonly="true"]):focus,
:root input.ck.ck-input-number:not([readonly="true"]):focus,
:root input.ck.ck-input-text:focus,
:root input.ck.ck-input-number:focus,
textarea.form-control:focus,
textarea:focus,
:root textarea.ck.ck-textarea:focus,

View File

@@ -109,11 +109,6 @@
* NOTE MAP
*/
.note-detail-note-map .fixnodes-type-switcher .tn-tool-button,
.note-map-widget .fixnodes-type-switcher .tn-tool-button {
padding: unset;
}
.note-detail-note-map .fixnodes-type-switcher .tn-tool-button.toggled {
color: var(--tab-close-button-hover-background);
}

View File

@@ -94,17 +94,18 @@ div.promoted-attributes-container {
/* Note type dropdown */
ul.note-type-dropdown .check {
div.note-type-dropdown .check {
margin-right: 6px;
}
ul.note-type-dropdown li.dropdown-item {
--menu-item-icon-vert-offset: 0;
}
/* Editability dropdown */
ul.editability-dropdown li.dropdown-item > div {
div.editability-dropdown a.dropdown-item {
padding: 4px 16px 4px 0;
align-items: start !important;
}
.editability-dropdown .dropdown-item .check {
margin-left: 4px;
}

View File

@@ -223,7 +223,7 @@ body.layout-horizontal > .horizontal {
}
#launcher-pane .launcher-button:focus,
#launcher-pane .global-menu :focus {
#launcher-pane .global-menu button:focus {
outline: none;
}
@@ -304,6 +304,18 @@ body.layout-horizontal > .horizontal {
color: var(--tooltip-foreground-color) !important;
}
/*
* Global menu
*/
.global-menu div.zoom-buttons a {
border: unset;
}
.global-menu div.zoom-buttons a.bx {
color: var(--menu-text-color) !important;
}
/*
* Calendar
*/
@@ -336,21 +348,6 @@ body.layout-horizontal > .horizontal {
--select-arrow-svg: initial; /* Disable the dropdown arrow */
}
/* Week number column */
.calendar-dropdown-widget .calendar-week-number {
transform: rotate(270deg);
justify-content: center;
padding: 0;
opacity: 0.5;
font-size: 1em;
font-weight: 700;
letter-spacing: .5pt;
}
.calendar-dropdown-widget .calendar-week-number::after {
display: none;
}
@media (max-width: 992px) {
.calendar-dropdown-widget .calendar-header button {
margin: 0 !important;
@@ -565,21 +562,15 @@ div.quick-search .search-button.show {
transition: background-color 100ms ease-out !important;
}
/*
* Quick search results
*/
div.quick-search .dropdown-menu {
--quick-search-item-delimiter-color: transparent;
--menu-item-icon-vert-offset: -.065em;
}
/*
* TO FIX: The quick search results dropdown has a backdrop issue with the tree panel
* when background effects are enabled in Electron.
* As a temporary workaround, the backdrop and transparency are disabled for the
* vertical layout.
*/
body.layout-vertical.background-effects div.quick-search .dropdown-menu {
--menu-background-color: var(--menu-background-color-no-backdrop) !important;
}
/* Item */
.quick-search .dropdown-menu *.dropdown-item {
padding: 8px 12px !important;
@@ -875,10 +866,7 @@ body.mobile .fancytree-node > span {
}
.tab-row-container .toggle-button {
--icon-button-size: 30px;
--icon-button-icon-ratio: .6;
margin: 3px 6px auto 8px !important;
margin: 6px 10px !important;
}
.tab-row-container {
@@ -908,7 +896,7 @@ body.electron.background-effects.layout-horizontal .tab-row-container .toggle-bu
bottom: 0;
left: -10px;
right: -10px;
top: 32px;
top: 29px;
height: 1px;
border-bottom: 1px solid var(--launcher-pane-horiz-border-color);
}
@@ -1055,14 +1043,6 @@ body.layout-horizontal .tab-row-widget .note-tab .note-tab-wrapper {
transform: translate3d(var(--tab-first-item-horiz-offset), 0, 0);
}
:root .tab-row-widget .note-tab .note-tab-icon {
padding-right: 5px; /* The gap between the icon and the title */
}
.tab-row-widget .note-tab[active] .note-tab-icon {
color: var(--active-tab-icon-color);
}
.tab-row-widget .note-tab .note-tab-title {
text-overflow: ellipsis;
}

View File

@@ -1,5 +0,0 @@
{
"about": {
"title": "حول تريليوم للملاحظات"
}
}

View File

@@ -276,12 +276,7 @@
"mime": "MIME 类型: ",
"file_size": "文件大小:",
"preview": "预览:",
"preview_not_available": "无法预览此类型的笔记。",
"diff_on": "显示差异",
"diff_off": "显示内容",
"diff_on_hint": "点击以显示笔记源代码差异",
"diff_off_hint": "点击以显示笔记内容",
"diff_not_available": "差异不可用。"
"preview_not_available": "无法预览此类型的笔记。"
},
"sort_child_notes": {
"sort_children_by": "按...排序子笔记",
@@ -592,18 +587,7 @@
"september": "九月",
"october": "十月",
"november": "十一月",
"december": "十二月",
"week_previous": "上周",
"week_next": "下周",
"month_previous": "上个月",
"month_next": "下个月",
"year": "年",
"year_previous": "上一年",
"year_next": "明年",
"today": "今天",
"week": "周",
"month": "月",
"list": "列表"
"december": "十二月"
},
"close_pane_button": {
"close_this_pane": "关闭此面板"
@@ -764,8 +748,7 @@
"book_properties": "集合属性",
"table": "表格",
"geo-map": "地理地图",
"board": "看板",
"include_archived_notes": "展示归档笔记"
"board": "看板"
},
"edited_notes": {
"no_edited_notes_found": "今天还没有编辑过的笔记...",
@@ -966,9 +949,7 @@
"no_attachments": "此笔记没有附件。"
},
"book": {
"no_children_help": "此类型为书籍的笔记没有任何子笔记,因此没有内容显示。请参阅 <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> 了解详情。",
"drag_locked_title": "锁定编辑",
"drag_locked_message": "无法拖拽,因为集合已被锁定编辑。"
"no_children_help": "此类型为书籍的笔记没有任何子笔记,因此没有内容显示。请参阅 <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> 了解详情。"
},
"editable_code": {
"placeholder": "在这里输入您的代码笔记内容..."
@@ -1426,7 +1407,7 @@
"button-tree-map": "树形地图"
},
"tree-context-menu": {
"open-in-a-new-tab": "在新标签页中打开",
"open-in-a-new-tab": "在新标签页中打开 <kbd>Ctrl+Click</kbd>",
"open-in-a-new-split": "在新分栏中打开",
"insert-note-after": "在后面插入笔记",
"insert-child-note": "插入子笔记",
@@ -1456,9 +1437,7 @@
"converted-to-attachments": "{{count}} 个笔记已被转换为附件。",
"convert-to-attachment-confirm": "确定要将选中的笔记转换为其父笔记的附件吗?",
"duplicate": "复制",
"open-in-popup": "快速编辑",
"archive": "归档",
"unarchive": "解压"
"open-in-popup": "快速编辑"
},
"shared_info": {
"help_link": "访问 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a> 获取帮助。",
@@ -1699,7 +1678,7 @@
"native-title-bar": "原生标题栏",
"native-title-bar-description": "对于 Windows 和 macOS关闭原生标题栏可使应用程序看起来更紧凑。在 Linux 上,保留原生标题栏可以更好地与系统集成。",
"background-effects": "启用背景效果(仅适用于 Windows 11",
"background-effects-description": "Mica 效果为应用窗口添加模糊且时尚的背景,营造出深度感和现代外观。「原生标题栏」必須被禁用。",
"background-effects-description": "Mica 效果为应用窗口添加模糊且时尚的背景,营造出深度感和现代外观。",
"restart-app-button": "重启应用程序以查看更改",
"zoom-factor": "缩放系数"
},
@@ -1988,21 +1967,14 @@
"delete_row": "删除行"
},
"board_view": {
"delete-note": "删除笔记...",
"delete-note": "删除笔记",
"move-to": "移动到",
"insert-above": "在上方插入",
"insert-below": "在下方插入",
"delete-column": "删除列",
"delete-column-confirmation": "确定要删除此列吗?此列下所有笔记中对应的属性也将被删除。",
"new-item": "新增项目",
"add-column": "添加列",
"archive-note": "存档笔记",
"unarchive-note": "解压笔记",
"new-item-placeholder": "输入笔记标题...",
"add-column-placeholder": "请输入列名...",
"edit-note-title": "点击编辑笔记标题",
"edit-column-title": "点击编辑列标题",
"remove-from-board": "从看板上移除"
"add-column": "添加列"
},
"command_palette": {
"tree-action-name": "树形:{{name}}",
@@ -2049,15 +2021,6 @@
"title": "性能",
"enable-motion": "启用过渡和动画",
"enable-shadows": "启用阴影",
"enable-backdrop-effects": "启用菜单、弹窗和面板的背景效果",
"enable-smooth-scroll": "启用平滑滚动",
"app-restart-required": "(需重启程序以应用更改)"
},
"pagination": {
"page_title": "第 {{startIndex}} 页 - 第 {{endIndex}} 页",
"total_notes": "{{count}} 笔记"
},
"collections": {
"rendering_error": "出现错误无法显示内容。"
"enable-backdrop-effects": "启用菜单、弹窗和面板的背景效果"
}
}

View File

@@ -1 +0,0 @@
{}

View File

@@ -276,12 +276,7 @@
"preview": "Vorschau:",
"preview_not_available": "Für diesen Notiztyp ist keine Vorschau verfügbar.",
"restore_button": "Wiederherstellen",
"delete_button": "Löschen",
"diff_on": "Zeige Differenz",
"diff_off": "Zeige Inhalt",
"diff_on_hint": "Klicke, um die Differenz des Notiz-Quellcodes zu zeigen",
"diff_off_hint": "Klicke, um den Notizinhalt zu zeigen",
"diff_not_available": "Differenz-Abgleich ist nicht verfügbar."
"delete_button": "Löschen"
},
"sort_child_notes": {
"sort_children_by": "Unternotizen sortieren nach...",
@@ -592,18 +587,7 @@
"october": "Oktober",
"november": "November",
"december": "Dezember",
"cannot_find_week_note": "Wochennotiz kann nicht gefunden werden",
"week": "Woche",
"week_previous": "vorherige Woche",
"week_next": "nächste Woche",
"month": "Monat",
"month_previous": "vorheriger Monat",
"month_next": "nächster Monat",
"year": "Jahr",
"year_previous": "vorheriges Jahr",
"year_next": "nächstes Jahr",
"list": "Liste",
"today": "Heute"
"cannot_find_week_note": "Wochennotiz kann nicht gefunden werden"
},
"close_pane_button": {
"close_this_pane": "Schließe diesen Bereich"
@@ -761,8 +745,7 @@
"book_properties": "Sammlungseigenschaften",
"table": "Tabelle",
"geo-map": "Weltkarte",
"board": "Tafel",
"include_archived_notes": "Zeige archivierte Notizen"
"board": "Tafel"
},
"edited_notes": {
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
@@ -963,9 +946,7 @@
"no_attachments": "Diese Notiz enthält keine Anhänge."
},
"book": {
"no_children_help": "Diese Notiz mit dem Notiztyp Buch besitzt keine Unternotizen, deshalb ist nichts zum Anzeigen vorhanden. Siehe <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">Wiki</a> für mehr Details.",
"drag_locked_title": "Für Bearbeitung gesperrt",
"drag_locked_message": "Das Ziehen ist nicht möglich, da die Sammlung für die Bearbeitung gesperrt ist."
"no_children_help": "Diese Notiz mit dem Notiztyp Buch besitzt keine Unternotizen, deshalb ist nichts zum Anzeigen vorhanden. Siehe <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">Wiki</a> für mehr Details."
},
"editable_code": {
"placeholder": "Gebe hier den Inhalt deiner Codenotiz ein..."
@@ -1390,7 +1371,7 @@
"button-tree-map": "Baumkarte"
},
"tree-context-menu": {
"open-in-a-new-tab": "In neuem Tab öffnen",
"open-in-a-new-tab": "In neuem Tab öffnen <kbd>Strg+Klick</kbd>",
"open-in-a-new-split": "In neuem Split öffnen",
"insert-note-after": "Notiz dahinter einfügen",
"insert-child-note": "Unternotiz einfügen",
@@ -1420,9 +1401,7 @@
"apply-bulk-actions": "Massenaktionen anwenden",
"converted-to-attachments": "{{count}} Notizen wurden als Anhang konvertiert.",
"convert-to-attachment-confirm": "Bist du sicher, dass du die ausgewählten Notizen in Anhänge ihrer übergeordneten Notizen umwandeln möchtest?",
"open-in-popup": "Schnellbearbeitung",
"archive": "Archiviere",
"unarchive": "Entarchivieren"
"open-in-popup": "Schnellbearbeitung"
},
"shared_info": {
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
@@ -1663,7 +1642,7 @@
"native-title-bar": "Native Anwendungsleiste",
"native-title-bar-description": "In Windows und macOS, sorgt das Deaktivieren der nativen Anwendungsleiste für ein kompakteres Aussehen. Unter Linux, sorgt das Aktivieren der nativen Anwendungsleiste für eine bessere Integration mit anderen Teilen des Systems.",
"background-effects": "Hintergrundeffekte aktivieren (nur Windows 11)",
"background-effects-description": "Der Mica Effekt fügt einen unscharfen, stylischen Hintergrund in Anwendungsfenstern ein. Dieser erzeugt Tiefe und ein modernes Auftreten. \"Native Titelleiste\" muss deaktiviert sein.",
"background-effects-description": "Der Mica Effekt fügt einen unscharfen, stylischen Hintergrund in Anwendungsfenstern ein. Dieser erzeugt Tiefe und ein modernes Auftreten.",
"restart-app-button": "Anwendung neustarten um Änderungen anzuwenden",
"zoom-factor": "Zoomfaktor"
},
@@ -1860,9 +1839,7 @@
"title": "Leistung",
"enable-motion": "Aktiviere Übergänge und Animationen",
"enable-shadows": "Aktiviere Schatten",
"enable-backdrop-effects": "Aktiviere Hintergrundeffekte für Menüs, Pop-up Fenster und Panele",
"enable-smooth-scroll": "Aktiviere sanftes Scrollen",
"app-restart-required": "(Ein Neustart der Anwendung ist erforderlich, damit die Änderungen wirksam werden)"
"enable-backdrop-effects": "Aktiviere Hintergrundeffekte für Menüs, Pop-up Fenster und Panele"
},
"code-editor-options": {
"title": "Editor"
@@ -2002,21 +1979,14 @@
"delete_row": "Zeile entfernen"
},
"board_view": {
"delete-note": "Lösche Notiz...",
"delete-note": "Lösche Notiz",
"move-to": "Verschiebe zu",
"insert-above": "Oberhalb einfügen",
"insert-below": "Unterhalb einfügen",
"delete-column": "Spalte entfernen",
"delete-column-confirmation": "Soll die Spalte wirklich gelöscht werden? Abhängige Attribute werden auch in den Notizen unter dieser Spalte gelöscht.",
"new-item": "Neuer Artikel",
"add-column": "Spalte hinzufügen",
"remove-from-board": "Entferne von Tafel",
"archive-note": "archiviere Notiz",
"unarchive-note": "entarchiviere Notiz",
"new-item-placeholder": "Notiz Titel eingeben...",
"add-column-placeholder": "Spaltenname eingeben...",
"edit-note-title": "Klicke zum Editieren des Notiz-Titels",
"edit-column-title": "Klicke zum Editieren des Spalten-Titels"
"add-column": "Spalte hinzufügen"
},
"command_palette": {
"tree-action-name": "Struktur: {{name}}",
@@ -2054,12 +2024,5 @@
},
"units": {
"percentage": "%"
},
"pagination": {
"page_title": "Seite {{startIndex}} von {{endIndex}}",
"total_notes": "{{count}} Notizen"
},
"collections": {
"rendering_error": "Aufgrund eines Fehlers können keine Inhalte angezeigt werden."
}
}

View File

@@ -592,18 +592,7 @@
"september": "September",
"october": "October",
"november": "November",
"december": "December",
"week": "Week",
"week_previous": "Previous week",
"week_next": "Next week",
"month": "Month",
"month_previous": "Previous month",
"month_next": "Next month",
"year": "Year",
"year_previous": "Previous year",
"year_next": "Next year",
"list": "List",
"today": "Today"
"december": "December"
},
"close_pane_button": {
"close_this_pane": "Close this pane"
@@ -764,8 +753,7 @@
"calendar": "Calendar",
"table": "Table",
"geo-map": "Geo Map",
"board": "Board",
"include_archived_notes": "Show archived notes"
"board": "Board"
},
"edited_notes": {
"no_edited_notes_found": "No edited notes on this day yet...",
@@ -966,9 +954,7 @@
"no_attachments": "This note has no attachments."
},
"book": {
"no_children_help": "This collection doesn't have any child notes so there's nothing to display. See <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> for details.",
"drag_locked_title": "Locked for editing",
"drag_locked_message": "Dragging not allowed since the collection is locked for editing."
"no_children_help": "This collection doesn't have any child notes so there's nothing to display. See <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> for details."
},
"editable_code": {
"placeholder": "Type the content of your code note here..."
@@ -1590,12 +1576,10 @@
"button-tree-map": "Tree map"
},
"tree-context-menu": {
"open-in-a-new-tab": "Open in a new tab",
"open-in-a-new-tab": "Open in a new tab <kbd>Ctrl+Click</kbd>",
"open-in-a-new-split": "Open in a new split",
"insert-note-after": "Insert note after",
"insert-child-note": "Insert child note",
"archive": "Archive",
"unarchive": "Unarchive",
"delete": "Delete",
"search-in-subtree": "Search in subtree",
"hoist-note": "Hoist note",
@@ -1863,7 +1847,7 @@
"native-title-bar": "Native title bar",
"native-title-bar-description": "For Windows and macOS, keeping the native title bar off makes the application look more compact. On Linux, keeping the native title bar on integrates better with the rest of the system.",
"background-effects": "Enable background effects (Windows 11 only)",
"background-effects-description": "The Mica effect adds a blurred, stylish background to app windows, creating depth and a modern look. \"Native title bar\" must be disabled.",
"background-effects-description": "The Mica effect adds a blurred, stylish background to app windows, creating depth and a modern look.",
"restart-app-button": "Restart the application to view the changes",
"zoom-factor": "Zoom factor"
},
@@ -1962,11 +1946,7 @@
"editorfeatures": {
"title": "Features",
"emoji_completion_enabled": "Enable Emoji auto-completion",
"emoji_completion_description": "If enabled, emojis can be easily inserted into text by typing `:`, followed by the name of an emoji.",
"note_completion_enabled": "Enable note auto-completion",
"note_completion_description": "If enabled, links to notes can be created by typing `@` followed by the title of a note.",
"slash_commands_enabled": "Enable slash commands",
"slash_commands_description": "If enabled, editing commands such as inserting line breaks or headings can be toggled by typing `/`."
"note_completion_enabled": "Enable note auto-completion"
},
"table_view": {
"new-row": "New row",
@@ -2002,21 +1982,14 @@
"delete_row": "Delete row"
},
"board_view": {
"delete-note": "Delete note...",
"remove-from-board": "Remove from board",
"archive-note": "Archive note",
"unarchive-note": "Unarchive note",
"delete-note": "Delete Note",
"move-to": "Move to",
"insert-above": "Insert above",
"insert-below": "Insert below",
"delete-column": "Delete column",
"delete-column-confirmation": "Are you sure you want to delete this column? The corresponding attribute will be deleted in the notes under this column as well.",
"new-item": "New item",
"new-item-placeholder": "Enter note title...",
"add-column": "Add Column",
"add-column-placeholder": "Enter column name...",
"edit-note-title": "Click to edit note title",
"edit-column-title": "Click to edit column title"
"add-column": "Add Column"
},
"command_palette": {
"tree-action-name": "Tree: {{name}}",
@@ -2058,12 +2031,5 @@
},
"units": {
"percentage": "%"
},
"pagination": {
"page_title": "Page of {{startIndex}} - {{endIndex}}",
"total_notes": "{{count}} notes"
},
"collections": {
"rendering_error": "Unable to show content due to an error."
}
}

View File

@@ -1563,7 +1563,7 @@
"button-tree-map": "Mapa de Árbol"
},
"tree-context-menu": {
"open-in-a-new-tab": "Abrir en nueva pestaña",
"open-in-a-new-tab": "Abrir en nueva pestaña <kbd>Ctrl+Click</kbd>",
"open-in-a-new-split": "Abrir en nueva división",
"insert-note-after": "Insertar nota después de",
"insert-child-note": "Insertar subnota",

View File

@@ -1357,7 +1357,7 @@
"button-tree-map": "Carte de l'arborescence"
},
"tree-context-menu": {
"open-in-a-new-tab": "Ouvrir dans un nouvel onglet",
"open-in-a-new-tab": "Ouvrir dans un nouvel onglet <kbd>Ctrl+Clic</kbd>",
"open-in-a-new-split": "Ouvrir dans une nouvelle division",
"insert-note-after": "Insérer une note après",
"insert-child-note": "Insérer une note enfant",

View File

@@ -63,7 +63,7 @@
"search_for_note_by_its_name": "cerca una nota per nome",
"cloned_note_prefix_title": "Le note clonate saranno mostrate nell'albero delle note con il dato prefisso",
"prefix_optional": "Prefisso (opzionale)",
"clone_to_selected_note": "Clona verso la nota selezionata <kbd>invio</kbd>",
"clone_to_selected_note": "Clona sotto la nota selezionata <kbd>invio</kbd>",
"no_path_to_clone_to": "Nessun percorso per clonare dentro.",
"note_cloned": "La nota \"{{clonedTitle}}\" è stata clonata in \"{{targetTitle}}\""
},
@@ -79,7 +79,7 @@
"ok": "OK",
"close": "Chiudi",
"delete_notes_preview": "Anteprima di eliminazione delle note",
"delete_all_clones_description": "Elimina anche tutti i cloni (può essere ripristinato nella sezione cambiamenti recenti)",
"delete_all_clones_description": "Elimina anche tutti i cloni (può essere disfatto tramite i cambiamenti recenti)",
"erase_notes_description": "L'eliminazione normale (soft) marca le note come eliminate e potranno essere recuperate entro un certo lasso di tempo (dalla finestra dei cambiamenti recenti). Selezionando questa opzione le note si elimineranno immediatamente e non sarà possibile recuperarle.",
"erase_notes_warning": "Elimina le note in modo permanente (non potrà essere disfatto), compresi tutti i cloni. Ciò forzerà un nuovo caricamento dell'applicazione.",
"cancel": "Annulla",
@@ -105,10 +105,7 @@
"format_html": "HTML - raccomandato in quanto mantiene tutti i formati",
"format_html_zip": "HTML in archivio ZIP - questo è raccomandato in quanto conserva tutta la formattazione.",
"format_markdown": "MArkdown - questo conserva la maggior parte della formattazione.",
"export_type_single": "Solo questa nota, senza le sottostanti",
"format_opml": "OPML - formato per scambio informazioni outline. Formattazione, immagini e files non sono inclusi.",
"opml_version_1": "OPML v.1.0 - solo testo semplice",
"opml_version_2": "OPML v2.0 - supporta anche HTML"
"export_type_single": "Solo questa nota, senza le sottostanti"
},
"password_not_set": {
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",

View File

@@ -66,8 +66,7 @@
"toggle-zen-mode": "禅モード",
"switch_to_mobile_version": "モバイル版に切り替え",
"switch_to_desktop_version": "デスクトップ版に切り替え",
"configure_launchbar": "ランチャーバーの設定",
"show_shared_notes_subtree": "共有ノートのサブツリーを表示"
"configure_launchbar": "ランチャーバーの設定"
},
"left_pane_toggle": {
"show_panel": "パネルを表示",
@@ -121,9 +120,7 @@
"september": "9月",
"october": "10月",
"november": "11月",
"december": "12月",
"cannot_find_day_note": "dayートが見つかりません",
"cannot_find_week_note": "weekートが見つかりません"
"december": "12月"
},
"note_icon": {
"change_note_icon": "ノートアイコンの変更",
@@ -194,8 +191,7 @@
"search_parameters": "検索パラメータ",
"unknown_search_option": "不明な検索オプション {{searchOptionName}}",
"search_note_saved": "検索ノートが {{- notePathTitle}} に保存されました",
"actions_executed": "アクションが実行されました。",
"ancestor": "祖先:"
"actions_executed": "アクションが実行されました。"
},
"shortcuts": {
"multiple_shortcuts": "同じアクションに対して複数のショートカットを設定する場合、カンマで区切ることができます。",
@@ -313,8 +309,7 @@
},
"import-status": "インポート状況",
"in-progress": "インポート中: {{progress}}",
"successful": "インポートは正常に終了しました。",
"explodeArchives": "<code>.zip</code>, <code>.enex</code> および <code>.opml</code> アーカイブの内容を読み取ります。"
"successful": "インポートは正常に終了しました。"
},
"password_not_set": {
"title": "パスワードが設定されていない",
@@ -370,7 +365,7 @@
},
"tree-context-menu": {
"advanced": "高度",
"open-in-a-new-tab": "新しいタブで開く",
"open-in-a-new-tab": "新しいタブで開く <kbd>Ctrl+Click</kbd>",
"open-in-a-new-split": "新しく分割して開く",
"insert-note-after": "ノートを後ろに挿入",
"insert-child-note": "子ノートを挿入",
@@ -396,10 +391,7 @@
"converted-to-attachments": "{{count}}ノートが添付ファイルに変換されました。",
"convert-to-attachment": "添付ファイルに変換",
"convert-to-attachment-confirm": "選択したノートを親ノートの添付ファイルに変換しますか?",
"open-in-popup": "クイックエディット",
"hoist-note": "ホイストノート",
"unhoist-note": "ノートをホイストしない",
"edit-branch-prefix": "ブランチの接頭辞を編集"
"open-in-popup": "クイックエディット"
},
"zen_mode": {
"button_exit": "禅モードを退出"
@@ -440,16 +432,10 @@
"search_notes_description": "高度な検索を開く",
"search_subtree_description": "現在のサブツリー内を検索",
"search_history_title": "検索履歴を表示",
"search_history_description": "過去の検索結果を見る",
"show_attachments_title": "添付ファイルを表示",
"show_attachments_description": "ノートの添付ファイルを表示",
"configure_launch_bar_title": "ランチャーバーの設定",
"configure_launch_bar_description": "ランチャーバーの構成を開き、項目を追加または削除します。"
"search_history_description": "過去の検索結果を見る"
},
"delete_note": {
"delete_note": "ノートを削除",
"delete_matched_notes": "一致したノートを削除",
"delete_matched_notes_description": "これにより、一致したノートが削除されます。"
"delete_note": "ノートを削除"
},
"board_view": {
"delete-note": "ノートを削除",
@@ -458,15 +444,7 @@
"delete-column": "列を削除",
"delete-column-confirmation": "本当にこの列を削除しますか?対応する属性は、この列の下のノートでも削除されます。",
"new-item": "新しいアイテム",
"add-column": "列を追加",
"remove-from-board": "ボードから削除",
"archive-note": "アーカイブノート",
"unarchive-note": "ノートのアーカイブを解除",
"move-to": "移動先",
"new-item-placeholder": "ノートのタイトルを入力...",
"add-column-placeholder": "列名を入力...",
"edit-note-title": "クリックしてノートのタイトルを編集",
"edit-column-title": "クリックして列のタイトルを編集"
"add-column": "列を追加"
},
"code_buttons": {
"execute_button_title": "スクリプトを実行",
@@ -476,9 +454,7 @@
"sql_console_saved_message": "SQLコンソールが {{note_path}} に保存されました"
},
"execute_script": {
"execute_script": "スクリプトを実行",
"help_text": "一致したノートに対して簡単なスクリプトを実行できます。",
"example_2": "より複雑な例としては、一致したノートの属性をすべて削除することが挙げられます:"
"execute_script": "スクリプトを実行"
},
"script_executor": {
"execute_script": "スクリプトを実行",
@@ -508,8 +484,7 @@
"calendar": "カレンダー",
"table": "テーブル",
"geo-map": "ジオマップ",
"board": "ボード",
"include_archived_notes": "アーカイブされたノートを表示"
"board": "ボード"
},
"note_types": {
"geo-map": "ジオマップ",
@@ -518,7 +493,7 @@
"text": "テキスト",
"code": "コード",
"saved-search": "検索の保存",
"relation-map": "リレーションマップ",
"relation-map": "関係マップ",
"note-map": "ノートマップ",
"render-note": "レンダリングノート",
"book": "コレクション",
@@ -588,14 +563,7 @@
"settings": "ノートの変更履歴の設定",
"file_size": "ファイルサイズ:",
"preview": "プレビュー:",
"preview_not_available": "このノートタイプではプレビューは利用できません。",
"diff_on": "差分を表示",
"diff_off": "内容を表示",
"diff_on_hint": "クリックしてノートソースとの差分を表示",
"diff_off_hint": "クリックしてノートの内容を表示",
"diff_not_available": "差分は利用できません。",
"snapshot_interval": "ノートの変更履歴の記録間隔: {{seconds}} 秒。",
"maximum_revisions": "ノートの変更履歴の記録制限: {{number}}."
"preview_not_available": "このノートタイプではプレビューは利用できません。"
},
"attachments_actions": {
"download": "ダウンロード",
@@ -645,8 +613,7 @@
"intro_placed": "このノートは以下のパスに置かれる:",
"intro_not_placed": "このノートはまだノートツリーに配置されていません。",
"archived": "アーカイブされた",
"search": "検索",
"outside_hoisted": "このパスはホイストされたノートの外側にあるため、ホイストを解除する必要があります。"
"search": "検索"
},
"note_properties": {
"info": "情報"
@@ -726,29 +693,13 @@
"dialog_title": "埋め込みノート",
"box_size_prompt": "埋め込みノート枠のサイズ:",
"button_include": "埋め込みノート",
"label_note": "ノート",
"box_size_small": "スモール (~ 10 行)",
"box_size_medium": "ミディアム (~ 30 行)",
"box_size_full": "フル (ボックスに全文が表示されます)"
"label_note": "ノート"
},
"ancestor": {
"placeholder": "ノート名で検索",
"label": "祖先",
"depth_label": "深さ",
"depth_doesnt_matter": "関係ない",
"depth_eq": "ちょうど {{count}} つ下の階層",
"direct_children": "直接の子",
"depth_gt": "{{count}} より下の階層",
"depth_lt": "{{count}} より上の階層"
"placeholder": "ノート名で検索"
},
"move_to": {
"search_placeholder": "ノート名で検索",
"dialog_title": "ノートを移動...",
"notes_to_move": "移動させるノート",
"target_parent_note": "対象の親ノート",
"move_button": "選択したノートに移動",
"error_no_path": "移動するパスがありません。",
"move_success_message": "選択したノートは以下に移動されました "
"search_placeholder": "ノート名で検索"
},
"web_view": {
"web_view": "Web ビュー",
@@ -789,8 +740,7 @@
"default_description": "Triliumは、ワイドスクリーンで最大化された画面での可読性を向上させるために、デフォルトでコンテンツの最大幅を制限しています。",
"max_width_label": "最大コンテンツ幅",
"max_width_unit": "ピクセル",
"apply_changes_description": "コンテンツ幅の変更を適用するには、クリックしてください",
"reload_description": "外観オプションからの変更"
"apply_changes_description": "コンテンツ幅の変更を適用するには、クリックしてください"
},
"theme": {
"title": "アプリのテーマ",
@@ -956,16 +906,10 @@
"close": "閉じる"
},
"info": {
"closeButton": "閉じる",
"modalTitle": "情報メッセージ",
"okButton": "OK"
"closeButton": "閉じる"
},
"protected_session_password": {
"close_label": "閉じる",
"modal_title": "保護されたセッション",
"help_title": "保護されたノートに関するヘルプ",
"form_label": "リクエストされたアクションを続行するには、パスワードを入力して保護されたセッションを開始する必要があります:",
"start_button": "保護されたセッションを開始"
"close_label": "閉じる"
},
"modal": {
"close": "閉じる",
@@ -1003,81 +947,7 @@
"digits": "桁",
"inheritable_title": "継承属性は、このツリー配下のすべての子孫に継承されます。",
"inheritable": "継承",
"related_notes_title": "このラベルが付いた他のノート",
"attr_detail_title": "属性の詳細なタイトル",
"target_note_title": "リレーションは、ソースノートとターゲットノート間の名前付き接続です。",
"target_note": "対象のノート",
"promoted_title": "プロモート属性はノートに目立つように表示されます。",
"promoted": "プロモート",
"promoted_alias_title": "プロモート属性のUIに表示される名前。",
"inverse_relation_title": "このリレーションがどのリレーションの反対であるかを定義するオプション設定。例:父と息子は互いに逆のリレーションです。",
"inverse_relation": "逆リレーション",
"more_notes": "その他のノート",
"label": "ラベルの詳細",
"label_definition": "ラベル定義の詳細",
"relation": "リレーションの詳細",
"relation_definition": "リレーション定義の詳細",
"disable_versioning": "自動バージョン管理を無効にします。例えば、スクリプト用の大きなJSライブラリなど、重要ではない大きなートに便利です",
"calendar_root": "dayートのルートとして使用するートをマークします。このようにマークできるのは 1 つだけです。",
"archived": "このラベルの付いたノートは、デフォルトでは検索結果に表示されません (ジャンプ先、リンクの追加ダイアログなどにも表示されません)。",
"exclude_from_export": "ノート(サブツリーを含む)はノートのエクスポートには含まれません",
"run": "どのイベントでスクリプトを実行するかを定義します。可能な値は次の通り:\n<ul>\n<li>frontendStartup - Trilium フロントエンドが起動(または更新)されたとき。モバイルは除く</li>\n<li>mobileStartup - モバイルで Trilium フロントエンドが起動(または更新)されたとき。</li>\n<li>backendStartup - Trilium バックエンドが起動したとき</li>\n<li>hourly - 1時間に1回実行します。 <code>runAtHour</code> というラベルを追加して、実行時刻を指定できます。</li>\n<li>daily - 1日に1回実行</li>\n</ul>",
"run_on_instance": "どの Trilium インスタンスでこれを実行するかを定義します。デフォルトはすべてのインスタンスです。",
"run_at_hour": "何時に実行するかを指定します。 <code>#run=hourly</code> と併用してください。1日に複数回実行したい場合は、複数回定義できます。",
"disable_inclusion": "このラベルが付いたスクリプトは親スクリプトの実行には含まれません。",
"sorted": "子ノートをアルファベット順に並べ替える",
"sort_direction": "ASCデフォルトまたは DESC",
"sort_folders_first": "フォルダ(子を持つノート)を上にして並べる",
"top": "指定されたノートをその親ノートの一番上に表示します(ソートされた親ノートにのみ適用されます)",
"hide_promoted_attributes": "このノートのプロモート属性を非表示にする",
"read_only": "エディターは読み取り専用モードです。テキストとコードノートのみ機能します。",
"auto_read_only_disabled": "テキスト/コードノートは、サイズが大きすぎる場合、自動的に読み取りモードに設定されます。このラベルをノートに追加することで、ノートごとにこの動作を無効にすることができます",
"app_css": "Trilium アプリケーションに読み込まれ、Trilium の外観を変更するために使用できる CSS ノートをマークします。",
"app_theme": "Trilium のフルテーマである CSS ートをマークし、Trilium オプションで利用できるようにします。",
"app_theme_base": "「next」、「next-light」、または「next-dark」に設定すると、従来のテーマではなく、対応する TriliumNext テーマ (auto、light、または dark) がカスタム テーマのベースとして使用されます。",
"css_class": "このラベルの値は、ツリー内の特定のートを表すードにCSSクラスとして追加されます。これは高度なテーマ設定に役立ちます。テンプレートートで使用できます。",
"icon_class": "このラベルの値は、ツリー上のアイコンにCSSクラスとして追加され、ツリー内のートを視覚的に区別するのに役立ちます。例えば、bx bx-home のように、アイコンは boxicons から取得されます。テンプレートノートで使用できます。",
"page_size": "ートリストの1ページあたりの項目数",
"custom_request_handler": "<a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">カスタムリクエストハンドラー</a>を参照してください",
"custom_resource_provider": "<a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">カスタムリクエストハンドラー</a>を参照してください",
"widget": "このートをカスタムウィジェットとしてマークし、Trilium コンポーネントツリーに追加します",
"workspace": "このノートをワークスペースとしてマークし、簡単にホイストできるようにします",
"workspace_icon_class": "このートにホイストされたときにタブで使用されるボックスアイコンのCSSクラスを定義します",
"workspace_tab_background_color": "このートにホイストされたときにートタブで使用されるCSSでの色",
"workspace_calendar_root": "ワークスペースごとのカレンダールートを定義する",
"workspace_template": "このノートは、新しいノートを作成するときに利用可能なテンプレートの選択肢に表示されますが、このテンプレートを含むワークスペースにホイストされた場合にのみ表示されます",
"search_home": "新しい検索ノートはこのノートの子として作成されます",
"workspace_search_home": "このワークスペースノートの祖先にホイストされた新しい検索ノートは、このノートの子として作成されます",
"inbox": "新しいートのデフォルトのinboxの場所 - サイドバーの「新しいノート」ボタンを使用してノートを作成すると、ノートは <code>#inbox</code> ラベルでマークされたノートの子ノートとして作成されます。",
"workspace_inbox": "このワークスペースートの祖先にホイストされた場合、新規ートのデフォルトのinboxの場所",
"sql_console_home": "SQLコンソールートのデフォルトの場所",
"bookmark_folder": "このラベルの付いたノートは、ブックマークにフォルダとして表示されます(子フォルダへのアクセスを許可します)",
"share_hidden_from_tree": "このートは左側のナビゲーションツリーには表示されていませんが、URL からアクセスできます",
"share_external_link": "ノートは共有ツリー内で外部ウェブサイトへのリンクとして機能します",
"share_alias": "https://your_trilium_host/share/[your_alias] でノートを利用できるようにエイリアスを定義します",
"share_omit_default_css": "デフォルトの共有ページのCSSは省略されます。スタイルを大幅に変更する場合に使用してください。",
"share_root": "/share root で提供されるノートをマークする。",
"share_raw": "ートはHTMLラッパーなしでそのままの形式で提供されます",
"share_disallow_robot_indexing": "<code>X-Robots-Tag: noindex</code> ヘッダーにより、このノートのロボットによるインデックス作成を禁止します",
"share_credentials": "この共有ノートにアクセスするには認証情報が必要です。値は「ユーザー名:パスワード」の形式である必要があります。子ノート/画像に適用するには、これを継承可能にすることを忘れないでください。",
"share_index": "このラベルの付いたノートには、共有ノートのルートがすべてリストされます",
"display_relations": "表示するリレーション名をカンマで区切って指定します。それ以外のリレーション名は非表示になります。",
"hide_relations": "非表示にするリレーション名をカンマで区切って指定します。それ以外のリレーションは表示されます。",
"template": "このノートは、新しいノートを作成するときに利用可能なテンプレートの選択肢に表示されます",
"toc": "<code>#toc</code> または <code>#toc=show</code><code> は目次を強制的に表示し、<code>#toc=hide</code> は目次を強制的に非表示にします。ラベルが存在しない場合は、グローバル設定が適用されます",
"color": "ノートツリー、リンクなどのノートの色を定義します。 'red' や #a13d5f などの有効な CSS カラー値を使用します",
"keyboard_shortcut": "このノートにすぐにジャンプするキーボードショートカットを定義します。例: 「ctrl+alt+e」。変更を有効にするには、フロントエンドをリロードする必要があります。",
"keep_current_hoisting": "このリンクを開いても、ノートが現在のホイストされたサブツリーに表示できない場合でも、ホイストは変更されません。",
"execute_button": "現在のコードノートを実行するボタンのタイトル",
"execute_description": "実行ボタンと一緒に表示される現在のコードノートの詳細な説明",
"exclude_from_note_map": "このラベルの付いたノートはノートマップから非表示になります",
"new_notes_on_top": "新しいノートは親ノートの下部ではなく上部に作成されます。",
"hide_highlight_widget": "ハイライトリスト ウィジェットを非表示にする",
"run_on_note_creation": "バックエンドでノートが作成された際に実行されます。特定のサブツリー配下に作成されたすべてのノートに対してスクリプトを実行したい場合は、このリレーションを使用してください。その場合は、サブツリーのルートノートにスクリプトを作成し、継承可能にしてください。サブツリー内(任意の深さ)に新しいノートが作成されると、スクリプトが実行されます。",
"run_on_child_note_creation": "このリレーションが定義されているノートの下に新しいノートが作成されたときに実行されます",
"render_note": "「HTMLートをレンダリング」タイプのートは、コードートHTMLまたはスクリプトを使用してレンダリングされます。このリレーションを使用して、どのートをレンダリングするかを指定する必要があります",
"other_notes_with_name": "{{attributeType}} の名前が「{{attributeName}}」であるその他のノート",
"color_type": "色"
"related_notes_title": "このラベルが付いた他のノート"
},
"link_context_menu": {
"open_note_in_popup": "クイックエディット",
@@ -1086,8 +956,7 @@
"open_note_in_new_window": "新しいウィンドウでノートを開く"
},
"note_tooltip": {
"quick-edit": "クイックエディット",
"note-has-been-deleted": "ノートは削除されました。"
"quick-edit": "クイックエディット"
},
"protect_note": {
"toggle-on": "ノートを保護",
@@ -1130,9 +999,7 @@
"automatically-collapse-notes-title": "一定期間使用されないと、ツリーを整理するためにノートは折りたたまれます。",
"save-changes": "変更を保存して適用",
"auto-collapsing-notes-after-inactivity": "非アクティブ状態が続いたためノートが自動で折りたたまれます...",
"create-child-note": "子ノートを作成",
"hoist-this-note-workspace": "このノートをホイストする(ワークスペース)",
"unhoist": "ホイスト解除"
"create-child-note": "子ノートを作成"
},
"bulk_actions": {
"bulk_actions": "一括操作",
@@ -1182,8 +1049,7 @@
"reset": "リセット",
"move-to-visible-launchers": "可視ランチャーに移動",
"move-to-available-launchers": "利用可能なランチャーに移動",
"duplicate-launcher": "ランチャーの複製 <kbd data-command=\"duplicateSubtree\">",
"reset_launcher_confirm": "本当に「{{title}}」をリセットしますか? このノート(およびその子ノート)のすべてのデータと設定が失われ、ランチャーは元の場所に戻ります。"
"duplicate-launcher": "ランチャーの複製 <kbd data-command=\"duplicateSubtree\">"
},
"editable-text": {
"auto-detect-language": "自動検出"
@@ -1207,8 +1073,7 @@
"editor_type": {
"label": "書式設定ツールバー",
"floating": {
"description": "編集ツールがカーソル付近に表示されます;",
"title": "フローティング"
"description": "編集ツールがカーソル付近に表示されます;"
},
"fixed": {
"title": "固定",
@@ -1239,9 +1104,7 @@
"show_login_link_description": "共有ページの下部にログインリンクを追加",
"share_root_found": "共有ルートノート '{{noteTitle}}' の準備が完了",
"share_root_not_found": "#shareRoot のラベルが付いたノートが見つかりません",
"share_root_not_shared": "ノート '{{noteTitle}}' は #shareRoot のラベルを持っていますが、共有されていません",
"show_login_link": "共有テーマにログインリンクを表示する",
"check_share_root": "共有ルートのステータスを確認"
"share_root_not_shared": "ノート '{{noteTitle}}' は #shareRoot のラベルを持っていますが、共有されていません"
},
"time_selector": {
"invalid_input": "入力された時間値が有効な数値ではありません。",
@@ -1269,10 +1132,7 @@
"cpu_arch_warning": {
"title": "ARM64版をダウンロードしてください",
"dont_show_again": "この警告を二度と表示しない",
"continue_anyway": "とにかく続ける",
"message_windows": "TriliumNext は現在エミュレーションを実行しています。つまり、ARM版WindowsデバイスでIntelx64版を使用していることになります。これはパフォーマンスとバッテリー寿命に重大な影響を及ぼします。",
"recommendation": "最適なエクスペリエンスを得るには、リリース ページから TriliumNext のネイティブ ARM64 バージョンをダウンロードしてください。",
"download_link": "ネイティブ版をダウンロード"
"continue_anyway": "とにかく続ける"
},
"editorfeatures": {
"emoji_completion_enabled": "絵文字のオートコンプリートを有効",
@@ -1368,9 +1228,7 @@
"add_label": "ラベルを追加",
"label_name_placeholder": "ラベル名",
"label_name_title": "英数字、アンダーバー、コロンが使用可能な文字です。",
"new_value_placeholder": "新しい値",
"help_text": "一致したすべてのノートに:",
"help_text_note": "このメソッドを値なしで呼び出すこともできます。その場合、ラベルは値なしでノートに割り当てられます。"
"new_value_placeholder": "新しい値"
},
"delete_label": {
"delete_label": "ラベルを削除",
@@ -1390,14 +1248,12 @@
"label_name_placeholder": "ラベル名",
"label_name_title": "英数字、アンダーバー、コロンが使用可能な文字です。",
"new_value_placeholder": "新しい値",
"help_text_note": "このメソッドは値なしで呼び出すこともできます。その場合、値なしでラベルがノートに割り当てられます。",
"help_text": "一致したすべてのノートで、既存のラベルの値を変更する。"
"help_text_note": "このメソッドは値なしで呼び出すこともできます。その場合、値なしでラベルがノートに割り当てられます。"
},
"add_relation": {
"add_relation": "関係を追加",
"relation_name": "関係の名前",
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。",
"create_relation_on_all_matched_notes": "一致したすべてのノートに対して、指定されたリレーションを作成します。"
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。"
},
"delete_relation": {
"delete_relation": "関係を削除",
@@ -1415,8 +1271,7 @@
"update_relation_target": {
"update_relation": "関係の更新",
"relation_name": "関係の名前",
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。",
"on_all_matched_notes": "一致したすべてのノートに"
"allowed_characters": "英数字、アンダーバー、コロンが使用可能な文字です。"
},
"revisions_button": {
"note_revisions": "ノートの変更履歴"
@@ -1466,9 +1321,7 @@
"specify_new_relation_name": "新しい関係の名前(使用可能な文字: 英数字、コロン、アンダースコア)を指定:",
"note_not_found": "ノート {{noteId}} が見つかりません!",
"enter_title_of_new_note": "新しいノートのタイトルを入力",
"default_new_note_title": "新しいノート",
"cannot_match_transform": "変換を一致させることができません: {{transform}}",
"click_on_canvas_to_place_new_note": "キャンバスをクリックして新しいノートを配置"
"default_new_note_title": "新しいノート"
},
"database_anonymization": {
"title": "データベースの匿名化",
@@ -1511,8 +1364,7 @@
"label_year_comparison": "数値比較(>、>=、<も含む)。",
"label_date_created": "過去1ヶ月以内に作成されたート",
"error": "検索エラー: {{error}}",
"search_prefix": "検索:",
"placeholder": "全文 キーワード、#tag = value..."
"search_prefix": "検索:"
},
"delete_revisions": {
"delete_note_revisions": "ノートの変更履歴を削除",
@@ -1526,8 +1378,7 @@
"example_new_title": "<code>NEW: ${note.title}</code> - 一致したノートの名前の前に 'NEW: ' を付ける",
"example_date_prefix": "<code>${note.dateCreatedObj.format('MM-DD:')}: ${note.title}</code> - マッチしたノートの前にノートの作成月日を付ける",
"api_docs": "詳細については、 <a href='https://zadam.github.io/trilium/backend_api/Note.html'>note</a> および <a href='https://day.js.org/docs/en/display/format'>dateCreatedObj / utcDateCreatedObj properties</a> の API ドキュメントを参照してください。",
"evaluated_as_js_string": "与えられた値はJavaScript文字列として評価されるため、注入された<code>note</code>変数noteは名前が変更されますを介して動的なコンテンツで強化できます。例:",
"click_help_icon": "右側のヘルプアイコンをクリックすると、すべてのオプションが表示されます"
"evaluated_as_js_string": "与えられた値はJavaScript文字列として評価されるため、注入された<code>note</code>変数noteは名前が変更されますを介して動的なコンテンツで強化できます。例:"
},
"electron_integration": {
"desktop-application": "デスクトップアプリケーション",
@@ -1541,243 +1392,5 @@
"zoom_factor": {
"description": "ズームは CTRL+- と CTRL+= のショートカットでも操作可能。",
"title": "ズーム倍率(デスクトップビルドのみ)"
},
"jump_to_note": {
"search_placeholder": "名前またはタイプでノートを検索 > コマンドを検索...",
"search_button": "全文検索"
},
"markdown_import": {
"dialog_title": "Markdownをインポート",
"modal_body_text": "ブラウザサンドボックスのため、JavaScriptからクリップボードの内容を直接読み込むことはできません。インポートするMarkdownを以下のテキストエリアに貼り付け、インポートボタンをクリックしてください",
"import_button": "インポート",
"import_success": "Markdown コンテンツがドキュメントにインポートされました。"
},
"note_type_chooser": {
"change_path_prompt": "新しいノートを作成する場所を変更する:",
"search_placeholder": "名前によるパスの検索 (空の場合はデフォルト)",
"modal_title": "ノートタイプを選択",
"modal_body": "新しいノートのノートタイプ / テンプレートを選択してください:",
"templates": "テンプレート",
"builtin_templates": "組み込みテンプレート"
},
"prompt": {
"title": "プロンプト",
"ok": "OK",
"defaultTitle": "プロンプト"
},
"upload_attachments": {
"upload_attachments_to_note": "ノートに添付ファイルをアップロード",
"choose_files": "ファイルを選択",
"files_will_be_uploaded": "ファイルは {{noteTitle}} に添付ファイルとしてアップロードされます",
"options": "オプション",
"shrink_images": "画像を縮小",
"upload": "アップロード",
"tooltip": "このオプションにチェックを入れると、Trilium はアップロードされた画像をスケーリングと最適化によって縮小しようとします。これにより、画質が多少変化する可能性があります。チェックを外すと、画像は変更されずにアップロードされます。"
},
"attribute_editor": {
"help_text_body1": "ラベルを追加するには、例 <code>#rock</code> と入力します。値も追加したい場合は、例 <code>#year = 2020</code> と入力します",
"help_text_body2": "リレーションについては、<code>~author = @</code> と入力すると、オートコンプリートが表示され、目的のノートを検索できるようになります。",
"placeholder": "ここにラベルとリレーションを入力"
},
"move_note": {
"on_all_matched_notes": "一致したすべてのノートに"
},
"onclick_button": {
"no_click_handler": "ボタン ウィジェット '{{componentId}}' にはクリック ハンドラーが定義されていません"
},
"protected_session_status": {
"active": "保護されたセッションが有効です。クリックして保護されたセッションを終了します。",
"inactive": "クリックして保護されたセッションに入る"
},
"editable_code": {
"placeholder": "ここにコードノートの内容を入力..."
},
"editable_text": {
"placeholder": "ここにノートの内容を入力..."
},
"empty": {
"open_note_instruction": "以下の入力欄にノートのタイトルを入力するか、ツリー内のノートを選択してノートを開きます。"
},
"file": {
"too_big": "パフォーマンス上の理由により、プレビューではファイルの最初の {{maxNumChars}} 文字のみが表示されます。ファイル全体を表示するには、ファイルをダウンロードして外部で開いてください。"
},
"protected_session": {
"enter_password_instruction": "保護されたノートを表示するにはパスワードを入力する必要があります:"
},
"render": {
"note_detail_render_help_1": "このヘルプートが表示されるのは、このートの「HTML のレンダリング」タイプには、正常に機能するために必要なリレーションがないためです。"
},
"consistency_checks": {
"find_and_fix_button": "一貫性の問題を見つけて修正する",
"finding_and_fixing_message": "一貫性の問題を見つけて修正中…"
},
"vacuum_database": {
"title": "データベースのバキューム",
"description": "これによりデータベースが再構築され、通常はデータベースファイルのサイズが小さくなります。実際のデータは変更されません。",
"button_text": "データベースをバキューム",
"vacuuming_database": "データベースのバキュームを実行中...",
"database_vacuumed": "データベースのバキューム処理が完了しました"
},
"ribbon": {
"promoted_attributes_message": "プロモート属性がノートに存在する場合、プロモート属性のリボンタブが自動的に開きます",
"edited_notes_message": "編集したートのリボンタブは、dayートで自動的に開きます"
},
"ui-performance": {
"enable-motion": "トランジションとアニメーションを有効にする",
"enable-shadows": "影を有効にする",
"enable-backdrop-effects": "メニュー、ポップアップ、パネルの背景効果を有効にする"
},
"code_mime_types": {
"title": "ドロップダウンで利用可能なMIMEタイプ"
},
"attachment_erasure_timeout": {
"attachment_erasure_timeout": "添付ファイル消去のタイムアウト",
"attachment_auto_deletion_description": "定義されたタイムアウト後にノートによって参照されなくなった場合、添付ファイルは自動的に削除 (および消去) されます。",
"erase_attachments_after": "使用されていない添付ファイルを消去する期間:",
"manual_erasing_description": "手動で消去をトリガーすることもできます (上記で定義したタイムアウトを考慮せずに):",
"erase_unused_attachments_now": "使用されていない添付ノートを今すぐ消去",
"unused_attachments_erased": "使用されていない添付ファイルは削除されました。"
},
"network_connections": {
"network_connections_title": "ネットワーク接続",
"check_for_updates": "アップデートを自動的に確認する"
},
"note_erasure_timeout": {
"note_erasure_timeout_title": "ノート消去のタイムアウト",
"note_erasure_description": "削除されたノート(および属性、変更履歴など)最初は削除済みとしてマークされるだけで、「最近の変更」ダイアログから復元できます。一定期間が経過すると、削除されたノートは「消去」され、内容は復元できなくなります。この設定では、ノートを削除してから消去するまでの期間を設定できます。",
"erase_notes_after": "ノートを消去する間隔:",
"manual_erasing_description": "手動で消去をトリガーすることもできます (上記で定義したタイムアウトを考慮せずに):",
"erase_deleted_notes_now": "削除したノートを今すぐ消去",
"deleted_notes_erased": "削除されたノートは消去されました。"
},
"revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "ノートの変更履歴の記録間隔",
"note_revisions_snapshot_description": "ノートの変更履歴の記録間隔は、そのノートに対して新しい変更が作成されるまでの時間です。詳細については、<doc>wiki</doc> をご覧ください。",
"snapshot_time_interval_label": "ノートの変更履歴が記憶される時間:"
},
"revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "ノートの変更履歴の記録制限",
"note_revisions_snapshot_limit_description": "ノートの変更履歴の記録制限とは、各ノートに保存できる変更履歴の最大数を指します。-1 は制限なし、0 はすべての変更履歴を削除することを意味します。#versioningLimit ラベルを使用して、1 つのノートの最大変更数を設定できます。",
"snapshot_number_limit_label": "ノートの変更履歴の記録数の制限:",
"snapshot_number_limit_unit": "スナップショット",
"erase_excess_revision_snapshots": "余分な変更履歴を今すぐ消去",
"erase_excess_revision_snapshots_prompt": "余分な変更履歴が消去されました。"
},
"editability_select": {
"note_is_read_only": "ノートは読み取り専用ですが、ボタンをクリックすると編集できます。"
},
"find": {
"case_sensitive": "大文字と小文字を区別",
"match_words": "単語が一致",
"find_placeholder": "テキスト内を検索...",
"replace_placeholder": "置換対象...",
"replace": "置換",
"replace_all": "すべて置換"
},
"title_bar_buttons": {
"window-on-top": "ウィンドウを最前面に維持"
},
"note_detail": {
"could_not_find_typewidget": "タイプ {{type}} の typeWidget が見つかりませんでした"
},
"watched_file_update_status": {
"ignore_this_change": "この変更を無視する"
},
"image": {
"copied-to-clipboard": "画像への参照がクリップボードにコピーされました。これは任意のテキストノートに貼り付けることができます。",
"cannot-copy": "画像参照をクリップボードにコピーできませんでした。"
},
"entrypoints": {
"note-revision-created": "ノートの改訂版が作成されました。",
"sql-error": "SQL クエリの実行中にエラーが発生しました: {{message}}"
},
"branches": {
"cannot-move-notes-here": "ここにノートを移動することはできません。",
"delete-status": "ステータスを削除",
"delete-notes-in-progress": "削除進行中のノート: {{count}}",
"delete-finished-successfully": "削除が正常に完了しました。",
"undeleting-notes-in-progress": "削除済みのノートを復元中: {{count}}",
"undeleting-notes-finished-successfully": "ノートの復元が正常に完了しました。"
},
"frontend_script_api": {
"async_warning": "`api.runOnBackend()` に非同期関数を渡していますが、これは意図したとおりに動作しない可能性があります。\\n関数を同期させる(`async` キーワードを削除する)か、`api.runAsyncOnBackendWithManualTransactionHandling()` を使用してください。",
"sync_warning": "`api.runAsyncOnBackendWithManualTransactionHandling()` に同期関数を渡していますが、\\n代わりに `api.runOnBackend()` を使用する必要がある可能性があります。"
},
"ws": {
"sync-check-failed": "同期チェックに失敗しました!",
"consistency-checks-failed": "整合性チェックに失敗しました! 詳細はログを参照してください。",
"encountered-error": "エラー「{{message}}」が発生しました。コンソールを確認してください。"
},
"hoisted_note": {
"confirm_unhoisting": "要求されたノート「{{requestedNote}}」は、ホイストされたノート「{{hoistedNote}}」サブツリーの外部にあるため、ノートにアクセスするにはホイストを解除する必要があります。ホイスト解除を続行しますか?"
},
"image_context_menu": {
"copy_reference_to_clipboard": "参照をクリップボードにコピー",
"copy_image_to_clipboard": "画像をクリップボードにコピー"
},
"note_autocomplete": {
"search-for": "「{{term}}」を検索",
"create-note": "子ノート「{{term}}」を作成してリンクする",
"insert-external-link": "「{{term}}」への外部リンクを挿入",
"clear-text-field": "テキストフィールドを消去",
"show-recent-notes": "最近のノートを表示",
"full-text-search": "全文検索"
},
"geo-map": {
"create-child-note-title": "新しい子ノートを作成し、マップに追加する",
"create-child-note-instruction": "地図をクリックしてその場所に新しいートを作成するか、Esc キーを押して閉じます。",
"unable-to-load-map": "マップを読み込めません。"
},
"geo-map-context": {
"open-location": "現在位置を表示",
"remove-from-map": "マップから削除",
"add-note": "この場所にマーカーを追加"
},
"help-button": {
"title": "関連するヘルプページを開く"
},
"content_widget": {
"unknown_widget": "「{{id}}」のウィジェットは不明です。"
},
"switch_layout_button": {
"title_vertical": "編集パネルを下に移動",
"title_horizontal": "編集パネルを左に移動"
},
"toggle_read_only_button": {
"unlock-editing": "編集のロックを解除",
"lock-editing": "編集をロック"
},
"book_properties_config": {
"hide-weekends": "週末を非表示",
"display-week-numbers": "週番号を表示",
"map-style": "マップスタイル:",
"max-nesting-depth": "最大階層の深さ:",
"show-scale": "スケールを表示"
},
"call_to_action": {
"next_theme_title": "新しいTriliumテーマをお試しください",
"next_theme_message": "現在、レガシーテーマを使用しています。新しいテーマを試してみませんか?",
"next_theme_button": "新しいテーマを試す",
"background_effects_title": "背景効果が安定しました",
"background_effects_message": "Windowsデバイスでは、背景効果が完全に安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。この技術は、Windowsエクスプローラーなどの他のアプリケーションでも使用されています。",
"background_effects_button": "背景効果を有効にする",
"dismiss": "却下"
},
"settings": {
"related_settings": "関連設定"
},
"settings_appearance": {
"related_code_blocks": "テキストノート内のコードブロックの配色",
"related_code_notes": "コードノートの配色"
},
"units": {
"percentage": "%"
},
"pagination": {
"page_title": "{{startIndex}} - {{endIndex}} ページ",
"total_notes": "{{count}} ノート"
},
"collections": {
"rendering_error": "エラーのためコンテンツを表示できません。"
}
}

View File

@@ -15,21 +15,13 @@
"message": "클라이언트 애플리케이션 시작 도중 심각한 오류가 발생했습니다:\n\n{{message}}\n\n이는 스크립트가 예기치 않게 실패하면서 발생한 것일 수 있습니다. 애플리케이션을 안전 모드로 시작한 뒤 문제를 해결해 보세요."
},
"widget-error": {
"title": "위젯 초기화 실패",
"message-custom": "ID가 \"{{id}}\"고 ,제목이 \"{{title}}\" 인 노트에서 사용자 지정 위젯을 초기화 하는데 실패했습니다:\n\n{{message}}",
"message-unknown": "알 수 없는 위젯이 초기화 하는데 실패했습니다:\n\n{{message}}"
},
"bundle-error": {
"title": "사용자 정의 스크립트를 불러오는데 실패했습니다",
"message": "ID가 \"{{id}}\"고, 제목이 \"{{title}}\"인 노트에서 스크립트가 실행되지 못했습니다:\n\n{{message}}"
"title": "위젯 초기화 실패"
}
},
"add_link": {
"add_link": "링크 추가",
"note": "노트",
"search_note": "이름으로 노트 검색하기",
"help_on_links": "링크 관련 도움말",
"link_title_mirrors": "링크 제목은 노트의 현재 제목을 반영합니다"
"search_note": "이름으로 노트 검색하기"
},
"branch_prefix": {
"save": "저장"

View File

@@ -4,67 +4,6 @@
"homepage": "Homepagina:",
"app_version": "App versie:",
"db_version": "DB Versie:",
"sync_version": "Sync Versie:",
"build_date": "Build datum:",
"build_revision": "Build revisie:",
"data_directory": "Gegevensmap:"
},
"toast": {
"critical-error": {
"title": "Kritische Error",
"message": "Een kritieke fout heeft plaatsgevonden waardoor de cliënt zich aanmeldt vanaf het begin:\n\n84X\n\nDit is waarschijnlijk veroorzaakt door een script dat op een onverwachte manier faalt. Probeer de sollicitatie in veilige modus te starten en de kwestie aan te spreken."
}
},
"add_link": {
"add_link": "Voeg link toe",
"help_on_links": "Hulp bij links",
"note": "notitie",
"search_note": "zoek voor notitie op naam",
"link_title_mirrors": "De link titel is hetzelfde als de notitie's huidige titel",
"link_title": "Link titel",
"button_add_link": "Link toevoegen"
},
"branch_prefix": {
"edit_branch_prefix": "Bewerk branch prefix",
"save": "Opslaan",
"branch_prefix_saved": "Branch prefix is opgeslagen.",
"help_on_tree_prefix": "Help bij boomvoorvoegsel",
"prefix": "Voorvoegsel: "
},
"bulk_actions": {
"bulk_actions": "Bulk acties",
"affected_notes": "Getroffen notities",
"available_actions": "Beschikbare acties",
"chosen_actions": "Kies acties",
"execute_bulk_actions": "Bulk acties uitvoeren",
"bulk_actions_executed": "Bulk acties zijn succesvol uitgevoerd.",
"none_yet": "Nog niks... voeg een actie toe door een van de beschikbare bovenstaande opties te klikken.",
"labels": "Labels",
"relations": "Relaties",
"notes": "Notities",
"other": "Andere"
},
"calendar": {
"april": "April",
"may": "Mei",
"june": "Juni",
"july": "Juli",
"august": "Augustus",
"september": "September",
"october": "Oktober",
"november": "November",
"december": "December"
},
"close_pane_button": {
"close_this_pane": "Sluit dit paneel"
},
"create_pane_button": {
"create_new_split": "Maak nieuwe split"
},
"edit_button": {
"edit_this_note": "Notitie bewerken"
},
"show_toc_widget_button": {
"show_toc": "Laat Inhoudsopgave zien"
"sync_version": "Sync Versie:"
}
}

View File

@@ -52,8 +52,7 @@
"chosen_actions": "Wybrane działania",
"execute_bulk_actions": "Wykonaj zbiór działań",
"bulk_actions_executed": "Zbiór działań został wykonany prawidłowo.",
"none_yet": "Brak zaznaczonych działań... dodaj działanie poprzez kliknięcie jednej z dostępnych opcji powyżej.",
"affected_notes": "Dotyczy notatek"
"none_yet": "Brak zaznaczonych działań... dodaj działanie poprzez kliknięcie jednej z dostępnych opcji powyżej."
},
"confirm": {
"ok": "OK",
@@ -103,8 +102,7 @@
"clone_to_selected_note": "Sklonuj do wybranej notatki",
"no_path_to_clone_to": "Brak ścieżki do sklonowania.",
"note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\"",
"help_on_links": "Pomoc dotycząca linków",
"target_parent_note": "Docelowa główna notatka"
"help_on_links": "Pomoc dotycząca linków"
},
"help": {
"title": "Ściągawka",
@@ -140,495 +138,6 @@
"cutNotes": "przytnij obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla przenoszenia notatek)",
"pasteNotes": "wklej notatkę jako podnotatka w obecnej notatce (rozumiane jako przenieś lub skopiuj, w zależności czy notatka była skopiowana czy wycięta)",
"deleteNotes": "usuń notatkę / gałąź",
"editingNotes": "Edytowanie notatek",
"other": "Inne",
"editNoteTitle": "W panelu drzewa nastąpi przejście z panelu drzewa do tytułu notatki. Naciśnięcie klawisza Enter w tytule notatki spowoduje przejście do edytora tekstu. <kbd>Ctrl+.</kbd> spowoduje powrót z edytora do panelu drzewa.",
"createEditLink": "stwórz / edytuj zewnętrzny link",
"createInternalLink": "stwórz wewnętrzny link",
"followLink": "kliknij link pod kursorem",
"insertDateTime": "wstaw aktualną datę i godzinę w pozycji kursora",
"markdownAutoformat": "Autoformatowanie w stylu Markdown",
"headings": "<code>##</code>, <code>###</code>, <code>####</code> itd., po których następuje miejsce na nagłówki",
"bulletList": "<code>*</code> lub <code>-</code>, a następnie spacja, aby utworzyć listę",
"jumpToTreePane": "przejdź do panelu drzewa i przewiń do aktywnej notatki",
"numberedList": "<code>1.</code> or <code>1)</code> po którym następuje miejsce na listę numerowaną",
"blockQuote": "zacznij linijkę od <code></code> aby po kliknięciu spacji dodać blok cytatu",
"troubleshooting": "Rozwiązywanie błędów",
"reloadFrontend": "Załaduj ponownie Frontend Trilium",
"showDevTools": "pokaż narzędzia deweloperskie",
"showSQLConsole": "pokaż konsolę SQL",
"quickSearch": "skup się na szybkim wyszukiwaniu",
"inPageSearch": "wyszukiwanie wewnątrz strony"
},
"book_properties": {
"list": "Lista"
},
"board_view": {
"move-to": "Przenieś do",
"insert-above": "Umieść nad",
"insert-below": "Umieść pod",
"delete-column": "Usuń kolumnę",
"delete-column-confirmation": "Czy na pewno chcesz usunąć tę kolumnę? Odpowiedni atrybut zostanie również usunięty w notatkach pod tą kolumną.",
"new-item": "Nowy element",
"new-item-placeholder": "Wpisz tytuł notatki...",
"add-column": "Dodaj kolumnę",
"add-column-placeholder": "Wpisz tytuł kolumny...",
"edit-note-title": "Naciśnij aby edytować tytuł notatki",
"edit-column-title": "Naciśnij aby edytować tytuł kolumny",
"delete-note": "Usuń notatkę...",
"remove-from-board": "Usuń z tablicy",
"archive-note": "Archiwalna notatka",
"unarchive-note": "Usuń notatkę z archiwum"
},
"command_palette": {
"tree-action-name": "Drzewo: {{name}}",
"export_note_title": "Wyeksportuj notatkę",
"export_note_description": "Wyeksportuj aktualną notatkę",
"show_attachments_title": "Pokaż załączniki",
"show_attachments_description": "Zobacz załączniki notatki",
"search_notes_title": "Szukaj notatek",
"search_notes_description": "Otwórz zaawansowane wyszukiwanie",
"search_subtree_title": "Poszukaj w poddrzewie",
"search_subtree_description": "poszukaj wewnątrz poddrzewa",
"search_history_title": "Pokaż historię wyszukiwania",
"search_history_description": "Pokaż poprzednie wyszukiwania",
"configure_launch_bar_title": "Ustaw Launch Bar",
"configure_launch_bar_description": "Otwórz konfigurację Launch Bar, aby dodać lub usunąć elementy."
},
"content_renderer": {
"open_externally": "Otwórz zewnętrznie"
},
"modal": {
"close": "Zamknij",
"help_title": "Pokaż więcej informacji na temat tego ekranu"
},
"call_to_action": {
"next_theme_title": "Spróbuj nowy motyw Trilium",
"next_theme_message": "Obecnie używasz starszego motywu. Czy chcesz wypróbować nowy motyw?",
"next_theme_button": "Spróbuj nowego motywu",
"background_effects_title": "Efekty w tle są już stabilne",
"dismiss": "Odrzuć",
"background_effects_button": "Włącz efekty w tle"
},
"settings": {
"related_settings": "Powiązane ustawienia"
},
"settings_appearance": {
"related_code_blocks": "Schemat kolorów dla bloków kodu w notatkach tekstowych",
"related_code_notes": "Schemat kolorów dla kodu"
},
"units": {
"percentage": "%"
},
"pagination": {
"page_title": "Strony:{{startIndex}}-{{endIndex}}",
"total_notes": "{{count}}notatek"
},
"collections": {
"rendering_error": "Błąd - Nie można pokazać treści."
},
"add_label": {
"add_label": "Dodaj etykietę",
"label_name_placeholder": "Nazwa etykiety",
"label_name_title": "Dozwolone są znaki alfanumeryczne, podkreślenie i dwukropek.",
"to_value": "do wartości",
"new_value_placeholder": "nowa wartość",
"help_text": "We wszystkich dopasowanych notatkach:",
"help_text_item2": "albo zmień wartość istniejącej etykiety"
},
"attribute_detail": {
"delete": "Usuń",
"related_notes_title": "Inne notatki z tą etykietą",
"more_notes": "Więcej notatek",
"label": "Szczegóły etykiety",
"label_definition": "Szczegóły definicji etykiety",
"relation": "Szczegóły powiązania",
"relation_definition": "Szczegóły definicji powiązania",
"disable_versioning": "Wyłącza automatyczne wersjonowanie. Przydatne np. w przypadku dużych, ale nieistotnych notatek np. dużych bibliotek JS używanych do skryptów",
"precision": "Prezycja",
"digits": "znaki",
"inverse_relation_title": "Opcjonalne ustawienie definiujące, do której relacji jest ta relacja przeciwna. Przykład: Główna - podnotatka są relacjami odwrotnymi do siebie.",
"inverse_relation": "Odwrócone powiązanie"
},
"import": {
"importIntoNote": "Importuj do notatki",
"chooseImportFile": "Wybierz plik do zaimportowania",
"importDescription": "Zawartość wybranego pliku(ów) zostanie zaimportowana jako notatka(i) podrzędna(e) do",
"options": "Opcje",
"shrinkImages": "Zmniejsz obrazy",
"safeImport": "Bezpieczny import",
"import-status": "Status importu",
"in-progress": "Import w trakcie: {{progress}}",
"successful": "Importowanie zakończone sukcesem.",
"safeImportTooltip": "Pliki eksportu Trilium <code>.zip</code> mogą zawierać skrypty wykonywalne, które mogą powodować szkodliwe zachowania. Bezpieczny import dezaktywuje automatyczne wykonywanie wszystkich importowanych skryptów. Odznacz opcję „Bezpieczny import” tylko wtedy, gdy importowane archiwum ma zawierać skrypty wykonywalne i masz pełne zaufanie do zawartości importowanego pliku.",
"import": "Import",
"failed": "Błąd importu: {{message}}.",
"html_import_tags": {
"title": "Tagi importu HTML",
"description": "Skonfiguruj, które tagi HTML mają zostać zachowane podczas importowania notatek. Tagi spoza tej listy zostaną usunięte podczas importu. Niektóre tagi (np. „script”) są zawsze usuwane ze względów bezpieczeństwa.",
"placeholder": "Wpisz tagi HTML, jedna na linijkę",
"reset_button": "Zresetuj do domyślnej listy"
}
},
"image_properties": {
"title": "Obraz",
"original_file_name": "Oryginalna nazwa pliku",
"file_type": "Typ pliku",
"file_size": "Rozmiar pliku",
"download": "Pobierz",
"open": "Otwórz",
"copy_reference_to_clipboard": "Kopiuj odniesienie do schowka",
"upload_new_revision": "Wgraj nową wersję",
"upload_success": "Nowa wersja obrazu została wysłana.",
"upload_failed": "Wysyłanie nowej wersji obrazu nie powiodło się: {{message}}"
},
"inherited_attribute_list": {
"title": "Odziedziczone atrybuty",
"no_inherited_attributes": "Brak odziedziczonych atrybutów."
},
"note_info_widget": {
"note_id": "ID notatki",
"created": "Stworzona",
"modified": "Zmodyfikowano",
"type": "Typ",
"note_size": "Rozmiar notatki",
"note_size_info": "Rozmiar notatki pozwala oszacować przybliżoną ilość miejsca potrzebnego na przechowanie jej. Uwzględnia ona jej treść oraz treść jej poprawek.",
"calculate": "oblicz",
"subtree_size": "(rozmiar poddrzewa: {{size}} w {{count}} notatkach)",
"title": "Informacje o notatce"
},
"note_map": {
"open_full": "Pełne rozszerzenie",
"collapse": "Zmniejsz do normalnego rozmiaru",
"title": "Mapa notatki",
"fix-nodes": "Napraw węzły",
"link-distance": "Odległość linku"
},
"note_paths": {
"title": "Ścieżki notatki",
"clone_button": "Sklonuj notatkę do nowej lokalizacji...",
"intro_placed": "Ta notatka jest umieszczona w następujących lokalizacjach:",
"intro_not_placed": "Ta notatka nie została jeszcze umieszczona w drzewie.",
"outside_hoisted": "Ta ścieżka znajduje się poza podniesioną notatką i trzeba ją odwiesić.",
"archived": "Zarchiwizowane",
"search": "Szukaj"
},
"note_properties": {
"this_note_was_originally_taken_from": "Ta notatka oryginalnie została wzięta z:",
"info": "Info"
},
"owned_attribute_list": {
"owned_attributes": "Posiadane atrybuty"
},
"promoted_attributes": {
"promoted_attributes": "Promowane atrybuty",
"unset-field-placeholder": "nie ustawione",
"url_placeholder": "http://strona...",
"open_external_link": "Otwórz link zewnętrzny",
"unknown_label_type": "Nieznany typ etykiety \"{{type}}\"",
"unknown_attribute_type": "Nieznany typ atrybutu \"{{type}}\"",
"add_new_attribute": "Dodaj nowy atrybut",
"remove_this_attribute": "Usuń ten atrybut",
"remove_color": "Usuń ten kolor etykiety"
},
"script_executor": {
"query": "Zapytanie",
"script": "Skrypt",
"execute_query": "Wykonaj zapytanie",
"execute_script": "Wykonaj skrypt"
},
"search_definition": {
"add_search_option": "Dodaj opcje wyszukiwania:",
"search_string": "ciąg wyszukiwania",
"search_script": "skrypt wyszukiwania",
"ancestor": "przodek",
"fast_search": "szybkie wyszukiwanie",
"fast_search_description": "Opcja szybkiego wyszukiwania wyłącza pełno tekstowe przeszukiwanie zawartości notatek, co może przyspieszyć przeszukiwanie dużych baz danych.",
"include_archived": "dodaj zarchiwizowane",
"include_archived_notes_description": "Domyślnie zarchiwizowane notatki są wyłączone z wyszukiwania, z tą opcją zostaną dodane do wyszukiwania.",
"order_by": "sortuj",
"limit": "limituj",
"limit_description": "Limituj liczbę wyników",
"save_to_note": "Zapisz do notatki",
"search_parameters": "Parametry wyszukiwania",
"unknown_search_option": "Nieznana opcja wyszukiwania {{searchOptionName}}",
"search_note_saved": "Wyszukiwana notatka została zapisana do {{- notePathTitle}}",
"actions_executed": "Akcja została wykonana."
},
"similar_notes": {
"title": "Podobne notatki",
"no_similar_notes_found": "Nie znaleziono podobnych notatek."
},
"abstract_search_option": {
"remove_this_search_option": "Usuń tą opcję wyszukiwania",
"failed_rendering": "Nieudana opcja wyszukiwania: {{dto}} z błędem: {{error}} {{stack}}"
},
"ancestor": {
"label": "Przodek",
"placeholder": "szukaj notatki po jej nazwie",
"depth_label": "glębokość",
"depth_doesnt_matter": "nie ważne",
"depth_eq": "jest dokładnie {{count}}",
"direct_children": "bezpośrednie podnotatki",
"depth_gt": "jest więcej niż {{count}}",
"depth_lt": "jest mniej niż {{count}}"
},
"debug": {
"debug": "Debuguj",
"debug_info": "Debugowanie wyświetli dodatkowe informacje debugowania w konsoli, aby ułatwić debugowanie złożonych zapytań."
},
"fast_search": {
"fast_search": "Szybkie wyszukiwanie"
},
"file_properties": {
"download": "Pobierz",
"open": "Otwórz",
"upload_new_revision": "Wgraj nową wersję",
"upload_success": "Nowa wersja pliku nie została wysłana.",
"upload_failed": "Wysyłanie nowej wersji pliku się nie udało.",
"title": "Plik"
},
"include_note": {
"label_note": "Notatka",
"placeholder_search": "szukaj notatki po jej nazwie",
"dialog_title": "Dołącz notatkę",
"button_include": "Dołącz notatkę"
},
"info": {
"closeButton": "Zamknij",
"okButton": "OK",
"modalTitle": "Wiadomość"
},
"jump_to_note": {
"search_placeholder": "Szukaj notatki po jej nazwie albo typie > komendy...",
"search_button": "Wyszukiwanie pełno tekstowe"
},
"markdown_import": {
"dialog_title": "Zaimportuj Markdown",
"import_button": "Import",
"import_success": "Treść Markdown została zaimportowana do dokumentu."
},
"limit": {
"limit": "Limit"
},
"link_context_menu": {
"open_note_in_popup": "Szybka edycja",
"open_note_in_new_tab": "Otwórz notatkę w nowej karcie",
"open_note_in_new_split": "Otwórz notatkę w nowym podziale ekranu",
"open_note_in_new_window": "Otwórz notatkę w nowym oknie"
},
"electron_integration": {
"desktop-application": "Aplikacja desktopowa"
},
"electron_context_menu": {
"cut": "Wytnij",
"copy": "Kopiuj",
"copy-link": "Kopiuj link",
"paste": "Wklej",
"paste-as-plain-text": "Wklej jako plain text",
"search_online": "Szukaj \"{{term}}\" za pomocą {{searchEngine}}"
},
"image_context_menu": {
"copy_reference_to_clipboard": "Skopiuj odnośnik do schowka",
"copy_image_to_clipboard": "Skopiuj obraz do schowka"
},
"note_autocomplete": {
"clear-text-field": "Wyczyść pole tekstowe",
"show-recent-notes": "Pokaż ostatnie notatki",
"full-text-search": "Wyszukiwanie pełnotekstowe"
},
"note_tooltip": {
"note-has-been-deleted": "Notatka została usunięta.",
"quick-edit": "Szybka edycja"
},
"duration": {
"seconds": "sekundy",
"minutes": "minuty",
"hours": "godziny",
"days": "dni"
},
"share": {
"title": "Ustawienia udostępniania"
},
"tasks": {
"due": {
"today": "Dziś",
"tomorrow": "Jutro",
"yesterday": "Wczoraj"
}
},
"content_widget": {
"unknown_widget": "Nieznany widget dla \"{{id}}\"."
},
"note_language": {
"not_set": "Nie ustawione",
"configure-languages": "Konfiguracja języków..."
},
"content_language": {
"title": "Język treści",
"description": "Wybierz jeden lub więcej języków, które mają być wyświetlane w sekcji Właściwości Podstawowe Notatki Tekstowej tylko do odczytu lub edytowalnej. Umożliwi to korzystanie z takich funkcji, jak sprawdzanie pisowni czy obsługa pisania od prawej do lewej."
},
"switch_layout_button": {
"title_vertical": "Przesuń panel edycji na dół",
"title_horizontal": "Przesuń panel edycji do lewej"
},
"toggle_read_only_button": {
"unlock-editing": "Odblokuj edycję",
"lock-editing": "Zablokuj edycję"
},
"png_export_button": {
"button_title": "Wyeksportuj diagram jako PNG"
},
"svg": {
"export_to_png": "Diagram nie może zostać wyeksportowany jako PNG."
},
"code_theme": {
"title": "Wygląd",
"word_wrapping": "Zawijanie słów",
"color-scheme": "Schemat kolorów"
},
"cpu_arch_warning": {
"title": "Proszę o pobranie wersji ARM64",
"message_macos": "TriliumNext działa obecnie w oparciu o technologię Rosetta 2, co oznacza, że używasz wersji Intel (x64) na komputerze Mac z procesorem Apple Silicon. Będzie to miało znaczący wpływ na wydajność i czas pracy baterii.",
"message_windows": "TriliumNext działa obecnie w trybie emulacji, co oznacza, że używasz wersji Intel (x64) na urządzeniu z systemem Windows na procesorze ARM. Będzie to miało znaczący wpływ na wydajność i czas pracy baterii.",
"recommendation": "Aby uzyskać najlepsze wrażenia, pobierz natywną wersję ARM64 aplikacji TriliumNext ze strony poświęconej wydaniom.",
"download_link": "Pobierz wersję natywną",
"continue_anyway": "Kontynuuj mimo wszystko",
"dont_show_again": "Nie pokazuj więcej tego ostrzeżenia"
},
"editorfeatures": {
"title": "Cechy",
"emoji_completion_enabled": "Włącz autouzupełnianie Emoji",
"note_completion_enabled": "Włącz autouzupełnianie notatki"
},
"table_view": {
"new-row": "Nowy wiersz",
"new-column": "Nowa kolumna",
"sort-column-by": "Sotuj po \"{{title}}\"",
"sort-column-ascending": "Rosnąco",
"sort-column-descending": "Malejąco",
"sort-column-clear": "Wyczyść sortowanie",
"hide-column": "Ukryj kolumnę \"{{title}}\"",
"show-hide-columns": "Pokaż/ukryj kolumny",
"row-insert-above": "Wstaw wiersz nad",
"row-insert-below": "Wstaw wiersz pod",
"row-insert-child": "Wstaw podnotatkę",
"add-column-to-the-left": "Dodaj kolumnę po lewej",
"add-column-to-the-right": "Dodaj kolumnę po prawej",
"edit-column": "Edytuj kolumnę",
"delete_column_confirmation": "Czy na pewno chcesz usunąć tę kolumnę? Odpowiedni atrybut zostanie usunięty ze wszystkich notatek.",
"delete-column": "Usuń kolumnę",
"new-column-label": "Etykieta",
"new-column-relation": "Relacje"
},
"book_properties_config": {
"hide-weekends": "Ukryj weekendy",
"display-week-numbers": "Pokaż numery tygodni",
"map-style": "Styl mapy:",
"max-nesting-depth": "Maksymalna głębokość zagnieżdżenia:",
"raster": "Raster",
"vector_light": "Wektor (jasny)",
"vector_dark": "Wektor (ciemny)",
"show-scale": "Pokaż skalę"
},
"table_context_menu": {
"delete_row": "Usuń wiersz"
},
"move_to": {
"dialog_title": "Przenieś notatki do ...",
"notes_to_move": "Notatki do przeniesienia"
},
"note_type_chooser": {
"modal_title": "Wybierz typ notatki",
"modal_body": "Wybierz typ / szablon notatki dla nowej notatki:",
"templates": "Szablony",
"builtin_templates": "Wbudowane szablony"
},
"password_not_set": {
"title": "Hasło nie zostało ustawione"
},
"add_relation": {
"add_relation": "Dodaj powiązanie",
"relation_name": "nazwa powiązania",
"allowed_characters": "Dozwolone są znaki alfanumeryczne, podkreślenie i dwukropek.",
"to": "do",
"target_note": "docelowa notatka"
},
"ai_llm": {
"actions": "Akcje",
"retry": "Spróbuj ponownie",
"partial": "{{ percentage }}% wykonania",
"retry_queued": "notatka dodana do kolejki",
"retry_failed": "Nieudana próba dodania notatki do kolejki",
"max_notes_per_llm_query": "Maksymalna ilość notatek w zapytaniu",
"index_all_notes": "Zindeksuj wszystkie notatki",
"index_status": "Status indeksowania",
"indexed_notes": "Zindeksowane notatki",
"indexing_stopped": "Indeksowanie zatrzymane",
"indexing_in_progress": "Indeksowanie w trakcie...",
"last_indexed": "Ostatnio zindeksowane",
"n_notes_queued_0": "{{ count }} notatka zakolejkowana do indeksowania",
"n_notes_queued_1": "{{ count }} notatek zakolejkowanych do indeksowania",
"n_notes_queued_2": "{{ count }} notatek zakolejkowanych do indeksowania",
"note_chat": "Czat notatki",
"note_title": "Tytuł notatki",
"error": "Błąd",
"last_attempt": "Ostatnia próba",
"queued_notes": "Zakolejkowane notatki",
"failed_notes": "Nieudane notatki",
"last_processed": "Ostatnio procesowane",
"refresh_stats": "Odśwież Statystyki",
"enable_ai_features": "Włącz funkcje AI/LLM",
"enable_ai_description": "Włącz funkcje AI, takie jak podsumowywanie notatek, generowanie treści i inne możliwości LLM",
"openai_tab": "OpenAI",
"anthropic_tab": "Anthropic",
"voyage_tab": "Voyage AI",
"ollama_tab": "Ollama",
"enable_ai": "Włącz funkcje AI/LLM",
"enable_ai_desc": "Włącz funkcje AI, takie jak podsumowywanie notatek, generowanie treści i inne możliwości LLM",
"provider_configuration": "Konfiguracja dostawcy AI",
"provider_precedence": "Pierwszeństwo dostawcy",
"provider_precedence_description": "Lista dostawców przedzielonych przecinkami w kolejności (np. „openai,anthropic,ollama”)",
"temperature": "Temperatura",
"temperature_description": "Kontroluje losowość odpowiedzi (0 = deterministyczna, 2 = maksymalna losowość)",
"system_prompt": "Monit systemowy",
"system_prompt_description": "Domyślny monit systemowy używany do wszystkich interakcji ze sztuczną inteligencją",
"openai_configuration": "Konfiguracja OpenAI",
"openai_settings": "Ustawienia OpenAI",
"api_key": "Klucz API",
"url": "Bazowy URL",
"model": "Model",
"openai_api_key_description": "Klucz API OpenAI umożliwiający dostęp do usług AI",
"anthropic_api_key_description": "Klucz API Anthropic umożliwiający dostęp do usług AI",
"default_model": "Domyślny Model",
"openai_model_description": "Przykłady: gpt-4o, gpt-4-turbo, gpt-3.5-turbo",
"base_url": "Bazowy URL",
"openai_url_description": "Domyślny: https://api.openai.com/v1",
"anthropic_settings": "Ustawienia Anthropic",
"anthropic_url_description": "Bazowy URL dla Anthropic API (domyślny: https://api.anthropic.com)",
"anthropic_model_description": "Modele Anthropic Claude'a do uzupełniania czatów",
"voyage_settings": "Ustawienia Voyage AI",
"ollama_settings": "Ustawienia Ollama",
"ollama_url_description": "URL dla Ollama API (domyślny: http://localhost:11434)",
"ollama_model_description": "Model Ollama używany do uzupełniania czatów",
"anthropic_configuration": "Konfiguracja Anthropic",
"voyage_configuration": "Konfiguracja Voyage AI",
"voyage_url_description": "Domyślny: https://api.voyageai.com/v1",
"ollama_configuration": "Konfiguracja Ollama",
"enable_ollama": "Włącz Ollama",
"enable_ollama_description": "Włącz Ollama dla lokalnego modelu AI",
"ollama_url": "Ollama URL",
"ollama_model": "Model Ollama",
"refresh_models": "Odśwież modele",
"refreshing_models": "Odświeżanie...",
"enable_automatic_indexing": "Włącz automatyczne indeksowanie",
"rebuild_index": "Odbuduj indeks",
"rebuild_index_error": "Błąd uruchomienia odbudowy indeksu. Sprawdź logi.",
"max_notes_per_llm_query_description": "Maksymalna liczba podobnych notatek do uwzględnienia w kontekście sztucznej inteligencji",
"active_providers": "Aktywni dostawcy",
"disabled_providers": "Wyłączeni dostawcy",
"remove_provider": "Usuń dostawcę z wyszukiwania",
"restore_provider": "Przywróć dostawcę do wyszukiwania",
"similarity_threshold": "Próg podobieństwa"
"editingNotes": "Edytowanie notatek"
}
}

View File

@@ -1 +0,0 @@
{}

View File

@@ -432,12 +432,7 @@
"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.",
"diff_on": "Exibir diferença",
"diff_off": "Exibir conteúdo",
"diff_on_hint": "Clique para exibir a diferença de fonte da nota",
"diff_off_hint": "Clique para exibir o conteúdo da nota",
"diff_not_available": "A diferença não está disponível."
"preview_not_available": "A visualização não está disponível para este tipo de nota."
},
"sort_child_notes": {
"sort_children_by": "Ordenar notas filhas por...",
@@ -749,7 +744,7 @@
"button-tree-map": "Mapa em Árvore"
},
"tree-context-menu": {
"open-in-a-new-tab": "Abrir em uma nova aba",
"open-in-a-new-tab": "Abrir em uma nova aba <kbd>Ctrl+Click</kbd>",
"open-in-a-new-split": "Abrir em um novo painel dividido",
"insert-note-after": "Inserir nota após",
"insert-child-note": "Inserir nota filha",
@@ -779,9 +774,7 @@
"apply-bulk-actions": "Aplicar ações em massa",
"converted-to-attachments": "{{count}} notas foram convertidas em anexos.",
"convert-to-attachment-confirm": "Tem certeza de que deseja converter as notas selecionadas em anexos de suas notas-pai?",
"open-in-popup": "Edição rápida",
"archive": "Ficheiro",
"unarchive": "Desarquivar"
"open-in-popup": "Edição rápida"
},
"command_palette": {
"search_subtree_title": "Buscar na Subárvore",
@@ -850,18 +843,7 @@
"september": "Setembro",
"october": "Outubro",
"november": "Novembro",
"december": "Dezembro",
"week": "Semana",
"week_previous": "Semana passada",
"week_next": "Próxima semana",
"month": "Mês",
"month_previous": "Mês passado",
"month_next": "Próximo mês",
"year": "Ano",
"year_previous": "Ano passado",
"year_next": "Próximo ano",
"list": "Lista",
"today": "Hoje"
"december": "Dezembro"
},
"close_pane_button": {
"close_this_pane": "Fechar este painel"
@@ -1013,8 +995,7 @@
"calendar": "Calendário",
"table": "Tabela",
"geo-map": "Mapa geográfico",
"board": "Quadro",
"include_archived_notes": "Exibir notas arquivadas"
"board": "Quadro"
},
"edited_notes": {
"no_edited_notes_found": "Ainda não há nenhuma nota editada neste dia…",
@@ -1369,9 +1350,7 @@
"title": "Desempenho",
"enable-motion": "Habilitar transições e animações",
"enable-shadows": "Habilitar sombras",
"enable-backdrop-effects": "Habilitar efeitos de fundo para menus, popups e painéis",
"enable-smooth-scroll": "Habilitar rolagem suave",
"app-restart-required": "(é necessário reiniciar o programa para que a mudança tenha efeito)"
"enable-backdrop-effects": "Habilitar efeitos de fundo para menus, popups e painéis"
},
"zoom_factor": {
"title": "Fator do Zoom (apenas versão de área de trabalho)",
@@ -1751,7 +1730,7 @@
"native-title-bar": "Barra de título nativa",
"native-title-bar-description": "Para Windows e macOS, manter a barra de título nativa desabilitada faz a aplicação parecer mais compacta. No Linux, manter a barra de título nativa habilitada faz a aplicação se integrar melhor com o restante do sistema.",
"background-effects": "Habilitar efeitos de fundo (apenas Windows 11)",
"background-effects-description": "O efeito Mica adiciona um fundo borrado e estilizado às janelas da aplicação, criando profundidade e um visual moderno. \"Barra de título nativa\" precisa ser desativada.",
"background-effects-description": "O efeito Mica adicionar um fundo borrado e estilizado às janelas da aplicação, criando profundidade e um visual moderno.",
"restart-app-button": "Reiniciar a aplicação para ver as alterações",
"zoom-factor": "Fator de Zoom"
},
@@ -1886,21 +1865,14 @@
"delete_row": "Excluir linha"
},
"board_view": {
"delete-note": "Deletar nota...",
"delete-note": "Excluir Nota",
"move-to": "Mover para",
"insert-above": "Inserir acima",
"insert-below": "Inserir abaixo",
"delete-column": "Excluir coluna",
"delete-column-confirmation": "Tem certeza de que deseja excluir esta coluna? O atributo correspondente também será removido de todas as notas abaixo desta coluna.",
"new-item": "Novo item",
"add-column": "Adicionar Coluna",
"remove-from-board": "Remover do quadro",
"archive-note": "Arquivar nota",
"unarchive-note": "Desarquivar nota",
"new-item-placeholder": "Escreva o título da nota...",
"add-column-placeholder": "Escreva o nome da coluna...",
"edit-note-title": "Clique para editar o título da nota",
"edit-column-title": "Clique para editar o título da coluna"
"add-column": "Adicionar Coluna"
},
"call_to_action": {
"next_theme_title": "Testar no novo tema do Trilium",
@@ -1922,9 +1894,7 @@
"percentage": "%"
},
"book": {
"no_children_help": "Esta coleção não possui nenhum nota filha, então não há nada para exibir. Veja <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> para detalhes.",
"drag_locked_title": "Bloqueado para edição",
"drag_locked_message": "Arrastar não é permitido pois a coleção está bloqueada para edição."
"no_children_help": "Esta coleção não possui nenhum nota filha, então não há nada para exibir. Veja <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> para detalhes."
},
"render": {
"note_detail_render_help_1": "Esta nota de ajuda é mostrada porque esta nota do tipo Renderizar HTML não possui a relação necessária para funcionar corretamente.",
@@ -2056,12 +2026,5 @@
"help_link": "Para ajuda, visite a <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>.",
"shared_publicly": "Esta nota é compartilhada publicamente em {{- link}}.",
"shared_locally": "Esta nota é compartilhada localmente em {{- link}}."
},
"pagination": {
"page_title": "Página de {{startIndex}} - {{endIndex}}",
"total_notes": "{{count}} notas"
},
"collections": {
"rendering_error": "Não foi possível exibir o conteúdo devido a um erro."
}
}

View File

@@ -267,13 +267,10 @@
"basic_properties": "Proprietăți de bază",
"editable": "Editabil",
"note_type": "Tipul notiței",
"language": "Limbă",
"configure_code_notes": "Configurează notițele de tip cod..."
"language": "Limbă"
},
"book": {
"no_children_help": "Această notiță de tip Carte nu are nicio subnotiță așadar nu este nimic de afișat. Vedeți <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> pentru detalii.",
"drag_locked_title": "Blocat pentru editare",
"drag_locked_message": "Glisarea notițelor nu este permisă deoarece colecția este blocată pentru editare."
"no_children_help": "Această notiță de tip Carte nu are nicio subnotiță așadar nu este nimic de afișat. Vedeți <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> pentru detalii."
},
"book_properties": {
"collapse": "Minimizează",
@@ -288,8 +285,7 @@
"book_properties": "Proprietăți colecție",
"table": "Tabel",
"geo-map": "Hartă geografică",
"board": "Tablă Kanban",
"include_archived_notes": "Afișează notițele arhivate"
"board": "Tablă Kanban"
},
"bookmark_switch": {
"bookmark": "Semn de carte",
@@ -338,18 +334,7 @@
"thu": "Joi",
"tue": "Mar",
"wed": "Mie",
"cannot_find_week_note": "Nu s-a putut găsi notița săptămânală",
"week": "Săptămână",
"week_previous": "Săptămâna trecută",
"week_next": "Următoarea săptămână",
"month": "Lună",
"month_previous": "Luna anterioară",
"month_next": "Următoarea lună",
"year": "An",
"year_previous": "Anul trecut",
"year_next": "Anul următor",
"list": "Agendă",
"today": "Astăzi"
"cannot_find_week_note": "Nu s-a putut găsi notița săptămânală"
},
"clone_to": {
"clone_notes_to": "Clonează notițele către...",
@@ -1085,12 +1070,7 @@
"revisions_deleted": "Notița reviziei a fost ștearsă.",
"maximum_revisions": "Numărul maxim de revizii pentru notița curentă: {{number}}.",
"settings": "Setări revizii ale notițelor",
"snapshot_interval": "Intervalul de creare a reviziilor pentru notițe: {{seconds}}s.",
"diff_on": "Evidențiază diferențele",
"diff_off": "Afișează conținutul",
"diff_on_hint": "Clic pentru a afișa diferențele față de revizia anterioară, la nivel de cod sursă",
"diff_off_hint": "Clic pentru a afișa întregul conținut al reviziei",
"diff_not_available": "Diferențele nu pot fi evidențiate."
"snapshot_interval": "Intervalul de creare a reviziilor pentru notițe: {{seconds}}s."
},
"revisions_button": {
"note_revisions": "Revizii ale notiței"
@@ -1377,7 +1357,7 @@
"insert-note-after": "Inserează după notiță",
"move-to": "Mutare la...",
"open-in-a-new-split": "Deschide în lateral",
"open-in-a-new-tab": "Deschide în tab nou",
"open-in-a-new-tab": "Deschide în tab nou <kbd>Ctrl+Clic</kbd>",
"paste-after": "Lipește după notiță",
"paste-into": "Lipește în notiță",
"protect-subtree": "Protejează ierarhia",
@@ -1389,14 +1369,12 @@
"unhoist-note": "Defocalizează notița",
"converted-to-attachments": "{{count}} notițe au fost convertite în atașamente.",
"convert-to-attachment-confirm": "Doriți convertirea notițelor selectate în atașamente ale notiței părinte?",
"open-in-popup": "Editare rapidă",
"archive": "Arhivează",
"unarchive": "Dezarhivează"
"open-in-popup": "Editare rapidă"
},
"shared_info": {
"help_link": "Pentru informații vizitați <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki-ul</a>.",
"shared_locally": "Această notiță este partajată local la {{- link}}.",
"shared_publicly": "Această notiță este partajată public la {{- link}}."
"shared_locally": "Această notiță este partajată local la {{- link}}",
"shared_publicly": "Această notiță este partajată public la {{- link}}"
},
"note_types": {
"book": "Colecție",
@@ -1494,8 +1472,7 @@
"create-child-note": "Crează subnotiță",
"hoist-this-note-workspace": "Focalizează spațiul de lucru",
"refresh-saved-search-results": "Reîmprospătează căutarea salvată",
"unhoist": "Defocalizează notița",
"toggle-sidebar": "Comută bara laterală"
"unhoist": "Defocalizează notița"
},
"title_bar_buttons": {
"window-on-top": "Menține fereastra mereu vizibilă"
@@ -1992,21 +1969,14 @@
"delete_row": "Șterge rândul"
},
"board_view": {
"delete-note": "Șterge notița...",
"delete-note": "Șterge notița",
"move-to": "Mută la",
"insert-above": "Inserează deasupra",
"insert-below": "Inserează dedesubt",
"delete-column": "Șterge coloana",
"delete-column-confirmation": "Doriți ștergerea acestei coloane? Atributul corespunzător va fi șters din notițele din acest tabel.",
"new-item": "Intrare nouă",
"add-column": "Adaugă coloană",
"remove-from-board": "Înlătură de pe tablă",
"archive-note": "Arhivează notița",
"unarchive-note": "Dezarhivează notița",
"new-item-placeholder": "Introduceți titlul notiței...",
"add-column-placeholder": "Introduceți denumirea coloanei...",
"edit-note-title": "Clic pentru a edita titlul notiței",
"edit-column-title": "Clic pentru a edita titlul coloanei"
"add-column": "Adaugă coloană"
},
"command_palette": {
"tree-action-name": "Listă de notițe: {{name}}",
@@ -2056,12 +2026,5 @@
},
"units": {
"percentage": "%"
},
"pagination": {
"page_title": "Pagina pentru {{startIndex}} - {{endIndex}}",
"total_notes": "{{count}} notițe"
},
"collections": {
"rendering_error": "Nu a putut fi afișat conținutul din cauza unei erori."
}
}

View File

@@ -301,7 +301,7 @@
"chooseImportFile": "Выберите файл импорта",
"safeImport": "Безопасный импорт",
"shrinkImages": "Уменьшить изображения",
"textImportedAsText": "Импортировать HTML, Markdown и TXT как текстовые заметки, если их метаданные не позволяют определить тип заметки",
"textImportedAsText": "Импортировать HTML, Markdown и TXT как текстовые заметки, если из метаданные не позволяют определить тип заметки",
"replaceUnderscoresWithSpaces": "Заменить подчеркивания пробелами в названиях импортированных заметок",
"import": "Импорт",
"failed": "Сбой при импорте: {{message}}.",
@@ -379,12 +379,7 @@
"settings": "Настройка версионирования заметок",
"no_revisions": "У этой заметки еще нет версий...",
"snapshot_interval": "Интервал создания версии заметки: {{seconds}} с.",
"maximum_revisions": "Максимальное количество версий заметки: {{number}}.",
"diff_on": "Сравнить",
"diff_off": "Показать содержимое",
"diff_on_hint": "Отобразить сравнение исходного кода заметки",
"diff_off_hint": "Отобразить контент заметки",
"diff_not_available": "Сравнение недоступно."
"maximum_revisions": "Максимальное количество версий заметки: {{number}}."
},
"sort_child_notes": {
"sort_children_by": "Сортировать дочерние заметки по...",
@@ -560,14 +555,7 @@
"insert-below": "Вставить ниже",
"insert-above": "Вставить выше",
"move-to": "Переместить",
"delete-note": "Удалить заметку...",
"remove-from-board": "Удалить из доски",
"archive-note": "Архивировать заметку",
"unarchive-note": "Разархивировать заметку",
"edit-column-title": "Нажмите, чтобы изменить заголовок столбца",
"edit-note-title": "Нажмите, чтобы изменить название заметки",
"add-column-placeholder": "Введите имя столбца...",
"new-item-placeholder": "Введите название заметки..."
"delete-note": "Удалить заметку"
},
"table_context_menu": {
"delete_row": "Удалить строку"
@@ -665,10 +653,10 @@
"electron_integration": {
"zoom-factor": "Коэффициент масштабирования",
"restart-app-button": "Применить изменения и перезапустить приложение",
"background-effects-description": "Эффект Mica добавляет размытый, стильный фон окнам приложений, создавая глубину и современный вид. Опция \"Системная строка заголовка\" должна быть отключена.",
"background-effects-description": "Эффект Mica добавляет размытый, стильный фон окнам приложений, создавая глубину и современный вид.",
"background-effects": "Включить фоновые эффекты (только Windows 11)",
"native-title-bar-description": "В Windows и macOS отключение системной строки заголовка делает приложение более компактным. В Linux включение системной строки заголовка улучшает интеграцию с остальной частью системы.",
"native-title-bar": "Системная панель заголовка",
"native-title-bar-description": "В Windows и macOS отключение нативной строки заголовка делает приложение более компактным. В Linux включение нативной строки заголовка улучшает интеграцию с остальной частью системы.",
"native-title-bar": "Нативная панель заголовка",
"desktop-application": "Десктопное приложение"
},
"link_context_menu": {
@@ -809,7 +797,7 @@
"cut": "Вырезать",
"duplicate": "Создать дубликат",
"export": "Экспорт",
"open-in-a-new-tab": "Открыть в новой вкладке",
"open-in-a-new-tab": "Открыть в новой вкладке <kbd>Ctrl+Click</kbd>",
"open-in-a-new-split": "Открыть в новой панели",
"unhoist-note": "Отрепить заметку",
"hoist-note": "Закрепить заметку",
@@ -824,7 +812,7 @@
"expand-subtree": "Развернуть поддерево",
"collapse-subtree": "Свернуть поддерево",
"sort-by": "Сортировать по...",
"insert-note-after": "Вставить заметку после",
"insert-note-after": "Вставить замтку после",
"insert-child-note": "Вставить дочернюю заметку",
"search-in-subtree": "Поиск в поддереве",
"edit-branch-prefix": "Изменить префикс ветки",
@@ -833,9 +821,7 @@
"recent-changes-in-subtree": "Последние изменения в поддереве",
"copy-note-path-to-clipboard": "Копировать путь к заметке в буфер обмена",
"convert-to-attachment-confirm": "Вы уверены, что хотите преобразовать выбранные заметки во вложения их родительских заметок?",
"converted-to-attachments": "{{count}} заметок были преобразованы во вложения.",
"archive": "Архивировать",
"unarchive": "Разархивировать"
"converted-to-attachments": "{{count}} заметок были преобразованы во вложения."
},
"info": {
"closeButton": "Закрыть",
@@ -937,18 +923,7 @@
"november": "Ноябрь",
"december": "Декабрь",
"cannot_find_week_note": "Не удалось найти заметку недели",
"cannot_find_day_note": "Не удалось найти заметку дня",
"week": "Неделя",
"week_previous": "Прошлая неделя",
"week_next": "Следующая неделя",
"month": "Месяц",
"month_previous": "Предыдущий месяц",
"month_next": "Следующий месяц",
"year": "Год",
"year_previous": "Предыдущий год",
"year_next": "Следующий год",
"list": "Список",
"today": "Сегодня"
"cannot_find_day_note": "Не удалось найти заметку дня"
},
"global_menu": {
"menu": "Меню",
@@ -1009,8 +984,7 @@
"geo-map": "Карта",
"invalid_view_type": "Недопустимый тип представления '{{type}}'",
"expand_all_children": "Развернуть все дочерние элементы",
"collapse_all_notes": "Свернуть все заметки",
"include_archived_notes": "Показать заархивированные заметки"
"collapse_all_notes": "Свернуть все заметки"
},
"edited_notes": {
"deleted": "(удалено)",
@@ -1207,7 +1181,7 @@
"native_title_bar": {
"enabled": "включено",
"disabled": "выключено",
"title": "Системная панель заголовка (требует перезапуска приложения)"
"title": "Нативная панель заголовка (требует перезапуска приложения)"
},
"ai_llm": {
"progress": "Прогресс",
@@ -2045,23 +2019,12 @@
"could_not_find_typewidget": "Не удалось найти typeWidget для типа '{{type}}'"
},
"book": {
"no_children_help": "В этой коллекции нет дочерних заметок, поэтому отображать нечего. Подробности см. на <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a>.",
"drag_locked_title": "Защищено от изменения",
"drag_locked_message": "Перетаскивание не допускается, так как коллекция защищена от редактирования."
"no_children_help": "В этой коллекции нет дочерних заметок, поэтому отображать нечего. Подробности см. на <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a>."
},
"ui-performance": {
"title": "Производительность",
"enable-motion": "Включить визуальные эффекты и анимации",
"enable-shadows": "Включить тени",
"enable-backdrop-effects": "Включить эффекты размытия фона меню, всплывающих окон и панелей",
"enable-smooth-scroll": "Включить плавную прокрутку",
"app-restart-required": "(для вступления изменений в силу требуется перезапуск приложения)"
},
"collections": {
"rendering_error": "Невозможно отобразить содержимое из-за ошибки."
},
"pagination": {
"total_notes": "{{count}} заметок",
"page_title": "Страница {{startIndex}} - {{endIndex}}"
"enable-backdrop-effects": "Включить эффекты размытия фона меню, всплывающих окон и панелей"
}
}

View File

@@ -1,96 +1,26 @@
{
"about": {
"homepage": "Anasayfa:",
"app_version": "Uygulama versiyonu:",
"db_version": "Veritabanı versiyonu:",
"title": "Trilium Notes Hakkında",
"sync_version": "Eşleştirme versiyonu:",
"data_directory": "Veri dizini:"
},
"branch_prefix": {
"save": "Kaydet",
"edit_branch_prefix": "Dalın önekini düzenle",
"prefix": "Önek: ",
"branch_prefix_saved": "Dal öneki kaydedildi."
},
"delete_notes": {
"close": "Kapat",
"delete_notes_preview": "Not önizlemesini sil",
"delete_all_clones_description": "Tüm klonları da sil (son değişikliklerden geri alınabilir)"
},
"export": {
"close": "Kapat"
},
"import": {
"chooseImportFile": "İçe aktarım dosyası",
"importDescription": "Seçilen dosya(lar) alt not olarak içe aktarılacaktır"
},
"info": {
"closeButton": "Kapat"
},
"protected_session_password": {
"close_label": "Kapat"
},
"toast": {
"critical-error": {
"title": "Kritik hata",
"message": "İstemci uygulamasının başlatılmasını engelleyen kritik bir hata meydana geldi\n\n{{message}}\n\nBu muhtemelen bir betiğin beklenmedik şekilde başarısız olmasından kaynaklanıyor. Uygulamayı güvenli modda başlatarak sorunu ele almayı deneyin."
"about": {
"homepage": "Giriş sayfası:",
"app_version": "Uygulama versiyonu:",
"db_version": "Veritabanı versiyonu:"
},
"widget-error": {
"title": "Bir widget başlatılamadı",
"message-unknown": "Bilinmeyen widget aşağıdaki sebeple başlatılamadı\n\n{{message}}"
"branch_prefix": {
"save": "Kaydet"
},
"bundle-error": {
"title": "Özel bir betik yüklenemedi"
"delete_notes": {
"close": "Kapat"
},
"export": {
"close": "Kapat"
},
"import": {
"chooseImportFile": "İçe aktarım dosyası",
"importDescription": "Seçilen dosya(lar) alt not olarak içe aktarılacaktır"
},
"info": {
"closeButton": "Kapat"
},
"protected_session_password": {
"close_label": "Kapat"
}
},
"add_link": {
"add_link": "Bağlantı ekle",
"help_on_links": "Bağlantılar konusunda yardım",
"note": "Not",
"search_note": "isimle not ara",
"link_title_mirrors": "bağlantı adı notun şu anki adıyla aynı",
"link_title_arbitrary": "bağlantı adı isteğe bağlı olarak değiştirilebilir",
"link_title": "Bağlantı adı",
"button_add_link": "Bağlantı ekle"
},
"bulk_actions": {
"bulk_actions": "Toplu eylemler",
"affected_notes": "Etkilenen notlar",
"include_descendants": "Seçili notların alt notlarını da ekle",
"available_actions": "Mevcut eylemler",
"chosen_actions": "Seçili eylemler",
"execute_bulk_actions": "Toplu eylemleri uygula",
"bulk_actions_executed": "Toplu eylemler başarıyla uygulandı.",
"none_yet": "Henüz yok... yukarıda mevcut eylemler arasından birine tıklayarak eylem ekle.",
"labels": "Etiketler",
"relations": "İlişkiler",
"notes": "Notlar",
"other": "Diğer"
},
"clone_to": {
"clone_notes_to": "Notları klonla...",
"help_on_links": "Bağlantılar konusunda yardım",
"notes_to_clone": "Klonlanacak notlar",
"target_parent_note": "Hedef üst not",
"search_for_note_by_its_name": "isme göre not ara",
"cloned_note_prefix_title": "Klonlanan not, not ağacında belirtilen önek ile gösterilecektir",
"prefix_optional": "önek (opsiyonel)",
"clone_to_selected_note": "Seçili nota klonla",
"no_path_to_clone_to": "Klonlanacak bir yol yok.",
"note_cloned": "\"{{clonedTitle}}\" notu \"{{targetTitle}}\"'a klonlandı"
},
"confirm": {
"confirmation": "Onay",
"cancel": "İptal",
"ok": "OK",
"are_you_sure_remove_note": "\"{{title}}\" notunu ilişki haritasından kaldırmak istediğinize emin misiniz?. ",
"also_delete_note": "Notu da sil"
},
"ai_llm": {
"n_notes_queued": "{{ count }} not dizinleme için sıraya alındı",
"n_notes_queued_plural": "{{ count }} not dizinleme için sıraya alındı",
"notes_indexed": "{{ count }} not dizinlendi",
"notes_indexed_plural": "{{ count }} not dizinlendi"
}
}

View File

@@ -276,12 +276,7 @@
"preview": "預覽:",
"preview_not_available": "無法預覽此類型的筆記。",
"restore_button": "還原",
"delete_button": "刪除",
"diff_on": "顯示差異",
"diff_off": "顯示內容",
"diff_on_hint": "點擊以顯示筆記原始碼差異",
"diff_off_hint": "點擊以顯示筆記內容",
"diff_not_available": "差異不可用。"
"delete_button": "刪除"
},
"sort_child_notes": {
"sort_children_by": "依…排序子筆記",
@@ -592,18 +587,7 @@
"october": "十月",
"november": "十一月",
"december": "十二月",
"cannot_find_week_note": "無法找到週記",
"week": "週",
"week_previous": "上週",
"week_next": "下週",
"month": "月",
"month_previous": "上個月",
"month_next": "下個月",
"year": "年",
"year_previous": "前一年",
"year_next": "後一年",
"list": "列表",
"today": "今天"
"cannot_find_week_note": "無法找到週記"
},
"close_pane_button": {
"close_this_pane": "關閉此面板"
@@ -761,8 +745,7 @@
"calendar": "日曆",
"table": "表格",
"geo-map": "地理地圖",
"board": "看板",
"include_archived_notes": "顯示已封存筆記"
"board": "看板"
},
"edited_notes": {
"no_edited_notes_found": "今天還沒有編輯過的筆記...",
@@ -963,9 +946,7 @@
"no_attachments": "此筆記沒有附件。"
},
"book": {
"no_children_help": "此類型為書籍的筆記沒有任何子筆記,因此沒有內容可顯示。請參閱 <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> 以了解詳情。",
"drag_locked_title": "鎖定編輯",
"drag_locked_message": "無法拖曳,因為此集合已被鎖定編輯。"
"no_children_help": "此類型為書籍的筆記沒有任何子筆記,因此沒有內容可顯示。請參閱 <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> 以了解詳情。"
},
"editable_code": {
"placeholder": "在這裡輸入您的程式碼筆記內容…"
@@ -1385,7 +1366,7 @@
"button-tree-map": "樹狀地圖"
},
"tree-context-menu": {
"open-in-a-new-tab": "在新分頁中打開",
"open-in-a-new-tab": "在新分頁中打開 <kbd>Ctrl+Click</kbd>",
"open-in-a-new-split": "在新頁面分割中打開",
"insert-note-after": "在後面插入筆記",
"insert-child-note": "插入子筆記",
@@ -1415,9 +1396,7 @@
"converted-to-attachments": "{{count}} 個筆記已被轉換為附件。",
"convert-to-attachment-confirm": "確定要將所選的筆記轉換為其父級筆記的附件嗎?",
"duplicate": "複製副本",
"open-in-popup": "快速編輯",
"archive": "封存",
"unarchive": "解除封存"
"open-in-popup": "快速編輯"
},
"shared_info": {
"help_link": "如需幫助,請訪問 <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>。",
@@ -1853,7 +1832,7 @@
"native-title-bar": "原生標題列",
"native-title-bar-description": "對於 Windows 和 macOS關閉原生標題列會讓應用程式看起來更緊湊。在 Linux 上,開啟原生標題列可以與系統的其他部分整合得更好。",
"background-effects": "啟用背景效果(僅適用於 Windows 11",
"background-effects-description": "Mica 效果為程式視窗新增模糊且時尚的背景,營造出深度感和現代化外觀。「原生標題列」必須被禁用。",
"background-effects-description": "Mica 效果為程式視窗新增模糊且時尚的背景,營造出深度感和現代化外觀。",
"restart-app-button": "重新啟動應用程式以查看更改",
"zoom-factor": "縮放係數"
},
@@ -1988,21 +1967,14 @@
"delete_row": "刪除列"
},
"board_view": {
"delete-note": "刪除筆記",
"delete-note": "刪除筆記",
"move-to": "移動至",
"insert-above": "在上方插入",
"insert-below": "在下方插入",
"delete-column": "刪除行",
"delete-column-confirmation": "您確定要刪除此行嗎?此行中所有筆記對應的屬性也將被移除。",
"new-item": "新增項目",
"add-column": "新增行",
"remove-from-board": "從看板上移除",
"archive-note": "封存筆記",
"unarchive-note": "解除封存筆記",
"new-item-placeholder": "輸入筆記標題…",
"add-column-placeholder": "輸入行名…",
"edit-note-title": "點擊以編輯筆記標題",
"edit-column-title": "點擊以編輯行標題"
"add-column": "新增行"
},
"command_palette": {
"tree-action-name": "樹:{{name}}",
@@ -2049,15 +2021,6 @@
"title": "效能",
"enable-motion": "啟用轉場與動畫",
"enable-shadows": "啟用陰影",
"enable-backdrop-effects": "啟用選單、彈出視窗和面板的背景特效",
"enable-smooth-scroll": "啟用平滑滾動",
"app-restart-required": "(需要重啟程式以套用更改)"
},
"pagination": {
"page_title": "第 {{startIndex}} - {{endIndex}} 頁",
"total_notes": "{{count}} 筆記"
},
"collections": {
"rendering_error": "發現錯誤,無法顯示內容。"
"enable-backdrop-effects": "啟用選單、彈出視窗和面板的背景特效"
}
}

View File

@@ -326,12 +326,7 @@
"mime": "МІМЕ: ",
"file_size": "Розмір файлу:",
"preview": "Попередній перегляд:",
"preview_not_available": "Попередній перегляд недоступний для цього типу нотатки.",
"diff_on": "Показати різницю",
"diff_off": "Показати вміст",
"diff_on_hint": "Натисніть, щоб показати різницю в джерелі нотатки",
"diff_off_hint": "Натисніть, щоб показати вміст нотатки",
"diff_not_available": "Різниця недоступна."
"preview_not_available": "Попередній перегляд недоступний для цього типу нотатки."
},
"include_note": {
"dialog_title": "Включити нотатку",
@@ -732,18 +727,7 @@
"september": "Вересень",
"october": "Жовтень",
"november": "Листопад",
"december": "Грудень",
"week": "Тиждень",
"week_previous": "Попередній тиждень",
"week_next": "Наступний тиждень",
"month": "Місяць",
"month_previous": "Попередній місяць",
"month_next": "Наступний місяць",
"year": "Рік",
"year_previous": "Попередній рік",
"year_next": "Наступний рік",
"list": "Список",
"today": "Сьогодні"
"december": "Грудень"
},
"close_pane_button": {
"close_this_pane": "Закрити цю панель"
@@ -876,8 +860,7 @@
"calendar": "Календар",
"table": "Таблиця",
"geo-map": "Географічна карта",
"board": "Дошка",
"include_archived_notes": "Показати архівовані нотатки"
"board": "Дошка"
},
"edited_notes": {
"no_edited_notes_found": "Цього дня ще немає редагованих нотаток...",
@@ -1069,9 +1052,7 @@
"no_attachments": "Ця нотатка не має вкладень."
},
"book": {
"no_children_help": "Ця колекція не має дочірніх нотаток, тому нічого відображати. Див. <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">вікі</a> для отримання детальної інформації.",
"drag_locked_title": "Заблоковано для редагування",
"drag_locked_message": "Перетягування заборонено, оскільки колекцію заблоковано для редагування."
"no_children_help": "Ця колекція не має дочірніх нотаток, тому нічого відображати. Див. <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">вікі</a> для отримання детальної інформації."
},
"editable_code": {
"placeholder": "Введіть тут вміст вашої нотатки з кодом..."
@@ -1698,7 +1679,7 @@
"native-title-bar": "Нативний рядок заголовка",
"native-title-bar-description": "У Windows та macOS вимкнення рядка заголовка робить програму компактнішою. У Linux увімкнення рядка заголовка краще інтегрується з рештою системи.",
"background-effects": "Увімкнення фонових ефектів (лише для Windows 11)",
"background-effects-description": "Ефект слюди додає розмитий, стильний фон вікнам програм, створюючи глибину та сучасний вигляд. \"Нативний рядок заголовка\" має бути вимкнено.",
"background-effects-description": "Ефект слюди додає розмитий, стильний фон вікнам програм, створюючи глибину та сучасний вигляд.",
"restart-app-button": "Перезапустіть програму, щоб переглянути зміни",
"zoom-factor": "Коефіцієнт масштабування"
},
@@ -1897,7 +1878,7 @@
"button-tree-map": "Карта дерева"
},
"tree-context-menu": {
"open-in-a-new-tab": "Відкрити в новій вкладці",
"open-in-a-new-tab": "Відкрити в новій вкладці <kbd>Ctrl+Клік</kbd>",
"open-in-a-new-split": "Відкрити в новому розділі",
"insert-note-after": "Вставити нотатку після",
"insert-child-note": "Вставити дочірню нотатку",
@@ -1927,9 +1908,7 @@
"apply-bulk-actions": "Застосувати масові дії",
"converted-to-attachments": "({{count}}) нотаток перетворено на вкладення.",
"convert-to-attachment-confirm": "Ви впевнені, що хочете конвертувати вибрані нотатки у вкладення до їхніх батьківських нотаток?",
"open-in-popup": "Швидке редагування",
"archive": "Архівувати",
"unarchive": "Розархівувати"
"open-in-popup": "Швидке редагування"
},
"shared_info": {
"shared_publicly": "Ця нотатка опублікована на {{- link}}.",
@@ -1996,21 +1975,14 @@
"delete_row": "Видалити рядок"
},
"board_view": {
"delete-note": "Видалити нотатку...",
"delete-note": "Видалити нотатку",
"move-to": "Перемістити до",
"insert-above": "Вставити вище",
"insert-below": "Вставити нижче",
"delete-column": "Видалити стовпець",
"delete-column-confirmation": "Ви впевнені, що хочете видалити цей стовпець? Відповідний атрибут також буде видалено в нотатках під цим стовпцем.",
"new-item": "Новий елемент",
"add-column": "Додати стовпець",
"remove-from-board": "Видалити з дошки",
"archive-note": "Архівувати нотатка",
"unarchive-note": "Розархівувати нотатку",
"new-item-placeholder": "Введіть заголовок нотатки...",
"add-column-placeholder": "Введіть назву стовпця...",
"edit-note-title": "Натисніть, щоб редагувати заголовок нотатки",
"edit-column-title": "Натисніть, щоб редагувати заголовок стовпця"
"add-column": "Додати стовпець"
},
"command_palette": {
"tree-action-name": "Дерево: {{name}}",
@@ -2053,15 +2025,6 @@
"title": "Продуктивність",
"enable-motion": "Увімкнути переходи та анімацію",
"enable-shadows": "Увімкнути тіні",
"enable-backdrop-effects": "Увімкнути фонові ефекти для меню, спливаючих вікон та панелей",
"enable-smooth-scroll": "Увімкнути плавне прокручування",
"app-restart-required": "(щоб зміни набули чинності, потрібен перезапуск програми)"
},
"pagination": {
"page_title": "Сторінка {{startIndex}} - {{endIndex}}",
"total_notes": "{{count}} нотаток"
},
"collections": {
"rendering_error": "Не вдалося показати вміст через помилку."
"enable-backdrop-effects": "Увімкнути фонові ефекти для меню, спливаючих вікон та панелей"
}
}

View File

@@ -51,12 +51,3 @@ declare module "leaflet" {
markers?: GPXMarker | undefined;
}
}
declare global {
interface Navigator {
/** Returns a boolean indicating whether the browser is running in standalone mode. Available on Apple's iOS Safari only. */
standalone?: boolean;
/** Returns the WindowControlsOverlay interface which exposes information about the geometry of the title bar in desktop Progressive Web Apps, and an event to know whenever it changes. */
windowControlsOverlay?: unknown;
}
}

View File

@@ -6,8 +6,8 @@
.floating-buttons-children,
.show-floating-buttons {
position: absolute;
top: var(--floating-buttons-vert-offset, 10px);
right: var(--floating-buttons-horiz-offset, 10px);
top: 10px;
right: 10px;
display: flex;
flex-direction: row;
z-index: 100;
@@ -28,8 +28,8 @@
transform: rotate(180deg);
}
.type-canvas {
--floating-buttons-vert-offset: 70px;
.type-canvas .floating-buttons-children {
top: 70px;
}
.type-canvas .floating-buttons-children > * {

View File

@@ -6,7 +6,7 @@ import { ParentComponent } from "./react/react_utils";
import { EventData, EventNames } from "../components/app_context";
import { type FloatingButtonsList, type FloatingButtonContext } from "./FloatingButtonsDefinitions";
import ActionButton from "./react/ActionButton";
import { ViewTypeOptions } from "./collections/interface";
import { ViewTypeOptions } from "../services/note_list_renderer";
interface FloatingButtonsProps {
items: FloatingButtonsList;

View File

@@ -19,7 +19,7 @@ import { getHelpUrlForNote } from "../services/in_app_help";
import froca from "../services/froca";
import NoteLink from "./react/NoteLink";
import RawHtml from "./react/RawHtml";
import { ViewTypeOptions } from "./collections/interface";
import { ViewTypeOptions } from "../services/note_list_renderer";
export interface FloatingButtonContext {
parentComponent: Component;

View File

@@ -0,0 +1,34 @@
import type { EventData } from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
import OnClickButtonWidget from "./onclick_button.js";
export default class ClosePaneButton extends OnClickButtonWidget {
isEnabled() {
return (
super.isEnabled() &&
// main note context should not be closeable
this.noteContext &&
!!this.noteContext.mainNtxId
);
}
async noteContextReorderEvent({ ntxIdsInOrder }: EventData<"noteContextReorder">) {
this.refresh();
}
constructor() {
super();
this.icon("bx-x")
.title(t("close_pane_button.close_this_pane"))
.titlePlacement("bottom")
.onClick((widget, e) => {
// to avoid split pane container detecting click within the pane which would try to activate this
// pane (which is being removed)
e.stopPropagation();
widget.triggerCommand("closeThisNoteSplit", { ntxId: widget.getClosestNtxId() });
})
.class("icon-action");
}
}

View File

@@ -1,31 +0,0 @@
import { useEffect, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import ActionButton from "../react/ActionButton";
import { useNoteContext, useTriliumEvent } from "../react/hooks";
export default function ClosePaneButton() {
const { noteContext, ntxId, parentComponent } = useNoteContext();
const [ isEnabled, setIsEnabled ] = useState(false);
function refresh() {
setIsEnabled(!!(noteContext && !!noteContext.mainNtxId));
}
useTriliumEvent("noteContextReorder", refresh);
useEffect(refresh, [ ntxId ]);
return (
<ActionButton
icon="bx bx-x"
text={t("close_pane_button.close_this_pane")}
className={!isEnabled ? "hidden-ext" : ""}
onClick={(e) => {
// to avoid split pane container detecting click within the pane which would try to activate this
// pane (which is being removed)
e.stopPropagation();
parentComponent?.triggerCommand("closeThisNoteSplit", { ntxId: parentComponent.getClosestNtxId() });
}}
/>
)
}

View File

@@ -0,0 +1,17 @@
import { t } from "../../services/i18n.js";
import OnClickButtonWidget from "./onclick_button.js";
export default class CreatePaneButton extends OnClickButtonWidget {
constructor() {
super();
this.icon("bx-dock-right")
.title(t("create_pane_button.create_new_split"))
.titlePlacement("bottom")
.onClick((widget, e) => {
widget.triggerCommand("openNewNoteSplit", { ntxId: widget.getClosestNtxId() });
e.stopPropagation();
})
.class("icon-action");
}
}

View File

@@ -1,21 +0,0 @@
import { useContext } from "preact/hooks";
import { t } from "../../services/i18n.js";
import ActionButton from "../react/ActionButton.jsx";
import { ParentComponent } from "../react/react_utils.jsx";
import BasicWidget from "../basic_widget.js";
export default function CreatePaneButton() {
const widget = useContext(ParentComponent) as BasicWidget | undefined;
return (
<ActionButton
icon="bx bx-dock-right"
text={t("create_pane_button.create_new_split")}
onClick={(e) => {
widget?.triggerCommand("openNewNoteSplit", { ntxId: widget.getClosestNtxId() });
e.stopPropagation();
}}
/>
)
}

View File

@@ -8,50 +8,31 @@
min-width: 20em;
}
button.global-menu-button {
position: relative;
.global-menu-button {
width: 100% !important;
height: 100% !important;
border: none;
position: relative;
padding: 6px;
}
.global-menu-button svg > g {
transform-origin: center;
transition: transform 300ms ease-in-out;
}
.global-menu-button:active svg > g,
.global-menu-button.show svg > g{
transform: scale(0.85) rotate(-10deg);
border: 0;
}
.global-menu-button svg path {
fill: var(--launcher-pane-text-color);
}
.global-menu-button:hover {
border: none;
.global-menu-button:hover { border: 0; }
.global-menu-button:hover svg path {
transition: 200ms ease-in-out fill;
}
.global-menu-button:hover svg path,
.global-menu-button.show svg path {
transition: 300ms linear fill;
}
.global-menu-button svg path {
transition: fill 1000ms ease-out;
}
.global-menu-button:is(:hover, .show) svg path.st0 { fill:#95C980; transition-delay: 0ms; }
.global-menu-button:is(:hover, .show) svg path.st1 { fill:#72B755; transition-delay: 50ms; }
.global-menu-button:is(:hover, .show) svg path.st2 { fill:#4FA52B; transition-delay: 100ms; }
.global-menu-button:is(:hover, .show) svg path.st3 { fill:#EE8C89; transition-delay: 200ms; }
.global-menu-button:is(:hover, .show) svg path.st4 { fill:#E96562; transition-delay: 250ms; }
.global-menu-button:is(:hover, .show) svg path.st5 { fill:#E33F3B; transition-delay: 300ms; }
.global-menu-button:is(:hover, .show) svg path.st6 { fill:#EFB075; transition-delay: 400ms; }
.global-menu-button:is(:hover, .show) svg path.st7 { fill:#E99547; transition-delay: 450ms; }
.global-menu-button:is(:hover, .show) svg path.st8 { fill:#E47B19; transition-delay: 500ms; }
.global-menu-button:hover svg path.st0 { fill:#95C980; }
.global-menu-button:hover svg path.st1 { fill:#72B755; }
.global-menu-button:hover svg path.st2 { fill:#4FA52B; }
.global-menu-button:hover svg path.st3 { fill:#EE8C89; }
.global-menu-button:hover svg path.st4 { fill:#E96562; }
.global-menu-button:hover svg path.st5 { fill:#E33F3B; }
.global-menu-button:hover svg path.st6 { fill:#EFB075; }
.global-menu-button:hover svg path.st7 { fill:#E99547; }
.global-menu-button:hover svg path.st8 { fill:#E47B19; }
.global-menu-button-update-available {
position: absolute;

View File

@@ -1,6 +1,6 @@
import Dropdown from "../react/Dropdown";
import "./global_menu.css";
import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool, useTriliumOptionInt } from "../react/hooks";
import { useStaticTooltip, useStaticTooltipWithKeyboardShortcut, useTriliumOption, useTriliumOptionBool } from "../react/hooks";
import { useContext, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList";
@@ -34,10 +34,9 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
text={<>
{isVerticalLayout && <VerticalLayoutIcon />}
{isUpdateAvailable && <div class="global-menu-button-update-available">
<span className="bx bx-sync global-menu-button-update-available-button" title={t("update_available.update_available")}></span>
<span className="bx bx-sync global-menu-button-update-available-button" title={t("update_available.update_available")}></span>
</div>}
</>}
noDropdownListStyle
>
<MenuItem command="openNewWindow" icon="bx bx-window-open" text={t("global_menu.open_new_window")} />
@@ -48,8 +47,8 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
<ToggleWindowOnTop />
<KeyboardActionMenuItem command="toggleZenMode" icon="bx bxs-yin-yang" text={t("global_menu.toggle-zen-mode")} />
<FormDropdownDivider />
<SwitchToOptions />
<SwitchToOptions />
<MenuItem command="showLaunchBarSubtree" icon={`bx ${isMobile() ? "bx-mobile" : "bx-sidebar"}`} text={t("global_menu.configure_launchbar")} />
<AdvancedMenu />
<MenuItem command="showOptions" icon="bx bx-cog" text={t("global_menu.options")} />
@@ -118,7 +117,7 @@ function KeyboardActionMenuItem({ text, command, ...props }: MenuItemProps<Keybo
/>
}
function VerticalLayoutIcon() {
function VerticalLayoutIcon() {
const logoRef = useRef<SVGSVGElement>(null);
useStaticTooltip(logoRef);
@@ -141,44 +140,48 @@ function VerticalLayoutIcon() {
)
}
function ZoomControls({ parentComponent }: { parentComponent?: Component | null }) {
const [ zoomLevel ] = useTriliumOption("zoomFactor");
function ZoomControls({ parentComponent }: { parentComponent?: Component | null }) {
const [ zoomLevel, setZoomLevel ] = useState(100);
function ZoomControlButton({ command, title, icon, children, dismiss }: { command: KeyboardActionNames, title: string, icon?: string, children?: ComponentChildren, dismiss?: boolean }) {
function updateZoomState() {
if (!isElectron()) {
return;
}
const zoomFactor = dynamicRequire("electron").webFrame.getZoomFactor();
setZoomLevel(Math.round(zoomFactor * 100));
}
useEffect(updateZoomState, []);
function ZoomControlButton({ command, title, icon, children }: { command: KeyboardActionNames, title: string, icon?: string, children?: ComponentChildren }) {
const linkRef = useRef<HTMLAnchorElement>(null);
useStaticTooltipWithKeyboardShortcut(linkRef, title, command, {
placement: "bottom",
fallbackPlacements: [ "bottom" ]
});
useStaticTooltipWithKeyboardShortcut(linkRef, title, command);
return (
<a
ref={linkRef}
tabIndex={0}
onClick={(e) => {
parentComponent?.triggerCommand(command);
if (!dismiss) {
e.stopPropagation();
}
setTimeout(() => updateZoomState(), 300)
e.stopPropagation();
}}
className={`dropdown-item-button ${icon}`}
className={icon}
>{children}</a>
)
}
return isElectron() ? (
<FormListItem
icon="bx bx-empty"
container
className="zoom-container"
onClick={(e) => e.stopPropagation()}
>
{t("global_menu.zoom")}
<>
<div className="zoom-buttons">
<ZoomControlButton command="toggleFullscreen" title={t("global_menu.toggle_fullscreen")} icon="bx bx-expand-alt" dismiss />
<ZoomControlButton command="toggleFullscreen" title={t("global_menu.toggle_fullscreen")} icon="bx bx-expand-alt" />
&nbsp;
<ZoomControlButton command="zoomOut" title={t("global_menu.zoom_out")} icon="bx bx-minus" />
<ZoomControlButton command="zoomReset" title={t("global_menu.reset_zoom_level")}>{(parseFloat(zoomLevel) * 100).toFixed(0)}{t("units.percentage")}</ZoomControlButton>
<ZoomControlButton command="zoomReset" title={t("global_menu.reset_zoom_level")}>{zoomLevel}{t("units.percentage")}</ZoomControlButton>
<ZoomControlButton command="zoomIn" title={t("global_menu.zoom_in")} icon="bx bx-plus" />
</div>
</>
@@ -196,7 +199,7 @@ function ToggleWindowOnTop() {
<MenuItem
icon="bx bx-pin"
text={t("title_bar_buttons.window-on-top")}
active={isAlwaysOnTop}
active={isAlwaysOnTop}
command={() => {
const newState = !isAlwaysOnTop;
focusedWindow?.setAlwaysOnTop(newState);
@@ -210,8 +213,8 @@ function useTriliumUpdateStatus() {
const [ latestVersion, setLatestVersion ] = useState<string>();
const [ checkForUpdates ] = useTriliumOptionBool("checkForUpdates");
const isUpdateAvailable = utils.isUpdateAvailable(latestVersion, glob.triliumVersion);
async function updateVersionStatus() {
async function updateVersionStatus() {
const RELEASES_API_URL = "https://api.github.com/repos/TriliumNext/Trilium/releases/latest";
const resp = await fetch(RELEASES_API_URL);
@@ -233,4 +236,4 @@ function useTriliumUpdateStatus() {
}, [ checkForUpdates ]);
return { isUpdateAvailable, latestVersion };
}
}

View File

@@ -0,0 +1,55 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
export default class MovePaneButton extends OnClickButtonWidget {
private isMovingLeft: boolean;
constructor(isMovingLeft: boolean) {
super();
this.isMovingLeft = isMovingLeft;
this.icon(isMovingLeft ? "bx-chevron-left" : "bx-chevron-right")
.title(isMovingLeft ? t("move_pane_button.move_left") : t("move_pane_button.move_right"))
.titlePlacement("bottom")
.onClick(async (widget, e) => {
e.stopPropagation();
widget.triggerCommand("moveThisNoteSplit", { ntxId: widget.getClosestNtxId(), isMovingLeft: this.isMovingLeft });
})
.class("icon-action");
}
isEnabled() {
if (!super.isEnabled()) {
return false;
}
if (this.isMovingLeft) {
// movable if the current context is not a main context, i.e. non-null mainNtxId
return !!this.noteContext?.mainNtxId;
} else {
const currentIndex = appContext.tabManager.noteContexts.findIndex((c) => c.ntxId === this.ntxId);
const nextContext = appContext.tabManager.noteContexts[currentIndex + 1];
// movable if the next context is not null and not a main context, i.e. non-null mainNtxId
return !!nextContext?.mainNtxId;
}
}
async noteContextRemovedEvent() {
this.refresh();
}
async newNoteContextCreatedEvent() {
this.refresh();
}
async noteContextReorderEvent() {
this.refresh();
}
async contextsReopenedEvent() {
this.refresh();
}
}

View File

@@ -1,42 +0,0 @@
import { useContext, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import ActionButton from "../react/ActionButton";
import { ParentComponent } from "../react/react_utils";
import BasicWidget from "../basic_widget";
import { useNoteContext, useTriliumEvents } from "../react/hooks";
import appContext from "../../components/app_context";
interface MovePaneButtonProps {
direction: "left" | "right";
}
export default function MovePaneButton({ direction }: MovePaneButtonProps) {
const parentWidget = useContext(ParentComponent) as BasicWidget | undefined;
const { noteContext, ntxId } = useNoteContext();
const [ refreshCounter, setRefreshCounter ] = useState(0);
function isEnabled() {
if (direction === "left") {
// movable if the current context is not a main context, i.e. non-null mainNtxId
return !!noteContext?.mainNtxId;
} else {
const currentIndex = appContext.tabManager.noteContexts.findIndex((c) => c.ntxId === ntxId);
const nextContext = appContext.tabManager.noteContexts[currentIndex + 1];
// movable if the next context is not null and not a main context, i.e. non-null mainNtxId
return !!nextContext?.mainNtxId;
}
}
useTriliumEvents([ "noteContextRemoved", "newNoteContextCreated", "noteContextReorder", "contextsReopened" ], () => {
setRefreshCounter(refreshCounter + 1);
});
return (
<ActionButton
icon={direction === "left" ? "bx bx-chevron-left" : "bx bx-chevron-right"}
text={direction === "left" ? t("move_pane_button.move_left") : t("move_pane_button.move_right")}
onClick={(() => parentWidget?.triggerCommand("moveThisNoteSplit", { ntxId: parentWidget.getClosestNtxId(), isMovingLeft: direction === "left" }))}
className={!isEnabled() ? "hidden-ext" : ""}
/>
);
}

View File

@@ -1,25 +0,0 @@
.note-list-widget {
min-height: 0;
overflow: auto;
contain: none !important;
}
.note-list-widget .note-list {
padding: 10px;
}
.note-list-widget.full-height,
.note-list-widget.full-height .note-list-widget-content {
height: 100%;
}
.note-list-widget video {
height: 100%;
}
/* #region Pagination */
.note-list-pager span.current-page {
text-decoration: underline;
font-weight: bold;
}
/* #endregion */

View File

@@ -1,203 +0,0 @@
import { allViewTypes, ViewModeProps, ViewTypeOptions } from "./interface";
import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useTriliumEvent } from "../react/hooks";
import FNote from "../../entities/fnote";
import "./NoteList.css";
import { ListView, GridView } from "./legacy/ListOrGridView";
import { useEffect, useRef, useState } from "preact/hooks";
import GeoView from "./geomap";
import ViewModeStorage from "./view_mode_storage";
import CalendarView from "./calendar";
import TableView from "./table";
import BoardView from "./board";
import { subscribeToMessages, unsubscribeToMessage as unsubscribeFromMessage } from "../../services/ws";
import { WebSocketMessage } from "@triliumnext/commons";
import froca from "../../services/froca";
interface NoteListProps<T extends object> {
note: FNote | null | undefined;
notePath: string | null | undefined;
highlightedTokens?: string[] | null;
/** if set to `true` then only collection-type views are displayed such as geo-map and the calendar. The original book types grid and list will be ignored. */
displayOnlyCollections?: boolean;
isEnabled: boolean;
ntxId: string | null | undefined;
}
export default function NoteList<T extends object>(props: Pick<NoteListProps<T>, "displayOnlyCollections">) {
const { note, noteContext, notePath, ntxId } = useNoteContext();
const isEnabled = noteContext?.hasNoteList();
return <CustomNoteList note={note} isEnabled={!!isEnabled} notePath={notePath} ntxId={ntxId} {...props} />
}
export function SearchNoteList<T extends object>(props: Omit<NoteListProps<T>, "isEnabled">) {
return <CustomNoteList {...props} isEnabled={true} />
}
function CustomNoteList<T extends object>({ note, isEnabled: shouldEnable, notePath, highlightedTokens, displayOnlyCollections, ntxId }: NoteListProps<T>) {
const widgetRef = useRef<HTMLDivElement>(null);
const viewType = useNoteViewType(note);
const noteIds = useNoteIds(note, viewType, ntxId);
const isFullHeight = (viewType && viewType !== "list" && viewType !== "grid");
const [ isIntersecting, setIsIntersecting ] = useState(false);
const shouldRender = (isFullHeight || isIntersecting || note?.type === "book");
const isEnabled = (note && shouldEnable && !!viewType && shouldRender);
useEffect(() => {
if (isFullHeight || displayOnlyCollections || note?.type === "book") {
// Double role: no need to check if the note list is visible if the view is full-height or book, but also prevent legacy views if `displayOnlyCollections` is true.
return;
}
const observer = new IntersectionObserver(
(entries) => {
if (!isIntersecting) {
setIsIntersecting(entries[0].isIntersecting);
observer.disconnect();
}
},
{
rootMargin: "50px",
threshold: 0.1
}
);
// there seems to be a race condition on Firefox which triggers the observer only before the widget is visible
// (intersection is false). https://github.com/zadam/trilium/issues/4165
setTimeout(() => widgetRef.current && observer.observe(widgetRef.current), 10);
return () => observer.disconnect();
}, [ widgetRef, isFullHeight, displayOnlyCollections, note ]);
// Preload the configuration.
let props: ViewModeProps<any> | undefined | null = null;
const viewModeConfig = useViewModeConfig(note, viewType);
if (note && notePath && viewModeConfig) {
props = {
note, noteIds, notePath,
highlightedTokens,
viewConfig: viewModeConfig[0],
saveConfig: viewModeConfig[1]
}
}
return (
<div ref={widgetRef} className={`note-list-widget component ${isFullHeight ? "full-height" : ""}`}>
{props && isEnabled && (
<div className="note-list-widget-content">
{getComponentByViewType(viewType, props)}
</div>
)}
</div>
);
}
function getComponentByViewType(viewType: ViewTypeOptions, props: ViewModeProps<any>) {
switch (viewType) {
case "list":
return <ListView {...props} />;
case "grid":
return <GridView {...props} />;
case "geoMap":
return <GeoView {...props} />;
case "calendar":
return <CalendarView {...props} />
case "table":
return <TableView {...props} />
case "board":
return <BoardView {...props} />
}
}
function useNoteViewType(note?: FNote | null): ViewTypeOptions | undefined {
const [ viewType ] = useNoteLabel(note, "viewType");
if (!note) {
return undefined;
} else if (!(allViewTypes as readonly string[]).includes(viewType || "")) {
// when not explicitly set, decide based on the note type
return note.type === "search" ? "list" : "grid";
} else {
return viewType as ViewTypeOptions;
}
}
function useNoteIds(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined, ntxId: string | null | undefined) {
const [ noteIds, setNoteIds ] = useState<string[]>([]);
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
async function refreshNoteIds() {
if (!note) {
setNoteIds([]);
} else {
setNoteIds(await getNoteIds(note));
}
}
async function getNoteIds(note: FNote) {
if (viewType === "list" || viewType === "grid") {
return note.getChildNoteIds();
} else {
return await note.getSubtreeNoteIds(includeArchived);
}
}
// Refresh on note switch.
useEffect(() => { refreshNoteIds() }, [ note, includeArchived ]);
// Refresh on alterations to the note subtree.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (note && loadResults.getBranchRows().some(branch =>
branch.parentNoteId === note.noteId
|| noteIds.includes(branch.parentNoteId ?? ""))
|| loadResults.getAttributeRows().some(attr => attr.name === "archived" && attr.noteId && noteIds.includes(attr.noteId))
) {
refreshNoteIds();
}
})
// Refresh on search.
useTriliumEvent("searchRefreshed", ({ ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId) return;
refreshNoteIds();
});
// Refresh on import.
useEffect(() => {
async function onImport(message: WebSocketMessage) {
if (!("taskType" in message) || message.taskType !== "importNotes" || message.type !== "taskSucceeded") return;
const { parentNoteId, importedNoteId } = message.result;
if (!parentNoteId || !importedNoteId) return;
if (importedNoteId && (parentNoteId === note?.noteId || noteIds.includes(parentNoteId))) {
const importedNote = await froca.getNote(importedNoteId);
if (!importedNote) return;
setNoteIds([
...noteIds,
...await getNoteIds(importedNote),
importedNoteId
])
}
}
subscribeToMessages(onImport);
return () => unsubscribeFromMessage(onImport);
}, [ note, noteIds, setNoteIds ])
return noteIds;
}
function useViewModeConfig<T extends object>(note: FNote | null | undefined, viewType: ViewTypeOptions | undefined) {
const [ viewConfig, setViewConfig ] = useState<[T | undefined, (data: T) => void]>();
useEffect(() => {
if (!note || !viewType) return;
const viewStorage = new ViewModeStorage<T>(note, viewType);
viewStorage.restore().then(config => {
const storeFn = (config: T) => {
setViewConfig([ config, storeFn ]);
viewStorage.store(config);
};
setViewConfig([ config, storeFn ]);
});
}, [ note, viewType ]);
return viewConfig;
}

View File

@@ -1,85 +0,0 @@
import { ComponentChildren } from "preact";
import { Dispatch, StateUpdater, useEffect, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import froca from "../../services/froca";
import { useNoteLabelInt } from "../react/hooks";
import { t } from "../../services/i18n";
interface PaginationContext {
page: number;
setPage: Dispatch<StateUpdater<number>>;
pageNotes?: FNote[];
pageCount: number;
pageSize: number;
totalNotes: number;
}
export function Pager({ page, pageSize, setPage, pageCount, totalNotes }: Omit<PaginationContext, "pageNotes">) {
if (pageCount < 2) return;
let lastPrinted = false;
let children: ComponentChildren[] = [];
for (let i = 1; i <= pageCount; i++) {
if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(page - i) <= 2) {
lastPrinted = true;
const startIndex = (i - 1) * pageSize + 1;
const endIndex = Math.min(totalNotes, i * pageSize);
if (i !== page) {
children.push((
<a
href="javascript:"
title={t("pagination.page_title", { startIndex, endIndex })}
onClick={() => setPage(i)}
>
{i}
</a>
))
} else {
// Current page
children.push(<span className="current-page">{i}</span>)
}
children.push(<>{" "}&nbsp;{" "}</>);
} else if (lastPrinted) {
children.push(<>{"... "}&nbsp;{" "}</>);
lastPrinted = false;
}
}
return (
<div class="note-list-pager">
{children}
<span className="note-list-pager-total-count">({t("pagination.total_notes", { count: totalNotes })})</span>
</div>
)
}
export function usePagination(note: FNote, noteIds: string[]): PaginationContext {
const [ page, setPage ] = useState(1);
const [ pageNotes, setPageNotes ] = useState<FNote[]>();
// Parse page size.
const [ pageSize ] = useNoteLabelInt(note, "pageSize");
const normalizedPageSize = (pageSize && pageSize > 0 ? pageSize : 20);
// Calculate start/end index.
const startIdx = (page - 1) * normalizedPageSize;
const endIdx = startIdx + normalizedPageSize;
const pageCount = Math.ceil(noteIds.length / normalizedPageSize);
// Obtain notes within the range.
const pageNoteIds = noteIds.slice(startIdx, Math.min(endIdx, noteIds.length));
useEffect(() => {
froca.getNotes(pageNoteIds).then(setPageNotes);
}, [ note, noteIds, page, pageSize ]);
return {
page, setPage, pageNotes, pageCount,
pageSize: normalizedPageSize,
totalNotes: noteIds.length
};
}

View File

@@ -1,210 +0,0 @@
import { BoardViewData } from ".";
import appContext from "../../../components/app_context";
import FNote from "../../../entities/fnote";
import attributes from "../../../services/attributes";
import branches from "../../../services/branches";
import { executeBulkActions } from "../../../services/bulk_action";
import froca from "../../../services/froca";
import { t } from "../../../services/i18n";
import note_create from "../../../services/note_create";
import server from "../../../services/server";
import { ColumnMap } from "./data";
export default class BoardApi {
constructor(
private byColumn: ColumnMap | undefined,
public columns: string[],
private parentNote: FNote,
private statusAttribute: string,
private viewConfig: BoardViewData,
private saveConfig: (newConfig: BoardViewData) => void,
private setBranchIdToEdit: (branchId: string | undefined) => void
) {};
async createNewItem(column: string, title: string) {
try {
// Get the parent note path
const parentNotePath = this.parentNote.noteId;
// Create a new note as a child of the parent note
const { note: newNote, branch: newBranch } = await note_create.createNote(parentNotePath, {
activate: false,
title
});
if (newNote && newBranch) {
await this.changeColumn(newNote.noteId, column);
}
} catch (error) {
console.error("Failed to create new item:", error);
}
}
async changeColumn(noteId: string, newColumn: string) {
await attributes.setLabel(noteId, this.statusAttribute, newColumn);
}
async addNewColumn(columnName: string) {
if (!columnName.trim()) {
return;
}
if (!this.viewConfig) {
this.viewConfig = {};
}
if (!this.viewConfig.columns) {
this.viewConfig.columns = [];
}
// Add the new column to persisted data if it doesn't exist
const existingColumn = this.viewConfig.columns.find(col => col.value === columnName);
if (!existingColumn) {
this.viewConfig.columns.push({ value: columnName });
this.saveConfig(this.viewConfig);
}
}
async removeColumn(column: string) {
// Remove the value from the notes.
const noteIds = this.byColumn?.get(column)?.map(item => item.note.noteId) || [];
await executeBulkActions(noteIds, [
{
name: "deleteLabel",
labelName: this.statusAttribute
}
]);
this.viewConfig.columns = (this.viewConfig.columns ?? []).filter(col => col.value !== column);
this.saveConfig(this.viewConfig);
}
async renameColumn(oldValue: string, newValue: string) {
const noteIds = this.byColumn?.get(oldValue)?.map(item => item.note.noteId) || [];
// Change the value in the notes.
await executeBulkActions(noteIds, [
{
name: "updateLabelValue",
labelName: this.statusAttribute,
labelValue: newValue
}
]);
// Rename the column in the persisted data.
for (const column of this.viewConfig.columns || []) {
if (column.value === oldValue) {
column.value = newValue;
}
}
this.saveConfig(this.viewConfig);
}
reorderColumn(fromIndex: number, toIndex: number) {
if (!this.columns || fromIndex === toIndex) return;
const newColumns = [...this.columns];
const [movedColumn] = newColumns.splice(fromIndex, 1);
// Adjust toIndex after removing the element
// When moving forward (right), the removal shifts indices left
let adjustedToIndex = toIndex;
if (fromIndex < toIndex) {
adjustedToIndex = toIndex - 1;
}
newColumns.splice(adjustedToIndex, 0, movedColumn);
// Update view config with new column order
const newViewConfig = {
...this.viewConfig,
columns: newColumns.map(col => ({ value: col }))
};
this.saveConfig(newViewConfig);
return newColumns;
}
async insertRowAtPosition(
column: string,
relativeToBranchId: string,
direction: "before" | "after") {
const { note, branch } = await note_create.createNote(this.parentNote.noteId, {
activate: false,
targetBranchId: relativeToBranchId,
target: direction,
title: t("board_view.new-item")
});
if (!note || !branch) {
throw new Error("Failed to create note");
}
const { noteId } = note;
await this.changeColumn(noteId, column);
this.startEditing(branch.branchId);
return note;
}
openNote(noteId: string) {
appContext.triggerCommand("openInPopup", { noteIdOrPath: noteId });
}
startEditing(branchId: string) {
this.setBranchIdToEdit(branchId);
}
dismissEditingTitle() {
this.setBranchIdToEdit(undefined);
}
renameCard(noteId: string, newTitle: string) {
return server.put(`notes/${noteId}/title`, { title: newTitle.trim() });
}
removeFromBoard(noteId: string) {
const note = froca.getNoteFromCache(noteId);
if (!note) return;
return attributes.removeOwnedLabelByName(note, this.statusAttribute);
}
async moveWithinBoard(noteId: string, sourceBranchId: string, sourceIndex: number, targetIndex: number, sourceColumn: string, targetColumn: string) {
const targetItems = this.byColumn?.get(targetColumn) ?? [];
const note = froca.getNoteFromCache(noteId);
if (!note) return;
if (sourceColumn !== targetColumn) {
// Moving to a different column
await this.changeColumn(noteId, targetColumn);
// If there are items in the target column, reorder
if (targetItems.length > 0 && targetIndex < targetItems.length) {
const targetBranch = targetItems[targetIndex].branch;
await branches.moveBeforeBranch([ sourceBranchId ], targetBranch.branchId);
}
} else if (sourceIndex !== targetIndex) {
// Reordering within the same column
let targetBranchId: string | null = null;
if (targetIndex < targetItems.length) {
// Moving before an existing item
const adjustedIndex = sourceIndex < targetIndex ? targetIndex : targetIndex;
if (adjustedIndex < targetItems.length) {
targetBranchId = targetItems[adjustedIndex].branch.branchId;
if (targetBranchId) {
await branches.moveBeforeBranch([ sourceBranchId ], targetBranchId);
}
}
} else if (targetIndex > 0) {
// Moving to the end - place after the last item
const lastItem = targetItems[targetItems.length - 1];
await branches.moveAfterBranch([ sourceBranchId ], lastItem.branch.branchId);
}
}
}
}

View File

@@ -1,115 +0,0 @@
import { useCallback, useContext, useEffect, useRef, useState } from "preact/hooks";
import FBranch from "../../../entities/fbranch";
import FNote from "../../../entities/fnote";
import BoardApi from "./api";
import { BoardViewContext, TitleEditor } from ".";
import { ContextMenuEvent } from "../../../menus/context_menu";
import { openNoteContextMenu } from "./context_menu";
import { t } from "../../../services/i18n";
export const CARD_CLIPBOARD_TYPE = "trilium/board-card";
export interface CardDragData {
noteId: string;
branchId: string;
index: number;
fromColumn: string;
}
export default function Card({
api,
note,
branch,
column,
index,
isDragging
}: {
api: BoardApi,
note: FNote,
branch: FBranch,
column: string,
index: number,
isDragging: boolean
}) {
const { branchIdToEdit, setBranchIdToEdit, setDraggedCard } = useContext(BoardViewContext)!;
const isEditing = branch.branchId === branchIdToEdit;
const colorClass = note.getColorClass() || '';
const editorRef = useRef<HTMLInputElement>(null);
const isArchived = note.isArchived;
const [ isVisible, setVisible ] = useState(true);
const [ title, setTitle ] = useState(note.title);
const handleDragStart = useCallback((e: DragEvent) => {
e.dataTransfer!.effectAllowed = 'move';
const data: CardDragData = { noteId: note.noteId, branchId: branch.branchId, fromColumn: column, index };
setDraggedCard(data);
e.dataTransfer!.setData(CARD_CLIPBOARD_TYPE, JSON.stringify(data));
}, [note.noteId, branch.branchId, column, index]);
const handleDragEnd = useCallback((e: DragEvent) => {
setDraggedCard(null);
}, [setDraggedCard]);
const handleContextMenu = useCallback((e: ContextMenuEvent) => {
openNoteContextMenu(api, e, note, branch.branchId, column);
}, [ api, note, branch, column ]);
const handleOpen = useCallback(() => {
api.openNote(note.noteId);
}, [ api, note ]);
const handleEdit = useCallback((e: MouseEvent) => {
e.stopPropagation(); // don't also open the note
setBranchIdToEdit?.(branch.branchId);
}, [ setBranchIdToEdit, branch ]);
useEffect(() => {
editorRef.current?.focus();
}, [ isEditing ]);
useEffect(() => {
setTitle(note.title);
}, [ note ]);
useEffect(() => {
setVisible(!isDragging);
}, [ isDragging ]);
return (
<div
className={`board-note ${colorClass} ${isDragging ? 'dragging' : ''} ${isEditing ? "editing" : ""} ${isArchived ? "archived" : ""}`}
draggable
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onContextMenu={handleContextMenu}
onClick={!isEditing ? handleOpen : undefined}
style={{
display: !isVisible ? "none" : undefined
}}
>
{!isEditing ? (
<>
<span className="title">
<span class={`icon ${note.getIcon()}`} />
{title}
</span>
<span
className="edit-icon icon bx bx-edit"
title={t("board_view.edit-note-title")}
onClick={handleEdit}
/>
</>
) : (
<TitleEditor
currentValue={note.title}
save={newTitle => {
api.renameCard(note.noteId, newTitle);
setTitle(newTitle);
}}
dismiss={() => api.dismissEditingTitle()}
multiline
/>
)}
</div>
)
}

View File

@@ -1,277 +0,0 @@
import { useCallback, useContext, useEffect, useRef, useState } from "preact/hooks";
import FBranch from "../../../entities/fbranch";
import FNote from "../../../entities/fnote";
import { BoardViewContext, TitleEditor } from ".";
import branches from "../../../services/branches";
import { openColumnContextMenu } from "./context_menu";
import { ContextMenuEvent } from "../../../menus/context_menu";
import Icon from "../../react/Icon";
import { t } from "../../../services/i18n";
import BoardApi from "./api";
import Card, { CARD_CLIPBOARD_TYPE, CardDragData } from "./card";
import { JSX } from "preact/jsx-runtime";
import froca from "../../../services/froca";
import { DragData, TREE_CLIPBOARD_TYPE } from "../../note_tree";
interface DragContext {
column: string;
columnIndex: number,
columnItems?: { note: FNote, branch: FBranch }[];
}
export default function Column({
column,
columnIndex,
isDraggingColumn,
columnItems,
api,
onColumnHover,
isAnyColumnDragging,
}: {
columnItems?: { note: FNote, branch: FBranch }[];
isDraggingColumn: boolean,
api: BoardApi,
onColumnHover?: (index: number, mouseX: number, rect: DOMRect) => void,
isAnyColumnDragging?: boolean
} & DragContext) {
const [ isVisible, setVisible ] = useState(true);
const { columnNameToEdit, setColumnNameToEdit, dropTarget, draggedCard, dropPosition } = useContext(BoardViewContext)!;
const isEditing = (columnNameToEdit === column);
const editorRef = useRef<HTMLInputElement>(null);
const { handleColumnDragStart, handleColumnDragEnd, handleDragOver, handleDragLeave, handleDrop } = useDragging({
column, columnIndex, columnItems, isEditing
});
const handleEdit = useCallback(() => {
setColumnNameToEdit?.(column);
}, [column]);
const handleContextMenu = useCallback((e: ContextMenuEvent) => {
openColumnContextMenu(api, e, column);
}, [ api, column ]);
/** Allow using mouse wheel to scroll inside card, while also maintaining column horizontal scrolling. */
const handleScroll = useCallback((event: JSX.TargetedWheelEvent<HTMLDivElement>) => {
const el = event.currentTarget;
if (!el) return;
const needsScroll = el.scrollHeight > el.clientHeight;
if (needsScroll) {
event.stopPropagation();
}
}, []);
useEffect(() => {
editorRef.current?.focus();
}, [ isEditing ]);
useEffect(() => {
setVisible(!isDraggingColumn);
}, [ isDraggingColumn ]);
const handleColumnDragOver = useCallback((e: DragEvent) => {
if (!isAnyColumnDragging || !onColumnHover) return;
e.preventDefault();
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
onColumnHover(columnIndex, e.clientX, rect);
}, [isAnyColumnDragging, onColumnHover, columnIndex]);
return (
<div
className={`board-column ${dropTarget === column && draggedCard?.fromColumn !== column ? 'drag-over' : ''}`}
onDragOver={isAnyColumnDragging ? handleColumnDragOver : handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onWheel={handleScroll}
style={{
display: !isVisible ? "none" : undefined
}}
>
<h3
className={`${isEditing ? "editing" : ""}`}
draggable
onDragStart={handleColumnDragStart}
onDragEnd={handleColumnDragEnd}
onContextMenu={handleContextMenu}
>
{!isEditing ? (
<>
<span className="title">{column}</span>
<span
className="edit-icon icon bx bx-edit-alt"
title={t("board_view.edit-column-title")}
onClick={handleEdit}
/>
</>
) : (
<TitleEditor
currentValue={column}
save={newTitle => api.renameColumn(column, newTitle)}
dismiss={() => setColumnNameToEdit?.(undefined)}
/>
)}
</h3>
{(columnItems ?? []).map(({ note, branch }, index) => {
const showIndicatorBefore = dropPosition?.column === column &&
dropPosition.index === index &&
draggedCard?.noteId !== note.noteId;
return (
<>
{showIndicatorBefore && (
<div className="board-drop-placeholder show" />
)}
<Card
key={note.noteId}
api={api}
note={note}
branch={branch}
column={column}
index={index}
isDragging={draggedCard?.noteId === note.noteId}
/>
</>
);
})}
{dropPosition?.column === column && dropPosition.index === (columnItems?.length ?? 0) && (
<div className="board-drop-placeholder show" />
)}
<AddNewItem api={api} column={column} />
</div>
)
}
function AddNewItem({ column, api }: { column: string, api: BoardApi }) {
const [ isCreatingNewItem, setIsCreatingNewItem ] = useState(false);
const addItemCallback = useCallback(() => setIsCreatingNewItem(true), []);
return (
<div
className={`board-new-item ${isCreatingNewItem ? "editing" : ""}`}
onClick={addItemCallback}
>
{!isCreatingNewItem ? (
<>
<Icon icon="bx bx-plus" />{" "}
{t("board_view.new-item")}
</>
) : (
<TitleEditor
placeholder={t("board_view.new-item-placeholder")}
save={(title) => api.createNewItem(column, title)}
dismiss={() => setIsCreatingNewItem(false)}
multiline isNewItem
/>
)}
</div>
);
}
function useDragging({ column, columnIndex, columnItems, isEditing }: DragContext & { isEditing: boolean }) {
const { api, parentNote, draggedColumn, setDraggedColumn, setDropTarget, setDropPosition, dropPosition } = useContext(BoardViewContext)!;
/** Needed to track if current column is dragged in real-time, since {@link draggedColumn} is populated one render cycle later. */
const isDraggingRef = useRef(false);
const handleColumnDragStart = useCallback((e: DragEvent) => {
if (isEditing) return;
isDraggingRef.current = true;
e.dataTransfer!.effectAllowed = 'move';
e.dataTransfer!.setData('text/plain', column);
setDraggedColumn({ column, index: columnIndex });
e.stopPropagation(); // Prevent card drag from interfering
}, [column, columnIndex, setDraggedColumn, isEditing]);
const handleColumnDragEnd = useCallback(() => {
isDraggingRef.current = false;
setDraggedColumn(null);
}, [setDraggedColumn]);
const handleDragOver = useCallback((e: DragEvent) => {
if (isEditing || draggedColumn || isDraggingRef.current) return; // Don't handle card drops when dragging columns
if (!e.dataTransfer?.types.includes(CARD_CLIPBOARD_TYPE) && !e.dataTransfer?.types.includes(TREE_CLIPBOARD_TYPE)) return;
e.preventDefault();
setDropTarget(column);
// Calculate drop position based on mouse position
const cards = Array.from((e.currentTarget as HTMLElement)?.querySelectorAll('.board-note'));
const mouseY = e.clientY;
let newIndex = cards.length;
for (let i = 0; i < cards.length; i++) {
const card = cards[i] as HTMLElement;
const rect = card.getBoundingClientRect();
const cardMiddle = rect.top + rect.height / 2;
if (mouseY < cardMiddle) {
newIndex = i;
break;
}
}
if (!(dropPosition?.column === column && dropPosition.index === newIndex)) {
setDropPosition({ column, index: newIndex });
}
}, [column, setDropTarget, dropPosition, setDropPosition, isEditing]);
const handleDragLeave = useCallback((e: DragEvent) => {
const relatedTarget = e.relatedTarget as HTMLElement;
const currentTarget = e.currentTarget as HTMLElement;
if (!currentTarget.contains(relatedTarget)) {
setDropTarget(null);
setDropPosition(null);
}
}, [setDropTarget, setDropPosition]);
const handleDrop = useCallback(async (e: DragEvent) => {
if (draggedColumn) return; // Don't handle card drops when dragging columns
e.preventDefault();
setDropTarget(null);
setDropPosition(null);
const data = e.dataTransfer?.getData(CARD_CLIPBOARD_TYPE) || e.dataTransfer?.getData("text");
if (!data) return;
let draggedCard: CardDragData | DragData[];
try {
draggedCard = JSON.parse(data);
} catch (e) {
return;
}
if (Array.isArray(draggedCard)) {
// From note tree.
const { noteId, branchId } = draggedCard[0];
const targetNote = await froca.getNote(noteId, true);
const parentNoteId = parentNote?.noteId;
if (!parentNoteId || !dropPosition) return;
const targetIndex = dropPosition.index - 1;
const targetItems = columnItems || [];
const targetBranch = targetIndex >= 0 ? targetItems[targetIndex].branch : null;
await api?.changeColumn(noteId, column);
const parents = targetNote?.getParentNoteIds();
if (!parents?.includes(parentNoteId)) {
if (!targetBranch) {
// First.
await branches.cloneNoteToParentNote(noteId, parentNoteId);
} else {
await branches.cloneNoteAfter(noteId, targetBranch.branchId);
}
} else if (targetBranch) {
await branches.moveAfterBranch([ branchId ], targetBranch.branchId);
}
} else if (draggedCard && dropPosition) {
api?.moveWithinBoard(draggedCard.noteId, draggedCard.branchId, draggedCard.index, dropPosition.index, draggedCard.fromColumn, column);
}
}, [ api, draggedColumn, dropPosition, columnItems, column, setDropTarget, setDropPosition ]);
return { handleColumnDragStart, handleColumnDragEnd, handleDragOver, handleDragLeave, handleDrop };
}

View File

@@ -1,98 +0,0 @@
import FNote from "../../../entities/fnote";
import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu";
import link_context_menu from "../../../menus/link_context_menu";
import attributes from "../../../services/attributes";
import branches from "../../../services/branches";
import dialog from "../../../services/dialog";
import { t } from "../../../services/i18n";
import Api from "./api";
export function openColumnContextMenu(api: Api, event: ContextMenuEvent, column: string) {
event.preventDefault();
event.stopPropagation();
contextMenu.show({
x: event.pageX,
y: event.pageY,
items: [
{
title: t("board_view.delete-column"),
uiIcon: "bx bx-trash",
async handler() {
const confirmed = await dialog.confirm(t("board_view.delete-column-confirmation"));
if (!confirmed) {
return;
}
await api.removeColumn(column);
}
}
],
selectMenuItemHandler() {}
});
}
export function openNoteContextMenu(api: Api, event: ContextMenuEvent, note: FNote, branchId: string, column: string) {
event.preventDefault();
event.stopPropagation();
contextMenu.show({
x: event.pageX,
y: event.pageY,
items: [
...link_context_menu.getItems(),
{ kind: "separator" },
{
title: t("board_view.move-to"),
uiIcon: "bx bx-transfer",
items: api.columns.map(columnToMoveTo => ({
title: columnToMoveTo,
enabled: columnToMoveTo !== column,
handler: () => api.changeColumn(note.noteId, columnToMoveTo)
})),
},
getArchiveMenuItem(note),
{ kind: "separator" },
{
title: t("board_view.insert-above"),
uiIcon: "bx bx-list-plus",
handler: () => api.insertRowAtPosition(column, branchId, "before")
},
{
title: t("board_view.insert-below"),
uiIcon: "bx bx-empty",
handler: () => api.insertRowAtPosition(column, branchId, "after")
},
{ kind: "separator" },
{
title: t("board_view.remove-from-board"),
uiIcon: "bx bx-task-x",
handler: () => api.removeFromBoard(note.noteId)
},
{
title: t("board_view.delete-note"),
uiIcon: "bx bx-trash",
handler: () => branches.deleteNotes([ branchId ], false, false)
},
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, note.noteId),
});
}
function getArchiveMenuItem(note: FNote) {
if (!note.isArchived) {
return {
title: t("board_view.archive-note"),
uiIcon: "bx bx-archive",
handler: () => attributes.addLabel(note.noteId, "archived")
}
} else {
return {
title: t("board_view.unarchive-note"),
uiIcon: "bx bx-archive-out",
handler: async () => {
attributes.removeOwnedLabelByName(note, "archived")
}
}
}
}

View File

@@ -1,313 +0,0 @@
.board-view {
overflow-x: auto;
position: relative;
height: 100%;
user-select: none;
--card-font-size: 0.9em;
--card-line-height: 1.2;
--card-padding: 0.6em;
}
.board-view-container {
height: 100%;
display: flex;
gap: 1em;
padding: 1em;
padding-bottom: 0;
align-items: flex-start;
}
.board-view-container .board-column {
width: 250px;
flex-shrink: 0;
border: 2px solid transparent;
border-radius: 8px;
padding: 0.5em;
background-color: var(--accented-background-color);
transition: border-color 0.2s ease;
overflow-y: auto;
max-height: 100%;
}
.board-view-container .board-column.drag-over {
border-color: var(--main-text-color);
background-color: var(--hover-item-background-color);
}
.board-view-container .board-column h3 {
font-size: 1em;
margin-bottom: 0.75em;
padding: 0.5em 0.5em 0.5em 0.5em;
border-bottom: 1px solid var(--main-border-color);
cursor: grab;
position: relative;
transition: background-color 0.2s ease, border-radius 0.2s ease;
box-sizing: border-box;
background-color: transparent;
}
.board-view-container .board-column h3 {
display: flex;
align-items: center;
}
.board-view-container .board-column h3 > .title {
flex-grow: 1;
}
.board-view-container .board-column h3:active {
cursor: grabbing;
}
.board-view-container .board-column h3.editing {
cursor: default;
}
.board-view-container .board-column h3.editing input {
padding: 0;
}
.board-view-container .board-column h3:hover {
background-color: var(--hover-item-background-color);
border-radius: 4px;
}
.board-view-container .board-column h3.editing {
background-color: var(--main-background-color);
border: 1px solid var(--main-text-color);
border-radius: 4px;
}
.board-view-container .board-column h3 input {
background: transparent;
border: none;
outline: none;
font-size: inherit;
font-weight: inherit;
color: inherit;
width: 100%;
font-family: inherit;
}
.board-view-container .board-column .edit-icon {
opacity: 0;
margin-left: 0.5em;
transition: opacity 0.2s ease;
color: var(--muted-text-color);
cursor: pointer;
}
.board-view-container .board-column h3:hover .edit-icon,
.board-view-container .board-note:hover .edit-icon {
opacity: 1;
}
.board-view-container .board-note,
.board-view-container .board-new-item.editing {
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1);
margin: 0.65em 0;
padding: var(--card-padding);
border-radius: 5px;
cursor: move;
position: relative;
background-color: var(--main-background-color);
border: 1px solid var(--main-border-color);
opacity: 1;
line-height: var(--card-line-height);
overflow-wrap: break-word;
overflow: hidden;
font-size: var(--card-font-size);
}
.board-view-container .board-note {
transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.15s ease, margin-top 0.2s ease;
}
.board-view-container .board-note.archived {
opacity: 0.5;
}
.board-view-container .board-note .icon {
margin-right: 0.25em;
display: inline;
}
.board-view-container .board-note > .edit-icon {
position: absolute;
top: 8px;
right: 4px;
padding: 2px;
background-color: var(--main-background-color);
}
.board-view-container .board-note:hover {
box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.1);
}
.board-view-container .board-note:hover > .edit-icon {
position: absolute;
top: 8px;
right: 4px;
color: var(--main-text-color);
background-color: var(--main-background-color);
padding-left: 6px;
}
.board-view-container .board-note.fade-in {
animation: fadeIn 0.15s ease-in;
}
.board-view-container .board-note.fade-out {
animation: fadeOut 0.15s ease-out forwards;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes fadeOut {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-10px); }
}
.board-view-container .board-note.dragging {
opacity: 0.5;
transform: rotate(5deg);
z-index: 1000;
box-shadow: 4px 8px 16px rgba(0, 0, 0, 0.5);
}
.board-view-container .board-note.editing,
.board-view-container .board-new-item.editing {
box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.2);
border-color: var(--main-text-color);
display: flex;
align-items: center;
padding: 0;
}
.board-view-container .board-note.editing textarea,
.board-view-container .board-new-item textarea.form-control {
background: transparent;
border: none;
outline: none;
font-family: inherit;
font-size: inherit;
color: var(--main-text-color);
width: 100%;
padding: var(--card-padding);
line-height: var(--card-line-height);
height: auto;
field-sizing: content;
resize: none;
}
.board-drop-placeholder {
height: 40px;
margin: 0.65em 0;
padding: 0.5em;
border-radius: 5px;
background-color: rgba(0, 0, 0, 0.15);
opacity: 0;
transition: opacity 0.15s ease, height 0.2s ease;
box-sizing: border-box;
}
.board-drop-placeholder.show {
opacity: 0.6;
}
.column-drop-placeholder {
width: 250px;
flex-shrink: 0;
height: 200px;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.1);
opacity: 0;
transition: opacity 0.15s ease;
margin: 0 0.5em;
}
.column-drop-placeholder.show {
opacity: 0.6;
}
.board-new-item {
margin-top: 0.5em;
padding: 0.25em 0.5em;
border-radius: 5px;
color: var(--muted-text-color);
cursor: pointer;
transition: all 0.2s ease;
background-color: transparent;
font-size: var(--card-font-size);
}
.board-new-item:hover {
border-color: var(--main-text-color);
color: var(--main-text-color);
background-color: var(--hover-item-background-color);
}
.board-new-item .icon {
margin-right: 0.25em;
}
.board-add-column {
width: 180px;
flex-shrink: 0;
height: 60px;
border-radius: 8px;
padding: 0.5em;
background-color: var(--accented-background-color);
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--muted-text-color);
font-size: 0.9em;
align-self: flex-start;
}
.board-add-column:hover {
border-color: var(--main-text-color);
color: var(--main-text-color);
background-color: var(--hover-item-background-color);
}
.board-add-column .icon {
margin-right: 0.5em;
font-size: 1.2em;
}
.board-add-column input {
background: var(--main-background-color);
border: 1px solid var(--main-text-color);
border-radius: 4px;
padding: 0.5em;
color: var(--main-text-color);
font-family: inherit;
font-size: inherit;
width: 100%;
text-align: center;
}
.board-drag-preview {
position: fixed;
z-index: 10000;
pointer-events: none;
opacity: 0.8;
transform: rotate(5deg);
box-shadow: 4px 8px 16px rgba(0, 0, 0, 0.5);
background-color: var(--main-background-color);
border: 1px solid var(--main-border-color);
border-radius: 5px;
padding: 0.5em;
font-size: 0.9em;
max-width: 200px;
word-wrap: break-word;
}

View File

@@ -1,271 +0,0 @@
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { ViewModeProps } from "../interface";
import "./index.css";
import { ColumnMap, getBoardData } from "./data";
import { useNoteLabelBoolean, useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
import Icon from "../../react/Icon";
import { t } from "../../../services/i18n";
import Api from "./api";
import FormTextBox from "../../react/FormTextBox";
import { createContext, TargetedKeyboardEvent } from "preact";
import { onWheelHorizontalScroll } from "../../widget_utils";
import Column from "./column";
import BoardApi from "./api";
import FormTextArea from "../../react/FormTextArea";
import FNote from "../../../entities/fnote";
export interface BoardViewData {
columns?: BoardColumnData[];
}
export interface BoardColumnData {
value: string;
}
interface BoardViewContextData {
api?: BoardApi;
parentNote?: FNote;
branchIdToEdit?: string;
columnNameToEdit?: string;
setColumnNameToEdit?: Dispatch<StateUpdater<string | undefined>>;
setBranchIdToEdit?: Dispatch<StateUpdater<string | undefined>>;
draggedColumn: { column: string, index: number } | null;
setDraggedColumn: (column: { column: string, index: number } | null) => void;
dropPosition: { column: string, index: number } | null;
setDropPosition: (position: { column: string, index: number } | null) => void;
setDropTarget: (target: string | null) => void,
dropTarget: string | null;
draggedCard: { noteId: string, branchId: string, fromColumn: string, index: number } | null;
setDraggedCard: Dispatch<StateUpdater<{ noteId: string; branchId: string; fromColumn: string; index: number; } | null>>;
}
export const BoardViewContext = createContext<BoardViewContextData | undefined>(undefined);
export default function BoardView({ note: parentNote, noteIds, viewConfig, saveConfig }: ViewModeProps<BoardViewData>) {
const [ statusAttribute ] = useNoteLabelWithDefault(parentNote, "board:groupBy", "status");
const [ includeArchived ] = useNoteLabelBoolean(parentNote, "includeArchived");
const [ byColumn, setByColumn ] = useState<ColumnMap>();
const [ columns, setColumns ] = useState<string[]>();
const [ draggedCard, setDraggedCard ] = useState<{ noteId: string, branchId: string, fromColumn: string, index: number } | null>(null);
const [ dropTarget, setDropTarget ] = useState<string | null>(null);
const [ dropPosition, setDropPosition ] = useState<{ column: string, index: number } | null>(null);
const [ draggedColumn, setDraggedColumn ] = useState<{ column: string, index: number } | null>(null);
const [ columnDropPosition, setColumnDropPosition ] = useState<number | null>(null);
const [ columnHoverIndex, setColumnHoverIndex ] = useState<number | null>(null);
const [ branchIdToEdit, setBranchIdToEdit ] = useState<string>();
const [ columnNameToEdit, setColumnNameToEdit ] = useState<string>();
const api = useMemo(() => {
return new Api(byColumn, columns ?? [], parentNote, statusAttribute, viewConfig ?? {}, saveConfig, setBranchIdToEdit );
}, [ byColumn, columns, parentNote, statusAttribute, viewConfig, saveConfig, setBranchIdToEdit ]);
const boardViewContext = useMemo<BoardViewContextData>(() => ({
api,
parentNote,
branchIdToEdit, setBranchIdToEdit,
columnNameToEdit, setColumnNameToEdit,
draggedColumn, setDraggedColumn,
dropPosition, setDropPosition,
draggedCard, setDraggedCard,
dropTarget, setDropTarget
}), [
api,
parentNote,
branchIdToEdit, setBranchIdToEdit,
columnNameToEdit, setColumnNameToEdit,
draggedColumn, setDraggedColumn,
dropPosition, setDropPosition,
draggedCard, setDraggedCard,
dropTarget, setDropTarget
]);
function refresh() {
getBoardData(parentNote, statusAttribute, viewConfig ?? {}, includeArchived).then(({ byColumn, newPersistedData }) => {
setByColumn(byColumn);
if (newPersistedData) {
viewConfig = { ...newPersistedData };
saveConfig(newPersistedData);
}
// Use the order from persistedData.columns, then add any new columns found
const orderedColumns = viewConfig?.columns?.map(col => col.value) || [];
const allColumns = Array.from(byColumn.keys());
const newColumns = allColumns.filter(col => !orderedColumns.includes(col));
setColumns([...orderedColumns, ...newColumns]);
});
}
useEffect(refresh, [ parentNote, noteIds, viewConfig ]);
const handleColumnDrop = useCallback((fromIndex: number, toIndex: number) => {
const newColumns = api.reorderColumn(fromIndex, toIndex);
if (newColumns) {
setColumns(newColumns);
}
setDraggedColumn(null);
setDraggedCard(null);
setColumnDropPosition(null);
}, [api]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
// Check if any changes affect our board
const hasRelevantChanges =
// React to changes in status attribute for notes in this board
loadResults.getAttributeRows().some(attr => attr.name === statusAttribute && noteIds.includes(attr.noteId!)) ||
// React to changes in note title
loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) ||
// React to changes in branches for subchildren (e.g., moved, added, or removed notes)
loadResults.getBranchRows().some(branch => noteIds.includes(branch.noteId!)) ||
// React to changes in note icon or color.
loadResults.getAttributeRows().some(attr => [ "iconClass", "color" ].includes(attr.name ?? "") && noteIds.includes(attr.noteId ?? "")) ||
// React to attachment change
loadResults.getAttachmentRows().some(att => att.ownerId === parentNote.noteId && att.title === "board.json") ||
// React to changes in "groupBy"
loadResults.getAttributeRows().some(attr => attr.name === "board:groupBy" && attr.noteId === parentNote.noteId);
if (hasRelevantChanges) {
refresh();
}
});
const handleColumnDragOver = useCallback((e: DragEvent) => {
if (!draggedColumn) return;
e.preventDefault();
}, [draggedColumn]);
const handleColumnHover = useCallback((index: number, mouseX: number, columnRect: DOMRect) => {
if (!draggedColumn) return;
const columnMiddle = columnRect.left + columnRect.width / 2;
// Determine if we should insert before or after this column
const insertBefore = mouseX < columnMiddle;
// Calculate the target position
let targetIndex = insertBefore ? index : index + 1;
setColumnDropPosition(targetIndex);
}, [draggedColumn]);
const handleContainerDrop = useCallback((e: DragEvent) => {
e.preventDefault();
if (draggedColumn && columnDropPosition !== null) {
handleColumnDrop(draggedColumn.index, columnDropPosition);
}
setColumnHoverIndex(null);
}, [draggedColumn, columnDropPosition, handleColumnDrop]);
return (
<div
className="board-view"
onWheel={onWheelHorizontalScroll}
>
<BoardViewContext.Provider value={boardViewContext}>
<div
className="board-view-container"
onDragOver={handleColumnDragOver}
onDrop={handleContainerDrop}
>
{byColumn && columns?.map((column, index) => (
<>
{columnDropPosition === index && (
<div className="column-drop-placeholder show" />
)}
<Column
api={api}
column={column}
columnIndex={index}
columnItems={byColumn.get(column)}
isDraggingColumn={draggedColumn?.column === column}
onColumnHover={handleColumnHover}
isAnyColumnDragging={!!draggedColumn}
/>
</>
))}
{columnDropPosition === columns?.length && draggedColumn && (
<div className="column-drop-placeholder show" />
)}
<AddNewColumn api={api} />
</div>
</BoardViewContext.Provider>
</div>
)
}
function AddNewColumn({ api }: { api: BoardApi }) {
const [ isCreatingNewColumn, setIsCreatingNewColumn ] = useState(false);
const addColumnCallback = useCallback(() => {
setIsCreatingNewColumn(true);
}, []);
return (
<div className={`board-add-column ${isCreatingNewColumn ? "editing" : ""}`} onClick={addColumnCallback}>
{!isCreatingNewColumn
? <>
<Icon icon="bx bx-plus" />{" "}
{t("board_view.add-column")}
</>
: (
<TitleEditor
placeholder={t("board_view.add-column-placeholder")}
save={(columnName) => api.addNewColumn(columnName)}
dismiss={() => setIsCreatingNewColumn(false)}
isNewItem
/>
)}
</div>
)
}
export function TitleEditor({ currentValue, placeholder, save, dismiss, multiline, isNewItem }: {
currentValue?: string;
placeholder?: string;
save: (newValue: string) => void;
dismiss: () => void;
multiline?: boolean;
isNewItem?: boolean;
}) {
const inputRef = useRef<any>(null);
const dismissOnNextRefreshRef = useRef(false);
const shouldDismiss = useRef(false);
useEffect(() => {
inputRef.current?.focus();
inputRef.current?.select();
}, [ inputRef ]);
const Element = multiline ? FormTextArea : FormTextBox;
useEffect(() => {
if (dismissOnNextRefreshRef.current) {
dismiss();
dismissOnNextRefreshRef.current = false;
}
});
return (
<Element
inputRef={inputRef}
currentValue={currentValue ?? ""}
placeholder={placeholder}
autoComplete="trilium-title-entry" // forces the auto-fill off better than the "off" value.
rows={multiline ? 4 : undefined}
onKeyDown={(e: TargetedKeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => {
if (e.key === "Enter" || e.key === "Escape") {
e.preventDefault();
shouldDismiss.current = (e.key === "Escape");
e.currentTarget.blur();
}
}}
onBlur={(newValue) => {
if (!shouldDismiss.current && newValue.trim() && (newValue !== currentValue || isNewItem)) {
save(newValue);
dismissOnNextRefreshRef.current = true;
} else {
dismiss();
}
}}
/>
);
}

View File

@@ -1,65 +0,0 @@
import { CreateChildrenResponse } from "@triliumnext/commons";
import server from "../../../services/server";
import FNote from "../../../entities/fnote";
import { setAttribute, setLabel } from "../../../services/attributes";
import froca from "../../../services/froca";
interface NewEventOpts {
title: string;
startDate: string;
endDate?: string | null;
startTime?: string | null;
endTime?: string | null;
}
interface ChangeEventOpts {
startDate: string;
endDate?: string | null;
startTime?: string | null;
endTime?: string | null;
}
export async function newEvent(parentNote: FNote, { title, startDate, endDate, startTime, endTime }: NewEventOpts) {
// Create the note.
const { note } = await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
title,
content: "",
type: "text"
});
// Set the attributes.
setLabel(note.noteId, "startDate", startDate);
if (endDate) {
setLabel(note.noteId, "endDate", endDate);
}
if (startTime) {
setLabel(note.noteId, "startTime", startTime);
}
if (endTime) {
setLabel(note.noteId, "endTime", endTime);
}
}
export async function changeEvent(note: FNote, { startDate, endDate, startTime, endTime }: ChangeEventOpts) {
// Don't store the end date if it's empty.
if (endDate === startDate) {
endDate = undefined;
}
// Since they can be customized via calendar:startDate=$foo and calendar:endDate=$bar we need to determine the
// attributes to be effectively updated
let startAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:startDate").shift()?.value||"startDate";
let endAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:endDate").shift()?.value||"endDate";
const noteId = note.noteId;
setLabel(noteId, startAttribute, startDate);
setAttribute(note, "label", endAttribute, endDate);
startAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:startTime").shift()?.value||"startTime";
endAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:endTime").shift()?.value||"endTime";
if (startTime && endTime) {
setAttribute(note, "label", startAttribute, startTime);
setAttribute(note, "label", endAttribute, endTime);
}
}

View File

@@ -1,32 +0,0 @@
import { useEffect, useLayoutEffect, useRef } from "preact/hooks";
import { CalendarOptions, Calendar as FullCalendar, PluginDef } from "@fullcalendar/core";
import { RefObject } from "preact";
interface CalendarProps extends CalendarOptions {
calendarRef?: RefObject<FullCalendar>;
}
export default function Calendar({ calendarRef, ...options }: CalendarProps) {
const containerRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
if (!containerRef.current) return;
const calendar = new FullCalendar(containerRef.current, options);
calendar.render();
if (calendarRef) {
calendarRef.current = calendar;
}
return () => calendar.destroy();
}, [ ]);
useEffect(() => {
calendarRef?.current?.resetOptions(options);
}, [ options ]);
return (
<div ref={containerRef} className="calendar-container" />
);
}

View File

@@ -1,160 +0,0 @@
import { EventInput, EventSourceFuncArg, EventSourceInput } from "@fullcalendar/core/index.js";
import froca from "../../../services/froca";
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
import FNote from "../../../entities/fnote";
import server from "../../../services/server";
interface Event {
startDate: string,
endDate?: string | null,
startTime?: string | null,
endTime?: string | null,
isArchived?: boolean;
}
export async function buildEvents(noteIds: string[]) {
const notes = await froca.getNotes(noteIds);
const events: EventSourceInput = [];
for (const note of notes) {
const startDate = getCustomisableLabel(note, "startDate", "calendar:startDate");
if (!startDate) {
continue;
}
const endDate = getCustomisableLabel(note, "endDate", "calendar:endDate");
const startTime = getCustomisableLabel(note, "startTime", "calendar:startTime");
const endTime = getCustomisableLabel(note, "endTime", "calendar:endTime");
const isArchived = note.hasLabel("archived");
events.push(await buildEvent(note, { startDate, endDate, startTime, endTime, isArchived }));
}
return events.flat();
}
export async function buildEventsForCalendar(note: FNote, e: EventSourceFuncArg) {
const events: EventInput[] = [];
// Gather all the required date note IDs.
const dateRange = getMonthsInDateRange(e.startStr, e.endStr);
let allDateNoteIds: string[] = [];
for (const month of dateRange) {
// TODO: Deduplicate get type.
const dateNotesForMonth = await server.get<Record<string, string>>(`special-notes/notes-for-month/${month}?calendarRoot=${note.noteId}`);
const dateNoteIds = Object.values(dateNotesForMonth);
allDateNoteIds = [...allDateNoteIds, ...dateNoteIds];
}
// Request all the date notes.
const dateNotes = await froca.getNotes(allDateNoteIds);
const childNoteToDateMapping: Record<string, string> = {};
for (const dateNote of dateNotes) {
const startDate = dateNote.getLabelValue("dateNote");
if (!startDate) {
continue;
}
events.push(await buildEvent(dateNote, { startDate }));
if (dateNote.hasChildren()) {
const childNoteIds = await dateNote.getSubtreeNoteIds();
for (const childNoteId of childNoteIds) {
childNoteToDateMapping[childNoteId] = startDate;
}
}
}
// Request all child notes of date notes in a single run.
const childNoteIds = Object.keys(childNoteToDateMapping);
const childNotes = await froca.getNotes(childNoteIds);
for (const childNote of childNotes) {
const startDate = childNoteToDateMapping[childNote.noteId];
const event = await buildEvent(childNote, { startDate });
events.push(event);
}
return events.flat();
}
export async function buildEvent(note: FNote, { startDate, endDate, startTime, endTime, isArchived }: Event) {
const customTitleAttributeName = note.getLabelValue("calendar:title");
const titles = await parseCustomTitle(customTitleAttributeName, note);
const color = note.getLabelValue("calendar:color") ?? note.getLabelValue("color");
const events: EventInput[] = [];
const calendarDisplayedAttributes = note.getLabelValue("calendar:displayedAttributes")?.split(",");
let displayedAttributesData: Array<[string, string]> | null = null;
if (calendarDisplayedAttributes) {
displayedAttributesData = await buildDisplayedAttributes(note, calendarDisplayedAttributes);
}
for (const title of titles) {
if (startTime && endTime && !endDate) {
endDate = startDate;
}
startDate = (startTime ? `${startDate}T${startTime}:00` : startDate);
if (!startTime) {
const endDateOffset = offsetDate(endDate ?? startDate, 1);
if (endDateOffset) {
endDate = formatDateToLocalISO(endDateOffset);
}
}
endDate = (endTime ? `${endDate}T${endTime}:00` : endDate);
const eventData: EventInput = {
title: title,
start: startDate,
url: `#${note.noteId}?popup`,
noteId: note.noteId,
color: color ?? undefined,
iconClass: note.getLabelValue("iconClass"),
promotedAttributes: displayedAttributesData,
className: isArchived ? "archived" : ""
};
if (endDate) {
eventData.end = endDate;
}
events.push(eventData);
}
return events;
}
async function parseCustomTitle(customTitlettributeName: string | null, note: FNote, allowRelations = true): Promise<string[]> {
if (customTitlettributeName) {
const labelValue = note.getAttributeValue("label", customTitlettributeName);
if (labelValue) return [labelValue];
if (allowRelations) {
const relations = note.getRelations(customTitlettributeName);
if (relations.length > 0) {
const noteIds = relations.map((r) => r.targetNoteId);
const notesFromRelation = await froca.getNotes(noteIds);
const titles: string[][] = [];
for (const targetNote of notesFromRelation) {
const targetCustomTitleValue = targetNote.getAttributeValue("label", "calendar:title");
const targetTitles = await parseCustomTitle(targetCustomTitleValue, targetNote, false);
titles.push(targetTitles.flat());
}
return titles.flat();
}
}
}
return [note.title];
}
async function buildDisplayedAttributes(note: FNote, calendarDisplayedAttributes: string[]) {
const filteredDisplayedAttributes = note.getAttributes().filter((attr): boolean => calendarDisplayedAttributes.includes(attr.name))
const result: Array<[string, string]> = [];
for (const attribute of filteredDisplayedAttributes) {
if (attribute.type === "label") result.push([attribute.name, attribute.value]);
else result.push([attribute.name, (await attribute.getTargetNote())?.title || ""])
}
return result;
}

View File

@@ -1,76 +0,0 @@
.calendar-view {
overflow: hidden;
position: relative;
height: 100%;
user-select: none;
padding: 10px;
}
.calendar-view a {
color: unset;
}
.search-result-widget-content .calendar-view {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.calendar-container {
height: 100%;
--fc-page-bg-color: var(--main-background-color);
--fc-border-color: var(--main-border-color);
--fc-neutral-bg-color: var(--launcher-pane-background-color);
--fc-list-event-hover-bg-color: var(--left-pane-item-hover-background);
}
.calendar-container .fc-list-sticky .fc-list-day > * {
z-index: 50;
}
.calendar-container a.fc-event {
text-decoration: none;
}
.calendar-container a.fc-event.archived {
opacity: 0.5;
}
.calendar-container .fc-button {
padding: 0.2em 0.5em;
}
.calendar-container .promoted-attribute {
font-size: 0.85em;
opacity: 0.85;
overflow: hidden;
}
/* #region Header */
.calendar-view .calendar-header {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.calendar-view .calendar-header .btn {
min-width: unset !important;
}
.calendar-view .calendar-header > .title {
flex-grow: 1;
font-size: 1.3rem;
font-weight: normal;
}
body.desktop:not(.zen) .calendar-view .calendar-header {
padding-right: 5em;
}
.search-result-widget-content .calendar-view .calendar-header {
padding-right: unset !important;
}
/* #endregion */

View File

@@ -1,354 +0,0 @@
import { DateSelectArg, EventChangeArg, EventMountArg, EventSourceFuncArg, LocaleInput, PluginDef } from "@fullcalendar/core/index.js";
import { ViewModeProps } from "../interface";
import Calendar from "./calendar";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import "./index.css";
import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTouchBar, useTriliumEvent, useTriliumOption, useTriliumOptionInt } from "../../react/hooks";
import { LOCALE_IDS } from "@triliumnext/commons";
import { Calendar as FullCalendar } from "@fullcalendar/core";
import { parseStartEndDateFromEvent, parseStartEndTimeFromEvent } from "./utils";
import dialog from "../../../services/dialog";
import { t } from "../../../services/i18n";
import { buildEvents, buildEventsForCalendar } from "./event_builder";
import { changeEvent, newEvent } from "./api";
import froca from "../../../services/froca";
import date_notes from "../../../services/date_notes";
import appContext from "../../../components/app_context";
import { DateClickArg } from "@fullcalendar/interaction";
import FNote from "../../../entities/fnote";
import Button, { ButtonGroup } from "../../react/Button";
import ActionButton from "../../react/ActionButton";
import { RefObject } from "preact";
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar";
interface CalendarViewData {
}
interface CalendarViewData {
type: string;
name: string;
previousText: string;
nextText: string;
}
const CALENDAR_VIEWS = [
{
type: "timeGridWeek",
name: t("calendar.week"),
previousText: t("calendar.week_previous"),
nextText: t("calendar.week_next")
},
{
type: "dayGridMonth",
name: t("calendar.month"),
previousText: t("calendar.month_previous"),
nextText: t("calendar.month_next")
},
{
type: "multiMonthYear",
name: t("calendar.year"),
previousText: t("calendar.year_previous"),
nextText: t("calendar.year_next")
},
{
type: "listMonth",
name: t("calendar.list"),
previousText: t("calendar.month_previous"),
nextText: t("calendar.month_next")
}
]
const SUPPORTED_CALENDAR_VIEW_TYPE = CALENDAR_VIEWS.map(v => v.type);
// Here we hard-code the imports in order to ensure that they are embedded by webpack without having to load all the languages.
export const LOCALE_MAPPINGS: Record<LOCALE_IDS, (() => Promise<{ default: LocaleInput }>) | null> = {
de: () => import("@fullcalendar/core/locales/de"),
es: () => import("@fullcalendar/core/locales/es"),
fr: () => import("@fullcalendar/core/locales/fr"),
cn: () => import("@fullcalendar/core/locales/zh-cn"),
tw: () => import("@fullcalendar/core/locales/zh-tw"),
ro: () => import("@fullcalendar/core/locales/ro"),
ru: () => import("@fullcalendar/core/locales/ru"),
ja: () => import("@fullcalendar/core/locales/ja"),
"pt_br": () => import("@fullcalendar/core/locales/pt-br"),
uk: () => import("@fullcalendar/core/locales/uk"),
en: null
};
export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarViewData>) {
const containerRef = useRef<HTMLDivElement>(null);
const calendarRef = useRef<FullCalendar>(null);
const [ calendarRoot ] = useNoteLabelBoolean(note, "calendarRoot");
const [ workspaceCalendarRoot ] = useNoteLabelBoolean(note, "workspaceCalendarRoot");
const [ firstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek");
const [ hideWeekends ] = useNoteLabelBoolean(note, "calendar:hideWeekends");
const [ weekNumbers ] = useNoteLabelBoolean(note, "calendar:weekNumbers");
const [ calendarView, setCalendarView ] = useNoteLabel(note, "calendar:view");
const initialView = useRef(calendarView);
const viewSpacedUpdate = useSpacedUpdate(() => setCalendarView(initialView.current));
useResizeObserver(containerRef, () => calendarRef.current?.updateSize());
const isCalendarRoot = (calendarRoot || workspaceCalendarRoot);
const isEditable = !isCalendarRoot;
const eventBuilder = useMemo(() => {
if (!isCalendarRoot) {
return async () => await buildEvents(noteIds);
} else {
return async (e: EventSourceFuncArg) => await buildEventsForCalendar(note, e);
}
}, [isCalendarRoot, noteIds]);
const plugins = usePlugins(isEditable, isCalendarRoot);
const locale = useLocale();
const { eventDidMount } = useEventDisplayCustomization();
const editingProps = useEditing(note, isEditable, isCalendarRoot);
// React to changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) // note title change.
|| loadResults.getAttributeRows().some((a) => noteIds.includes(a.noteId ?? ""))) // subnote change.
{
calendarRef.current?.refetchEvents();
}
});
return (plugins &&
<div className="calendar-view" ref={containerRef} tabIndex={100}>
<CalendarHeader calendarRef={calendarRef} />
<Calendar
events={eventBuilder}
calendarRef={calendarRef}
plugins={plugins}
initialView={initialView.current && SUPPORTED_CALENDAR_VIEW_TYPE.includes(initialView.current) ? initialView.current : "dayGridMonth"}
headerToolbar={false}
firstDay={firstDayOfWeek ?? 0}
weekends={!hideWeekends}
weekNumbers={weekNumbers}
height="90%"
nowIndicator
handleWindowResize={false}
locale={locale}
{...editingProps}
eventDidMount={eventDidMount}
viewDidMount={({ view }) => {
if (initialView.current !== view.type) {
initialView.current = view.type;
viewSpacedUpdate.scheduleUpdate();
}
}}
/>
<CalendarTouchBar calendarRef={calendarRef} />
</div>
);
}
function CalendarHeader({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
const { title, viewType: currentViewType } = useOnDatesSet(calendarRef);
const currentViewData = CALENDAR_VIEWS.find(v => calendarRef.current && v.type === currentViewType);
return (
<div className="calendar-header">
<span className="title">{title}</span>
<ButtonGroup>
{CALENDAR_VIEWS.map(viewData => (
<Button
text={viewData.name.toLocaleLowerCase()}
className={currentViewType === viewData.type ? "active" : ""}
onClick={() => calendarRef.current?.changeView(viewData.type)}
/>
))}
</ButtonGroup>
<Button text={t("calendar.today").toLocaleLowerCase()} onClick={() => calendarRef.current?.today()} />
<ButtonGroup>
<ActionButton icon="bx bx-chevron-left" text={currentViewData?.previousText ?? ""} frame onClick={() => calendarRef.current?.prev()} />
<ActionButton icon="bx bx-chevron-right" text={currentViewData?.nextText ?? ""} frame onClick={() => calendarRef.current?.next()} />
</ButtonGroup>
</div>
)
}
function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
const [ plugins, setPlugins ] = useState<PluginDef[]>();
useEffect(() => {
async function loadPlugins() {
const plugins: PluginDef[] = [];
plugins.push((await import("@fullcalendar/daygrid")).default);
plugins.push((await import("@fullcalendar/timegrid")).default);
plugins.push((await import("@fullcalendar/list")).default);
plugins.push((await import("@fullcalendar/multimonth")).default);
if (isEditable || isCalendarRoot) {
plugins.push((await import("@fullcalendar/interaction")).default);
}
setPlugins(plugins);
}
loadPlugins();
}, [ isEditable, isCalendarRoot ]);
return plugins;
}
function useLocale() {
const [ locale ] = useTriliumOption("locale");
const [ calendarLocale, setCalendarLocale ] = useState<LocaleInput>();
useEffect(() => {
const correspondingLocale = LOCALE_MAPPINGS[locale];
if (correspondingLocale) {
correspondingLocale().then((locale) => setCalendarLocale(locale.default));
} else {
setCalendarLocale(undefined);
}
});
return calendarLocale;
}
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
const onCalendarSelection = useCallback(async (e: DateSelectArg) => {
const { startDate, endDate } = parseStartEndDateFromEvent(e);
if (!startDate) return;
const { startTime, endTime } = parseStartEndTimeFromEvent(e);
// Ask for the title
const title = await dialog.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
if (!title?.trim()) {
return;
}
newEvent(note, { title, startDate, endDate, startTime, endTime });
}, [ note ]);
const onEventChange = useCallback(async (e: EventChangeArg) => {
const { startDate, endDate } = parseStartEndDateFromEvent(e.event);
if (!startDate) return;
const { startTime, endTime } = parseStartEndTimeFromEvent(e.event);
const note = await froca.getNote(e.event.extendedProps.noteId);
if (!note) return;
changeEvent(note, { startDate, endDate, startTime, endTime });
}, []);
// Called upon when clicking the day number in the calendar, opens or creates the day note but only if in a calendar root.
const onDateClick = useCallback(async (e: DateClickArg) => {
const eventNote = await date_notes.getDayNote(e.dateStr);
if (eventNote) {
appContext.triggerCommand("openInPopup", { noteIdOrPath: eventNote.noteId });
}
}, []);
return {
select: onCalendarSelection,
eventChange: onEventChange,
dateClick: isCalendarRoot ? onDateClick : undefined,
editable: isEditable,
selectable: isEditable
};
}
function useEventDisplayCustomization() {
const eventDidMount = useCallback((e: EventMountArg) => {
const { iconClass, promotedAttributes } = e.event.extendedProps;
// Prepend the icon to the title, if any.
if (iconClass) {
let titleContainer;
switch (e.view.type) {
case "timeGridWeek":
case "dayGridMonth":
titleContainer = e.el.querySelector(".fc-event-title");
break;
case "multiMonthYear":
break;
case "listMonth":
titleContainer = e.el.querySelector(".fc-list-event-title a");
break;
}
if (titleContainer) {
const icon = /*html*/`<span class="${iconClass}"></span> `;
titleContainer.insertAdjacentHTML("afterbegin", icon);
}
}
// Append promoted attributes to the end of the event container.
if (promotedAttributes) {
let promotedAttributesHtml = "";
for (const [name, value] of promotedAttributes) {
promotedAttributesHtml = promotedAttributesHtml + /*html*/`\
<div class="promoted-attribute">
<span class="promoted-attribute-name">${name}</span>: <span class="promoted-attribute-value">${value}</span>
</div>`;
}
let mainContainer;
switch (e.view.type) {
case "timeGridWeek":
case "dayGridMonth":
mainContainer = e.el.querySelector(".fc-event-main");
break;
case "multiMonthYear":
break;
case "listMonth":
mainContainer = e.el.querySelector(".fc-list-event-title");
break;
}
$(mainContainer ?? e.el).append($(promotedAttributesHtml));
}
}, []);
return { eventDidMount };
}
function CalendarTouchBar({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
const { title, viewType } = useOnDatesSet(calendarRef);
return (
<TouchBar>
<TouchBarSegmentedControl
mode="single"
segments={CALENDAR_VIEWS.map(({ name }) => ({
label: name,
}))}
selectedIndex={CALENDAR_VIEWS.findIndex(v => v.type === viewType) ?? 0}
onChange={(selectedIndex) => calendarRef.current?.changeView(CALENDAR_VIEWS[selectedIndex].type)}
/>
<TouchBarSpacer size="flexible" />
<TouchBarLabel label={title ?? ""} />
<TouchBarSpacer size="flexible" />
<TouchBarButton
label={t("calendar.today")}
click={() => calendarRef.current?.today()}
/>
<TouchBarButton
icon="NSImageNameTouchBarGoBackTemplate"
click={() => calendarRef.current?.prev()}
/>
<TouchBarButton
icon="NSImageNameTouchBarGoForwardTemplate"
click={() => calendarRef.current?.next()}
/>
</TouchBar>
);
}
function useOnDatesSet(calendarRef: RefObject<FullCalendar>) {
const [ title, setTitle ] = useState<string>();
const [ viewType ,setViewType ] = useState<string>();
useEffect(() => {
const api = calendarRef.current;
if (!api) return;
const handler = () => {
setTitle(api.view.title);
setViewType(api.view.type);
};
handler();
api.on("datesSet", handler);
return () => api.off("datesSet", handler);
}, [calendarRef]);
return { title, viewType };
}

View File

@@ -1,103 +0,0 @@
import { DateSelectArg } from "@fullcalendar/core/index.js";
import { EventImpl } from "@fullcalendar/core/internal";
import FNote from "../../../entities/fnote";
export function parseStartEndDateFromEvent(e: DateSelectArg | EventImpl) {
const startDate = formatDateToLocalISO(e.start);
if (!startDate) {
return { startDate: null, endDate: null };
}
let endDate;
if (e.allDay) {
endDate = formatDateToLocalISO(offsetDate(e.end, -1));
} else {
endDate = formatDateToLocalISO(e.end);
}
return { startDate, endDate };
}
export function parseStartEndTimeFromEvent(e: DateSelectArg | EventImpl) {
let startTime: string | undefined | null = null;
let endTime: string | undefined | null = null;
if (!e.allDay) {
startTime = formatTimeToLocalISO(e.start);
endTime = formatTimeToLocalISO(e.end);
}
return { startTime, endTime };
}
export function formatDateToLocalISO(date: Date | null | undefined) {
if (!date) {
return undefined;
}
const offset = date.getTimezoneOffset();
const localDate = new Date(date.getTime() - offset * 60 * 1000);
return localDate.toISOString().split("T")[0];
}
export function offsetDate(date: Date | string | null | undefined, offset: number) {
if (!date) {
return undefined;
}
const newDate = new Date(date);
newDate.setDate(newDate.getDate() + offset);
return newDate;
}
export function formatTimeToLocalISO(date: Date | null | undefined) {
if (!date) {
return undefined;
}
const offset = date.getTimezoneOffset();
const localDate = new Date(date.getTime() - offset * 60 * 1000);
return localDate.toISOString()
.split("T")[1]
.substring(0, 5);
}
/**
* Allows the user to customize the attribute from which to obtain a particular value. For example, if `customLabelNameAttribute` is `calendar:startDate`
* and `defaultLabelName` is `startDate` and the note at hand has `#calendar:startDate=myStartDate #myStartDate=2025-02-26` then the value returned will
* be `2025-02-26`. If there is no custom attribute value, then the value of the default attribute is returned instead (e.g. `#startDate`).
*
* @param note the note from which to read the values.
* @param defaultLabelName the name of the label in case a custom value is not found.
* @param customLabelNameAttribute the name of the label to look for a custom value.
* @returns the value of either the custom label or the default label.
*/
export function getCustomisableLabel(note: FNote, defaultLabelName: string, customLabelNameAttribute: string) {
const customAttributeName = note.getLabelValue(customLabelNameAttribute);
if (customAttributeName) {
const customValue = note.getLabelValue(customAttributeName);
if (customValue) {
return customValue;
}
}
return note.getLabelValue(defaultLabelName);
}
// Source: https://stackoverflow.com/a/30465299/4898894
export function getMonthsInDateRange(startDate: string, endDate: string) {
const start = startDate.split("-");
const end = endDate.split("-");
const startYear = parseInt(start[0]);
const endYear = parseInt(end[0]);
const dates: string[] = [];
for (let i = startYear; i <= endYear; i++) {
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
const startMon = i === startYear ? parseInt(start[1]) - 1 : 0;
for (let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j + 1) {
const month = j + 1;
const displayMonth = month < 10 ? "0" + month : month;
dates.push([i, displayMonth].join("-"));
}
}
return dates;
}

View File

@@ -1,28 +0,0 @@
import type { LatLng, LeafletMouseEvent } from "leaflet";
import { LOCATION_ATTRIBUTE } from ".";
import attributes from "../../../services/attributes";
import { prompt } from "../../../services/dialog";
import server from "../../../services/server";
import { t } from "../../../services/i18n";
import { CreateChildrenResponse } from "@triliumnext/commons";
const CHILD_NOTE_ICON = "bx bx-pin";
export async function moveMarker(noteId: string, latLng: LatLng | null) {
const value = latLng ? [latLng.lat, latLng.lng].join(",") : "";
await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value);
}
export async function createNewNote(noteId: string, e: LeafletMouseEvent) {
const title = await prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
if (title?.trim()) {
const { note } = await server.post<CreateChildrenResponse>(`notes/${noteId}/children?target=into`, {
title,
content: "",
type: "text"
});
attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON);
moveMarker(note.noteId, e.latlng);
}
}

View File

@@ -1,78 +0,0 @@
.geo-view {
overflow: hidden;
position: relative;
height: 100%;
}
.geo-map-container {
height: 100%;
overflow: hidden;
}
.leaflet-pane {
z-index: 1;
}
.leaflet-top,
.leaflet-bottom {
z-index: 997;
}
.geo-view.placing-note .geo-map-container {
cursor: crosshair;
}
.geo-map-container .marker-pin {
position: relative;
}
.geo-map-container .leaflet-div-icon {
position: relative;
background: transparent;
border: 0;
overflow: visible;
}
.geo-map-container .leaflet-div-icon .icon-shadow {
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
.geo-map-container .leaflet-div-icon .bx {
position: absolute;
top: 3px;
left: 2px;
background-color: white;
color: black;
padding: 2px;
border-radius: 50%;
font-size: 17px;
}
.geo-map-container .leaflet-div-icon .title-label {
display: block;
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
font-size: 0.75rem;
height: 1rem;
color: black;
width: 100px;
text-align: center;
text-overflow: ellipsis;
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
white-space: no-wrap;
overflow: hidden;
}
.geo-map-container .leaflet-div-icon .archived {
opacity: 0.5;
}
.geo-map-container.dark .leaflet-div-icon .title-label {
color: white;
text-shadow: -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black, 1px 1px 0 black;
}

View File

@@ -1,296 +0,0 @@
import Map from "./map";
import "./index.css";
import { ViewModeProps } from "../interface";
import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTouchBar, useTriliumEvent } from "../../react/hooks";
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
import { divIcon, GPXOptions, LatLng, LeafletMouseEvent } from "leaflet";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import Marker, { GpxTrack } from "./marker";
import froca from "../../../services/froca";
import FNote from "../../../entities/fnote";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
import appContext from "../../../components/app_context";
import { createNewNote, moveMarker } from "./api";
import openContextMenu, { openMapContextMenu } from "./context_menu";
import toast from "../../../services/toast";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import branches from "../../../services/branches";
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSlider } from "../../react/TouchBar";
import { ParentComponent } from "../../react/react_utils";
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
const DEFAULT_ZOOM = 2;
export const LOCATION_ATTRIBUTE = "geolocation";
interface MapData {
view?: {
center?: LatLng | [number, number];
zoom?: number;
};
}
enum State {
Normal,
NewNote
}
export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewModeProps<MapData>) {
const [ state, setState ] = useState(State.Normal);
const [ coordinates, setCoordinates ] = useState(viewConfig?.view?.center);
const [ zoom, setZoom ] = useState(viewConfig?.view?.zoom);
const [ layerName ] = useNoteLabel(note, "map:style");
const [ hasScale ] = useNoteLabelBoolean(note, "map:scale");
const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const [ notes, setNotes ] = useState<FNote[]>([]);
const spacedUpdate = useSpacedUpdate(() => {
if (viewConfig) {
saveConfig(viewConfig);
}
}, 5000);
useEffect(() => { froca.getNotes(noteIds).then(setNotes) }, [ noteIds ]);
useEffect(() => {
if (!note) return;
setCoordinates(viewConfig?.view?.center ?? DEFAULT_COORDINATES);
setZoom(viewConfig?.view?.zoom ?? DEFAULT_ZOOM);
}, [ note, viewConfig ]);
// Note creation.
useTriliumEvent("geoMapCreateChildNote", () => {
toast.showPersistent({
icon: "plus",
id: "geo-new-note",
title: "New note",
message: t("geo-map.create-child-note-instruction")
});
setState(State.NewNote);
const globalKeyListener: (this: Window, ev: KeyboardEvent) => any = (e) => {
if (e.key === "Escape") {
setState(State.Normal);
window.removeEventListener("keydown", globalKeyListener);
toast.closePersistent("geo-new-note");
}
};
window.addEventListener("keydown", globalKeyListener);
});
useTriliumEvent("deleteFromMap", ({ noteId }) => {
moveMarker(noteId, null);
});
const onClick = useCallback(async (e: LeafletMouseEvent) => {
if (state === State.NewNote) {
toast.closePersistent("geo-new-note");
await createNewNote(note.noteId, e);
setState(State.Normal);
}
}, [ state ]);
const onContextMenu = useCallback((e: LeafletMouseEvent) => {
openMapContextMenu(note.noteId, e, !isReadOnly);
}, [ note.noteId, isReadOnly ]);
// Dragging
const containerRef = useRef<HTMLDivElement>(null);
const apiRef = useRef<L.Map>(null);
useNoteTreeDrag(containerRef, {
dragEnabled: !isReadOnly,
dragNotEnabledMessage: {
icon: "bx bx-lock-alt",
title: t("book.drag_locked_title"),
message: t("book.drag_locked_message")
},
async callback(treeData, e) {
const api = apiRef.current;
if (!note || !api || isReadOnly) return;
const { noteId } = treeData[0];
const offset = containerRef.current?.getBoundingClientRect();
const x = e.clientX - (offset?.left ?? 0);
const y = e.clientY - (offset?.top ?? 0);
const latlng = api.containerPointToLatLng([ x, y ]);
const targetNote = await froca.getNote(noteId, true);
const parents = targetNote?.getParentNoteIds();
if (parents?.includes(note.noteId)) {
await moveMarker(noteId, latlng);
} else {
await branches.cloneNoteToParentNote(noteId, noteId);
await moveMarker(noteId, latlng);
}
}
});
return (
<div className={`geo-view ${state === State.NewNote ? "placing-note" : ""}`}>
{ coordinates && zoom && <Map
apiRef={apiRef} containerRef={containerRef}
coordinates={coordinates}
zoom={zoom}
layerName={layerName ?? DEFAULT_MAP_LAYER_NAME}
viewportChanged={(coordinates, zoom) => {
if (!viewConfig) viewConfig = {};
viewConfig.view = { center: coordinates, zoom };
spacedUpdate.scheduleUpdate();
}}
onClick={onClick}
onContextMenu={onContextMenu}
scale={hasScale}
>
{notes.map(note => <NoteWrapper note={note} isReadOnly={isReadOnly} />)}
</Map>}
<GeoMapTouchBar state={state} map={apiRef.current} />
</div>
);
}
function NoteWrapper({ note, isReadOnly }: { note: FNote, isReadOnly: boolean }) {
const mime = useNoteProperty(note, "mime");
const [ location ] = useNoteLabel(note, LOCATION_ATTRIBUTE);
if (mime === "application/gpx+xml") {
return <NoteGpxTrack note={note} />;
}
if (location) {
const latLng = location?.split(",", 2).map((el) => parseFloat(el)) as [ number, number ] | undefined;
if (!latLng) return;
return <NoteMarker note={note} editable={!isReadOnly} latLng={latLng} />;
}
}
function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean, latLng: [number, number] }) {
// React to changes
const [ color ] = useNoteLabel(note, "color");
const [ iconClass ] = useNoteLabel(note, "iconClass");
const [ archived ] = useNoteLabelBoolean(note, "archived");
const title = useNoteProperty(note, "title");
const icon = useMemo(() => {
return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, title, note.noteId, archived);
}, [ iconClass, color, title, note.noteId, archived]);
const onClick = useCallback(() => {
appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId });
}, [ note.noteId ]);
// Middle click to open in new tab
const onMouseDown = useCallback((e: MouseEvent) => {
if (e.button === 1) {
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
appContext.tabManager.openInNewTab(note.noteId, hoistedNoteId);
return true;
}
}, [ note.noteId ]);
const onDragged = useCallback((newCoordinates: LatLng) => {
moveMarker(note.noteId, newCoordinates);
}, [ note.noteId ]);
const onContextMenu = useCallback((e: LeafletMouseEvent) => openContextMenu(note.noteId, e, editable), [ note.noteId, editable ]);
return latLng && <Marker
coordinates={latLng}
icon={icon}
draggable={editable}
onMouseDown={onMouseDown}
onDragged={editable ? onDragged : undefined}
onClick={!editable ? onClick : undefined}
onContextMenu={onContextMenu}
/>
}
function NoteGpxTrack({ note }: { note: FNote }) {
const [ xmlString, setXmlString ] = useState<string>();
const blob = useNoteBlob(note);
useEffect(() => {
if (!blob) return;
server.get<string | Uint8Array>(`notes/${note.noteId}/open`, undefined, true).then(xmlResponse => {
if (xmlResponse instanceof Uint8Array) {
setXmlString(new TextDecoder().decode(xmlResponse));
} else {
setXmlString(xmlResponse);
}
});
}, [ blob ]);
// React to changes
const color = useNoteLabel(note, "color");
const iconClass = useNoteLabel(note, "iconClass");
const options = useMemo<GPXOptions>(() => ({
markers: {
startIcon: buildIcon(note.getIcon(), note.getColorClass(), note.title),
endIcon: buildIcon("bxs-flag-checkered"),
wptIcons: {
"": buildIcon("bx bx-pin")
}
},
polyline_options: {
color: note.getLabelValue("color") ?? "blue"
}
}), [ color, iconClass ]);
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />
}
function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) {
let html = /*html*/`\
<img class="icon" src="${markerIcon}" />
<img class="icon-shadow" src="${markerIconShadow}" />
<span class="bx ${bxIconClass} ${colorClass ?? ""}"></span>
<span class="title-label">${title ?? ""}</span>`;
if (noteIdLink) {
html = `<div data-href="#root/${noteIdLink}" class="${archived ? "archived" : ""}">${html}</div>`;
}
return divIcon({
html,
iconSize: [25, 41],
iconAnchor: [12, 41]
});
}
function GeoMapTouchBar({ state, map }: { state: State, map: L.Map | null | undefined }) {
const [ currentZoom, setCurrentZoom ] = useState<number>();
const parentComponent = useContext(ParentComponent);
useEffect(() => {
if (!map) return;
function onZoomChanged() {
setCurrentZoom(map?.getZoom());
}
map.on("zoom", onZoomChanged);
return () => map.off("zoom", onZoomChanged);
}, [ map ]);
return map && currentZoom && (
<TouchBar>
<TouchBarSlider
label="Zoom"
value={currentZoom}
minValue={map.getMinZoom()}
maxValue={map.getMaxZoom()}
onChange={(newValue) => {
setCurrentZoom(newValue);
map.setZoom(newValue);
}}
/>
<TouchBarButton
label="New geo note"
click={() => parentComponent?.triggerCommand("geoMapCreateChildNote")}
enabled={state === State.Normal}
/>
</TouchBar>
)
}

View File

@@ -1,143 +0,0 @@
import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks";
import L, { control, LatLng, Layer, LeafletMouseEvent } from "leaflet";
import "leaflet/dist/leaflet.css";
import { MAP_LAYERS } from "./map_layer";
import { ComponentChildren, createContext, RefObject } from "preact";
import { useElementSize, useSyncedRef } from "../../react/hooks";
export const ParentMap = createContext<L.Map | null>(null);
interface MapProps {
apiRef?: RefObject<L.Map | null>;
containerRef?: RefObject<HTMLDivElement>;
coordinates: LatLng | [number, number];
zoom: number;
layerName: string;
viewportChanged: (coordinates: LatLng, zoom: number) => void;
children: ComponentChildren;
onClick?: (e: LeafletMouseEvent) => void;
onContextMenu?: (e: LeafletMouseEvent) => void;
onZoom?: () => void;
scale: boolean;
}
export default function Map({ coordinates, zoom, layerName, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) {
const mapRef = useRef<L.Map>(null);
const containerRef = useSyncedRef<HTMLDivElement>(_containerRef);
useImperativeHandle(apiRef ?? null, () => mapRef.current);
useEffect(() => {
if (!containerRef.current) return;
const mapInstance = L.map(containerRef.current, {
worldCopyJump: true
});
mapRef.current = mapInstance;
return () => {
mapInstance.off();
mapInstance.remove();
};
}, []);
// Load the layer asynchronously.
const [ layer, setLayer ] = useState<Layer>();
useEffect(() => {
async function load() {
const layerData = MAP_LAYERS[layerName];
if (layerData.type === "vector") {
const style = (typeof layerData.style === "string" ? layerData.style : await layerData.style());
await import("@maplibre/maplibre-gl-leaflet");
setLayer(L.maplibreGL({
style: style as any
}));
} else {
setLayer(L.tileLayer(layerData.url, {
attribution: layerData.attribution,
detectRetina: true
}));
}
}
load();
}, [ layerName ]);
// Attach layer to the map.
useEffect(() => {
const map = mapRef.current;
const layerToAdd = layer;
if (!map || !layerToAdd) return;
layerToAdd.addTo(map);
return () => layerToAdd.removeFrom(map);
}, [ mapRef, layer ]);
// React to coordinate changes.
useEffect(() => {
if (!mapRef.current) return;
mapRef.current.setView(coordinates, zoom);
}, [ mapRef, coordinates, zoom ]);
// Viewport callback.
useEffect(() => {
const map = mapRef.current;
if (!map) return;
const updateFn = () => viewportChanged(map.getBounds().getCenter(), map.getZoom());
map.on("moveend", updateFn);
map.on("zoomend", updateFn);
return () => {
map.off("moveend", updateFn);
map.off("zoomend", updateFn);
};
}, [ mapRef, viewportChanged ]);
useEffect(() => {
if (onClick && mapRef.current) {
mapRef.current.on("click", onClick);
return () => mapRef.current?.off("click", onClick);
}
}, [ mapRef, onClick ]);
useEffect(() => {
if (onContextMenu && mapRef.current) {
mapRef.current.on("contextmenu", onContextMenu);
return () => mapRef.current?.off("contextmenu", onContextMenu);
}
}, [ mapRef, onContextMenu ]);
useEffect(() => {
if (onZoom && mapRef.current) {
mapRef.current.on("zoom", onZoom);
return () => mapRef.current?.off("zoom", onZoom);
}
}, [ mapRef, onZoom ]);
// Scale
useEffect(() => {
const map = mapRef.current;
if (!scale || !map) return;
const scaleControl = control.scale();
scaleControl.addTo(map);
return () => scaleControl.remove();
}, [ mapRef, scale ]);
// Adapt to container size changes.
const size = useElementSize(containerRef);
useEffect(() => {
mapRef.current?.invalidateSize();
}, [ size?.width, size?.height ]);
return (
<div
ref={containerRef}
className={`geo-map-container ${MAP_LAYERS[layerName].isDarkTheme ? "dark" : ""}`}
>
<ParentMap.Provider value={mapRef.current}>
{children}
</ParentMap.Provider>
</div>
);
}

View File

@@ -1,71 +0,0 @@
import { useContext, useEffect } from "preact/hooks";
import { ParentMap } from "./map";
import { DivIcon, GPX, GPXOptions, Icon, LatLng, Marker as LeafletMarker, LeafletMouseEvent, marker, MarkerOptions } from "leaflet";
import "leaflet-gpx";
export interface MarkerProps {
coordinates: [ number, number ];
icon?: Icon | DivIcon;
onClick?: () => void;
onMouseDown?: (e: MouseEvent) => void;
onDragged?: ((newCoordinates: LatLng) => void);
onContextMenu: (e: LeafletMouseEvent) => void;
draggable?: boolean;
}
export default function Marker({ coordinates, icon, draggable, onClick, onDragged, onMouseDown, onContextMenu }: MarkerProps) {
const parentMap = useContext(ParentMap);
useEffect(() => {
if (!parentMap) return;
const options: MarkerOptions = { icon };
if (draggable) {
options.draggable = true;
options.autoPan = true;
options.autoPanSpeed = 5;
}
const newMarker = marker(coordinates, options);
if (onClick) {
newMarker.on("click", () => onClick());
}
if (onMouseDown) {
newMarker.on("mousedown", e => onMouseDown(e.originalEvent));
}
if (onDragged) {
newMarker.on("moveend", e => {
const coordinates = (e.target as LeafletMarker).getLatLng();
onDragged(coordinates);
});
}
if (onContextMenu) {
newMarker.on("contextmenu", e => onContextMenu(e))
}
newMarker.addTo(parentMap);
return () => newMarker.removeFrom(parentMap);
}, [ parentMap, coordinates, onMouseDown, onDragged, icon ]);
return (<div />)
}
export function GpxTrack({ gpxXmlString, options }: { gpxXmlString: string, options: GPXOptions }) {
const parentMap = useContext(ParentMap);
useEffect(() => {
if (!parentMap) return;
const track = new GPX(gpxXmlString, options);
track.addTo(parentMap);
return () => track.removeFrom(parentMap);
}, [ parentMap, gpxXmlString, options ]);
return <div />;
}

View File

@@ -1,16 +0,0 @@
import FNote from "../../entities/fnote";
export const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board"] as const;
export type ViewTypeOptions = typeof allViewTypes[number];
export interface ViewModeProps<T extends object> {
note: FNote;
notePath: string;
/**
* We're using noteIds so that it's not necessary to load all notes at once when paging.
*/
noteIds: string[];
highlightedTokens: string[] | null | undefined;
viewConfig: T | undefined;
saveConfig(newConfig: T): void;
}

View File

@@ -1,134 +0,0 @@
.note-list {
overflow: hidden;
position: relative;
height: 100%;
}
.note-book-card {
border-radius: 10px;
background-color: var(--accented-background-color);
padding: 10px 15px 15px 8px;
margin: 5px 5px 5px 5px;
overflow: hidden;
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-grow: 1;
}
.note-book-card:not(.expanded) .note-book-content {
padding: 10px
}
.note-book-card.expanded .note-book-content {
display: block;
min-height: 0;
height: 100%;
padding-top: 10px;
}
.note-book-content .rendered-content {
height: 100%;
}
.note-book-header {
border-bottom: 1px solid var(--main-border-color);
margin-bottom: 0;
padding-bottom: .5rem;
word-break: break-all;
flex-shrink: 0;
}
/* not-expanded title is limited to one line only */
.note-book-card:not(.expanded) .note-book-header {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.note-book-header .rendered-note-attributes {
font-size: medium;
}
.note-book-header .rendered-note-attributes:before {
content: "\00a0\00a0";
}
.note-book-header .note-icon {
font-size: 100%;
display: inline-block;
padding-right: 7px;
position: relative;
}
.note-book-card .note-book-card {
border: 1px solid var(--main-border-color);
}
.note-book-content.type-image, .note-book-content.type-file, .note-book-content.type-protectedSession {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 10px;
}
.note-book-content.type-image img, .note-book-content.type-canvas svg {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.note-book-card.type-image .note-book-content img,
.note-book-card.type-text .note-book-content img,
.note-book-card.type-canvas .note-book-content img {
max-width: 100%;
max-height: 100%;
}
.note-book-header {
flex-grow: 0;
}
.note-list-wrapper {
height: 100%;
overflow: auto;
}
.note-expander {
font-size: x-large;
position: relative;
top: 3px;
cursor: pointer;
}
.note-list-pager {
text-align: center;
}
/* #region Grid view */
.note-list.grid-view .note-list-container {
display: flex;
flex-wrap: wrap;
}
.note-list.grid-view .note-book-card {
flex-basis: 300px;
border: 1px solid transparent;
}
.note-list.grid-view .note-book-card {
max-height: 300px;
}
.note-list.grid-view .note-book-card img {
max-height: 220px;
object-fit: contain;
}
.note-list.grid-view .note-book-card:hover {
cursor: pointer;
border: 1px solid var(--main-border-color);
background: var(--more-accented-background-color);
}
/* #endregion */

View File

@@ -1,183 +0,0 @@
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import FNote from "../../../entities/fnote";
import Icon from "../../react/Icon";
import { ViewModeProps } from "../interface";
import { useNoteLabelBoolean, useImperativeSearchHighlighlighting } from "../../react/hooks";
import NoteLink from "../../react/NoteLink";
import "./ListOrGridView.css";
import content_renderer from "../../../services/content_renderer";
import { Pager, usePagination } from "../Pagination";
import tree from "../../../services/tree";
import link from "../../../services/link";
import { t } from "../../../services/i18n";
import attribute_renderer from "../../../services/attribute_renderer";
export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
const [ isExpanded ] = useNoteLabelBoolean(note, "expanded");
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
const { pageNotes, ...pagination } = usePagination(note, noteIds);
return (
<div class="note-list list-view">
{ noteIds.length > 0 && <div class="note-list-wrapper">
<Pager {...pagination} />
<div class="note-list-container use-tn-links">
{pageNotes?.map(childNote => (
<ListNoteCard note={childNote} parentNote={note} expand={isExpanded} highlightedTokens={highlightedTokens} />
))}
</div>
<Pager {...pagination} />
</div>}
</div>
);
}
export function GridView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
const { pageNotes, ...pagination } = usePagination(note, noteIds);
return (
<div class="note-list grid-view">
<div class="note-list-wrapper">
<Pager {...pagination} />
<div class="note-list-container use-tn-links">
{pageNotes?.map(childNote => (
<GridNoteCard note={childNote} parentNote={note} highlightedTokens={highlightedTokens} />
))}
</div>
<Pager {...pagination} />
</div>
</div>
);
}
function ListNoteCard({ note, parentNote, expand, highlightedTokens }: { note: FNote, parentNote: FNote, expand?: boolean, highlightedTokens: string[] | null | undefined }) {
const [ isExpanded, setExpanded ] = useState(expand);
const notePath = getNotePath(parentNote, note);
// Reset expand state if switching to another note.
useEffect(() => setExpanded(expand), [ note ]);
return (
<div
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""}`}
data-note-id={note.noteId}
>
<h5 className="note-book-header">
<span
className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
onClick={() => setExpanded(!isExpanded)}
/>
<Icon className="note-icon" icon={note.getIcon()} />
<NoteLink className="note-book-title" notePath={notePath} noPreview showNotePath={note.type === "search"} highlightedTokens={highlightedTokens} />
<NoteAttributes note={note} />
</h5>
{isExpanded && <>
<NoteContent note={note} highlightedTokens={highlightedTokens} />
<NoteChildren note={note} parentNote={parentNote} highlightedTokens={highlightedTokens} />
</>}
</div>
)
}
function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined }) {
const titleRef = useRef<HTMLSpanElement>(null);
const [ noteTitle, setNoteTitle ] = useState<string>();
const notePath = getNotePath(parentNote, note);
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
useEffect(() => {
tree.getNoteTitle(note.noteId, parentNote.noteId).then(setNoteTitle);
}, [ note ]);
useEffect(() => highlightSearch(titleRef.current), [ noteTitle, highlightedTokens ]);
return (
<div
className={`note-book-card no-tooltip-preview block-link`}
data-href={`#${notePath}`}
data-note-id={note.noteId}
onClick={(e) => link.goToLink(e)}
>
<h5 className="note-book-header">
<Icon className="note-icon" icon={note.getIcon()} />
<span ref={titleRef} className="note-book-title">{noteTitle}</span>
<NoteAttributes note={note} />
</h5>
<NoteContent note={note} trim highlightedTokens={highlightedTokens} />
</div>
)
}
function NoteAttributes({ note }: { note: FNote }) {
const ref = useRef<HTMLSpanElement>(null);
useEffect(() => {
attribute_renderer.renderNormalAttributes(note).then(({$renderedAttributes}) => {
ref.current?.replaceChildren(...$renderedAttributes);
});
}, [ note ]);
return <span className="note-list-attributes" ref={ref} />
}
function NoteContent({ note, trim, highlightedTokens }: { note: FNote, trim?: boolean, highlightedTokens }) {
const contentRef = useRef<HTMLDivElement>(null);
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
useEffect(() => {
content_renderer.getRenderedContent(note, { trim })
.then(({ $renderedContent, type }) => {
if (!contentRef.current) return;
contentRef.current.replaceChildren(...$renderedContent);
contentRef.current.classList.add(`type-${type}`);
highlightSearch(contentRef.current);
})
.catch(e => {
console.warn(`Caught error while rendering note '${note.noteId}' of type '${note.type}'`);
console.error(e);
contentRef.current?.replaceChildren(t("collections.rendering_error"));
})
}, [ note, highlightedTokens ]);
return <div ref={contentRef} className="note-book-content" />;
}
function NoteChildren({ note, parentNote, highlightedTokens }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined }) {
const imageLinks = note.getRelations("imageLink");
const [ childNotes, setChildNotes ] = useState<FNote[]>();
useEffect(() => {
note.getChildNotes().then(childNotes => {
const filteredChildNotes = childNotes.filter((childNote) => !imageLinks.find((rel) => rel.value === childNote.noteId));
setChildNotes(filteredChildNotes);
});
}, [ note ]);
return childNotes?.map(childNote => <ListNoteCard note={childNote} parentNote={parentNote} highlightedTokens={highlightedTokens} />)
}
/**
* Filters the note IDs for the legacy view to filter out subnotes that are already included in the note content such as images, included notes.
*/
function useFilteredNoteIds(note: FNote, noteIds: string[]) {
return useMemo(() => {
const includedLinks = note ? note.getRelations().filter((rel) => rel.name === "imageLink" || rel.name === "includeNoteLink") : [];
const includedNoteIds = new Set(includedLinks.map((rel) => rel.value));
return noteIds.filter((noteId) => !includedNoteIds.has(noteId) && noteId !== "_hidden");
}, noteIds);
}
function getNotePath(parentNote: FNote, childNote: FNote) {
if (parentNote.type === "search") {
// for search note parent, we want to display a non-search path
return childNote.noteId;
} else {
return `${parentNote.noteId}/${childNote.noteId}`
}
}

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