Merge remote-tracking branch 'origin/develop' into feature/improved_promoted_attributes

; Conflicts:
;	src/public/app/layouts/desktop_layout.js
This commit is contained in:
Elian Doran
2024-11-12 20:03:54 +02:00
78 changed files with 3997 additions and 504 deletions

View File

@@ -129,6 +129,8 @@ jobs:
type=ref,event=branch type=ref,event=branch
type=ref,event=tag type=ref,event=tag
type=sha type=sha
flavor: |
latest=false
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@@ -213,6 +215,8 @@ jobs:
images: | images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }} ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }} ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=false
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@v2 uses: docker/login-action@v2
@@ -246,6 +250,7 @@ jobs:
# If the ref is a tag, also tag the image as stable as this is part of a 'release' # If the ref is a tag, also tag the image as stable as this is part of a 'release'
# and only go in the `if` if there is NOT a `-` in the tag's name, due to tagging of `-alpha`, `-beta`, etc... # and only go in the `if` if there is NOT a `-` in the tag's name, due to tagging of `-alpha`, `-beta`, etc...
if [[ "${GITHUB_REF}" == refs/tags/* && ! "${REF_NAME}" =~ - ]]; then if [[ "${GITHUB_REF}" == refs/tags/* && ! "${REF_NAME}" =~ - ]]; then
# First create stable tags
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \ -t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) $(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
@@ -253,6 +258,19 @@ jobs:
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \ -t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) $(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Small delay to ensure stable tag is fully propagated
sleep 5
# Now update latest tags
docker buildx imagetools create \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
fi fi
- name: Inspect image - name: Inspect image

View File

@@ -41,6 +41,8 @@ jobs:
run: npm ci run: npm ci
- name: Update build info - name: Update build info
run: npm run update-build-info run: npm run update-build-info
- name: Update nightly version
run: npm run ci-update-nightly-version
- name: Run electron-forge - name: Run electron-forge
run: npm run make-electron -- --arch=${{ matrix.arch }} run: npm run make-electron -- --arch=${{ matrix.arch }}
- name: Prepare artifacts (Unix) - name: Prepare artifacts (Unix)
@@ -103,6 +105,7 @@ jobs:
- name: Run Linux server build (x86_64) - name: Run Linux server build (x86_64)
run: | run: |
npm run update-build-info npm run update-build-info
npm run ci-update-nightly-version
./bin/build-server.sh ./bin/build-server.sh
- name: Prepare artifacts - name: Prepare artifacts
if: runner.os != 'windows' if: runner.os != 'windows'

View File

@@ -1,7 +1,7 @@
# !!! Don't try to build this Dockerfile directly, run it through bin/build-docker.sh script !!! # Build stage
FROM node:20.15.1-bullseye-slim FROM node:20.15.1-bullseye-slim AS builder
# Configure system dependencies # Configure build dependencies in a single layer
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \ autoconf \
automake \ automake \
@@ -12,49 +12,52 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
nasm \ nasm \
libpng-dev \ libpng-dev \
python3 \ python3 \
gosu \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Create app directory
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Bundle app source # Copy only necessary files for build
COPY . . COPY . .
COPY server-package.json package.json COPY server-package.json package.json
# Copy TypeScript build artifacts into the original directory structure. # Build and cleanup in a single layer
# Copy the healthcheck
RUN cp -R build/src/* src/. && \ RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \ cp build/docker_healthcheck.js . && \
rm -r build && \ rm -r build && \
rm docker_healthcheck.ts rm docker_healthcheck.ts && \
npm install && \
# Install app dependencies
RUN apt-get purge -y --auto-remove \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3 \
&& rm -rf /var/lib/apt/lists/*
RUN npm install && \
npm run webpack && \ npm run webpack && \
npm prune --omit=dev npm prune --omit=dev && \
RUN cp src/public/app/share.js src/public/app-dist/. && \ npm cache clean --force && \
cp src/public/app/share.js src/public/app-dist/. && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \ cp -r src/public/app/doc_notes src/public/app-dist/. && \
rm -rf src/public/app && rm src/services/asset_path.ts rm -rf src/public/app && \
rm src/services/asset_path.ts
# Some setup tools need to be kept # Runtime stage
FROM node:20.15.1-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
gosu \ gosu \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/* && \
rm -rf /var/cache/apt/*
# Start the application WORKDIR /usr/src/app
# Copy only necessary files from builder
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src
COPY --from=builder /usr/src/app/db ./db
COPY --from=builder /usr/src/app/docker_healthcheck.js .
COPY --from=builder /usr/src/app/start-docker.sh .
COPY --from=builder /usr/src/app/package.json .
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries
# Configure container
EXPOSE 8080 EXPOSE 8080
CMD [ "./start-docker.sh" ] CMD [ "./start-docker.sh" ]
HEALTHCHECK --start-period=10s CMD exec gosu node node docker_healthcheck.js HEALTHCHECK --start-period=10s CMD exec gosu node node docker_healthcheck.js

View File

@@ -1,7 +1,7 @@
# !!! Don't try to build this Dockerfile directly, run it through bin/build-docker.sh script !!! # Build stage
FROM node:20.15.1-alpine FROM node:20.15.1-alpine AS builder
# Configure system dependencies # Configure build dependencies
RUN apk add --no-cache --virtual .build-dependencies \ RUN apk add --no-cache --virtual .build-dependencies \
autoconf \ autoconf \
automake \ automake \
@@ -13,41 +13,50 @@ RUN apk add --no-cache --virtual .build-dependencies \
libpng-dev \ libpng-dev \
python3 python3
# Create app directory
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Bundle app source # Copy only necessary files for build
COPY . . COPY . .
COPY server-package.json package.json COPY server-package.json package.json
# Copy TypeScript build artifacts into the original directory structure. # Build and cleanup in a single layer
# Copy the healthcheck
RUN cp -R build/src/* src/. && \ RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \ cp build/docker_healthcheck.js . && \
rm -r build && \ rm -r build && \
rm docker_healthcheck.ts rm docker_healthcheck.ts && \
# Install app dependencies
RUN set -x && \
npm install && \ npm install && \
apk del .build-dependencies && \
npm run webpack && \ npm run webpack && \
npm prune --omit=dev && \ npm prune --omit=dev && \
npm cache clean --force && \
cp src/public/app/share.js src/public/app-dist/. && \ cp src/public/app/share.js src/public/app-dist/. && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \ cp -r src/public/app/doc_notes src/public/app-dist/. && \
rm -rf src/public/app && \ rm -rf src/public/app && \
rm src/services/asset_path.ts rm src/services/asset_path.ts
# Runtime stage
FROM node:20.15.1-alpine
# Some setup tools need to be kept # Install runtime dependencies
RUN apk add --no-cache su-exec shadow RUN apk add --no-cache su-exec shadow
# Add application user and setup proper volume permissions WORKDIR /usr/src/app
# Copy only necessary files from builder
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src
COPY --from=builder /usr/src/app/db ./db
COPY --from=builder /usr/src/app/docker_healthcheck.js .
COPY --from=builder /usr/src/app/start-docker.sh .
COPY --from=builder /usr/src/app/package.json .
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries
# Add application user
RUN adduser -s /bin/false node; exit 0 RUN adduser -s /bin/false node; exit 0
# Start the application # Configure container
EXPOSE 8080 EXPOSE 8080
CMD [ "./start-docker.sh" ] CMD [ "./start-docker.sh" ]
HEALTHCHECK --start-period=10s CMD exec su-exec node node docker_healthcheck.js HEALTHCHECK --start-period=10s CMD exec su-exec node node docker_healthcheck.js

View File

@@ -18,6 +18,8 @@ See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for q
There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Notes instance. Just upgrade your Trilium instance to the latest version and [install TriliumNext/Notes as usual](#-installation) There are no special migration steps to migrate from a zadam/Trilium instance to a TriliumNext/Notes instance. Just upgrade your Trilium instance to the latest version and [install TriliumNext/Notes as usual](#-installation)
Versions up to and including [v0.90.4](https://github.com/TriliumNext/Notes/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 have their sync versions incremented.
## 💬 Discuss with us ## 💬 Discuss with us
Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have! Feel free to join our official conversations. We would love to hear what features, suggestions, or issues you may have!
@@ -65,6 +67,16 @@ To use TriliumNext on your desktop machine (Linux, MacOS, and Windows) you have
* Currently only the latest versions of Chrome & Firefox are supported (and tested). * Currently only the latest versions of Chrome & Firefox are supported (and tested).
* (Coming Soon) TriliumNext will also be provided as a Flatpak * (Coming Soon) TriliumNext will also be provided as a Flatpak
#### MacOS
Currently when running TriliumNext/Notes on MacOS, you may get the following error:
> Apple could not verify "TriliumNext Notes" is free of malware and may harm your Mac or compromise your privacy.
You will need to run the command on your shell to resolve the error (documented [here](https://github.com/TriliumNext/Notes/issues/329#issuecomment-2287164137)):
```bash
xattr -c "/path/to/Trilium Next.app"
```
### Mobile ### Mobile
To use TriliumNext on a mobile device: To use TriliumNext on a mobile device:

View File

@@ -47,6 +47,15 @@ const copy = async () => {
await fs.copy(dir, path.join(DEST_DIR_SRC, path.basename(dir))); await fs.copy(dir, path.join(DEST_DIR_SRC, path.basename(dir)));
} }
/**
* Directories to be copied relative to the project root into <resource_dir>/src/public/app-dist.
*/
const publicDirsToCopy = [ "./src/public/app/doc_notes" ];
const PUBLIC_DIR = path.join(DEST_DIR, "src", "public", "app-dist");
for (const dir of publicDirsToCopy) {
await fs.copy(dir, path.join(PUBLIC_DIR, path.basename(dir)));
}
const nodeModulesFile = [ const nodeModulesFile = [
"node_modules/react/umd/react.production.min.js", "node_modules/react/umd/react.production.min.js",
"node_modules/react/umd/react.development.js", "node_modules/react/umd/react.development.js",
@@ -55,6 +64,7 @@ const copy = async () => {
"node_modules/katex/dist/katex.min.js", "node_modules/katex/dist/katex.min.js",
"node_modules/katex/dist/contrib/mhchem.min.js", "node_modules/katex/dist/contrib/mhchem.min.js",
"node_modules/katex/dist/contrib/auto-render.min.js", "node_modules/katex/dist/contrib/auto-render.min.js",
"node_modules/@highlightjs/cdn-assets/highlight.min.js"
]; ];
for (const file of nodeModulesFile) { for (const file of nodeModulesFile) {
@@ -89,7 +99,9 @@ const copy = async () => {
"node_modules/codemirror/addon/", "node_modules/codemirror/addon/",
"node_modules/codemirror/mode/", "node_modules/codemirror/mode/",
"node_modules/codemirror/keymap/", "node_modules/codemirror/keymap/",
"node_modules/mind-elixir/dist/" "node_modules/mind-elixir/dist/",
"node_modules/@highlightjs/cdn-assets/languages",
"node_modules/@highlightjs/cdn-assets/styles"
]; ];
for (const folder of nodeModulesFolder) { for (const folder of nodeModulesFolder) {

View File

@@ -0,0 +1,50 @@
/**
* @module
*
* The nightly version works uses the version described in `package.json`, just like any release.
* The problem with this approach is that production builds have a very aggressive cache, and
* usually running the nightly with this cached version of the application will mean that the
* user might run into module not found errors or styling errors caused by an old cache.
*
* This script is supposed to be run in the CI, which will update locally the version field of
* `package.json` to contain the date. For example, `0.90.9-beta` will become `0.90.9-test-YYMMDD-HHMMSS`.
*
*/
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import fs from "fs";
function processVersion(version) {
// Remove the beta suffix if any.
version = version.replace("-beta", "");
// Add the nightly suffix, plus the date.
const referenceDate = new Date()
.toISOString()
.substring(2, 19)
.replace(/[-:]*/g, "")
.replace("T", "-");
version = `${version}-test-${referenceDate}`;
return version;
}
function main() {
const scriptDir = dirname(fileURLToPath(import.meta.url));
const packageJsonPath = join(scriptDir, "..", "package.json");
// Read the version from package.json and process it.
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
const currentVersion = packageJson.version;
const adjustedVersion = processVersion(currentVersion);
console.log("Current version is", currentVersion);
console.log("Adjusted version is", adjustedVersion);
// Write the adjusted version back in.
packageJson.version = adjustedVersion;
const formattedJson = JSON.stringify(packageJson, null, 4);
fs.writeFileSync(packageJsonPath, formattedJson);
}
main();

Binary file not shown.

View File

@@ -15,7 +15,8 @@ module.exports = {
...getExtraResourcesForPlatform(), ...getExtraResourcesForPlatform(),
// Moved to resources (TriliumNext Notes.app/Contents/Resources on macOS) // Moved to resources (TriliumNext Notes.app/Contents/Resources on macOS)
"translations/" "translations/",
"node_modules/@highlightjs/cdn-assets/styles"
], ],
afterComplete: [(buildPath, _electronVersion, platform, _arch, callback) => { afterComplete: [(buildPath, _electronVersion, platform, _arch, callback) => {
const extraResources = getExtraResourcesForPlatform(); const extraResources = getExtraResourcesForPlatform();

View File

@@ -526,16 +526,19 @@
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */ /* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre { .ck-content pre {
padding: 1em; padding: 1em;
color: hsl(0, 0%, 20.8%);
background: hsla(0, 0%, 78%, 0.3);
border: 1px solid hsl(0, 0%, 77%);
border-radius: 2px;
text-align: left; text-align: left;
direction: ltr; direction: ltr;
tab-size: 4; tab-size: 4;
white-space: pre-wrap; white-space: pre-wrap;
font-style: normal; font-style: normal;
min-width: 200px; min-width: 200px;
border: 0px;
border-radius: 6px;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
}
.ck-content pre:not(.hljs) {
color: hsl(0, 0%, 20.8%);
background: hsla(0, 0%, 78%, 0.3);
} }
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */ /* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre code { .ck-content pre code {

49
libraries/ckeditor/ckeditor.d.ts vendored Normal file
View File

@@ -0,0 +1,49 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
import { DecoupledEditor as DecoupledEditorBase } from '@ckeditor/ckeditor5-editor-decoupled';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { Alignment } from '@ckeditor/ckeditor5-alignment';
import { FontSize, FontFamily, FontColor, FontBackgroundColor } from '@ckeditor/ckeditor5-font';
import { CKFinderUploadAdapter } from '@ckeditor/ckeditor5-adapter-ckfinder';
import { Autoformat } from '@ckeditor/ckeditor5-autoformat';
import { Bold, Italic, Strikethrough, Underline } from '@ckeditor/ckeditor5-basic-styles';
import { BlockQuote } from '@ckeditor/ckeditor5-block-quote';
import { CKBox } from '@ckeditor/ckeditor5-ckbox';
import { CKFinder } from '@ckeditor/ckeditor5-ckfinder';
import { EasyImage } from '@ckeditor/ckeditor5-easy-image';
import { Heading } from '@ckeditor/ckeditor5-heading';
import { Image, ImageCaption, ImageResize, ImageStyle, ImageToolbar, ImageUpload, PictureEditing } from '@ckeditor/ckeditor5-image';
import { Indent, IndentBlock } from '@ckeditor/ckeditor5-indent';
import { Link } from '@ckeditor/ckeditor5-link';
import { List, ListProperties } from '@ckeditor/ckeditor5-list';
import { MediaEmbed } from '@ckeditor/ckeditor5-media-embed';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
import { PasteFromOffice } from '@ckeditor/ckeditor5-paste-from-office';
import { Table, TableToolbar } from '@ckeditor/ckeditor5-table';
import { TextTransformation } from '@ckeditor/ckeditor5-typing';
import { CloudServices } from '@ckeditor/ckeditor5-cloud-services';
export default class DecoupledEditor extends DecoupledEditorBase {
static builtinPlugins: (typeof TextTransformation | typeof Essentials | typeof Alignment | typeof FontBackgroundColor | typeof FontColor | typeof FontFamily | typeof FontSize | typeof CKFinderUploadAdapter | typeof Paragraph | typeof Heading | typeof Autoformat | typeof Bold | typeof Italic | typeof Strikethrough | typeof Underline | typeof BlockQuote | typeof Image | typeof ImageCaption | typeof ImageResize | typeof ImageStyle | typeof ImageToolbar | typeof ImageUpload | typeof CloudServices | typeof CKBox | typeof CKFinder | typeof EasyImage | typeof List | typeof ListProperties | typeof Indent | typeof IndentBlock | typeof Link | typeof MediaEmbed | typeof PasteFromOffice | typeof Table | typeof TableToolbar | typeof PictureEditing)[];
static defaultConfig: {
toolbar: {
items: string[];
};
image: {
resizeUnit: "px";
toolbar: string[];
};
table: {
contentToolbar: string[];
};
list: {
properties: {
styles: boolean;
startIndex: boolean;
reversed: boolean;
};
};
language: string;
};
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

607
package-lock.json generated
View File

@@ -1,17 +1,18 @@
{ {
"name": "trilium", "name": "trilium",
"version": "0.90.9-beta", "version": "0.90.11-beta",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "trilium", "name": "trilium",
"version": "0.90.9-beta", "version": "0.90.11-beta",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "7.1.0", "@braintree/sanitize-url": "7.1.0",
"@electron/remote": "2.1.2", "@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "0.17.6", "@excalidraw/excalidraw": "0.17.6",
"@highlightjs/cdn-assets": "11.10.0",
"archiver": "7.0.1", "archiver": "7.0.1",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"autocomplete.js": "0.38.1", "autocomplete.js": "0.38.1",
@@ -34,19 +35,19 @@
"electron-squirrel-startup": "1.0.1", "electron-squirrel-startup": "1.0.1",
"electron-window-state": "5.0.3", "electron-window-state": "5.0.3",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"eslint": "9.10.0", "eslint": "9.14.0",
"express": "4.21.1", "express": "4.21.1",
"express-partial-content": "1.0.2", "express-partial-content": "1.0.2",
"express-rate-limit": "7.4.1", "express-rate-limit": "7.4.1",
"express-session": "1.18.1", "express-session": "1.18.1",
"force-graph": "1.45.0", "force-graph": "1.46.0",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
"helmet": "7.1.0", "helmet": "7.1.0",
"html": "1.0.0", "html": "1.0.0",
"html2plaintext": "2.1.4", "html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2", "http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.5", "https-proxy-agent": "7.0.5",
"i18next": "23.16.2", "i18next": "23.16.4",
"i18next-fs-backend": "2.3.2", "i18next-fs-backend": "2.3.2",
"i18next-http-backend": "2.6.2", "i18next-http-backend": "2.6.2",
"image-type": "4.1.0", "image-type": "4.1.0",
@@ -64,9 +65,9 @@
"knockout": "3.5.1", "knockout": "3.5.1",
"mark.js": "8.11.1", "mark.js": "8.11.1",
"marked": "14.1.3", "marked": "14.1.3",
"mermaid": "11.3.0", "mermaid": "11.4.0",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"mind-elixir": "4.2.3", "mind-elixir": "4.3.1",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
"node-abi": "3.67.0", "node-abi": "3.67.0",
"normalize-strings": "1.1.1", "normalize-strings": "1.1.1",
@@ -107,7 +108,7 @@
"@electron-forge/maker-squirrel": "7.5.0", "@electron-forge/maker-squirrel": "7.5.0",
"@electron-forge/maker-zip": "7.5.0", "@electron-forge/maker-zip": "7.5.0",
"@electron-forge/plugin-auto-unpack-natives": "7.5.0", "@electron-forge/plugin-auto-unpack-natives": "7.5.0",
"@playwright/test": "1.47.1", "@playwright/test": "1.48.2",
"@types/archiver": "6.0.2", "@types/archiver": "6.0.2",
"@types/better-sqlite3": "7.6.11", "@types/better-sqlite3": "7.6.11",
"@types/cls-hooked": "4.3.8", "@types/cls-hooked": "4.3.8",
@@ -152,10 +153,10 @@
"rcedit": "4.0.1", "rcedit": "4.0.1",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"ts-node": "10.9.2", "ts-node": "10.9.2",
"tslib": "2.8.0", "tslib": "2.8.1",
"tsx": "4.19.1", "tsx": "4.19.2",
"typescript": "5.6.3", "typescript": "5.6.3",
"webpack": "5.95.0", "webpack": "5.96.1",
"webpack-cli": "5.1.4" "webpack-cli": "5.1.4"
} }
}, },
@@ -2347,10 +2348,9 @@
} }
}, },
"node_modules/@eslint-community/regexpp": { "node_modules/@eslint-community/regexpp": {
"version": "4.11.0", "version": "4.12.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
"integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
"license": "MIT",
"engines": { "engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0" "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
} }
@@ -2368,6 +2368,14 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/core": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz",
"integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
@@ -2404,9 +2412,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.10.0", "version": "9.14.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.14.0.tgz",
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", "integrity": "sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
@@ -2420,9 +2428,9 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.1.0", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz",
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==",
"dependencies": { "dependencies": {
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
@@ -2445,6 +2453,46 @@
"integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
"dev": true "dev": true
}, },
"node_modules/@highlightjs/cdn-assets": {
"version": "11.10.0",
"resolved": "https://registry.npmjs.org/@highlightjs/cdn-assets/-/cdn-assets-11.10.0.tgz",
"integrity": "sha512-vWXpu+Rdm0YMJmugFdUiL/9DmgYjEiV+d5DBqlXdApnGPSIeo6+LRS5Hpx6fvVsKkvR4RsLYD9rQ6DOLkj7OKA==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node": {
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
"integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
"dependencies": {
"@humanfs/core": "^0.19.1",
"@humanwhocodes/retry": "^0.3.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/module-importer": { "node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -2459,10 +2507,9 @@
} }
}, },
"node_modules/@humanwhocodes/retry": { "node_modules/@humanwhocodes/retry": {
"version": "0.3.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.0.tgz",
"integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", "integrity": "sha512-xnRgu9DxZbkWak/te3fcytNyp8MTbuiZIaueg2rgEvBuN55n04nwLYLU9TX/VVlusc9L2ZNXi99nUFNkHXtr5g==",
"license": "Apache-2.0",
"engines": { "engines": {
"node": ">=18.18" "node": ">=18.18"
}, },
@@ -3042,6 +3089,7 @@
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.stat": "2.0.5", "@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9" "run-parallel": "^1.1.9"
@@ -3054,6 +3102,7 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"engines": { "engines": {
"node": ">= 8" "node": ">= 8"
} }
@@ -3062,6 +3111,7 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"dependencies": { "dependencies": {
"@nodelib/fs.scandir": "2.1.5", "@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0" "fastq": "^1.6.0"
@@ -3133,12 +3183,12 @@
} }
}, },
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.47.1", "version": "1.48.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.1.tgz", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.2.tgz",
"integrity": "sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==", "integrity": "sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"playwright": "1.47.1" "playwright": "1.48.2"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -3317,12 +3367,242 @@
"@types/express-serve-static-core": "*" "@types/express-serve-static-core": "*"
} }
}, },
"node_modules/@types/d3": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
"integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
"dependencies": {
"@types/d3-array": "*",
"@types/d3-axis": "*",
"@types/d3-brush": "*",
"@types/d3-chord": "*",
"@types/d3-color": "*",
"@types/d3-contour": "*",
"@types/d3-delaunay": "*",
"@types/d3-dispatch": "*",
"@types/d3-drag": "*",
"@types/d3-dsv": "*",
"@types/d3-ease": "*",
"@types/d3-fetch": "*",
"@types/d3-force": "*",
"@types/d3-format": "*",
"@types/d3-geo": "*",
"@types/d3-hierarchy": "*",
"@types/d3-interpolate": "*",
"@types/d3-path": "*",
"@types/d3-polygon": "*",
"@types/d3-quadtree": "*",
"@types/d3-random": "*",
"@types/d3-scale": "*",
"@types/d3-scale-chromatic": "*",
"@types/d3-selection": "*",
"@types/d3-shape": "*",
"@types/d3-time": "*",
"@types/d3-time-format": "*",
"@types/d3-timer": "*",
"@types/d3-transition": "*",
"@types/d3-zoom": "*"
}
},
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
},
"node_modules/@types/d3-axis": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
"integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-brush": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
"integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-chord": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
"integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
},
"node_modules/@types/d3-contour": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
"integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
"dependencies": {
"@types/d3-array": "*",
"@types/geojson": "*"
}
},
"node_modules/@types/d3-delaunay": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="
},
"node_modules/@types/d3-dispatch": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz",
"integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ=="
},
"node_modules/@types/d3-drag": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-dsv": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
"integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
},
"node_modules/@types/d3-fetch": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
"integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
"dependencies": {
"@types/d3-dsv": "*"
}
},
"node_modules/@types/d3-force": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
"integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="
},
"node_modules/@types/d3-format": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
"integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="
},
"node_modules/@types/d3-geo": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
"integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/d3-hierarchy": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
"integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ=="
},
"node_modules/@types/d3-polygon": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
"integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="
},
"node_modules/@types/d3-quadtree": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
"integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="
},
"node_modules/@types/d3-random": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
"integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="
},
"node_modules/@types/d3-scale": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-scale-chromatic": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz",
"integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw=="
},
"node_modules/@types/d3-selection": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="
},
"node_modules/@types/d3-shape": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
"integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz",
"integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
},
"node_modules/@types/d3-time-format": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
"integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
},
"node_modules/@types/d3-transition": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-zoom": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
"dependencies": {
"@types/d3-interpolate": "*",
"@types/d3-selection": "*"
}
},
"node_modules/@types/debounce": { "node_modules/@types/debounce": {
"version": "1.2.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz",
"integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==", "integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==",
"dev": true "dev": true
}, },
"node_modules/@types/dompurify": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
"integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
"dependencies": {
"@types/trusted-types": "*"
}
},
"node_modules/@types/ejs": { "node_modules/@types/ejs": {
"version": "3.1.5", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz",
@@ -3341,11 +3621,30 @@
"integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==", "integrity": "sha512-qZ72SFTgUAZ5a7Tj6kf2SHLetiH5S6f8G5frB2SPQ3EyF02kxdyBFf4Tz4banE3xCgGnKgWLt//a6VuYHKYJTg==",
"dev": true "dev": true
}, },
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"dev": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
"dev": true
}, },
"node_modules/@types/express": { "node_modules/@types/express": {
"version": "4.17.21", "version": "4.17.21",
@@ -3402,6 +3701,11 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/geojson": {
"version": "7946.0.14",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
"integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg=="
},
"node_modules/@types/glob": { "node_modules/@types/glob": {
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
@@ -3477,10 +3781,9 @@
} }
}, },
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.9", "version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
"dev": true
}, },
"node_modules/@types/keyv": { "node_modules/@types/keyv": {
"version": "3.1.3", "version": "3.1.3",
@@ -3750,6 +4053,11 @@
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
"dev": true "dev": true
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
},
"node_modules/@types/turndown": { "node_modules/@types/turndown": {
"version": "5.0.5", "version": "5.0.5",
"resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz",
@@ -4038,10 +4346,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.12.1", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"license": "MIT",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -4049,15 +4356,6 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/acorn-import-attributes": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
"dev": true,
"peerDependencies": {
"acorn": "^8"
}
},
"node_modules/acorn-jsx": { "node_modules/acorn-jsx": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
@@ -5003,9 +5301,9 @@
} }
}, },
"node_modules/browserslist": { "node_modules/browserslist": {
"version": "4.23.0", "version": "4.24.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -5022,10 +5320,10 @@
} }
], ],
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001587", "caniuse-lite": "^1.0.30001669",
"electron-to-chromium": "^1.4.668", "electron-to-chromium": "^1.5.41",
"node-releases": "^2.0.14", "node-releases": "^2.0.18",
"update-browserslist-db": "^1.0.13" "update-browserslist-db": "^1.1.1"
}, },
"bin": { "bin": {
"browserslist": "cli.js" "browserslist": "cli.js"
@@ -5311,9 +5609,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001594", "version": "1.0.30001676",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz",
"integrity": "sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g==", "integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -6512,9 +6810,9 @@
} }
}, },
"node_modules/d3-geo": { "node_modules/d3-geo": {
"version": "3.1.0", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
"integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==", "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
"dependencies": { "dependencies": {
"d3-array": "2.5.0 - 3" "d3-array": "2.5.0 - 3"
}, },
@@ -6723,11 +7021,11 @@
} }
}, },
"node_modules/dagre-d3-es": { "node_modules/dagre-d3-es": {
"version": "7.0.10", "version": "7.0.11",
"resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.10.tgz", "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz",
"integrity": "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A==", "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==",
"dependencies": { "dependencies": {
"d3": "^7.8.2", "d3": "^7.9.0",
"lodash-es": "^4.17.21" "lodash-es": "^4.17.21"
} }
}, },
@@ -6898,11 +7196,11 @@
} }
}, },
"node_modules/delaunator": { "node_modules/delaunator": {
"version": "5.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
"integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==", "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
"dependencies": { "dependencies": {
"robust-predicates": "^3.0.0" "robust-predicates": "^3.0.2"
} }
}, },
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
@@ -7540,9 +7838,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.693", "version": "1.5.50",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.693.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.50.tgz",
"integrity": "sha512-/if4Ueg0GUQlhCrW2ZlXwDAm40ipuKo+OgeHInlL8sbjt+hzISxZK949fZeJaVsheamrzANXvw1zQTvbxTvSHw==", "integrity": "sha512-eMVObiUQ2LdgeO1F/ySTXsvqvxb6ZH2zPGaMYsWzRDdOddUa77tdmI0ltg+L16UpbWdhPmuF3wIQYyQq65WfZw==",
"dev": true "dev": true
}, },
"node_modules/electron-window-state": { "node_modules/electron-window-state": {
@@ -7778,9 +8076,9 @@
} }
}, },
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.1.1", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -7812,27 +8110,30 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.10.0", "version": "9.14.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.14.0.tgz",
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", "integrity": "sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.11.0", "@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.18.0", "@eslint/config-array": "^0.18.0",
"@eslint/core": "^0.7.0",
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
"@eslint/js": "9.10.0", "@eslint/js": "9.14.0",
"@eslint/plugin-kit": "^0.1.0", "@eslint/plugin-kit": "^0.2.0",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.3.0", "@humanwhocodes/retry": "^0.4.0",
"@nodelib/fs.walk": "^1.2.8", "@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4", "ajv": "^6.12.4",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.2",
"debug": "^4.3.2", "debug": "^4.3.2",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^8.0.2", "eslint-scope": "^8.2.0",
"eslint-visitor-keys": "^4.0.0", "eslint-visitor-keys": "^4.2.0",
"espree": "^10.1.0", "espree": "^10.3.0",
"esquery": "^1.5.0", "esquery": "^1.5.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@@ -7842,13 +8143,11 @@
"ignore": "^5.2.0", "ignore": "^5.2.0",
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
"is-glob": "^4.0.0", "is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"minimatch": "^3.1.2", "minimatch": "^3.1.2",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"optionator": "^0.9.3", "optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"text-table": "^0.2.0" "text-table": "^0.2.0"
}, },
"bin": { "bin": {
@@ -7892,10 +8191,9 @@
} }
}, },
"node_modules/eslint-visitor-keys": { "node_modules/eslint-visitor-keys": {
"version": "4.0.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
"license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}, },
@@ -7916,10 +8214,9 @@
} }
}, },
"node_modules/eslint/node_modules/eslint-scope": { "node_modules/eslint/node_modules/eslint-scope": {
"version": "8.0.2", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
"integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
"license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
@@ -8023,14 +8320,13 @@
} }
}, },
"node_modules/espree": { "node_modules/espree": {
"version": "10.1.0", "version": "10.3.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
"license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"acorn": "^8.12.0", "acorn": "^8.14.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.0.0" "eslint-visitor-keys": "^4.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -8562,6 +8858,7 @@
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dev": true,
"dependencies": { "dependencies": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
@@ -8847,9 +9144,9 @@
} }
}, },
"node_modules/force-graph": { "node_modules/force-graph": {
"version": "1.45.0", "version": "1.46.0",
"resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.45.0.tgz", "resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.46.0.tgz",
"integrity": "sha512-QM/J72Vji5D3ug+TDu8wH+qne0zEKE9Cn7m9ocH/1RtaVY0BBqZQ4Mn6MiwNRyxwl28lsUd0F54kDpINnagvOA==", "integrity": "sha512-RR4XIsMgKMquEmN6me2MoDeqMr85Cv1cpXDFha6gwEczaaC3RWDH4YmXQXnI8/egRiIKFMq4HKjBjWXZwyy/9Q==",
"dependencies": { "dependencies": {
"@tweenjs/tween.js": "18 - 25", "@tweenjs/tween.js": "18 - 25",
"accessor-fn": "1", "accessor-fn": "1",
@@ -9727,9 +10024,9 @@
} }
}, },
"node_modules/i18next": { "node_modules/i18next": {
"version": "23.16.2", "version": "23.16.4",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.2.tgz", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.4.tgz",
"integrity": "sha512-dFyxwLXxEQK32f6tITBMaRht25mZPJhQ0WbC0p3bO2mWBal9lABTMqSka5k+GLSRWLzeJBKDpH7BeIA9TZI7Jg==", "integrity": "sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -10671,15 +10968,6 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-plain-obj": { "node_modules/is-plain-obj": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
@@ -11915,19 +12203,21 @@
} }
}, },
"node_modules/mermaid": { "node_modules/mermaid": {
"version": "11.3.0", "version": "11.4.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.3.0.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.4.0.tgz",
"integrity": "sha512-fFmf2gRXLtlGzug4wpIGN+rQdZ30M8IZEB1D3eZkXNqC7puhqeURBcD/9tbwXsqBO+A6Nzzo3MSSepmnw5xSeg==", "integrity": "sha512-mxCfEYvADJqOiHfGpJXLs4/fAjHz448rH0pfY5fAoxiz70rQiDSzUUy4dNET2T08i46IVpjohPd6WWbzmRHiPA==",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.0.1", "@braintree/sanitize-url": "^7.0.1",
"@iconify/utils": "^2.1.32", "@iconify/utils": "^2.1.32",
"@mermaid-js/parser": "^0.3.0", "@mermaid-js/parser": "^0.3.0",
"@types/d3": "^7.4.3",
"@types/dompurify": "^3.0.5",
"cytoscape": "^3.29.2", "cytoscape": "^3.29.2",
"cytoscape-cose-bilkent": "^4.1.0", "cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0", "cytoscape-fcose": "^2.2.0",
"d3": "^7.9.0", "d3": "^7.9.0",
"d3-sankey": "^0.12.3", "d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.10", "dagre-d3-es": "7.0.11",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"dompurify": "^3.0.11 <3.1.7", "dompurify": "^3.0.11 <3.1.7",
"katex": "^0.16.9", "katex": "^0.16.9",
@@ -12040,9 +12330,9 @@
} }
}, },
"node_modules/mind-elixir": { "node_modules/mind-elixir": {
"version": "4.2.3", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.2.3.tgz", "resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.3.1.tgz",
"integrity": "sha512-o/t9/mrJkRu0PE5UjXBv8ZZuhwSdm6C1Hw65v/+bIlB2CS2MOGZ/GNPvU3U4kPDu6LnCZ0kw0L7hoVfHhrZLtw==" "integrity": "sha512-9dHqiNRlAFUlGUKHwPwLC+Dka2cEaNunzHbZkOw+mafz8pqeZbmmm7Xxlk2S2zbKPGxeayxTYrDDg2tmNAXe3Q=="
}, },
"node_modules/minimalistic-assert": { "node_modules/minimalistic-assert": {
"version": "1.0.1", "version": "1.0.1",
@@ -12443,9 +12733,9 @@
} }
}, },
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.14", "version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
"dev": true "dev": true
}, },
"node_modules/nodemon": { "node_modules/nodemon": {
@@ -13146,9 +13436,9 @@
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@@ -13212,12 +13502,12 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.47.1", "version": "1.48.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.1.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz",
"integrity": "sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==", "integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"playwright-core": "1.47.1" "playwright-core": "1.48.2"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -13230,9 +13520,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.47.1", "version": "1.48.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.1.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz",
"integrity": "sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==", "integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==",
"dev": true, "dev": true,
"bin": { "bin": {
"playwright-core": "cli.js" "playwright-core": "cli.js"
@@ -13519,6 +13809,7 @@
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -14106,6 +14397,7 @@
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true,
"engines": { "engines": {
"iojs": ">=1.0.0", "iojs": ">=1.0.0",
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -14283,6 +14575,7 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -15659,9 +15952,9 @@
} }
}, },
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.0", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==" "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
}, },
"node_modules/tsscmp": { "node_modules/tsscmp": {
"version": "1.0.6", "version": "1.0.6",
@@ -15672,9 +15965,9 @@
} }
}, },
"node_modules/tsx": { "node_modules/tsx": {
"version": "4.19.1", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
"integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==", "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esbuild": "~0.23.0", "esbuild": "~0.23.0",
@@ -15919,9 +16212,9 @@
} }
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.13", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -15938,8 +16231,8 @@
} }
], ],
"dependencies": { "dependencies": {
"escalade": "^3.1.1", "escalade": "^3.2.0",
"picocolors": "^1.0.0" "picocolors": "^1.1.0"
}, },
"bin": { "bin": {
"update-browserslist-db": "cli.js" "update-browserslist-db": "cli.js"
@@ -16162,18 +16455,18 @@
} }
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.95.0", "version": "5.96.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz",
"integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/estree": "^1.0.5", "@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
"@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/ast": "^1.12.1",
"@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1",
"@webassemblyjs/wasm-parser": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1",
"acorn": "^8.7.1", "acorn": "^8.14.0",
"acorn-import-attributes": "^1.9.5", "browserslist": "^4.24.0",
"browserslist": "^4.21.10",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.1", "enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1", "es-module-lexer": "^1.2.1",

View File

@@ -2,7 +2,7 @@
"name": "trilium", "name": "trilium",
"productName": "TriliumNext Notes", "productName": "TriliumNext Notes",
"description": "Build your personal knowledge base with TriliumNext Notes", "description": "Build your personal knowledge base with TriliumNext Notes",
"version": "0.90.9-beta", "version": "0.90.11-beta",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "./dist/electron-main.js", "main": "./dist/electron-main.js",
"author": { "author": {
@@ -46,12 +46,14 @@
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"generate-document": "cross-env nodemon src/tools/generate_document.ts 1000" "generate-document": "cross-env nodemon src/tools/generate_document.ts 1000",
"ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts"
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "7.1.0", "@braintree/sanitize-url": "7.1.0",
"@electron/remote": "2.1.2", "@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "0.17.6", "@excalidraw/excalidraw": "0.17.6",
"@highlightjs/cdn-assets": "11.10.0",
"archiver": "7.0.1", "archiver": "7.0.1",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"autocomplete.js": "0.38.1", "autocomplete.js": "0.38.1",
@@ -74,19 +76,19 @@
"electron-squirrel-startup": "1.0.1", "electron-squirrel-startup": "1.0.1",
"electron-window-state": "5.0.3", "electron-window-state": "5.0.3",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"eslint": "9.10.0", "eslint": "9.14.0",
"express": "4.21.1", "express": "4.21.1",
"express-partial-content": "1.0.2", "express-partial-content": "1.0.2",
"express-rate-limit": "7.4.1", "express-rate-limit": "7.4.1",
"express-session": "1.18.1", "express-session": "1.18.1",
"force-graph": "1.45.0", "force-graph": "1.46.0",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
"helmet": "7.1.0", "helmet": "7.1.0",
"html": "1.0.0", "html": "1.0.0",
"html2plaintext": "2.1.4", "html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2", "http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.5", "https-proxy-agent": "7.0.5",
"i18next": "23.16.2", "i18next": "23.16.4",
"i18next-fs-backend": "2.3.2", "i18next-fs-backend": "2.3.2",
"i18next-http-backend": "2.6.2", "i18next-http-backend": "2.6.2",
"image-type": "4.1.0", "image-type": "4.1.0",
@@ -104,9 +106,9 @@
"knockout": "3.5.1", "knockout": "3.5.1",
"mark.js": "8.11.1", "mark.js": "8.11.1",
"marked": "14.1.3", "marked": "14.1.3",
"mermaid": "11.3.0", "mermaid": "11.4.0",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"mind-elixir": "4.2.3", "mind-elixir": "4.3.1",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
"node-abi": "3.67.0", "node-abi": "3.67.0",
"normalize-strings": "1.1.1", "normalize-strings": "1.1.1",
@@ -144,7 +146,7 @@
"@electron-forge/maker-squirrel": "7.5.0", "@electron-forge/maker-squirrel": "7.5.0",
"@electron-forge/maker-zip": "7.5.0", "@electron-forge/maker-zip": "7.5.0",
"@electron-forge/plugin-auto-unpack-natives": "7.5.0", "@electron-forge/plugin-auto-unpack-natives": "7.5.0",
"@playwright/test": "1.47.1", "@playwright/test": "1.48.2",
"@types/archiver": "6.0.2", "@types/archiver": "6.0.2",
"@types/better-sqlite3": "7.6.11", "@types/better-sqlite3": "7.6.11",
"@types/cls-hooked": "4.3.8", "@types/cls-hooked": "4.3.8",
@@ -189,10 +191,10 @@
"rcedit": "4.0.1", "rcedit": "4.0.1",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"ts-node": "10.9.2", "ts-node": "10.9.2",
"tslib": "2.8.0", "tslib": "2.8.1",
"tsx": "4.19.1", "tsx": "4.19.2",
"typescript": "5.6.3", "typescript": "5.6.3",
"webpack": "5.95.0", "webpack": "5.96.1",
"webpack-cli": "5.1.4" "webpack-cli": "5.1.4"
} }
} }

View File

@@ -38,9 +38,18 @@ export interface RecentNoteRow {
utcDateCreated?: string; utcDateCreated?: string;
} }
/**
* Database representation of an option.
*
* Options are key-value pairs that are used to store information such as user preferences (for example
* the current theme, sync server information), but also information about the state of the application).
*/
export interface OptionRow { export interface OptionRow {
/** The name of the option. */
name: string; name: string;
/** The value of the option. */
value: string; value: string;
/** `true` if the value should be synced across multiple instances (e.g. locale) or `false` if it should be local-only (e.g. theme). */
isSynced: boolean; isSynced: boolean;
utcDateModified?: string; utcDateModified?: string;
} }

View File

@@ -560,6 +560,18 @@ export default class TabManager extends Component {
} }
} }
async closeRightTabsCommand({ntxId}) {
const ntxIds = this.mainNoteContexts.map(nc => nc.ntxId);
const index = ntxIds.indexOf(ntxId);
if (index !== -1) {
const idsToRemove = ntxIds.slice(index + 1);
for (const ntxIdToRemove of idsToRemove) {
await this.removeNoteContext(ntxIdToRemove);
}
}
}
async closeTabCommand({ntxId}) { async closeTabCommand({ntxId}) {
await this.removeNoteContext(ntxId); await this.removeNoteContext(ntxId);
} }

View File

@@ -16,7 +16,7 @@ class ZoomComponent extends Component {
window.addEventListener("wheel", event => { window.addEventListener("wheel", event => {
if (event.ctrlKey) { if (event.ctrlKey) {
this.setZoomFactorAndSave(this.getCurrentZoom() + event.deltaY * 0.001); this.setZoomFactorAndSave(this.getCurrentZoom() - event.deltaY * 0.001);
} }
}); });
} }

View File

@@ -82,6 +82,7 @@ import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js"; import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js"; import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js";
import ScrollPaddingWidget from "../widgets/scroll_padding.js"; import ScrollPaddingWidget from "../widgets/scroll_padding.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
export default class DesktopLayout { export default class DesktopLayout {
constructor(customWidgets) { constructor(customWidgets) {
@@ -127,6 +128,7 @@ export default class DesktopLayout {
// the order of the widgets matter. Some of these want to "activate" themselves // the order of the widgets matter. Some of these want to "activate" themselves
// when visible. When this happens to multiple of them, the first one "wins". // when visible. When this happens to multiple of them, the first one "wins".
// promoted attributes should always win. // promoted attributes should always win.
.ribbon(new ClassicEditorToolbar())
.ribbon(new ScriptExecutorWidget()) .ribbon(new ScriptExecutorWidget())
.ribbon(new SearchDefinitionWidget()) .ribbon(new SearchDefinitionWidget())
.ribbon(new EditedNotesWidget()) .ribbon(new EditedNotesWidget())

View File

@@ -23,6 +23,7 @@ import LauncherContainer from "../widgets/containers/launcher_container.js";
import RootContainer from "../widgets/containers/root_container.js"; import RootContainer from "../widgets/containers/root_container.js";
import SharedInfoWidget from "../widgets/shared_info.js"; import SharedInfoWidget from "../widgets/shared_info.js";
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js"; import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
const MOBILE_CSS = ` const MOBILE_CSS = `
<style> <style>
@@ -167,6 +168,7 @@ export default class MobileLayout {
.child(new NoteListWidget()) .child(new NoteListWidget())
.child(new FilePropertiesWidget().css('font-size','smaller')) .child(new FilePropertiesWidget().css('font-size','smaller'))
) )
.child(new ClassicEditorToolbar())
) )
.child(new ProtectedSessionPasswordDialog()) .child(new ProtectedSessionPasswordDialog())
.child(new ConfirmDialog()) .child(new ConfirmDialog())

View File

@@ -3,6 +3,7 @@ import toastService from "./toast.js";
import froca from "./froca.js"; import froca from "./froca.js";
import linkService from "./link.js"; import linkService from "./link.js";
import utils from "./utils.js"; import utils from "./utils.js";
import { t } from "./i18n.js";
let clipboardBranchIds = []; let clipboardBranchIds = [];
let clipboardMode = null; let clipboardMode = null;

View File

@@ -10,6 +10,8 @@ import treeService from "./tree.js";
import FNote from "../entities/fnote.js"; import FNote from "../entities/fnote.js";
import FAttachment from "../entities/fattachment.js"; import FAttachment from "../entities/fattachment.js";
import imageContextMenuService from "../menus/image_context_menu.js"; import imageContextMenuService from "../menus/image_context_menu.js";
import { applySingleBlockSyntaxHighlight, applySyntaxHighlight } from "./syntax_highlight.js";
import mime_types from "./mime_types.js";
let idCounter = 1; let idCounter = 1;
@@ -105,16 +107,25 @@ async function renderText(note, $renderedContent) {
for (const el of referenceLinks) { for (const el of referenceLinks) {
await linkService.loadReferenceLinkTitle($(el)); await linkService.loadReferenceLinkTitle($(el));
} }
await applySyntaxHighlight($renderedContent);
} else { } else {
await renderChildrenList($renderedContent, note); await renderChildrenList($renderedContent, note);
} }
} }
/** @param {FNote} note */ /**
* Renders a code note, by displaying its content and applying syntax highlighting based on the selected MIME type.
*
* @param {FNote} note
*/
async function renderCode(note, $renderedContent) { async function renderCode(note, $renderedContent) {
const blob = await note.getBlob(); const blob = await note.getBlob();
$renderedContent.append($("<pre>").text(blob.content)); const $codeBlock = $("<code>");
$codeBlock.text(blob.content);
$renderedContent.append($("<pre>").append($codeBlock));
await applySingleBlockSyntaxHighlight($codeBlock, mime_types.normalizeMimeTypeForCKEditor(note.mime));
} }
function renderImage(entity, $renderedContent, options = {}) { function renderImage(entity, $renderedContent, options = {}) {

View File

@@ -1,3 +1,7 @@
import mimeTypesService from "./mime_types.js";
import optionsService from "./options.js";
import { getStylesheetUrl } from "./syntax_highlight.js";
const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]}; const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]};
const CODE_MIRROR = { const CODE_MIRROR = {
@@ -84,18 +88,44 @@ const MIND_ELIXIR = {
] ]
}; };
const HIGHLIGHT_JS = {
js: () => {
const mimeTypes = mimeTypesService.getMimeTypes();
const scriptsToLoad = new Set();
scriptsToLoad.add("node_modules/@highlightjs/cdn-assets/highlight.min.js");
for (const mimeType of mimeTypes) {
if (mimeType.enabled && mimeType.highlightJs) {
scriptsToLoad.add(`node_modules/@highlightjs/cdn-assets/languages/${mimeType.highlightJs}.min.js`);
}
}
const currentTheme = optionsService.get("codeBlockTheme");
loadHighlightingTheme(currentTheme);
return Array.from(scriptsToLoad);
}
};
async function requireLibrary(library) { async function requireLibrary(library) {
if (library.css) { if (library.css) {
library.css.map(cssUrl => requireCss(cssUrl)); library.css.map(cssUrl => requireCss(cssUrl));
} }
if (library.js) { if (library.js) {
for (const scriptUrl of library.js) { for (const scriptUrl of unwrapValue(library.js)) {
await requireScript(scriptUrl); await requireScript(scriptUrl);
} }
} }
} }
function unwrapValue(value) {
if (typeof value === "function") {
return value();
}
return value;
}
// we save the promises in case of the same script being required concurrently multiple times // we save the promises in case of the same script being required concurrently multiple times
const loadedScriptPromises = {}; const loadedScriptPromises = {};
@@ -127,9 +157,36 @@ async function requireCss(url, prependAssetPath = true) {
} }
} }
let highlightingThemeEl = null;
function loadHighlightingTheme(theme) {
if (!theme) {
return;
}
if (theme === "none") {
// Deactivate the theme.
if (highlightingThemeEl) {
highlightingThemeEl.remove();
highlightingThemeEl = null;
}
return;
}
if (!highlightingThemeEl) {
highlightingThemeEl = $(`<link rel="stylesheet" type="text/css" />`);
$("head").append(highlightingThemeEl);
}
const url = getStylesheetUrl(theme);
if (url) {
highlightingThemeEl.attr("href", url);
}
}
export default { export default {
requireCss, requireCss,
requireLibrary, requireLibrary,
loadHighlightingTheme,
CKEDITOR, CKEDITOR,
CODE_MIRROR, CODE_MIRROR,
ESLINT, ESLINT,
@@ -143,5 +200,6 @@ export default {
EXCALIDRAW, EXCALIDRAW,
MARKJS, MARKJS,
I18NEXT, I18NEXT,
MIND_ELIXIR MIND_ELIXIR,
HIGHLIGHT_JS
} }

View File

@@ -1,162 +1,171 @@
import options from "./options.js"; import options from "./options.js";
/**
* A pseudo-MIME type which is used in the editor to automatically determine the language used in code blocks via heuristics.
*/
const MIME_TYPE_AUTO = "text-x-trilium-auto";
/**
* For highlight.js-supported languages, see https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md.
*/
const MIME_TYPES_DICT = [ const MIME_TYPES_DICT = [
{ default: true, title: "Plain text", mime: "text/plain" }, { default: true, title: "Plain text", mime: "text/plain", highlightJs: "plaintext" },
{ title: "APL", mime: "text/apl" }, { title: "APL", mime: "text/apl" },
{ title: "ASN.1", mime: "text/x-ttcn-asn" }, { title: "ASN.1", mime: "text/x-ttcn-asn" },
{ title: "ASP.NET", mime: "application/x-aspx" }, { title: "ASP.NET", mime: "application/x-aspx" },
{ title: "Asterisk", mime: "text/x-asterisk" }, { title: "Asterisk", mime: "text/x-asterisk" },
{ title: "Brainfuck", mime: "text/x-brainfuck" }, { title: "Brainfuck", mime: "text/x-brainfuck", highlightJs: "brainfuck" },
{ default: true, title: "C", mime: "text/x-csrc" }, { default: true, title: "C", mime: "text/x-csrc", highlightJs: "c" },
{ default: true, title: "C#", mime: "text/x-csharp" }, { default: true, title: "C#", mime: "text/x-csharp", highlightJs: "csharp" },
{ default: true, title: "C++", mime: "text/x-c++src" }, { default: true, title: "C++", mime: "text/x-c++src", highlightJs: "cpp" },
{ title: "Clojure", mime: "text/x-clojure" }, { title: "Clojure", mime: "text/x-clojure", highlightJs: "clojure" },
{ title: "ClojureScript", mime: "text/x-clojurescript" }, { title: "ClojureScript", mime: "text/x-clojurescript" },
{ title: "Closure Stylesheets (GSS)", mime: "text/x-gss" }, { title: "Closure Stylesheets (GSS)", mime: "text/x-gss" },
{ title: "CMake", mime: "text/x-cmake" }, { title: "CMake", mime: "text/x-cmake", highlightJs: "cmake" },
{ title: "Cobol", mime: "text/x-cobol" }, { title: "Cobol", mime: "text/x-cobol" },
{ title: "CoffeeScript", mime: "text/coffeescript" }, { title: "CoffeeScript", mime: "text/coffeescript", highlightJs: "coffeescript" },
{ title: "Common Lisp", mime: "text/x-common-lisp" }, { title: "Common Lisp", mime: "text/x-common-lisp", highlightJs: "lisp" },
{ title: "CQL", mime: "text/x-cassandra" }, { title: "CQL", mime: "text/x-cassandra" },
{ title: "Crystal", mime: "text/x-crystal" }, { title: "Crystal", mime: "text/x-crystal", highlightJs: "crystal" },
{ default: true, title: "CSS", mime: "text/css" }, { default: true, title: "CSS", mime: "text/css", highlightJs: "css" },
{ title: "Cypher", mime: "application/x-cypher-query" }, { title: "Cypher", mime: "application/x-cypher-query" },
{ title: "Cython", mime: "text/x-cython" }, { title: "Cython", mime: "text/x-cython" },
{ title: "D", mime: "text/x-d" }, { title: "D", mime: "text/x-d", highlightJs: "d" },
{ title: "Dart", mime: "application/dart" }, { title: "Dart", mime: "application/dart", highlightJs: "dart" },
{ title: "diff", mime: "text/x-diff" }, { title: "diff", mime: "text/x-diff", highlightJs: "diff" },
{ title: "Django", mime: "text/x-django" }, { title: "Django", mime: "text/x-django", highlightJs: "django" },
{ title: "Dockerfile", mime: "text/x-dockerfile" }, { title: "Dockerfile", mime: "text/x-dockerfile", highlightJs: "dockerfile" },
{ title: "DTD", mime: "application/xml-dtd" }, { title: "DTD", mime: "application/xml-dtd" },
{ title: "Dylan", mime: "text/x-dylan" }, { title: "Dylan", mime: "text/x-dylan" },
{ title: "EBNF", mime: "text/x-ebnf" }, { title: "EBNF", mime: "text/x-ebnf", highlightJs: "ebnf" },
{ title: "ECL", mime: "text/x-ecl" }, { title: "ECL", mime: "text/x-ecl" },
{ title: "edn", mime: "application/edn" }, { title: "edn", mime: "application/edn" },
{ title: "Eiffel", mime: "text/x-eiffel" }, { title: "Eiffel", mime: "text/x-eiffel" },
{ title: "Elm", mime: "text/x-elm" }, { title: "Elm", mime: "text/x-elm", highlightJs: "elm" },
{ title: "Embedded Javascript", mime: "application/x-ejs" }, { title: "Embedded Javascript", mime: "application/x-ejs" },
{ title: "Embedded Ruby", mime: "application/x-erb" }, { title: "Embedded Ruby", mime: "application/x-erb", highlightJs: "erb" },
{ title: "Erlang", mime: "text/x-erlang" }, { title: "Erlang", mime: "text/x-erlang", highlightJs: "erlang" },
{ title: "Esper", mime: "text/x-esper" }, { title: "Esper", mime: "text/x-esper" },
{ title: "F#", mime: "text/x-fsharp" }, { title: "F#", mime: "text/x-fsharp", highlightJs: "fsharp" },
{ title: "Factor", mime: "text/x-factor" }, { title: "Factor", mime: "text/x-factor" },
{ title: "FCL", mime: "text/x-fcl" }, { title: "FCL", mime: "text/x-fcl" },
{ title: "Forth", mime: "text/x-forth" }, { title: "Forth", mime: "text/x-forth" },
{ title: "Fortran", mime: "text/x-fortran" }, { title: "Fortran", mime: "text/x-fortran", highlightJs: "fortran" },
{ title: "Gas", mime: "text/x-gas" }, { title: "Gas", mime: "text/x-gas" },
{ title: "Gherkin", mime: "text/x-feature" }, { title: "Gherkin", mime: "text/x-feature", highlightJs: "gherkin" },
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm" }, { title: "GitHub Flavored Markdown", mime: "text/x-gfm", highlightJs: "markdown" },
{ default: true, title: "Go", mime: "text/x-go" }, { default: true, title: "Go", mime: "text/x-go", highlightJs: "go" },
{ default: true, title: "Groovy", mime: "text/x-groovy" }, { default: true, title: "Groovy", mime: "text/x-groovy", highlightJs: "groovy" },
{ title: "HAML", mime: "text/x-haml" }, { title: "HAML", mime: "text/x-haml", highlightJs: "haml" },
{ default: true, title: "Haskell", mime: "text/x-haskell" }, { default: true, title: "Haskell", mime: "text/x-haskell", highlightJs: "haskell" },
{ title: "Haskell (Literate)", mime: "text/x-literate-haskell" }, { title: "Haskell (Literate)", mime: "text/x-literate-haskell" },
{ title: "Haxe", mime: "text/x-haxe" }, { title: "Haxe", mime: "text/x-haxe", highlightJs: "haxe" },
{ default: true, title: "HTML", mime: "text/html" }, { default: true, title: "HTML", mime: "text/html", highlightJs: "xml" },
{ default: true, title: "HTTP", mime: "message/http" }, { default: true, title: "HTTP", mime: "message/http", highlightJs: "http" },
{ title: "HXML", mime: "text/x-hxml" }, { title: "HXML", mime: "text/x-hxml" },
{ title: "IDL", mime: "text/x-idl" }, { title: "IDL", mime: "text/x-idl" },
{ default: true, title: "Java", mime: "text/x-java" }, { default: true, title: "Java", mime: "text/x-java", highlightJs: "java" },
{ title: "Java Server Pages", mime: "application/x-jsp" }, { title: "Java Server Pages", mime: "application/x-jsp", highlightJs: "java" },
{ title: "Jinja2", mime: "text/jinja2" }, { title: "Jinja2", mime: "text/jinja2" },
{ default: true, title: "JS backend", mime: "application/javascript;env=backend" }, { default: true, title: "JS backend", mime: "application/javascript;env=backend", highlightJs: "javascript" },
{ default: true, title: "JS frontend", mime: "application/javascript;env=frontend" }, { default: true, title: "JS frontend", mime: "application/javascript;env=frontend", highlightJs: "javascript" },
{ default: true, title: "JSON", mime: "application/json" }, { default: true, title: "JSON", mime: "application/json", highlightJs: "json" },
{ title: "JSON-LD", mime: "application/ld+json" }, { title: "JSON-LD", mime: "application/ld+json", highlightJs: "json" },
{ title: "JSX", mime: "text/jsx" }, { title: "JSX", mime: "text/jsx", highlightJs: "javascript" },
{ title: "Julia", mime: "text/x-julia" }, { title: "Julia", mime: "text/x-julia", highlightJs: "julia" },
{ default: true, title: "Kotlin", mime: "text/x-kotlin" }, { default: true, title: "Kotlin", mime: "text/x-kotlin", highlightJs: "kotlin" },
{ title: "LaTeX", mime: "text/x-latex" }, { title: "LaTeX", mime: "text/x-latex", highlightJs: "latex" },
{ title: "LESS", mime: "text/x-less" }, { title: "LESS", mime: "text/x-less", highlightJs: "less" },
{ title: "LiveScript", mime: "text/x-livescript" }, { title: "LiveScript", mime: "text/x-livescript", highlightJs: "livescript" },
{ title: "Lua", mime: "text/x-lua" }, { title: "Lua", mime: "text/x-lua", highlightJs: "lua" },
{ title: "MariaDB SQL", mime: "text/x-mariadb" }, { title: "MariaDB SQL", mime: "text/x-mariadb", highlightJs: "sql" },
{ default: true, title: "Markdown", mime: "text/x-markdown" }, { default: true, title: "Markdown", mime: "text/x-markdown", highlightJs: "markdown" },
{ title: "Mathematica", mime: "text/x-mathematica" }, { title: "Mathematica", mime: "text/x-mathematica", highlightJs: "mathematica" },
{ title: "mbox", mime: "application/mbox" }, { title: "mbox", mime: "application/mbox" },
{ title: "mIRC", mime: "text/mirc" }, { title: "mIRC", mime: "text/mirc" },
{ title: "Modelica", mime: "text/x-modelica" }, { title: "Modelica", mime: "text/x-modelica" },
{ title: "MS SQL", mime: "text/x-mssql" }, { title: "MS SQL", mime: "text/x-mssql", highlightJs: "sql" },
{ title: "mscgen", mime: "text/x-mscgen" }, { title: "mscgen", mime: "text/x-mscgen" },
{ title: "msgenny", mime: "text/x-msgenny" }, { title: "msgenny", mime: "text/x-msgenny" },
{ title: "MUMPS", mime: "text/x-mumps" }, { title: "MUMPS", mime: "text/x-mumps" },
{ title: "MySQL", mime: "text/x-mysql" }, { title: "MySQL", mime: "text/x-mysql", highlightJs: "sql" },
{ title: "Nginx", mime: "text/x-nginx-conf" }, { title: "Nginx", mime: "text/x-nginx-conf", highlightJs: "nginx" },
{ title: "NSIS", mime: "text/x-nsis" }, { title: "NSIS", mime: "text/x-nsis", highlightJs: "nsis" },
{ title: "NTriples", mime: "application/n-triples" }, { title: "NTriples", mime: "application/n-triples" },
{ title: "Objective-C", mime: "text/x-objectivec" }, { title: "Objective-C", mime: "text/x-objectivec", highlightJs: "objectivec" },
{ title: "OCaml", mime: "text/x-ocaml" }, { title: "OCaml", mime: "text/x-ocaml", highlightJs: "ocaml" },
{ title: "Octave", mime: "text/x-octave" }, { title: "Octave", mime: "text/x-octave" },
{ title: "Oz", mime: "text/x-oz" }, { title: "Oz", mime: "text/x-oz" },
{ title: "Pascal", mime: "text/x-pascal" }, { title: "Pascal", mime: "text/x-pascal", highlightJs: "delphi" },
{ title: "PEG.js", mime: "null" }, { title: "PEG.js", mime: "null" },
{ default: true, title: "Perl", mime: "text/x-perl" }, { default: true, title: "Perl", mime: "text/x-perl" },
{ title: "PGP", mime: "application/pgp" }, { title: "PGP", mime: "application/pgp" },
{ default: true, title: "PHP", mime: "text/x-php" }, { default: true, title: "PHP", mime: "text/x-php" },
{ title: "Pig", mime: "text/x-pig" }, { title: "Pig", mime: "text/x-pig" },
{ title: "PLSQL", mime: "text/x-plsql" }, { title: "PLSQL", mime: "text/x-plsql", highlightJs: "sql" },
{ title: "PostgreSQL", mime: "text/x-pgsql" }, { title: "PostgreSQL", mime: "text/x-pgsql", highlightJs: "pgsql" },
{ title: "PowerShell", mime: "application/x-powershell" }, { title: "PowerShell", mime: "application/x-powershell", highlightJs: "powershell" },
{ title: "Properties files", mime: "text/x-properties" }, { title: "Properties files", mime: "text/x-properties", highlightJs: "properties" },
{ title: "ProtoBuf", mime: "text/x-protobuf" }, { title: "ProtoBuf", mime: "text/x-protobuf", highlightJs: "protobuf" },
{ title: "Pug", mime: "text/x-pug" }, { title: "Pug", mime: "text/x-pug" },
{ title: "Puppet", mime: "text/x-puppet" }, { title: "Puppet", mime: "text/x-puppet", highlightJs: "puppet" },
{ default: true, title: "Python", mime: "text/x-python" }, { default: true, title: "Python", mime: "text/x-python", highlightJs: "python" },
{ title: "Q", mime: "text/x-q" }, { title: "Q", mime: "text/x-q", highlightJs: "q" },
{ title: "R", mime: "text/x-rsrc" }, { title: "R", mime: "text/x-rsrc", highlightJs: "r" },
{ title: "reStructuredText", mime: "text/x-rst" }, { title: "reStructuredText", mime: "text/x-rst" },
{ title: "RPM Changes", mime: "text/x-rpm-changes" }, { title: "RPM Changes", mime: "text/x-rpm-changes" },
{ title: "RPM Spec", mime: "text/x-rpm-spec" }, { title: "RPM Spec", mime: "text/x-rpm-spec" },
{ default: true, title: "Ruby", mime: "text/x-ruby" }, { default: true, title: "Ruby", mime: "text/x-ruby", highlightJs: "ruby" },
{ title: "Rust", mime: "text/x-rustsrc" }, { title: "Rust", mime: "text/x-rustsrc", highlightJs: "rust" },
{ title: "SAS", mime: "text/x-sas" }, { title: "SAS", mime: "text/x-sas", highlightJs: "sas" },
{ title: "Sass", mime: "text/x-sass" }, { title: "Sass", mime: "text/x-sass" },
{ title: "Scala", mime: "text/x-scala" }, { title: "Scala", mime: "text/x-scala" },
{ title: "Scheme", mime: "text/x-scheme" }, { title: "Scheme", mime: "text/x-scheme" },
{ title: "SCSS", mime: "text/x-scss" }, { title: "SCSS", mime: "text/x-scss", highlightJs: "scss" },
{ default: true, title: "Shell (bash)", mime: "text/x-sh" }, { default: true, title: "Shell (bash)", mime: "text/x-sh", highlightJs: "bash" },
{ title: "Sieve", mime: "application/sieve" }, { title: "Sieve", mime: "application/sieve" },
{ title: "Slim", mime: "text/x-slim" }, { title: "Slim", mime: "text/x-slim" },
{ title: "Smalltalk", mime: "text/x-stsrc" }, { title: "Smalltalk", mime: "text/x-stsrc", highlightJs: "smalltalk" },
{ title: "Smarty", mime: "text/x-smarty" }, { title: "Smarty", mime: "text/x-smarty" },
{ title: "SML", mime: "text/x-sml" }, { title: "SML", mime: "text/x-sml", highlightJs: "sml" },
{ title: "Solr", mime: "text/x-solr" }, { title: "Solr", mime: "text/x-solr" },
{ title: "Soy", mime: "text/x-soy" }, { title: "Soy", mime: "text/x-soy" },
{ title: "SPARQL", mime: "application/sparql-query" }, { title: "SPARQL", mime: "application/sparql-query" },
{ title: "Spreadsheet", mime: "text/x-spreadsheet" }, { title: "Spreadsheet", mime: "text/x-spreadsheet" },
{ default: true, title: "SQL", mime: "text/x-sql" }, { default: true, title: "SQL", mime: "text/x-sql", highlightJs: "sql" },
{ title: "SQLite", mime: "text/x-sqlite" }, { title: "SQLite", mime: "text/x-sqlite", highlightJs: "sql" },
{ default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium" }, { default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium", highlightJs: "sql" },
{ title: "Squirrel", mime: "text/x-squirrel" }, { title: "Squirrel", mime: "text/x-squirrel" },
{ title: "sTeX", mime: "text/x-stex" }, { title: "sTeX", mime: "text/x-stex" },
{ title: "Stylus", mime: "text/x-styl" }, { title: "Stylus", mime: "text/x-styl", highlightJs: "stylus" },
{ default: true, title: "Swift", mime: "text/x-swift" }, { default: true, title: "Swift", mime: "text/x-swift" },
{ title: "SystemVerilog", mime: "text/x-systemverilog" }, { title: "SystemVerilog", mime: "text/x-systemverilog" },
{ title: "Tcl", mime: "text/x-tcl" }, { title: "Tcl", mime: "text/x-tcl", highlightJs: "tcl" },
{ title: "Textile", mime: "text/x-textile" }, { title: "Textile", mime: "text/x-textile" },
{ title: "TiddlyWiki ", mime: "text/x-tiddlywiki" }, { title: "TiddlyWiki ", mime: "text/x-tiddlywiki" },
{ title: "Tiki wiki", mime: "text/tiki" }, { title: "Tiki wiki", mime: "text/tiki" },
{ title: "TOML", mime: "text/x-toml" }, { title: "TOML", mime: "text/x-toml", highlightJs: "ini" },
{ title: "Tornado", mime: "text/x-tornado" }, { title: "Tornado", mime: "text/x-tornado" },
{ title: "troff", mime: "text/troff" }, { title: "troff", mime: "text/troff" },
{ title: "TTCN", mime: "text/x-ttcn" }, { title: "TTCN", mime: "text/x-ttcn" },
{ title: "TTCN_CFG", mime: "text/x-ttcn-cfg" }, { title: "TTCN_CFG", mime: "text/x-ttcn-cfg" },
{ title: "Turtle", mime: "text/turtle" }, { title: "Turtle", mime: "text/turtle" },
{ title: "Twig", mime: "text/x-twig" }, { title: "Twig", mime: "text/x-twig", highlightJs: "twig" },
{ title: "TypeScript", mime: "application/typescript" }, { title: "TypeScript", mime: "application/typescript", highlightJs: "typescript" },
{ title: "TypeScript-JSX", mime: "text/typescript-jsx" }, { title: "TypeScript-JSX", mime: "text/typescript-jsx" },
{ title: "VB.NET", mime: "text/x-vb" }, { title: "VB.NET", mime: "text/x-vb", highlightJs: "vbnet" },
{ title: "VBScript", mime: "text/vbscript" }, { title: "VBScript", mime: "text/vbscript", highlightJs: "vbscript" },
{ title: "Velocity", mime: "text/velocity" }, { title: "Velocity", mime: "text/velocity" },
{ title: "Verilog", mime: "text/x-verilog" }, { title: "Verilog", mime: "text/x-verilog", highlightJs: "verilog" },
{ title: "VHDL", mime: "text/x-vhdl" }, { title: "VHDL", mime: "text/x-vhdl", highlightJs: "vhdl" },
{ title: "Vue.js Component", mime: "text/x-vue" }, { title: "Vue.js Component", mime: "text/x-vue" },
{ title: "Web IDL", mime: "text/x-webidl" }, { title: "Web IDL", mime: "text/x-webidl" },
{ default: true, title: "XML", mime: "text/xml" }, { default: true, title: "XML", mime: "text/xml", highlightJs: "xml" },
{ title: "XQuery", mime: "application/xquery" }, { title: "XQuery", mime: "application/xquery", highlightJs: "xquery" },
{ title: "xu", mime: "text/x-xu" }, { title: "xu", mime: "text/x-xu" },
{ title: "Yacas", mime: "text/x-yacas" }, { title: "Yacas", mime: "text/x-yacas" },
{ default: true, title: "YAML", mime: "text/x-yaml" }, { default: true, title: "YAML", mime: "text/x-yaml", highlightJs: "yaml" },
{ title: "Z80", mime: "text/x-z80" } { title: "Z80", mime: "text/x-z80" }
]; ];
@@ -173,7 +182,7 @@ function loadMimeTypes() {
} }
} }
async function getMimeTypes() { function getMimeTypes() {
if (mimeTypes === null) { if (mimeTypes === null) {
loadMimeTypes(); loadMimeTypes();
} }
@@ -181,7 +190,46 @@ async function getMimeTypes() {
return mimeTypes; return mimeTypes;
} }
export default { let mimeToHighlightJsMapping = null;
getMimeTypes,
loadMimeTypes /**
* Obtains the corresponding language tag for highlight.js for a given MIME type.
*
* The mapping is built the first time this method is built and then the results are cached for better performance.
*
* @param {string} mimeType The MIME type of the code block, in the CKEditor-normalized format (e.g. `text-c-src` instead of `text/c-src`).
* @returns the corresponding highlight.js tag, for example `c` for `text-c-src`.
*/
function getHighlightJsNameForMime(mimeType) {
if (!mimeToHighlightJsMapping) {
const mimeTypes = getMimeTypes();
mimeToHighlightJsMapping = {};
for (const mimeType of mimeTypes) {
// The mime stored by CKEditor is text-x-csrc instead of text/x-csrc so we keep this format for faster lookup.
const normalizedMime = normalizeMimeTypeForCKEditor(mimeType.mime);
mimeToHighlightJsMapping[normalizedMime] = mimeType.highlightJs;
}
}
return mimeToHighlightJsMapping[mimeType];
}
/**
* Given a MIME type in the usual format (e.g. `text/csrc`), it returns a MIME type that can be passed down to the CKEditor
* code plugin.
*
* @param {string} mimeType The MIME type to normalize, in the usual format (e.g. `text/c-src`).
* @returns the normalized MIME type (e.g. `text-c-src`).
*/
function normalizeMimeTypeForCKEditor(mimeType) {
return mimeType.toLowerCase()
.replace(/[\W_]+/g,"-");
}
export default {
MIME_TYPE_AUTO,
getMimeTypes,
loadMimeTypes,
getHighlightJsNameForMime,
normalizeMimeTypeForCKEditor
} }

View File

@@ -371,7 +371,8 @@ class NoteListRenderer {
$content.append($renderedContent); $content.append($renderedContent);
$content.addClass(`type-${type}`); $content.addClass(`type-${type}`);
} catch (e) { } catch (e) {
console.log(`Caught error while rendering note '${note.noteId}' of type '${note.type}': ${e.message}, stack: ${e.stack}`); console.warn(`Caught error while rendering note '${note.noteId}' of type '${note.type}'`);
console.error(e);
$content.append("rendering error"); $content.append("rendering error");
} }

View File

@@ -1,3 +1,4 @@
import { t } from './i18n.js';
import server from './server.js'; import server from './server.js';
import toastService from "./toast.js"; import toastService from "./toast.js";

View File

@@ -0,0 +1,94 @@
import library_loader from "./library_loader.js";
import mime_types from "./mime_types.js";
import options from "./options.js";
export function getStylesheetUrl(theme) {
if (!theme) {
return null;
}
const defaultPrefix = "default:";
if (theme.startsWith(defaultPrefix)) {
return `${window.glob.assetPath}/node_modules/@highlightjs/cdn-assets/styles/${theme.substr(defaultPrefix.length)}.min.css`;
}
return null;
}
/**
* Identifies all the code blocks (as `pre code`) under the specified hierarchy and uses the highlight.js library to obtain the highlighted text which is then applied on to the code blocks.
*
* @param $container the container under which to look for code blocks and to apply syntax highlighting to them.
*/
export async function applySyntaxHighlight($container) {
if (!isSyntaxHighlightEnabled()) {
return;
}
const codeBlocks = $container.find("pre code");
for (const codeBlock of codeBlocks) {
const normalizedMimeType = extractLanguageFromClassList(codeBlock);
if (!normalizedMimeType) {
continue;
}
applySingleBlockSyntaxHighlight($(codeBlock, normalizedMimeType));
}
}
/**
* Applies syntax highlight to the given code block (assumed to be <pre><code>), using highlight.js.
*
* @param {*} $codeBlock
* @param {*} normalizedMimeType
*/
export async function applySingleBlockSyntaxHighlight($codeBlock, normalizedMimeType) {
$codeBlock.parent().toggleClass("hljs");
const text = $codeBlock.text();
if (!window.hljs) {
await library_loader.requireLibrary(library_loader.HIGHLIGHT_JS);
}
let highlightedText = null;
if (normalizedMimeType === mime_types.MIME_TYPE_AUTO) {
highlightedText = hljs.highlightAuto(text);
} else if (normalizedMimeType) {
const language = mime_types.getHighlightJsNameForMime(normalizedMimeType);
if (language) {
highlightedText = hljs.highlight(text, { language });
} else {
console.warn(`Unknown mime type: ${normalizedMimeType}.`);
}
}
if (highlightedText) {
$codeBlock.html(highlightedText.value);
}
}
/**
* Indicates whether syntax highlighting should be enabled for code blocks, by querying the value of the `codeblockTheme` option.
* @returns whether syntax highlighting should be enabled for code blocks.
*/
export function isSyntaxHighlightEnabled() {
const theme = options.get("codeBlockTheme");
return theme && theme !== "none";
}
/**
* Given a HTML element, tries to extract the `language-` class name out of it.
*
* @param {string} el the HTML element from which to extract the language tag.
* @returns the normalized MIME type (e.g. `text-css` instead of `language-text-css`).
*/
function extractLanguageFromClassList(el) {
const prefix = "language-";
for (const className of el.classList) {
if (className.startsWith(prefix)) {
return className.substr(prefix.length);
}
}
return null;
}

View File

@@ -527,6 +527,58 @@ function downloadSvg(nameWithoutExtension, svgContent) {
document.body.removeChild(element); document.body.removeChild(element);
} }
/**
* Compares two semantic version strings.
* Returns:
* 1 if v1 is greater than v2
* 0 if v1 is equal to v2
* -1 if v1 is less than v2
*
* @param {string} v1 First version string
* @param {string} v2 Second version string
* @returns {number}
*/
function compareVersions(v1, v2) {
// Remove 'v' prefix and everything after dash if present
v1 = v1.replace(/^v/, '').split('-')[0];
v2 = v2.replace(/^v/, '').split('-')[0];
const v1parts = v1.split('.').map(Number);
const v2parts = v2.split('.').map(Number);
// Pad shorter version with zeros
while (v1parts.length < 3) v1parts.push(0);
while (v2parts.length < 3) v2parts.push(0);
// Compare major version
if (v1parts[0] !== v2parts[0]) {
return v1parts[0] > v2parts[0] ? 1 : -1;
}
// Compare minor version
if (v1parts[1] !== v2parts[1]) {
return v1parts[1] > v2parts[1] ? 1 : -1;
}
// Compare patch version
if (v1parts[2] !== v2parts[2]) {
return v1parts[2] > v2parts[2] ? 1 : -1;
}
return 0;
}
/**
* Compares two semantic version strings and returns `true` if the latest version is greater than the current version.
* @param {string} latestVersion
* @param {string} currentVersion
* @returns {boolean}
*/
function isUpdateAvailable(latestVersion, currentVersion) {
return compareVersions(latestVersion, currentVersion) > 0;
}
export default { export default {
reloadFrontendApp, reloadFrontendApp,
parseDate, parseDate,
@@ -567,5 +619,7 @@ export default {
areObjectsEqual, areObjectsEqual,
copyHtmlToClipboard, copyHtmlToClipboard,
createImageSrcUrl, createImageSrcUrl,
downloadSvg downloadSvg,
compareVersions,
isUpdateAvailable
}; };

View File

@@ -347,8 +347,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
this.$editor.on("click", e => this.handleEditorClick(e)); this.$editor.on("click", e => this.handleEditorClick(e));
/** @property {BalloonEditor} */ this.textEditor = await CKEditor.BalloonEditor.create(this.$editor[0], editorConfig);
this.textEditor = await BalloonEditor.create(this.$editor[0], editorConfig);
this.textEditor.model.document.on('change:data', () => this.dataChanged()); this.textEditor.model.document.on('change:data', () => this.dataChanged());
this.textEditor.editing.view.document.on('enter', (event, data) => { this.textEditor.editing.view.document.on('enter', (event, data) => {
// disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422 // disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422
@@ -358,9 +357,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
// disable spellcheck for attribute editor // disable spellcheck for attribute editor
this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot())); this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot()));
//await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector');
//CKEditorInspector.attach(this.textEditor);
} }
dataChanged() { dataChanged() {

View File

@@ -333,7 +333,8 @@ export default class GlobalMenuWidget extends BasicWidget {
const latestVersion = await this.fetchLatestVersion(); const latestVersion = await this.fetchLatestVersion();
this.updateAvailableWidget.updateVersionStatus(latestVersion); this.updateAvailableWidget.updateVersionStatus(latestVersion);
this.$updateToLatestVersionButton.toggle(latestVersion > glob.triliumVersion); // Show "click to download" button in options menu if there's a new version available
this.$updateToLatestVersionButton.toggle(utils.isUpdateAvailable(latestVersion, glob.triliumVersion));
this.$updateToLatestVersionButton.find(".version-text").text(`Version ${latestVersion} is available, click to download.`); this.$updateToLatestVersionButton.find(".version-text").text(`Version ${latestVersion} is available, click to download.`);
} }

View File

@@ -1,5 +1,6 @@
import { t } from "../../services/i18n.js"; import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js"; import BasicWidget from "../basic_widget.js";
import utils from "../../services/utils.js";
const TPL = ` const TPL = `
<div style="display: none;"> <div style="display: none;">
@@ -34,6 +35,6 @@ export default class UpdateAvailableWidget extends BasicWidget {
} }
updateVersionStatus(latestVersion) { updateVersionStatus(latestVersion) {
this.$widget.toggle(latestVersion > glob.triliumVersion); this.$widget.toggle(utils.isUpdateAvailable(latestVersion, glob.triliumVersion));
} }
} }

View File

@@ -216,7 +216,7 @@ export default class RibbonContainer extends NoteContextAwareWidget {
this.$tabContainer.empty(); this.$tabContainer.empty();
for (const ribbonWidget of this.ribbonWidgets) { for (const ribbonWidget of this.ribbonWidgets) {
const ret = ribbonWidget.getTitle(note); const ret = await ribbonWidget.getTitle(note);
if (!ret.show) { if (!ret.show) {
continue; continue;
@@ -351,6 +351,16 @@ export default class RibbonContainer extends NoteContextAwareWidget {
} }
} }
/**
* Executed as soon as the user presses the "Edit" floating button in a read-only text note.
*
* <p>
* We need to refresh the ribbon for cases such as the classic editor which relies on the read-only state.
*/
readOnlyTemporarilyDisabledEvent() {
this.refresh();
}
getActiveRibbonWidget() { getActiveRibbonWidget() {
return this.ribbonWidgets.find(ch => ch.componentId === this.lastActiveComponentId) return this.ribbonWidgets.find(ch => ch.componentId === this.lastActiveComponentId)
} }

View File

@@ -25,7 +25,7 @@ const TPL = `
</div> </div>
<div class="delete-notes-list-wrapper"> <div class="delete-notes-list-wrapper">
<h4>${t('delete_notes.notes_to_be_deleted')} (<span class="deleted-notes-count"></span>)</h4> <h4>${t('delete_notes.notes_to_be_deleted', { noteCount: '<span class="deleted-notes-count"></span>' })}</h4>
<ul class="delete-notes-list" style="max-height: 200px; overflow: auto;"></ul> <ul class="delete-notes-list" style="max-height: 200px; overflow: auto;"></ul>
</div> </div>
@@ -36,7 +36,7 @@ const TPL = `
<div class="broken-relations-wrapper"> <div class="broken-relations-wrapper">
<div class="alert alert-danger"> <div class="alert alert-danger">
<h4>${t('delete_notes.broken_relations_to_be_deleted')} (<span class="broke-relations-count"></span>)</h4> <h4>${t('delete_notes.broken_relations_to_be_deleted', { relationCount: '<span class="broke-relations-count"></span>'})}</h4>
<ul class="broken-relations-list" style="max-height: 200px; overflow: auto;"></ul> <ul class="broken-relations-list" style="max-height: 200px; overflow: auto;"></ul>
</div> </div>
@@ -126,11 +126,11 @@ export default class DeleteNotesDialog extends BasicWidget {
for (const attr of response.brokenRelations) { for (const attr of response.brokenRelations) {
this.$brokenRelationsList.append( this.$brokenRelationsList.append(
$("<li>") $("<li>").html(t("delete_notes.deleted_relation_text", {
.append(`${t('delete_notes.note')} `) note: (await linkService.createLink(attr.value)).html(),
.append(await linkService.createLink(attr.value)) relation: `<code>${attr.name}</code>`,
.append(` ${t('delete_notes.to_be_deleted', {attrName: attr.name})} `) source: (await linkService.createLink(attr.noteId)).html()
.append(await linkService.createLink(attr.noteId)) }))
); );
} }
} }

View File

@@ -50,6 +50,9 @@ class NoteContextAwareWidget extends BasicWidget {
/** /**
* @inheritdoc * @inheritdoc
* *
* <p>
* If the widget is not enabled, it will not receive `refreshWithNote` updates.
*
* @returns {boolean} true when an active note exists * @returns {boolean} true when an active note exists
*/ */
isEnabled() { isEnabled() {

View File

@@ -30,6 +30,7 @@ import ContentWidgetTypeWidget from "./type_widgets/content_widget.js";
import AttachmentListTypeWidget from "./type_widgets/attachment_list.js"; import AttachmentListTypeWidget from "./type_widgets/attachment_list.js";
import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js"; import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
import MindMapWidget from "./type_widgets/mind_map.js"; import MindMapWidget from "./type_widgets/mind_map.js";
import { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js";
const TPL = ` const TPL = `
<div class="note-detail"> <div class="note-detail">
@@ -255,6 +256,19 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
} }
const {assetPath} = window.glob; const {assetPath} = window.glob;
const cssToLoad = [
`${assetPath}/node_modules/codemirror/lib/codemirror.css`,
`${assetPath}/libraries/ckeditor/ckeditor-content.css`,
`${assetPath}/node_modules/bootstrap/dist/css/bootstrap.min.css`,
`${assetPath}/node_modules/katex/dist/katex.min.css`,
`${assetPath}/stylesheets/print.css`,
`${assetPath}/stylesheets/relation_map.css`,
`${assetPath}/stylesheets/ckeditor-theme.css`
];
if (isSyntaxHighlightEnabled()) {
cssToLoad.push(getStylesheetUrl("default:vs"));
}
this.$widget.find('.note-detail-printable:visible').printThis({ this.$widget.find('.note-detail-printable:visible').printThis({
header: $("<div>") header: $("<div>")
@@ -273,15 +287,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
</script> </script>
`, `,
importCSS: false, importCSS: false,
loadCSS: [ loadCSS: cssToLoad,
`${assetPath}/node_modules/codemirror/lib/codemirror.css`,
`${assetPath}/libraries/ckeditor/ckeditor-content.css`,
`${assetPath}/node_modules/bootstrap/dist/css/bootstrap.min.css`,
`${assetPath}/node_modules/katex/dist/katex.min.css`,
`${assetPath}/stylesheets/print.css`,
`${assetPath}/stylesheets/relation_map.css`,
`${assetPath}/stylesheets/ckeditor-theme.css`
],
debug: true debug: true
}); });
} }

View File

@@ -101,7 +101,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
this.$noteTypeDropdown.append($typeLink); this.$noteTypeDropdown.append($typeLink);
} }
for (const mimeType of await mimeTypesService.getMimeTypes()) { for (const mimeType of mimeTypesService.getMimeTypes()) {
if (!mimeType.enabled) { if (!mimeType.enabled) {
continue; continue;
} }
@@ -128,7 +128,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
async findTypeTitle(type, mime) { async findTypeTitle(type, mime) {
if (type === 'code') { if (type === 'code') {
const mimeTypes = await mimeTypesService.getMimeTypes(); const mimeTypes = mimeTypesService.getMimeTypes();
const found = mimeTypes.find(mt => mt.mime === mime); const found = mimeTypes.find(mt => mt.mime === mime);
return found ? found.title : mime; return found ? found.title : mime;

View File

@@ -0,0 +1,83 @@
import { t } from "../../services/i18n.js";
import options from "../../services/options.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `\
<div class="classic-toolbar-widget"></div>
<style>
.classic-toolbar-widget {
--ck-color-toolbar-background: transparent;
--ck-color-button-default-background: transparent;
--ck-color-button-default-disabled-background: transparent;
min-height: 39px;
}
.classic-toolbar-widget .ck.ck-toolbar {
border: none;
}
.classic-toolbar-widget .ck.ck-button.ck-disabled {
opacity: 0.3;
}
body.mobile .classic-toolbar-widget {
position: relative;
overflow-x: auto;
}
body.mobile .classic-toolbar-widget .ck.ck-toolbar {
position: absolute;
}
</style>
`;
/**
* Handles the editing toolbar when the CKEditor is in decoupled mode.
*
* <p>
* This toolbar is only enabled if the user has selected the classic CKEditor.
*
* <p>
* The ribbon item is active by default for text notes, as long as they are not in read-only mode.
*/
export default class ClassicEditorToolbar extends NoteContextAwareWidget {
get name() {
return "classicEditor";
}
get toggleCommand() {
return "toggleRibbonTabClassicEditor";
}
doRender() {
this.$widget = $(TPL);
this.contentSized();
}
async getTitle() {
return {
show: await this.#shouldDisplay(),
activate: true,
title: t("classic_editor_toolbar.title"),
icon: "bx bx-edit-alt"
};
}
async #shouldDisplay() {
if (options.get("textNoteEditorType") !== "ckeditor-classic") {
return false;
}
if (this.note.type !== "text") {
return false;
}
if (await this.noteContext.isReadOnly()) {
return false;
}
return true;
}
}

View File

@@ -260,8 +260,10 @@ export default class TabRowWidget extends BasicWidget {
y: e.pageY, y: e.pageY,
items: [ items: [
{title: t('tab_row.close'), command: "closeTab", uiIcon: "bx bx-x"}, {title: t('tab_row.close'), command: "closeTab", uiIcon: "bx bx-x"},
{title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-x"}, {title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.length !== 1},
{title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-x"}, {title: t('tab_row.close_right_tabs'), command: "closeRightTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.at(-1).ntxId !== ntxId},
{title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-empty"},
{ title: "----" },
{title: t('tab_row.move_tab_to_new_window'), command: "moveTabToNewWindow", uiIcon: "bx bx-window-open"} {title: t('tab_row.move_tab_to_new_window'), command: "moveTabToNewWindow", uiIcon: "bx bx-window-open"}
], ],
selectMenuItemHandler: ({command}) => { selectMenuItemHandler: ({command}) => {

View File

@@ -4,8 +4,15 @@ import froca from "../../services/froca.js";
import linkService from "../../services/link.js"; import linkService from "../../services/link.js";
import contentRenderer from "../../services/content_renderer.js"; import contentRenderer from "../../services/content_renderer.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import options from "../../services/options.js";
export default class AbstractTextTypeWidget extends TypeWidget { export default class AbstractTextTypeWidget extends TypeWidget {
doRender() {
super.doRender();
this.refreshCodeBlockOptions();
}
setupImageOpening(singleClickOpens) { setupImageOpening(singleClickOpens) {
this.$widget.on("dblclick", "img", e => this.openImageInCurrentTab($(e.target))); this.$widget.on("dblclick", "img", e => this.openImageInCurrentTab($(e.target)));
@@ -33,8 +40,8 @@ export default class AbstractTextTypeWidget extends TypeWidget {
} }
} }
openImageInNewTab($img) { async openImageInNewTab($img) {
const { noteId, viewScope } = this.parseFromImage($img); const { noteId, viewScope } = await this.parseFromImage($img);
if (noteId) { if (noteId) {
appContext.tabManager.openTabWithNoteWithHoisting(noteId, { viewScope }); appContext.tabManager.openTabWithNoteWithHoisting(noteId, { viewScope });
@@ -108,4 +115,16 @@ export default class AbstractTextTypeWidget extends TypeWidget {
}); });
} }
} }
refreshCodeBlockOptions() {
const wordWrap = options.is("codeBlockWordWrap");
this.$widget.toggleClass("word-wrap", wordWrap);
}
async entitiesReloadedEvent({loadResults}) {
if (loadResults.isOptionReloaded("codeBlockWordWrap")) {
this.refreshCodeBlockOptions();
}
}
} }

View File

@@ -0,0 +1,354 @@
/*
* This code is an adaptation of https://github.com/antoniotejada/Trilium-SyntaxHighlightWidget with additional improvements, such as:
*
* - support for selecting the language manually;
* - support for determining the language automatically, if a special language is selected ("Auto-detected");
* - limit for highlighting.
*
* TODO: Generally this class can be done directly in the CKEditor repository.
*/
import library_loader from "../../../services/library_loader.js";
import mime_types from "../../../services/mime_types.js";
import { isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
export async function initSyntaxHighlighting(editor) {
if (!isSyntaxHighlightEnabled) {
return;
}
await library_loader.requireLibrary(library_loader.HIGHLIGHT_JS);
initTextEditor(editor);
}
const HIGHLIGHT_MAX_BLOCK_COUNT = 500;
const tag = "SyntaxHighlightWidget";
const debugLevels = ["error", "warn", "info", "log", "debug"];
const debugLevel = "debug";
let warn = function() {};
if (debugLevel >= debugLevels.indexOf("warn")) {
warn = console.warn.bind(console, tag + ": ");
}
let info = function() {};
if (debugLevel >= debugLevels.indexOf("info")) {
info = console.info.bind(console, tag + ": ");
}
let log = function() {};
if (debugLevel >= debugLevels.indexOf("log")) {
log = console.log.bind(console, tag + ": ");
}
let dbg = function() {};
if (debugLevel >= debugLevels.indexOf("debug")) {
dbg = console.debug.bind(console, tag + ": ");
}
function assert(e, msg) {
console.assert(e, tag + ": " + msg);
}
// TODO: Should this be scoped to note?
let markerCounter = 0;
function initTextEditor(textEditor) {
log("initTextEditor");
let widget = this;
const document = textEditor.model.document;
// Create a conversion from model to view that converts
// hljs:hljsClassName:uniqueId into a span with hljsClassName
// See the list of hljs class names at
// https://github.com/highlightjs/highlight.js/blob/6b8c831f00c4e87ecd2189ebbd0bb3bbdde66c02/docs/css-classes-reference.rst
textEditor.conversion.for('editingDowncast').markerToHighlight( {
model: "hljs",
view: ( { markerName } ) => {
dbg("markerName " + markerName);
// markerName has the pattern addMarker:cssClassName:uniqueId
const [ , cssClassName, id ] = markerName.split( ':' );
// The original code at
// https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-find-and-replace/src/findandreplaceediting.js
// has this comment
// Marker removal from the view has a bug:
// https://github.com/ckeditor/ckeditor5/issues/7499
// A minimal option is to return a new object for each converted marker...
return {
name: 'span',
classes: [ cssClassName ],
attributes: {
// ...however, adding a unique attribute should be future-proof..
'data-syntax-result': id
},
};
}
});
// XXX This is done at BalloonEditor.create time, so it assumes this
// document is always attached to this textEditor, empirically that
// seems to be the case even with two splits showing the same note,
// it's not clear if CKEditor5 has apis to attach and detach
// documents around
document.registerPostFixer(function(writer) {
log("postFixer");
// Postfixers are a simpler way of tracking changes than onchange
// See
// https://github.com/ckeditor/ckeditor5/blob/b53d2a4b49679b072f4ae781ac094e7e831cfb14/packages/ckeditor5-block-quote/src/blockquoteediting.js#L54
const changes = document.differ.getChanges();
let dirtyCodeBlocks = new Set();
for (const change of changes) {
dbg("change " + JSON.stringify(change));
if ((change.type == "insert") && (change.name == "codeBlock")) {
// A new code block was inserted
const codeBlock = change.position.nodeAfter;
// Even if it's a new codeblock, it needs dirtying in case
// it already has children, like when pasting one or more
// full codeblocks, undoing a delete, changing the language,
// etc (the postfixer won't get later changes for those).
log("dirtying inserted codeBlock " + JSON.stringify(codeBlock.toJSON()));
dirtyCodeBlocks.add(codeBlock);
} else if (change.type == "remove" && (change.name == "codeBlock")) {
// An existing codeblock was removed, do nothing. Note the
// node is no longer in the editor so the codeblock cannot
// be inspected here. No need to dirty the codeblock since
// it has been removed
log("removing codeBlock at path " + JSON.stringify(change.position.toJSON()));
} else if (((change.type == "remove") || (change.type == "insert")) &&
change.position.parent.is('element', 'codeBlock')) {
// Text was added or removed from the codeblock, force a
// highlight
const codeBlock = change.position.parent;
log("dirtying codeBlock " + JSON.stringify(codeBlock.toJSON()));
dirtyCodeBlocks.add(codeBlock);
}
}
for (let codeBlock of dirtyCodeBlocks) {
highlightCodeBlock(codeBlock, writer);
}
// Adding markers doesn't modify the document data so no need for
// postfixers to run again
return false;
});
// This assumes the document is empty and a explicit call to highlight
// is not necessary here. Empty documents have a single children of type
// paragraph with no text
assert((document.getRoot().childCount == 1) &&
(document.getRoot().getChild(0).name == "paragraph") &&
document.getRoot().getChild(0).isEmpty);
}
/**
* This implements highlighting via ephemeral markers (not stored in the
* document).
*
* XXX Another option would be to use formatting markers, which would have
* the benefit of making it work for readonly notes. On the flip side,
* the formatting would be stored with the note and it would need a
* way to remove that formatting when editing back the note.
*/
function highlightCodeBlock(codeBlock, writer) {
log("highlighting codeblock " + JSON.stringify(codeBlock.toJSON()));
const model = codeBlock.root.document.model;
// Can't invoke addMarker with an already existing marker name,
// clear all highlight markers first. Marker names follow the
// pattern hljs:cssClassName:uniqueId, eg hljs:hljs-comment:1
const codeBlockRange = model.createRangeIn(codeBlock);
for (const marker of model.markers.getMarkersIntersectingRange(codeBlockRange)) {
dbg("removing marker " + marker.name);
writer.removeMarker(marker.name);
}
// Don't highlight if plaintext (note this needs to remove the markers
// above first, in case this was a switch from non plaintext to
// plaintext)
const mimeType = codeBlock.getAttribute("language");
if (mimeType == "text-plain") {
// XXX There's actually a plaintext language that could be used
// if you wanted the non-highlight formatting of
// highlight.js css applied, see
// https://github.com/highlightjs/highlight.js/issues/700
log("not highlighting plaintext codeblock");
return;
}
// Find the corresponding language for the given mimetype.
const highlightJsLanguage = mime_types.getHighlightJsNameForMime(mimeType);
if (mimeType !== mime_types.MIME_TYPE_AUTO && !highlightJsLanguage) {
console.warn(`Unsupported highlight.js for mime type ${mimeType}.`);
return;
}
// Don't highlight if the code is too big, as the typing performance will be highly degraded.
if (codeBlock.childCount >= HIGHLIGHT_MAX_BLOCK_COUNT) {
return;
}
// highlight.js needs the full text without HTML tags, eg for the
// text
// #include <stdio.h>
// the highlighted html is
// <span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">&lt;stdio.h&gt;</span></span>
// But CKEditor codeblocks have <br> instead of \n
// Do a two pass algorithm:
// - First pass collect the codeblock children text, change <br> to
// \n
// - invoke highlight.js on the collected text generating html
// - Second pass parse the highlighted html spans and match each
// char to the CodeBlock text. Issue addMarker CKEditor calls for
// each span
// XXX This is brittle and assumes how highlight.js generates html
// (blanks, which characters escapes, etc), a better approach
// would be to use highlight.js beta api TreeTokenizer?
// Collect all the text nodes to pass to the highlighter Text is
// direct children of the codeBlock
let text = "";
for (let i = 0; i < codeBlock.childCount; ++i) {
let child = codeBlock.getChild(i);
// We only expect text and br elements here
if (child.is("$text")) {
dbg("child text " + child.data);
text += child.data;
} else if (child.is("element") &&
(child.name == "softBreak")) {
dbg("softBreak");
text += "\n";
} else {
warn("Unkown child " + JSON.stringify(child.toJSON()));
}
}
let highlightRes;
if (mimeType === mime_types.MIME_TYPE_AUTO) {
highlightRes = hljs.highlightAuto(text);
} else {
highlightRes = hljs.highlight(text, { language: highlightJsLanguage });
}
dbg("text\n" + text);
dbg("html\n" + highlightRes.value);
let iHtml = 0;
let html = highlightRes.value;
let spanStack = [];
let iChild = -1;
let childText = "";
let child = null;
let iChildText = 0;
while (iHtml < html.length) {
// Advance the text index and fetch a new child if necessary
if (iChildText >= childText.length) {
iChild++;
if (iChild < codeBlock.childCount) {
dbg("Fetching child " + iChild);
child = codeBlock.getChild(iChild);
if (child.is("$text")) {
dbg("child text " + child.data);
childText = child.data;
iChildText = 0;
} else if (child.is("element", "softBreak")) {
dbg("softBreak");
iChildText = 0;
childText = "\n";
} else {
warn("child unknown!!!");
}
} else {
// Don't bail if beyond the last children, since there's
// still html text, it must be a closing span tag that
// needs to be dealt with below
childText = "";
}
}
// This parsing is made slightly simpler and faster by only
// expecting <span> and </span> tags in the highlighted html
if ((html[iHtml] == "<") && (html[iHtml+1] != "/")) {
// new span, note they can be nested eg C preprocessor lines
// are inside a hljs-meta span, hljs-title function names
// inside a hljs-function span, etc
let iStartQuot = html.indexOf("\"", iHtml+1);
let iEndQuot = html.indexOf("\"", iStartQuot+1);
let className = html.slice(iStartQuot+1, iEndQuot);
// XXX highlight js uses scope for Python "title function_",
// etc for now just use the first style only
// See https://highlightjs.readthedocs.io/en/latest/css-classes-reference.html#a-note-on-scopes-with-sub-scopes
let iBlank = className.indexOf(" ");
if (iBlank > 0) {
className = className.slice(0, iBlank);
}
dbg("Found span start " + className);
iHtml = html.indexOf(">", iHtml) + 1;
// push the span
let posStart = writer.createPositionAt(codeBlock, child.startOffset + iChildText);
spanStack.push({ "className" : className, "posStart": posStart});
} else if ((html[iHtml] == "<") && (html[iHtml+1] == "/")) {
// Done with this span, pop the span and mark the range
iHtml = html.indexOf(">", iHtml+1) + 1;
let stackTop = spanStack.pop();
let posStart = stackTop.posStart;
let className = stackTop.className;
let posEnd = writer.createPositionAt(codeBlock, child.startOffset + iChildText);
let range = writer.createRange(posStart, posEnd);
let markerName = "hljs:" + className + ":" + markerCounter;
// Use an incrementing number for the uniqueId, random of
// 10000000 is known to cause collisions with a few
// codeblocks of 10s of lines on real notes (each line is
// one or more marker).
// Wrap-around for good measure so all numbers are positive
// XXX Another option is to catch the exception and retry or
// go through the markers and get the largest + 1
markerCounter = (markerCounter + 1) & 0xFFFFFF;
dbg("Found span end " + className);
dbg("Adding marker " + markerName + ": " + JSON.stringify(range.toJSON()));
writer.addMarker(markerName, {"range": range, "usingOperation": false});
} else {
// Text, we should also have text in the children
assert(
((iChild < codeBlock.childCount) && (iChildText < childText.length)),
"Found text in html with no corresponding child text!!!!"
);
if (html[iHtml] == "&") {
// highlight.js only encodes
// .replace(/&/g, '&amp;')
// .replace(/</g, '&lt;')
// .replace(/>/g, '&gt;')
// .replace(/"/g, '&quot;')
// .replace(/'/g, '&#x27;');
// see https://github.com/highlightjs/highlight.js/blob/7addd66c19036eccd7c602af61f1ed84d215c77d/src/lib/utils.js#L5
let iAmpEnd = html.indexOf(";", iHtml);
dbg(html.slice(iHtml, iAmpEnd));
iHtml = iAmpEnd + 1;
} else {
// regular text
dbg(html[iHtml]);
iHtml++;
}
iChildText++;
}
}
}

View File

@@ -34,6 +34,8 @@ import BackendLogWidget from "./content/backend_log.js";
import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js"; import AttachmentErasureTimeoutOptions from "./options/other/attachment_erasure_timeout.js";
import RibbonOptions from "./options/appearance/ribbon.js"; import RibbonOptions from "./options/appearance/ribbon.js";
import LocalizationOptions from "./options/appearance/i18n.js"; import LocalizationOptions from "./options/appearance/i18n.js";
import CodeBlockOptions from "./options/appearance/code_block.js";
import EditorOptions from "./options/text_notes/editor.js";
const TPL = `<div class="note-detail-content-widget note-detail-printable"> const TPL = `<div class="note-detail-content-widget note-detail-printable">
<style> <style>
@@ -59,6 +61,7 @@ const CONTENT_WIDGETS = {
LocalizationOptions, LocalizationOptions,
ThemeOptions, ThemeOptions,
FontsOptions, FontsOptions,
CodeBlockOptions,
ZoomFactorOptions, ZoomFactorOptions,
NativeTitleBarOptions, NativeTitleBarOptions,
MaxContentWidthOptions, MaxContentWidthOptions,
@@ -66,6 +69,7 @@ const CONTENT_WIDGETS = {
], ],
_optionsShortcuts: [ KeyboardShortcutsOptions ], _optionsShortcuts: [ KeyboardShortcutsOptions ],
_optionsTextNotes: [ _optionsTextNotes: [
EditorOptions,
HeadingStyleOptions, HeadingStyleOptions,
TableOfContentsOptions, TableOfContentsOptions,
HighlightsListOptions, HighlightsListOptions,

View File

@@ -10,6 +10,8 @@ import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
import link from "../../services/link.js"; import link from "../../services/link.js";
import appContext from "../../components/app_context.js"; import appContext from "../../components/app_context.js";
import dialogService from "../../services/dialog.js"; import dialogService from "../../services/dialog.js";
import { initSyntaxHighlighting } from "./ckeditor/syntax_highlight.js";
import options from "../../services/options.js";
const ENABLE_INSPECTOR = false; const ENABLE_INSPECTOR = false;
@@ -87,6 +89,29 @@ const TPL = `
</div> </div>
`; `;
function buildListOfLanguages() {
const userLanguages = (mimeTypesService.getMimeTypes())
.filter(mt => mt.enabled)
.map(mt => ({
language: mimeTypesService.normalizeMimeTypeForCKEditor(mt.mime),
label: mt.title
}));
return [
{
language: mimeTypesService.MIME_TYPE_AUTO,
label: t("editable-text.auto-detect-language")
},
...userLanguages
];
}
/**
* The editor can operate into two distinct modes:
*
* - Ballon block mode, in which there is a floating toolbar for the selected text, but another floating button for the entire block (i.e. paragraph).
* - Decoupled mode, in which the editing toolbar is actually added on the client side (in {@link ClassicEditorToolbar}), see https://ckeditor.com/docs/ckeditor5/latest/examples/framework/bottom-toolbar-editor.html for an example on how the decoupled editor works.
*/
export default class EditableTextTypeWidget extends AbstractTextTypeWidget { export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
static getType() { return "editableText"; } static getType() { return "editableText"; }
@@ -105,21 +130,17 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
async initEditor() { async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
const isClassicEditor = (options.get("textNoteEditorType") === "ckeditor-classic")
const editorClass = (isClassicEditor ? CKEditor.DecoupledEditor : CKEditor.BalloonEditor);
const codeBlockLanguages = const codeBlockLanguages = buildListOfLanguages();
(await mimeTypesService.getMimeTypes())
.filter(mt => mt.enabled)
.map(mt => ({
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
label: mt.title
}));
// CKEditor since version 12 needs the element to be visible before initialization. At the same time, // CKEditor since version 12 needs the element to be visible before initialization. At the same time,
// we want to avoid flicker - i.e., show editor only once everything is ready. That's why we have separate // we want to avoid flicker - i.e., show editor only once everything is ready. That's why we have separate
// display of $widget in both branches. // display of $widget in both branches.
this.$widget.show(); this.$widget.show();
this.watchdog = new EditorWatchdog(BalloonEditor, { this.watchdog = new CKEditor.EditorWatchdog(editorClass, {
// An average number of milliseconds between the last editor errors (defaults to 5000). // An average number of milliseconds between the last editor errors (defaults to 5000).
// When the period of time between errors is lower than that and the crashNumberLimit // When the period of time between errors is lower than that and the crashNumberLimit
// is also reached, the watchdog changes its state to crashedPermanently, and it stops // is also reached, the watchdog changes its state to crashedPermanently, and it stops
@@ -155,7 +176,22 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
}); });
this.watchdog.setCreator(async (elementOrData, editorConfig) => { this.watchdog.setCreator(async (elementOrData, editorConfig) => {
const editor = await BalloonEditor.create(elementOrData, editorConfig); const editor = await editorClass.create(elementOrData, editorConfig);
await initSyntaxHighlighting(editor);
if (isClassicEditor) {
let $classicToolbarWidget;
if (!utils.isMobile()) {
const $parentSplit = this.$widget.parents(".note-split.type-text");
$classicToolbarWidget = $parentSplit.find("> .ribbon-container .classic-toolbar-widget");
} else {
$classicToolbarWidget = $("body").find(".classic-toolbar-widget");
}
$classicToolbarWidget.empty();
$classicToolbarWidget[0].appendChild(editor.ui.view.toolbar.element);
}
editor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate()); editor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());

View File

@@ -18,7 +18,6 @@ const TPL = `
width: 130px; width: 130px;
text-align: center; text-align: center;
margin: 10px; margin: 10px;
padding; 10px;
border: 1px transparent solid; border: 1px transparent solid;
} }
@@ -27,20 +26,35 @@ const TPL = `
border: 1px solid var(--main-border-color); border: 1px solid var(--main-border-color);
} }
.note-detail-empty-results .aa-dropdown-menu {
max-height: 50vh;
overflow: scroll;
border: var(--bs-border-width) solid var(--bs-border-color);
border-top: 0;
}
.empty-tab-search .note-autocomplete-input {
border-bottom-left-radius: 0;
}
.empty-tab-search .input-clearer-button {
border-bottom-right-radius: 0;
}
.workspace-icon { .workspace-icon {
text-align: center; text-align: center;
font-size: 500%; font-size: 500%;
} }
</style> </style>
<div class="form-group"> <div class="workspace-notes"></div>
<div class="form-group empty-tab-search">
<label>${t('empty.open_note_instruction')}</label> <label>${t('empty.open_note_instruction')}</label>
<div class="input-group"> <div class="input-group mt-1">
<input class="form-control note-autocomplete" placeholder="${t('empty.search_placeholder')}"> <input class="form-control note-autocomplete" placeholder="${t('empty.search_placeholder')}">
</div> </div>
</div> </div>
<div class="note-detail-empty-results"></div>
<div class="workspace-notes"></div>
</div>`; </div>`;
export default class EmptyTypeWidget extends TypeWidget { export default class EmptyTypeWidget extends TypeWidget {
@@ -51,10 +65,12 @@ export default class EmptyTypeWidget extends TypeWidget {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$autoComplete = this.$widget.find(".note-autocomplete"); this.$autoComplete = this.$widget.find(".note-autocomplete");
this.$results = this.$widget.find(".note-detail-empty-results");
noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, { noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, {
hideGoToSelectedNoteButton: true, hideGoToSelectedNoteButton: true,
allowCreatingNotes: true allowCreatingNotes: true,
container: this.$results
}) })
.on('autocomplete:noteselected', function(event, suggestion, dataset) { .on('autocomplete:noteselected', function(event, suggestion, dataset) {
if (!suggestion.notePath) { if (!suggestion.notePath) {
@@ -66,6 +82,7 @@ export default class EmptyTypeWidget extends TypeWidget {
this.$workspaceNotes = this.$widget.find('.workspace-notes'); this.$workspaceNotes = this.$widget.find('.workspace-notes');
noteAutocompleteService.showRecentNotes(this.$autoComplete);
super.doRender(); super.doRender();
} }

View File

@@ -0,0 +1,119 @@
import { t } from "../../../../services/i18n.js";
import library_loader from "../../../../services/library_loader.js";
import server from "../../../../services/server.js";
import OptionsWidget from "../options_widget.js";
const SAMPLE_LANGUAGE = "javascript";
const SAMPLE_CODE = `\
const n = 10;
greet(n); // Print "Hello World" for n times
/**
* Displays a "Hello World!" message for a given amount of times, on the standard console. The "Hello World!" text will be displayed once per line.
*
* @param {number} times The number of times to print the \`Hello World!\` message.
*/
function greet(times) {
for (let i = 0; i++; i < times) {
console.log("Hello World!");
}
}
`
const TPL = `
<div class="options-section">
<h4>${t("highlighting.title")}</h4>
<p>${t("highlighting.description")}</p>
<div class="form-group row">
<div class="col-6">
<label>${t("highlighting.color-scheme")}</label>
<select class="theme-select form-select"></select>
</div>
<div class="col-6 side-checkbox">
<label class="form-check">
<input type="checkbox" class="word-wrap form-check-input" />
${t("code_block.word_wrapping")}
</label>
</div>
</div>
<div class="form-group row">
<div class="note-detail-readonly-text-content ck-content code-sample-wrapper">
<pre class="hljs"><code class="code-sample">${SAMPLE_CODE}</code></pre>
</div>
</div>
<style>
.code-sample-wrapper {
margin-top: 1em;
}
</style>
</div>
`;
/**
* Contains appearance settings for code blocks within text notes, such as the theme for the syntax highlighter.
*/
export default class CodeBlockOptions extends OptionsWidget {
doRender() {
this.$widget = $(TPL);
this.$themeSelect = this.$widget.find(".theme-select");
this.$themeSelect.on("change", async () => {
const newTheme = this.$themeSelect.val();
library_loader.loadHighlightingTheme(newTheme);
await server.put(`options/codeBlockTheme/${newTheme}`);
});
this.$wordWrap = this.$widget.find("input.word-wrap");
this.$wordWrap.on("change", () => this.updateCheckboxOption("codeBlockWordWrap", this.$wordWrap));
// Set up preview
this.$sampleEl = this.$widget.find(".code-sample");
}
#setupPreview(shouldEnableSyntaxHighlight) {
const text = SAMPLE_CODE;
if (shouldEnableSyntaxHighlight) {
library_loader
.requireLibrary(library_loader.HIGHLIGHT_JS)
.then(() => {
const highlightedText = hljs.highlight(text, {
language: SAMPLE_LANGUAGE
});
this.$sampleEl.html(highlightedText.value);
});
} else {
this.$sampleEl.text(text);
}
}
async optionsLoaded(options) {
const themeGroups = await server.get("options/codeblock-themes");
this.$themeSelect.empty();
for (const [ key, themes ] of Object.entries(themeGroups)) {
const $group = (key ? $("<optgroup>").attr("label", key) : null);
for (const theme of themes) {
const option = $("<option>")
.attr("value", theme.val)
.text(theme.title);
if ($group) {
$group.append(option);
} else {
this.$themeSelect.append(option);
}
}
this.$themeSelect.append($group);
}
this.$themeSelect.val(options.codeBlockTheme);
this.setCheckboxState(this.$wordWrap, options.codeBlockWordWrap);
this.$widget.closest(".note-detail-printable").toggleClass("word-wrap", options.codeBlockWordWrap === "true");
this.#setupPreview(options.codeBlockTheme !== "none");
}
}

View File

@@ -133,14 +133,17 @@ export default class FontsOptions extends OptionsWidget {
this.$widget.find(".reload-frontend-button").on("click", () => utils.reloadFrontendApp("changes from appearance options")); this.$widget.find(".reload-frontend-button").on("click", () => utils.reloadFrontendApp("changes from appearance options"));
} }
isEnabled() {
return this._isEnabled;
}
async optionsLoaded(options) { async optionsLoaded(options) {
if (options.overrideThemeFonts !== 'true') { this._isEnabled = (options.overrideThemeFonts === 'true');
this.toggleInt(false); this.toggleInt(this._isEnabled);
if (!this._isEnabled) {
return; return;
} }
this.toggleInt(true);
this.$mainFontSize.val(options.mainFontSize); this.$mainFontSize.val(options.mainFontSize);
this.fillFontFamilyOptions(this.$mainFontFamily, options.mainFontFamily); this.fillFontFamilyOptions(this.$mainFontFamily, options.mainFontFamily);

View File

@@ -13,11 +13,11 @@ const TPL = `
<select class="theme-select form-select"></select> <select class="theme-select form-select"></select>
</div> </div>
<div class="col-6"> <div class="col-6 side-checkbox">
<label>${t("theme.override_theme_fonts_label")}</label> <label class="form-check">
<div class="form-check">
<input type="checkbox" class="override-theme-fonts form-check-input"> <input type="checkbox" class="override-theme-fonts form-check-input">
</div> ${t("theme.override_theme_fonts_label")}
</label>
</div> </div>
</div> </div>
</div>`; </div>`;

View File

@@ -19,9 +19,24 @@ export default class CodeMimeTypesOptions extends OptionsWidget {
async optionsLoaded(options) { async optionsLoaded(options) {
this.$mimeTypes.empty(); this.$mimeTypes.empty();
let index = -1;
let prevInitial = "";
for (const mimeType of await mimeTypesService.getMimeTypes()) { for (const mimeType of mimeTypesService.getMimeTypes()) {
const id = "code-mime-type-" + (idCtr++); const id = "code-mime-type-" + (idCtr++);
index++;
// Append a heading to group items by the first letter, excepting for the
// first item ("Plain Text"). Note: this code assumes the items are already
// in alphabetical ordered.
if (index > 0) {
const initial = mimeType.title.charAt(0).toUpperCase();
if (initial !== prevInitial) {
this.$mimeTypes.append($("<h5>").text(initial));
prevInitial = initial;
}
}
this.$mimeTypes.append($("<li>") this.$mimeTypes.append($("<li>")
.append($('<input type="checkbox" class="form-check-input">') .append($('<input type="checkbox" class="form-check-input">')

View File

@@ -44,6 +44,20 @@ export default class OptionsWidget extends NoteContextAwareWidget {
optionsLoaded(options) {} optionsLoaded(options) {}
async refresh() {
this.toggleInt(this.isEnabled());
try {
await this.refreshWithNote(this.note);
} catch (e) {
// Ignore errors when user is refreshing or navigating away.
if (e === "rejected by browser") {
return;
}
throw e;
}
}
async refreshWithNote(note) { async refreshWithNote(note) {
const options = await server.get('options'); const options = await server.get('options');

View File

@@ -0,0 +1,30 @@
import { t } from "../../../../services/i18n.js";
import utils from "../../../../services/utils.js";
import OptionsWidget from "../options_widget.js";
const TPL = `
<div class="options-section">
<h4>${t("editing.editor_type.label")}</h4>
<select class="editor-type-select form-select">
<option value="ckeditor-balloon">${t("editing.editor_type.floating")}</option>
<option value="ckeditor-classic">${t("editing.editor_type.fixed")}</option>
</select>
</div>`;
export default class EditorOptions extends OptionsWidget {
doRender() {
this.$widget = $(TPL);
this.$body = $("body");
this.$editorType = this.$widget.find(".editor-type-select");
this.$editorType.on('change', async () => {
const newEditorType = this.$editorType.val();
await this.updateOption('textNoteEditorType', newEditorType);
utils.reloadFrontendApp("editor type change");
});
}
async optionsLoaded(options) {
this.$editorType.val(options.textNoteEditorType);
}
}

View File

@@ -1,5 +1,6 @@
import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
import libraryLoader from "../../services/library_loader.js"; import libraryLoader from "../../services/library_loader.js";
import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
const TPL = ` const TPL = `
<div class="note-detail-readonly-text note-detail-printable"> <div class="note-detail-readonly-text note-detail-printable">
@@ -110,6 +111,8 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
renderMathInElement(this.$content[0], {trust: true}); renderMathInElement(this.$content[0], {trust: true});
} }
await applySyntaxHighlight(this.$content);
} }
async refreshIncludedNoteEvent({noteId}) { async refreshIncludedNoteEvent({noteId}) {

View File

@@ -20,4 +20,10 @@
vertical-align: middle; vertical-align: middle;
} }
pre {
box-shadow: unset !important;
border: .75pt solid gray !important;
border-radius: 2pt !important;
}
} }

View File

@@ -382,7 +382,7 @@ button.btn, button.btn-sm {
padding: 0; padding: 0;
} }
pre:not(.CodeMirror-line) { pre:not(.CodeMirror-line):not(.hljs) {
color: var(--main-text-color) !important; color: var(--main-text-color) !important;
white-space: pre-wrap; white-space: pre-wrap;
font-size: 100%; font-size: 100%;
@@ -807,6 +807,65 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
vertical-align: baseline !important; vertical-align: baseline !important;
} }
.ck-content pre {
border: 0;
border-radius: 6px;
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2);
padding: 0 !important;
margin-top: 2px !important;
overflow: unset;
}
html .note-detail-editable-text :not(figure, .include-note):first-child {
/* Create some space for the top-side shadow */
margin-top: 1px !important;
}
.ck.ck-editor__editable pre[data-language]::after {
--ck-color-code-block-label-background: rgba(128, 128, 128, .5);
border-radius: 0 0 5px 5px;
padding: 0px 10px;
letter-spacing: .5px;
font-weight: bold;
}
.ck-content pre code {
display: block;
padding: 1em;
overflow: auto;
}
.ck-content pre code::-webkit-scrollbar {
height: 6px;
}
.ck-content pre code::-webkit-scrollbar-thumb {
height: 4px;
border: none !important;
background: gray !important;
}
.ck-content pre code::-webkit-scrollbar-track, ::-webkit-scrollbar-thumb {
cursor: default;
}
.note-detail-printable:not(.word-wrap) pre code {
white-space: pre;
margin-right: 1em;
}
.code-sample-wrapper .hljs {
transition: background-color linear 100ms;
}
.side-checkbox {
display: flex;
align-items: end;
padding-top: .375rem;
padding-bottom: .375rem;
line-height: 1.5;
}
.ck-content .todo-list .todo-list__label > input:before { .ck-content .todo-list .todo-list__label > input:before {
border: 1px solid var(--muted-text-color) !important; border: 1px solid var(--muted-text-color) !important;
} }
@@ -1140,6 +1199,10 @@ button.close:hover {
flex-grow: 0 !important; flex-grow: 0 !important;
} }
.options-mime-types {
column-width: 250px;
}
textarea { textarea {
cursor: auto; cursor: auto;
} }
@@ -1174,3 +1237,4 @@ textarea {
.jump-to-note-results .aa-suggestions { .jump-to-note-results .aa-suggestions {
padding: 1rem; padding: 1rem;
} }

View File

@@ -92,3 +92,7 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
.btn-close { .btn-close {
filter: invert(1); filter: invert(1);
} }
.ck-content pre {
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6) !important;
}

View File

@@ -72,13 +72,12 @@
"delete_all_clones_description": "同时删除所有克隆(可以在最近修改中撤消)", "delete_all_clones_description": "同时删除所有克隆(可以在最近修改中撤消)",
"erase_notes_description": "通常(软)删除仅标记笔记为已删除,可以在一段时间内通过最近修改对话框撤消。选中此选项将立即擦除笔记,不可撤销。", "erase_notes_description": "通常(软)删除仅标记笔记为已删除,可以在一段时间内通过最近修改对话框撤消。选中此选项将立即擦除笔记,不可撤销。",
"erase_notes_warning": "永久擦除笔记(无法撤销),包括所有克隆。这将强制应用程序重新加载。", "erase_notes_warning": "永久擦除笔记(无法撤销),包括所有克隆。这将强制应用程序重新加载。",
"notes_to_be_deleted": "将删除以下笔记<span class=\"deleted-notes-count\"></span>", "notes_to_be_deleted": "将删除以下笔记 ({{- noteCount}})",
"no_note_to_delete": "没有笔记将被删除(仅克隆)。", "no_note_to_delete": "没有笔记将被删除(仅克隆)。",
"broken_relations_to_be_deleted": "将删除以下关系并断开连接<span class=\"broke-relations-count\"></span>", "broken_relations_to_be_deleted": "将删除以下关系并断开连接 ({{- relationCount}})",
"cancel": "取消", "cancel": "取消",
"ok": "确定", "ok": "确定",
"note": "笔记", "deleted_relation_text": "笔记 {{- note}} (将被删除的笔记) 被以下关系 {{- relation}} 引用, 来自 {{- source}}。"
"to_be_deleted": " (将被删除的笔记) 被以下关系 <code>{{attrName}}</code> 引用, 来自 "
}, },
"export": { "export": {
"export_note_title": "导出笔记", "export_note_title": "导出笔记",

File diff suppressed because it is too large Load Diff

View File

@@ -75,16 +75,15 @@
}, },
"delete_notes": { "delete_notes": {
"delete_notes_preview": "Delete notes preview", "delete_notes_preview": "Delete notes preview",
"delete_all_clones_description": "delete also all clones (can be undone in recent changes)", "delete_all_clones_description": "Delete also all clones (can be undone in recent changes)",
"erase_notes_description": "Normal (soft) deletion only marks the notes as deleted and they can be undeleted (in recent changes dialog) within a period of time. Checking this option will erase the notes immediately and it won't be possible to undelete the notes.", "erase_notes_description": "Normal (soft) deletion only marks the notes as deleted and they can be undeleted (in recent changes dialog) within a period of time. Checking this option will erase the notes immediately and it won't be possible to undelete the notes.",
"erase_notes_warning": "erase notes permanently (can't be undone), including all clones. This will force application reload.", "erase_notes_warning": "Erase notes permanently (can't be undone), including all clones. This will force application reload.",
"notes_to_be_deleted": "Following notes will be deleted (<span class=\"deleted-notes-count\"></span>)", "notes_to_be_deleted": "Following notes will be deleted ({{- noteCount}})",
"no_note_to_delete": "No note will be deleted (only clones).", "no_note_to_delete": "No note will be deleted (only clones).",
"broken_relations_to_be_deleted": "Following relations will be broken and deleted (<span class=\"broke-relations-count\"></span>)", "broken_relations_to_be_deleted": "Following relations will be broken and deleted ({{- relationCount}})",
"cancel": "Cancel", "cancel": "Cancel",
"ok": "OK", "ok": "OK",
"note": "Note", "deleted_relation_text": "Note {{- note}} (to be deleted) is referenced by relation {{- relation}} originating from {{- source}}."
"to_be_deleted": " (to be deleted) is referenced by relation <code>{{attrName}}</code> originating from "
}, },
"export": { "export": {
"export_note_title": "Export note", "export_note_title": "Export note",
@@ -1054,7 +1053,7 @@
"edited_notes_message": "Edited Notes ribbon tab will automatically open on day notes" "edited_notes_message": "Edited Notes ribbon tab will automatically open on day notes"
}, },
"theme": { "theme": {
"title": "Theme", "title": "Application Theme",
"theme_label": "Theme", "theme_label": "Theme",
"override_theme_fonts_label": "Override theme fonts", "override_theme_fonts_label": "Override theme fonts",
"light_theme": "Light", "light_theme": "Light",
@@ -1434,6 +1433,7 @@
"add_new_tab": "Add new tab", "add_new_tab": "Add new tab",
"close": "Close", "close": "Close",
"close_other_tabs": "Close other tabs", "close_other_tabs": "Close other tabs",
"close_right_tabs": "Close tabs to the right",
"close_all_tabs": "Close all tabs", "close_all_tabs": "Close all tabs",
"move_tab_to_new_window": "Move this tab to a new window", "move_tab_to_new_window": "Move this tab to a new window",
"new_tab": "New tab" "new_tab": "New tab"
@@ -1497,5 +1497,29 @@
"move-to-visible-launchers": "Move to visible launchers", "move-to-visible-launchers": "Move to visible launchers",
"move-to-available-launchers": "Move to available launchers", "move-to-available-launchers": "Move to available launchers",
"duplicate-launcher": "Duplicate launcher" "duplicate-launcher": "Duplicate launcher"
},
"editable-text": {
"auto-detect-language": "Auto-detected"
},
"highlighting": {
"title": "Code Syntax Highlighting for Text Notes",
"description": "Controls the syntax highlighting for code blocks inside text notes, code notes will not be affected.",
"color-scheme": "Color Scheme"
},
"code_block": {
"word_wrapping": "Word wrapping"
},
"classic_editor_toolbar": {
"title": "Formatting"
},
"editor": {
"title": "Editor"
},
"editing": {
"editor_type": {
"label": "Formatting toolbar",
"floating": "Floating (editing tools appear near the cursor)",
"fixed": "Fixed (editing tools appear in the \"Formatting\" ribbon tab)"
}
} }
} }

View File

@@ -75,16 +75,15 @@
}, },
"delete_notes": { "delete_notes": {
"delete_notes_preview": "Eliminar vista previa de notas", "delete_notes_preview": "Eliminar vista previa de notas",
"delete_all_clones_description": "eliminar también todos los clones (se puede deshacer en cambios recientes)", "delete_all_clones_description": "Eliminar también todos los clones (se puede deshacer en cambios recientes)",
"erase_notes_description": "La eliminación normal (suave) solo marca las notas como eliminadas y se pueden recuperar (en el cuadro de diálogo de cambios recientes) dentro de un periodo de tiempo. Al marcar esta opción se borrarán las notas inmediatamente y no será posible recuperarlas.", "erase_notes_description": "La eliminación normal (suave) solo marca las notas como eliminadas y se pueden recuperar (en el cuadro de diálogo de cambios recientes) dentro de un periodo de tiempo. Al marcar esta opción se borrarán las notas inmediatamente y no será posible recuperarlas.",
"erase_notes_warning": "eliminar notas permanentemente (no se puede deshacer), incluidos todos los clones. Esto forzará la recarga de la aplicación.", "erase_notes_warning": "Eliminar notas permanentemente (no se puede deshacer), incluidos todos los clones. Esto forzará la recarga de la aplicación.",
"notes_to_be_deleted": "Las siguientes notas serán eliminadas (<span class=\"deleted-notes-count\"></span>)", "notes_to_be_deleted": "Las siguientes notas serán eliminadas ({{- noteCount}})",
"no_note_to_delete": "No se eliminará ninguna nota (solo clones).", "no_note_to_delete": "No se eliminará ninguna nota (solo clones).",
"broken_relations_to_be_deleted": "Las siguientes relaciones se romperán y serán eliminadas (<span class=\"broke-relations-count\"></span>)", "broken_relations_to_be_deleted": "Las siguientes relaciones se romperán y serán eliminadas ({{- relationCount}})",
"cancel": "Cancelar", "cancel": "Cancelar",
"ok": "Aceptar", "ok": "Aceptar",
"note": "Nota", "deleted_relation_text": "Nota {{- note}} (para ser eliminada) está referenciado por la relación {{- relation}} que se origina en {{- source}}."
"to_be_deleted": " (para ser eliminada) está referenciado por la relación <code>{{attrName}}</code> que se origina en "
}, },
"export": { "export": {
"export_note_title": "Exportar nota", "export_note_title": "Exportar nota",
@@ -896,7 +895,8 @@
"label_rock_or_pop": "sólo una de las etiquetas debe estar presente", "label_rock_or_pop": "sólo una de las etiquetas debe estar presente",
"label_year_comparison": "comparación numérica (también >, >=, <).", "label_year_comparison": "comparación numérica (también >, >=, <).",
"label_date_created": "notas creadas en el último mes", "label_date_created": "notas creadas en el último mes",
"error": "Error de búsqueda: {{error}}" "error": "Error de búsqueda: {{error}}",
"search_prefix": "Buscar:"
}, },
"attachment_detail": { "attachment_detail": {
"open_help_page": "Abrir página de ayuda en archivos adjuntos", "open_help_page": "Abrir página de ayuda en archivos adjuntos",
@@ -1433,6 +1433,7 @@
"add_new_tab": "Agregar nueva pestaña", "add_new_tab": "Agregar nueva pestaña",
"close": "Cerrar", "close": "Cerrar",
"close_other_tabs": "Cerrar otras pestañas", "close_other_tabs": "Cerrar otras pestañas",
"close_right_tabs": "Cerrar pestañas a la derecha",
"close_all_tabs": "Cerras todas las pestañas", "close_all_tabs": "Cerras todas las pestañas",
"move_tab_to_new_window": "Mover esta pestaña a una nueva ventana", "move_tab_to_new_window": "Mover esta pestaña a una nueva ventana",
"new_tab": "Nueva pestaña" "new_tab": "Nueva pestaña"
@@ -1496,5 +1497,29 @@
"move-to-visible-launchers": "Mover a lanzadores visibles", "move-to-visible-launchers": "Mover a lanzadores visibles",
"move-to-available-launchers": "Mover a lanzadores disponibles", "move-to-available-launchers": "Mover a lanzadores disponibles",
"duplicate-launcher": "Duplicar lanzador" "duplicate-launcher": "Duplicar lanzador"
},
"editable-text": {
"auto-detect-language": "Detectado automaticamente"
},
"highlighting": {
"title": "Resaltado de sintaxis de de código para Notas de Texto",
"description": "Controla el resaltado de sintaxis para bloques de código dentro de las notas de texto, las notas de código no serán afectadas.",
"color-scheme": "Esquema de color"
},
"code_block": {
"word_wrapping": "Ajuste de palabras"
},
"classic_editor_toolbar": {
"title": "Formato"
},
"editor": {
"title": "Editor"
},
"editing": {
"editor_type": {
"label": "Barra de herramientas de formato",
"floating": "Flotante (las herramientas de edición aparecen cerca del cursor)",
"fixed": "Fijo (las herramientas de edición aparecen en la pestaña de la cinta \"Formato\")"
}
} }
} }

View File

@@ -15,7 +15,13 @@
"message": "Une erreur critique s'est produite qui empêche l'application client de démarrer :\n\n{{message}}\n\nCeci est probablement dû à un échec inattendu d'un script. Essayez de démarrer l'application en mode sans échec et de résoudre le problème." "message": "Une erreur critique s'est produite qui empêche l'application client de démarrer :\n\n{{message}}\n\nCeci est probablement dû à un échec inattendu d'un script. Essayez de démarrer l'application en mode sans échec et de résoudre le problème."
}, },
"widget-error": { "widget-error": {
"title": "Impossible d'initialiser un widget" "title": "Impossible d'initialiser un widget",
"message-custom": "Le widget personnalisé de la note avec l'ID \"{{id}}\", intitulée \"{{title}}\" n'a pas pu être initialisé en raison de\n\n{{message}}",
"message-unknown": "Le widget inconnu n'a pas pu être initialisé :\n\n{{message}}"
},
"bundle-error": {
"title": "Echec du chargement d'un script personnalisé",
"message": "Le script de la note avec l'ID \"{{id}}\", intitulé \"{{title}}\" n'a pas pu être exécuté à cause de\n\n{{message}}"
} }
}, },
"add_link": { "add_link": {
@@ -51,7 +57,7 @@
"clone_notes_to": "Cloner des notes dans...", "clone_notes_to": "Cloner des notes dans...",
"help_on_links": "Aide sur les liens", "help_on_links": "Aide sur les liens",
"notes_to_clone": "Notes à cloner", "notes_to_clone": "Notes à cloner",
"target_parent_note": "Note parent cible", "target_parent_note": "Note parent de destination",
"search_for_note_by_its_name": "rechercher une note par son nom", "search_for_note_by_its_name": "rechercher une note par son nom",
"cloned_note_prefix_title": "La note clonée sera affichée dans l'arbre des notes avec le préfixe donné", "cloned_note_prefix_title": "La note clonée sera affichée dans l'arbre des notes avec le préfixe donné",
"prefix_optional": "Préfixe (facultatif)", "prefix_optional": "Préfixe (facultatif)",
@@ -68,17 +74,16 @@
"also_delete_note": "Supprimer également la note" "also_delete_note": "Supprimer également la note"
}, },
"delete_notes": { "delete_notes": {
"delete_notes_preview": "Supprimer l'aperçu des notes", "delete_notes_preview": "Supprimer la note",
"delete_all_clones_description": "supprime également tous les clones (peut être annulé depuis les Modifications récentes)", "delete_all_clones_description": "supprime également tous les clones (peut être annulé depuis les Modifications récentes)",
"erase_notes_description": "La suppression normale (douce) marque uniquement les notes comme supprimées et elles peuvent être restaurées (dans la boîte de dialogue des Modifications récentes) dans un délai donné. Cocher cette option effacera les notes immédiatement et il ne sera pas possible de les restaurer.", "erase_notes_description": "La suppression normale (douce) marque uniquement les notes comme supprimées et elles peuvent être restaurées (dans la boîte de dialogue des Modifications récentes) dans un délai donné. Cocher cette option effacera les notes immédiatement et il ne sera pas possible de les restaurer.",
"erase_notes_warning": "efface les notes de manière permanente (ne peut pas être annulée), y compris tous les clones. Cela forcera le rechargement de lapplication.", "erase_notes_warning": "Efface les notes de manière permanente (ne peut pas être annulée), y compris tous les clones. Cela forcera le rechargement de lapplication.",
"notes_to_be_deleted": "Les notes suivantes seront supprimées (<span class=\"deleted-notes-count\"></span>)", "notes_to_be_deleted": "Les notes suivantes seront supprimées ({{- noteCount}})",
"no_note_to_delete": "Aucune note ne sera supprimée (uniquement les clones).", "no_note_to_delete": "Aucune note ne sera supprimée (uniquement les clones).",
"broken_relations_to_be_deleted": "Les relations suivantes seront rompues et supprimées (<span class=\"broke-relations-count\"></span>)", "broken_relations_to_be_deleted": "Les relations suivantes seront rompues et supprimées ({{- relationCount}})",
"cancel": "Annuler", "cancel": "Annuler",
"ok": "OK", "ok": "OK",
"note": "Note", "deleted_relation_text": "Note {{- note}} (à supprimer) est référencé par la relation {{- relation}} provenant de {{- source}}."
"to_be_deleted": " (à supprimer) est référencé par la relation <code>{{attrName}}</code> provenant de "
}, },
"export": { "export": {
"export_note_title": "Exporter la note", "export_note_title": "Exporter la note",
@@ -165,7 +170,8 @@
"textImportedAsText": "Importez HTML, Markdown et TXT sous forme de notes de texte si les métadonnées ne sont pas claires", "textImportedAsText": "Importez HTML, Markdown et TXT sous forme de notes de texte si les métadonnées ne sont pas claires",
"codeImportedAsCode": "Importez des fichiers de code reconnus (par exemple <code>.json</code>) en tant que notes de code si cela n'est pas clair à partir des métadonnées", "codeImportedAsCode": "Importez des fichiers de code reconnus (par exemple <code>.json</code>) en tant que notes de code si cela n'est pas clair à partir des métadonnées",
"replaceUnderscoresWithSpaces": "Remplacez les tirets bas par des espaces dans les noms de notes importées", "replaceUnderscoresWithSpaces": "Remplacez les tirets bas par des espaces dans les noms de notes importées",
"import": "Importer" "import": "Importer",
"failed": "Échec de l'importation : {{message}}."
}, },
"include_note": { "include_note": {
"dialog_title": "Inclure une note", "dialog_title": "Inclure une note",
@@ -195,7 +201,7 @@
"move_to": { "move_to": {
"dialog_title": "Déplacer les notes vers...", "dialog_title": "Déplacer les notes vers...",
"notes_to_move": "Notes à déplacer", "notes_to_move": "Notes à déplacer",
"target_parent_note": "Note parent cible", "target_parent_note": "Note parent de destination",
"search_placeholder": "rechercher une note par son nom", "search_placeholder": "rechercher une note par son nom",
"move_button": "Déplacer vers la note sélectionnée <kbd>entrer</kbd>", "move_button": "Déplacer vers la note sélectionnée <kbd>entrer</kbd>",
"error_no_path": "Aucun chemin vers lequel déplacer.", "error_no_path": "Aucun chemin vers lequel déplacer.",
@@ -232,7 +238,7 @@
"confirm_undelete": "Voulez-vous restaurer cette note et ses sous-notes ?" "confirm_undelete": "Voulez-vous restaurer cette note et ses sous-notes ?"
}, },
"revisions": { "revisions": {
"note_revisions": "Versions des notes", "note_revisions": "Versions de la note",
"delete_all_revisions": "Supprimer toutes les versions de cette note", "delete_all_revisions": "Supprimer toutes les versions de cette note",
"delete_all_button": "Supprimer toutes les versions", "delete_all_button": "Supprimer toutes les versions",
"help_title": "Aide sur les versions de notes", "help_title": "Aide sur les versions de notes",
@@ -246,7 +252,7 @@
"revisions_deleted": "Les versions de notes ont été supprimées.", "revisions_deleted": "Les versions de notes ont été supprimées.",
"revision_restored": "La version de la note a été restaurée.", "revision_restored": "La version de la note a été restaurée.",
"revision_deleted": "La version de la note a été supprimée.", "revision_deleted": "La version de la note a été supprimée.",
"snapshot_interval": "Intervalle d'instantané des versions de notes : {{seconds}}s.", "snapshot_interval": "Intervalle d'enregistrement des versions de notes : {{seconds}}s.",
"maximum_revisions": "Nombre maximal de versions pour la note actuelle : {{number}}.", "maximum_revisions": "Nombre maximal de versions pour la note actuelle : {{number}}.",
"settings": "Paramètres pour les versions de notes", "settings": "Paramètres pour les versions de notes",
"download_button": "Télécharger", "download_button": "Télécharger",
@@ -397,7 +403,7 @@
"share_js": "Note JavaScript qui sera injectée dans la page de partage. La note JS doit également figurer dans le sous-arbre partagé. Pensez à utiliser 'share_hidden_from_tree'.", "share_js": "Note JavaScript qui sera injectée dans la page de partage. La note JS doit également figurer dans le sous-arbre partagé. Pensez à utiliser 'share_hidden_from_tree'.",
"share_template": "Note JavaScript intégrée qui sera utilisée comme modèle pour afficher la note partagée. Revient au modèle par défaut. Pensez à utiliser 'share_hidden_from_tree'.", "share_template": "Note JavaScript intégrée qui sera utilisée comme modèle pour afficher la note partagée. Revient au modèle par défaut. Pensez à utiliser 'share_hidden_from_tree'.",
"share_favicon": "Favicon de la note à définir dans la page partagée. En règle générale, vous souhaitez le configurer pour partager la racine et le rendre héritable. La note Favicon doit également figurer dans le sous-arbre partagé. Pensez à utiliser 'share_hidden_from_tree'.", "share_favicon": "Favicon de la note à définir dans la page partagée. En règle générale, vous souhaitez le configurer pour partager la racine et le rendre héritable. La note Favicon doit également figurer dans le sous-arbre partagé. Pensez à utiliser 'share_hidden_from_tree'.",
"is_owned_by_note": "appartient à la note", "is_owned_by_note": "appartenant à la note",
"other_notes_with_name": "Autres notes portant le nom {{attributeType}} \"{{attributeName}}\"", "other_notes_with_name": "Autres notes portant le nom {{attributeType}} \"{{attributeName}}\"",
"and_more": "... et {{count}} plus." "and_more": "... et {{count}} plus."
}, },
@@ -426,7 +432,7 @@
"add_label": "Ajouter un label", "add_label": "Ajouter un label",
"label_name_placeholder": "nom du label", "label_name_placeholder": "nom du label",
"label_name_title": "Les caractères autorisés sont : caractères alphanumériques, les tirets bas et les deux-points.", "label_name_title": "Les caractères autorisés sont : caractères alphanumériques, les tirets bas et les deux-points.",
"to_value": "modifié par", "to_value": "égal à",
"new_value_placeholder": "nouvelle valeur", "new_value_placeholder": "nouvelle valeur",
"help_text": "Pour toutes les notes correspondantes :", "help_text": "Pour toutes les notes correspondantes :",
"help_text_item1": "créer un label donné si la note ne le possède pas encore", "help_text_item1": "créer un label donné si la note ne le possède pas encore",
@@ -469,7 +475,7 @@
"move_note": { "move_note": {
"move_note": "Déplacer la note", "move_note": "Déplacer la note",
"to": "vers", "to": "vers",
"target_parent_note": "note parent cible", "target_parent_note": "note parent de destination",
"on_all_matched_notes": "Pour toutes les notes correspondantes", "on_all_matched_notes": "Pour toutes les notes correspondantes",
"move_note_new_parent": "déplacer la note vers le nouveau parent si la note n'a qu'un seul parent (c.-à-d. l'ancienne branche est supprimée et une nouvelle branche est créée dans le nouveau parent)", "move_note_new_parent": "déplacer la note vers le nouveau parent si la note n'a qu'un seul parent (c.-à-d. l'ancienne branche est supprimée et une nouvelle branche est créée dans le nouveau parent)",
"clone_note_new_parent": "cloner la note vers le nouveau parent si la note a plusieurs clones/branches (il n'est pas clair quelle branche doit être supprimée)", "clone_note_new_parent": "cloner la note vers le nouveau parent si la note a plusieurs clones/branches (il n'est pas clair quelle branche doit être supprimée)",
@@ -477,7 +483,7 @@
}, },
"rename_note": { "rename_note": {
"rename_note": "Renommer la note", "rename_note": "Renommer la note",
"rename_note_title_to": "Renommer le titre de la note en", "rename_note_title_to": "Renommer la note en",
"new_note_title": "nouveau titre de note", "new_note_title": "nouveau titre de note",
"click_help_icon": "Cliquez sur l'icône d'aide à droite pour voir toutes les options", "click_help_icon": "Cliquez sur l'icône d'aide à droite pour voir toutes les options",
"evaluated_as_js_string": "La valeur donnée est évaluée comme une chaîne JavaScript et peut ainsi être enrichie de contenu dynamique via la variable <code>note</code> injectée (la note étant renommée). Exemples :", "evaluated_as_js_string": "La valeur donnée est évaluée comme une chaîne JavaScript et peut ainsi être enrichie de contenu dynamique via la variable <code>note</code> injectée (la note étant renommée). Exemples :",
@@ -552,7 +558,7 @@
"febuary": "Février", "febuary": "Février",
"march": "Mars", "march": "Mars",
"april": "Avril", "april": "Avril",
"may": "Peut", "may": "Mai",
"june": "Juin", "june": "Juin",
"july": "Juillet", "july": "Juillet",
"august": "Août", "august": "Août",
@@ -620,18 +626,21 @@
}, },
"note_actions": { "note_actions": {
"convert_into_attachment": "Convertir en pièce jointe", "convert_into_attachment": "Convertir en pièce jointe",
"re_render_note": "Re-rendre la note", "re_render_note": "Recharger la note",
"search_in_note": "Rechercher dans la note", "search_in_note": "Rechercher dans la note",
"note_source": "Source de la note", "note_source": "Code source",
"note_attachments": "Pièces jointes", "note_attachments": "Pièces jointes",
"open_note_externally": "Ouverture externe", "open_note_externally": "Ouverture externe",
"open_note_externally_title": "Le fichier sera ouvert dans une application externe et les modifications apportées seront surveillées. Vous pourrez ensuite téléverser la version modifiée dans Trilium.", "open_note_externally_title": "Le fichier sera ouvert dans une application externe et les modifications apportées seront surveillées. Vous pourrez ensuite téléverser la version modifiée dans Trilium.",
"open_note_custom": "Ouvrir la note avec", "open_note_custom": "Ouvrir la note avec",
"import_files": "Importer des fichiers", "import_files": "Importer des fichiers",
"export_note": "Exporter la note", "export_note": "Exporter",
"delete_note": "Supprimer la note", "delete_note": "Supprimer la note",
"print_note": "Imprimer la note", "print_note": "Imprimer",
"save_revision": "Enregistrer la version" "save_revision": "Enregistrer une version",
"convert_into_attachment_failed": "La conversion de la note '{{title}}' a échoué.",
"convert_into_attachment_successful": "La note '{{title}}' a été convertie en pièce jointe.",
"convert_into_attachment_prompt": "Êtes-vous sûr de vouloir convertir la note '{{title}}' en une pièce jointe de la note parente ?"
}, },
"onclick_button": { "onclick_button": {
"no_click_handler": "Le widget bouton '{{componentId}}' n'a pas de gestionnaire de clic défini" "no_click_handler": "Le widget bouton '{{componentId}}' n'a pas de gestionnaire de clic défini"
@@ -641,13 +650,13 @@
"inactive": "Cliquez pour accéder à une session protégée" "inactive": "Cliquez pour accéder à une session protégée"
}, },
"revisions_button": { "revisions_button": {
"note_revisions": "Versions des Notes" "note_revisions": "Versions de la note"
}, },
"update_available": { "update_available": {
"update_available": "Mise à jour disponible" "update_available": "Mise à jour disponible"
}, },
"note_launcher": { "note_launcher": {
"this_launcher_doesnt_define_target_note": "Ce lanceur ne définit pas de note cible." "this_launcher_doesnt_define_target_note": "Ce raccourci ne définit pas de note cible."
}, },
"code_buttons": { "code_buttons": {
"execute_button_title": "Exécuter le script", "execute_button_title": "Exécuter le script",
@@ -694,7 +703,7 @@
"basic_properties": { "basic_properties": {
"note_type": "Type de note", "note_type": "Type de note",
"editable": "Modifiable", "editable": "Modifiable",
"basic_properties": "Propriétés de base" "basic_properties": "Propriétés basiques"
}, },
"book_properties": { "book_properties": {
"view_type": "Type d'affichage", "view_type": "Type d'affichage",
@@ -704,7 +713,7 @@
"expand_all_children": "Développer tous les enfants", "expand_all_children": "Développer tous les enfants",
"collapse": "Réduire", "collapse": "Réduire",
"expand": "Développer", "expand": "Développer",
"book_properties": "Propriétés du livre", "book_properties": "Propriétés basiques",
"invalid_view_type": "Type de vue non valide '{{type}}'" "invalid_view_type": "Type de vue non valide '{{type}}'"
}, },
"edited_notes": { "edited_notes": {
@@ -741,9 +750,9 @@
"no_inherited_attributes": "Aucun attribut hérité." "no_inherited_attributes": "Aucun attribut hérité."
}, },
"note_info_widget": { "note_info_widget": {
"note_id": "Identifiant de la note", "note_id": "ID de la note",
"created": "Créé", "created": "Créée le",
"modified": "Modifié", "modified": "Modifiée le",
"type": "Type", "type": "Type",
"note_size": "Taille de la note", "note_size": "Taille de la note",
"note_size_info": "La taille de la note fournit une estimation approximative des besoins de stockage pour cette note. Il prend en compte le contenu de la note et de ses versions.", "note_size_info": "La taille de la note fournit une estimation approximative des besoins de stockage pour cette note. Il prend en compte le contenu de la note et de ses versions.",
@@ -752,12 +761,12 @@
"title": "Infos sur la Note" "title": "Infos sur la Note"
}, },
"note_map": { "note_map": {
"open_full": "Développer au maximum", "open_full": "Agrandir au maximum",
"collapse": "Réduire à la taille normale", "collapse": "Réduire à la taille normale",
"title": "Carte de la Note" "title": "Liens de la note"
}, },
"note_paths": { "note_paths": {
"title": "Chemins de la Note", "title": "Chemins de la note",
"clone_button": "Cloner la note vers un nouvel emplacement...", "clone_button": "Cloner la note vers un nouvel emplacement...",
"intro_placed": "Cette note est située dans les chemins suivants :", "intro_placed": "Cette note est située dans les chemins suivants :",
"intro_not_placed": "Cette note n'est pas encore située dans l'arbre des notes.", "intro_not_placed": "Cette note n'est pas encore située dans l'arbre des notes.",
@@ -788,7 +797,7 @@
"execute_script": "Exécuter le script" "execute_script": "Exécuter le script"
}, },
"search_definition": { "search_definition": {
"add_search_option": "Ajouter une option de recherche :", "add_search_option": "Options de recherche :",
"search_string": "chaîne de caractères à rechercher", "search_string": "chaîne de caractères à rechercher",
"search_script": "script de recherche", "search_script": "script de recherche",
"ancestor": "ancêtre", "ancestor": "ancêtre",
@@ -886,7 +895,8 @@
"label_rock_or_pop": "un seul des labels doit être présent", "label_rock_or_pop": "un seul des labels doit être présent",
"label_year_comparison": "comparaison numérique (également >, >=, <).", "label_year_comparison": "comparaison numérique (également >, >=, <).",
"label_date_created": "notes créées le mois dernier", "label_date_created": "notes créées le mois dernier",
"error": "Erreur de recherche : {{error}}" "error": "Erreur de recherche : {{error}}",
"search_prefix": "Recherche :"
}, },
"attachment_detail": { "attachment_detail": {
"open_help_page": "Ouvrir la page d'aide sur les pièces jointes", "open_help_page": "Ouvrir la page d'aide sur les pièces jointes",
@@ -920,7 +930,15 @@
}, },
"protected_session": { "protected_session": {
"enter_password_instruction": "L'affichage de la note protégée nécessite la saisie de votre mot de passe :", "enter_password_instruction": "L'affichage de la note protégée nécessite la saisie de votre mot de passe :",
"start_session_button": "Démarrer une session protégée" "start_session_button": "Démarrer une session protégée",
"started": "La session protégée a démarré.",
"wrong_password": "Mot de passe incorrect.",
"protecting-finished-successfully": "La protection de la note s'est terminée avec succès.",
"unprotecting-finished-successfully": "La protection de la note a été retirée avec succès.",
"protecting-in-progress": "Protection en cours : {{count}}",
"unprotecting-in-progress-count": "Retrait de la protection en cours : {{count}}",
"protecting-title": "Statut de la protection",
"unprotecting-title": "Statut de la non-protection"
}, },
"relation_map": { "relation_map": {
"open_in_new_tab": "Ouvrir dans un nouvel onglet", "open_in_new_tab": "Ouvrir dans un nouvel onglet",
@@ -975,7 +993,7 @@
"error_creating_anonymized_database": "Impossible de créer une base de données anonymisée, vérifiez les journaux backend pour plus de détails", "error_creating_anonymized_database": "Impossible de créer une base de données anonymisée, vérifiez les journaux backend pour plus de détails",
"successfully_created_fully_anonymized_database": "Base de données entièrement anonymisée crée dans {{anonymizedFilePath}}", "successfully_created_fully_anonymized_database": "Base de données entièrement anonymisée crée dans {{anonymizedFilePath}}",
"successfully_created_lightly_anonymized_database": "Base de données partiellement anonymisée crée dans {{anonymizedFilePath}}", "successfully_created_lightly_anonymized_database": "Base de données partiellement anonymisée crée dans {{anonymizedFilePath}}",
"no_anonymized_database_yet": "Aucune base de données anonymisée" "no_anonymized_database_yet": "Aucune base de données anonymisée."
}, },
"database_integrity_check": { "database_integrity_check": {
"title": "Vérification de l'intégrité de la base de données", "title": "Vérification de l'intégrité de la base de données",
@@ -991,7 +1009,9 @@
"fill_entity_changes_button": "Remplir les enregistrements de modifications d'entité", "fill_entity_changes_button": "Remplir les enregistrements de modifications d'entité",
"full_sync_triggered": "Synchronisation complète déclenchée", "full_sync_triggered": "Synchronisation complète déclenchée",
"filling_entity_changes": "Remplissage changements de ligne d'entité ...", "filling_entity_changes": "Remplissage changements de ligne d'entité ...",
"sync_rows_filled_successfully": "Synchronisation avec succès des lignes remplies" "sync_rows_filled_successfully": "Synchronisation avec succès des lignes remplies",
"finished-successfully": "Synchronisation terminée avec succès.",
"failed": "Échec de la synchronisation : {{message}}"
}, },
"vacuum_database": { "vacuum_database": {
"title": "Nettoyage la base de donnée", "title": "Nettoyage la base de donnée",
@@ -1001,7 +1021,7 @@
"database_vacuumed": "La base de données a été nettoyée" "database_vacuumed": "La base de données a été nettoyée"
}, },
"fonts": { "fonts": {
"theme_defined": "Thème défini", "theme_defined": "Défini par le thème",
"fonts": "Polices", "fonts": "Polices",
"main_font": "Police principale", "main_font": "Police principale",
"font_family": "Famille de polices", "font_family": "Famille de polices",
@@ -1009,14 +1029,14 @@
"note_tree_font": "Police de l'arborescence", "note_tree_font": "Police de l'arborescence",
"note_detail_font": "Police du contenu des notes", "note_detail_font": "Police du contenu des notes",
"monospace_font": "Police Monospace (code)", "monospace_font": "Police Monospace (code)",
"note_tree_and_detail_font_sizing": "Notez que la taille de la police de larborescence et des détails est relative au paramètre de taille de police principal.", "note_tree_and_detail_font_sizing": "Notez que la taille de la police de larborescence et du contenu est relative au paramètre de taille de police principal.",
"not_all_fonts_available": "Toutes les polices répertoriées peuvent ne pas être disponibles sur votre système.", "not_all_fonts_available": "Toutes les polices répertoriées ne sont peut-être pas disponibles sur votre système.",
"apply_font_changes": "Pour appliquer les modifications de police, cliquez sur", "apply_font_changes": "Pour appliquer les modifications de police, cliquez sur",
"reload_frontend": "recharger l'interface" "reload_frontend": "recharger l'interface"
}, },
"max_content_width": { "max_content_width": {
"title": "Largeur du contenu", "title": "Largeur du contenu",
"default_description": "Trilium limite par défaut la largeur maximale du contenu pour améliorer la lisibilité sur des écrans larges.", "default_description": "Trilium limite par défaut la largeur maximale du contenu pour améliorer la lisibilité sur les écrans larges.",
"max_width_label": "Largeur maximale du contenu en pixels", "max_width_label": "Largeur maximale du contenu en pixels",
"apply_changes_description": "Pour appliquer les modifications de largeur du contenu, cliquez sur", "apply_changes_description": "Pour appliquer les modifications de largeur du contenu, cliquez sur",
"reload_button": "recharger l'interface", "reload_button": "recharger l'interface",
@@ -1036,7 +1056,7 @@
"title": "Thème", "title": "Thème",
"theme_label": "Thème", "theme_label": "Thème",
"override_theme_fonts_label": "Remplacer les polices du thème", "override_theme_fonts_label": "Remplacer les polices du thème",
"light_theme": "Lumière", "light_theme": "Clair",
"dark_theme": "Sombre" "dark_theme": "Sombre"
}, },
"zoom_factor": { "zoom_factor": {
@@ -1088,16 +1108,16 @@
"deleted_notes_erased": "Les notes supprimées ont été effacées." "deleted_notes_erased": "Les notes supprimées ont été effacées."
}, },
"revisions_snapshot_interval": { "revisions_snapshot_interval": {
"note_revisions_snapshot_interval_title": "Intervalle dinstantané des Versions de notes", "note_revisions_snapshot_interval_title": "Intervalle d'enregistrement automatique des versions des notes",
"note_revisions_snapshot_description": "L'intervalle de temps de l'instantané de version de note est le temps en secondes après lequel une nouvelle version de note est créée pour une note. Consultez le <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> pour plus d'informations.", "note_revisions_snapshot_description": "L'intervalle d'enregistrement automatique des versions de note est le temps en secondes après lequel une nouvelle version de note est créée pour une note. Consultez le <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> pour plus d'informations.",
"snapshot_time_interval_label": "Intervalle de temps entre deux instantanés de version de note (en secondes) :" "snapshot_time_interval_label": "Intervalle de temps entre deux enregistrements de version de note (en secondes) :"
}, },
"revisions_snapshot_limit": { "revisions_snapshot_limit": {
"note_revisions_snapshot_limit_title": "Limite des instantanés de version de note", "note_revisions_snapshot_limit_title": "Limite des enregistrements de version de note",
"note_revisions_snapshot_limit_description": "La limite du nombre dinstantanés de version de note désigne le nombre maximum de versions pouvant être enregistrées pour chaque note. -1 signifie aucune limite, 0 signifie supprimer toutes les versions. Vous pouvez définir le nombre maximal de versions pour une seule note via le label #versioningLimit.", "note_revisions_snapshot_limit_description": "La limite du nombre d'enregistrements de version de note désigne le nombre maximum de versions pouvant être enregistrées pour chaque note. -1 signifie aucune limite, 0 signifie supprimer toutes les versions. Vous pouvez définir le nombre maximal de versions pour une seule note via le label #versioningLimit.",
"snapshot_number_limit_label": "Nombre limite d'instantanés de version de la note :", "snapshot_number_limit_label": "Nombre limite d'enregistrements de version de la note :",
"erase_excess_revision_snapshots": "Effacez maintenant les instantanés de version en excès", "erase_excess_revision_snapshots": "Effacez maintenant les versions en excès",
"erase_excess_revision_snapshots_prompt": "Les instantanés de version en excès ont été effacés." "erase_excess_revision_snapshots_prompt": "Les versions en excès ont été effacées."
}, },
"search_engine": { "search_engine": {
"title": "Moteur de recherche", "title": "Moteur de recherche",
@@ -1110,7 +1130,7 @@
"custom_name_label": "Nom du moteur de recherche personnalisé", "custom_name_label": "Nom du moteur de recherche personnalisé",
"custom_name_placeholder": "Personnaliser le nom du moteur de recherche", "custom_name_placeholder": "Personnaliser le nom du moteur de recherche",
"custom_url_label": "L'URL du moteur de recherche personnalisé doit inclure {keyword} comme espace réservé pour le terme de recherche.", "custom_url_label": "L'URL du moteur de recherche personnalisé doit inclure {keyword} comme espace réservé pour le terme de recherche.",
"custom_url_placeholder": "Personnaliser l'URL du moteur de recherche", "custom_url_placeholder": "Personnaliser l'url du moteur de recherche",
"save_button": "Sauvegarder" "save_button": "Sauvegarder"
}, },
"tray": { "tray": {
@@ -1147,7 +1167,7 @@
"label": "Taille automatique en lecture seule (notes de texte)" "label": "Taille automatique en lecture seule (notes de texte)"
}, },
"i18n": { "i18n": {
"title": "Localisation", "title": "Paramètres régionaux",
"language": "Langue", "language": "Langue",
"first-day-of-the-week": "Premier jour de la semaine", "first-day-of-the-week": "Premier jour de la semaine",
"sunday": "Dimanche", "sunday": "Dimanche",
@@ -1198,7 +1218,7 @@
"password": { "password": {
"heading": "Mot de passe", "heading": "Mot de passe",
"alert_message": "Prenez soin de mémoriser votre nouveau mot de passe. Le mot de passe est utilisé pour se connecter à l'interface Web et pour crypter les notes protégées. Si vous oubliez votre mot de passe, toutes vos notes protégées seront définitivement perdues.", "alert_message": "Prenez soin de mémoriser votre nouveau mot de passe. Le mot de passe est utilisé pour se connecter à l'interface Web et pour crypter les notes protégées. Si vous oubliez votre mot de passe, toutes vos notes protégées seront définitivement perdues.",
"reset_link": "cliquez ici pour le réinitialiser.", "reset_link": "Cliquez ici pour le réinitialiser.",
"old_password": "Ancien mot de passe", "old_password": "Ancien mot de passe",
"new_password": "Nouveau mot de passe", "new_password": "Nouveau mot de passe",
"new_password_confirmation": "Confirmation du nouveau mot de passe", "new_password_confirmation": "Confirmation du nouveau mot de passe",
@@ -1266,7 +1286,7 @@
"unrecognized_role": "Rôle de pièce jointe « {{role}} » non reconnu." "unrecognized_role": "Rôle de pièce jointe « {{role}} » non reconnu."
}, },
"bookmark_switch": { "bookmark_switch": {
"bookmark": "Favoris", "bookmark": "Favori",
"bookmark_this_note": "Ajouter cette note à vos favoris dans le panneau latéral gauche", "bookmark_this_note": "Ajouter cette note à vos favoris dans le panneau latéral gauche",
"remove_bookmark": "Supprimer le favori" "remove_bookmark": "Supprimer le favori"
}, },
@@ -1280,7 +1300,7 @@
}, },
"note-map": { "note-map": {
"button-link-map": "Carte des liens", "button-link-map": "Carte des liens",
"button-tree-map": "Carte de l'arborescence" "button-tree-map": "Carte arborescente"
}, },
"tree-context-menu": { "tree-context-menu": {
"open-in-a-new-tab": "Ouvrir dans un nouvel onglet", "open-in-a-new-tab": "Ouvrir dans un nouvel onglet",
@@ -1289,6 +1309,8 @@
"insert-child-note": "Insérer une note enfant", "insert-child-note": "Insérer une note enfant",
"delete": "Supprimer", "delete": "Supprimer",
"search-in-subtree": "Rechercher dans le sous-arbre", "search-in-subtree": "Rechercher dans le sous-arbre",
"hoist-note": "Focus sur la note",
"unhoist-note": "Ne plus focus la note",
"edit-branch-prefix": "Modifier le préfixe de branche", "edit-branch-prefix": "Modifier le préfixe de branche",
"advanced": "Avancé", "advanced": "Avancé",
"expand-subtree": "Développer le sous-arbre", "expand-subtree": "Développer le sous-arbre",
@@ -1308,7 +1330,9 @@
"duplicate-subtree": "Dupliquer le sous-arbre", "duplicate-subtree": "Dupliquer le sous-arbre",
"export": "Exporter", "export": "Exporter",
"import-into-note": "Importer dans la note", "import-into-note": "Importer dans la note",
"apply-bulk-actions": "Appliquer des Actions groupées" "apply-bulk-actions": "Actions groupées",
"converted-to-attachments": "Les notes {{count}} ont été converties en pièces jointes.",
"convert-to-attachment-confirm": "Êtes-vous sûr de vouloir convertir les notes sélectionnées en pièces jointes de leurs notes parentes ?"
}, },
"shared_info": { "shared_info": {
"shared_publicly": "Cette note est partagée publiquement sur", "shared_publicly": "Cette note est partagée publiquement sur",
@@ -1321,7 +1345,7 @@
"saved-search": "Recherche enregistrée", "saved-search": "Recherche enregistrée",
"relation-map": "Carte des relations", "relation-map": "Carte des relations",
"note-map": "Carte de notes", "note-map": "Carte de notes",
"render-note": "Rendu HTML", "render-note": "Rendre la note",
"book": "Livre", "book": "Livre",
"mermaid-diagram": "Diagramme Mermaid", "mermaid-diagram": "Diagramme Mermaid",
"canvas": "Canevas", "canvas": "Canevas",
@@ -1331,7 +1355,8 @@
"image": "Image", "image": "Image",
"launcher": "Raccourci", "launcher": "Raccourci",
"doc": "Doc", "doc": "Doc",
"widget": "Widget" "widget": "Widget",
"confirm-change": "Il n'est pas recommandé de modifier le type de note lorsque son contenu n'est pas vide. Voulez-vous continuer ?"
}, },
"protect_note": { "protect_note": {
"toggle-on": "Protéger la note", "toggle-on": "Protéger la note",
@@ -1354,7 +1379,7 @@
"open-help-page": "Ouvrir la page d'aide", "open-help-page": "Ouvrir la page d'aide",
"find": { "find": {
"case_sensitive": "sensible aux majuscules et minuscules", "case_sensitive": "sensible aux majuscules et minuscules",
"match_words": "faire correspondre les mots" "match_words": "correspondance exacte"
}, },
"highlights_list_2": { "highlights_list_2": {
"title": "Accentuations", "title": "Accentuations",
@@ -1377,10 +1402,12 @@
"hide-archived-notes": "Masquer les notes archivées", "hide-archived-notes": "Masquer les notes archivées",
"automatically-collapse-notes": "Réduire automatiquement les notes", "automatically-collapse-notes": "Réduire automatiquement les notes",
"automatically-collapse-notes-title": "Les notes seront réduites après une période d'inactivité pour désencombrer l'arborescence.", "automatically-collapse-notes-title": "Les notes seront réduites après une période d'inactivité pour désencombrer l'arborescence.",
"save-changes": "Enregistrer et appliquer les modifications" "save-changes": "Enregistrer et appliquer les modifications",
"auto-collapsing-notes-after-inactivity": "Réduction automatique des notes après inactivité...",
"saved-search-note-refreshed": "Note de recherche enregistrée actualisée."
}, },
"title_bar_buttons": { "title_bar_buttons": {
"window-on-top": "Gardez cette fenêtre au premier plan." "window-on-top": "Épingler cette fenêtre au premier plan."
}, },
"note_detail": { "note_detail": {
"could_not_find_typewidget": "Impossible de trouver typeWidget pour le type '{{type}}'" "could_not_find_typewidget": "Impossible de trouver typeWidget pour le type '{{type}}'"
@@ -1400,5 +1427,74 @@
}, },
"sql_table_schemas": { "sql_table_schemas": {
"tables": "Tableaux" "tables": "Tableaux"
},
"tab_row": {
"close_tab": "Fermer l'onglet",
"add_new_tab": "Ajouter un nouvel onglet",
"close": "Fermer",
"close_other_tabs": "Fermer les autres onglets",
"close_all_tabs": "Fermer tous les onglets",
"move_tab_to_new_window": "Déplacer cet onglet vers une nouvelle fenêtre",
"new_tab": "Nouvel onglet"
},
"toc": {
"table_of_contents": "Table des matières",
"options": "Options"
},
"watched_file_update_status": {
"file_last_modified": "Le fichier <code class=\"file-path\"></code> a été modifié pour la dernière fois le <span class=\"file-last-modified\"></span>.",
"upload_modified_file": "Téléverser le fichier modifié",
"ignore_this_change": "Ignorer ce changement"
},
"app_context": {
"please_wait_for_save": "Veuillez patienter quelques secondes la fin de la sauvegarde, puis réessayer."
},
"note_create": {
"duplicated": "La note «{{title}}» a été dupliquée."
},
"image": {
"copied-to-clipboard": "Une référence à l'image a été copiée dans le presse-papiers. Elle peut être collée dans n'importe quelle note texte.",
"cannot-copy": "Impossible de copier la référence d'image dans le presse-papiers."
},
"clipboard": {
"cut": "Les note(s) ont été coupées dans le presse-papiers.",
"copied": "Les note(s) ont été coupées dans le presse-papiers."
},
"entrypoints": {
"note-revision-created": "La version de la note a été créée.",
"note-executed": "Note exécutée.",
"sql-error": "Erreur lors de l'exécution de la requête SQL: {{message}}"
},
"branches": {
"cannot-move-notes-here": "Impossible de déplacer les notes ici.",
"delete-status": "Etat de la suppression",
"delete-notes-in-progress": "Suppression des notes en cours : {{count}}",
"delete-finished-successfully": "Suppression terminée avec succès.",
"undeleting-notes-in-progress": "Restauration des notes en cours : {{count}}",
"undeleting-notes-finished-successfully": "Restauration des notes terminée avec succès."
},
"frontend_script_api": {
"async_warning": "Vous passez une fonction asynchrone à `api.runOnBackend()`, ce qui ne fonctionnera probablement pas comme vous le souhaitez.\\n Rendez la fonction synchronisée (en supprimant le mot-clé `async`), ou bien utilisez `api.runAsyncOnBackendWithManualTransactionHandling()`.",
"sync_warning": "Vous passez une fonction synchrone à `api.runAsyncOnBackendWithManualTransactionHandling()`,\\nalors que vous devriez probablement utiliser `api.runOnBackend()` à la place."
},
"ws": {
"sync-check-failed": "Le test de synchronisation a échoué !",
"consistency-checks-failed": "Les tests de cohérence ont échoué ! Consultez les journaux pour plus de détails.",
"encountered-error": "Erreur \"{{message}}\", consultez la console."
},
"hoisted_note": {
"confirm_unhoisting": "La note demandée «{{requestedNote}}» est en dehors du sous-arbre de la note focus «{{hoistedNote}}». Le focus doit être désactivé pour accéder à la note. Voulez-vous enlever le focus ?"
},
"launcher_context_menu": {
"reset_launcher_confirm": "Voulez-vous vraiment réinitialiser \"{{title}}\" ? Toutes les données / paramètres de cette note (et de ses enfants) seront perdus et le raccourci retrouvera son emplacement d'origine.",
"add-note-launcher": "Ajouter un raccourci de note",
"add-script-launcher": "Ajouter un raccourci de script",
"add-custom-widget": "Ajouter un widget personnalisé",
"add-spacer": "Ajouter un séparateur",
"delete": "Supprimer",
"reset": "Réinitialiser",
"move-to-visible-launchers": "Déplacer vers les raccourcis visibles",
"move-to-available-launchers": "Déplacer vers les raccourcis disponibles",
"duplicate-launcher": "Dupliquer le raccourci"
} }
} }

View File

@@ -414,17 +414,16 @@
"undelete_notes_instruction": "După ștergere, se pot recupera din ecranul Schimbări recente." "undelete_notes_instruction": "După ștergere, se pot recupera din ecranul Schimbări recente."
}, },
"delete_notes": { "delete_notes": {
"broken_relations_to_be_deleted": "Următoarele relații vor fi întrerupte și șterse (<span class=\"broke-relations-count\"></span>)", "broken_relations_to_be_deleted": "Următoarele relații vor fi întrerupte și șterse ({{- relationCount}})",
"cancel": "Anulează", "cancel": "Anulează",
"delete_all_clones_description": "Șterge și toate clonele (se pot recupera în ecranul Schimbări recente)", "delete_all_clones_description": "Șterge și toate clonele (se pot recupera în ecranul Schimbări recente)",
"delete_notes_preview": "Șterge previzualizările notițelor", "delete_notes_preview": "Previzualizare ștergerea notițelor",
"erase_notes_description": "Ștergerea obișnuită doar marchează notițele ca fiind șterse și pot fi recuperate (în ecranul Schimbări recente) pentru o perioadă de timp. Dacă se bifează această opțiune, notițele vor fi șterse imediat fără posibilitatea de a le recupera.", "erase_notes_description": "Ștergerea obișnuită doar marchează notițele ca fiind șterse și pot fi recuperate (în ecranul Schimbări recente) pentru o perioadă de timp. Dacă se bifează această opțiune, notițele vor fi șterse imediat fără posibilitatea de a le recupera.",
"erase_notes_warning": "șterge notițele permanent (nu se mai pot recupera), incluzând toate clonele. Va forța reîncărcarea aplicației.", "erase_notes_warning": "Șterge notițele permanent (nu se mai pot recupera), incluzând toate clonele. Va forța reîncărcarea aplicației.",
"no_note_to_delete": "Nicio notiță nu va fi ștearsă (doar clonele).", "no_note_to_delete": "Nicio notiță nu va fi ștearsă (doar clonele).",
"note": "Notiță", "notes_to_be_deleted": "Următoarele notițe vor fi șterse ({{- noteCount}})",
"notes_to_be_deleted": "Următoarele notițe vor fi șterse (<span class=\"deleted-notes-count\"></span>)",
"ok": "OK", "ok": "OK",
"to_be_deleted": " (pentru ștergere) este referențiat(ă) de relația <code>{{attrName}}</code> originând de la " "deleted_relation_text": "Notița {{- note}} ce va fi ștearsă este referențiată de relația {{- relation}}, originând din {{- source}}."
}, },
"delete_relation": { "delete_relation": {
"allowed_characters": "Se permit caractere alfanumerice, underline și două puncte.", "allowed_characters": "Se permit caractere alfanumerice, underline și două puncte.",
@@ -554,7 +553,7 @@
"open_sql_console": "Deschide consola SQL", "open_sql_console": "Deschide consola SQL",
"open_sql_console_history": "Deschide istoricul consolei SQL", "open_sql_console_history": "Deschide istoricul consolei SQL",
"options": "Opțiuni", "options": "Opțiuni",
"reload_frontend": "Reîncarcă interfață", "reload_frontend": "Reîncarcă interfața",
"reload_hint": "Reîncărcarea poate ajuta atunci când există ceva probleme vizuale fără a trebui repornită întreaga aplicație.", "reload_hint": "Reîncărcarea poate ajuta atunci când există ceva probleme vizuale fără a trebui repornită întreaga aplicație.",
"reset_zoom_level": "Resetează nivelul de zoom", "reset_zoom_level": "Resetează nivelul de zoom",
"show_backend_log": "Afișează log-ul din backend", "show_backend_log": "Afișează log-ul din backend",
@@ -1182,7 +1181,7 @@
"light_theme": "Temă luminoasă", "light_theme": "Temă luminoasă",
"override_theme_fonts_label": "Suprascrie fonturile temei", "override_theme_fonts_label": "Suprascrie fonturile temei",
"theme_label": "Temă", "theme_label": "Temă",
"title": "Temă" "title": "Tema aplicației"
}, },
"toast": { "toast": {
"critical-error": { "critical-error": {
@@ -1439,7 +1438,8 @@
"close_other_tabs": "Închide celelalte taburi", "close_other_tabs": "Închide celelalte taburi",
"close_tab": "Închide tab", "close_tab": "Închide tab",
"move_tab_to_new_window": "Mută acest tab în altă fereastră", "move_tab_to_new_window": "Mută acest tab în altă fereastră",
"new_tab": "Tab nou" "new_tab": "Tab nou",
"close_right_tabs": "Închide taburile din dreapta"
}, },
"toc": { "toc": {
"options": "Setări", "options": "Setări",
@@ -1497,5 +1497,29 @@
"move-to-available-launchers": "Mută în Lansatoare disponibile", "move-to-available-launchers": "Mută în Lansatoare disponibile",
"move-to-visible-launchers": "Mută în Lansatoare vizibile", "move-to-visible-launchers": "Mută în Lansatoare vizibile",
"reset": "Resetează" "reset": "Resetează"
},
"editable-text": {
"auto-detect-language": "Automat"
},
"highlighting": {
"color-scheme": "Temă de culori",
"title": "Evidențiere de sintaxă pentru notițele de tip text",
"description": "Controlează evidențierea de sintaxă pentru blocurile de cod în interiorul notițelor text, notițele de tip cod nu vor fi afectate de aceste setări."
},
"code_block": {
"word_wrapping": "Încadrare text"
},
"classic_editor_toolbar": {
"title": "Formatare"
},
"editing": {
"editor_type": {
"fixed": "Editor cu bară fixă (uneltele de editare vor apărea în tab-ul „Formatare” din panglică)",
"floating": "Editor cu bară flotantă (uneltele de editare vor apărea lângă cursor)",
"label": "Bară de formatare"
}
},
"editor": {
"title": "Editor"
} }
} }

View File

@@ -6,6 +6,7 @@ import searchService from "../../services/search/services/search.js";
import ValidationError from "../../errors/validation_error.js"; import ValidationError from "../../errors/validation_error.js";
import { Request } from 'express'; import { Request } from 'express';
import { changeLanguage } from "../../services/i18n.js"; import { changeLanguage } from "../../services/i18n.js";
import { listSyntaxHighlightingThemes } from "../../services/code_block_theme.js";
// options allowed to be updated directly in the Options dialog // options allowed to be updated directly in the Options dialog
const ALLOWED_OPTIONS = new Set([ const ALLOWED_OPTIONS = new Set([
@@ -15,6 +16,8 @@ const ALLOWED_OPTIONS = new Set([
'revisionSnapshotNumberLimit', 'revisionSnapshotNumberLimit',
'zoomFactor', 'zoomFactor',
'theme', 'theme',
'codeBlockTheme',
"codeBlockWordWrap",
'syncServerHost', 'syncServerHost',
'syncServerTimeout', 'syncServerTimeout',
'syncProxy', 'syncProxy',
@@ -62,7 +65,8 @@ const ALLOWED_OPTIONS = new Set([
'promotedAttributesOpenInRibbon', 'promotedAttributesOpenInRibbon',
'editedNotesOpenInRibbon', 'editedNotesOpenInRibbon',
'locale', 'locale',
'firstDayOfWeek' 'firstDayOfWeek',
'textNoteEditorType'
]); ]);
function getOptions() { function getOptions() {
@@ -138,6 +142,10 @@ function getUserThemes() {
return ret; return ret;
} }
function getSyntaxHighlightingThemes() {
return listSyntaxHighlightingThemes();
}
function getSupportedLocales() { function getSupportedLocales() {
// TODO: Currently hardcoded, needs to read the list of available languages. // TODO: Currently hardcoded, needs to read the list of available languages.
return [ return [
@@ -145,6 +153,10 @@ function getSupportedLocales() {
"id": "en", "id": "en",
"name": "English" "name": "English"
}, },
{
"id": "de",
"name": "Deutsch"
},
{ {
"id": "es", "id": "es",
"name": "Español" "name": "Español"
@@ -176,5 +188,6 @@ export default {
updateOption, updateOption,
updateOptions, updateOptions,
getUserThemes, getUserThemes,
getSyntaxHighlightingThemes,
getSupportedLocales getSupportedLocales
}; };

View File

@@ -12,14 +12,15 @@ import syncOptions from "../../services/sync_options.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import ws from "../../services/ws.js"; import ws from "../../services/ws.js";
import { Request } from 'express'; import { Request } from 'express';
import { EntityChange, EntityChangeRecord } from '../../services/entity_changes_interface.js'; import { EntityChange } from '../../services/entity_changes_interface.js';
import ValidationError from "../../errors/validation_error.js"; import ValidationError from "../../errors/validation_error.js";
import consistencyChecksService from "../../services/consistency_checks.js"; import consistencyChecksService from "../../services/consistency_checks.js";
import { t } from "i18next";
async function testSync() { async function testSync() {
try { try {
if (!syncOptions.isSyncSetup()) { if (!syncOptions.isSyncSetup()) {
return { success: false, message: "Sync server host is not configured. Please configure sync first." }; return { success: false, message: t("test_sync.not-configured") };
} }
await syncService.login(); await syncService.login();
@@ -28,7 +29,7 @@ async function testSync() {
// this is important in case when sync server has been just initialized // this is important in case when sync server has been just initialized
syncService.sync(); syncService.sync();
return { success: true, message: "Sync server handshake has been successful, sync has been started." }; return { success: true, message: t("test_sync.successful") };
} }
catch (e: any) { catch (e: any) {
return { return {

View File

@@ -102,6 +102,7 @@ function register(app: express.Application) {
app.use(`/${assetPath}/node_modules/codemirror/keymap/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/codemirror/keymap/'))); app.use(`/${assetPath}/node_modules/codemirror/keymap/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/codemirror/keymap/')));
app.use(`/${assetPath}/node_modules/mind-elixir/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/mind-elixir/dist/"))); app.use(`/${assetPath}/node_modules/mind-elixir/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/mind-elixir/dist/")));
app.use(`/${assetPath}/node_modules/@highlightjs/cdn-assets/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@highlightjs/cdn-assets/")));
} }
export default { export default {

View File

@@ -218,6 +218,7 @@ function register(app: express.Application) {
apiRoute(PUT, '/api/options/:name/:value*', optionsApiRoute.updateOption); apiRoute(PUT, '/api/options/:name/:value*', optionsApiRoute.updateOption);
apiRoute(PUT, '/api/options', optionsApiRoute.updateOptions); apiRoute(PUT, '/api/options', optionsApiRoute.updateOptions);
apiRoute(GET, '/api/options/user-themes', optionsApiRoute.getUserThemes); apiRoute(GET, '/api/options/user-themes', optionsApiRoute.getUserThemes);
apiRoute(GET, '/api/options/codeblock-themes', optionsApiRoute.getSyntaxHighlightingThemes);
apiRoute(GET, '/api/options/locales', optionsApiRoute.getSupportedLocales); apiRoute(GET, '/api/options/locales', optionsApiRoute.getSupportedLocales);
apiRoute(PST, '/api/password/change', passwordApiRoute.changePassword); apiRoute(PST, '/api/password/change', passwordApiRoute.changePassword);

View File

@@ -0,0 +1,105 @@
/**
* @module
*
* Manages the server-side functionality of the code blocks feature, mostly for obtaining the available themes for syntax highlighting.
*/
import fs from "fs";
import themeNames from "./code_block_theme_names.json" with { type: "json" }
import { t } from "i18next";
import { join } from "path";
import utils from "./utils.js";
import env from "./env.js";
/**
* Represents a color scheme for the code block syntax highlight.
*/
interface ColorTheme {
/** The ID of the color scheme which should be stored in the options. */
val: string;
/** A user-friendly name of the theme. The name is already localized. */
title: string;
}
/**
* Returns all the supported syntax highlighting themes for code blocks, in groups.
*
* The return value is an object where the keys represent groups in their human-readable name (e.g. "Light theme")
* and the values are an array containing the information about every theme. There is also a special group with no
* title (empty string) which should be displayed at the top of the listing pages, without a group.
*
* @returns the supported themes, grouped.
*/
export function listSyntaxHighlightingThemes() {
const path = join(utils.getResourceDir(), getStylesDirectory());
const systemThemes = readThemesFromFileSystem(path);
return {
"": [
{
val: "none",
title: t("code_block.theme_none")
}
],
...groupThemesByLightOrDark(systemThemes)
}
}
function getStylesDirectory() {
if (utils.isElectron() && !env.isDev()) {
return "styles";
}
return "node_modules/@highlightjs/cdn-assets/styles";
}
/**
* Reads all the predefined themes by listing all minified CSSes from a given directory.
*
* The theme names are mapped against a known list in order to provide more descriptive names such as "Visual Studio 2015 (Dark)" instead of "vs2015".
*
* @param path the path to read from. Usually this is the highlight.js `styles` directory.
* @returns the list of themes.
*/
function readThemesFromFileSystem(path: string): ColorTheme[] {
return fs.readdirSync(path)
.filter((el) => el.endsWith(".min.css"))
.map((name) => {
const nameWithoutExtension = name.replace(".min.css", "");
let title = nameWithoutExtension.replace(/-/g, " ");
if (title in themeNames) {
title = (themeNames as Record<string, string>)[title];
}
return {
val: `default:${nameWithoutExtension}`,
title: title
};
});
}
/**
* Groups a list of themes by dark or light themes. This is done simply by checking whether "Dark" is present in the given theme, otherwise it's considered a light theme.
* This generally only works if the theme has a known human-readable name (see {@link #readThemesFromFileSystem()})
*
* @param listOfThemes the list of themes to be grouped.
* @returns the grouped themes by light or dark.
*/
function groupThemesByLightOrDark(listOfThemes: ColorTheme[]) {
const darkThemes = [];
const lightThemes = [];
for (const theme of listOfThemes) {
if (theme.title.includes("Dark")) {
darkThemes.push(theme);
} else {
lightThemes.push(theme);
}
}
const output: Record<string, ColorTheme[]> = {};
output[t("code_block.theme_group_light")] = lightThemes;
output[t("code_block.theme_group_dark")] = darkThemes;
return output;
}

View File

@@ -0,0 +1,75 @@
{
"1c light": "1C (Light)",
"a11y dark": "a11y (Dark)",
"a11y light": "a11y (Light)",
"agate": "Agate (Dark)",
"an old hope": "An Old Hope (Dark)",
"androidstudio": "Android Studio (Dark)",
"arduino light": "Arduino (Light)",
"arta": "Arta (Dark)",
"ascetic": "Ascetic (Light)",
"atom one dark reasonable": "Atom One with ReasonML support (Dark)",
"atom one dark": "Atom One (Dark)",
"atom one light": "Atom One (Light)",
"brown paper": "Brown Paper (Light)",
"codepen embed": "CodePen Embed (Dark)",
"color brewer": "Color Brewer (Light)",
"dark": "Dark",
"default": "Original highlight.js Theme (Light)",
"devibeans": "devibeans (Dark)",
"docco": "Docco (Light)",
"far": "FAR (Dark)",
"felipec": "FelipeC (Dark)",
"foundation": "Foundation 4 Docs (Light)",
"github dark dimmed": "GitHub Dimmed (Dark)",
"github dark": "GitHub (Dark)",
"github": "GitHub (Light)",
"gml": "GML (Dark)",
"googlecode": "Google Code (Light)",
"gradient dark": "Gradient (Dark)",
"gradient light": "Gradient (Light)",
"grayscale": "Grayscale (Light)",
"hybrid": "hybrid (Dark)",
"idea": "Idea (Light)",
"intellij light": "IntelliJ (Light)",
"ir black": "IR Black (Dark)",
"isbl editor dark": "ISBL Editor (Dark)",
"isbl editor light": "ISBL Editor (Light)",
"kimbie dark": "Kimbie (Dark)",
"kimbie light": "Kimbie (Light)",
"lightfair": "Lightfair (Light)",
"lioshi": "Lioshi (Dark)",
"magula": "Magula (Light)",
"mono blue": "Mono Blue (Light)",
"monokai sublime": "Monokai Sublime (Dark)",
"monokai": "Monokai (Dark)",
"night owl": "Night Owl (Dark)",
"nnfx dark": "NNFX (Dark)",
"nnfx light": "NNFX (Light)",
"nord": "Nord (Dark)",
"obsidian": "Obsidian (Dark)",
"panda syntax dark": "Panda (Dark)",
"panda syntax light": "Panda (Light)",
"paraiso dark": "Paraiso (Dark)",
"paraiso light": "Paraiso (Light)",
"pojoaque": "Pojoaque (Dark)",
"purebasic": "PureBasic (Light)",
"qtcreator dark": "Qt Creator (Dark)",
"qtcreator light": "Qt Creator (Light)",
"rainbow": "Rainbow (Dark)",
"routeros": "RouterOS Script (Light)",
"school book": "School Book (Light)",
"shades of purple": "Shades of Purple (Dark)",
"srcery": "Srcery (Dark)",
"stackoverflow dark": "Stack Overflow (Dark)",
"stackoverflow light": "Stack Overflow (Light)",
"sunburst": "Sunburst (Dark)",
"tokyo night dark": "Tokyo Night (Dark)",
"tokyo night light": "Tokyo Night (Light)",
"tomorrow night blue": "Tomorrow Night Blue (Dark)",
"tomorrow night bright": "Tomorrow Night Bright (Dark)",
"vs": "Visual Studio (Light)",
"vs2015": "Visual Studio 2015 (Dark)",
"xcode": "Xcode (Light)",
"xt256": "xt256 (Dark)"
}

View File

@@ -58,8 +58,16 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
function getDataFileName(type: string | null, mime: string, baseFileName: string, existingFileNames: Record<string, number>): string { function getDataFileName(type: string | null, mime: string, baseFileName: string, existingFileNames: Record<string, number>): string {
let fileName = baseFileName.trim(); let fileName = baseFileName.trim();
// Crop fileName to avoid its length exceeding 30 and prevent cutting into the extension.
if (fileName.length > 30) { if (fileName.length > 30) {
fileName = fileName.substr(0, 30).trim(); // We use regex to match the extension to preserve multiple dots in extensions (e.g. .tar.gz).
let match = fileName.match(/(\.[a-zA-Z0-9_.!#-]+)$/);
let ext = match ? match[0] : '';
// Crop the extension if extension length exceeds 30
const croppedExt = ext.slice(-30);
// Crop the file name section and append the cropped extension
fileName = fileName.slice(0, 30 - croppedExt.length) + croppedExt;
} }
let existingExtension = path.extname(fileName).toLowerCase(); let existingExtension = path.extname(fileName).toLowerCase();
@@ -76,6 +84,9 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
else if (mime === 'application/x-javascript' || mime === 'text/javascript') { else if (mime === 'application/x-javascript' || mime === 'text/javascript') {
newExtension = 'js'; newExtension = 'js';
} }
else if (type === 'canvas' || mime === 'application/json') {
newExtension = 'json';
}
else if (existingExtension.length > 0) { // if the page already has an extension, then we'll just keep it else if (existingExtension.length > 0) { // if the page already has an extension, then we'll just keep it
newExtension = null; newExtension = null;
} }

View File

@@ -2,10 +2,8 @@ import i18next from "i18next";
import Backend from "i18next-fs-backend"; import Backend from "i18next-fs-backend";
import options from "./options.js"; import options from "./options.js";
import sql_init from "./sql_init.js"; import sql_init from "./sql_init.js";
import { fileURLToPath } from "url"; import { join } from "path";
import { dirname, join } from "path"; import { getResourceDir } from "./utils.js";
import utils from "./utils.js";
import env from "./env.js";
export async function initializeTranslations() { export async function initializeTranslations() {
const resourceDir = getResourceDir(); const resourceDir = getResourceDir();
@@ -21,14 +19,6 @@ export async function initializeTranslations() {
}); });
} }
function getResourceDir() {
if (utils.isElectron() && !env.isDev()) {
return process.resourcesPath;
} else {
return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
}
}
function getCurrentLanguage() { function getCurrentLanguage() {
let language; let language;
if (sql_init.isDbInitialized()) { if (sql_init.isDbInitialized()) {

View File

@@ -420,6 +420,12 @@ function getDefaultKeyboardActions() {
separator: t("keyboard_actions.ribbon-tabs") separator: t("keyboard_actions.ribbon-tabs")
}, },
{
actionName: "toggleRibbonTabClassicEditor",
defaultShortcuts: [],
description: t("keyboard_actions.toggle-classic-editor-toolbar"),
scope: "window"
},
{ {
actionName: "toggleRibbonTabBasicProperties", actionName: "toggleRibbonTabBasicProperties",
defaultShortcuts: [], defaultShortcuts: [],

View File

@@ -1,8 +1,27 @@
/**
* @module
*
* Options are key-value pairs that are used to store information such as user preferences (for example
* the current theme, sync server information), but also information about the state of the application.
*
* Although options internally are represented as strings, their value can be interpreted as a number or
* boolean by calling the appropriate methods from this service (e.g. {@link #getOptionInt}).\
*
* Generally options are shared across multiple instances of the application via the sync mechanism,
* however it is possible to have options that are local to an instance. For example, the user can select
* a theme on a device and it will not affect other devices.
*/
import becca from "../becca/becca.js"; import becca from "../becca/becca.js";
import BOption from "../becca/entities/boption.js"; import BOption from "../becca/entities/boption.js";
import { OptionRow } from '../becca/entities/rows.js'; import { OptionRow } from '../becca/entities/rows.js';
import sql from "./sql.js"; import sql from "./sql.js";
/**
* A dictionary where the keys are the option keys (e.g. `theme`) and their corresponding values.
*/
export type OptionMap = Record<string | number, string>;
function getOptionOrNull(name: string): string | null { function getOptionOrNull(name: string): string | null {
let option; let option;
@@ -69,6 +88,13 @@ function setOption(name: string, value: string | number | boolean) {
} }
} }
/**
* Creates a new option in the database, with the given name, value and whether it should be synced.
*
* @param name the name of the option to be created.
* @param value the value of the option, as a string. It can then be interpreted as other types such as a number of boolean.
* @param isSynced `true` if the value should be synced across multiple instances (e.g. locale) or `false` if it should be local-only (e.g. theme).
*/
function createOption(name: string, value: string, isSynced: boolean) { function createOption(name: string, value: string, isSynced: boolean) {
new BOption({ new BOption({
name: name, name: name,
@@ -82,7 +108,7 @@ function getOptions() {
} }
function getOptionMap() { function getOptionMap() {
const map: Record<string | number, string> = {}; const map: OptionMap = {};
for (const option of Object.values(becca.options)) { for (const option of Object.values(becca.options)) {
map[option.name] = option.value; map[option.name] = option.value;

View File

@@ -1,4 +1,5 @@
import optionService from "./options.js"; import optionService from "./options.js";
import type { OptionMap } from "./options.js";
import appInfo from "./app_info.js"; import appInfo from "./app_info.js";
import utils from "./utils.js"; import utils from "./utils.js";
import log from "./log.js"; import log from "./log.js";
@@ -11,17 +12,35 @@ function initDocumentOptions() {
optionService.createOption('documentSecret', utils.randomSecureToken(16), false); optionService.createOption('documentSecret', utils.randomSecureToken(16), false);
} }
/**
* Contains additional options to be initialized for a new database, containing the information entered by the user.
*/
interface NotSyncedOpts { interface NotSyncedOpts {
syncServerHost?: string; syncServerHost?: string;
syncProxy?: string; syncProxy?: string;
} }
/**
* Represents a correspondence between an option and its default value, to be initialized when the database is missing that particular option (after a migration from an older version, or when creating a new database).
*/
interface DefaultOption { interface DefaultOption {
name: string; name: string;
value: string; /**
* The value to initialize the option with, if the option is not already present in the database.
*
* If a function is passed in instead, the function is called if the option does not exist (with access to the current options) and the return value is used instead. Useful to migrate a new option with a value depending on some other option that might be initialized.
*/
value: string | ((options: OptionMap) => string);
isSynced: boolean; isSynced: boolean;
} }
/**
* Initializes the default options for new databases only.
*
* @param initialized `true` if the database has been fully initialized (i.e. a new database was created), or `false` if the database is created for sync.
* @param theme the theme to set as default, based on a user's system preference.
* @param opts additional options to be initialized, for example the sync configuration.
*/
async function initNotSyncedOptions(initialized: boolean, theme: string, opts: NotSyncedOpts = {}) { async function initNotSyncedOptions(initialized: boolean, theme: string, opts: NotSyncedOpts = {}) {
optionService.createOption('openNoteContexts', JSON.stringify([ optionService.createOption('openNoteContexts', JSON.stringify([
{ {
@@ -47,6 +66,9 @@ async function initNotSyncedOptions(initialized: boolean, theme: string, opts: N
optionService.createOption('syncProxy', opts.syncProxy || '', false); optionService.createOption('syncProxy', opts.syncProxy || '', false);
} }
/**
* Contains all the default options that must be initialized on new and existing databases (at startup). The value can also be determined based on other options, provided they have already been initialized.
*/
const defaultOptions: DefaultOption[] = [ const defaultOptions: DefaultOption[] = [
{ name: 'revisionSnapshotTimeInterval', value: '600', isSynced: true }, { name: 'revisionSnapshotTimeInterval', value: '600', isSynced: true },
{ name: 'revisionSnapshotNumberLimit', value: '-1', isSynced: true }, { name: 'revisionSnapshotNumberLimit', value: '-1', isSynced: true },
@@ -99,9 +121,27 @@ const defaultOptions: DefaultOption[] = [
// Internationalization // Internationalization
{ name: 'locale', value: 'en', isSynced: true }, { name: 'locale', value: 'en', isSynced: true },
{ name: 'firstDayOfWeek', value: '1', isSynced: true } { name: 'firstDayOfWeek', value: '1', isSynced: true },
// Code block configuration
{ name: "codeBlockTheme", value: (optionsMap) => {
if (optionsMap.theme === "light") {
return "default:stackoverflow-light";
} else {
return "default:stackoverflow-dark";
}
}, isSynced: false },
{ name: "codeBlockWordWrap", value: "false", isSynced: true },
// Text note configuration
{ name: "textNoteEditorType", value: "ckeditor-balloon", isSynced: true }
]; ];
/**
* Initializes the options, by checking which options from {@link #allDefaultOptions()} are missing and registering them. It will also check some environment variables such as safe mode, to make any necessary adjustments.
*
* This method is called regardless of whether a new database is created, or an existing database is used.
*/
function initStartupOptions() { function initStartupOptions() {
const optionsMap = optionService.getOptionMap(); const optionsMap = optionService.getOptionMap();
@@ -109,9 +149,15 @@ function initStartupOptions() {
for (const {name, value, isSynced} of allDefaultOptions) { for (const {name, value, isSynced} of allDefaultOptions) {
if (!(name in optionsMap)) { if (!(name in optionsMap)) {
optionService.createOption(name, value, isSynced); let resolvedValue;
if (typeof value === "function") {
resolvedValue = value(optionsMap);
} else {
resolvedValue = value;
}
log.info(`Created option "${name}" with default value "${value}"`); optionService.createOption(name, resolvedValue, isSynced);
log.info(`Created option "${name}" with default value "${resolvedValue}"`);
} }
} }

View File

@@ -7,6 +7,9 @@ import escape from "escape-html";
import sanitize from "sanitize-filename"; import sanitize from "sanitize-filename";
import mimeTypes from "mime-types"; import mimeTypes from "mime-types";
import path from "path"; import path from "path";
import { fileURLToPath } from "url";
import env from "./env.js";
import { dirname, join } from "path";
const randtoken = generator({source: 'crypto'}); const randtoken = generator({source: 'crypto'});
@@ -315,6 +318,20 @@ function isString(x: any) {
return Object.prototype.toString.call(x) === "[object String]"; return Object.prototype.toString.call(x) === "[object String]";
} }
/**
* Returns the directory for resources. On Electron builds this corresponds to the `resources` subdirectory inside the distributable package.
* On development builds, this simply refers to the root directory of the application.
*
* @returns the resource dir.
*/
export function getResourceDir() {
if (isElectron() && !env.isDev()) {
return process.resourcesPath;
} else {
return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
}
}
export default { export default {
randomSecureToken, randomSecureToken,
randomString, randomString,
@@ -347,5 +364,6 @@ export default {
normalize, normalize,
hashedBlobId, hashedBlobId,
toMap, toMap,
isString isString,
getResourceDir
}; };

View File

@@ -111,13 +111,9 @@ async function createMainWindow(app: App) {
} }
function configureWebContents(webContents: WebContents, spellcheckEnabled: boolean) { function configureWebContents(webContents: WebContents, spellcheckEnabled: boolean) {
if (!mainWindow) {
return;
}
remoteMain.enable(webContents); remoteMain.enable(webContents);
mainWindow.webContents.setWindowOpenHandler((details) => { webContents.setWindowOpenHandler((details) => {
async function openExternal() { async function openExternal() {
(await import('electron')).shell.openExternal(details.url); (await import('electron')).shell.openExternal(details.url);
} }

196
translations/de/server.json Normal file
View File

@@ -0,0 +1,196 @@
{
"keyboard_actions": {
"open-jump-to-note-dialog": "Öffne das Dialogfeld \"Zu Notiz springen\"",
"search-in-subtree": "Suche nach Notizen im Unterbaum der aktuellen Notiz",
"expand-subtree": "Erweitere den Unterbaum der aktuellen Notiz",
"collapse-tree": "Kollabiere den gesamten Notizbaum",
"collapse-subtree": "Kollabiere den Unterbaum der aktuellen Notiz",
"sort-child-notes": "Sortiere untergeordnete Notizen",
"creating-and-moving-notes": "Erstellen und Verschieben von Notizen",
"create-note-into-inbox": "Erstelle eine Notiz im Posteingang (falls definiert) oder in der Tagesnotiz",
"delete-note": "Notiz löschen",
"move-note-up": "Notiz nach oben verschieben",
"move-note-down": "Notiz nach unten verschieben",
"move-note-up-in-hierarchy": "Notiz in der Hierarchie nach oben verschieben",
"move-note-down-in-hierarchy": "Notiz in der Hierarchie nach unten verschieben",
"edit-note-title": "Springe vom Baum zur Notiz-Detailansicht und bearbeite den Titel",
"edit-branch-prefix": "Zeige Dialog zum Bearbeiten des Zweigpräfixes",
"note-clipboard": "Notiz-Zwischenablage",
"copy-notes-to-clipboard": "Kopiere ausgewählte Notizen in die Zwischenablage",
"paste-notes-from-clipboard": "Füge Notizen aus der Zwischenablage in die aktive Notiz ein",
"cut-notes-to-clipboard": "Schneide ausgewählte Notizen in die Zwischenablage",
"select-all-notes-in-parent": "Wähle alle Notizen der aktuellen Notizenebene",
"add-note-above-to-the-selection": "Füge eine Notiz oberhalb zur Auswahl hinzu",
"add-note-below-to-selection": "Füge eine Notiz unterhalb zur Auswahl hinzu",
"duplicate-subtree": "Dupliziere den Unterbaum",
"tabs-and-windows": "Tabs & Fenster",
"open-new-tab": "Öffne einen neuen Tab",
"close-active-tab": "Schließe den aktiven Tab",
"reopen-last-tab": "Öffne den zuletzt geschlossenen Tab",
"activate-next-tab": "Aktiviere den Tab rechts",
"activate-previous-tab": "Aktiviere den Tab links",
"open-new-window": "Öffne ein neues leeres Fenster",
"toggle-tray": "Zeige/verstecke die Anwendung im Systemtray",
"first-tab": "Aktiviere den ersten Tab in der Liste",
"second-tab": "Aktiviere den zweiten Tab in der Liste",
"third-tab": "Aktiviere den dritten Tab in der Liste",
"fourth-tab": "Aktiviere den vierten Tab in der Liste",
"fifth-tab": "Aktiviere den fünften Tab in der Liste",
"sixth-tab": "Aktiviere den sechsten Tab in der Liste",
"seventh-tab": "Aktiviere den siebten Tab in der Liste",
"eight-tab": "Aktiviere den achten Tab in der Liste",
"ninth-tab": "Aktiviere den neunten Tab in der Liste",
"last-tab": "Aktiviere den letzten Tab in der Liste",
"dialogs": "Dialoge",
"show-note-source": "Zeige das Dialogfeld der Notizquelle",
"show-options": "Zeige das Dialogfeld der Optionen",
"show-revisions": "Zeige das Dialogfeld der Notizrevisionen",
"show-recent-changes": "Zeige das Dialogfeld der letzten Änderungen",
"show-sql-console": "Zeige das Dialogfeld der SQL-Konsole",
"show-backend-log": "Zeige das Dialogfeld des Backend-Logs",
"text-note-operations": "Textnotizoperationen",
"add-link-to-text": "Öffne das Dialogfeld zum Hinzufügen eines Links zum Text",
"follow-link-under-cursor": "Folge dem Link, unter dem sich der Cursor befindet",
"insert-date-and-time-to-text": "Füge das aktuelle Datum und die Uhrzeit in den Text ein",
"paste-markdown-into-text": "Füge Markdown aus der Zwischenablage in die Textnotiz ein",
"cut-into-note": "Schneide die Auswahl aus der aktuellen Notiz und erstelle eine Unternotiz mit dem ausgewählten Text",
"add-include-note-to-text": "Öffne das Dialogfeld zum Einfügen einer Notiz",
"edit-readonly-note": "Bearbeite eine schreibgeschützte Notiz",
"attributes-labels-and-relations": "Attribute (Labels & Beziehungen)",
"add-new-label": "Erstelle ein neues Label",
"create-new-relation": "Erstelle eine neue Beziehung",
"ribbon-tabs": "Ribbon-Tabs",
"toggle-basic-properties": "Schalte die Grundattribute um",
"toggle-file-properties": "Schalte die Dateiattribute um",
"toggle-image-properties": "Schalte die Bildattribute um",
"toggle-owned-attributes": "Schalte eigene Attribute um",
"toggle-inherited-attributes": "Schalte vererbte Attribute um",
"toggle-promoted-attributes": "Schalte beworbene Attribute um",
"toggle-link-map": "Schalte die Link-Karte um",
"toggle-note-info": "Schalte Notizinformationen um",
"toggle-note-paths": "Schalte Notizpfade um",
"toggle-similar-notes": "Schalte ähnliche Notizen um",
"other": "Andere",
"toggle-right-pane": "Schalte die Anzeige des rechten Fensters um, das Inhaltsverzeichnis und Markierungen enthält",
"print-active-note": "Drucke die aktive Notiz",
"open-note-externally": "Öffne die Notiz als Datei mit der Standardanwendung",
"render-active-note": "Render (erneut rendern) der aktiven Notiz",
"run-active-note": "Führe den aktiven JavaScript (Frontend/Backend) Notizcode aus",
"toggle-note-hoisting": "Schaltet das Hoisting der aktiven Notiz um",
"unhoist": "Von überall ent-hoisten",
"reload-frontend-app": "Lade die Frontend-App neu",
"open-dev-tools": "Öffne die Entwicklertools",
"toggle-left-note-tree-panel": "Schalte das linke Notizbaum-Panel um",
"toggle-full-screen": "Schalte den Vollbildmodus um",
"zoom-out": "Zoome heraus",
"zoom-in": "Zoome hinein",
"note-navigation": "Notiznavigation",
"reset-zoom-level": "Setze den Zoomlevel zurück",
"copy-without-formatting": "Kopiere den ausgewählten Text ohne Formatierung",
"force-save-revision": "Erzwinge das Erstellen / Speichern einer neuen Notizrevision der aktiven Notiz",
"show-help": "Zeige die eingebaute Hilfe / Cheat-Sheet",
"toggle-book-properties": "Schalte die Buch-Eigenschaften um"
},
"login": {
"title": "Anmeldung",
"heading": "Trilium Anmeldung",
"incorrect-password": "Das Passwort ist falsch. Bitte versuche es erneut.",
"password": "Passwort",
"remember-me": "Erinnere dich an mich",
"button": "Anmelden"
},
"set_password": {
"heading": "Passwort festlegen",
"description": "Bevor du Trilium im Web verwenden kannst, musst du zuerst ein Passwort festlegen. Du wirst dieses Passwort dann zur Anmeldung verwenden.",
"password": "Passwort",
"password-confirmation": "Passwortbestätigung",
"button": "Passwort festlegen"
},
"javascript-required": "Trilium erfordert, dass JavaScript aktiviert ist.",
"setup": {
"heading": "TriliumNext Notizen Setup",
"new-document": "Ich bin ein neuer Benutzer und möchte ein neues Trilium-Dokument für meine Notizen erstellen",
"sync-from-desktop": "Ich habe bereits eine Desktop-Instanz und möchte die Synchronisierung damit einrichten",
"sync-from-server": "Ich habe bereits eine Server-Instanz und möchte die Synchronisierung damit einrichten",
"next": "Weiter",
"init-in-progress": "Dokumenteninitialisierung läuft",
"redirecting": "Du wirst in Kürze zur Anwendung weitergeleitet.",
"title": "Setup"
},
"setup_sync-from-desktop": {
"heading": "Synchronisation vom Desktop",
"description": "Dieses Setup muss von der Desktop-Instanz aus initiiert werden:",
"step1": "Öffne deine TriliumNext Notes Desktop-Instanz.",
"step2": "Klicke im Trilium-Menü auf Optionen.",
"step3": "Klicke auf die Kategorie Synchronisation.",
"step4": "Ändere die Server-Instanzadresse auf: {{- host}} und klicke auf Speichern.",
"step5": "Klicke auf den Button \"Test-Synchronisation\", um zu überprüfen, ob die Verbindung erfolgreich ist.",
"step6": "Sobald du diese Schritte abgeschlossen hast, klicke auf {{- link}}.",
"step6-here": "hier"
},
"setup_sync-from-server": {
"heading": "Synchronisation vom Server",
"instructions": "Bitte gib unten die Trilium-Server-Adresse und die Zugangsdaten ein. Dies wird das gesamte Trilium-Dokument vom Server herunterladen und die Synchronisation einrichten. Je nach Dokumentgröße und Verbindungsgeschwindigkeit kann dies eine Weile dauern.",
"server-host": "Trilium Server-Adresse",
"server-host-placeholder": "https://<hostname>:<port>",
"proxy-server": "Proxy-Server (optional)",
"proxy-server-placeholder": "https://<hostname>:<port>",
"note": "Hinweis:",
"proxy-instruction": "Wenn du die Proxy-Einstellung leer lässt, wird der System-Proxy verwendet (nur für die Desktop-Anwendung)",
"password": "Passwort",
"password-placeholder": "Passwort",
"back": "Zurück",
"finish-setup": "Setup abschließen"
},
"setup_sync-in-progress": {
"heading": "Synchronisation läuft",
"successful": "Die Synchronisation wurde erfolgreich eingerichtet. Es wird eine Weile dauern, bis die erste Synchronisation abgeschlossen ist. Sobald dies erledigt ist, wirst du zur Anmeldeseite weitergeleitet.",
"outstanding-items": "Ausstehende Synchronisationselemente:",
"outstanding-items-default": "N/A"
},
"share_404": {
"title": "Nicht gefunden",
"heading": "Nicht gefunden"
},
"share_page": {
"parent": "Eltern:",
"clipped-from": "Diese Notiz wurde ursprünglich von {{- url}} ausgeschnitten",
"child-notes": "Untergeordnete Notizen:",
"no-content": "Diese Notiz hat keinen Inhalt."
},
"weekdays": {
"monday": "Montag",
"tuesday": "Dienstag",
"wednesday": "Mittwoch",
"thursday": "Donnerstag",
"friday": "Freitag",
"saturday": "Samstag",
"sunday": "Sonntag"
},
"months": {
"january": "Januar",
"february": "Februar",
"march": "März",
"april": "April",
"may": "Mai",
"june": "Juni",
"july": "Juli",
"august": "August",
"september": "September",
"october": "Oktober",
"november": "November",
"december": "Dezember"
},
"special_notes": {
"search_prefix": "Suche:"
},
"code_block": {
"theme_none": "Keine Syntax-Hervorhebung",
"theme_group_light": "Helle Themen",
"theme_group_dark": "Dunkle Themen"
},
"test_sync": {
"not-configured": "Der Synchronisations-Server-Host ist nicht konfiguriert. Bitte konfiguriere zuerst die Synchronisation.",
"successful": "Die Server-Verbindung wurde erfolgreich hergestellt, die Synchronisation wurde gestartet."
}
}

View File

@@ -89,7 +89,8 @@
"copy-without-formatting": "Copy selected text without formatting", "copy-without-formatting": "Copy selected text without formatting",
"force-save-revision": "Force creating / saving new note revision of the active note", "force-save-revision": "Force creating / saving new note revision of the active note",
"show-help": "Shows built-in Help / cheatsheet", "show-help": "Shows built-in Help / cheatsheet",
"toggle-book-properties": "Toggle Book Properties" "toggle-book-properties": "Toggle Book Properties",
"toggle-classic-editor-toolbar": "Toggle the Formatting tab for the editor with fixed toolbar"
}, },
"login": { "login": {
"title": "Login", "title": "Login",
@@ -183,5 +184,14 @@
}, },
"special_notes": { "special_notes": {
"search_prefix": "Search:" "search_prefix": "Search:"
},
"code_block": {
"theme_none": "No syntax highlighting",
"theme_group_light": "Light themes",
"theme_group_dark": "Dark themes"
},
"test_sync": {
"not-configured": "Sync server host is not configured. Please configure sync first.",
"successful": "Sync server handshake has been successful, sync has been started."
} }
} }

View File

@@ -89,7 +89,8 @@
"copy-without-formatting": "Copiar el texto seleccionado sin formatear", "copy-without-formatting": "Copiar el texto seleccionado sin formatear",
"force-save-revision": "Forzar la creación/guardado de una nueva revisión de nota de la nota activa", "force-save-revision": "Forzar la creación/guardado de una nueva revisión de nota de la nota activa",
"show-help": "Muestra ayuda/hoja de referencia integrada", "show-help": "Muestra ayuda/hoja de referencia integrada",
"toggle-book-properties": "Alternar propiedades del libro" "toggle-book-properties": "Alternar propiedades del libro",
"toggle-classic-editor-toolbar": "Alternar la pestaña de formato por el editor con barra de herramientas fija"
}, },
"login": { "login": {
"title": "Iniciar sesión", "title": "Iniciar sesión",
@@ -180,5 +181,17 @@
"october": "Octubre", "october": "Octubre",
"november": "Noviembre", "november": "Noviembre",
"december": "Diciembre" "december": "Diciembre"
},
"special_notes": {
"search_prefix": "Buscar:"
},
"code_block": {
"theme_none": "Sin resaltado de sintaxis",
"theme_group_light": "Temas claros",
"theme_group_dark": "Temas oscuros"
},
"test_sync": {
"not-configured": "El servidor de sincronización no está configurado. Por favor configure primero la sincronización.",
"successful": "El protocolo de enlace del servidor de sincronización ha sido exitoso, la sincronización ha comenzado."
} }
} }

View File

@@ -66,7 +66,7 @@
"toggle-owned-attributes": "Afficher/masquer les Attributs propres", "toggle-owned-attributes": "Afficher/masquer les Attributs propres",
"toggle-inherited-attributes": "Afficher/masquer les Attributs hérités", "toggle-inherited-attributes": "Afficher/masquer les Attributs hérités",
"toggle-promoted-attributes": "Afficher/masquer les Attributs promus", "toggle-promoted-attributes": "Afficher/masquer les Attributs promus",
"toggle-link-map": "Afficher/masquer la Carte des liens", "toggle-link-map": "Afficher/masquer la Carte de la note",
"toggle-note-info": "Afficher/masquer les Informations de la note", "toggle-note-info": "Afficher/masquer les Informations de la note",
"toggle-note-paths": "Afficher/masquer les Emplacements de la note", "toggle-note-paths": "Afficher/masquer les Emplacements de la note",
"toggle-similar-notes": "Afficher/masquer les Notes similaires", "toggle-similar-notes": "Afficher/masquer les Notes similaires",
@@ -157,5 +157,31 @@
"clipped-from": "Cette note a été initialement extraite de {{- url}}", "clipped-from": "Cette note a été initialement extraite de {{- url}}",
"child-notes": "Notes enfants :", "child-notes": "Notes enfants :",
"no-content": "Cette note n'a aucun contenu." "no-content": "Cette note n'a aucun contenu."
},
"weekdays": {
"monday": "Lundi",
"tuesday": "Mardi",
"wednesday": "Mercredi",
"thursday": "Jeudi",
"friday": "Vendredi",
"saturday": "Samedi",
"sunday": "Dimanche"
},
"months": {
"january": "Janvier",
"february": "Février",
"march": "Mars",
"april": "Avril",
"may": "Mai",
"june": "Juin",
"july": "Juillet",
"august": "Août",
"september": "Septembre",
"october": "Octobre",
"november": "Novembre",
"december": "Décembre"
},
"special_notes": {
"search_prefix": "Recherche :"
} }
} }

View File

@@ -89,7 +89,8 @@
"toggle-tray": "Afișează/ascunde aplicația din tray-ul de sistem", "toggle-tray": "Afișează/ascunde aplicația din tray-ul de sistem",
"unhoist": "Defocalizează complet", "unhoist": "Defocalizează complet",
"zoom-in": "Mărește zoom-ul", "zoom-in": "Mărește zoom-ul",
"zoom-out": "Micșorează zoom-ul" "zoom-out": "Micșorează zoom-ul",
"toggle-classic-editor-toolbar": "Comută tab-ul „Formatare” pentru editorul cu bară fixă"
}, },
"login": { "login": {
"button": "Autentifică", "button": "Autentifică",
@@ -183,5 +184,14 @@
}, },
"special_notes": { "special_notes": {
"search_prefix": "Căutare:" "search_prefix": "Căutare:"
},
"code_block": {
"theme_none": "Fără evidențiere de sintaxă",
"theme_group_dark": "Teme întunecate",
"theme_group_light": "Teme luminoase"
},
"test_sync": {
"not-configured": "Calea către serverul de sincronizare nu este configurată. Configurați sincronizarea înainte.",
"successful": "Comunicarea cu serverul de sincronizare a avut loc cu succes, s-a început sincronizarea."
} }
} }