diff --git a/.github/actions/build-server/action.yml b/.github/actions/build-server/action.yml index faa3c0752..b92b3875f 100644 --- a/.github/actions/build-server/action.yml +++ b/.github/actions/build-server/action.yml @@ -12,7 +12,7 @@ runs: - name: Set up node & dependencies uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: "pnpm" - name: Install dependencies shell: bash diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 62e04d48d..5e8fb1301 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,6 +1,4 @@ -# GitHub Actions workflow for deploying MkDocs documentation to Cloudflare Pages -# This workflow builds and deploys your MkDocs site when changes are pushed to main -name: Deploy MkDocs Documentation +name: Deploy Documentation on: # Trigger on push to main branch @@ -11,11 +9,9 @@ on: # Only run when docs files change paths: - 'docs/**' - - 'README.md' # README is synced to docs/index.md - - 'mkdocs.yml' - - 'requirements-docs.txt' - - '.github/workflows/deploy-docs.yml' - - 'scripts/fix-mkdocs-structure.ts' + - 'apps/edit-docs/**' + - 'apps/build-docs/**' + - 'packages/share-theme/**' # Allow manual triggering from Actions tab workflow_dispatch: @@ -27,15 +23,13 @@ on: - master paths: - 'docs/**' - - 'README.md' # README is synced to docs/index.md - - 'mkdocs.yml' - - 'requirements-docs.txt' - - '.github/workflows/deploy-docs.yml' - - 'scripts/fix-mkdocs-structure.ts' + - 'apps/edit-docs/**' + - 'apps/build-docs/**' + - 'packages/share-theme/**' jobs: build-and-deploy: - name: Build and Deploy MkDocs + name: Build and Deploy Documentation runs-on: ubuntu-latest timeout-minutes: 10 @@ -49,72 +43,27 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v5 - with: - fetch-depth: 0 # Fetch all history for git info and mkdocs-git-revision-date plugin - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: '3.14' - cache: 'pip' - cache-dependency-path: 'requirements-docs.txt' - - - name: Install MkDocs and Dependencies - run: | - pip install --upgrade pip - pip install -r requirements-docs.txt - env: - PIP_DISABLE_PIP_VERSION_CHECK: 1 - - # Setup pnpm before fixing docs structure - name: Setup pnpm uses: pnpm/action-setup@v4 - # Setup Node.js with pnpm - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: '22' + node-version: '24' cache: 'pnpm' - # Install Node.js dependencies for the TypeScript script - name: Install Dependencies - run: | - pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile - - name: Fix Documentation Structure - run: | - # Fix duplicate navigation entries by moving overview pages to index.md - pnpm run chore:fix-mkdocs-structure - - - name: Build MkDocs Site - run: | - # Build with strict mode but allow expected warnings - mkdocs build --verbose || { - EXIT_CODE=$? - # Check if the only issue is expected warnings - if mkdocs build 2>&1 | grep -E "WARNING.*(README|not found)" && \ - [ $(mkdocs build 2>&1 | grep -c "ERROR") -eq 0 ]; then - echo "✅ Build succeeded with expected warnings" - mkdocs build --verbose - else - echo "❌ Build failed with unexpected errors" - 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: Trigger build of documentation + run: pnpm docs:build - name: Validate Built Site run: | - # Basic validation that important files exist test -f site/index.html || (echo "ERROR: site/index.html not found" && exit 1) - test -f site/sitemap.xml || (echo "ERROR: site/sitemap.xml not found" && exit 1) - test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1) - echo "✅ Site validation passed" + test -f site/developer-guide/index.html || (echo "ERROR: site/developer-guide/index.html not found" && exit 1) + echo "✓ User Guide and Developer Guide built successfully" - name: Deploy uses: ./.github/actions/deploy-to-cloudflare-pages diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index ec4aeda0e..f9174fb42 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -30,7 +30,7 @@ jobs: - name: Set up node & dependencies uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: "pnpm" - run: pnpm install --frozen-lockfile diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 407272a99..fab20d242 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -46,7 +46,7 @@ jobs: - name: Set up node & dependencies uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: "pnpm" - name: Install npm dependencies @@ -86,12 +86,12 @@ jobs: - name: Upload Playwright trace if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: Playwright trace (${{ matrix.dockerfile }}) path: test-output/playwright/output - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 if: ${{ !cancelled() }} with: name: Playwright report (${{ matrix.dockerfile }}) @@ -116,10 +116,10 @@ jobs: - dockerfile: Dockerfile platform: linux/arm64 image: ubuntu-24.04-arm - - dockerfile: Dockerfile + - dockerfile: Dockerfile.legacy platform: linux/arm/v7 image: ubuntu-24.04-arm - - dockerfile: Dockerfile + - dockerfile: Dockerfile.legacy platform: linux/arm/v8 image: ubuntu-24.04-arm runs-on: ${{ matrix.image }} @@ -146,7 +146,7 @@ jobs: - name: Set up node & dependencies uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: 'pnpm' - name: Install dependencies @@ -209,7 +209,7 @@ jobs: touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }} path: /tmp/digests/* @@ -223,7 +223,7 @@ jobs: - build steps: - name: Download digests - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: path: /tmp/digests pattern: digests-* diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0d0205f32..ddce68d42 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -52,7 +52,7 @@ jobs: - name: Set up node & dependencies uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile @@ -89,7 +89,7 @@ jobs: name: Nightly Build - name: Publish artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 if: ${{ github.event_name == 'pull_request' }} with: name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }} diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 886b46d61..a33d24283 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -24,7 +24,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: 'pnpm' - name: Install dependencies @@ -35,7 +35,7 @@ jobs: - name: Upload test report if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: e2e report path: apps/server-e2e/test-output diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ee30040d..3d48cb80d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,7 @@ jobs: - name: Set up node & dependencies uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile @@ -73,7 +73,7 @@ jobs: GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }} - name: Upload the artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }} path: apps/desktop/upload/*.* @@ -100,7 +100,7 @@ jobs: arch: ${{ matrix.arch }} - name: Upload the artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: release-server-linux-${{ matrix.arch }} path: upload/*.* @@ -120,7 +120,7 @@ jobs: docs/Release Notes - name: Download all artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: merge-multiple: true pattern: release-* diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index fe94c987a..7a87cc192 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -30,7 +30,7 @@ jobs: - name: Set up node & dependencies uses: actions/setup-node@v6 with: - node-version: 22 + node-version: 24 cache: "pnpm" - name: Install dependencies diff --git a/.nvmrc b/.nvmrc index f5b3ef39f..40115e966 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.21.0 \ No newline at end of file +24.11.0 \ No newline at end of file diff --git a/_regroup/package.json b/_regroup/package.json index 4cec178aa..fd13c78f1 100644 --- a/_regroup/package.json +++ b/_regroup/package.json @@ -37,20 +37,18 @@ "devDependencies": { "@playwright/test": "1.56.1", "@stylistic/eslint-plugin": "5.5.0", - "@types/express": "5.0.3", - "@types/node": "22.18.12", - "@types/yargs": "17.0.33", + "@types/express": "5.0.5", + "@types/node": "24.10.0", + "@types/yargs": "17.0.34", "@vitest/coverage-v8": "3.2.4", - "eslint": "9.38.0", + "eslint": "9.39.0", "eslint-plugin-simple-import-sort": "12.1.1", "esm": "3.2.25", "jsdoc": "4.0.5", "lorem-ipsum": "2.0.8", "rcedit": "4.0.1", - "rimraf": "6.0.1", - "tslib": "2.8.1", - "typedoc": "0.28.14", - "typedoc-plugin-missing-exports": "4.1.2" + "rimraf": "6.1.0", + "tslib": "2.8.1" }, "optionalDependencies": { "appdmg": "0.6.6" diff --git a/_regroup/typedoc.json b/_regroup/typedoc.json deleted file mode 100644 index 30771621c..000000000 --- a/_regroup/typedoc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "entryPoints": [ - "src/services/backend_script_entrypoint.ts", - "src/public/app/services/frontend_script_entrypoint.ts" - ], - "plugin": [ - "typedoc-plugin-missing-exports" - ], - "outputs": [ - { - "name": "html", - "path": "./docs/Script API" - } - ] -} diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json new file mode 100644 index 000000000..f22baec81 --- /dev/null +++ b/apps/build-docs/package.json @@ -0,0 +1,22 @@ +{ + "name": "build-docs", + "version": "1.0.0", + "description": "", + "main": "src/main.ts", + "scripts": { + "start": "tsx ." + }, + "keywords": [], + "author": "Elian Doran ", + "license": "AGPL-3.0-only", + "packageManager": "pnpm@10.20.0", + "devDependencies": { + "@redocly/cli": "2.10.0", + "archiver": "7.0.1", + "fs-extra": "11.3.2", + "react": "19.2.0", + "react-dom": "19.2.0", + "typedoc": "0.28.14", + "typedoc-plugin-missing-exports": "4.1.2" + } +} diff --git a/apps/build-docs/src/backend_script_entrypoint.ts b/apps/build-docs/src/backend_script_entrypoint.ts new file mode 100644 index 000000000..bc9087c0c --- /dev/null +++ b/apps/build-docs/src/backend_script_entrypoint.ts @@ -0,0 +1,36 @@ +/** + * The backend script API is accessible to code notes with the "JS (backend)" language. + * + * The entire API is exposed as a single global: {@link api} + * + * @module Backend Script API + */ + +/** + * This file creates the entrypoint for TypeDoc that simulates the context from within a + * script note on the server side. + * + * Make sure to keep in line with backend's `script_context.ts`. + */ + +export type { default as AbstractBeccaEntity } from "../../server/src/becca/entities/abstract_becca_entity.js"; +export type { default as BAttachment } from "../../server/src/becca/entities/battachment.js"; +export type { default as BAttribute } from "../../server/src/becca/entities/battribute.js"; +export type { default as BBranch } from "../../server/src/becca/entities/bbranch.js"; +export type { default as BEtapiToken } from "../../server/src/becca/entities/betapi_token.js"; +export type { BNote }; +export type { default as BOption } from "../../server/src/becca/entities/boption.js"; +export type { default as BRecentNote } from "../../server/src/becca/entities/brecent_note.js"; +export type { default as BRevision } from "../../server/src/becca/entities/brevision.js"; + +import BNote from "../../server/src/becca/entities/bnote.js"; +import BackendScriptApi, { type Api } from "../../server/src/services/backend_script_api.js"; + +export type { Api }; + +const fakeNote = new BNote(); + +/** + * The `api` global variable allows access to the backend script API, which is documented in {@link Api}. + */ +export const api: Api = new BackendScriptApi(fakeNote, {}); diff --git a/apps/build-docs/src/build-docs.ts b/apps/build-docs/src/build-docs.ts new file mode 100644 index 000000000..5d1a0cdd6 --- /dev/null +++ b/apps/build-docs/src/build-docs.ts @@ -0,0 +1,147 @@ +process.env.TRILIUM_INTEGRATION_TEST = "memory-no-store"; +process.env.TRILIUM_RESOURCE_DIR = "../server/src"; +process.env.NODE_ENV = "development"; + +import cls from "@triliumnext/server/src/services/cls.js"; +import { dirname, join, resolve } from "path"; +import * as fs from "fs/promises"; +import * as fsExtra from "fs-extra"; +import archiver from "archiver"; +import { WriteStream } from "fs"; +import { execSync } from "child_process"; +import BuildContext from "./context.js"; + +const DOCS_ROOT = "../../../docs"; +const OUTPUT_DIR = "../../site"; + +async function importAndExportDocs(sourcePath: string, outputSubDir: string) { + const note = await importData(sourcePath); + + // Use a meaningful name for the temporary zip file + const zipName = outputSubDir || "user-guide"; + const zipFilePath = `output-${zipName}.zip`; + try { + const { exportToZip } = (await import("@triliumnext/server/src/services/export/zip.js")).default; + const branch = note.getParentBranches()[0]; + const taskContext = new (await import("@triliumnext/server/src/services/task_context.js")).default( + "no-progress-reporting", + "export", + null + ); + const fileOutputStream = fsExtra.createWriteStream(zipFilePath); + await exportToZip(taskContext, branch, "share", fileOutputStream); + await waitForStreamToFinish(fileOutputStream); + + // Output to root directory if outputSubDir is empty, otherwise to subdirectory + const outputPath = outputSubDir ? join(OUTPUT_DIR, outputSubDir) : OUTPUT_DIR; + await extractZip(zipFilePath, outputPath); + } finally { + if (await fsExtra.exists(zipFilePath)) { + await fsExtra.rm(zipFilePath); + } + } +} + +async function buildDocsInner() { + const i18n = await import("@triliumnext/server/src/services/i18n.js"); + await i18n.initializeTranslations(); + + const sqlInit = (await import("../../server/src/services/sql_init.js")).default; + await sqlInit.createInitialDatabase(true); + + // Wait for becca to be loaded before importing data + const beccaLoader = await import("../../server/src/becca/becca_loader.js"); + await beccaLoader.beccaLoaded; + + // Build User Guide + console.log("Building User Guide..."); + await importAndExportDocs(join(__dirname, DOCS_ROOT, "User Guide"), "user-guide"); + + // Build Developer Guide + console.log("Building Developer Guide..."); + await importAndExportDocs(join(__dirname, DOCS_ROOT, "Developer Guide"), "developer-guide"); + + // Copy favicon. + await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "favicon.ico")); + await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "user-guide", "favicon.ico")); + await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "developer-guide", "favicon.ico")); + + console.log("Documentation built successfully!"); +} + +export async function importData(path: string) { + const buffer = await createImportZip(path); + const importService = (await import("../../server/src/services/import/zip.js")).default; + const TaskContext = (await import("../../server/src/services/task_context.js")).default; + const context = new TaskContext("no-progress-reporting", "importNotes", null); + const becca = (await import("../../server/src/becca/becca.js")).default; + + const rootNote = becca.getRoot(); + if (!rootNote) { + throw new Error("Missing root note for import."); + } + return await importService.importZip(context, buffer, rootNote, { + preserveIds: true + }); +} + +async function createImportZip(path: string) { + const inputFile = "input.zip"; + const archive = archiver("zip", { + zlib: { level: 0 } + }); + + console.log("Archive path is ", resolve(path)) + archive.directory(path, "/"); + + const outputStream = fsExtra.createWriteStream(inputFile); + archive.pipe(outputStream); + archive.finalize(); + await waitForStreamToFinish(outputStream); + + try { + return await fsExtra.readFile(inputFile); + } finally { + await fsExtra.rm(inputFile); + } +} + +function waitForStreamToFinish(stream: WriteStream) { + return new Promise((res, rej) => { + stream.on("finish", () => res()); + stream.on("error", (err) => rej(err)); + }); +} + +export async function extractZip(zipFilePath: string, outputPath: string, ignoredFiles?: Set) { + const { readZipFile, readContent } = (await import("@triliumnext/server/src/services/import/zip.js")); + await readZipFile(await fs.readFile(zipFilePath), async (zip, entry) => { + // We ignore directories since they can appear out of order anyway. + if (!entry.fileName.endsWith("/") && !ignoredFiles?.has(entry.fileName)) { + const destPath = join(outputPath, entry.fileName); + const fileContent = await readContent(zip, entry); + + await fsExtra.mkdirs(dirname(destPath)); + await fs.writeFile(destPath, fileContent); + } + + zip.readEntry(); + }); +} + +export default async function buildDocs({ gitRootDir }: BuildContext) { + // Build the share theme. + execSync(`pnpm run --filter share-theme build`, { + stdio: "inherit", + cwd: gitRootDir + }); + + // Trigger the actual build. + await new Promise((res, rej) => { + cls.init(() => { + buildDocsInner() + .catch(rej) + .then(res); + }); + }); +} diff --git a/apps/build-docs/src/context.ts b/apps/build-docs/src/context.ts new file mode 100644 index 000000000..ab2289e50 --- /dev/null +++ b/apps/build-docs/src/context.ts @@ -0,0 +1,4 @@ +export default interface BuildContext { + gitRootDir: string; + baseDir: string; +} diff --git a/apps/build-docs/src/frontend_script_entrypoint.ts b/apps/build-docs/src/frontend_script_entrypoint.ts new file mode 100644 index 000000000..768774eca --- /dev/null +++ b/apps/build-docs/src/frontend_script_entrypoint.ts @@ -0,0 +1,28 @@ +/** + * The front script API is accessible to code notes with the "JS (frontend)" language. + * + * The entire API is exposed as a single global: {@link api} + * + * @module Frontend Script API + */ + +/** + * This file creates the entrypoint for TypeDoc that simulates the context from within a + * script note. + * + * Make sure to keep in line with frontend's `script_context.ts`. + */ + +export type { default as BasicWidget } from "../../client/src/widgets/basic_widget.js"; +export type { default as FAttachment } from "../../client/src/entities/fattachment.js"; +export type { default as FAttribute } from "../../client/src/entities/fattribute.js"; +export type { default as FBranch } from "../../client/src/entities/fbranch.js"; +export type { default as FNote } from "../../client/src/entities/fnote.js"; +export type { Api } from "../../client/src/services/frontend_script_api.js"; +export type { default as NoteContextAwareWidget } from "../../client/src/widgets/note_context_aware_widget.js"; +export type { default as RightPanelWidget } from "../../client/src/widgets/right_panel_widget.js"; + +import FrontendScriptApi, { type Api } from "../../client/src/services/frontend_script_api.js"; + +//@ts-expect-error +export const api: Api = new FrontendScriptApi(); diff --git a/apps/build-docs/src/index.html b/apps/build-docs/src/index.html new file mode 100644 index 000000000..47a0bfb34 --- /dev/null +++ b/apps/build-docs/src/index.html @@ -0,0 +1,10 @@ + + + + + Redirecting... + + +

If you are not redirected automatically, click here.

+ + \ No newline at end of file diff --git a/apps/build-docs/src/main.ts b/apps/build-docs/src/main.ts new file mode 100644 index 000000000..19d533420 --- /dev/null +++ b/apps/build-docs/src/main.ts @@ -0,0 +1,29 @@ +import { join } from "path"; +import BuildContext from "./context"; +import buildSwagger from "./swagger"; +import { cpSync, existsSync, mkdirSync, rmSync } from "fs"; +import buildDocs from "./build-docs"; +import buildScriptApi from "./script-api"; + +const context: BuildContext = { + gitRootDir: join(__dirname, "../../../"), + baseDir: join(__dirname, "../../../site") +}; + +async function main() { + // Clean input dir. + if (existsSync(context.baseDir)) { + rmSync(context.baseDir, { recursive: true }); + } + mkdirSync(context.baseDir); + + // Start building. + await buildDocs(context); + buildSwagger(context); + buildScriptApi(context); + + // Copy index file. + cpSync(join(__dirname, "index.html"), join(context.baseDir, "index.html")); +} + +main(); diff --git a/apps/build-docs/src/script-api.ts b/apps/build-docs/src/script-api.ts new file mode 100644 index 000000000..8473ae3a0 --- /dev/null +++ b/apps/build-docs/src/script-api.ts @@ -0,0 +1,15 @@ +import { execSync } from "child_process"; +import BuildContext from "./context"; +import { join } from "path"; + +export default function buildScriptApi({ baseDir, gitRootDir }: BuildContext) { + // Generate types + execSync(`pnpm typecheck`, { stdio: "inherit", cwd: gitRootDir }); + + for (const config of [ "backend", "frontend" ]) { + const outDir = join(baseDir, "script-api", config); + execSync(`pnpm typedoc --options typedoc.${config}.json --html "${outDir}"`, { + stdio: "inherit" + }); + } +} diff --git a/apps/build-docs/src/swagger.ts b/apps/build-docs/src/swagger.ts new file mode 100644 index 000000000..b3677aeeb --- /dev/null +++ b/apps/build-docs/src/swagger.ts @@ -0,0 +1,32 @@ +import BuildContext from "./context"; +import { join } from "path"; +import { execSync } from "child_process"; +import { mkdirSync } from "fs"; + +interface BuildInfo { + specPath: string; + outDir: string; +} + +const DIR_PREFIX = "rest-api"; + +const buildInfos: BuildInfo[] = [ + { + // Paths are relative to Git root. + specPath: "apps/server/internal.openapi.yaml", + outDir: `${DIR_PREFIX}/internal` + }, + { + specPath: "apps/server/etapi.openapi.yaml", + outDir: `${DIR_PREFIX}/etapi` + } +]; + +export default function buildSwagger({ baseDir, gitRootDir }: BuildContext) { + for (const { specPath, outDir } of buildInfos) { + const absSpecPath = join(gitRootDir, specPath); + const targetDir = join(baseDir, outDir); + mkdirSync(targetDir, { recursive: true }); + execSync(`pnpm redocly build-docs ${absSpecPath} -o ${targetDir}/index.html`, { stdio: "inherit" }); + } +} diff --git a/apps/build-docs/tsconfig.app.json b/apps/build-docs/tsconfig.app.json new file mode 100644 index 000000000..b9e17115a --- /dev/null +++ b/apps/build-docs/tsconfig.app.json @@ -0,0 +1,36 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", + "target": "ES2020", + "outDir": "dist", + "strict": false, + "types": [ + "node", + "express" + ], + "rootDir": "src", + "tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo" + }, + "include": [ + "src/**/*.ts", + "../server/src/*.d.ts" + ], + "exclude": [ + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs" + ], + "references": [ + { + "path": "../server/tsconfig.app.json" + }, + { + "path": "../desktop/tsconfig.app.json" + }, + { + "path": "../client/tsconfig.app.json" + } + ] +} diff --git a/apps/build-docs/tsconfig.json b/apps/build-docs/tsconfig.json new file mode 100644 index 000000000..858921cfb --- /dev/null +++ b/apps/build-docs/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [], + "references": [ + { + "path": "../server" + }, + { + "path": "../client" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/apps/build-docs/typedoc.backend.json b/apps/build-docs/typedoc.backend.json new file mode 100644 index 000000000..1781774c6 --- /dev/null +++ b/apps/build-docs/typedoc.backend.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "name": "Trilium Backend API", + "entryPoints": [ + "src/backend_script_entrypoint.ts" + ], + "plugin": [ + "typedoc-plugin-missing-exports" + ] +} diff --git a/apps/build-docs/typedoc.frontend.json b/apps/build-docs/typedoc.frontend.json new file mode 100644 index 000000000..f07d20dc7 --- /dev/null +++ b/apps/build-docs/typedoc.frontend.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "name": "Trilium Frontend API", + "entryPoints": [ + "src/frontend_script_entrypoint.ts" + ], + "plugin": [ + "typedoc-plugin-missing-exports" + ] +} diff --git a/apps/client/package.json b/apps/client/package.json index 71957e495..c080281ec 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -15,7 +15,7 @@ "circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular" }, "dependencies": { - "@eslint/js": "9.38.0", + "@eslint/js": "9.39.0", "@excalidraw/excalidraw": "0.18.0", "@fullcalendar/core": "6.1.19", "@fullcalendar/daygrid": "6.1.19", @@ -37,12 +37,12 @@ "bootstrap": "5.3.8", "boxicons": "2.1.4", "color": "5.0.2", - "dayjs": "1.11.18", + "dayjs": "1.11.19", "dayjs-plugin-utc": "0.1.2", - "debounce": "2.2.0", + "debounce": "3.0.0", "draggabilly": "3.0.0", "force-graph": "1.51.0", - "globals": "16.4.0", + "globals": "16.5.0", "i18next": "25.6.0", "i18next-http-backend": "3.0.2", "jquery": "3.7.1", @@ -54,12 +54,12 @@ "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", "marked": "16.4.1", - "mermaid": "11.12.0", - "mind-elixir": "5.3.3", + "mermaid": "11.12.1", + "mind-elixir": "5.3.4", "normalize.css": "8.0.1", "panzoom": "9.4.3", "preact": "10.27.2", - "react-i18next": "16.1.2", + "react-i18next": "16.2.3", "reveal.js": "5.2.1", "svg-pan-zoom": "3.6.2", "tabulator-tables": "6.3.1", @@ -74,9 +74,9 @@ "@types/leaflet-gpx": "1.3.8", "@types/mark.js": "8.11.12", "@types/reveal.js": "5.2.1", - "@types/tabulator-tables": "6.2.11", + "@types/tabulator-tables": "6.3.0", "copy-webpack-plugin": "13.0.1", - "happy-dom": "20.0.7", + "happy-dom": "20.0.10", "script-loader": "0.7.2", "vite-plugin-static-copy": "3.1.4" } diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index ce33d1447..7bc544e7e 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -218,12 +218,12 @@ export type CommandMappings = { /** Works only in the electron context menu. */ replaceMisspelling: CommandData; - importMarkdownInline: CommandData; showPasswordNotSet: CommandData; showProtectedSessionPasswordDialog: CommandData; showUploadAttachmentsDialog: CommandData & { noteId: string }; showIncludeNoteDialog: CommandData & { textTypeWidget: EditableTextTypeWidget }; showAddLinkDialog: CommandData & { textTypeWidget: EditableTextTypeWidget, text: string }; + showPasteMarkdownDialog: CommandData & { textTypeWidget: EditableTextTypeWidget }; closeProtectedSessionPasswordDialog: CommandData; copyImageReferenceToClipboard: CommandData; copyImageToClipboard: CommandData; @@ -270,6 +270,7 @@ export type CommandMappings = { closeThisNoteSplit: CommandData; moveThisNoteSplit: CommandData & { isMovingLeft: boolean }; jumpToNote: CommandData; + openTodayNote: CommandData; commandPalette: CommandData; // Keyboard shortcuts diff --git a/apps/client/src/components/entrypoints.ts b/apps/client/src/components/entrypoints.ts index 7989960a6..8a902666f 100644 --- a/apps/client/src/components/entrypoints.ts +++ b/apps/client/src/components/entrypoints.ts @@ -159,6 +159,16 @@ export default class Entrypoints extends Component { this.openInWindowCommand({ notePath: "", hoistedNoteId: "root" }); } + async openTodayNoteCommand() { + const todayNote = await dateNoteService.getTodayNote(); + if (!todayNote) { + console.warn("Missing today note."); + return; + } + + await appContext.tabManager.openInSameTab(todayNote.noteId); + } + async runActiveNoteCommand() { const noteContext = appContext.tabManager.getActiveContext(); if (!noteContext) { diff --git a/apps/client/src/entities/fnote.ts b/apps/client/src/entities/fnote.ts index bcb6c408e..6d0a15506 100644 --- a/apps/client/src/entities/fnote.ts +++ b/apps/client/src/entities/fnote.ts @@ -417,7 +417,7 @@ export default class FNote { return notePaths; } - getSortedNotePathRecords(hoistedNoteId = "root"): NotePathRecord[] { + getSortedNotePathRecords(hoistedNoteId = "root", activeNotePath: string | null = null): NotePathRecord[] { const isHoistedRoot = hoistedNoteId === "root"; const notePaths: NotePathRecord[] = this.getAllNotePaths().map((path) => ({ @@ -428,7 +428,23 @@ export default class FNote { isHidden: path.includes("_hidden") })); + // Calculate the length of the prefix match between two arrays + const prefixMatchLength = (path: string[], target: string[]) => { + const diffIndex = path.findIndex((seg, i) => seg !== target[i]); + return diffIndex === -1 ? Math.min(path.length, target.length) : diffIndex; + }; + notePaths.sort((a, b) => { + if (activeNotePath) { + const activeSegments = activeNotePath.split('/'); + const aOverlap = prefixMatchLength(a.notePath, activeSegments); + const bOverlap = prefixMatchLength(b.notePath, activeSegments); + // Paths with more matching prefix segments are prioritized + // when the match count is equal, other criteria are used for sorting + if (bOverlap !== aOverlap) { + return bOverlap - aOverlap; + } + } if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { return a.isInHoistedSubTree ? -1 : 1; } else if (a.isArchived !== b.isArchived) { @@ -449,10 +465,11 @@ export default class FNote { * Returns the note path considered to be the "best" * * @param {string} [hoistedNoteId='root'] + * @param {string|null} [activeNotePath=null] * @return {string[]} array of noteIds constituting the particular note path */ - getBestNotePath(hoistedNoteId = "root") { - return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; + getBestNotePath(hoistedNoteId = "root", activeNotePath: string | null = null) { + return this.getSortedNotePathRecords(hoistedNoteId, activeNotePath)[0]?.notePath; } /** diff --git a/apps/client/src/print.tsx b/apps/client/src/print.tsx index de11d581a..3dbdf1de0 100644 --- a/apps/client/src/print.tsx +++ b/apps/client/src/print.tsx @@ -56,7 +56,20 @@ function SingleNoteRenderer({ note, onReady }: RendererProps) { await import("@triliumnext/ckeditor5/src/theme/ck-content.css"); } const { $renderedContent } = await content_renderer.getRenderedContent(note, { noChildrenList: true }); - containerRef.current?.replaceChildren(...$renderedContent); + const container = containerRef.current!; + container.replaceChildren(...$renderedContent); + + // Wait for all images to load. + const images = Array.from(container.querySelectorAll("img")); + await Promise.all( + images.map(img => { + if (img.complete) return Promise.resolve(); + return new Promise(resolve => { + img.addEventListener("load", () => resolve(), { once: true }); + img.addEventListener("error", () => resolve(), { once: true }); + }); + }) + ); } load().then(() => requestAnimationFrame(onReady)) diff --git a/apps/client/src/services/frontend_script_entrypoint.ts b/apps/client/src/services/frontend_script_entrypoint.ts deleted file mode 100644 index 75a27d204..000000000 --- a/apps/client/src/services/frontend_script_entrypoint.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * The front script API is accessible to code notes with the "JS (frontend)" language. - * - * The entire API is exposed as a single global: {@link api} - * - * @module Frontend Script API - */ - -/** - * This file creates the entrypoint for TypeDoc that simulates the context from within a - * script note. - * - * Make sure to keep in line with frontend's `script_context.ts`. - */ - -export type { default as BasicWidget } from "../widgets/basic_widget.js"; -export type { default as FAttachment } from "../entities/fattachment.js"; -export type { default as FAttribute } from "../entities/fattribute.js"; -export type { default as FBranch } from "../entities/fbranch.js"; -export type { default as FNote } from "../entities/fnote.js"; -export type { Api } from "./frontend_script_api.js"; -export type { default as NoteContextAwareWidget } from "../widgets/note_context_aware_widget.js"; -export type { default as RightPanelWidget } from "../widgets/right_panel_widget.js"; - -import FrontendScriptApi, { type Api } from "./frontend_script_api.js"; - -//@ts-expect-error -export const api: Api = new FrontendScriptApi(); diff --git a/apps/client/src/services/glob.ts b/apps/client/src/services/glob.ts index 48d0d29a7..44ce64309 100644 --- a/apps/client/src/services/glob.ts +++ b/apps/client/src/services/glob.ts @@ -20,9 +20,6 @@ function setupGlobs() { window.glob.froca = froca; window.glob.treeCache = froca; // compatibility for CKEditor builds for a while - // for CKEditor integration (button on block toolbar) - window.glob.importMarkdownInline = async () => appContext.triggerCommand("importMarkdownInline"); - window.onerror = function (msg, url, lineNo, columnNo, error) { const string = String(msg).toLowerCase(); diff --git a/apps/client/src/services/in_app_help.ts b/apps/client/src/services/in_app_help.ts index a0b118e5c..2f805783a 100644 --- a/apps/client/src/services/in_app_help.ts +++ b/apps/client/src/services/in_app_help.ts @@ -10,7 +10,7 @@ export const byNoteType: Record, string | null> = { file: null, image: null, launcher: null, - mermaid: null, + mermaid: "s1aBHPd79XYj", mindMap: null, noteMap: null, relationMap: null, diff --git a/apps/client/src/services/tree.ts b/apps/client/src/services/tree.ts index fc54c3c75..ec5bc0191 100644 --- a/apps/client/src/services/tree.ts +++ b/apps/client/src/services/tree.ts @@ -26,21 +26,12 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root } const path = notePath.split("/").reverse(); - - if (!path.includes("root")) { - path.push("root"); - } - const effectivePathSegments: string[] = []; let childNoteId: string | null = null; let i = 0; - while (true) { - if (i >= path.length) { - break; - } - - const parentNoteId = path[i++]; + for (let i = 0; i < path.length; i++) { + const parentNoteId = path[i]; if (childNoteId !== null) { const child = await froca.getNote(childNoteId, !logErrors); @@ -65,7 +56,7 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root return null; } - if (!parents.some((p) => p.noteId === parentNoteId)) { + if (!parents.some(p => p.noteId === parentNoteId) || (i === path.length - 1 && parentNoteId !== 'root')) { if (logErrors) { const parent = froca.getNoteFromCache(parentNoteId); @@ -77,7 +68,8 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root ); } - const bestNotePath = child.getBestNotePath(hoistedNoteId); + const activeNotePath = appContext.tabManager.getActiveContextNotePath(); + const bestNotePath = child.getBestNotePath(hoistedNoteId, activeNotePath); if (bestNotePath) { const pathToRoot = bestNotePath.reverse().slice(1); @@ -108,7 +100,9 @@ async function resolveNotePathToSegments(notePath: string, hoistedNoteId = "root if (!note) { throw new Error(`Unable to find note: ${notePath}.`); } - const bestNotePath = note.getBestNotePath(hoistedNoteId); + + const activeNotePath = appContext.tabManager.getActiveContextNotePath(); + const bestNotePath = note.getBestNotePath(hoistedNoteId, activeNotePath); if (!bestNotePath) { throw new Error(`Did not find any path segments for '${note.toString()}', hoisted note '${hoistedNoteId}'`); diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 0f17bdc79..f5e037be5 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -11,7 +11,11 @@ export function reloadFrontendApp(reason?: string) { logInfo(`Frontend app reload: ${reason}`); } - window.location.reload(); + if (isElectron()) { + dynamicRequire("@electron/remote").BrowserWindow.getFocusedWindow()?.reload(); + } else { + window.location.reload(); + } } export function restartDesktopApp() { diff --git a/apps/client/src/share.ts b/apps/client/src/share.ts deleted file mode 100644 index b438f0c0c..000000000 --- a/apps/client/src/share.ts +++ /dev/null @@ -1,84 +0,0 @@ -import "normalize.css"; -import "boxicons/css/boxicons.min.css"; -import "@triliumnext/ckeditor5/src/theme/ck-content.css"; -import "@triliumnext/share-theme/styles/index.css"; -import "@triliumnext/share-theme/scripts/index.js"; - -async function ensureJQuery() { - const $ = (await import("jquery")).default; - (window as any).$ = $; -} - -async function applyMath() { - const anyMathBlock = document.querySelector("#content .math-tex"); - if (!anyMathBlock) { - return; - } - - const renderMathInElement = (await import("./services/math.js")).renderMathInElement; - renderMathInElement(document.getElementById("content")); -} - -async function formatCodeBlocks() { - const anyCodeBlock = document.querySelector("#content pre"); - if (!anyCodeBlock) { - return; - } - await ensureJQuery(); - const { formatCodeBlocks } = await import("./services/syntax_highlight.js"); - await formatCodeBlocks($("#content")); -} - -async function setupTextNote() { - formatCodeBlocks(); - applyMath(); - - const setupMermaid = (await import("./share/mermaid.js")).default; - setupMermaid(); -} - -/** - * Fetch note with given ID from backend - * - * @param noteId of the given note to be fetched. If false, fetches current note. - */ -async function fetchNote(noteId: string | null = null) { - if (!noteId) { - noteId = document.body.getAttribute("data-note-id"); - } - - const resp = await fetch(`api/notes/${noteId}`); - - return await resp.json(); -} - -document.addEventListener( - "DOMContentLoaded", - () => { - const noteType = determineNoteType(); - - if (noteType === "text") { - setupTextNote(); - } - - const toggleMenuButton = document.getElementById("toggleMenuButton"); - const layout = document.getElementById("layout"); - - if (toggleMenuButton && layout) { - toggleMenuButton.addEventListener("click", () => layout.classList.toggle("showMenu")); - } - }, - false -); - -function determineNoteType() { - const bodyClass = document.body.className; - const match = bodyClass.match(/type-([^\s]+)/); - return match ? match[1] : null; -} - -// workaround to prevent webpack from removing "fetchNote" as dead code: -// add fetchNote as property to the window object -Object.defineProperty(window, "fetchNote", { - value: fetchNote -}); diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 7f67e4827..1f4152792 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -2034,9 +2034,9 @@ body.zen #right-pane, body.zen #mobile-sidebar-wrapper, body.zen .tab-row-container, body.zen .tab-row-widget, -body.zen .ribbon-container:not(:has(.classic-toolbar-widget.visible)), -body.zen .ribbon-container:has(.classic-toolbar-widget.visible) .ribbon-top-row, -body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget.visible)), +body.zen .ribbon-container:not(:has(.classic-toolbar-widget)), +body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row, +body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)), body.zen .note-icon-widget, body.zen .title-row .icon-action, body.zen .floating-buttons-children > *:not(.bx-edit-alt), diff --git a/apps/client/src/translations/ar/translation.json b/apps/client/src/translations/ar/translation.json index 2e11116fb..dffb4f9d4 100644 --- a/apps/client/src/translations/ar/translation.json +++ b/apps/client/src/translations/ar/translation.json @@ -12,6 +12,9 @@ "toast": { "critical-error": { "title": "خطأ فادح" + }, + "widget-error": { + "title": "فشل في البدء بعنصر الواجهة" } }, "add_link": { @@ -26,7 +29,8 @@ "edit_branch_prefix": "تعديل بادئة الفرع", "prefix": "البادئة: ", "save": "حفظ", - "help_on_tree_prefix": "مساعدة حول بادئة الشجرة" + "help_on_tree_prefix": "مساعدة حول بادئة الشجرة", + "branch_prefix_saved": "تم حفظ بادئة الفرع." }, "bulk_actions": { "bulk_actions": "اجراءات جماعية", @@ -83,7 +87,8 @@ "workspace_calendar_root": "‎تحديد جذر التقويم لكل مساحة عمل", "hide_highlight_widget": "اخفاء عنصر واجهة قائمة التمييزات", "is_owned_by_note": "تخص الملاحظة", - "and_more": "... و {{count}}مرات اكثر." + "and_more": "... و {{count}}مرات اكثر.", + "related_notes_title": "ملاحظات اخرى بنفس التسمية" }, "rename_label": { "to": "الى", @@ -127,7 +132,9 @@ "delete_attachment": "حذف المرفق", "upload_new_revision": "رفع مراجعة جديدة", "copy_link_to_clipboard": "نسخ الرابط الى الحافظة", - "convert_attachment_into_note": "تحويل المرفق الى ملاحظة" + "convert_attachment_into_note": "تحويل المرفق الى ملاحظة", + "delete_success": "تم حذف المرفق \"{{title}}\" .", + "enter_new_name": "ادخل اسم مرفق جديد" }, "calendar": { "week": "أسبوع", @@ -259,7 +266,8 @@ "note_paths": { "search": "بحث", "archived": "مؤرشف", - "title": "مسارات الملاحظة" + "title": "مسارات الملاحظة", + "clone_button": "جار نسخ الملاحظة الى مكان جديد..." }, "script_executor": { "query": "استعلام", @@ -372,7 +380,8 @@ "export_note_title": "تصدير الملاحظة", "export_status": "حالة التصدير", "export_finished_successfully": "اكتمل التصدير بنجاح.", - "export_in_progress": "جار التصدير: {{progressCount}}" + "export_in_progress": "جار التصدير: {{progressCount}}", + "choose_export_type": "اختر نوع التصدير اولا من فضلك" }, "help": { "troubleshooting": "أستكشاف الاخطاء واصلاحها", @@ -402,7 +411,10 @@ "movingCloningNotes": "نقل/ استنساخ الملاحظات", "deleteNotes": "حذف الملاحظة/ الشجرة الفرعية", "collapseWholeTree": "طي شجرة الملاحظة باكملها", - "followLink": "اتبع تلرابط تحت المؤشر" + "followLink": "اتبع تلرابط تحت المؤشر", + "onlyInDesktop": "في سطح المكتب فقط(Electron build)", + "createEditLink": "انشاء/ تحرير رابط خارجي", + "quickSearch": "الانتقال الى مربع البحث السريع" }, "import": { "options": "خيارات", @@ -465,7 +477,13 @@ "delete_all_button": "حذف كل المراجعات", "settings": "اعدادات مراجعة الملاحظة", "diff_not_available": "المقارنة غير متوفرة.", - "help_title": "مساعدة حول مراجعات الملاحظة" + "help_title": "مساعدة حول مراجعات الملاحظة", + "diff_off_hint": "انقر لعرض محتويات الملاحظة", + "revisions_deleted": "تم حذف جميع نسخ المراجعات للملاحظة.", + "revision_restored": "تم استعادة نسخ المراجعة للملاحظة.", + "revision_deleted": "تم حذف مراجعة الملاحظة.", + "snapshot_interval": "فاصل زمني لحفظ لقطات اصدارات المراجعة: {{seconds}}", + "maximum_revisions": "حد عدد لقطات اصدارات الملاحظة: {{number}}" }, "sort_child_notes": { "title": "عنوان", @@ -479,13 +497,15 @@ "sorting_direction": "اتجاه الترتيب", "natural_sort": "الترتيب الطبيعي", "natural_sort_language": "لغات الترتيب الطبيعي", - "sort_children_by": "ترتيب العناصر الفرعية حسب..." + "sort_children_by": "ترتيب العناصر الفرعية حسب...", + "sort_folders_at_top": "ترتيب المجلدات في الاعلى" }, "recent_changes": { "undelete_link": "الغاء الحذف", "title": "التغيرات الاخيرة", "no_changes_message": "لايوجد تغيير لحد الان...", - "erase_notes_button": "مسح الملاحظات المحذوفة الان" + "erase_notes_button": "مسح الملاحظات المحذوفة الان", + "deleted_notes_message": "تم حذف الملاحظات نهائيا." }, "edited_notes": { "deleted": "(حذف)", @@ -696,7 +716,6 @@ "backup_database_now": "نسخ اختياطي لقاعدة البيانات الان" }, "etapi": { - "wiki": "ويكي", "created": "تم الأنشاء", "actions": "أجراءات", "title": "ETAPI", @@ -705,7 +724,9 @@ "default_token_name": "رمز جديد", "rename_token_title": "اعادة تسمية الرمز", "rename_token": "اعادة تسمية هذا الرمز", - "create_token": "انشاء رمز PEAPI جديد" + "create_token": "انشاء رمز PEAPI جديد", + "new_token_title": "رمز ETAPI جديد", + "token_created_title": "انشاء رمز ETAPI" }, "password": { "heading": "كلمة المرور", @@ -811,7 +832,8 @@ "help_on_links": "مساعدة حول الارتباطات التشعبية", "notes_to_clone": "ملاحظات للنسخ", "target_parent_note": "الملاحظة الاصلية الهدف", - "clone_to_selected_note": "استنساخ الى الملاحظة المحددة" + "clone_to_selected_note": "استنساخ الى الملاحظة المحددة", + "no_path_to_clone_to": "لايوجد مسار لنسخ المحتوى الية." }, "table_of_contents": { "unit": "عناوين", @@ -1029,7 +1051,8 @@ }, "delete_note": { "delete_note": "حذف الملاحظة", - "delete_matched_notes": "حف الملاحظات المطابقة" + "delete_matched_notes": "حف الملاحظات المطابقة", + "delete_matched_notes_description": "سوف يؤدي هذا الى حذف الملاحظات المطابقة." }, "rename_note": { "rename_note": "اعادة تسمية الملاحظة", @@ -1312,7 +1335,8 @@ "notes_to_move": "الملاحظات المراد نقلها", "target_parent_note": "ملاحظة الاصل الهدف", "dialog_title": "انقل الملاحظات الى...", - "move_button": "نقل الىالملاحظة المحددة" + "move_button": "نقل الىالملاحظة المحددة", + "error_no_path": "لايوجد مسار لنقل العنصر الية." }, "delete_revisions": { "delete_note_revisions": "حذف مراجعات الملاحظة" @@ -1363,7 +1387,8 @@ "save_attributes": "حفظ السمات ", "add_a_new_attribute": "اضافة سمة جديدة", "add_new_label_definition": "اضافة تعريف لتسمية جديدة", - "add_new_relation_definition": "اضافة تعريف لعلاقة جديدة" + "add_new_relation_definition": "اضافة تعريف لعلاقة جديدة", + "add_new_relation": "اضافة علاقة جديدة " }, "zen_mode": { "button_exit": "الخروج من وضع Zen" @@ -1434,5 +1459,8 @@ }, "png_export_button": { "button_title": "تصدير المخطط كملف PNG" + }, + "protected_session_status": { + "inactive": "انقر للدخول الى جلسة محمية" } } diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index c7053e709..44aa6d387 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -51,7 +51,7 @@ "bulk_actions_executed": "批量操作已成功执行。", "none_yet": "暂无操作 ... 通过点击上方的可用操作添加一个操作。", "labels": "标签", - "relations": "关联关系", + "relations": "关系", "notes": "笔记", "other": "其它" }, @@ -104,7 +104,8 @@ "export_status": "导出状态", "export_in_progress": "导出进行中:{{progressCount}}", "export_finished_successfully": "导出成功完成。", - "format_pdf": "PDF - 用于打印或共享目的。" + "format_pdf": "PDF - 用于打印或共享目的。", + "share-format": "HTML 网页发布——采用与共享笔记相同的主题,但可发布为静态网站。" }, "help": { "noteNavigation": "笔记导航", @@ -184,7 +185,8 @@ }, "import-status": "导入状态", "in-progress": "导入进行中:{{progress}}", - "successful": "导入成功完成。" + "successful": "导入成功完成。", + "importZipRecommendation": "导入 ZIP 文件时,笔记层级将反映压缩文件内的子目录结构。" }, "include_note": { "dialog_title": "包含笔记", @@ -259,7 +261,6 @@ "delete_all_revisions": "删除此笔记的所有修订版本", "delete_all_button": "删除所有修订版本", "help_title": "关于笔记修订版本的帮助", - "revision_last_edited": "此修订版本上次编辑于 {{date}}", "confirm_delete_all": "您是否要删除此笔记的所有修订版本?", "no_revisions": "此笔记暂无修订版本...", "restore_button": "恢复", @@ -1288,10 +1289,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI 是一个 REST API,用于以编程方式访问 Trilium 实例,而无需 UI。", - "see_more": "有关更多详细信息,请参见 {{- link_to_wiki}} 和 {{- link_to_openapi_spec}} 或 {{- link_to_swagger_ui}}。", - "wiki": "维基", - "openapi_spec": "ETAPI OpenAPI 规范", - "swagger_ui": "ETAPI Swagger UI", "create_token": "创建新的 ETAPI 令牌", "existing_tokens": "现有令牌", "no_tokens_yet": "目前还没有令牌。点击上面的按钮创建一个。", @@ -1558,7 +1555,9 @@ "window-on-top": "保持此窗口置顶" }, "note_detail": { - "could_not_find_typewidget": "找不到类型为 '{{type}}' 的 typeWidget" + "could_not_find_typewidget": "找不到类型为 '{{type}}' 的 typeWidget", + "printing": "正在打印…", + "printing_pdf": "正在导出为PDF…" }, "note_title": { "placeholder": "请输入笔记标题..." diff --git a/apps/client/src/translations/de/translation.json b/apps/client/src/translations/de/translation.json index ca0e40d69..6ff9525ff 100644 --- a/apps/client/src/translations/de/translation.json +++ b/apps/client/src/translations/de/translation.json @@ -4,7 +4,7 @@ "homepage": "Startseite:", "app_version": "App-Version:", "db_version": "DB-Version:", - "sync_version": "Synch-version:", + "sync_version": "Sync-Version:", "build_date": "Build-Datum:", "build_revision": "Build-Revision:", "data_directory": "Datenverzeichnis:" @@ -104,7 +104,8 @@ "export_status": "Exportstatus", "export_in_progress": "Export läuft: {{progressCount}}", "export_finished_successfully": "Der Export wurde erfolgreich abgeschlossen.", - "format_pdf": "PDF - für Ausdrucke oder Teilen." + "format_pdf": "PDF - für Ausdrucke oder Teilen.", + "share-format": "HTML für die Web-Veröffentlichung – verwendet dasselbe Theme wie bei freigegebenen Notizen, kann jedoch als statische Website veröffentlicht werden." }, "help": { "noteNavigation": "Notiz Navigation", @@ -184,7 +185,8 @@ }, "import-status": "Importstatus", "in-progress": "Import läuft: {{progress}}", - "successful": "Import erfolgreich abgeschlossen." + "successful": "Import erfolgreich abgeschlossen.", + "importZipRecommendation": "Beim Import einer ZIP-Datei wird die Notizhierarchie aus der Ordnerstruktur im Archiv übernommen." }, "include_note": { "dialog_title": "Notiz beifügen", @@ -259,7 +261,6 @@ "delete_all_revisions": "Lösche alle Revisionen dieser Notiz", "delete_all_button": "Alle Revisionen löschen", "help_title": "Hilfe zu Notizrevisionen", - "revision_last_edited": "Diese Revision wurde zuletzt am {{date}} bearbeitet", "confirm_delete_all": "Möchtest du alle Revisionen dieser Notiz löschen?", "no_revisions": "Für diese Notiz gibt es noch keine Revisionen...", "confirm_restore": "Möchtest du diese Revision wiederherstellen? Dadurch werden der aktuelle Titel und Inhalt der Notiz mit dieser Revision überschrieben.", @@ -647,7 +648,8 @@ "logout": "Abmelden", "show-cheatsheet": "Cheatsheet anzeigen", "toggle-zen-mode": "Zen Modus", - "new-version-available": "Neues Update verfügbar" + "new-version-available": "Neues Update verfügbar", + "download-update": "Version {{latestVersion}} herunterladen" }, "sync_status": { "unknown": "

Der Synchronisations-Status wird bekannt, sobald der nächste Synchronisierungsversuch gestartet wird.

Klicke, um eine Synchronisierung jetzt auszulösen.

", @@ -989,7 +991,7 @@ "enter_password_instruction": "Um die geschützte Notiz anzuzeigen, musst du dein Passwort eingeben:", "start_session_button": "Starte eine geschützte Sitzung Eingabetaste", "started": "Geschützte Sitzung gestartet.", - "wrong_password": "Passwort flasch.", + "wrong_password": "Passwort falsch.", "protecting-finished-successfully": "Geschützt erfolgreich beendet.", "unprotecting-finished-successfully": "Ungeschützt erfolgreich beendet.", "protecting-in-progress": "Schützen läuft: {{count}}", @@ -1284,10 +1286,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI ist eine REST-API, die für den programmgesteuerten Zugriff auf die Trilium-Instanz ohne Benutzeroberfläche verwendet wird.", - "see_more": "Weitere Details können im {{- link_to_wiki}} und in der {{- link_to_openapi_spec}} oder der {{- link_to_swagger_ui }} gefunden werden.", - "wiki": "Wiki", - "openapi_spec": "ETAPI OpenAPI-Spezifikation", - "swagger_ui": "ETAPI Swagger UI", "create_token": "Erstelle ein neues ETAPI-Token", "existing_tokens": "Vorhandene Token", "no_tokens_yet": "Es sind noch keine Token vorhanden. Klicke auf die Schaltfläche oben, um eine zu erstellen.", @@ -1521,7 +1519,9 @@ "window-on-top": "Dieses Fenster immer oben halten" }, "note_detail": { - "could_not_find_typewidget": "Konnte typeWidget für Typ ‚{{type}}‘ nicht finden" + "could_not_find_typewidget": "Konnte typeWidget für Typ ‚{{type}}‘ nicht finden", + "printing": "Druckvorgang läuft…", + "printing_pdf": "PDF-Export läuft…" }, "note_title": { "placeholder": "Titel der Notiz hier eingeben…" @@ -1654,7 +1654,7 @@ "add-term-to-dictionary": "Begriff \"{{term}}\" zum Wörterbuch hinzufügen", "cut": "Ausschneiden", "copy": "Kopieren", - "copy-link": "Link opieren", + "copy-link": "Link kopieren", "paste": "Einfügen", "paste-as-plain-text": "Als unformatierten Text einfügen", "search_online": "Suche nach \"{{term}}\" mit {{searchEngine}} starten" @@ -2079,6 +2079,7 @@ }, "presentation_view": { "edit-slide": "Folie bearbeiten", - "start-presentation": "Präsentation starten" + "start-presentation": "Präsentation starten", + "slide-overview": "Übersicht der Folien ein-/ausblenden" } } diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 1949d3357..9f203eab3 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -104,7 +104,8 @@ "export_status": "Export status", "export_in_progress": "Export in progress: {{progressCount}}", "export_finished_successfully": "Export finished successfully.", - "format_pdf": "PDF - for printing or sharing purposes." + "format_pdf": "PDF - for printing or sharing purposes.", + "share-format": "HTML for web publishing - uses the same theme that is used shared notes, but can be published as a static website." }, "help": { "title": "Cheatsheet", @@ -260,7 +261,6 @@ "delete_all_revisions": "Delete all revisions of this note", "delete_all_button": "Delete all revisions", "help_title": "Help on Note Revisions", - "revision_last_edited": "This revision was last edited on {{date}}", "confirm_delete_all": "Do you want to delete all revisions of this note?", "no_revisions": "No revisions for this note yet...", "restore_button": "Restore", @@ -1453,10 +1453,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI is a REST API used to access Trilium instance programmatically, without UI.", - "see_more": "See more details in the {{- link_to_wiki}} and the {{- link_to_openapi_spec}} or the {{- link_to_swagger_ui }}.", - "wiki": "wiki", - "openapi_spec": "ETAPI OpenAPI spec", - "swagger_ui": "ETAPI Swagger UI", "create_token": "Create new ETAPI token", "existing_tokens": "Existing tokens", "no_tokens_yet": "There are no tokens yet. Click on the button above to create one.", diff --git a/apps/client/src/translations/es/translation.json b/apps/client/src/translations/es/translation.json index e07ce8036..b83fcf7bb 100644 --- a/apps/client/src/translations/es/translation.json +++ b/apps/client/src/translations/es/translation.json @@ -104,7 +104,8 @@ "export_status": "Estado de exportación", "export_in_progress": "Exportación en curso: {{progressCount}}", "export_finished_successfully": "La exportación finalizó exitosamente.", - "format_pdf": "PDF - para propósitos de impresión o compartición." + "format_pdf": "PDF - para propósitos de impresión o compartición.", + "share-format": "HTML para publicación web: utiliza el mismo tema que se utiliza en las notas compartidas, pero se puede publicar como un sitio web estático." }, "help": { "noteNavigation": "Navegación de notas", @@ -184,7 +185,8 @@ }, "import-status": "Estado de importación", "in-progress": "Importación en progreso: {{progress}}", - "successful": "Importación finalizada exitosamente." + "successful": "Importación finalizada exitosamente.", + "importZipRecommendation": "Al importar un archivo ZIP, la jerarquía de notas reflejará la estructura de subdirectorios dentro del archivo comprimido." }, "include_note": { "dialog_title": "Incluir nota", @@ -259,7 +261,6 @@ "delete_all_revisions": "Eliminar todas las revisiones de esta nota", "delete_all_button": "Eliminar todas las revisiones", "help_title": "Ayuda sobre revisiones de notas", - "revision_last_edited": "Esta revisión se editó por última vez en {{date}}", "confirm_delete_all": "¿Quiere eliminar todas las revisiones de esta nota?", "no_revisions": "Aún no hay revisiones para esta nota...", "restore_button": "Restaurar", @@ -1445,10 +1446,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI es una REST API que se utiliza para acceder a la instancia de Trilium mediante programación, sin interfaz de usuario.", - "see_more": "Véa más detalles en el {{- link_to_wiki}} y el {{- link_to_openapi_spec}} o el {{- link_to_swagger_ui }}.", - "wiki": "wiki", - "openapi_spec": "Especificación ETAPI OpenAPI", - "swagger_ui": "ETAPI Swagger UI", "create_token": "Crear nuevo token ETAPI", "existing_tokens": "Tokens existentes", "no_tokens_yet": "Aún no hay tokens. Dé clic en el botón de arriba para crear uno.", @@ -1715,7 +1712,9 @@ "window-on-top": "Mantener esta ventana en la parte superior" }, "note_detail": { - "could_not_find_typewidget": "No se pudo encontrar typeWidget para el tipo '{{type}}'" + "could_not_find_typewidget": "No se pudo encontrar typeWidget para el tipo '{{type}}'", + "printing": "Impresión en curso...", + "printing_pdf": "Exportando a PDF en curso.." }, "note_title": { "placeholder": "escriba el título de la nota aquí..." diff --git a/apps/client/src/translations/fr/translation.json b/apps/client/src/translations/fr/translation.json index 926307d18..1a4a1dec8 100644 --- a/apps/client/src/translations/fr/translation.json +++ b/apps/client/src/translations/fr/translation.json @@ -260,7 +260,6 @@ "delete_all_revisions": "Supprimer toutes les versions de cette note", "delete_all_button": "Supprimer toutes les versions", "help_title": "Aide sur les versions de notes", - "revision_last_edited": "Cette version a été modifiée pour la dernière fois le {{date}}", "confirm_delete_all": "Voulez-vous supprimer toutes les versions de cette note ?", "no_revisions": "Aucune version pour cette note pour l'instant...", "confirm_restore": "Voulez-vous restaurer cette version ? Le titre et le contenu actuels de la note seront écrasés par cette version.", @@ -1289,8 +1288,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI est une API REST utilisée pour accéder à l'instance Trilium par programme, sans interface utilisateur.", - "wiki": "wiki", - "openapi_spec": "Spec ETAPI OpenAPI", "create_token": "Créer un nouveau jeton ETAPI", "existing_tokens": "Jetons existants", "no_tokens_yet": "Il n'y a pas encore de jetons. Cliquez sur le bouton ci-dessus pour en créer un.", @@ -1307,9 +1304,7 @@ "delete_token": "Supprimer/désactiver ce token", "rename_token_title": "Renommer le jeton", "rename_token_message": "Veuillez saisir le nom du nouveau jeton", - "delete_token_confirmation": "Êtes-vous sûr de vouloir supprimer le jeton ETAPI « {{name}} » ?", - "see_more": "Voir plus de détails dans le {{- link_to_wiki}} et le {{- link_to_openapi_spec}} ou le {{- link_to_swagger_ui }}.", - "swagger_ui": "Interface utilisateur ETAPI Swagger" + "delete_token_confirmation": "Êtes-vous sûr de vouloir supprimer le jeton ETAPI « {{name}} » ?" }, "options_widget": { "options_status": "Statut des options", diff --git a/apps/client/src/translations/hi/translation.json b/apps/client/src/translations/hi/translation.json new file mode 100644 index 000000000..2d1c3b9f1 --- /dev/null +++ b/apps/client/src/translations/hi/translation.json @@ -0,0 +1,5 @@ +{ + "about": { + "title": "ट्रिलियम नोट्स के बारें में" + } +} diff --git a/apps/client/src/translations/hu/translation.json b/apps/client/src/translations/hu/translation.json index 0967ef424..042fbb281 100644 --- a/apps/client/src/translations/hu/translation.json +++ b/apps/client/src/translations/hu/translation.json @@ -1 +1,50 @@ -{} +{ + "about": { + "title": "A Trilium Notes-ról", + "homepage": "Kezdőlap:", + "app_version": "Alkalmazás verziója:", + "db_version": "Adatbázis verzió:", + "sync_version": "Verzió szinkronizálás :", + "build_revision": "Build revízió:", + "data_directory": "Adatkönyvtár:", + "build_date": "Build dátum:" + }, + "toast": { + "critical-error": { + "title": "Kritikus hiba", + "message": "Kritikus hiba történt, amely megakadályozza a kliensalkalmazás indítását:\n\n{{message}}\n\nEzt valószínűleg egy váratlan szkripthiba okozza. Próbálja meg biztonságos módban elindítani az alkalmazást, és hárítsa el a problémát." + }, + "widget-error": { + "title": "Nem sikerült inicializálni egy widgetet", + "message-custom": "A(z) \"{{id}}\" azonosítójú, \"{{title}}\" című jegyzetből származó egyéni widget inicializálása sikertelen volt a következő ok miatt:\n\n{{message}}", + "message-unknown": "Ismeretlen widget inicializálása sikertelen volt a következő ok miatt:\n\n{{message}}" + }, + "bundle-error": { + "title": "Nem sikerült betölteni az egyéni szkriptet", + "message": "A(z) \"{{id}}\" azonosítójú, \"{{title}}\" című jegyzetből származó szkript nem hajtható végre a következő ok miatt:\n\n{{message}}" + } + }, + "add_link": { + "add_link": "Link hozzáadása", + "help_on_links": "Segítség a linkekhez", + "note": "Jegyzet", + "search_note": "név szerinti jegyzetkeresés", + "link_title_mirrors": "A link cím tükrözi a jegyzet aktuális címét", + "link_title_arbitrary": "link cím önkényesen módosítható", + "link_title": "Link cím", + "button_add_link": "Link hozzáadása" + }, + "branch_prefix": { + "edit_branch_prefix": "Az elágazás előtagjának szerkesztése", + "help_on_tree_prefix": "Segítség a fa előtagján", + "prefix": "Az előtag: ", + "save": "Mentés" + }, + "bulk_actions": { + "bulk_actions": "Tömeges akciók", + "affected_notes": "Érintett jegyzetek", + "labels": "Címkék", + "relations": "Kapcsolatok", + "notes": "Jegyzetek" + } +} diff --git a/apps/client/src/translations/it/translation.json b/apps/client/src/translations/it/translation.json index a49a9a5dc..5105b13f3 100644 --- a/apps/client/src/translations/it/translation.json +++ b/apps/client/src/translations/it/translation.json @@ -132,10 +132,6 @@ "new_token_message": "Inserisci il nome del nuovo token", "title": "ETAPI", "description": "ETAPI è un'API REST utilizzata per accedere alle istanze di Trilium in modo programmatico, senza interfaccia utente.", - "see_more": "Per maggiori dettagli consulta {{- link_to_wiki}} e {{- link_to_openapi_spec}} o {{- link_to_swagger_ui}}.", - "wiki": "wiki", - "openapi_spec": "Specifiche ETAPI OpenAPI", - "swagger_ui": "Interfaccia utente ETAPI Swagger", "create_token": "Crea un nuovo token ETAPI", "existing_tokens": "Token esistenti", "no_tokens_yet": "Non ci sono ancora token. Clicca sul pulsante qui sopra per crearne uno.", @@ -867,7 +863,6 @@ "delete_all_revisions": "Elimina tutte le revisioni di questa nota", "delete_all_button": "Elimina tutte le revisioni", "help_title": "Aiuto sulle revisioni delle note", - "revision_last_edited": "Questa revisione è stata modificata l'ultima volta il {{date}}", "confirm_delete_all": "Vuoi eliminare tutte le revisioni di questa nota?", "no_revisions": "Ancora nessuna revisione per questa nota...", "restore_button": "Ripristina", diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 1f81a4a4f..6a9b4f75e 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -254,7 +254,8 @@ "export_status": "エクスポート状況", "export_in_progress": "エクスポート処理中: {{progressCount}}", "export_finished_successfully": "エクスポートが正常に完了しました。", - "format_pdf": "PDF - 印刷または共有目的に。" + "format_pdf": "PDF - 印刷または共有目的に。", + "share-format": "Web 公開用の HTML - 共有ノートで使用されるのと同じテーマを使用しますが、静的 Web サイトとして公開できます。" }, "help": { "title": "チートシート", @@ -610,7 +611,6 @@ "delete_all_revisions": "このノートの変更履歴をすべて削除", "delete_all_button": "変更履歴をすべて削除", "help_title": "変更履歴のヘルプ", - "revision_last_edited": "この変更は{{date}}に行われました", "confirm_delete_all": "このノートのすべての変更履歴を削除しますか?", "no_revisions": "このノートに変更履歴はまだありません...", "restore_button": "復元", @@ -657,10 +657,6 @@ "created": "作成日時", "title": "ETAPI", "description": "ETAPI は、Trilium インスタンスに UI なしでプログラム的にアクセスするための REST API です。", - "see_more": "詳細は{{- link_to_wiki}}と{{- link_to_openapi_spec}}または{{- link_to_swagger_ui }}を参照してください。", - "wiki": "wiki", - "openapi_spec": "ETAPI OpenAPIの仕様", - "swagger_ui": "ETAPI Swagger UI", "create_token": "新しくETAPIトークンを作成", "existing_tokens": "既存のトークン", "no_tokens_yet": "トークンはまだありません。上のボタンをクリックして作成してください。", diff --git a/apps/website/public/translations/ca/translation.json b/apps/client/src/translations/mr/translation.json similarity index 100% rename from apps/website/public/translations/ca/translation.json rename to apps/client/src/translations/mr/translation.json diff --git a/apps/client/src/translations/nl/translation.json b/apps/client/src/translations/nl/translation.json index d07df69e5..de5cfb6c7 100644 --- a/apps/client/src/translations/nl/translation.json +++ b/apps/client/src/translations/nl/translation.json @@ -13,6 +13,13 @@ "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." + }, + "widget-error": { + "title": "Starten widget mislukt", + "message-unknown": "Onbekende widget kan niet gestart worden omdat:\n\n{{message}}" + }, + "bundle-error": { + "title": "Custom script laden mislukt" } }, "add_link": { diff --git a/apps/client/src/translations/pl/translation.json b/apps/client/src/translations/pl/translation.json index 1d90a30e8..6ff4b26f4 100644 --- a/apps/client/src/translations/pl/translation.json +++ b/apps/client/src/translations/pl/translation.json @@ -912,7 +912,6 @@ "delete_all_revisions": "Usuń wszystkie wersje tej notatki", "delete_all_button": "Usuń wszystkie wersje", "help_title": "Pomoc dotycząca wersji notatki", - "revision_last_edited": "Ta wersja była ostatnio edytowana {{date}}", "confirm_delete_all": "Czy chcesz usunąć wszystkie wersje tej notatki?", "no_revisions": "Brak wersji dla tej notatki...", "restore_button": "Przywróć", @@ -1664,10 +1663,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI to interfejs API REST używany do programowego dostępu do instancji Trilium, bez interfejsu użytkownika.", - "see_more": "Zobacz więcej szczegółów w {{- link_to_wiki}} oraz w {{- link_to_openapi_spec}} lub {{- link_to_swagger_ui }}.", - "wiki": "wiki", - "openapi_spec": "specyfikacja ETAPI OpenAPI", - "swagger_ui": "ETAPI Swagger UI", "create_token": "Utwórz nowy token ETAPI", "existing_tokens": "Istniejące tokeny", "no_tokens_yet": "Nie ma jeszcze żadnych tokenów. Kliknij przycisk powyżej, aby utworzyć jeden.", diff --git a/apps/client/src/translations/pt/translation.json b/apps/client/src/translations/pt/translation.json index b4b1366bb..5497ad817 100644 --- a/apps/client/src/translations/pt/translation.json +++ b/apps/client/src/translations/pt/translation.json @@ -259,7 +259,6 @@ "delete_all_revisions": "Apagar todas as versões desta nota", "delete_all_button": "Apagar todas as versões", "help_title": "Ajuda sobre as versões da nota", - "revision_last_edited": "Esta versão foi editada pela última vez em {{date}}", "confirm_delete_all": "Quer apagar todas as versões desta nota?", "no_revisions": "Ainda não há versões para esta nota...", "restore_button": "Recuperar", @@ -1423,10 +1422,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI é uma API REST usada para aceder a instância do Trilium programaticamente, sem interface gráfica.", - "see_more": "Veja mais pormenores no {{- link_to_wiki}}, na {{- link_to_openapi_spec}} ou na {{- link_to_swagger_ui}}.", - "wiki": "wiki", - "openapi_spec": "Especificação OpenAPI do ETAPI", - "swagger_ui": "ETAPI Swagger UI", "create_token": "Criar token ETAPI", "existing_tokens": "Tokens existentes", "no_tokens_yet": "Ainda não existem tokens. Clique no botão acima para criar um.", diff --git a/apps/client/src/translations/pt_br/translation.json b/apps/client/src/translations/pt_br/translation.json index 7c51d7c2a..0d1676832 100644 --- a/apps/client/src/translations/pt_br/translation.json +++ b/apps/client/src/translations/pt_br/translation.json @@ -415,7 +415,6 @@ "delete_all_revisions": "Excluir todas as versões desta nota", "delete_all_button": "Excluir todas as versões", "help_title": "Ajuda sobre as versões da nota", - "revision_last_edited": "Esta versão foi editada pela última vez em {{date}}", "confirm_delete_all": "Você quer excluir todas as versões desta nota?", "no_revisions": "Ainda não há versões para esta nota...", "restore_button": "Recuperar", @@ -1933,10 +1932,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI é uma API REST usada para acessar a instância do Trilium programaticamente, sem interface gráfica.", - "see_more": "Veja mais detalhes no {{- link_to_wiki}}, na {{- link_to_openapi_spec}} ou na {{- link_to_swagger_ui}}.", - "wiki": "wiki", - "openapi_spec": "Especificação OpenAPI do ETAPI", - "swagger_ui": "ETAPI Swagger UI", "create_token": "Criar novo token ETAPI", "existing_tokens": "Tokens existentes", "no_tokens_yet": "Ainda não existem tokens. Clique no botão acima para criar um.", diff --git a/apps/client/src/translations/ro/translation.json b/apps/client/src/translations/ro/translation.json index f35d2ce89..dd8947003 100644 --- a/apps/client/src/translations/ro/translation.json +++ b/apps/client/src/translations/ro/translation.json @@ -507,17 +507,13 @@ "new_token_message": "Introduceți denumirea noului token", "new_token_title": "Token ETAPI nou", "no_tokens_yet": "Nu există încă token-uri. Clic pe butonul de deasupra pentru a crea una.", - "openapi_spec": "Specificația OpenAPI pentru ETAPI", - "swagger_ui": "UI-ul Swagger pentru ETAPI", "rename_token": "Redenumește token-ul", "rename_token_message": "Introduceți denumirea noului token", "rename_token_title": "Redenumire token", - "see_more": "Vedeți mai multe detalii în {{- link_to_wiki}} și în {{- link_to_openapi_spec}} sau în {{- link_to_swagger_ui }}.", "title": "ETAPI", "token_created_message": "Copiați token-ul creat în clipboard. Trilium stochează token-ul ca hash așadar această valoare poate fi văzută doar acum.", "token_created_title": "Token ETAPI creat", - "token_name": "Denumire token", - "wiki": "wiki" + "token_name": "Denumire token" }, "execute_script": { "example_1": "De exemplu, pentru a adăuga un șir de caractere la titlul unei notițe, se poate folosi acest mic script:", @@ -1090,7 +1086,6 @@ "preview_not_available": "Nu este disponibilă o previzualizare pentru acest tip de notiță.", "restore_button": "Restaurează", "revision_deleted": "Revizia notiței a fost ștearsă.", - "revision_last_edited": "Revizia a fost ultima oară modificată pe {{date}}", "revision_restored": "Revizia notiței a fost restaurată.", "revisions_deleted": "Notița reviziei a fost ștearsă.", "maximum_revisions": "Numărul maxim de revizii pentru notița curentă: {{number}}.", diff --git a/apps/client/src/translations/ru/translation.json b/apps/client/src/translations/ru/translation.json index 2dc068850..adc2bab50 100644 --- a/apps/client/src/translations/ru/translation.json +++ b/apps/client/src/translations/ru/translation.json @@ -320,7 +320,8 @@ "explodeArchivesTooltip": "Если этот флажок установлен, Trilium будет читать файлы .zip, .enex и .opml и создавать заметки из файлов внутри этих архивов. Если флажок не установлен, Trilium будет прикреплять сами архивы к заметке.", "explodeArchives": "Прочитать содержимое архивов .zip, .enex и .opml.", "shrinkImagesTooltip": "

Если этот параметр включен, Trilium попытается уменьшить размер импортируемых изображений путём масштабирования и оптимизации, что может повлиять на воспринимаемое качество изображения. Если этот параметр не установлен, изображения будут импортированы без изменений.

Это не относится к импорту файлов .zip с метаданными, поскольку предполагается, что эти файлы уже оптимизированы.

", - "codeImportedAsCode": "Импортировать распознанные файлы кода (например, .json) в виде заметок типа \"код\", если это неясно из метаданных" + "codeImportedAsCode": "Импортировать распознанные файлы кода (например, .json) в виде заметок типа \"код\", если это неясно из метаданных", + "importZipRecommendation": "При импорте ZIP файла иерархия заметок будет отражена в структуре папок внутри архива." }, "markdown_import": { "dialog_title": "Импорт Markdown", @@ -365,7 +366,6 @@ "delete_all_button": "Удалить все версии", "help_title": "Помощь по версиям заметок", "confirm_delete_all": "Вы хотите удалить все версии этой заметки?", - "revision_last_edited": "Эта версия последний раз редактировалась {{date}}", "confirm_restore": "Хотите восстановить эту версию? Текущее название и содержание заметки будут перезаписаны этой версией.", "confirm_delete": "Вы хотите удалить эту версию?", "revisions_deleted": "Версии заметки были удалены.", @@ -980,7 +980,8 @@ "open_sql_console_history": "Открыть историю консоли SQL", "show_shared_notes_subtree": "Поддерево общедоступных заметок", "switch_to_mobile_version": "Перейти на мобильную версию", - "switch_to_desktop_version": "Переключиться на версию для ПК" + "switch_to_desktop_version": "Переключиться на версию для ПК", + "new-version-available": "Доступно обновление" }, "zpetne_odkazy": { "backlink": "{{count}} ссылки", @@ -1439,7 +1440,6 @@ }, "etapi": { "title": "ETAPI", - "wiki": "вики", "created": "Создано", "actions": "Действия", "existing_tokens": "Существующие токены", @@ -1447,10 +1447,7 @@ "default_token_name": "новый токен", "rename_token_title": "Переименовать токен", "description": "ETAPI — это REST API, используемый для программного доступа к экземпляру Trilium без пользовательского интерфейса.", - "see_more": "Более подробную информацию смотрите в {{- link_to_wiki}} и {{- link_to_openapi_spec}} или {{- link_to_swagger_ui }}.", "create_token": "Создать новый токен ETAPI", - "openapi_spec": "Спецификация ETAPI OpenAPI", - "swagger_ui": "Пользовательский интерфейс ETAPI Swagger", "new_token_title": "Новый токен ETAPI", "token_created_title": "Создан токен ETAPI", "rename_token": "Переименовать этот токен", diff --git a/apps/client/src/translations/sr/translation.json b/apps/client/src/translations/sr/translation.json index dd0ff6ff9..df88fdcda 100644 --- a/apps/client/src/translations/sr/translation.json +++ b/apps/client/src/translations/sr/translation.json @@ -256,7 +256,6 @@ "delete_all_revisions": "Obriši sve revizije ove beleške", "delete_all_button": "Obriši sve revizije", "help_title": "Pomoć za Revizije beleški", - "revision_last_edited": "Ova revizija je poslednji put izmenjena {{date}}", "confirm_delete_all": "Da li želite da obrišete sve revizije ove beleške?", "no_revisions": "Još uvek nema revizija za ovu belešku...", "restore_button": "Vrati", diff --git a/apps/client/src/translations/tw/translation.json b/apps/client/src/translations/tw/translation.json index ea0e021e5..2ba0f9afd 100644 --- a/apps/client/src/translations/tw/translation.json +++ b/apps/client/src/translations/tw/translation.json @@ -104,7 +104,8 @@ "export_in_progress": "正在匯出:{{progressCount}}", "export_finished_successfully": "成功匯出。", "format_html": "HTML - 推薦,因為它保留了所有格式", - "format_pdf": "PDF - 用於列印或與他人分享。" + "format_pdf": "PDF - 用於列印或與他人分享。", + "share-format": "HTML 網頁發佈——使用與共享筆記相同的佈景主題,但可發佈為靜態網站。" }, "help": { "noteNavigation": "筆記導航", @@ -260,7 +261,6 @@ "delete_all_revisions": "刪除此筆記的所有歷史版本", "delete_all_button": "刪除所有歷史版本", "help_title": "關於筆記歷史版本的說明", - "revision_last_edited": "此歷史版本上次於 {{date}} 編輯", "confirm_delete_all": "您是否要刪除此筆記的所有歷史版本?", "no_revisions": "此筆記暫無歷史版本…", "confirm_restore": "您是否要還原此歷史版本?這將使用此歷史版本覆寫筆記的目前標題和內容。", @@ -1281,8 +1281,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI 是一個 REST API,用於以編程方式訪問 Trilium 實例,而無需 UI。", - "wiki": "維基", - "openapi_spec": "ETAPI OpenAPI 規範", "create_token": "新增 ETAPI 令牌", "existing_tokens": "現有令牌", "no_tokens_yet": "目前還沒有令牌。點擊上面的按鈕新增一個。", @@ -1299,9 +1297,7 @@ "delete_token": "刪除 / 停用此令牌", "rename_token_title": "重新命名令牌", "rename_token_message": "請輸入新的令牌名稱", - "delete_token_confirmation": "您確定要刪除 ETAPI 令牌 \"{{name}}\" 嗎?", - "see_more": "有關更多詳細資訊,請參閱 {{- link_to_wiki}} 和 {{- link_to_openapi_spec}} 或 {{- link_to_swagger_ui}}。", - "swagger_ui": "ETAPI Swagger UI" + "delete_token_confirmation": "您確定要刪除 ETAPI 令牌 \"{{name}}\" 嗎?" }, "options_widget": { "options_status": "選項狀態", diff --git a/apps/client/src/translations/uk/translation.json b/apps/client/src/translations/uk/translation.json index b35c6c826..ebddac41b 100644 --- a/apps/client/src/translations/uk/translation.json +++ b/apps/client/src/translations/uk/translation.json @@ -309,7 +309,6 @@ "delete_all_revisions": "Видалити всі версії цієї нотатки", "delete_all_button": "Видалити всі версії", "help_title": "Довідка щодо Версій нотаток", - "revision_last_edited": "Цю версію востаннє редагували {{date}}", "confirm_delete_all": "Ви хочете видалити всі версії цієї нотатки?", "no_revisions": "Поки що немає версій цієї нотатки...", "restore_button": "Відновити", @@ -1403,10 +1402,6 @@ "etapi": { "title": "ETAPI", "description": "ETAPI — це REST API, який використовується для програмного доступу до екземпляра Trilium без інтерфейсу користувача.", - "see_more": "Див. докладнішу інформацію у {{- link_to_wiki}} та {{- link_to_openapi_spec}} або {{- link_to_swagger_ui }}.", - "wiki": "вікі", - "openapi_spec": "ETAPI OpenAPI spec", - "swagger_ui": "ETAPI Swagger UI", "create_token": "Створити новий токен ETAPI", "existing_tokens": "Існуючі токени", "no_tokens_yet": "Токенів поки що немає. Натисніть кнопку вище, щоб створити його.", diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts index c5a93bd0a..d283983b4 100644 --- a/apps/client/src/types.d.ts +++ b/apps/client/src/types.d.ts @@ -26,7 +26,6 @@ interface CustomGlobals { appContext: AppContext; froca: Froca; treeCache: Froca; - importMarkdownInline: () => Promise; SEARCH_HELP_TEXT: string; activeDialog: JQuery | null; componentId: string; diff --git a/apps/client/src/widgets/dialogs/export.tsx b/apps/client/src/widgets/dialogs/export.tsx index dded32624..b694d9abe 100644 --- a/apps/client/src/widgets/dialogs/export.tsx +++ b/apps/client/src/widgets/dialogs/export.tsx @@ -79,6 +79,7 @@ export default function ExportDialog() { values={[ { value: "html", label: t("export.format_html_zip") }, { value: "markdown", label: t("export.format_markdown") }, + { value: "share", label: t("export.share-format") }, { value: "opml", label: t("export.format_opml") } ]} /> diff --git a/apps/client/src/widgets/dialogs/markdown_import.tsx b/apps/client/src/widgets/dialogs/markdown_import.tsx index d14d6fb11..43b20d378 100644 --- a/apps/client/src/widgets/dialogs/markdown_import.tsx +++ b/apps/client/src/widgets/dialogs/markdown_import.tsx @@ -7,6 +7,7 @@ import utils from "../../services/utils"; import Modal from "../react/Modal"; import Button from "../react/Button"; import { useTriliumEvent } from "../react/hooks"; +import EditableTextTypeWidget from "../type_widgets/editable_text"; interface RenderMarkdownResponse { htmlContent: string; @@ -14,39 +15,34 @@ interface RenderMarkdownResponse { export default function MarkdownImportDialog() { const markdownImportTextArea = useRef(null); + const [textTypeWidget, setTextTypeWidget] = useState(); const [ text, setText ] = useState(""); const [ shown, setShown ] = useState(false); - const triggerImport = useCallback(() => { - if (appContext.tabManager.getActiveContextNoteType() !== "text") { - return; - } - + useTriliumEvent("showPasteMarkdownDialog", ({ textTypeWidget }) => { + setTextTypeWidget(textTypeWidget); if (utils.isElectron()) { const { clipboard } = utils.dynamicRequire("electron"); const text = clipboard.readText(); - convertMarkdownToHtml(text); + convertMarkdownToHtml(text, textTypeWidget); } else { setShown(true); } - }, []); - - useTriliumEvent("importMarkdownInline", triggerImport); - useTriliumEvent("pasteMarkdownIntoText", triggerImport); - - async function sendForm() { - await convertMarkdownToHtml(text); - setText(""); - setShown(false); - } + }); return ( } + footer={