Merge branch 'develop' into ai-llm-integration

This commit is contained in:
perf3ct 2025-03-08 20:51:57 +00:00
commit 9f84a84f96
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
323 changed files with 4558 additions and 116282 deletions

View File

@ -1,10 +1,37 @@
.git # ignored Files
.idea .dockerignore
.editorconfig
.git*
.prettier*
electron*
entitlements.plist
forge.config.cjs
nodemon.json
renovate.json
trilium.iml
Dockerfile
Dockerfile.*
npm-debug.log
/src/**/*.spec.ts
# ignored folders
/.cache
/.git
/.github
/.idea
/.vscode
/bin /bin
/build
/dist /dist
/docs /docs
/npm-debug.log /dump-db
node_modules /e2e
/integration-tests
/spec
/test
/test-etapi
/node_modules
src/**/*.ts
!src/services/asset_path.ts # exceptions
!/bin/copy-dist.ts

View File

@ -44,16 +44,6 @@ jobs:
- test_dev - test_dev
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- uses: docker/setup-buildx-action@v3 - uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6 - uses: docker/build-push-action@v6
with: with:
@ -82,20 +72,6 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Build and export to Docker - name: Build and export to Docker
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:

View File

@ -57,9 +57,6 @@ jobs:
- name: Run the TypeScript build - name: Run the TypeScript build
run: npx tsc run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Build and export to Docker - name: Build and export to Docker
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
@ -154,18 +151,6 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Login to GHCR - name: Login to GHCR
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:

View File

@ -2,4 +2,5 @@
*.md *.md
*.yml *.yml
libraries/* libraries/*
docs/* docs/*
src/public/app/doc_notes/**/*

View File

@ -1,3 +1,7 @@
{ {
"recommendations": ["lokalise.i18n-ally", "editorconfig.editorconfig"] "recommendations": [
"lokalise.i18n-ally",
"editorconfig.editorconfig",
"vitest.explorer"
]
} }

4
.vscode/launch.json vendored
View File

@ -5,8 +5,8 @@
{ {
"console": "integratedTerminal", "console": "integratedTerminal",
"internalConsoleOptions": "neverOpen", "internalConsoleOptions": "neverOpen",
"name": "nodemon server:start", "name": "nodemon start-server",
"program": "${workspaceFolder}/src/main", "program": "${workspaceFolder}/src/www",
"request": "launch", "request": "launch",
"restart": true, "restart": true,
"runtimeExecutable": "nodemon", "runtimeExecutable": "nodemon",

View File

@ -19,5 +19,12 @@
"[css]": { "[css]": {
"editor.defaultFormatter": "vscode.css-language-features" "editor.defaultFormatter": "vscode.css-language-features"
}, },
"npm.exclude": ["**/build", "**/dist", "**/out/**"] "npm.exclude": [
"**/build",
"**/dist",
"**/out/**"
],
"[xml]": {
"editor.defaultFormatter": "redhat.vscode-xml"
}
} }

View File

@ -1,62 +1,46 @@
# Build stage # Build stage
FROM node:22.14.0-bullseye-slim AS builder FROM node:22.14.0-bullseye-slim AS builder
# Configure build dependencies in a single layer WORKDIR /usr/src/app/build
RUN apt-get update && apt-get install -y --no-install-recommends \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
# Copy only necessary files for build # Copy only necessary files for build
COPY . . COPY . .
COPY server-package.json package.json
# Build and cleanup in a single layer # Build and cleanup in a single layer
RUN cp -R build/src/* src/. && \ RUN npm ci && \
cp build/docker_healthcheck.js . && \ npm run build:prepare-dist && \
rm docker_healthcheck.ts && \
npm install && \
npm run build:webpack && \
npm prune --omit=dev && \
npm cache clean --force && \ npm cache clean --force && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \ rm -rf dist/node_modules && \
rm -rf src/public/app/* && \ mv dist/* \
mkdir -p src/public/app/services && \ start-docker.sh \
cp -r build/src/public/app/services/mime_type_definitions.js src/public/app/services/mime_type_definitions.js && \ /usr/src/app/ && \
rm src/services/asset_path.ts && \ rm -rf \
rm -r build /usr/src/app/build \
/tmp/node-compile-cache
#TODO: improve node_modules handling in copy-dist/Dockerfile -> remove duplicated work
# currently copy-dist will copy certain node_module folders, but in the Dockerfile we delete them again (to keep image size down),
# as we install necessary dependencies in runtime buildstage anyways
# Runtime stage # Runtime stage
FROM node:22.14.0-bullseye-slim FROM node:22.14.0-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gosu \
&& rm -rf /var/lib/apt/lists/* && \
rm -rf /var/cache/apt/*
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Copy only necessary files from builder # Install only runtime dependencies
COPY --from=builder /usr/src/app/node_modules ./node_modules RUN apt-get update && \
COPY --from=builder /usr/src/app/src ./src apt-get install -y --no-install-recommends \
COPY --from=builder /usr/src/app/db ./db gosu && \
COPY --from=builder /usr/src/app/docker_healthcheck.js . rm -rf \
COPY --from=builder /usr/src/app/start-docker.sh . /var/lib/apt/lists/* \
COPY --from=builder /usr/src/app/package.json . /var/cache/apt/*
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images COPY --from=builder /usr/src/app ./
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries RUN sed -i "/electron/d" package.json && \
npm ci --omit=dev && \
npm cache clean --force && \
rm -rf /tmp/node-compile-cache
# Configure container # Configure container
EXPOSE 8080 EXPOSE 8080

View File

@ -1,38 +1,26 @@
# Build stage # Build stage
FROM node:22.14.0-alpine AS builder FROM node:22.14.0-alpine AS builder
# Configure build dependencies WORKDIR /usr/src/app/build
RUN apk add --no-cache --virtual .build-dependencies \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3
WORKDIR /usr/src/app
# Copy only necessary files for build # Copy only necessary files for build
COPY . . COPY . .
COPY server-package.json package.json
# Build and cleanup in a single layer # Build and cleanup in a single layer
RUN cp -R build/src/* src/. && \ RUN npm ci && \
cp build/docker_healthcheck.js . && \ npm run build:prepare-dist && \
rm docker_healthcheck.ts && \
npm install && \
npm run build:webpack && \
npm prune --omit=dev && \
npm cache clean --force && \ npm cache clean --force && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \ rm -rf dist/node_modules && \
rm -rf src/public/app && \ mv dist/* \
mkdir -p src/public/app/services && \ start-docker.sh \
cp -r build/src/public/app/services/mime_type_definitions.js src/public/app/services/mime_type_definitions.js && \ /usr/src/app/ && \
rm src/services/asset_path.ts && \ rm -rf \
rm -r build /usr/src/app/build \
/tmp/node-compile-cache
#TODO: improve node_modules handling in copy-dist/Dockerfile -> remove duplicated work
# currently copy-dist will copy certain node_module folders, but in the Dockerfile we delete them again (to keep image size down),
# as we install necessary dependencies in runtime buildstage anyways
# Runtime stage # Runtime stage
FROM node:22.14.0-alpine FROM node:22.14.0-alpine
@ -42,17 +30,12 @@ RUN apk add --no-cache su-exec shadow
WORKDIR /usr/src/app WORKDIR /usr/src/app
# Copy only necessary files from builder COPY --from=builder /usr/src/app ./
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src RUN sed -i "/electron/d" package.json && \
COPY --from=builder /usr/src/app/db ./db npm ci --omit=dev && \
COPY --from=builder /usr/src/app/docker_healthcheck.js . npm cache clean --force && \
COPY --from=builder /usr/src/app/start-docker.sh . rm -rf /tmp/node-compile-cache
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 # Add application user
RUN adduser -s /bin/false node; exit 0 RUN adduser -s /bin/false node; exit 0

View File

@ -5,11 +5,6 @@ set -e # Fail on any command error
VERSION=`jq -r ".version" package.json` VERSION=`jq -r ".version" package.json`
SERIES=${VERSION:0:4}-latest SERIES=${VERSION:0:4}-latest
cat package.json | grep -v electron > server-package.json
echo "Compiling typescript..."
npx tsc
sudo docker build -t triliumnext/notes:$VERSION --network host -t triliumnext/notes:$SERIES . sudo docker build -t triliumnext/notes:$VERSION --network host -t triliumnext/notes:$SERIES .
if [[ $VERSION != *"beta"* ]]; then if [[ $VERSION != *"beta"* ]]; then

View File

@ -66,8 +66,6 @@ chmod 755 $PKG_DIR/trilium.sh
cp bin/tpl/anonymize-database.sql $PKG_DIR/ cp bin/tpl/anonymize-database.sql $PKG_DIR/
cp -r translations $PKG_DIR/ cp -r translations $PKG_DIR/
cp -r dump-db $PKG_DIR/
rm -rf $PKG_DIR/dump-db/node_modules
VERSION=`jq -r ".version" package.json` VERSION=`jq -r ".version" package.json`

View File

@ -2,8 +2,6 @@ import fs from "fs-extra";
import path from "path"; import path from "path";
const DEST_DIR = "./dist"; const DEST_DIR = "./dist";
const DEST_DIR_SRC = path.join(DEST_DIR, "src");
const DEST_DIR_NODE_MODULES = path.join(DEST_DIR, "node_modules");
const VERBOSE = process.env.VERBOSE; const VERBOSE = process.env.VERBOSE;
@ -13,43 +11,37 @@ function log(...args: any[]) {
} }
} }
async function copyNodeModuleFileOrFolder(source: string) { function copyNodeModuleFileOrFolder(source: string) {
const adjustedSource = source.substring(13); const destination = path.join(DEST_DIR, source);
const destination = path.join(DEST_DIR_NODE_MODULES, adjustedSource);
log(`Copying ${source} to ${destination}`); log(`Copying ${source} to ${destination}`);
await fs.ensureDir(path.dirname(destination)); fs.ensureDirSync(path.dirname(destination));
await fs.copy(source, destination); fs.copySync(source, destination);
} }
const copy = async () => { try {
for (const srcFile of fs.readdirSync("build")) {
const destFile = path.join(DEST_DIR, path.basename(srcFile));
log(`Copying source ${srcFile} -> ${destFile}.`);
fs.copySync(path.join("build", srcFile), destFile, { recursive: true });
}
const filesToCopy = [ const assetsToCopy = new Set([
"config-sample.ini", "./images",
"tsconfig.webpack.json", "./libraries",
"./translations",
"./db",
"./config-sample.ini",
"./package-lock.json",
"./package.json",
"./src/views/",
"./src/etapi/etapi.openapi.yaml", "./src/etapi/etapi.openapi.yaml",
"./src/routes/api/openapi.json" "./src/routes/api/openapi.json",
]; "./src/public/icon.png",
for (const file of filesToCopy) { "./src/public/manifest.webmanifest",
log(`Copying ${file}`); "./src/public/robots.txt",
await fs.copy(file, path.join(DEST_DIR, file)); "./src/public/fonts",
} "./src/public/stylesheets",
"./src/public/translations"
]);
const dirsToCopy = ["images", "libraries", "translations", "db"]; for (const asset of assetsToCopy) {
for (const dir of dirsToCopy) { log(`Copying ${asset}`);
log(`Copying ${dir}`); fs.copySync(asset, path.join(DEST_DIR, asset));
await fs.copy(dir, path.join(DEST_DIR, dir));
}
const srcDirsToCopy = ["./src/public", "./src/views", "./build"];
for (const dir of srcDirsToCopy) {
log(`Copying ${dir}`);
await fs.copy(dir, path.join(DEST_DIR_SRC, path.basename(dir)));
} }
/** /**
@ -58,10 +50,10 @@ const copy = async () => {
const publicDirsToCopy = ["./src/public/app/doc_notes"]; const publicDirsToCopy = ["./src/public/app/doc_notes"];
const PUBLIC_DIR = path.join(DEST_DIR, "src", "public", "app-dist"); const PUBLIC_DIR = path.join(DEST_DIR, "src", "public", "app-dist");
for (const dir of publicDirsToCopy) { for (const dir of publicDirsToCopy) {
await fs.copy(dir, path.join(PUBLIC_DIR, path.basename(dir))); fs.copySync(dir, path.join(PUBLIC_DIR, path.basename(dir)));
} }
const nodeModulesFile = [ const nodeModulesFile = new Set([
"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",
"node_modules/react-dom/umd/react-dom.production.min.js", "node_modules/react-dom/umd/react-dom.production.min.js",
@ -71,13 +63,9 @@ const copy = async () => {
"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", "node_modules/@highlightjs/cdn-assets/highlight.min.js",
"node_modules/@mind-elixir/node-menu/dist/node-menu.umd.cjs" "node_modules/@mind-elixir/node-menu/dist/node-menu.umd.cjs"
]; ]);
for (const file of nodeModulesFile) { const nodeModulesFolder = new Set([
await copyNodeModuleFileOrFolder(file);
}
const nodeModulesFolder = [
"node_modules/@excalidraw/excalidraw/dist/", "node_modules/@excalidraw/excalidraw/dist/",
"node_modules/katex/dist/", "node_modules/katex/dist/",
"node_modules/dayjs/", "node_modules/dayjs/",
@ -104,13 +92,15 @@ const copy = async () => {
"node_modules/@highlightjs/cdn-assets/languages", "node_modules/@highlightjs/cdn-assets/languages",
"node_modules/@highlightjs/cdn-assets/styles", "node_modules/@highlightjs/cdn-assets/styles",
"node_modules/leaflet/dist" "node_modules/leaflet/dist"
]; ]);
for (const folder of nodeModulesFolder) {
await copyNodeModuleFileOrFolder(folder);
for (const nodeModuleItem of [...nodeModulesFile, ...nodeModulesFolder]) {
copyNodeModuleFileOrFolder(nodeModuleItem);
} }
}; console.log("Copying complete!")
copy() } catch(err) {
.then(() => console.log("Copying complete!")) console.error("Error during copy:", err)
.catch((err) => console.error("Error during copy:", err)); }

View File

@ -14,7 +14,7 @@ fi
# Trigger the TypeScript build # Trigger the TypeScript build
echo TypeScript build start echo TypeScript build start
npx tsc npm run build:ts
echo TypeScript build finished echo TypeScript build finished
# Copy the TypeScript artifacts # Copy the TypeScript artifacts

View File

@ -1,6 +1,6 @@
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
import { dirname, join } from "path"; import { dirname, join } from "path";
import swaggerJsdoc from 'swagger-jsdoc'; import swaggerJsdoc from "swagger-jsdoc";
import fs from "fs"; import fs from "fs";
/* /*
@ -11,28 +11,30 @@ import fs from "fs";
*/ */
const options = { const options = {
definition: { definition: {
openapi: '3.1.1', openapi: "3.1.1",
info: { info: {
title: 'Trilium Notes - Sync server API', title: "Trilium Notes - Sync server API",
version: '0.96.6', version: "0.96.6",
description: "This is the internal sync server API used by Trilium Notes / TriliumNext Notes.\n\n_If you're looking for the officially supported External Trilium API, see [here](https://triliumnext.github.io/Docs/Wiki/etapi.html)._\n\nThis page does not yet list all routes. For a full list, see the [route controller](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/routes.ts).", description:
contact: { "This is the internal sync server API used by Trilium Notes / TriliumNext Notes.\n\n_If you're looking for the officially supported External Trilium API, see [here](https://triliumnext.github.io/Docs/Wiki/etapi.html)._\n\nThis page does not yet list all routes. For a full list, see the [route controller](https://github.com/TriliumNext/Notes/blob/v0.91.6/src/routes/routes.ts).",
name: "TriliumNext issue tracker", contact: {
url: "https://github.com/TriliumNext/Notes/issues", name: "TriliumNext issue tracker",
}, url: "https://github.com/TriliumNext/Notes/issues"
license: { },
name: "GNU Free Documentation License 1.3 (or later)", license: {
url: "https://www.gnu.org/licenses/fdl-1.3", name: "GNU Free Documentation License 1.3 (or later)",
}, url: "https://www.gnu.org/licenses/fdl-1.3"
}
}
}, },
}, apis: [
apis: [ // Put individual files here to have them ordered first.
// Put individual files here to have them ordered first. "./src/routes/api/setup.ts",
'./src/routes/api/setup.ts', // all other files
// all other files "./src/routes/api/*.ts",
'./src/routes/api/*.ts', './bin/generate-openapi.js' "./bin/generate-openapi.js"
], ]
}; };
const openapiSpecification = swaggerJsdoc(options); const openapiSpecification = swaggerJsdoc(options);

View File

@ -1,5 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e
if [[ $# -eq 0 ]] ; then if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version" echo "Missing argument of new version"
exit 1 exit 1

View File

@ -1,10 +0,0 @@
CREATE TABLE IF NOT EXISTS "tasks"
(
"taskId" TEXT NOT NULL PRIMARY KEY,
"parentNoteId" TEXT NOT NULL,
"title" TEXT NOT NULL DEFAULT "",
"dueDate" INTEGER,
"isDone" INTEGER NOT NULL DEFAULT 0,
"isDeleted" INTEGER NOT NULL DEFAULT 0,
"utcDateModified" TEXT NOT NULL
);

View File

@ -132,14 +132,3 @@ CREATE INDEX IDX_attachments_ownerId_role
CREATE INDEX IDX_notes_blobId on notes (blobId); CREATE INDEX IDX_notes_blobId on notes (blobId);
CREATE INDEX IDX_revisions_blobId on revisions (blobId); CREATE INDEX IDX_revisions_blobId on revisions (blobId);
CREATE INDEX IDX_attachments_blobId on attachments (blobId); CREATE INDEX IDX_attachments_blobId on attachments (blobId);
CREATE TABLE IF NOT EXISTS "tasks"
(
"taskId" TEXT NOT NULL PRIMARY KEY,
"parentNoteId" TEXT NOT NULL,
"title" TEXT NOT NULL DEFAULT "",
"dueDate" INTEGER,
"isDone" INTEGER NOT NULL DEFAULT 0,
"isDeleted" INTEGER NOT NULL DEFAULT 0,
"utcDateModified" TEXT NOT NULL
);

View File

@ -14,7 +14,7 @@ npm install
## Running ## Running
See output of `npx esrun dump.ts --help`: See output of `npx tsx dump.ts --help`:
``` ```
dump-db.ts <path_to_document> <target_directory> dump-db.ts <path_to_document> <target_directory>

File diff suppressed because it is too large Load Diff

View File

@ -18,9 +18,9 @@
"homepage": "https://github.com/TriliumNext/Notes/blob/master/dump-db/README.md", "homepage": "https://github.com/TriliumNext/Notes/blob/master/dump-db/README.md",
"dependencies": { "dependencies": {
"better-sqlite3": "^11.1.2", "better-sqlite3": "^11.1.2",
"esrun": "^3.2.26",
"mime-types": "^2.1.34", "mime-types": "^2.1.34",
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"tsx": "^4.19.3",
"yargs": "^17.3.1" "yargs": "^17.3.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -11,16 +11,14 @@ test("Displays translation on desktop", async ({ page, context }) => {
const app = new App(page, context); const app = new App(page, context);
await app.goto(); await app.goto();
await expect(page.locator("#left-pane .quick-search input")) await expect(page.locator("#left-pane .quick-search input")).toHaveAttribute("placeholder", "Quick search");
.toHaveAttribute("placeholder", "Quick search");
}); });
test("Displays translation on mobile", async ({ page, context }) => { test("Displays translation on mobile", async ({ page, context }) => {
const app = new App(page, context); const app = new App(page, context);
await app.goto({ isMobile: true }); await app.goto({ isMobile: true });
await expect(page.locator("#mobile-sidebar-wrapper .quick-search input")) await expect(page.locator("#mobile-sidebar-wrapper .quick-search input")).toHaveAttribute("placeholder", "Quick search");
.toHaveAttribute("placeholder", "Quick search");
}); });
test("Displays translations in Settings", async ({ page, context }) => { test("Displays translations in Settings", async ({ page, context }) => {
@ -44,14 +42,16 @@ test("User can change language from settings", async ({ page, context }) => {
// Check that the default value (English) is set. // Check that the default value (English) is set.
await expect(app.currentNoteSplit).toContainText("Theme"); await expect(app.currentNoteSplit).toContainText("Theme");
const languageCombobox = await app.currentNoteSplit.getByRole("combobox").first(); const languageCombobox = app.currentNoteSplit.getByRole("combobox").first();
await expect(languageCombobox).toHaveValue("en"); await expect(languageCombobox).toHaveValue("en");
// Select Chinese and ensure the translation is set. // Select Chinese and ensure the translation is set.
await languageCombobox.selectOption("cn"); await languageCombobox.selectOption("cn");
await expect(app.currentNoteSplit).toContainText("主题", { timeout: 15000 }); await expect(app.currentNoteSplit).toContainText("主题", { timeout: 15000 });
await expect(languageCombobox).toHaveValue("cn");
// Select English again. // Select English again.
await languageCombobox.selectOption("en"); await languageCombobox.selectOption("en");
await expect(app.currentNoteSplit).toContainText("Language", { timeout: 15000 }); await expect(app.currentNoteSplit).toContainText("Language", { timeout: 15000 });
await expect(languageCombobox).toHaveValue("en");
}); });

View File

@ -19,13 +19,13 @@ test("Can drag tabs around", async ({ page, context }) => {
let tab = app.getTab(0); let tab = app.getTab(0);
// Drag the first tab at the end // Drag the first tab at the end
await tab.dragTo(app.getTab(2), { targetPosition: { x: 50, y: 0 }}); await tab.dragTo(app.getTab(2), { targetPosition: { x: 50, y: 0 } });
tab = app.getTab(2); tab = app.getTab(2);
await expect(tab).toContainText(NOTE_TITLE); await expect(tab).toContainText(NOTE_TITLE);
// Drag the tab to the left // Drag the tab to the left
await tab.dragTo(app.getTab(0), { targetPosition: { x: 50, y: 0 }}); await tab.dragTo(app.getTab(0), { targetPosition: { x: 50, y: 0 } });
await expect(app.getTab(0)).toContainText(NOTE_TITLE); await expect(app.getTab(0)).toContainText(NOTE_TITLE);
}); });

View File

@ -39,7 +39,9 @@ test("Displays lint errors for backend script", async ({ page, context }) => {
}); });
async function expectTooltip(page: Page, tooltip: string) { async function expectTooltip(page: Page, tooltip: string) {
await expect(page.locator(".CodeMirror-lint-tooltip:visible", { await expect(
"hasText": tooltip page.locator(".CodeMirror-lint-tooltip:visible", {
})).toBeVisible(); hasText: tooltip
})
).toBeVisible();
} }

View File

@ -3,7 +3,8 @@ import App from "../support/app";
test("renders ELK flowchart", async ({ page, context }) => { test("renders ELK flowchart", async ({ page, context }) => {
await testAriaSnapshot({ await testAriaSnapshot({
page, context, page,
context,
noteTitle: "Flowchart ELK on", noteTitle: "Flowchart ELK on",
snapshot: ` snapshot: `
- document: - document:
@ -22,12 +23,13 @@ test("renders ELK flowchart", async ({ page, context }) => {
- paragraph: Guarantee - paragraph: Guarantee
- text: Interfaces for B - text: Interfaces for B
` `
}) });
}); });
test("renders standard flowchart", async ({ page, context }) => { test("renders standard flowchart", async ({ page, context }) => {
await testAriaSnapshot({ await testAriaSnapshot({
page, context, page,
context,
noteTitle: "Flowchart ELK off", noteTitle: "Flowchart ELK off",
snapshot: ` snapshot: `
- document: - document:
@ -46,7 +48,7 @@ test("renders standard flowchart", async ({ page, context }) => {
- paragraph: C - paragraph: C
- text: Interfaces for B - text: Interfaces for B
` `
}) });
}); });
interface AriaTestOpts { interface AriaTestOpts {

View File

@ -44,8 +44,8 @@ test("Highlights list is displayed", async ({ page, context }) => {
await expect(app.sidebar).toContainText("Highlights List"); await expect(app.sidebar).toContainText("Highlights List");
const rootList = app.sidebar.locator(".highlights-list ol"); const rootList = app.sidebar.locator(".highlights-list ol");
let index=0; let index = 0;
for (const highlightedEl of [ "Bold 1", "Italic 1", "Underline 1", "Colored text 1", "Background text 1", "Bold 2", "Italic 2", "Underline 2", "Colored text 2", "Background text 2" ]) { for (const highlightedEl of ["Bold 1", "Italic 1", "Underline 1", "Colored text 1", "Background text 1", "Bold 2", "Italic 2", "Underline 2", "Colored text 2", "Background text 2"]) {
await expect(rootList.locator("li").nth(index++)).toContainText(highlightedEl); await expect(rootList.locator("li").nth(index++)).toContainText(highlightedEl);
} }
}); });
@ -54,7 +54,7 @@ test("Displays math popup", async ({ page, context }) => {
const app = new App(page, context); const app = new App(page, context);
await app.goto(); await app.goto();
await app.goToNoteInNewTab("Empty text"); await app.goToNoteInNewTab("Empty text");
const noteContent = app.currentNoteSplit.locator(".note-detail-editable-text-editor") const noteContent = app.currentNoteSplit.locator(".note-detail-editable-text-editor");
await noteContent.fill("Hello world"); await noteContent.fill("Hello world");
await noteContent.press("ControlOrMeta+M"); await noteContent.press("ControlOrMeta+M");

View File

@ -25,7 +25,7 @@ export default class App {
this.tabBar = page.locator(".tab-row-widget-container"); this.tabBar = page.locator(".tab-row-widget-container");
this.noteTree = page.locator(".tree-wrapper"); this.noteTree = page.locator(".tree-wrapper");
this.launcherBar = page.locator("#launcher-container"); this.launcherBar = page.locator("#launcher-container");
this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)") this.currentNoteSplit = page.locator(".note-split:not(.hidden-ext)");
this.sidebar = page.locator("#right-pane"); this.sidebar = page.locator("#right-pane");
} }
@ -42,12 +42,11 @@ export default class App {
url = "/"; url = "/";
} }
await this.page.goto(url, { waitUntil: "networkidle" }); await this.page.goto(url, { waitUntil: "networkidle", timeout: 30_000 });
// Wait for the page to load. // Wait for the page to load.
if (url === "/") { if (url === "/") {
await expect(this.page.locator(".tree")) await expect(this.page.locator(".tree")).toContainText("Trilium Integration Test");
.toContainText("Trilium Integration Test");
await this.closeAllTabs(); await this.closeAllTabs();
} }
} }
@ -109,11 +108,12 @@ export default class App {
}); });
expect(csrfToken).toBeTruthy(); expect(csrfToken).toBeTruthy();
await expect(await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, { await expect(
headers: { await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, {
"x-csrf-token": csrfToken headers: {
} "x-csrf-token": csrfToken
})).toBeOK(); }
})
).toBeOK();
} }
} }

43
eslint.config.js Normal file
View File

@ -0,0 +1,43 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
// consider using rules below, once we have a full TS codebase and can be more strict
// tseslint.configs.strictTypeChecked,
// tseslint.configs.stylisticTypeChecked,
// tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
}
},
{
rules: {
// add rule overrides here
"no-undef": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
}
]
}
},
{
ignores: [
"build/*",
"dist/*",
"docs/*",
"libraries/*",
"src/public/app-dist/*",
"src/public/app/doc_notes/*"
]
}
);

Binary file not shown.

View File

@ -35,39 +35,13 @@
return []; return [];
} }
await glob.requireLibrary(glob.ESLINT);
if (text.length > 20000) { if (text.length > 20000) {
console.log("Skipping linting because of large size: ", text.length); console.log("Skipping linting because of large size: ", text.length);
return []; return [];
} }
const errors = new eslint().verify(text, { const errors = await glob.linter(text);
root: true,
parserOptions: {
ecmaVersion: "2019"
},
extends: ['eslint:recommended', 'airbnb-base'],
env: {
'browser': true,
'node': true
},
rules: {
'import/no-unresolved': 'off',
'func-names': 'off',
'comma-dangle': ['warn'],
'padded-blocks': 'off',
'linebreak-style': 'off',
'class-methods-use-this': 'off',
'no-unused-vars': ['warn', { vars: 'local', args: 'after-used' }],
'no-nested-ternary': 'off',
'no-underscore-dangle': ['error', {'allow': ['_super', '_lookupFactory']}]
},
globals: {
"api": "readonly"
}
});
console.log(errors); console.log(errors);

112883
libraries/eslint/eslint.js vendored

File diff suppressed because one or more lines are too long

1107
package-lock.json generated

File diff suppressed because it is too large Load Diff

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.92.2-beta", "version": "0.92.3-beta",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"main": "./dist/electron-main.js", "main": "./dist/electron-main.js",
"author": { "author": {
@ -43,20 +43,24 @@
"docs:build-backend": "rimraf ./docs/backend_api && typedoc ./docs/backend_api src/becca/entities/*.ts src/services/backend_script_api.ts src/services/sql.ts", "docs:build-backend": "rimraf ./docs/backend_api && typedoc ./docs/backend_api src/becca/entities/*.ts src/services/backend_script_api.ts src/services/sql.ts",
"docs:build-frontend": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js", "docs:build-frontend": "rimraf ./docs/frontend_api && jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
"docs:build": "npm run docs:build-backend && npm run docs:build-frontend", "docs:build": "npm run docs:build-backend && npm run docs:build-frontend",
"build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts", "build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts --progress",
"build:prepare-dist": "npm run build:webpack && rimraf ./dist && tsc && tsx ./bin/copy-dist.ts", "build:ts": "tsc -p tsconfig.build.json",
"build:clean": "rimraf ./dist ./build",
"build:prepare-dist": "npm run build:clean && npm run build:ts && npm run build:webpack && tsx ./bin/copy-dist.ts",
"test": "npm run client:test && npm run server:test", "test": "npm run client:test && npm run server:test",
"server:test": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest", "server:test": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest",
"server:coverage": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --coverage", "server:coverage": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --coverage",
"client:test": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app", "client:test": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app",
"client:coverage": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app --coverage", "client:coverage": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app --coverage",
"test:playwright": "playwright test", "test:playwright": "playwright test --workers 1",
"test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"test:integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "test:integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"test: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", "test: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",
"dev:watch-dist": "tsx ./bin/watch-dist.ts", "dev:watch-dist": "tsx ./bin/watch-dist.ts",
"dev:prettier-check": "prettier . --check", "dev:prettier-check": "prettier . --check",
"dev:prettier-fix": "prettier . --write", "dev:prettier-fix": "prettier . --write",
"dev:linter-check": "eslint .",
"dev:linter-fix": "eslint . --fix",
"chore:update-build-info": "tsx bin/update-build-info.ts", "chore:update-build-info": "tsx bin/update-build-info.ts",
"chore:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts", "chore:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts",
"chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000", "chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
@ -77,7 +81,7 @@
"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",
"axios": "1.8.1", "axios": "1.8.2",
"better-sqlite3": "11.8.1", "better-sqlite3": "11.8.1",
"boxicons": "2.1.4", "boxicons": "2.1.4",
"chardet": "2.1.0", "chardet": "2.1.0",
@ -98,12 +102,12 @@
"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-linter-browserify": "9.22.0",
"express": "4.21.2", "express": "4.21.2",
"express-rate-limit": "7.5.0", "express-rate-limit": "7.5.0",
"express-session": "1.18.1", "express-session": "1.18.1",
"force-graph": "1.49.2", "force-graph": "1.49.3",
"fs-extra": "11.3.0", "fs-extra": "11.3.0",
"happy-dom": "17.1.8",
"helmet": "8.0.0", "helmet": "8.0.0",
"html": "1.0.0", "html": "1.0.0",
"html2plaintext": "2.1.4", "html2plaintext": "2.1.4",
@ -123,7 +127,6 @@
"jsdom": "26.0.0", "jsdom": "26.0.0",
"jsplumb": "2.15.6", "jsplumb": "2.15.6",
"katex": "0.16.21", "katex": "0.16.21",
"knockout": "3.5.1",
"leaflet": "1.9.4", "leaflet": "1.9.4",
"leaflet-gpx": "2.1.2", "leaflet-gpx": "2.1.2",
"mark.js": "8.11.1", "mark.js": "8.11.1",
@ -150,7 +153,6 @@
"striptags": "3.2.0", "striptags": "3.2.0",
"swagger-ui-express": "5.0.1", "swagger-ui-express": "5.0.1",
"tmp": "0.2.3", "tmp": "0.2.3",
"ts-loader": "9.5.2",
"turndown": "7.2.0", "turndown": "7.2.0",
"unescape": "1.0.1", "unescape": "1.0.1",
"vanilla-js-wheel-zoom": "9.0.4", "vanilla-js-wheel-zoom": "9.0.4",
@ -168,7 +170,8 @@
"@electron-forge/maker-zip": "7.7.0", "@electron-forge/maker-zip": "7.7.0",
"@electron-forge/plugin-auto-unpack-natives": "7.7.0", "@electron-forge/plugin-auto-unpack-natives": "7.7.0",
"@electron/rebuild": "3.7.1", "@electron/rebuild": "3.7.1",
"@playwright/test": "1.50.1", "@eslint/js": "9.22.0",
"@playwright/test": "1.51.0",
"@popperjs/core": "2.11.8", "@popperjs/core": "2.11.8",
"@types/archiver": "6.0.3", "@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12", "@types/better-sqlite3": "7.6.12",
@ -193,7 +196,7 @@
"@types/leaflet-gpx": "1.3.7", "@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/multer": "1.4.12", "@types/multer": "1.4.12",
"@types/node": "22.13.8", "@types/node": "22.13.9",
"@types/react": "18.3.18", "@types/react": "18.3.18",
"@types/react-dom": "18.3.5", "@types/react-dom": "18.3.5",
"@types/safe-compare": "1.1.2", "@types/safe-compare": "1.1.2",
@ -207,23 +210,27 @@
"@types/swagger-ui-express": "4.1.8", "@types/swagger-ui-express": "4.1.8",
"@types/tmp": "0.2.6", "@types/tmp": "0.2.6",
"@types/turndown": "5.0.5", "@types/turndown": "5.0.5",
"@types/ws": "8.5.14", "@types/ws": "8.18.0",
"@types/xml2js": "0.4.14", "@types/xml2js": "0.4.14",
"@types/yargs": "17.0.33", "@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.0.7", "@vitest/coverage-v8": "3.0.8",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.20",
"bootstrap": "5.3.3", "bootstrap": "5.3.3",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"css-loader": "7.1.2", "css-loader": "7.1.2",
"electron": "34.3.0", "electron": "34.3.1",
"eslint": "9.22.0",
"esm": "3.2.25", "esm": "3.2.25",
"globals": "16.0.0",
"happy-dom": "17.4.0",
"i18next-http-backend": "3.0.2", "i18next-http-backend": "3.0.2",
"jsdoc": "4.0.4", "jsdoc": "4.0.4",
"knockout": "3.5.1",
"lorem-ipsum": "2.0.8", "lorem-ipsum": "2.0.8",
"mini-css-extract-plugin": "2.9.2", "mini-css-extract-plugin": "2.9.2",
"nodemon": "3.1.9", "nodemon": "3.1.9",
"postcss-loader": "8.1.1", "postcss-loader": "8.1.1",
"prettier": "3.5.2", "prettier": "3.5.3",
"rcedit": "4.0.1", "rcedit": "4.0.1",
"rimraf": "6.0.1", "rimraf": "6.0.1",
"sass": "1.85.1", "sass": "1.85.1",
@ -231,11 +238,13 @@
"split.js": "1.6.5", "split.js": "1.6.5",
"supertest": "7.0.0", "supertest": "7.0.0",
"swagger-jsdoc": "6.2.8", "swagger-jsdoc": "6.2.8",
"ts-loader": "9.5.2",
"tslib": "2.8.1", "tslib": "2.8.1",
"tsx": "4.19.3", "tsx": "4.19.3",
"typedoc": "0.27.9", "typedoc": "0.27.9",
"typescript": "5.8.2", "typescript": "5.8.2",
"vitest": "3.0.7", "typescript-eslint": "8.26.0",
"vitest": "3.0.8",
"webpack": "5.98.0", "webpack": "5.98.0",
"webpack-cli": "6.0.1", "webpack-cli": "6.0.1",
"webpack-dev-middleware": "7.4.2" "webpack-dev-middleware": "7.4.2"

View File

@ -1,6 +1,6 @@
import { defineConfig, devices } from '@playwright/test'; import { defineConfig, devices } from "@playwright/test";
const SERVER_URL = 'http://127.0.0.1:8082'; const SERVER_URL = "http://127.0.0.1:8082";
/** /**
* Read environment variables from file. * Read environment variables from file.
@ -14,68 +14,70 @@ const SERVER_URL = 'http://127.0.0.1:8082';
* See https://playwright.dev/docs/test-configuration. * See https://playwright.dev/docs/test-configuration.
*/ */
export default defineConfig({ export default defineConfig({
testDir: './e2e', testDir: "./e2e",
/* Run tests in files in parallel */ /* Run tests in files in parallel */
fullyParallel: true, fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */ /* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
/* Retry on CI only */ /* Retry on CI only */
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html', reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */
baseURL: SERVER_URL, baseURL: SERVER_URL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry', trace: "on-first-retry"
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
}, },
// { /* Configure projects for major browsers */
// name: 'firefox', projects: [
// use: { ...devices['Desktop Firefox'] }, {
// }, name: "chromium",
use: { ...devices["Desktop Chrome"] }
}
// { // {
// name: 'webkit', // name: 'firefox',
// use: { ...devices['Desktop Safari'] }, // use: { ...devices['Desktop Firefox'] },
// }, // },
/* Test against mobile viewports. */ // {
// { // name: 'webkit',
// name: 'Mobile Chrome', // use: { ...devices['Desktop Safari'] },
// use: { ...devices['Pixel 5'] }, // },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */ /* Test against mobile viewports. */
// { // {
// name: 'Microsoft Edge', // name: 'Mobile Chrome',
// use: { ...devices['Desktop Edge'], channel: 'msedge' }, // use: { ...devices['Pixel 5'] },
// }, // },
// { // {
// name: 'Google Chrome', // name: 'Mobile Safari',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' }, // use: { ...devices['iPhone 12'] },
// }, // },
],
/* Run your local dev server before starting the tests */ /* Test against branded browsers. */
webServer: !process.env.TRILIUM_DOCKER ? { // {
command: 'npm run test:integration-mem-db-dev', // name: 'Microsoft Edge',
url: SERVER_URL, // use: { ...devices['Desktop Edge'], channel: 'msedge' },
reuseExistingServer: !process.env.CI, // },
} : undefined, // {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: !process.env.TRILIUM_DOCKER
? {
command: "npm run test:integration-mem-db-dev",
url: SERVER_URL,
reuseExistingServer: !process.env.CI
}
: undefined
}); });

View File

@ -6,4 +6,4 @@ etapi.describeEtapi("app_info", () => {
expect(appInfo.clipperProtocolVersion).toEqual("1.0"); expect(appInfo.clipperProtocolVersion).toEqual("1.0");
}); });
}); });
*/ */

View File

@ -7,4 +7,4 @@ etapi.describeEtapi("backup", () => {
expect(response.status).toEqual(204); expect(response.status).toEqual(204);
}); });
}); });
*/ */

View File

@ -23,4 +23,4 @@ etapi.describeEtapi("import", () => {
expect(content).toContain("test export content"); expect(content).toContain("test export content");
}); });
}); });
*/ */

View File

@ -100,4 +100,4 @@ etapi.describeEtapi("notes", () => {
expect(error.message).toEqual(`Note '${note.noteId}' not found.`); expect(error.message).toEqual(`Note '${note.noteId}' not found.`);
}); });
}); });
*/ */

View File

@ -12,7 +12,6 @@ import type { AttachmentRow, BlobRow, RevisionRow } from "./entities/rows.js";
import BBlob from "./entities/bblob.js"; import BBlob from "./entities/bblob.js";
import BRecentNote from "./entities/brecent_note.js"; import BRecentNote from "./entities/brecent_note.js";
import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js"; import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
import type BTask from "./entities/btask.js";
interface AttachmentOpts { interface AttachmentOpts {
includeContentLength?: boolean; includeContentLength?: boolean;
@ -33,7 +32,6 @@ export default class Becca {
attributeIndex!: Record<string, BAttribute[]>; attributeIndex!: Record<string, BAttribute[]>;
options!: Record<string, BOption>; options!: Record<string, BOption>;
etapiTokens!: Record<string, BEtapiToken>; etapiTokens!: Record<string, BEtapiToken>;
tasks!: Record<string, BTask>;
allNoteSetCache: NoteSet | null; allNoteSetCache: NoteSet | null;
@ -50,7 +48,6 @@ export default class Becca {
this.attributeIndex = {}; this.attributeIndex = {};
this.options = {}; this.options = {};
this.etapiTokens = {}; this.etapiTokens = {};
this.tasks = {};
this.dirtyNoteSetCache(); this.dirtyNoteSetCache();
@ -216,14 +213,6 @@ export default class Becca {
return this.etapiTokens[etapiTokenId]; return this.etapiTokens[etapiTokenId];
} }
getTasks(): BTask[] {
return Object.values(this.tasks);
}
getTask(taskId: string): BTask | null {
return this.tasks[taskId];
}
getEntity<T extends AbstractBeccaEntity<T>>(entityName: string, entityId: string): AbstractBeccaEntity<T> | null { getEntity<T extends AbstractBeccaEntity<T>>(entityName: string, entityId: string): AbstractBeccaEntity<T> | null {
if (!entityName || !entityId) { if (!entityName || !entityId) {
return null; return null;

View File

@ -11,10 +11,9 @@ import BOption from "./entities/boption.js";
import BEtapiToken from "./entities/betapi_token.js"; import BEtapiToken from "./entities/betapi_token.js";
import cls from "../services/cls.js"; import cls from "../services/cls.js";
import entityConstructor from "../becca/entity_constructor.js"; import entityConstructor from "../becca/entity_constructor.js";
import type { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow, TaskRow } from "./entities/rows.js"; import type { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from "./entities/rows.js";
import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js"; import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
import ws from "../services/ws.js"; import ws from "../services/ws.js";
import BTask from "./entities/btask.js";
const beccaLoaded = new Promise<void>(async (res, rej) => { const beccaLoaded = new Promise<void>(async (res, rej) => {
const sqlInit = (await import("../services/sql_init.js")).default; const sqlInit = (await import("../services/sql_init.js")).default;
@ -64,17 +63,6 @@ function load() {
for (const row of sql.getRows<EtapiTokenRow>(`SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified FROM etapi_tokens WHERE isDeleted = 0`)) { for (const row of sql.getRows<EtapiTokenRow>(`SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified FROM etapi_tokens WHERE isDeleted = 0`)) {
new BEtapiToken(row); new BEtapiToken(row);
} }
try {
for (const row of sql.getRows<TaskRow>(`SELECT taskId, parentNoteId, title, dueDate, isDone, isDeleted FROM tasks WHERE isDeleted = 0`)) {
new BTask(row);
}
} catch (e: any) {
// Some older migrations trigger becca which would fail since the "tasks" table is not yet defined (didn't reach the right migration).
if (!(e.message.includes("no such table"))) {
throw e;
}
}
}); });
for (const noteId in becca.notes) { for (const noteId in becca.notes) {

View File

@ -1618,7 +1618,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
* @param matchBy - choose by which property we detect if to update an existing attachment. * @param matchBy - choose by which property we detect if to update an existing attachment.
* Supported values are either 'attachmentId' (default) or 'title' * Supported values are either 'attachmentId' (default) or 'title'
*/ */
saveAttachment({ attachmentId, role, mime, title, content, position }: AttachmentRow, matchBy = "attachmentId") { saveAttachment({ attachmentId, role, mime, title, content, position }: AttachmentRow, matchBy: "attachmentId" | "title" | undefined = "attachmentId") {
if (!["attachmentId", "title"].includes(matchBy)) { if (!["attachmentId", "title"].includes(matchBy)) {
throw new Error(`Unsupported value '${matchBy}' for matchBy param, has to be either 'attachmentId' or 'title'.`); throw new Error(`Unsupported value '${matchBy}' for matchBy param, has to be either 'attachmentId' or 'title'.`);
} }

View File

@ -7,7 +7,7 @@ import becca from "../becca.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js"; import AbstractBeccaEntity from "./abstract_becca_entity.js";
import sql from "../../services/sql.js"; import sql from "../../services/sql.js";
import BAttachment from "./battachment.js"; import BAttachment from "./battachment.js";
import type { AttachmentRow, RevisionRow } from "./rows.js"; import type { AttachmentRow, NoteType, RevisionRow } from "./rows.js";
import eraseService from "../../services/erase.js"; import eraseService from "../../services/erase.js";
interface ContentOpts { interface ContentOpts {
@ -36,7 +36,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
revisionId?: string; revisionId?: string;
noteId!: string; noteId!: string;
type!: string; type!: NoteType;
mime!: string; mime!: string;
title!: string; title!: string;
dateLastEdited?: string; dateLastEdited?: string;

View File

@ -1,84 +0,0 @@
import date_utils from "../../services/date_utils.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js";
import type BOption from "./boption.js";
import type { TaskRow } from "./rows.js";
export default class BTask extends AbstractBeccaEntity<BOption> {
static get entityName() {
return "tasks";
}
static get primaryKeyName() {
return "taskId";
}
static get hashedProperties() {
return [ "taskId", "parentNoteId", "title", "dueDate", "isDone", "isDeleted" ];
}
taskId?: string;
parentNoteId!: string;
title!: string;
dueDate?: string;
isDone!: boolean;
private _isDeleted?: boolean;
constructor(row?: TaskRow) {
super();
if (!row) {
return;
}
this.updateFromRow(row);
this.init();
}
get isDeleted() {
return !!this._isDeleted;
}
updateFromRow(row: TaskRow) {
this.taskId = row.taskId;
this.parentNoteId = row.parentNoteId;
this.title = row.title;
this.dueDate = row.dueDate;
this.isDone = !!row.isDone;
this._isDeleted = !!row.isDeleted;
this.utcDateModified = row.utcDateModified;
if (this.taskId) {
this.becca.tasks[this.taskId] = this;
}
}
init() {
if (this.taskId) {
this.becca.tasks[this.taskId] = this;
}
}
protected beforeSaving(opts?: {}): void {
super.beforeSaving();
this.utcDateModified = date_utils.utcNowDateTime();
if (this.taskId) {
this.becca.tasks[this.taskId] = this;
}
}
getPojo() {
return {
taskId: this.taskId,
parentNoteId: this.parentNoteId,
title: this.title,
dueDate: this.dueDate,
isDone: this.isDone,
isDeleted: this.isDeleted,
utcDateModified: this.utcDateModified
};
}
}

View File

@ -22,7 +22,7 @@ export interface AttachmentRow {
export interface RevisionRow { export interface RevisionRow {
revisionId?: string; revisionId?: string;
noteId: string; noteId: string;
type: string; type: NoteType;
mime: string; mime: string;
isProtected?: boolean; isProtected?: boolean;
title: string; title: string;
@ -139,13 +139,3 @@ export interface NoteRow {
utcDateModified: string; utcDateModified: string;
content?: string | Buffer; content?: string | Buffer;
} }
export interface TaskRow {
taskId?: string;
parentNoteId: string;
title: string;
dueDate?: string;
isDone?: boolean;
isDeleted?: boolean;
utcDateModified?: string;
}

View File

@ -9,7 +9,6 @@ import BNote from "./entities/bnote.js";
import BOption from "./entities/boption.js"; import BOption from "./entities/boption.js";
import BRecentNote from "./entities/brecent_note.js"; import BRecentNote from "./entities/brecent_note.js";
import BRevision from "./entities/brevision.js"; import BRevision from "./entities/brevision.js";
import BTask from "./entities/btask.js";
type EntityClass = new (row?: any) => AbstractBeccaEntity<any>; type EntityClass = new (row?: any) => AbstractBeccaEntity<any>;
@ -22,8 +21,7 @@ const ENTITY_NAME_TO_ENTITY: Record<string, ConstructorData<any> & EntityClass>
notes: BNote, notes: BNote,
options: BOption, options: BOption,
recent_notes: BRecentNote, recent_notes: BRecentNote,
revisions: BRevision, revisions: BRevision
tasks: BTask
}; };
function getEntityFromEntityName(entityName: keyof typeof ENTITY_NAME_TO_ENTITY) { function getEntityFromEntityName(entityName: keyof typeof ENTITY_NAME_TO_ENTITY) {

View File

@ -0,0 +1,12 @@
import HttpError from "./http_error.js";
class ForbiddenError extends HttpError {
constructor(message: string) {
super(message, 403);
this.name = "ForbiddenError";
}
}
export default ForbiddenError;

13
src/errors/http_error.ts Normal file
View File

@ -0,0 +1,13 @@
class HttpError extends Error {
statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.name = "HttpError";
this.statusCode = statusCode;
}
}
export default HttpError;

View File

@ -1,9 +1,12 @@
class NotFoundError { import HttpError from "./http_error.js";
message: string;
class NotFoundError extends HttpError {
constructor(message: string) { constructor(message: string) {
this.message = message; super(message, 404);
this.name = "NotFoundError";
} }
} }
export default NotFoundError; export default NotFoundError;

View File

@ -1,9 +1,12 @@
class ValidationError { import HttpError from "./http_error.js";
message: string;
class ValidationError extends HttpError {
constructor(message: string) { constructor(message: string) {
this.message = message; super(message, 400)
this.name = "ValidationError";
} }
} }
export default ValidationError; export default ValidationError;

View File

@ -24,6 +24,7 @@ import type NoteTreeWidget from "../widgets/note_tree.js";
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
import type { ContextMenuEvent } from "../menus/context_menu.js"; import type { ContextMenuEvent } from "../menus/context_menu.js";
import type TypeWidget from "../widgets/type_widgets/type_widget.js"; import type TypeWidget from "../widgets/type_widgets/type_widget.js";
import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js";
interface Layout { interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget; getRootWidget: (appContext: AppContext) => RootWidget;
@ -62,7 +63,7 @@ export interface NoteCommandData extends CommandData {
} }
export interface ExecuteCommandData<T> extends CommandData { export interface ExecuteCommandData<T> extends CommandData {
resolve: (data: T) => void resolve: (data: T) => void;
} }
/** /**
@ -70,7 +71,7 @@ export interface ExecuteCommandData<T> extends CommandData {
*/ */
export type CommandMappings = { export type CommandMappings = {
"api-log-messages": CommandData; "api-log-messages": CommandData;
focusTree: CommandData, focusTree: CommandData;
focusOnTitle: CommandData; focusOnTitle: CommandData;
focusOnDetail: CommandData; focusOnDetail: CommandData;
focusOnSearchDefinition: Required<CommandData>; focusOnSearchDefinition: Required<CommandData>;
@ -108,7 +109,7 @@ export type CommandMappings = {
showInfoDialog: ConfirmWithMessageOptions; showInfoDialog: ConfirmWithMessageOptions;
showConfirmDialog: ConfirmWithMessageOptions; showConfirmDialog: ConfirmWithMessageOptions;
showRecentChanges: CommandData & { ancestorNoteId: string }; showRecentChanges: CommandData & { ancestorNoteId: string };
showImportDialog: CommandData & { noteId: string; }; showImportDialog: CommandData & { noteId: string };
openNewNoteSplit: NoteCommandData; openNewNoteSplit: NoteCommandData;
openInWindow: NoteCommandData; openInWindow: NoteCommandData;
openNoteInNewTab: CommandData; openNoteInNewTab: CommandData;
@ -131,8 +132,10 @@ export type CommandMappings = {
editNoteTitle: ContextMenuCommandData; editNoteTitle: ContextMenuCommandData;
protectSubtree: ContextMenuCommandData; protectSubtree: ContextMenuCommandData;
unprotectSubtree: ContextMenuCommandData; unprotectSubtree: ContextMenuCommandData;
openBulkActionsDialog: ContextMenuCommandData | { openBulkActionsDialog:
selectedOrActiveNoteIds?: string[] | ContextMenuCommandData
| {
selectedOrActiveNoteIds?: string[];
}; };
editBranchPrefix: ContextMenuCommandData; editBranchPrefix: ContextMenuCommandData;
convertNoteToAttachment: ContextMenuCommandData; convertNoteToAttachment: ContextMenuCommandData;
@ -221,11 +224,11 @@ export type CommandMappings = {
moveTabToNewWindow: CommandData; moveTabToNewWindow: CommandData;
copyTabToNewWindow: CommandData; copyTabToNewWindow: CommandData;
closeActiveTab: CommandData & { closeActiveTab: CommandData & {
$el: JQuery<HTMLElement> $el: JQuery<HTMLElement>;
}, };
setZoomFactorAndSave: { setZoomFactorAndSave: {
zoomFactor: string; zoomFactor: string;
} };
reEvaluateRightPaneVisibility: CommandData; reEvaluateRightPaneVisibility: CommandData;
runActiveNote: CommandData; runActiveNote: CommandData;
@ -234,18 +237,18 @@ export type CommandMappings = {
}; };
scrollToEnd: CommandData; scrollToEnd: CommandData;
closeThisNoteSplit: CommandData; closeThisNoteSplit: CommandData;
moveThisNoteSplit: CommandData & { isMovingLeft: boolean; }; moveThisNoteSplit: CommandData & { isMovingLeft: boolean };
// Geomap // Geomap
deleteFromMap: { noteId: string }, deleteFromMap: { noteId: string };
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent } openGeoLocation: { noteId: string; event: JQuery.MouseDownEvent };
toggleZenMode: CommandData; toggleZenMode: CommandData;
updateAttributeList: CommandData & { attributes: Attribute[] }; updateAttributeList: CommandData & { attributes: Attribute[] };
saveAttributes: CommandData; saveAttributes: CommandData;
reloadAttributes: CommandData; reloadAttributes: CommandData;
refreshNoteList: CommandData & { noteId: string; }; refreshNoteList: CommandData & { noteId: string };
refreshResults: {}; refreshResults: {};
refreshSearchDefinition: {}; refreshSearchDefinition: {};
@ -348,7 +351,7 @@ type EventMappings = {
ntxId: string | null | undefined; // TODO: deduplicate ntxId ntxId: string | null | undefined; // TODO: deduplicate ntxId
}; };
tabReorder: { tabReorder: {
ntxIdsInOrder: string[] ntxIdsInOrder: string[];
}; };
refreshNoteList: { refreshNoteList: {
noteId: string; noteId: string;
@ -359,6 +362,12 @@ type EventMappings = {
relationMapResetPanZoom: { ntxId: string | null | undefined }; relationMapResetPanZoom: { ntxId: string | null | undefined };
relationMapResetZoomIn: { ntxId: string | null | undefined }; relationMapResetZoomIn: { ntxId: string | null | undefined };
relationMapResetZoomOut: { ntxId: string | null | undefined }; relationMapResetZoomOut: { ntxId: string | null | undefined };
activeNoteChangedEvent: {};
showAddLinkDialog: {
textTypeWidget: EditableTextTypeWidget;
text: string;
};
}; };
export type EventListener<T extends EventNames> = { export type EventListener<T extends EventNames> = {
@ -542,10 +551,12 @@ $(window).on("beforeunload", () => {
}); });
$(window).on("hashchange", function () { $(window).on("hashchange", function () {
const { notePath, ntxId, viewScope } = linkService.parseNavigationStateFromUrl(window.location.href); const { notePath, ntxId, viewScope, searchString } = linkService.parseNavigationStateFromUrl(window.location.href);
if (notePath || ntxId) { if (notePath || ntxId) {
appContext.tabManager.switchToNoteContext(ntxId, notePath, viewScope); appContext.tabManager.switchToNoteContext(ntxId, notePath, viewScope);
} else if (searchString) {
appContext.triggerCommand("searchNotes", { searchString });
} }
}); });

View File

@ -18,7 +18,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
children: ChildT[]; children: ChildT[];
initialized: Promise<void> | null; initialized: Promise<void> | null;
parent?: TypedComponent<any>; parent?: TypedComponent<any>;
position!: number; _position!: number;
constructor() { constructor() {
this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`; this.componentId = `${this.sanitizedClassName}-${utils.randomString(8)}`;
@ -31,6 +31,14 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
return this.constructor.name.replace(/[^A-Z0-9]/gi, "_"); return this.constructor.name.replace(/[^A-Z0-9]/gi, "_");
} }
get position() {
return this._position;
}
set position(newPosition: number) {
this._position = newPosition;
}
setParent(parent: TypedComponent<any>) { setParent(parent: TypedComponent<any>) {
this.parent = parent; this.parent = parent;
return this; return this;

View File

@ -369,7 +369,8 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
const { note, viewScope } = this; const { note, viewScope } = this;
let title = viewScope?.viewMode === "default" ? note.title : `${note.title}: ${viewScope?.viewMode}`; const isNormalView = (viewScope?.viewMode === "default" || viewScope?.viewMode === "contextual-help");
let title = (isNormalView ? note.title : `${note.title}: ${viewScope?.viewMode}`);
if (viewScope?.attachmentId) { if (viewScope?.attachmentId) {
// assuming the attachment has been already loaded // assuming the attachment has been already loaded

View File

@ -17,7 +17,7 @@ export default class ShortcutComponent extends Component implements EventListene
} }
bindNoteShortcutHandler(labelOrRow: AttributeRow) { bindNoteShortcutHandler(labelOrRow: AttributeRow) {
const handler = () => appContext.tabManager.getActiveContext().setNote(labelOrRow.noteId); const handler = () => appContext.tabManager.getActiveContext()?.setNote(labelOrRow.noteId);
const namespace = labelOrRow.attributeId; const namespace = labelOrRow.attributeId;
if (labelOrRow.isDeleted) { if (labelOrRow.isDeleted) {

View File

@ -248,7 +248,7 @@ export default class TabManager extends Component {
await noteContext.setEmpty(); await noteContext.setEmpty();
} }
async openEmptyTab(ntxId = null, hoistedNoteId = "root", mainNtxId = null) { async openEmptyTab(ntxId = null, hoistedNoteId = "root", mainNtxId) {
const noteContext = new NoteContext(ntxId, hoistedNoteId, mainNtxId); const noteContext = new NoteContext(ntxId, hoistedNoteId, mainNtxId);
const existingNoteContext = this.children.find((nc) => nc.ntxId === noteContext.ntxId); const existingNoteContext = this.children.find((nc) => nc.ntxId === noteContext.ntxId);

View File

@ -1,6 +1,6 @@
{ {
"formatVersion": 2, "formatVersion": 2,
"appVersion": "0.92.0-beta", "appVersion": "0.92.2-beta",
"files": [ "files": [
{ {
"isClone": false, "isClone": false,
@ -34,7 +34,7 @@
"OkOZllzB3fqN", "OkOZllzB3fqN",
"yoAe4jV2yzbd" "yoAe4jV2yzbd"
], ],
"title": "Features", "title": "New Features",
"notePosition": 40, "notePosition": 40,
"prefix": null, "prefix": null,
"isExpanded": false, "isExpanded": false,
@ -47,53 +47,91 @@
"value": "bx bx-star", "value": "bx bx-star",
"isInheritable": false, "isInheritable": false,
"position": 10 "position": 10
},
{
"type": "label",
"name": "sorted",
"value": "dateCreated",
"isInheritable": false,
"position": 20
},
{
"type": "label",
"name": "sortDirection",
"value": "desc",
"isInheritable": false,
"position": 30
} }
], ],
"format": "html", "format": "html",
"attachments": [], "attachments": [],
"dirFileName": "Features", "dirFileName": "New Features",
"children": [ "children": [
{ {
"isClone": false, "isClone": false,
"noteId": "13D1lOc9sqmZ", "noteId": "3I277VKYxWDH",
"notePath": [ "notePath": [
"OkOZllzB3fqN", "OkOZllzB3fqN",
"yoAe4jV2yzbd", "yoAe4jV2yzbd",
"13D1lOc9sqmZ" "3I277VKYxWDH"
], ],
"title": "Export as PDF", "title": "Right-to-left text notes",
"notePosition": 20, "notePosition": 10,
"prefix": null, "prefix": null,
"isExpanded": false, "isExpanded": false,
"type": "text", "type": "text",
"mime": "text/html", "mime": "text/html",
"attributes": [], "attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bx-align-right",
"isInheritable": false,
"position": 10
}
],
"format": "html", "format": "html",
"dataFileName": "Export as PDF.html", "dataFileName": "Right-to-left text notes.html",
"attachments": [ "attachments": [
{ {
"attachmentId": "xsGM34t8ssKV", "attachmentId": "PSBNAvDyj5Vy",
"title": "image.png", "title": "image.png",
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "Export as PDF_image.png" "dataFileName": "Right-to-left text notes_i.png"
}, },
{ {
"attachmentId": "cvyes4f1Vhmm", "attachmentId": "YXYIJznak915",
"title": "image.png", "title": "image.png",
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "1_Export as PDF_image.png" "dataFileName": "1_Right-to-left text notes_i.png"
}, },
{ {
"attachmentId": "b3v1pLE6TF1Y", "attachmentId": "Do0S17lDl7uu",
"title": "image.png", "title": "image.png",
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "2_Export as PDF_image.png" "dataFileName": "2_Right-to-left text notes_i.png"
},
{
"attachmentId": "D3lyhPvPvocb",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "3_Right-to-left text notes_i.png"
},
{
"attachmentId": "Tu7llk3GgRkA",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "4_Right-to-left text notes_i.png"
} }
] ]
}, },
@ -106,12 +144,20 @@
"B3YLYM4erjnW" "B3YLYM4erjnW"
], ],
"title": "Zen mode", "title": "Zen mode",
"notePosition": 30, "notePosition": 20,
"prefix": null, "prefix": null,
"isExpanded": false, "isExpanded": false,
"type": "text", "type": "text",
"mime": "text/html", "mime": "text/html",
"attributes": [], "attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bxs-yin-yang",
"isInheritable": false,
"position": 10
}
],
"format": "html", "format": "html",
"dataFileName": "Zen mode.html", "dataFileName": "Zen mode.html",
"attachments": [ "attachments": [
@ -180,6 +226,50 @@
"dataFileName": "7_Zen mode_image.png" "dataFileName": "7_Zen mode_image.png"
} }
] ]
},
{
"isClone": false,
"noteId": "13D1lOc9sqmZ",
"notePath": [
"OkOZllzB3fqN",
"yoAe4jV2yzbd",
"13D1lOc9sqmZ"
],
"title": "Export as PDF",
"notePosition": 30,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bxs-file-pdf",
"isInheritable": false,
"position": 30
}
],
"format": "html",
"dataFileName": "Export as PDF.html",
"attachments": [
{
"attachmentId": "xsGM34t8ssKV",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Export as PDF_image.png"
},
{
"attachmentId": "b3v1pLE6TF1Y",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "1_Export as PDF_image.png"
}
]
} }
] ]
}, },
@ -233,8 +323,47 @@
} }
], ],
"format": "html", "format": "html",
"dataFileName": "Text.html", "attachments": [],
"attachments": [] "dirFileName": "Text",
"children": [
{
"isClone": false,
"noteId": "B0lcI9xz1r8K",
"notePath": [
"OkOZllzB3fqN",
"wmegHv51MJMd",
"crJtzsol4olb",
"B0lcI9xz1r8K"
],
"title": "Content language",
"notePosition": 10,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "3I277VKYxWDH",
"isInheritable": false,
"position": 10
}
],
"format": "html",
"dataFileName": "Content language.html",
"attachments": [
{
"attachmentId": "OpIv6CnYCLVa",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Content language_image.png"
}
]
}
]
}, },
{ {
"isClone": false, "isClone": false,
@ -382,7 +511,7 @@
"title": "Book", "title": "Book",
"notePosition": 70, "notePosition": 70,
"prefix": null, "prefix": null,
"isExpanded": true, "isExpanded": false,
"type": "text", "type": "text",
"mime": "text/html", "mime": "text/html",
"attributes": [ "attributes": [
@ -576,6 +705,14 @@
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "18_Calendar View_image.png" "dataFileName": "18_Calendar View_image.png"
},
{
"attachmentId": "JM6AU8N4MIgB",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "19_Calendar View_image.png"
} }
] ]
} }
@ -697,7 +834,7 @@
"wmegHv51MJMd", "wmegHv51MJMd",
"foPEtsL51pD2" "foPEtsL51pD2"
], ],
"title": "Geo Map", "title": "Geo map",
"notePosition": 120, "notePosition": 120,
"prefix": null, "prefix": null,
"isExpanded": false, "isExpanded": false,
@ -713,23 +850,15 @@
} }
], ],
"format": "html", "format": "html",
"dataFileName": "Geo Map.html", "dataFileName": "Geo map.html",
"attachments": [ "attachments": [
{
"attachmentId": "J0baLTpafs7C",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Geo Map_image.png"
},
{ {
"attachmentId": "kcYjOvJDFkbS", "attachmentId": "kcYjOvJDFkbS",
"title": "image.png", "title": "image.png",
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "1_Geo Map_image.png" "dataFileName": "Geo map_image.png"
}, },
{ {
"attachmentId": "FDP3JzIVSnuJ", "attachmentId": "FDP3JzIVSnuJ",
@ -737,7 +866,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "2_Geo Map_image.png" "dataFileName": "1_Geo map_image.png"
}, },
{ {
"attachmentId": "eUrcqc8RRuZG", "attachmentId": "eUrcqc8RRuZG",
@ -745,7 +874,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "3_Geo Map_image.png" "dataFileName": "2_Geo map_image.png"
}, },
{ {
"attachmentId": "1quk4yxJpeHZ", "attachmentId": "1quk4yxJpeHZ",
@ -753,7 +882,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "4_Geo Map_image.png" "dataFileName": "3_Geo map_image.png"
}, },
{ {
"attachmentId": "iSpyhQ5Ya6Nk", "attachmentId": "iSpyhQ5Ya6Nk",
@ -761,7 +890,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "5_Geo Map_image.png" "dataFileName": "4_Geo map_image.png"
}, },
{ {
"attachmentId": "ut6vm2aXVfXI", "attachmentId": "ut6vm2aXVfXI",
@ -769,7 +898,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "6_Geo Map_image.png" "dataFileName": "5_Geo map_image.png"
}, },
{ {
"attachmentId": "uYdb9wWf5Nuv", "attachmentId": "uYdb9wWf5Nuv",
@ -777,15 +906,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "7_Geo Map_image.png" "dataFileName": "6_Geo map_image.png"
},
{
"attachmentId": "GhHYO2LteDmZ",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "8_Geo Map_image.png"
}, },
{ {
"attachmentId": "viN50n5G4kB0", "attachmentId": "viN50n5G4kB0",
@ -793,7 +914,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "9_Geo Map_image.png" "dataFileName": "7_Geo map_image.png"
}, },
{ {
"attachmentId": "mgwGrtQZjxxb", "attachmentId": "mgwGrtQZjxxb",
@ -801,7 +922,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "10_Geo Map_image.png" "dataFileName": "8_Geo map_image.png"
}, },
{ {
"attachmentId": "PMqmCbNLlZOG", "attachmentId": "PMqmCbNLlZOG",
@ -809,7 +930,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "11_Geo Map_image.png" "dataFileName": "9_Geo map_image.png"
}, },
{ {
"attachmentId": "0AwaQMqt3FVA", "attachmentId": "0AwaQMqt3FVA",
@ -817,7 +938,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "12_Geo Map_image.png" "dataFileName": "10_Geo map_image.png"
}, },
{ {
"attachmentId": "gR2c2Thmfy3I", "attachmentId": "gR2c2Thmfy3I",
@ -825,7 +946,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "13_Geo Map_image.png" "dataFileName": "11_Geo map_image.png"
}, },
{ {
"attachmentId": "JULizn130rVI", "attachmentId": "JULizn130rVI",
@ -833,7 +954,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "14_Geo Map_image.png" "dataFileName": "12_Geo map_image.png"
}, },
{ {
"attachmentId": "MdC0DpifJwu4", "attachmentId": "MdC0DpifJwu4",
@ -841,7 +962,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "15_Geo Map_image.png" "dataFileName": "13_Geo map_image.png"
}, },
{ {
"attachmentId": "gFR2Izzp18LQ", "attachmentId": "gFR2Izzp18LQ",
@ -849,7 +970,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "16_Geo Map_image.png" "dataFileName": "14_Geo map_image.png"
}, },
{ {
"attachmentId": "42AncDs7SSAf", "attachmentId": "42AncDs7SSAf",
@ -857,15 +978,7 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "17_Geo Map_image.png" "dataFileName": "15_Geo map_image.png"
},
{
"attachmentId": "pKdtiq4r0eFY",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "18_Geo Map_image.png"
}, },
{ {
"attachmentId": "FXRVvYpOxWyR", "attachmentId": "FXRVvYpOxWyR",
@ -873,7 +986,23 @@
"role": "image", "role": "image",
"mime": "image/png", "mime": "image/png",
"position": 10, "position": 10,
"dataFileName": "19_Geo Map_image.png" "dataFileName": "16_Geo map_image.png"
},
{
"attachmentId": "qudP7UCtwIq3",
"title": "image.png",
"role": "image",
"mime": "image/jpg",
"position": 10,
"dataFileName": "17_Geo map_image.png"
},
{
"attachmentId": "utecGxWk08QY",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "18_Geo map_image.png"
} }
] ]
} }
@ -943,173 +1072,6 @@
} }
] ]
}, },
{
"isClone": false,
"noteId": "DtJJ20yEozPA",
"notePath": [
"OkOZllzB3fqN",
"DtJJ20yEozPA"
],
"title": "Theme development",
"notePosition": 130,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bx-palette",
"isInheritable": false,
"position": 10
}
],
"format": "html",
"attachments": [],
"dirFileName": "Theme development",
"children": [
{
"isClone": false,
"noteId": "5HH79ztN0fZA",
"notePath": [
"OkOZllzB3fqN",
"DtJJ20yEozPA",
"5HH79ztN0fZA"
],
"title": "Creating a custom theme",
"notePosition": 10,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "aH8Dk5aMiq7R",
"isInheritable": false,
"position": 10
}
],
"format": "html",
"dataFileName": "Creating a custom theme.html",
"attachments": [
{
"attachmentId": "AJHVfQtIQgJ7",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Creating a custom theme_im.png"
},
{
"attachmentId": "gXLyv5KXjfxg",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "1_Creating a custom theme_im.png"
},
{
"attachmentId": "on1gD7BzCWdN",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "2_Creating a custom theme_im.png"
},
{
"attachmentId": "17p6z24yW5eP",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "3_Creating a custom theme_im.png"
},
{
"attachmentId": "K3cdwj8f90m0",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "4_Creating a custom theme_im.png"
},
{
"attachmentId": "bn93hwF7C8sR",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "5_Creating a custom theme_im.png"
}
]
},
{
"isClone": false,
"noteId": "aH8Dk5aMiq7R",
"notePath": [
"OkOZllzB3fqN",
"DtJJ20yEozPA",
"aH8Dk5aMiq7R"
],
"title": "Customize the Next theme",
"notePosition": 20,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [],
"format": "html",
"dataFileName": "Customize the Next theme.html",
"attachments": [
{
"attachmentId": "5z4bC0x0eH0P",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Customize the Next theme_i.png"
},
{
"attachmentId": "u0zkXkD7rGXA",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "1_Customize the Next theme_i.png"
}
]
},
{
"isClone": false,
"noteId": "pMq6N1oBV9oo",
"notePath": [
"OkOZllzB3fqN",
"DtJJ20yEozPA",
"pMq6N1oBV9oo"
],
"title": "Reference",
"notePosition": 30,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "po38jIc0LD2H",
"isInheritable": false,
"position": 10
}
],
"format": "html",
"dataFileName": "Reference.html",
"attachments": []
}
]
},
{ {
"isClone": false, "isClone": false,
"noteId": "LTnkDnYmmZ7s", "noteId": "LTnkDnYmmZ7s",
@ -1283,7 +1245,7 @@
"title": "ETAPI", "title": "ETAPI",
"notePosition": 10, "notePosition": 10,
"prefix": null, "prefix": null,
"isExpanded": true, "isExpanded": false,
"type": "text", "type": "text",
"mime": "text/html", "mime": "text/html",
"attributes": [], "attributes": [],
@ -1333,7 +1295,7 @@
"title": "Internal API", "title": "Internal API",
"notePosition": 20, "notePosition": 20,
"prefix": null, "prefix": null,
"isExpanded": true, "isExpanded": false,
"type": "text", "type": "text",
"mime": "text/html", "mime": "text/html",
"attributes": [], "attributes": [],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -23,7 +23,7 @@
as PDF. On the server or PWA (mobile), the option is not available due as PDF. On the server or PWA (mobile), the option is not available due
to technical constraints and it will be hidden.</p> to technical constraints and it will be hidden.</p>
<p>To print a note, select the <p>To print a note, select the
<img src="2_Export as PDF_image.png" width="29" <img src="1_Export as PDF_image.png" width="29"
height="31">button to the right of the note and select <i>Export as PDF</i>.</p> height="31">button to the right of the note and select <i>Export as PDF</i>.</p>
<p>Afterwards you will be prompted to select where to save the PDF file. <p>Afterwards you will be prompted to select where to save the PDF file.
Upon confirmation, the resulting PDF will be opened automatically using Upon confirmation, the resulting PDF will be opened automatically using
@ -33,7 +33,7 @@
<a <a
href="#root/OeKBfN6JbMIq/jRV1MPt4mNSP/hrC6xn7hnDq5">report the issue</a>. In this case, it's best to offer a sample note (click href="#root/OeKBfN6JbMIq/jRV1MPt4mNSP/hrC6xn7hnDq5">report the issue</a>. In this case, it's best to offer a sample note (click
on the on the
<img src="2_Export as PDF_image.png" width="29" height="31">button, select Export note → This note and all of its descendants → HTML <img src="1_Export as PDF_image.png" width="29" height="31">button, select Export note → This note and all of its descendants → HTML
in ZIP archive). Make sure not to accidentally leak any personal information.</p> in ZIP archive). Make sure not to accidentally leak any personal information.</p>
<h2>Landscape mode</h2> <h2>Landscape mode</h2>
<p>When exporting to PDF, there are no customizable settings such as page <p>When exporting to PDF, there are no customizable settings such as page

View File

@ -0,0 +1,56 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Right-to-left text notes</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Right-to-left text notes</h1>
<div class="ck-content">
<p>Trilium now has basic support for right-to-left text, at note level.</p>
<figure
class="table">
<table>
<tbody>
<tr>
<td>
<figure class="image">
<img style="aspect-ratio:906/557;" src="3_Right-to-left text notes_i.png"
width="906" height="557">
</figure>
</td>
<td>
<figure class="image">
<img style="aspect-ratio:906/557;" src="2_Right-to-left text notes_i.png"
width="906" height="557">
</figure>
</td>
</tr>
</tbody>
</table>
</figure>
<p>Note that only the Text note type supports this.</p>
<p>The list of languages is configurable via the a new dedicated settings
page:</p>
<figure class="image">
<img style="aspect-ratio:1248/635;" src="4_Right-to-left text notes_i.png"
width="1248" height="635">
</figure>
<p>To select the corresponding language of the text, go to “Basic Properties”
and select your desired language.</p>
<p>
<img src="1_Right-to-left text notes_i.png" width="635" height="492">
</p>
<p>Feel free to report any issues regarding right to left support.</p>
<p>&nbsp;</p>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -118,6 +118,12 @@
<td>When present (regardless of value), it will show the number of the week <td>When present (regardless of value), it will show the number of the week
on the calendar.</td> on the calendar.</td>
</tr> </tr>
<tr>
<td><code>~child:template</code>
</td>
<td>Defines the template for newly created notes in the calendar (via dragging
or clicking).</td>
</tr>
</tbody> </tbody>
</table> </table>
</figure> </figure>
@ -175,6 +181,36 @@
than the title, either a label (e.g. <code>#assignee</code>) or a relation than the title, either a label (e.g. <code>#assignee</code>) or a relation
(e.g. <code>~for</code>). See <i>Advanced use-cases</i> for more information.</td> (e.g. <code>~for</code>). See <i>Advanced use-cases</i> for more information.</td>
</tr> </tr>
<tr>
<td><code>#calendar:promotedAttributes</code>
</td>
<td>
<p>Allows displaying the value of one or more promoted attributes in the
calendar like this:
<img src="19_Calendar View_image.png" width="131" height="113">
</p><pre><code class="language-text-x-trilium-auto">#label:weight="promoted,number,single,precision=1"
#label:mood="promoted,alias=Mood,single,text"
#calendar:promotedAttributes="label:weight,label:mood" </code></pre>
<p>It can also be used with relations, case in which it will display the
title of the target note:</p><pre><code class="language-text-x-trilium-auto">#relation:assignee="promoted,alias=Assignee,single,text"
#calendar:promotedAttributes="relation:assignee"
~assignee=@My assignee</code></pre>
</td>
</tr>
<tr>
<td><code>#calendar:startDate</code>
</td>
<td>Allows using a different label to represent the start date, other than <code>#startDate</code> (e.g. <code>#expiryDate</code>).
The label name must be prefixed with <code>#</code>. If the label is not
defined for a note, the default will be used instead.</td>
</tr>
<tr>
<td><code>#calendar:endDate</code>
</td>
<td>Allows using a different label to represent the start date, other than <code>#endDate</code>.
The label name must be prefixed with <code>#</code>. If the label is not
defined for a note, the default will be used instead.</td>
</tr>
</tbody> </tbody>
</table> </table>
</figure> </figure>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -5,12 +5,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css"> <link rel="stylesheet" href="../../style.css">
<base target="_parent"> <base target="_parent">
<title data-trilium-title>Geo Map</title> <title data-trilium-title>Geo map</title>
</head> </head>
<body> <body>
<div class="content"> <div class="content">
<h1 data-trilium-h1>Geo Map</h1> <h1 data-trilium-h1>Geo map</h1>
<div class="ck-content"> <div class="ck-content">
<h2>Creating a new geo map</h2> <h2>Creating a new geo map</h2>
@ -26,7 +26,7 @@
<th>1</th> <th>1</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:1256/1044;" src="9_Geo Map_image.png" width="1256" <img style="aspect-ratio:1256/1044;" src="7_Geo map_image.png" width="1256"
height="1044"> height="1044">
</figure> </figure>
</td> </td>
@ -36,7 +36,7 @@
<th>2</th> <th>2</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:1720/1396;" src="3_Geo Map_image.png" width="1720" <img style="aspect-ratio:1720/1396;" src="2_Geo map_image.png" width="1720"
height="1396"> height="1396">
</figure> </figure>
</td> </td>
@ -69,18 +69,18 @@
<p>To create a marker, first navigate to the desired point on the map. Then <p>To create a marker, first navigate to the desired point on the map. Then
press the press the
<img class="image_resized" style="aspect-ratio:72/66;width:7.37%;" <img class="image_resized" style="aspect-ratio:72/66;width:7.37%;"
src="4_Geo Map_image.png" width="72" height="66">button on the top-right of the map.</p> src="3_Geo map_image.png" width="72" height="66">button on the top-right of the map.</p>
<p>If the button is not visible, make sure the button section is visible <p>If the button is not visible, make sure the button section is visible
by pressing the chevron button ( by pressing the chevron button (
<img class="image_resized" style="aspect-ratio:72/66;width:7.51%;" <img class="image_resized" style="aspect-ratio:72/66;width:7.51%;"
src="10_Geo Map_image.png" width="72" height="66">) in the top-right of the map.</p> src="8_Geo map_image.png" width="72" height="66">) in the top-right of the map.</p>
</td> </td>
</tr> </tr>
<tr> <tr>
<th>2</th> <th>2</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:1730/416;" src="14_Geo Map_image.png" width="1730" <img style="aspect-ratio:1730/416;" src="12_Geo map_image.png" width="1730"
height="416"> height="416">
</figure> </figure>
<p>&nbsp;</p> <p>&nbsp;</p>
@ -96,7 +96,7 @@
<th>3</th> <th>3</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:1586/404;" src="1_Geo Map_image.png" width="1586" <img style="aspect-ratio:1586/404;" src="Geo map_image.png" width="1586"
height="404"> height="404">
</figure> </figure>
<p>&nbsp;</p> <p>&nbsp;</p>
@ -107,7 +107,7 @@
<th>4</th> <th>4</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:1696/608;" src="6_Geo Map_image.png" width="1696" <img style="aspect-ratio:1696/608;" src="5_Geo map_image.png" width="1696"
height="608"> height="608">
</figure> </figure>
<p>&nbsp;</p> <p>&nbsp;</p>
@ -122,7 +122,7 @@
<p>The location of a marker is stored in the <code>#geolocation</code> attribute <p>The location of a marker is stored in the <code>#geolocation</code> attribute
of the child notes:</p> of the child notes:</p>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:1288/278;" src="12_Geo Map_image.png" width="1288" <img style="aspect-ratio:1288/278;" src="10_Geo map_image.png" width="1288"
height="278"> height="278">
</figure> </figure>
<p>This value can be added manually if needed. The value of the attribute <p>This value can be added manually if needed. The value of the attribute
@ -155,6 +155,13 @@
</ul> </ul>
</li> </li>
</ul> </ul>
<h2>Icon and color of the markers</h2>
<p>
<img src="18_Geo map_image.png" alt="image" width="523" height="295">
</p>
<p>The markers will have the same icon as the note.</p>
<p>It's possible to add a custom color to a marker by assigning them a <code>#color</code> attribute
such as <code>#color=green</code>.</p>
<h2>Adding the coordinates manually</h2> <h2>Adding the coordinates manually</h2>
<p>In a nutshell, create a child note and set the <code>#geolocation</code> attribute <p>In a nutshell, create a child note and set the <code>#geolocation</code> attribute
to the coordinates.</p> to the coordinates.</p>
@ -168,7 +175,7 @@
<th>1</th> <th>1</th>
<td> <td>
<figure class="image image-style-align-center image_resized" style="width:100%;"> <figure class="image image-style-align-center image_resized" style="width:100%;">
<img style="aspect-ratio:732/918;" src="16_Geo Map_image.png" width="732" <img style="aspect-ratio:732/918;" src="14_Geo map_image.png" width="732"
height="918"> height="918">
</figure> </figure>
</td> </td>
@ -185,7 +192,7 @@
<th>2</th> <th>2</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:518/84;" src="19_Geo Map_image.png" width="518" <img style="aspect-ratio:518/84;" src="16_Geo map_image.png" width="518"
height="84"> height="84">
</figure> </figure>
</td> </td>
@ -199,7 +206,7 @@
<th>3</th> <th>3</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:1074/276;" src="11_Geo Map_image.png" width="1074" <img style="aspect-ratio:1074/276;" src="9_Geo map_image.png" width="1074"
height="276"> height="276">
</figure> </figure>
</td> </td>
@ -225,7 +232,7 @@
<th>1</th> <th>1</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:562/454;" src="17_Geo Map_image.png" width="562" <img style="aspect-ratio:562/454;" src="15_Geo map_image.png" width="562"
height="454"> height="454">
</figure> </figure>
</td> </td>
@ -236,7 +243,7 @@
<th>2</th> <th>2</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:696/480;" src="13_Geo Map_image.png" width="696" <img style="aspect-ratio:696/480;" src="11_Geo map_image.png" width="696"
height="480"> height="480">
</figure> </figure>
</td> </td>
@ -250,7 +257,7 @@
<th>3</th> <th>3</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:640/276;" src="2_Geo Map_image.png" width="640" <img style="aspect-ratio:640/276;" src="1_Geo map_image.png" width="640"
height="276"> height="276">
</figure> </figure>
</td> </td>
@ -275,7 +282,7 @@
<th>1</th> <th>1</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:226/74;" src="7_Geo Map_image.png" width="226" <img style="aspect-ratio:226/74;" src="6_Geo map_image.png" width="226"
height="74"> height="74">
</figure> </figure>
</td> </td>
@ -286,7 +293,7 @@
<th>2</th> <th>2</th>
<td> <td>
<figure class="image"> <figure class="image">
<img style="aspect-ratio:322/222;" src="5_Geo Map_image.png" width="322" <img style="aspect-ratio:322/222;" src="4_Geo map_image.png" width="322"
height="222"> height="222">
</figure> </figure>
</td> </td>
@ -297,7 +304,7 @@
<th>3</th> <th>3</th>
<td> <td>
<figure class="image image_resized" style="width:100%;"> <figure class="image image_resized" style="width:100%;">
<img style="aspect-ratio:620/530;" src="15_Geo Map_image.png" width="620" <img style="aspect-ratio:620/530;" src="13_Geo map_image.png" width="620"
height="530"> height="530">
</figure> </figure>
</td> </td>
@ -310,9 +317,16 @@
</tbody> </tbody>
</table> </table>
</figure> </figure>
<p>&nbsp;</p> <h2>Troubleshooting</h2>
<p>&nbsp;</p> <h3>Grid-like artifacts on the map</h3>
<p>&nbsp;</p> <p>
<img class="image_resized" style="aspect-ratio:678/499;width:58%;" src="17_Geo map_image.png"
width="678" height="499">
</p>
<p>This occurs if the application is not at 100% zoom which causes the pixels
of the map to not render correctly due to fractional scaling. The only
possible solution i to set the UI zoom at 100% (default keyboard shortcut
is Ctrl+0).</p>
<p>&nbsp;</p> <p>&nbsp;</p>
</div> </div>
</div> </div>

View File

@ -1,19 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Text</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Text</h1>
<div class="ck-content"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,34 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../../style.css">
<base target="_parent">
<title data-trilium-title>Content language</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Content language</h1>
<div class="ck-content">
<p>A language hint can be provided for text notes. This option informs the
browser or the desktop application about the language the note is written
in (for example this might help with spellchecking), and it also determines
whether the text is displayed from right-to-left for languages such as
Arabic, Hebrew, etc.</p>
<p>For more information about right-to-left support, see&nbsp;<a class="reference-link"
href="../../New%20Features/Right-to-left%20text%20notes.html">Right-to-left text notes</a>.</p>
<p>To set the language of the content, go to “Basic Properties” and look
for the “Language” field. By default there will be no content languages
set, they can be configured by going to settings or by selecting the “Configure
languages” item in the list.</p>
<p>
<img src="Content language_image.png" width="635" height="492">
</p>
</div>
</div>
</body>
</html>

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