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
.idea
# ignored Files
.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
/build
/dist
/docs
/npm-debug.log
node_modules
/dump-db
/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
steps:
- 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/build-push-action@v6
with:
@ -82,20 +72,6 @@ jobs:
- name: Set up Docker Buildx
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
uses: docker/build-push-action@v6
with:

View File

@ -57,9 +57,6 @@ jobs:
- 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
uses: docker/build-push-action@v6
with:
@ -154,18 +151,6 @@ jobs:
- name: Set up Docker Buildx
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
uses: docker/login-action@v3
with:

View File

@ -2,4 +2,5 @@
*.md
*.yml
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",
"internalConsoleOptions": "neverOpen",
"name": "nodemon server:start",
"program": "${workspaceFolder}/src/main",
"name": "nodemon start-server",
"program": "${workspaceFolder}/src/www",
"request": "launch",
"restart": true,
"runtimeExecutable": "nodemon",

View File

@ -19,5 +19,12 @@
"[css]": {
"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
FROM node:22.14.0-bullseye-slim AS builder
# Configure build dependencies in a single layer
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
WORKDIR /usr/src/app/build
# Copy only necessary files for build
COPY . .
COPY server-package.json package.json
# Build and cleanup in a single layer
RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \
rm docker_healthcheck.ts && \
npm install && \
npm run build:webpack && \
npm prune --omit=dev && \
RUN npm ci && \
npm run build:prepare-dist && \
npm cache clean --force && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \
rm -rf src/public/app/* && \
mkdir -p src/public/app/services && \
cp -r build/src/public/app/services/mime_type_definitions.js src/public/app/services/mime_type_definitions.js && \
rm src/services/asset_path.ts && \
rm -r build
rm -rf dist/node_modules && \
mv dist/* \
start-docker.sh \
/usr/src/app/ && \
rm -rf \
/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
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
# Copy only necessary files from builder
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src
COPY --from=builder /usr/src/app/db ./db
COPY --from=builder /usr/src/app/docker_healthcheck.js .
COPY --from=builder /usr/src/app/start-docker.sh .
COPY --from=builder /usr/src/app/package.json .
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries
# Install only runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gosu && \
rm -rf \
/var/lib/apt/lists/* \
/var/cache/apt/*
COPY --from=builder /usr/src/app ./
RUN sed -i "/electron/d" package.json && \
npm ci --omit=dev && \
npm cache clean --force && \
rm -rf /tmp/node-compile-cache
# Configure container
EXPOSE 8080

View File

@ -1,38 +1,26 @@
# Build stage
FROM node:22.14.0-alpine AS builder
# Configure build dependencies
RUN apk add --no-cache --virtual .build-dependencies \
autoconf \
automake \
g++ \
gcc \
libtool \
make \
nasm \
libpng-dev \
python3
WORKDIR /usr/src/app
WORKDIR /usr/src/app/build
# Copy only necessary files for build
COPY . .
COPY server-package.json package.json
# Build and cleanup in a single layer
RUN cp -R build/src/* src/. && \
cp build/docker_healthcheck.js . && \
rm docker_healthcheck.ts && \
npm install && \
npm run build:webpack && \
npm prune --omit=dev && \
RUN npm ci && \
npm run build:prepare-dist && \
npm cache clean --force && \
cp -r src/public/app/doc_notes src/public/app-dist/. && \
rm -rf src/public/app && \
mkdir -p src/public/app/services && \
cp -r build/src/public/app/services/mime_type_definitions.js src/public/app/services/mime_type_definitions.js && \
rm src/services/asset_path.ts && \
rm -r build
rm -rf dist/node_modules && \
mv dist/* \
start-docker.sh \
/usr/src/app/ && \
rm -rf \
/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
FROM node:22.14.0-alpine
@ -42,17 +30,12 @@ RUN apk add --no-cache su-exec shadow
WORKDIR /usr/src/app
# Copy only necessary files from builder
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/src ./src
COPY --from=builder /usr/src/app/db ./db
COPY --from=builder /usr/src/app/docker_healthcheck.js .
COPY --from=builder /usr/src/app/start-docker.sh .
COPY --from=builder /usr/src/app/package.json .
COPY --from=builder /usr/src/app/config-sample.ini .
COPY --from=builder /usr/src/app/images ./images
COPY --from=builder /usr/src/app/translations ./translations
COPY --from=builder /usr/src/app/libraries ./libraries
COPY --from=builder /usr/src/app ./
RUN sed -i "/electron/d" package.json && \
npm ci --omit=dev && \
npm cache clean --force && \
rm -rf /tmp/node-compile-cache
# Add application user
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`
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 .
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 -r translations $PKG_DIR/
cp -r dump-db $PKG_DIR/
rm -rf $PKG_DIR/dump-db/node_modules
VERSION=`jq -r ".version" package.json`

View File

@ -2,8 +2,6 @@ import fs from "fs-extra";
import path from "path";
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;
@ -13,43 +11,37 @@ function log(...args: any[]) {
}
}
async function copyNodeModuleFileOrFolder(source: string) {
const adjustedSource = source.substring(13);
const destination = path.join(DEST_DIR_NODE_MODULES, adjustedSource);
function copyNodeModuleFileOrFolder(source: string) {
const destination = path.join(DEST_DIR, source);
log(`Copying ${source} to ${destination}`);
await fs.ensureDir(path.dirname(destination));
await fs.copy(source, destination);
fs.ensureDirSync(path.dirname(destination));
fs.copySync(source, destination);
}
const copy = async () => {
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 });
}
try {
const filesToCopy = [
"config-sample.ini",
"tsconfig.webpack.json",
const assetsToCopy = new Set([
"./images",
"./libraries",
"./translations",
"./db",
"./config-sample.ini",
"./package-lock.json",
"./package.json",
"./src/views/",
"./src/etapi/etapi.openapi.yaml",
"./src/routes/api/openapi.json"
];
for (const file of filesToCopy) {
log(`Copying ${file}`);
await fs.copy(file, path.join(DEST_DIR, file));
}
"./src/routes/api/openapi.json",
"./src/public/icon.png",
"./src/public/manifest.webmanifest",
"./src/public/robots.txt",
"./src/public/fonts",
"./src/public/stylesheets",
"./src/public/translations"
]);
const dirsToCopy = ["images", "libraries", "translations", "db"];
for (const dir of dirsToCopy) {
log(`Copying ${dir}`);
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)));
for (const asset of assetsToCopy) {
log(`Copying ${asset}`);
fs.copySync(asset, path.join(DEST_DIR, asset));
}
/**
@ -58,10 +50,10 @@ const copy = async () => {
const publicDirsToCopy = ["./src/public/app/doc_notes"];
const PUBLIC_DIR = path.join(DEST_DIR, "src", "public", "app-dist");
for (const dir of publicDirsToCopy) {
await fs.copy(dir, path.join(PUBLIC_DIR, path.basename(dir)));
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.development.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/@highlightjs/cdn-assets/highlight.min.js",
"node_modules/@mind-elixir/node-menu/dist/node-menu.umd.cjs"
];
]);
for (const file of nodeModulesFile) {
await copyNodeModuleFileOrFolder(file);
}
const nodeModulesFolder = [
const nodeModulesFolder = new Set([
"node_modules/@excalidraw/excalidraw/dist/",
"node_modules/katex/dist/",
"node_modules/dayjs/",
@ -104,13 +92,15 @@ const copy = async () => {
"node_modules/@highlightjs/cdn-assets/languages",
"node_modules/@highlightjs/cdn-assets/styles",
"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()
.then(() => console.log("Copying complete!"))
.catch((err) => 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
echo TypeScript build start
npx tsc
npm run build:ts
echo TypeScript build finished
# Copy the TypeScript artifacts

View File

@ -1,6 +1,6 @@
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerJsdoc from "swagger-jsdoc";
import fs from "fs";
/*
@ -11,28 +11,30 @@ import fs from "fs";
*/
const options = {
definition: {
openapi: '3.1.1',
info: {
title: 'Trilium Notes - Sync server API',
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).",
contact: {
name: "TriliumNext issue tracker",
url: "https://github.com/TriliumNext/Notes/issues",
},
license: {
name: "GNU Free Documentation License 1.3 (or later)",
url: "https://www.gnu.org/licenses/fdl-1.3",
},
definition: {
openapi: "3.1.1",
info: {
title: "Trilium Notes - Sync server API",
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).",
contact: {
name: "TriliumNext issue tracker",
url: "https://github.com/TriliumNext/Notes/issues"
},
license: {
name: "GNU Free Documentation License 1.3 (or later)",
url: "https://www.gnu.org/licenses/fdl-1.3"
}
}
},
},
apis: [
// Put individual files here to have them ordered first.
'./src/routes/api/setup.ts',
// all other files
'./src/routes/api/*.ts', './bin/generate-openapi.js'
],
apis: [
// Put individual files here to have them ordered first.
"./src/routes/api/setup.ts",
// all other files
"./src/routes/api/*.ts",
"./bin/generate-openapi.js"
]
};
const openapiSpecification = swaggerJsdoc(options);

View File

@ -1,5 +1,7 @@
#!/usr/bin/env bash
set -e
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
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_revisions_blobId on revisions (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
See output of `npx esrun dump.ts --help`:
See output of `npx tsx dump.ts --help`:
```
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",
"dependencies": {
"better-sqlite3": "^11.1.2",
"esrun": "^3.2.26",
"mime-types": "^2.1.34",
"sanitize-filename": "^1.6.3",
"tsx": "^4.19.3",
"yargs": "^17.3.1"
},
"devDependencies": {

View File

@ -11,16 +11,14 @@ test("Displays translation on desktop", async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
await expect(page.locator("#left-pane .quick-search input"))
.toHaveAttribute("placeholder", "Quick search");
await expect(page.locator("#left-pane .quick-search input")).toHaveAttribute("placeholder", "Quick search");
});
test("Displays translation on mobile", async ({ page, context }) => {
const app = new App(page, context);
await app.goto({ isMobile: true });
await expect(page.locator("#mobile-sidebar-wrapper .quick-search input"))
.toHaveAttribute("placeholder", "Quick search");
await expect(page.locator("#mobile-sidebar-wrapper .quick-search input")).toHaveAttribute("placeholder", "Quick search");
});
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.
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");
// Select Chinese and ensure the translation is set.
await languageCombobox.selectOption("cn");
await expect(app.currentNoteSplit).toContainText("主题", { timeout: 15000 });
await expect(languageCombobox).toHaveValue("cn");
// Select English again.
await languageCombobox.selectOption("en");
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);
// 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);
await expect(tab).toContainText(NOTE_TITLE);
// 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);
});

View File

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

View File

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

View File

@ -44,8 +44,8 @@ test("Highlights list is displayed", async ({ page, context }) => {
await expect(app.sidebar).toContainText("Highlights List");
const rootList = app.sidebar.locator(".highlights-list ol");
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" ]) {
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"]) {
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);
await app.goto();
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.press("ControlOrMeta+M");

View File

@ -25,7 +25,7 @@ export default class App {
this.tabBar = page.locator(".tab-row-widget-container");
this.noteTree = page.locator(".tree-wrapper");
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");
}
@ -42,12 +42,11 @@ export default class App {
url = "/";
}
await this.page.goto(url, { waitUntil: "networkidle" });
await this.page.goto(url, { waitUntil: "networkidle", timeout: 30_000 });
// Wait for the page to load.
if (url === "/") {
await expect(this.page.locator(".tree"))
.toContainText("Trilium Integration Test");
await expect(this.page.locator(".tree")).toContainText("Trilium Integration Test");
await this.closeAllTabs();
}
}
@ -109,11 +108,12 @@ export default class App {
});
expect(csrfToken).toBeTruthy();
await expect(await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, {
headers: {
"x-csrf-token": csrfToken
}
})).toBeOK();
await expect(
await this.page.request.put(`${BASE_URL}/api/options/${key}/${value}`, {
headers: {
"x-csrf-token": csrfToken
}
})
).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 [];
}
await glob.requireLibrary(glob.ESLINT);
if (text.length > 20000) {
console.log("Skipping linting because of large size: ", text.length);
return [];
}
const errors = new eslint().verify(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"
}
});
const errors = await glob.linter(text);
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",
"productName": "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",
"main": "./dist/electron-main.js",
"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-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",
"build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts",
"build:prepare-dist": "npm run build:webpack && rimraf ./dist && tsc && tsx ./bin/copy-dist.ts",
"build:webpack": "tsx node_modules/webpack/bin/webpack.js -c webpack.config.ts --progress",
"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",
"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",
"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",
"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-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",
"dev:watch-dist": "tsx ./bin/watch-dist.ts",
"dev:prettier-check": "prettier . --check",
"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:ci-update-nightly-version": "tsx ./bin/update-nightly-version.ts",
"chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
@ -77,7 +81,7 @@
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"autocomplete.js": "0.38.1",
"axios": "1.8.1",
"axios": "1.8.2",
"better-sqlite3": "11.8.1",
"boxicons": "2.1.4",
"chardet": "2.1.0",
@ -98,12 +102,12 @@
"electron-squirrel-startup": "1.0.1",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"eslint-linter-browserify": "9.22.0",
"express": "4.21.2",
"express-rate-limit": "7.5.0",
"express-session": "1.18.1",
"force-graph": "1.49.2",
"force-graph": "1.49.3",
"fs-extra": "11.3.0",
"happy-dom": "17.1.8",
"helmet": "8.0.0",
"html": "1.0.0",
"html2plaintext": "2.1.4",
@ -123,7 +127,6 @@
"jsdom": "26.0.0",
"jsplumb": "2.15.6",
"katex": "0.16.21",
"knockout": "3.5.1",
"leaflet": "1.9.4",
"leaflet-gpx": "2.1.2",
"mark.js": "8.11.1",
@ -150,7 +153,6 @@
"striptags": "3.2.0",
"swagger-ui-express": "5.0.1",
"tmp": "0.2.3",
"ts-loader": "9.5.2",
"turndown": "7.2.0",
"unescape": "1.0.1",
"vanilla-js-wheel-zoom": "9.0.4",
@ -168,7 +170,8 @@
"@electron-forge/maker-zip": "7.7.0",
"@electron-forge/plugin-auto-unpack-natives": "7.7.0",
"@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",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12",
@ -193,7 +196,7 @@
"@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.13.8",
"@types/node": "22.13.9",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5",
"@types/safe-compare": "1.1.2",
@ -207,23 +210,27 @@
"@types/swagger-ui-express": "4.1.8",
"@types/tmp": "0.2.6",
"@types/turndown": "5.0.5",
"@types/ws": "8.5.14",
"@types/ws": "8.18.0",
"@types/xml2js": "0.4.14",
"@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.0.7",
"@vitest/coverage-v8": "3.0.8",
"autoprefixer": "10.4.20",
"bootstrap": "5.3.3",
"cross-env": "7.0.3",
"css-loader": "7.1.2",
"electron": "34.3.0",
"electron": "34.3.1",
"eslint": "9.22.0",
"esm": "3.2.25",
"globals": "16.0.0",
"happy-dom": "17.4.0",
"i18next-http-backend": "3.0.2",
"jsdoc": "4.0.4",
"knockout": "3.5.1",
"lorem-ipsum": "2.0.8",
"mini-css-extract-plugin": "2.9.2",
"nodemon": "3.1.9",
"postcss-loader": "8.1.1",
"prettier": "3.5.2",
"prettier": "3.5.3",
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"sass": "1.85.1",
@ -231,11 +238,13 @@
"split.js": "1.6.5",
"supertest": "7.0.0",
"swagger-jsdoc": "6.2.8",
"ts-loader": "9.5.2",
"tslib": "2.8.1",
"tsx": "4.19.3",
"typedoc": "0.27.9",
"typescript": "5.8.2",
"vitest": "3.0.7",
"typescript-eslint": "8.26.0",
"vitest": "3.0.8",
"webpack": "5.98.0",
"webpack-cli": "6.0.1",
"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.
@ -14,68 +14,70 @@ const SERVER_URL = 'http://127.0.0.1:8082';
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: SERVER_URL,
testDir: "./e2e",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: SERVER_URL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry"
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] }
}
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* 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,
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// 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");
});
});
*/
*/

View File

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

View File

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

View File

@ -100,4 +100,4 @@ etapi.describeEtapi("notes", () => {
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 BRecentNote from "./entities/brecent_note.js";
import type AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
import type BTask from "./entities/btask.js";
interface AttachmentOpts {
includeContentLength?: boolean;
@ -33,7 +32,6 @@ export default class Becca {
attributeIndex!: Record<string, BAttribute[]>;
options!: Record<string, BOption>;
etapiTokens!: Record<string, BEtapiToken>;
tasks!: Record<string, BTask>;
allNoteSetCache: NoteSet | null;
@ -50,7 +48,6 @@ export default class Becca {
this.attributeIndex = {};
this.options = {};
this.etapiTokens = {};
this.tasks = {};
this.dirtyNoteSetCache();
@ -216,14 +213,6 @@ export default class Becca {
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 {
if (!entityName || !entityId) {
return null;

View File

@ -11,10 +11,9 @@ import BOption from "./entities/boption.js";
import BEtapiToken from "./entities/betapi_token.js";
import cls from "../services/cls.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 ws from "../services/ws.js";
import BTask from "./entities/btask.js";
const beccaLoaded = new Promise<void>(async (res, rej) => {
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`)) {
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) {

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.
* 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)) {
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 sql from "../../services/sql.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";
interface ContentOpts {
@ -36,7 +36,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
revisionId?: string;
noteId!: string;
type!: string;
type!: NoteType;
mime!: string;
title!: 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 {
revisionId?: string;
noteId: string;
type: string;
type: NoteType;
mime: string;
isProtected?: boolean;
title: string;
@ -139,13 +139,3 @@ export interface NoteRow {
utcDateModified: string;
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 BRecentNote from "./entities/brecent_note.js";
import BRevision from "./entities/brevision.js";
import BTask from "./entities/btask.js";
type EntityClass = new (row?: any) => AbstractBeccaEntity<any>;
@ -22,8 +21,7 @@ const ENTITY_NAME_TO_ENTITY: Record<string, ConstructorData<any> & EntityClass>
notes: BNote,
options: BOption,
recent_notes: BRecentNote,
revisions: BRevision,
tasks: BTask
revisions: BRevision
};
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 {
message: string;
import HttpError from "./http_error.js";
class NotFoundError extends HttpError {
constructor(message: string) {
this.message = message;
super(message, 404);
this.name = "NotFoundError";
}
}
export default NotFoundError;

View File

@ -1,9 +1,12 @@
class ValidationError {
message: string;
import HttpError from "./http_error.js";
class ValidationError extends HttpError {
constructor(message: string) {
this.message = message;
super(message, 400)
this.name = "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 { ContextMenuEvent } from "../menus/context_menu.js";
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js";
interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget;
@ -62,7 +63,7 @@ export interface NoteCommandData 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 = {
"api-log-messages": CommandData;
focusTree: CommandData,
focusTree: CommandData;
focusOnTitle: CommandData;
focusOnDetail: CommandData;
focusOnSearchDefinition: Required<CommandData>;
@ -108,7 +109,7 @@ export type CommandMappings = {
showInfoDialog: ConfirmWithMessageOptions;
showConfirmDialog: ConfirmWithMessageOptions;
showRecentChanges: CommandData & { ancestorNoteId: string };
showImportDialog: CommandData & { noteId: string; };
showImportDialog: CommandData & { noteId: string };
openNewNoteSplit: NoteCommandData;
openInWindow: NoteCommandData;
openNoteInNewTab: CommandData;
@ -131,8 +132,10 @@ export type CommandMappings = {
editNoteTitle: ContextMenuCommandData;
protectSubtree: ContextMenuCommandData;
unprotectSubtree: ContextMenuCommandData;
openBulkActionsDialog: ContextMenuCommandData | {
selectedOrActiveNoteIds?: string[]
openBulkActionsDialog:
| ContextMenuCommandData
| {
selectedOrActiveNoteIds?: string[];
};
editBranchPrefix: ContextMenuCommandData;
convertNoteToAttachment: ContextMenuCommandData;
@ -221,11 +224,11 @@ export type CommandMappings = {
moveTabToNewWindow: CommandData;
copyTabToNewWindow: CommandData;
closeActiveTab: CommandData & {
$el: JQuery<HTMLElement>
},
$el: JQuery<HTMLElement>;
};
setZoomFactorAndSave: {
zoomFactor: string;
}
};
reEvaluateRightPaneVisibility: CommandData;
runActiveNote: CommandData;
@ -234,18 +237,18 @@ export type CommandMappings = {
};
scrollToEnd: CommandData;
closeThisNoteSplit: CommandData;
moveThisNoteSplit: CommandData & { isMovingLeft: boolean; };
moveThisNoteSplit: CommandData & { isMovingLeft: boolean };
// Geomap
deleteFromMap: { noteId: string },
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
deleteFromMap: { noteId: string };
openGeoLocation: { noteId: string; event: JQuery.MouseDownEvent };
toggleZenMode: CommandData;
updateAttributeList: CommandData & { attributes: Attribute[] };
saveAttributes: CommandData;
reloadAttributes: CommandData;
refreshNoteList: CommandData & { noteId: string; };
refreshNoteList: CommandData & { noteId: string };
refreshResults: {};
refreshSearchDefinition: {};
@ -348,7 +351,7 @@ type EventMappings = {
ntxId: string | null | undefined; // TODO: deduplicate ntxId
};
tabReorder: {
ntxIdsInOrder: string[]
ntxIdsInOrder: string[];
};
refreshNoteList: {
noteId: string;
@ -359,6 +362,12 @@ type EventMappings = {
relationMapResetPanZoom: { ntxId: string | null | undefined };
relationMapResetZoomIn: { ntxId: string | null | undefined };
relationMapResetZoomOut: { ntxId: string | null | undefined };
activeNoteChangedEvent: {};
showAddLinkDialog: {
textTypeWidget: EditableTextTypeWidget;
text: string;
};
};
export type EventListener<T extends EventNames> = {
@ -542,10 +551,12 @@ $(window).on("beforeunload", () => {
});
$(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) {
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[];
initialized: Promise<void> | null;
parent?: TypedComponent<any>;
position!: number;
_position!: number;
constructor() {
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, "_");
}
get position() {
return this._position;
}
set position(newPosition: number) {
this._position = newPosition;
}
setParent(parent: TypedComponent<any>) {
this.parent = parent;
return this;

View File

@ -369,7 +369,8 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
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) {
// assuming the attachment has been already loaded

View File

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

View File

@ -248,7 +248,7 @@ export default class TabManager extends Component {
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 existingNoteContext = this.children.find((nc) => nc.ntxId === noteContext.ntxId);

View File

@ -1,6 +1,6 @@
{
"formatVersion": 2,
"appVersion": "0.92.0-beta",
"appVersion": "0.92.2-beta",
"files": [
{
"isClone": false,
@ -34,7 +34,7 @@
"OkOZllzB3fqN",
"yoAe4jV2yzbd"
],
"title": "Features",
"title": "New Features",
"notePosition": 40,
"prefix": null,
"isExpanded": false,
@ -47,53 +47,91 @@
"value": "bx bx-star",
"isInheritable": false,
"position": 10
},
{
"type": "label",
"name": "sorted",
"value": "dateCreated",
"isInheritable": false,
"position": 20
},
{
"type": "label",
"name": "sortDirection",
"value": "desc",
"isInheritable": false,
"position": 30
}
],
"format": "html",
"attachments": [],
"dirFileName": "Features",
"dirFileName": "New Features",
"children": [
{
"isClone": false,
"noteId": "13D1lOc9sqmZ",
"noteId": "3I277VKYxWDH",
"notePath": [
"OkOZllzB3fqN",
"yoAe4jV2yzbd",
"13D1lOc9sqmZ"
"3I277VKYxWDH"
],
"title": "Export as PDF",
"notePosition": 20,
"title": "Right-to-left text notes",
"notePosition": 10,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [],
"attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bx-align-right",
"isInheritable": false,
"position": 10
}
],
"format": "html",
"dataFileName": "Export as PDF.html",
"dataFileName": "Right-to-left text notes.html",
"attachments": [
{
"attachmentId": "xsGM34t8ssKV",
"attachmentId": "PSBNAvDyj5Vy",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Export as PDF_image.png"
"dataFileName": "Right-to-left text notes_i.png"
},
{
"attachmentId": "cvyes4f1Vhmm",
"attachmentId": "YXYIJznak915",
"title": "image.png",
"role": "image",
"mime": "image/png",
"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",
"role": "image",
"mime": "image/png",
"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"
],
"title": "Zen mode",
"notePosition": 30,
"notePosition": 20,
"prefix": null,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [],
"attributes": [
{
"type": "label",
"name": "iconClass",
"value": "bx bxs-yin-yang",
"isInheritable": false,
"position": 10
}
],
"format": "html",
"dataFileName": "Zen mode.html",
"attachments": [
@ -180,6 +226,50 @@
"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",
"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,
@ -382,7 +511,7 @@
"title": "Book",
"notePosition": 70,
"prefix": null,
"isExpanded": true,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [
@ -576,6 +705,14 @@
"mime": "image/png",
"position": 10,
"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",
"foPEtsL51pD2"
],
"title": "Geo Map",
"title": "Geo map",
"notePosition": 120,
"prefix": null,
"isExpanded": false,
@ -713,23 +850,15 @@
}
],
"format": "html",
"dataFileName": "Geo Map.html",
"dataFileName": "Geo map.html",
"attachments": [
{
"attachmentId": "J0baLTpafs7C",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Geo Map_image.png"
},
{
"attachmentId": "kcYjOvJDFkbS",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "1_Geo Map_image.png"
"dataFileName": "Geo map_image.png"
},
{
"attachmentId": "FDP3JzIVSnuJ",
@ -737,7 +866,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "2_Geo Map_image.png"
"dataFileName": "1_Geo map_image.png"
},
{
"attachmentId": "eUrcqc8RRuZG",
@ -745,7 +874,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "3_Geo Map_image.png"
"dataFileName": "2_Geo map_image.png"
},
{
"attachmentId": "1quk4yxJpeHZ",
@ -753,7 +882,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "4_Geo Map_image.png"
"dataFileName": "3_Geo map_image.png"
},
{
"attachmentId": "iSpyhQ5Ya6Nk",
@ -761,7 +890,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "5_Geo Map_image.png"
"dataFileName": "4_Geo map_image.png"
},
{
"attachmentId": "ut6vm2aXVfXI",
@ -769,7 +898,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "6_Geo Map_image.png"
"dataFileName": "5_Geo map_image.png"
},
{
"attachmentId": "uYdb9wWf5Nuv",
@ -777,15 +906,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "7_Geo Map_image.png"
},
{
"attachmentId": "GhHYO2LteDmZ",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "8_Geo Map_image.png"
"dataFileName": "6_Geo map_image.png"
},
{
"attachmentId": "viN50n5G4kB0",
@ -793,7 +914,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "9_Geo Map_image.png"
"dataFileName": "7_Geo map_image.png"
},
{
"attachmentId": "mgwGrtQZjxxb",
@ -801,7 +922,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "10_Geo Map_image.png"
"dataFileName": "8_Geo map_image.png"
},
{
"attachmentId": "PMqmCbNLlZOG",
@ -809,7 +930,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "11_Geo Map_image.png"
"dataFileName": "9_Geo map_image.png"
},
{
"attachmentId": "0AwaQMqt3FVA",
@ -817,7 +938,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "12_Geo Map_image.png"
"dataFileName": "10_Geo map_image.png"
},
{
"attachmentId": "gR2c2Thmfy3I",
@ -825,7 +946,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "13_Geo Map_image.png"
"dataFileName": "11_Geo map_image.png"
},
{
"attachmentId": "JULizn130rVI",
@ -833,7 +954,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "14_Geo Map_image.png"
"dataFileName": "12_Geo map_image.png"
},
{
"attachmentId": "MdC0DpifJwu4",
@ -841,7 +962,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "15_Geo Map_image.png"
"dataFileName": "13_Geo map_image.png"
},
{
"attachmentId": "gFR2Izzp18LQ",
@ -849,7 +970,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "16_Geo Map_image.png"
"dataFileName": "14_Geo map_image.png"
},
{
"attachmentId": "42AncDs7SSAf",
@ -857,15 +978,7 @@
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "17_Geo Map_image.png"
},
{
"attachmentId": "pKdtiq4r0eFY",
"title": "image.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "18_Geo Map_image.png"
"dataFileName": "15_Geo map_image.png"
},
{
"attachmentId": "FXRVvYpOxWyR",
@ -873,7 +986,23 @@
"role": "image",
"mime": "image/png",
"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,
"noteId": "LTnkDnYmmZ7s",
@ -1283,7 +1245,7 @@
"title": "ETAPI",
"notePosition": 10,
"prefix": null,
"isExpanded": true,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"attributes": [],
@ -1333,7 +1295,7 @@
"title": "Internal API",
"notePosition": 20,
"prefix": null,
"isExpanded": true,
"isExpanded": false,
"type": "text",
"mime": "text/html",
"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
to technical constraints and it will be hidden.</p>
<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>
<p>Afterwards you will be prompted to select where to save the PDF file.
Upon confirmation, the resulting PDF will be opened automatically using
@ -33,7 +33,7 @@
<a
href="#root/OeKBfN6JbMIq/jRV1MPt4mNSP/hrC6xn7hnDq5">report the issue</a>. In this case, it's best to offer a sample note (click
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>
<h2>Landscape mode</h2>
<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
on the calendar.</td>
</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>
</table>
</figure>
@ -175,6 +181,36 @@
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>
</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>
</table>
</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">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Geo Map</title>
<title data-trilium-title>Geo map</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Geo Map</h1>
<h1 data-trilium-h1>Geo map</h1>
<div class="ck-content">
<h2>Creating a new geo map</h2>
@ -26,7 +26,7 @@
<th>1</th>
<td>
<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">
</figure>
</td>
@ -36,7 +36,7 @@
<th>2</th>
<td>
<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">
</figure>
</td>
@ -69,18 +69,18 @@
<p>To create a marker, first navigate to the desired point on the map. Then
press the
<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
by pressing the chevron button (
<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>
</tr>
<tr>
<th>2</th>
<td>
<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">
</figure>
<p>&nbsp;</p>
@ -96,7 +96,7 @@
<th>3</th>
<td>
<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">
</figure>
<p>&nbsp;</p>
@ -107,7 +107,7 @@
<th>4</th>
<td>
<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">
</figure>
<p>&nbsp;</p>
@ -122,7 +122,7 @@
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
of the child notes:</p>
<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">
</figure>
<p>This value can be added manually if needed. The value of the attribute
@ -155,6 +155,13 @@
</ul>
</li>
</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>
<p>In a nutshell, create a child note and set the <code>#geolocation</code> attribute
to the coordinates.</p>
@ -168,7 +175,7 @@
<th>1</th>
<td>
<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">
</figure>
</td>
@ -185,7 +192,7 @@
<th>2</th>
<td>
<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">
</figure>
</td>
@ -199,7 +206,7 @@
<th>3</th>
<td>
<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">
</figure>
</td>
@ -225,7 +232,7 @@
<th>1</th>
<td>
<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">
</figure>
</td>
@ -236,7 +243,7 @@
<th>2</th>
<td>
<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">
</figure>
</td>
@ -250,7 +257,7 @@
<th>3</th>
<td>
<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">
</figure>
</td>
@ -275,7 +282,7 @@
<th>1</th>
<td>
<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">
</figure>
</td>
@ -286,7 +293,7 @@
<th>2</th>
<td>
<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">
</figure>
</td>
@ -297,7 +304,7 @@
<th>3</th>
<td>
<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">
</figure>
</td>
@ -310,9 +317,16 @@
</tbody>
</table>
</figure>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2>Troubleshooting</h2>
<h3>Grid-like artifacts on the map</h3>
<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>
</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