Merge branch 'develop' into ai-llm-integration
@ -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
|
24
.github/workflows/dev.yml
vendored
@ -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:
|
||||
|
15
.github/workflows/main-docker.yml
vendored
@ -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:
|
||||
|
@ -2,4 +2,5 @@
|
||||
*.md
|
||||
*.yml
|
||||
libraries/*
|
||||
docs/*
|
||||
docs/*
|
||||
src/public/app/doc_notes/**/*
|
6
.vscode/extensions.json
vendored
@ -1,3 +1,7 @@
|
||||
{
|
||||
"recommendations": ["lokalise.i18n-ally", "editorconfig.editorconfig"]
|
||||
"recommendations": [
|
||||
"lokalise.i18n-ally",
|
||||
"editorconfig.editorconfig",
|
||||
"vitest.explorer"
|
||||
]
|
||||
}
|
||||
|
4
.vscode/launch.json
vendored
@ -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",
|
||||
|
9
.vscode/settings.json
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
|
72
Dockerfile
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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`
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
if [[ $# -eq 0 ]] ; then
|
||||
echo "Missing argument of new version"
|
||||
exit 1
|
||||
|
@ -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
|
||||
);
|
@ -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
|
||||
);
|
@ -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>
|
||||
|
858
dump-db/package-lock.json
generated
@ -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": {
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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
@ -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/*"
|
||||
]
|
||||
}
|
||||
);
|
28
libraries/codemirror/eslint.js
vendored
@ -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
1107
package-lock.json
generated
41
package.json
@ -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"
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -6,4 +6,4 @@ etapi.describeEtapi("app_info", () => {
|
||||
expect(appInfo.clipperProtocolVersion).toEqual("1.0");
|
||||
});
|
||||
});
|
||||
*/
|
||||
*/
|
||||
|
@ -7,4 +7,4 @@ etapi.describeEtapi("backup", () => {
|
||||
expect(response.status).toEqual(204);
|
||||
});
|
||||
});
|
||||
*/
|
||||
*/
|
||||
|
@ -23,4 +23,4 @@ etapi.describeEtapi("import", () => {
|
||||
expect(content).toContain("test export content");
|
||||
});
|
||||
});
|
||||
*/
|
||||
*/
|
||||
|
@ -100,4 +100,4 @@ etapi.describeEtapi("notes", () => {
|
||||
expect(error.message).toEqual(`Note '${note.noteId}' not found.`);
|
||||
});
|
||||
});
|
||||
*/
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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'.`);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
12
src/errors/forbidden_error.ts
Normal 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
@ -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;
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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": [],
|
||||
|
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 340 B |
After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
@ -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
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
@ -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> </p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 323 KiB After Width: | Height: | Size: 323 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 191 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 515 KiB After Width: | Height: | Size: 515 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 397 KiB After Width: | Height: | Size: 397 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 260 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 4.6 KiB |
@ -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>
|
||||
|
Before Width: | Height: | Size: 6.5 KiB |
@ -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> </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> </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> </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> </p>
|
||||
<p> </p>
|
||||
<p> </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> </p>
|
||||
</div>
|
||||
</div>
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
@ -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>
|
@ -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 <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>
|