Compare commits

..

76 Commits

Author SHA1 Message Date
perf3ct
53f31f9c78 feat(docs): try to also sync docs like this 2025-10-21 15:04:55 -07:00
perf3ct
22c5651eeb feat(docs): try to get images to load at least? 2025-09-03 22:53:07 +00:00
perf3ct
68a10a9813 feat(docs): hopefully fix images 2025-09-03 22:39:38 +00:00
perf3ct
b204964e57 feat(docs): also update the Home.md 2025-09-03 21:33:52 +00:00
perf3ct
b2c869d7ab fix(docs): have this ci use explicit token 2025-09-03 18:29:42 +00:00
perf3ct
307e17f9c8 feat(docs): push to wiki when docs change
asdf

asdf
2025-09-03 17:58:04 +00:00
Elian Doran
ee7052ebc2 chore(deps): update dependency dotenv to v17.2.2 (#6876) 2025-09-03 18:02:23 +03:00
renovate[bot]
9059642738 chore(deps): update dependency dotenv to v17.2.2 2025-09-03 14:14:24 +00:00
Elian Doran
fe8e3b4489 chore(deps): update svelte monorepo (#6874) 2025-09-03 17:11:58 +03:00
Elian Doran
4e00e5b995 chore(deps): update dependency dotenv to v17.2.2 (#6875) 2025-09-03 17:11:45 +03:00
Elian Doran
05ae0ca9d7 chore(deps): update dependency @anthropic-ai/sdk to v0.61.0 (#6877) 2025-09-03 17:11:38 +03:00
Elian Doran
ac0116109b chore(deps): update typescript-eslint monorepo to v8.42.0 (#6878) 2025-09-03 17:11:28 +03:00
renovate[bot]
710ed9dd0e chore(deps): update svelte monorepo 2025-09-03 12:45:44 +00:00
Adorian Doran
267f5105b2 Theme tweaks (#6832) 2025-09-03 10:53:57 +03:00
Adorian Doran
a9564f8f38 style/launchbar buttons: fix broken hover state when background effects are enabled 2025-09-03 05:25:26 +03:00
Adorian Doran
9c5a130ab4 style/text editor/forms: restyle text areas 2025-09-03 04:38:37 +03:00
Adorian Doran
c40398df5d style/text editor/forms: tweak text boxes 2025-09-03 04:29:02 +03:00
Adorian Doran
27fdd9e715 style/text editor/find and replace: add style for the "replace" buttons 2025-09-03 04:13:29 +03:00
renovate[bot]
59697095b1 chore(deps): update typescript-eslint monorepo to v8.42.0 2025-09-03 00:40:09 +00:00
renovate[bot]
a16f5f5505 chore(deps): update dependency @anthropic-ai/sdk to v0.61.0 2025-09-03 00:38:49 +00:00
renovate[bot]
922d484a33 chore(deps): update dependency dotenv to v17.2.2 2025-09-03 00:37:18 +00:00
Elian Doran
e1b4a0b720 fix(deps): update dependency @codemirror/view to v6.38.2 (#6860) 2025-09-02 21:33:12 +03:00
Elian Doran
991399fe4f Merge branch 'main' into renovate/codemirror 2025-09-02 20:41:41 +03:00
Elian Doran
27855456a0 chore(deps): update dependency lint-staged to v16.1.6 (#6856) 2025-09-02 20:40:55 +03:00
Elian Doran
0687ed9ec4 chore(deps): update dependency typedoc to v0.28.12 (#6857) 2025-09-02 20:40:42 +03:00
Elian Doran
632976e71f chore(deps): update pnpm to v10.15.1 (#6859) 2025-09-02 20:40:25 +03:00
Elian Doran
98addef614 fix(deps): update dependency dayjs to v1.11.18 (#6861) 2025-09-02 20:40:10 +03:00
Elian Doran
c72c9934b5 fix(deps): update dependency react-i18next to v15.7.3 (#6862) 2025-09-02 20:39:54 +03:00
Elian Doran
6362f24ae9 chore(deps): update dependency @stylistic/eslint-plugin to v5.3.1 (#6863) 2025-09-02 20:39:41 +03:00
Elian Doran
dc2d2fe25b chore(deps): update dependency vite to v7.1.4 (#6858) 2025-09-02 20:39:20 +03:00
renovate[bot]
bbc007e6cf chore(deps): update dependency vite to v7.1.4 2025-09-02 17:36:12 +00:00
Elian Doran
3dfd195630 chore(deps): update dependency @sveltejs/kit to v2.37.0 (#6864) 2025-09-02 20:34:24 +03:00
Elian Doran
e3f72baab3 chore(deps): update node.js to v22.19.0 (#6865) 2025-09-02 20:33:23 +03:00
Elian Doran
1bb19d0d9e Translations update from Hosted Weblate (#6871) 2025-09-02 20:31:45 +03:00
Микола Копитін
bc1b69a836 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (1566 of 1566 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/uk/
2025-09-02 17:03:22 +00:00
renovate[bot]
0bfa9f0c58 chore(deps): update node.js to v22.19.0 2025-09-02 01:18:18 +00:00
renovate[bot]
498ffa806d chore(deps): update dependency @sveltejs/kit to v2.37.0 2025-09-02 01:18:13 +00:00
renovate[bot]
9bfed2a80d chore(deps): update dependency @stylistic/eslint-plugin to v5.3.1 2025-09-02 01:17:37 +00:00
renovate[bot]
ec902c5762 fix(deps): update dependency react-i18next to v15.7.3 2025-09-02 01:17:32 +00:00
renovate[bot]
bb1d31f877 fix(deps): update dependency dayjs to v1.11.18 2025-09-02 01:16:58 +00:00
renovate[bot]
09f938fb72 fix(deps): update dependency @codemirror/view to v6.38.2 2025-09-02 01:16:21 +00:00
renovate[bot]
a3e9192998 chore(deps): update pnpm to v10.15.1 2025-09-02 01:15:40 +00:00
renovate[bot]
80b7c0b4c9 chore(deps): update dependency typedoc to v0.28.12 2025-09-02 01:14:46 +00:00
renovate[bot]
f01d6938f3 chore(deps): update dependency lint-staged to v16.1.6 2025-09-02 01:14:40 +00:00
Adorian Doran
ab95d707a3 style/text editor/forms: refactor 2025-09-02 02:43:11 +03:00
Adorian Doran
6475b4029a style/text editor/forms: restyle number inputs 2025-09-02 02:42:05 +03:00
Adorian Doran
f646b3dc5c style/text editor/color selector dropdown: fix layout 2025-09-02 02:14:43 +03:00
Adorian Doran
bcef0802e4 style/text editor/insert emoji flyout: fix the spacing of the skin tone dropdown items 2025-09-02 02:08:04 +03:00
Adorian Doran
47099cc77b style/text editor/insert math flyout: fix layout 2025-09-02 02:03:49 +03:00
Elian Doran
83be42f4ea Translations update from Hosted Weblate (#6843) 2025-09-01 09:10:52 +03:00
Mik Piet
ab9fec0186 Translated using Weblate (Polish)
Currently translated at 7.0% (111 of 1566 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-09-01 06:02:00 +02:00
Flowerlywind
c6dd32ea7b Translated using Weblate (Vietnamese)
Currently translated at 2.5% (40 of 1566 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/vi/
2025-09-01 06:01:58 +02:00
Kuzma Simonov
1d4cd538ac Translated using Weblate (Russian)
Currently translated at 100.0% (1566 of 1566 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-09-01 06:01:57 +02:00
rodrigomescua
dc99f725f9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1566 of 1566 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pt_BR/
2025-09-01 06:01:55 +02:00
Newcomer1989
2f804f3eac Translated using Weblate (German)
Currently translated at 100.0% (1566 of 1566 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2025-09-01 06:01:54 +02:00
Elian Doran
9d6bb306e7 fix(electron): history navigation context menu not working 2025-08-31 19:39:54 +03:00
Adorian Doran
03ab912495 style/text editor/forms: tweak buttons 2025-08-31 03:33:30 +03:00
Adorian Doran
d12dfabd0b style/text editor/forms: various layout fixes 2025-08-31 03:19:33 +03:00
Adorian Doran
ed748bbebd style/text editor/forms: restyle dropdowns 2025-08-31 02:58:53 +03:00
Adorian Doran
e85858d22d style/text editor/forms: fix visible focus for buttons 2025-08-31 02:30:13 +03:00
Adorian Doran
0afa9717e5 style/text editor/forms: restyle buttons 2025-08-31 02:28:24 +03:00
Adorian Doran
1e2e3498c6 style/text editor/forms: restyle text boxes 2025-08-31 01:45:12 +03:00
Adorian Doran
fcb77360e1 style/text editor/text alignment dropdown: use a horizontal toolbar instead of a vertical one 2025-08-30 19:22:27 +03:00
Adorian Doran
3d285e105e style/text editor/insert text snippet dropdown: tweak appearance 2025-08-30 18:25:44 +03:00
Adorian Doran
a94cc5bdab style/text editor/insert text snippet dropdown: tweak appearance 2025-08-30 02:55:23 +03:00
Adorian Doran
526c5a6dd8 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/theme/improvements 2025-08-30 01:06:02 +03:00
Adorian Doran
7d3a672b55 style/toasts: prevent long text from overflowing 2025-08-29 21:36:09 +03:00
Adorian Doran
c08b30a060 style/text editor: hide icons from the text snippets dropdown 2025-08-29 21:30:04 +03:00
Adorian Doran
b147d4bdeb style: brighten the border of dropdowns 2025-08-29 21:17:43 +03:00
Adorian Doran
48faa8a813 client/quick search results: tweak the busy indicator 2025-08-29 21:06:48 +03:00
Adorian Doran
d73e84ea6c client/quick search results: tweak icon alignment 2025-08-29 18:37:02 +03:00
Adorian Doran
9464667323 client/quick search results: remove deprecated styles 2025-08-29 17:12:52 +03:00
Adorian Doran
4d82f2f22d client/quick search results: tweak footer divider margins 2025-08-29 17:11:10 +03:00
Adorian Doran
e3d28e703f client/quick search results: tweak snippet background color 2025-08-29 17:01:08 +03:00
Adorian Doran
5f39a314b5 client/quick search results: fix overflowing snippets 2025-08-29 16:58:13 +03:00
Adorian Doran
43caadc472 client/quick search results: refactor the item delimiter line 2025-08-29 16:57:02 +03:00
69 changed files with 1941 additions and 600 deletions

40
.github/instructions/nx.instructions.md vendored Normal file
View File

@@ -0,0 +1,40 @@
---
applyTo: '**'
---
// This file is automatically generated by Nx Console
You are in an nx workspace using Nx 21.3.9 and pnpm as the package manager.
You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user:
# General Guidelines
- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture
- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration
- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors
- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool
# Generation Guidelines
If the user wants to generate something, use the following flow:
- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable
- get the available generators using the 'nx_generators' tool
- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them
- get generator details using the 'nx_generator_schema' tool
- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure
- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic
- open the generator UI using the 'nx_open_generate_ui' tool
- wait for the user to finish the generator
- read the generator log file using the 'nx_read_generator_log' tool
- use the information provided in the log file to answer the user's question or continue with what they were doing
# Running Tasks Guidelines
If the user wants help with tasks or commands (which include keywords like "test", "build", "lint", or other similar actions), use the following flow:
- Use the 'nx_current_running_tasks_details' tool to get the list of tasks (this can include tasks that were completed, stopped or failed).
- If there are any tasks, ask the user if they would like help with a specific task then use the 'nx_current_running_task_output' tool to get the terminal output for that task/command
- Use the terminal output from 'nx_current_running_task_output' to see what's wrong and help the user fix their problem. Use the appropriate tools if necessary
- If the user would like to rerun the task or command, always use `nx run <taskId>` to rerun in the terminal. This will ensure that the task will run in the nx context and will be run the same way it originally executed
- If the task was marked as "continuous" do not offer to rerun the task. This task is already running and the user can see the output in the terminal. You can use 'nx_current_running_task_output' to get the output of the task to verify the output.

View File

@@ -0,0 +1,453 @@
#!/usr/bin/env tsx
import * as fs from 'fs/promises';
import * as path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import { Dirent } from 'fs';
const execAsync = promisify(exec);
// Configuration
const FILE_EXTENSIONS = ['.md', '.png', '.jpg', '.jpeg', '.gif', '.svg'] as const;
const README_PATTERN = /^README(?:[-.](.+))?\.md$/;
interface SyncConfig {
mainRepoPath: string;
wikiPath: string;
docsPath: string;
}
/**
* Convert markdown to GitHub Wiki format
* - Images: ![](image.png) → [[image.png]]
* - Links: [text](page.md) → [[text|page]]
*/
async function convertToWikiFormat(wikiDir: string): Promise<void> {
console.log('Converting to GitHub Wiki format...');
const mdFiles = await findFiles(wikiDir, ['.md']);
let convertedCount = 0;
for (const file of mdFiles) {
let content = await fs.readFile(file, 'utf-8');
const originalContent = content;
// Convert image references to wiki format
// ![alt](image.png) → [[image.png]]
content = content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
// Skip external URLs
if (src.startsWith('http://') || src.startsWith('https://')) {
return match;
}
// Decode URL encoding
let imagePath = src;
if (src.includes('%')) {
try {
imagePath = decodeURIComponent(src);
} catch {
imagePath = src;
}
}
// Extract just the filename for wiki syntax
const filename = path.basename(imagePath);
// Use wiki syntax for images
// If alt text exists, add it after pipe
if (alt && alt.trim()) {
return `[[${filename}|alt=${alt}]]`;
} else {
return `[[${filename}]]`;
}
});
// Convert internal markdown links to wiki format
// [text](../path/to/Page.md) → [[text|Page]]
content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, href) => {
// Skip external URLs, anchors, and images
if (href.startsWith('http://') ||
href.startsWith('https://') ||
href.startsWith('#') ||
href.match(/\.(png|jpg|jpeg|gif|svg)$/i)) {
return match;
}
// Check if it's a markdown file link
if (href.endsWith('.md') || href.includes('.md#')) {
// Decode URL encoding
let decodedHref = href;
if (href.includes('%')) {
try {
decodedHref = decodeURIComponent(href);
} catch {
decodedHref = href;
}
}
// Extract page name without extension and path
let pageName = decodedHref
.replace(/\.md(#.*)?$/, '') // Remove .md and anchor
.split('/') // Split by path
.pop() || ''; // Get last part (filename)
// Convert spaces to hyphens (GitHub wiki convention)
pageName = pageName.replace(/ /g, '-');
// Use wiki link syntax
if (text === pageName || text === pageName.replace(/-/g, ' ')) {
return `[[${pageName}]]`;
} else {
return `[[${text}|${pageName}]]`;
}
}
// For other internal links, just decode URL encoding
if (href.includes('%') && !href.startsWith('http')) {
try {
const decodedHref = decodeURIComponent(href);
return `[${text}](${decodedHref})`;
} catch {
return match;
}
}
return match;
});
// Save if modified
if (content !== originalContent) {
await fs.writeFile(file, content, 'utf-8');
const relativePath = path.relative(wikiDir, file);
console.log(` Converted: ${relativePath}`);
convertedCount++;
}
}
if (convertedCount > 0) {
console.log(`Converted ${convertedCount} files to wiki format`);
} else {
console.log('No files needed conversion');
}
}
/**
* Recursively find all files matching the given extensions
*/
async function findFiles(dir: string, extensions: readonly string[]): Promise<string[]> {
const files: string[] = [];
async function walk(currentDir: string): Promise<void> {
const entries: Dirent[] = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
if (extensions.includes(ext)) {
files.push(fullPath);
}
}
}
}
await walk(dir);
return files;
}
/**
* Get all files in a directory recursively
*/
async function getAllFiles(dir: string): Promise<Set<string>> {
const files = new Set<string>();
async function walk(currentDir: string): Promise<void> {
try {
const entries = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
const relativePath = path.relative(dir, fullPath);
// Skip .git directory
if (entry.name === '.git' || relativePath.startsWith('.git')) continue;
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.isFile()) {
files.add(relativePath);
}
}
} catch (error) {
// Directory might not exist yet
if ((error as any).code !== 'ENOENT') {
throw error;
}
}
}
await walk(dir);
return files;
}
/**
* Flatten directory structure - move all files to root
* GitHub Wiki prefers flat structure
*/
async function flattenStructure(wikiDir: string): Promise<void> {
console.log('Flattening directory structure for wiki...');
const allFiles = await getAllFiles(wikiDir);
let movedCount = 0;
for (const file of allFiles) {
// Skip if already at root
if (!file.includes('/')) continue;
const oldPath = path.join(wikiDir, file);
const basename = path.basename(file);
// Create unique name if file already exists at root
let newName = basename;
let counter = 1;
while (await fileExists(path.join(wikiDir, newName))) {
const ext = path.extname(basename);
const nameWithoutExt = basename.slice(0, -ext.length);
newName = `${nameWithoutExt}-${counter}${ext}`;
counter++;
}
const newPath = path.join(wikiDir, newName);
// Move file to root
await fs.rename(oldPath, newPath);
console.log(` Moved: ${file}${newName}`);
movedCount++;
}
if (movedCount > 0) {
console.log(`Moved ${movedCount} files to root`);
// Clean up empty directories
await cleanEmptyDirectories(wikiDir);
}
}
async function fileExists(path: string): Promise<boolean> {
try {
await fs.access(path);
return true;
} catch {
return false;
}
}
/**
* Remove empty directories recursively
*/
async function cleanEmptyDirectories(dir: string): Promise<void> {
const allDirs = await getAllDirectories(dir);
for (const subDir of allDirs) {
try {
const entries = await fs.readdir(subDir);
if (entries.length === 0 || (entries.length === 1 && entries[0] === '.git')) {
await fs.rmdir(subDir);
}
} catch {
// Ignore errors
}
}
}
/**
* Get all directories recursively
*/
async function getAllDirectories(dir: string): Promise<string[]> {
const dirs: string[] = [];
async function walk(currentDir: string): Promise<void> {
try {
const entries = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && entry.name !== '.git') {
const fullPath = path.join(currentDir, entry.name);
dirs.push(fullPath);
await walk(fullPath);
}
}
} catch {
// Ignore errors
}
}
await walk(dir);
return dirs.sort((a, b) => b.length - a.length); // Sort longest first for cleanup
}
/**
* Sync files from source to wiki
*/
async function syncFiles(sourceDir: string, wikiDir: string): Promise<void> {
console.log('Syncing files to wiki...');
// Get all valid source files
const sourceFiles = await findFiles(sourceDir, FILE_EXTENSIONS);
const sourceRelativePaths = new Set<string>();
// Copy all source files
console.log(`Found ${sourceFiles.length} files to sync`);
for (const file of sourceFiles) {
const relativePath = path.relative(sourceDir, file);
sourceRelativePaths.add(relativePath);
const targetPath = path.join(wikiDir, relativePath);
const targetDir = path.dirname(targetPath);
// Create directory structure
await fs.mkdir(targetDir, { recursive: true });
// Copy file
await fs.copyFile(file, targetPath);
}
// Remove orphaned files
const wikiFiles = await getAllFiles(wikiDir);
for (const wikiFile of wikiFiles) {
if (!sourceRelativePaths.has(wikiFile) && !wikiFile.startsWith('Home')) {
const fullPath = path.join(wikiDir, wikiFile);
await fs.unlink(fullPath);
}
}
}
/**
* Copy root README.md to wiki as Home.md if it exists
*/
async function copyRootReadme(mainRepoPath: string, wikiPath: string): Promise<void> {
const rootReadmePath = path.join(mainRepoPath, 'README.md');
const wikiHomePath = path.join(wikiPath, 'Home.md');
try {
await fs.access(rootReadmePath);
await fs.copyFile(rootReadmePath, wikiHomePath);
console.log(' Copied root README.md as Home.md');
} catch (error) {
console.log(' No root README.md found to use as Home page');
}
}
/**
* Rename README files to wiki-compatible names
*/
async function renameReadmeFiles(wikiDir: string): Promise<void> {
console.log('Converting README files for wiki compatibility...');
const files = await fs.readdir(wikiDir);
for (const file of files) {
const match = file.match(README_PATTERN);
if (match) {
const oldPath = path.join(wikiDir, file);
let newName: string;
if (match[1]) {
// Language-specific README
newName = `Home-${match[1]}.md`;
} else {
// Main README
newName = 'Home.md';
}
const newPath = path.join(wikiDir, newName);
await fs.rename(oldPath, newPath);
console.log(` Renamed: ${file}${newName}`);
}
}
}
/**
* Check if there are any changes in the wiki
*/
async function hasChanges(wikiDir: string): Promise<boolean> {
try {
const { stdout } = await execAsync('git status --porcelain', { cwd: wikiDir });
return stdout.trim().length > 0;
} catch (error) {
console.error('Error checking git status:', error);
return false;
}
}
/**
* Get configuration from environment variables
*/
function getConfig(): SyncConfig {
const mainRepoPath = process.env.MAIN_REPO_PATH || 'main-repo';
const wikiPath = process.env.WIKI_PATH || 'wiki';
const docsPath = path.join(mainRepoPath, 'docs');
return { mainRepoPath, wikiPath, docsPath };
}
/**
* Main sync function
*/
async function syncDocsToWiki(): Promise<void> {
const config = getConfig();
const flattenWiki = process.env.FLATTEN_WIKI === 'true';
console.log('Starting documentation sync to wiki...');
console.log(`Source: ${config.docsPath}`);
console.log(`Target: ${config.wikiPath}`);
console.log(`Flatten structure: ${flattenWiki}`);
try {
// Verify paths exist
await fs.access(config.docsPath);
await fs.access(config.wikiPath);
// Sync files
await syncFiles(config.docsPath, config.wikiPath);
// Copy root README.md as Home.md
await copyRootReadme(config.mainRepoPath, config.wikiPath);
// Convert to wiki format
await convertToWikiFormat(config.wikiPath);
// Optionally flatten directory structure
if (flattenWiki) {
await flattenStructure(config.wikiPath);
}
// Rename README files to wiki-compatible names
await renameReadmeFiles(config.wikiPath);
// Check for changes
const changed = await hasChanges(config.wikiPath);
if (changed) {
console.log('\nChanges detected in wiki');
process.stdout.write('::set-output name=changes::true\n');
} else {
console.log('\nNo changes detected in wiki');
process.stdout.write('::set-output name=changes::false\n');
}
console.log('Sync completed successfully!');
} catch (error) {
console.error('Error during sync:', error);
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
syncDocsToWiki();
}
export { syncDocsToWiki };

437
.github/scripts/sync-docs-to-wiki.ts vendored Executable file
View File

@@ -0,0 +1,437 @@
#!/usr/bin/env tsx
import * as fs from 'fs/promises';
import * as path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import { Dirent } from 'fs';
const execAsync = promisify(exec);
// Configuration
const FILE_EXTENSIONS = ['.md', '.png', '.jpg', '.jpeg', '.gif', '.svg'] as const;
const README_PATTERN = /^README(?:[-.](.+))?\.md$/;
interface SyncConfig {
mainRepoPath: string;
wikiPath: string;
docsPath: string;
}
/**
* Recursively find all files matching the given extensions
*/
async function findFiles(dir: string, extensions: readonly string[]): Promise<string[]> {
const files: string[] = [];
async function walk(currentDir: string): Promise<void> {
const entries: Dirent[] = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
if (extensions.includes(ext)) {
files.push(fullPath);
}
}
}
}
await walk(dir);
return files;
}
/**
* Get all files in a directory recursively
*/
async function getAllFiles(dir: string): Promise<Set<string>> {
const files = new Set<string>();
async function walk(currentDir: string): Promise<void> {
try {
const entries = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
const relativePath = path.relative(dir, fullPath);
// Skip .git directory
if (entry.name === '.git' || relativePath.startsWith('.git')) continue;
if (entry.isDirectory()) {
await walk(fullPath);
} else if (entry.isFile()) {
files.add(relativePath);
}
}
} catch (error) {
// Directory might not exist yet
if ((error as any).code !== 'ENOENT') {
throw error;
}
}
}
await walk(dir);
return files;
}
/**
* Sync files from source to wiki, preserving directory structure and removing orphaned files
*/
async function syncFiles(sourceDir: string, wikiDir: string): Promise<void> {
console.log('Analyzing files to sync...');
// Get all valid source files
const sourceFiles = await findFiles(sourceDir, FILE_EXTENSIONS);
const sourceRelativePaths = new Set<string>();
// Copy all source files and track their paths
console.log(`Found ${sourceFiles.length} files to sync`);
let copiedCount = 0;
let skippedCount = 0;
for (const file of sourceFiles) {
const relativePath = path.relative(sourceDir, file);
sourceRelativePaths.add(relativePath);
const targetPath = path.join(wikiDir, relativePath);
const targetDir = path.dirname(targetPath);
// Create directory structure
await fs.mkdir(targetDir, { recursive: true });
// Check if file needs updating (compare modification times)
let needsCopy = true;
try {
const sourceStat = await fs.stat(file);
const targetStat = await fs.stat(targetPath);
// Only copy if source is newer or sizes differ
needsCopy = sourceStat.mtime > targetStat.mtime || sourceStat.size !== targetStat.size;
} catch {
// Target doesn't exist, needs copy
needsCopy = true;
}
if (needsCopy) {
await fs.copyFile(file, targetPath);
console.log(` Updated: ${relativePath}`);
copiedCount++;
} else {
skippedCount++;
}
}
console.log(`Updated ${copiedCount} files, ${skippedCount} unchanged`);
// Find and remove files that don't exist in source
console.log('Checking for orphaned files in wiki...');
const wikiFiles = await getAllFiles(wikiDir);
let removedCount = 0;
for (const wikiFile of wikiFiles) {
// Check if this file should exist (either as-is or will be renamed)
let shouldExist = sourceRelativePaths.has(wikiFile);
// Special handling for Home files that will be created from READMEs
if (wikiFile.startsWith('Home')) {
const readmeVariant1 = wikiFile.replace(/^Home(-.*)?\.md$/, 'README$1.md');
const readmeVariant2 = wikiFile.replace(/^Home-(.+)\.md$/, 'README.$1.md');
shouldExist = sourceRelativePaths.has(readmeVariant1) || sourceRelativePaths.has(readmeVariant2) || sourceRelativePaths.has('README.md');
}
if (!shouldExist) {
const fullPath = path.join(wikiDir, wikiFile);
await fs.unlink(fullPath);
console.log(` Removed: ${wikiFile}`);
removedCount++;
}
}
if (removedCount > 0) {
console.log(`Removed ${removedCount} orphaned files`);
// Clean up empty directories
await cleanEmptyDirectories(wikiDir);
}
}
/**
* Remove empty directories recursively
*/
async function cleanEmptyDirectories(dir: string): Promise<void> {
async function removeEmptyDirs(currentDir: string): Promise<boolean> {
if (currentDir === dir) return false; // Don't remove root
try {
const entries = await fs.readdir(currentDir, { withFileTypes: true });
// Skip .git directory
const filteredEntries = entries.filter(e => e.name !== '.git');
if (filteredEntries.length === 0) {
await fs.rmdir(currentDir);
return true;
}
// Check subdirectories
for (const entry of filteredEntries) {
if (entry.isDirectory()) {
const subDir = path.join(currentDir, entry.name);
await removeEmptyDirs(subDir);
}
}
// Check again after cleaning subdirectories
const remainingEntries = await fs.readdir(currentDir);
const filteredRemaining = remainingEntries.filter(e => e !== '.git');
if (filteredRemaining.length === 0 && currentDir !== dir) {
await fs.rmdir(currentDir);
return true;
}
return false;
} catch (error) {
return false;
}
}
// Get all directories and process them
const allDirs = await getAllDirectories(dir);
for (const subDir of allDirs) {
await removeEmptyDirs(subDir);
}
}
/**
* Get all directories recursively
*/
async function getAllDirectories(dir: string): Promise<string[]> {
const dirs: string[] = [];
async function walk(currentDir: string): Promise<void> {
try {
const entries = await fs.readdir(currentDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && entry.name !== '.git') {
const fullPath = path.join(currentDir, entry.name);
dirs.push(fullPath);
await walk(fullPath);
}
}
} catch {
// Ignore errors
}
}
await walk(dir);
return dirs.sort((a, b) => b.length - a.length); // Sort longest first for cleanup
}
/**
* Fix references in markdown files for wiki compatibility
*
* Issues fixed:
* 1. URL-encoded image references (spaces as %20) need to match actual filenames
* 2. Internal markdown links need to be converted to wiki syntax
* 3. Images can optionally use wiki syntax [[image.png]] for better compatibility
*/
async function fixImageReferences(wikiDir: string): Promise<void> {
console.log('Fixing references for GitHub Wiki compatibility...');
const mdFiles = await findFiles(wikiDir, ['.md']);
let fixedCount = 0;
for (const file of mdFiles) {
let content = await fs.readFile(file, 'utf-8');
let modified = false;
const originalContent = content;
// Step 1: Fix URL-encoded image references
// Convert ![](Name%20With%20Spaces.png) to ![](Name With Spaces.png)
content = content.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => {
// Skip external URLs
if (src.startsWith('http://') || src.startsWith('https://')) {
return match;
}
// Decode URL encoding if present
if (src.includes('%')) {
try {
const decodedSrc = decodeURIComponent(src);
return `![${alt}](${decodedSrc})`;
} catch {
return match;
}
}
return match;
});
// Step 2: Fix internal links - decode URL encoding but keep standard markdown format
// GitHub Wiki actually supports standard markdown links with relative paths
content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, href) => {
// Skip external URLs, anchors, and images
if (href.startsWith('http://') ||
href.startsWith('https://') ||
href.startsWith('#') ||
href.match(/\.(png|jpg|jpeg|gif|svg)$/i)) {
return match;
}
// Decode URL encoding for all internal links
if (href.includes('%')) {
try {
const decodedHref = decodeURIComponent(href);
return `[${text}](${decodedHref})`;
} catch {
return match;
}
}
return match;
});
// Check if content was modified
if (content !== originalContent) {
modified = true;
await fs.writeFile(file, content, 'utf-8');
const relativePath = path.relative(wikiDir, file);
console.log(` Fixed references in: ${relativePath}`);
fixedCount++;
}
}
if (fixedCount > 0) {
console.log(`Fixed references in ${fixedCount} files`);
} else {
console.log('No references needed fixing');
}
}
/**
* Rename README files to wiki-compatible names
*/
async function renameReadmeFiles(wikiDir: string): Promise<void> {
console.log('Converting README files for wiki compatibility...');
const files = await fs.readdir(wikiDir);
for (const file of files) {
const match = file.match(README_PATTERN);
if (match) {
const oldPath = path.join(wikiDir, file);
let newName: string;
if (match[1]) {
// Language-specific README (e.g., README-ZH_CN.md or README.es.md)
newName = `Home-${match[1]}.md`;
} else {
// Main README
newName = 'Home.md';
}
const newPath = path.join(wikiDir, newName);
await fs.rename(oldPath, newPath);
console.log(` Renamed: ${file}${newName}`);
}
}
}
/**
* Check if there are any changes in the wiki
*/
async function hasChanges(wikiDir: string): Promise<boolean> {
try {
const { stdout } = await execAsync('git status --porcelain', { cwd: wikiDir });
return stdout.trim().length > 0;
} catch (error) {
console.error('Error checking git status:', error);
return false;
}
}
/**
* Copy root README.md to wiki as Home.md if it exists
*/
async function copyRootReadme(mainRepoPath: string, wikiPath: string): Promise<void> {
const rootReadmePath = path.join(mainRepoPath, 'README.md');
const wikiHomePath = path.join(wikiPath, 'Home.md');
try {
await fs.access(rootReadmePath);
await fs.copyFile(rootReadmePath, wikiHomePath);
console.log(' Copied root README.md as Home.md');
} catch (error) {
// Root README doesn't exist or can't be accessed
console.log(' No root README.md found to use as Home page');
}
}
/**
* Get configuration from environment variables
*/
function getConfig(): SyncConfig {
const mainRepoPath = process.env.MAIN_REPO_PATH || 'main-repo';
const wikiPath = process.env.WIKI_PATH || 'wiki';
const docsPath = path.join(mainRepoPath, 'docs');
return { mainRepoPath, wikiPath, docsPath };
}
/**
* Main sync function
*/
async function syncDocsToWiki(): Promise<void> {
const config = getConfig();
console.log('Starting documentation sync to wiki...');
console.log(`Source: ${config.docsPath}`);
console.log(`Target: ${config.wikiPath}`);
try {
// Verify paths exist
await fs.access(config.docsPath);
await fs.access(config.wikiPath);
// Sync files (copy new/updated, remove orphaned)
await syncFiles(config.docsPath, config.wikiPath);
// Copy root README.md as Home.md
await copyRootReadme(config.mainRepoPath, config.wikiPath);
// Fix image and link references for wiki compatibility
await fixImageReferences(config.wikiPath);
// Rename README files to wiki-compatible names
await renameReadmeFiles(config.wikiPath);
// Check for changes
const changed = await hasChanges(config.wikiPath);
if (changed) {
console.log('\nChanges detected in wiki');
// GitHub Actions output format
process.stdout.write('::set-output name=changes::true\n');
} else {
console.log('\nNo changes detected in wiki');
process.stdout.write('::set-output name=changes::false\n');
}
console.log('Sync completed successfully!');
} catch (error) {
console.error('Error during sync:', error);
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
syncDocsToWiki();
}
export { syncDocsToWiki };

67
.github/workflows/sync-docs-to-wiki.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Sync Docs to Wiki
on:
push:
branches:
- main
paths:
- 'docs/**'
workflow_dispatch: # Allow manual triggering
permissions:
contents: read # Read access to repository contents
# Note: Writing to wiki requires a PAT or GITHUB_TOKEN with additional permissions
# The default GITHUB_TOKEN cannot write to wikis, so we need to:
# 1. Create a Personal Access Token (PAT) with 'repo' scope
# 2. Add it as a repository secret named WIKI_TOKEN
jobs:
sync-wiki:
runs-on: ubuntu-latest
steps:
- name: Checkout main repository
uses: actions/checkout@v4
with:
path: main-repo
- name: Checkout wiki repository
uses: actions/checkout@v4
with:
repository: TriliumNext/Trilium.wiki
path: wiki
token: ${{ secrets.WIKI_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install tsx for TypeScript execution
run: npm install -g tsx
- name: Setup Git
run: |
git config --global user.email "action@github.com"
git config --global user.name "GitHub Action"
- name: Sync documentation to wiki
id: sync
run: |
tsx main-repo/.github/scripts/sync-docs-to-wiki.ts
env:
MAIN_REPO_PATH: main-repo
WIKI_PATH: wiki
- name: Commit and push changes
if: contains(steps.sync.outputs.changes, 'true')
run: |
cd wiki
git add .
git commit -m "Sync documentation from main repository
Source commit: ${{ github.sha }}
Triggered by: ${{ github.event.head_commit.message }}"
git push
env:
GITHUB_TOKEN: ${{ secrets.WIKI_TOKEN }}

2
.gitignore vendored
View File

@@ -1,7 +1,5 @@
# See https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
.github/instructions/nx.instructions.md
# compiled output
dist
tmp

2
.nvmrc
View File

@@ -1 +1 @@
22.18.0
22.19.0

8
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"servers": {
"nx-mcp": {
"type": "http",
"url": "http://localhost:9461/mcp"
}
}
}

View File

@@ -36,7 +36,7 @@
},
"devDependencies": {
"@playwright/test": "1.55.0",
"@stylistic/eslint-plugin": "5.2.3",
"@stylistic/eslint-plugin": "5.3.1",
"@types/express": "5.0.3",
"@types/node": "22.18.0",
"@types/yargs": "17.0.33",
@@ -49,7 +49,7 @@
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"tslib": "2.8.1",
"typedoc": "0.28.11",
"typedoc": "0.28.12",
"typedoc-plugin-missing-exports": "4.1.0"
},
"optionalDependencies": {

View File

@@ -30,7 +30,7 @@
"autocomplete.js": "0.38.1",
"bootstrap": "5.3.8",
"boxicons": "2.1.4",
"dayjs": "1.11.14",
"dayjs": "1.11.18",
"dayjs-plugin-utc": "0.1.2",
"debounce": "2.2.0",
"draggabilly": "3.0.0",
@@ -52,7 +52,7 @@
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.1",
"react-i18next": "15.7.2",
"react-i18next": "15.7.3",
"split.js": "1.6.5",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
@@ -76,7 +76,9 @@
"name": "client",
"targets": {
"serve": {
"dependsOn": []
"dependsOn": [
"^build"
]
},
"circular-deps": {
"command": "pnpx dpdm -T {projectRoot}/src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"

View File

@@ -1463,7 +1463,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
cursor: pointer;
border: none;
color: var(--launcher-pane-text-color);
background-color: var(--launcher-pane-background-color);
background: transparent;
flex-shrink: 0;
}
@@ -2366,3 +2366,12 @@ footer.webview-footer button {
content: "\ec24";
transform: rotate(180deg);
}
/* CK Edito */
/* Insert text snippet: limit the width of the listed items to avoid overly long names */
:root body.desktop div.ck-template-form li.ck-list__item .ck-template-form__text-part > span {
max-width: 25vw;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@@ -18,7 +18,7 @@
--main-text-color: #ccc;
--main-border-color: #454545;
--subtle-border-color: #313131;
--dropdown-border-color: #292929;
--dropdown-border-color: #404040;
--dropdown-shadow-opacity: 0.6;
--dropdown-item-icon-destructive-color: #de6e5b;
--disabled-tooltip-icon-color: #7fd2ef;

View File

@@ -115,7 +115,7 @@
--quick-search-focus-border: #00000029;
--quick-search-focus-background: #ffffff80;
--quick-search-focus-color: #000;
--quick-search-result-content-background: #00000017;
--quick-search-result-content-background: #0000000f;
--quick-search-result-highlight-color: #c65050;
--left-pane-collapsed-border-color: #0000000d;

View File

@@ -329,6 +329,8 @@ body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
#toast-container .toast .toast-body {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
/*

View File

@@ -5,7 +5,8 @@
button.btn.btn-primary,
button.btn.btn-secondary,
button.btn.btn-sm:not(.select-button),
button.btn.btn-success {
button.btn.btn-success,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -21,7 +22,8 @@ button.btn.btn-success {
button.btn.btn-primary:hover,
button.btn.btn-secondary:hover,
button.btn.btn-sm:not(.select-button):hover,
button.btn.btn-success:hover {
button.btn.btn-success:hover,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):hover {
background: var(--cmd-button-hover-background-color);
color: var(--cmd-button-hover-text-color);
}
@@ -29,7 +31,8 @@ button.btn.btn-success:hover {
button.btn.btn-primary:active,
button.btn.btn-secondary:active,
button.btn.btn-sm:not(.select-button):active,
button.btn.btn-success:active {
button.btn.btn-success:active,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):active {
opacity: 0.85;
box-shadow: unset;
background: var(--cmd-button-background-color) !important;
@@ -40,14 +43,16 @@ button.btn.btn-success:active {
button.btn.btn-primary:disabled,
button.btn.btn-secondary:disabled,
button.btn.btn-sm:not(.select-button):disabled,
button.btn.btn-success:disabled {
button.btn.btn-success:disabled,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text.ck-disabled {
opacity: var(--cmd-button-disabled-opacity);
}
button.btn.btn-primary:focus-visible,
button.btn.btn-secondary:focus-visible,
button.btn.btn-sm:not(.select-button):focus-visible,
button.btn.btn-success:focus-visible {
button.btn.btn-success:focus-visible,
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .ck-button-replaceall, .ck-button-replace).ck-button_with-text:not(.ck-disabled):focus-visible {
outline: 2px solid var(--input-focus-outline-color);
}
@@ -149,8 +154,11 @@ input[type="password"],
input[type="date"],
input[type="time"],
input[type="datetime-local"],
:root input.ck.ck-input-text,
:root input.ck.ck-input-number,
textarea.form-control,
textarea,
:root textarea.ck.ck-textarea,
.tn-input-field {
outline: 3px solid transparent;
outline-offset: 6px;
@@ -167,8 +175,11 @@ input[type="password"]:hover,
input[type="date"]:hover,
input[type="time"]:hover,
input[type="datetime-local"]:hover,
:root input.ck.ck-input-text:not([readonly="true"]):hover,
:root input.ck.ck-input-number:not([readonly="true"]):hover,
textarea.form-control:hover,
textarea:hover,
:root textarea.ck.ck-textarea:hover,
.tn-input-field:hover {
background: var(--input-hover-background);
color: var(--input-hover-color);
@@ -181,8 +192,11 @@ input[type="password"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="datetime-local"]:focus,
:root input.ck.ck-input-text:focus,
:root input.ck.ck-input-number:focus,
textarea.form-control:focus,
textarea:focus,
:root textarea.ck.ck-textarea:focus,
.tn-input-field:focus,
.tn-input-field:focus-within {
box-shadow: unset;

View File

@@ -4,6 +4,7 @@
:root {
--ck-font-face: var(--main-font-family);
--ck-input-label-height: 1.5em;
}
/*
@@ -307,6 +308,11 @@
fill: black !important;
}
/* Hex color input box prefix */
:root .ck.ck-color-selector .ck-color-picker__hash-view {
margin-top: var(--ck-input-label-height);
}
/* Numbered list */
:root .ck.ck-list-properties_with-numbered-properties .ck.ck-list-styles-list {
@@ -363,19 +369,86 @@
color: var(--accent);
}
/* Action buttons */
/* Text snippet dropdown */
:root .ck-link-actions button.ck-button,
:root .ck-link-form button.ck-button {
--ck-border-radius: 6px;
background: transparent;
box-shadow: unset;
div.ck-template-form {
padding: 8px;
}
:root .ck-link-actions button.ck-button:hover,
:root .ck-link-form button.ck-button:hover {
background: var(--hover-item-background-color);
div.ck-template-form .ck-labeled-field-view {
margin-bottom: 8px;
}
/* Template item */
:root div.ck-template-form li.ck-list__item button.ck-template-button {
padding: 4px 8px;
}
/* Template icon */
:root .ck-template-form .ck-button__icon {
--ck-spacing-medium: 2px;
}
:root div.ck-template-form .note-icon {
color: var(--menu-item-icon-color);
}
/* Template name */
div.ck-template-form .ck-template-form__text-part {
color: var(--hover-item-text-color);
font-size: .9rem;
}
div.ck-template-form .ck-template-form__text-part mark {
background: unset;
color: var(--quick-search-result-highlight-color);
font-weight: bold;
}
/* Template description */
:root div.ck-template-form .ck-template-form__description {
opacity: .5;
font-size: .9em;
}
/* Messages */
div.ck-template-form .ck-search__info > span {
line-height: initial;
color: var(--muted-text-color);
}
div.ck-template-form .ck-search__info span:nth-child(2) {
display: block;
opacity: .5;
margin-top: 8px;
font-size: .9em;
}
/* Link dropdown */
:root .ck.ck-form.ck-link-form ul.ck-link-form__providers-list {
border-top: none;
}
/* Math popup */
.ck-math-form .ck-labeled-field-view {
--ck-input-label-height: 0;
margin-inline-end: 8px;
}
/* Emoji dropdown */
.ck-emoji-picker-form .ck-emoji__search .ck-button_with-text:not(.ck-list-item-button) {
margin-top: var(--ck-input-label-height);
}
/* Find and replace dialog */
.ck-find-and-replace-form .ck-find-and-replace-form__inputs button {
margin-top: var(--ck-input-label-height);
}
/* Mention list (the autocompletion list for emojis, labels and relations) */
@@ -392,6 +465,58 @@
background: transparent;
}
/*
* FORMS
*/
/*
* Buttons
*/
button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel).ck-button_with-text {
--ck-color-text: var(--cmd-button-text-color);
min-width: 60px;
font-weight: 500;
}
/*
* Text boxes
*/
.ck.ck-labeled-field-view {
padding-top: var(--ck-input-label-height) !important; /* Create space for the label */
}
.ck.ck-labeled-field-view > .ck.ck-labeled-field-view__input-wrapper > label.ck.ck-label {
/* Move the label above the text box regardless of the text box state */
transform: translate(0, calc(-.2em - var(--ck-input-label-height))) !important;
padding-left: 0 !important;
background: transparent;
font-size: .85em;
font-weight: 600;
}
:root input.ck.ck-input-text[readonly="true"] {
cursor: not-allowed;
background: var(--input-background-color);
}
/* Forms */
:root .ck.ck-form__row.ck-form__row_with-submit > :not(:first-child) {
margin-inline-start: 16px;
}
.ck.ck-form__row_with-submit button {
margin-top: var(--ck-input-label-height);
}
.ck.ck-form__header {
border-bottom: none;
}
/*
* EDITOR'S CONTENT
*/

View File

@@ -59,8 +59,7 @@ body.background-effects.platform-win32.layout-vertical {
}
body.background-effects.platform-win32,
body.background-effects.platform-win32 #root-widget,
body.background-effects.platform-win32 #launcher-pane .launcher-button {
body.background-effects.platform-win32 #root-widget {
background: transparent !important;
}
@@ -575,31 +574,20 @@ div.quick-search .search-button.show {
* Quick search results
*/
div.quick-search .dropdown-menu {
--quick-search-item-delimiter-color: transparent;
--menu-item-icon-vert-offset: -.065em;
}
/* Item */
.quick-search .dropdown-menu *.dropdown-item {
padding: 8px 12px !important;
}
/* Note icon */
.quick-search .dropdown-menu .dropdown-item > .bx {
position: relative;
top: 1px;
}
.quick-search .quick-search-item-icon {
vertical-align: text-bottom;
}
/* Note title */
.quick-search .dropdown-menu .dropdown-item > a {
color: var(--menu-text-color);
}
.quick-search .dropdown-menu .dropdown-item > a:hover {
--hover-item-background-color: transparent;
text-decoration: underline;
}
/* Note path */
.quick-search .dropdown-menu small {
display: block;
@@ -622,9 +610,8 @@ div.quick-search .search-button.show {
font-weight: 600;
}
/* Divider line */
.quick-search .dropdown-item::after {
display: none;
.quick-search div.dropdown-divider {
margin: 8px 0;
}
/*

View File

@@ -1404,8 +1404,8 @@
"open-in-popup": "Schnellbearbeitung"
},
"shared_info": {
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}",
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}",
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}.",
"help_link": "Für Hilfe besuche <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"note_types": {
@@ -1484,7 +1484,8 @@
"hoist-this-note-workspace": "Diese Notiz fokussieren (Arbeitsbereich)",
"refresh-saved-search-results": "Gespeicherte Suchergebnisse aktualisieren",
"create-child-note": "Unternotiz anlegen",
"unhoist": "Fokus verlassen"
"unhoist": "Fokus verlassen",
"toggle-sidebar": "Seitenleiste ein-/ausblenden"
},
"title_bar_buttons": {
"window-on-top": "Dieses Fenster immer oben halten"

View File

@@ -13,29 +13,127 @@
"critical-error": {
"title": "Błąd krytyczny",
"message": "Wystąpił krytyczny błąd uniemożliwiający uruchomienie aplikacji:\n\n{{message}}\n\nJest to spowodowane najprawdopodobniej niespodziewanym błędem skryptu. Spróbuj uruchomić aplikację ponownie w trybie bezpiecznym i zaadresuj problem."
},
"widget-error": {
"title": "Nie udało się zainicjować widżetu",
"message-custom": "Niestandardowy widżet z notatki o identyfikatorze \"{{id}}\", i tytule \"{{title}}\" nie mógł zostać zainicjowany z powodu:\n\n{{message}}",
"message-unknown": "Nieznany widżet nie mógł być zainicjowany z powodu:\n\n{{message}}"
},
"bundle-error": {
"title": "Nie udało się załadować niestandardowego skryptu",
"message": "Skrypt z notatki o identyfikatorze \"{{id}}\", tytule \"{{title}}: nie został uruchomiony z powodu:\n\n{{message}}"
}
},
"add_link": {
"add_link": "Dodaj link"
"add_link": "Dodaj link",
"note": "Notatka",
"search_note": "Wyszukaj notatkę po nazwie",
"link_title_arbitrary": "Tytuł linku można dowolnie zmieniać",
"link_title": "Tytuł linku",
"button_add_link": "Dodaj link"
},
"branch_prefix": {
"save": "Zapisz"
"save": "Zapisz",
"edit_branch_prefix": "Edytuj prefiks gałęzi",
"prefix": "Prefiks: ",
"branch_prefix_saved": "Zapisano prefiks gałęzi."
},
"bulk_actions": {
"labels": "Etykiety",
"notes": "Notatki",
"other": "Inne",
"relations": "Powiązania"
"relations": "Powiązania",
"bulk_actions": "Działania zbiorcze",
"include_descendants": "Uwzględnia rozwinięcia wybranych notatek",
"available_actions": "Dostępne działania",
"chosen_actions": "Wybrane działania",
"execute_bulk_actions": "Wykonaj zbiór działań",
"bulk_actions_executed": "Zbiór działań został wykonany prawidłowo.",
"none_yet": "Brak zaznaczonych działań... dodaj działanie poprzez kliknięcie jednej z dostępnych opcji powyżej."
},
"confirm": {
"ok": "OK",
"cancel": "Anuluj"
"cancel": "Anuluj",
"confirmation": "Potwierdzenie",
"are_you_sure_remove_note": "Czy napewno chcesz usunąć notatkę \"{{title}}\" z mapy powiązań? ",
"if_you_dont_check": "Jeśli nie zaznaczysz tej opcji, notatka zostanie usunięta jedynie z mapy powiązań.",
"also_delete_note": "Usuń dodatkowo notatkę"
},
"delete_notes": {
"cancel": "Anuluj",
"close": "Zamknij"
"close": "Zamknij",
"delete_notes_preview": "Usuń podgląd notatek",
"delete_all_clones_description": "Usuń również wszystkie sklonowania (działanie może zostać cofnięte w ostatnich zmianach)",
"erase_notes_description": "Normalne (miękkie) usuwanie zaznacza jedynie notatki jako usunięte i można je przywrócić (w oknie dialogowym ostatnich zmian) przez wyznaczony okres czasu. Zaznaczenie tej opcji spowoduje natychmiastowe usunięcie notatek, bez możliwości ich przywrócenia.",
"erase_notes_warning": "Usuń notatki permanentnie (bez opcji ich przywrócenia), włączając wszystkie kopie. Działanie to wymaga ponownego uruchomienia aplikacji.",
"notes_to_be_deleted": "Następujące notatki zostaną usunięte ({{notesCount}})",
"no_note_to_delete": "Żadne notatki nie zostaną usunięte (jedynie kopie).",
"broken_relations_to_be_deleted": "Następujące powiązania zostaną uszkodzone i usunięte ({{ relationCount}})",
"ok": "OK",
"deleted_relation_text": "Notatka {{- note}} (do usunięcia) jest powiązana przez relację {{- relation}} pochodzącą z {{- source}}."
},
"export": {
"close": "Zamknij"
"close": "Zamknij",
"export_note_title": "Eksportuj notatkę",
"export_type_subtree": "Ta notatka oraz wszystkie podrzędne",
"format_html": "HTML - rekomendowany jako zachowujący całość formatowania",
"format_html_zip": "HTML w archiwum ZIP - rekomendowany jako zachowujący całość formatowania.",
"format_markdown": "Markdown - zachowuje większość formatowania.",
"format_opml": "OPML - format wymiany danych dla outlinerów zawierający tylko tekst. Formatowanie, obrazy i pliki nie są uwzględnione.",
"opml_version_1": "OPML v1.0 - tylko zwykły tekst",
"opml_version_2": "OPML v2.0 - umożliwia również HTML",
"export_type_single": "Tylko ta notatka, bez elementów podrzędnych",
"export": "Eksportuj",
"choose_export_type": "Wybierz najpierw rodzaj pliku do eksportu",
"export_status": "Status eksportu",
"export_in_progress": "Postęp eksportowania: {{progressCount}}",
"export_finished_successfully": "Eksportowanie zakończone.",
"format_pdf": "PDF - w celu drukowania lub udostępniania."
},
"clone_to": {
"clone_notes_to": "Sklonuj notatki do...",
"notes_to_clone": "Notatki do sklonowania",
"search_for_note_by_its_name": "Wyszukaj notatkę po jej nazwie",
"cloned_note_prefix_title": "Sklonowana notatka zostanie wyświetlona w drzewie notatki z podanym prefiksem",
"prefix_optional": "Prefiks (opcjonalne)",
"clone_to_selected_note": "Sklonuj do wybranej notatki",
"no_path_to_clone_to": "Brak ścieżki do sklonowania.",
"note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\""
},
"help": {
"title": "Ściągawka",
"noteNavigation": "Nawigacja po notatkach",
"goUpDown": "przewijanie w górę/w dół w liście notatek",
"collapseExpand": "zwiń/rozwiń zbiór",
"notSet": "niezdefiniowany",
"goBackForwards": "przewijaj do tyłu/do przodu w historii",
"showJumpToNoteDialog": "pokaż <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"przejdź do dialogu</a>",
"scrollToActiveNote": "przewiń do aktywnej notatki",
"jumpToParentNote": "przejdź do głównej notatki",
"collapseWholeTree": "zwiń całe drzewko notatki",
"collapseSubTree": "zwiń gałąź notatki",
"tabShortcuts": "Skóry kart",
"newTabNoteLink": "link notatki otwiera notatkę w nowej karcie",
"newTabWithActivationNoteLink": "link notatki otwiera i aktywuje notatkę w nowej karcie",
"onlyInDesktop": "Tylko na komputerze stacjonarnym (wersja Electron)",
"openEmptyTab": "Otwórz pustą kartę",
"closeActiveTab": "zamknij aktywną kartę",
"activateNextTab": "aktywuj następną kartę",
"activatePreviousTab": "aktywuj poprzednią kartę",
"creatingNotes": "Tworzenie notatek",
"createNoteAfter": "Utwórz nową notatkę obok obecnie aktywnej",
"createNoteInto": "Utwórz nową podnotatkę w obecnie otwartej",
"editBranchPrefix": "edytuj <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefiks</a> aktywnej kopii notatki",
"movingCloningNotes": "Przenoszenie / kopiowanie notatek",
"moveNoteUpDown": "Przenieś notatkę w górę/w dół na liście notatek",
"moveNoteUpHierarchy": "Przenieś notatkę w górę w hierarchii",
"multiSelectNote": "Zaznacz wiele notatek powyżej/poniżej",
"selectAllNotes": "Wybierz wszystkie notatki na obecnym poziomie",
"selectNote": "Wybierz notatkę",
"copyNotes": "skopiuj obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">klonowania</a>)",
"cutNotes": "przytnij obecną notatkę (lub obecną sekcję) do schowka (zastosowanie dla przenoszenia notatek)",
"pasteNotes": "wklej notatkę jako podnotatka w obecnej notatce (rozumiane jako przenieś lub skopiuj, w zależności czy notatka była skopiowana czy wycięta)",
"deleteNotes": "usuń notatkę / gałąź",
"editingNotes": "Edytowanie notatek"
}
}

View File

@@ -994,7 +994,7 @@
"invalid_view_type": "Tipo de visualização inválido '{{type}}'",
"calendar": "Calendário",
"table": "Tabela",
"geo-map": "Geo Map",
"geo-map": "Mapa geográfico",
"board": "Quadro"
},
"edited_notes": {
@@ -1278,7 +1278,7 @@
"handwriting-system-fonts": "Fontes de escrita à mão de sistema",
"serif": "Serifa",
"sans-serif": "Sem Serifa",
"monospace": "Monospace",
"monospace": "Monoespaçado",
"system-default": "Padrão do Sistema",
"note_tree_and_detail_font_sizing": "Note que o tamanho da fonte da árvore e dos detalhes é relativo à configuração principal do tamanho de fonte."
},
@@ -1518,7 +1518,7 @@
"doc": "Documento",
"widget": "Widget",
"confirm-change": "Não é recomentado alterar o tipo da nota quando o conteúdo da nota não está vazio. Quer continuar assim mesmo?",
"geo-map": "Geo Map",
"geo-map": "Mapa geográfico",
"beta-feature": "Beta",
"ai-chat": "Chat IA",
"task-list": "Lista de Tarefas",
@@ -1579,7 +1579,8 @@
"hoist-this-note-workspace": "Fixar esta nota (workspace)",
"refresh-saved-search-results": "Atualizar resultados de pesquisa salvos",
"create-child-note": "Criar nota filha",
"unhoist": "Desafixar"
"unhoist": "Desafixar",
"toggle-sidebar": "Alternar barra lateral"
},
"title_bar_buttons": {
"window-on-top": "Manter Janela no Topo"
@@ -2022,6 +2023,8 @@
"handshake_failed": "Falha no handshake com o servidor de sincronização, erro: {{message}}"
},
"shared_info": {
"help_link": "Para ajuda, visite a <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
"help_link": "Para ajuda, visite a <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>.",
"shared_publicly": "Esta nota é compartilhada publicamente em {{- link}}.",
"shared_locally": "Esta nota é compartilhada localmente em {{- link}}."
}
}

View File

@@ -742,7 +742,8 @@
"save-changes": "Сохранить и применить изменения",
"saved-search-note-refreshed": "Сохраненная поисковая заметка обновлена.",
"refresh-saved-search-results": "Обновить сохраненные результаты поиска",
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве."
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве.",
"toggle-sidebar": "Переключить боковую панель"
},
"quick-search": {
"no-results": "Результаты не найдены",
@@ -1974,8 +1975,8 @@
},
"shared_info": {
"help_link": "Для получения справки посетите <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">вики</a>.",
"shared_locally": "Заметка общедоступна локально в {{- link}}",
"shared_publicly": "Заметка общедоступна публично в {{- link}}"
"shared_locally": "Заметка общедоступна локально в {{- link}}.",
"shared_publicly": "Заметка общедоступна публично в {{- link}}."
},
"note_create": {
"duplicated": "Создан дубль заметки \"{{title}}\"."

View File

@@ -844,7 +844,8 @@
"note_type": "Тип нотатки",
"editable": "Редагув.",
"basic_properties": "Основні Властивості",
"language": "Мова"
"language": "Мова",
"configure_code_notes": "Конфігурація нотатки з кодом..."
},
"book_properties": {
"view_type": "Тип перегляду",
@@ -1586,7 +1587,8 @@
"hoist-this-note-workspace": "Закріпити цю нотатку (робочий простір)",
"refresh-saved-search-results": "Оновити збережені результати пошуку",
"create-child-note": "Створити дочірню нотатку",
"unhoist": "Відкріпити"
"unhoist": "Відкріпити",
"toggle-sidebar": "Перемикання бічної панелі"
},
"title_bar_buttons": {
"window-on-top": "Тримати вікно зверху"
@@ -1909,8 +1911,8 @@
"open-in-popup": "Швидке редагування"
},
"shared_info": {
"shared_publicly": "Ця нотатка опублікована на {{- link}}",
"shared_locally": "Цю нотатку опубліковано локально на {{- link}}",
"shared_publicly": "Ця нотатка опублікована на {{- link}}.",
"shared_locally": "Цю нотатку опубліковано локально на {{- link}}.",
"help_link": "Щоб отримати допомогу, відвідайте <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">вікі</a>."
},
"note_types": {
@@ -2018,5 +2020,11 @@
},
"units": {
"percentage": "%"
},
"ui-performance": {
"title": "Продуктивність",
"enable-motion": "Увімкнути переходи та анімацію",
"enable-shadows": "Увімкнути тіні",
"enable-backdrop-effects": "Увімкнути фонові ефекти для меню, спливаючих вікон та панелей"
}
}

View File

@@ -1,7 +1,10 @@
{
"about": {
"homepage": "Trang chủ:",
"title": "Về Trilium Notes"
"title": "Về Trilium Notes",
"app_version": "Phiên bản:",
"db_version": "Phiên bản DB:",
"sync_version": "Phiên bản liên kết:"
},
"add_link": {
"add_link": "Thêm liên kết",
@@ -26,7 +29,8 @@
"close": "Đóng"
},
"help": {
"other": "Khác"
"other": "Khác",
"notSet": "chưa được đặt"
},
"toast": {
"critical-error": {
@@ -69,12 +73,16 @@
"add_label": {
"add_label": "Thêm nhãn",
"label_name_placeholder": "tên nhãn",
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn"
"help_text_item2": "hoặc thay đổi giá trị của nhãn có sẵn",
"new_value_placeholder": "giá trị mới"
},
"rename_label": {
"rename_label": "Đặt lại tên nhãn"
},
"call_to_action": {
"dismiss": "Bỏ qua"
},
"abstract_search_option": {
"remove_this_search_option": "Xoá lựa chọn tìm kiếm này"
}
}

View File

@@ -1,16 +1,11 @@
import utils from "../../services/utils.js";
import contextMenu from "../../menus/context_menu.js";
import contextMenu, { MenuCommandItem } from "../../menus/context_menu.js";
import treeService from "../../services/tree.js";
import ButtonFromNoteWidget from "./button_from_note.js";
import type FNote from "../../entities/fnote.js";
import type { CommandNames } from "../../components/app_context.js";
import type { WebContents } from "electron";
interface ContextMenuItem {
title: string;
idx: string;
uiIcon: string;
}
import link from "../../services/link.js";
export default class HistoryNavigationButton extends ButtonFromNoteWidget {
private webContents?: WebContents;
@@ -47,24 +42,20 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
return;
}
let items: ContextMenuItem[] = [];
let items: MenuCommandItem<string>[] = [];
const history = this.webContents.navigationHistory;
const activeIndex = history.getActiveIndex();
const history = this.webContents.navigationHistory.getAllEntries();
const activeIndex = this.webContents.navigationHistory.getActiveIndex();
for (const idx in history) {
const url = history[idx];
const parts = url.split("#");
if (parts.length < 2) continue;
const notePathWithTab = parts[1];
const notePath = notePathWithTab.split("-")[0];
const { notePath } = link.parseNavigationStateFromUrl(history[idx].url);
if (!notePath) continue;
const title = await treeService.getNotePathTitle(notePath);
items.push({
title,
idx,
command: idx,
uiIcon:
parseInt(idx) === activeIndex
? "bx bx-radio-circle-marked" // compare with type coercion!
@@ -84,9 +75,10 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget {
x: e.pageX,
y: e.pageY,
items,
selectMenuItemHandler: (item: any) => {
if (item && item.idx && this.webContents) {
this.webContents.goToIndex(item.idx);
selectMenuItemHandler: (item: MenuCommandItem<string>) => {
if (item && item.command && this.webContents) {
const idx = parseInt(item.command, 10);
this.webContents.navigationHistory.goToIndex(idx);
}
}
});

View File

@@ -15,13 +15,15 @@ const TPL = /*html*/`
padding: 10px 10px 10px 0px;
height: 50px;
}
.quick-search button, .quick-search input {
border: 0;
font-size: 100% !important;
}
.quick-search .dropdown-menu {
--quick-search-item-delimiter-color: var(--dropdown-border-color);
max-height: 80vh;
min-width: 400px;
max-width: 720px;
@@ -38,14 +40,14 @@ const TPL = /*html*/`
position: relative;
}
.quick-search .dropdown-item:not(:last-child)::after {
.quick-search .dropdown-item + .dropdown-item::after {
content: '';
position: absolute;
left: 0;
bottom: 0;
top: 0;
width: 100%;
height: 1px;
background: var(--dropdown-border-color);
border-bottom: 1px solid var(--quick-search-item-delimiter-color);
}
.quick-search .dropdown-item:last-child::after {
@@ -92,6 +94,8 @@ const TPL = /*html*/`
background-color: var(--accented-background-color);
color: var(--main-text-color);
font-size: .85em;
overflow: hidden;
text-overflow: ellipsis;
}
/* Search result highlighting */
@@ -106,6 +110,10 @@ const TPL = /*html*/`
margin: 0;
}
.quick-search .bx-loader {
margin-inline-end: 4px;
}
</style>
<div class="input-group-prepend">
@@ -220,7 +228,11 @@ export default class QuickSearchWidget extends BasicWidget {
this.isLoadingMore = false;
this.$dropdownMenu.empty();
this.$dropdownMenu.append(`<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span>${t("quick-search.searching")}</span>`);
this.$dropdownMenu.append(`
<span class="dropdown-item disabled">
<span class="bx bx-loader bx-spin"></span>
${t("quick-search.searching")}
</span>`);
const { searchResultNoteIds, searchResults, error } = await server.get<QuickSearchResponse>(`quick-search/${encodeURIComponent(searchString)}`);

View File

@@ -1,5 +1,6 @@
import utils from "../../../services/utils.js";
import options from "../../../services/options.js";
import IconAlignCenter from "@ckeditor/ckeditor5-icons/theme/icons/align-center.svg?raw";
const TEXT_FORMATTING_GROUP = {
label: "Text formatting",
@@ -77,7 +78,7 @@ export function buildClassicToolbar(multilineToolbar: boolean) {
items: ["imageUpload", "|", "link", "bookmark", "internallink", "includeNote", "|", "specialCharacters", "emoji", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"]
},
"|",
"alignment",
buildAlignmentToolbar(),
"outdent",
"indent",
"|",
@@ -134,7 +135,7 @@ export function buildFloatingToolbar() {
items: ["link", "bookmark", "internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"]
},
"|",
"alignment",
buildAlignmentToolbar(),
"outdent",
"indent",
"|",
@@ -147,3 +148,11 @@ export function buildFloatingToolbar() {
]
};
}
function buildAlignmentToolbar() {
return {
label: "Alignment",
icon: IconAlignCenter,
items: ["alignment:left", "alignment:center", "alignment:right", "|", "alignment:justify"]
};
}

View File

@@ -41,6 +41,11 @@ export default defineConfig(() => ({
] as Plugin[],
resolve: {
alias: [
// Force the use of dist in development mode because upstream ESM is broken (some hybrid between CJS and ESM, will be improved in upcoming versions).
{
find: "@triliumnext/highlightjs",
replacement: resolve(__dirname, "node_modules/@triliumnext/highlightjs/dist")
},
{
find: "react",
replacement: "preact/compat"

View File

@@ -18,7 +18,7 @@
}
},
"devDependencies": {
"dotenv": "17.2.1",
"dotenv": "17.2.2",
"electron": "37.4.0"
}
}

View File

@@ -17,6 +17,6 @@
}
},
"devDependencies": {
"dotenv": "17.2.1"
"dotenv": "17.2.2"
}
}

View File

@@ -1,4 +1,4 @@
TRILIUM_ENV=dev
TRILIUM_DATA_DIR=./apps/server/data
TRILIUM_RESOURCE_DIR=./apps/server/src
TRILIUM_RESOURCE_DIR=./apps/server/dist
TRILIUM_PUBLIC_SERVER=http://localhost:4200

View File

@@ -1,4 +1,4 @@
FROM node:22.18.0-bullseye-slim AS builder
FROM node:22.19.0-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:22.18.0-bullseye-slim
FROM node:22.19.0-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \

View File

@@ -1,4 +1,4 @@
FROM node:22.18.0-alpine AS builder
FROM node:22.19.0-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:22.18.0-alpine
FROM node:22.19.0-alpine
# Install runtime dependencies
RUN apk add --no-cache su-exec shadow

View File

@@ -1,4 +1,4 @@
FROM node:22.18.0-alpine AS builder
FROM node:22.19.0-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:22.18.0-alpine
FROM node:22.19.0-alpine
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@@ -1,4 +1,4 @@
FROM node:22.18.0-bullseye-slim AS builder
FROM node:22.19.0-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:22.18.0-bullseye-slim
FROM node:22.19.0-bullseye-slim
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@@ -39,7 +39,7 @@
"@types/ws": "8.18.1",
"@types/xml2js": "0.4.14",
"express-http-proxy": "2.1.1",
"@anthropic-ai/sdk": "0.60.0",
"@anthropic-ai/sdk": "0.61.0",
"@braintree/sanitize-url": "7.1.1",
"@triliumnext/commons": "workspace:*",
"@triliumnext/express-partial-content": "workspace:*",
@@ -55,7 +55,7 @@
"compression": "1.8.1",
"cookie-parser": "1.4.7",
"csrf-csrf": "3.2.2",
"dayjs": "1.11.14",
"dayjs": "1.11.18",
"debounce": "2.2.0",
"debug": "4.4.1",
"ejs": "3.1.10",
@@ -116,18 +116,20 @@
],
"targets": {
"serve": {
"executor": "nx:run-commands",
"executor": "@nx/js:node",
"dependsOn": [
{
"projects": [
"client"
],
"target": "serve"
}
},
"build-without-client"
],
"continuous": true,
"options": {
"command": "tsx watch --conditions development {projectRoot}/src/main.ts"
"buildTarget": "server:build-without-client:development",
"runBuildTargetDependencies": false
}
},
"serve-nodir": {

View File

@@ -21,6 +21,7 @@ import type AttributeMeta from "../meta/attribute_meta.js";
import type BBranch from "../../becca/entities/bbranch.js";
import type { Response } from "express";
import type { NoteMetaFile } from "../meta/note_meta.js";
import cssContent from "@triliumnext/ckeditor5/content.css";
type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string;
@@ -513,6 +514,8 @@ ${markdownContent}`;
if (!cssMeta.dataFileName) {
return;
}
archive.append(cssContent, { name: cssMeta.dataFileName });
}
try {

View File

@@ -1,3 +1,4 @@
import log from "./log.js";
import path from "path";
import fs from "fs";
@@ -9,7 +10,7 @@ const ELECTRON_APP_ROOT_DIR = path.resolve(RESOURCE_DIR, "../..");
const DB_INIT_DIR = path.resolve(RESOURCE_DIR, "db");
if (!fs.existsSync(DB_INIT_DIR)) {
console.error(`Could not find DB initialization directory: ${DB_INIT_DIR}`);
log.error(`Could not find DB initialization directory: ${DB_INIT_DIR}`);
process.exit(1);
}

View File

@@ -34,6 +34,9 @@
"src/**/*.spec.jsx"
],
"references": [
{
"path": "../../packages/ckeditor5/tsconfig.lib.json"
},
{
"path": "../../packages/turndown-plugin-gfm/tsconfig.lib.json"
},

View File

@@ -3,6 +3,9 @@
"files": [],
"include": [],
"references": [
{
"path": "../../packages/ckeditor5"
},
{
"path": "../../packages/turndown-plugin-gfm"
},

View File

@@ -1,7 +1,6 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"defaultBase": "main",
"useDaemonProcess": false,
"namedInputs": {
"default": [
"{projectRoot}/**/*",

View File

@@ -81,7 +81,7 @@
"url": "https://github.com/TriliumNext/Notes/issues"
},
"homepage": "https://github.com/TriliumNext/Notes#readme",
"packageManager": "pnpm@10.15.0",
"packageManager": "pnpm@10.15.1",
"pnpm": {
"patchedDependencies": {
"@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch",

View File

@@ -11,14 +11,13 @@
"ckeditor5-package-generator"
],
"type": "module",
"source": "src/index.ts",
"main": "dist/index.ts",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./src/index.ts"
"import": "./dist/index.js"
},
"./*": "./dist/*",
"./browser/*": null,
@@ -36,7 +35,7 @@
"@ckeditor/ckeditor5-dev-build-tools": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "^4.0.0",
"@typescript-eslint/eslint-plugin": "~8.41.0",
"@typescript-eslint/eslint-plugin": "~8.42.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitest/browser": "^3.0.5",
"@vitest/coverage-istanbul": "^3.0.5",

View File

@@ -10,7 +10,7 @@
import { Plugin, addListToDropdown, createDropdown, ListDropdownItemDefinition, SplitButtonView, ViewModel } from 'ckeditor5';
import '../theme/blockquote.css';
import admonitionIcon from '../theme/icons/admonition.svg?raw';
import admonitionIcon from '../theme/icons/admonition.svg';
import { AdmonitionType } from './admonitioncommand.js';
import { Collection } from 'ckeditor5';

View File

@@ -1,4 +1,4 @@
import admonitionIcon from '../theme/icons/admonition.svg?raw';
import admonitionIcon from '../theme/icons/admonition.svg';
import './augmentation.js';
import "../theme/blockquote.css";

View File

@@ -1,4 +1,4 @@
declare module '*.svg?raw' {
declare module '*.svg' {
const content: string;
export default content;
}

View File

@@ -11,14 +11,13 @@
"ckeditor5-package-generator"
],
"type": "module",
"source": "src/index.ts",
"main": "dist/index.ts",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./src/index.ts"
"import": "./dist/index.js"
},
"./*": "./dist/*",
"./browser/*": null,
@@ -37,7 +36,7 @@
"@ckeditor/ckeditor5-dev-build-tools": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "^4.0.0",
"@typescript-eslint/eslint-plugin": "~8.41.0",
"@typescript-eslint/eslint-plugin": "~8.42.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitest/browser": "^3.0.5",
"@vitest/coverage-istanbul": "^3.0.5",

View File

@@ -6,7 +6,7 @@ import {
ELEMENTS,
TOOLBAR_COMPONENT_NAME
} from './constants.js';
import insertFootnoteIcon from '../theme/icons/insert-footnote.svg?raw';
import insertFootnoteIcon from '../theme/icons/insert-footnote.svg';
import { modelQueryElement, modelQueryElementsAll } from './utils.js';
export default class FootnoteUI extends Plugin {

View File

@@ -1,4 +1,4 @@
import insertFootnoteIcon from './../theme/icons/insert-footnote.svg?raw';
import insertFootnoteIcon from './../theme/icons/insert-footnote.svg';
import './augmentation.js';
import "../theme/footnote.css";

View File

@@ -1,4 +1,4 @@
declare module '*.svg?raw' {
declare module '*.svg' {
const content: string;
export default content;
}

View File

@@ -11,14 +11,13 @@
"ckeditor5-package-generator"
],
"type": "module",
"source": "src/index.ts",
"main": "dist/index.ts",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./src/index.ts"
"import": "./dist/index.js"
},
"./*": "./dist/*",
"./browser/*": null,
@@ -39,7 +38,7 @@
"@ckeditor/ckeditor5-dev-build-tools": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "^4.0.0",
"@typescript-eslint/eslint-plugin": "~8.41.0",
"@typescript-eslint/eslint-plugin": "~8.42.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitest/browser": "^3.0.5",
"@vitest/coverage-istanbul": "^3.0.5",

View File

@@ -1,4 +1,4 @@
import kbdIcon from '../theme/icons/kbd.svg?raw';
import kbdIcon from '../theme/icons/kbd.svg';
import './augmentation.js';
export { default as Kbd } from './kbd.js';

View File

@@ -1,5 +1,5 @@
import { AttributeCommand, ButtonView, Plugin } from 'ckeditor5';
import kbdIcon from '../theme/icons/kbd.svg?raw';
import kbdIcon from '../theme/icons/kbd.svg';
const KBD = 'kbd';

View File

@@ -1,4 +1,4 @@
declare module '*.svg?raw' {
declare module '*.svg' {
const content: string;
export default content;
}

View File

@@ -12,14 +12,13 @@
"katex"
],
"type": "module",
"source": "src/index.ts",
"main": "dist/index.ts",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./src/index.ts"
"import": "./dist/index.js"
},
"./*": "./dist/*",
"./browser/*": null,
@@ -40,7 +39,7 @@
"@ckeditor/ckeditor5-dev-utils": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "^4.0.0",
"@typescript-eslint/eslint-plugin": "~8.41.0",
"@typescript-eslint/eslint-plugin": "~8.42.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitest/browser": "^3.0.5",
"@vitest/coverage-istanbul": "^3.0.5",

View File

@@ -1,4 +1,4 @@
import ckeditor from './../theme/icons/math.svg?raw';
import ckeditor from './../theme/icons/math.svg';
import './augmentation.js';
import "../theme/mathform.css";

View File

@@ -1,6 +1,6 @@
import MathEditing from './mathediting.js';
import MainFormView from './ui/mainformview.js';
import mathIcon from '../theme/icons/math.svg?raw';
import mathIcon from '../theme/icons/math.svg';
import { Plugin, ClickObserver, ButtonView, ContextualBalloon, clickOutsideHandler, CKEditorError, uid } from 'ckeditor5';
import { getBalloonPositionData } from './utils.js';
import MathCommand from './mathcommand.js';
@@ -54,10 +54,10 @@ export default class MathUI extends Plugin {
this._addFormView();
this._balloon.showStack( 'main' );
requestAnimationFrame(() => {
this.formView?.mathInputView.fieldView.element?.focus();
});
});
}
private _createFormView() {

View File

@@ -1,4 +1,4 @@
declare module '*.svg?raw' {
declare module '*.svg' {
const content: string;
export default content;
}

View File

@@ -11,14 +11,13 @@
"ckeditor5-package-generator"
],
"type": "module",
"source": "src/index.ts",
"main": "dist/index.ts",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./src/index.ts"
"import": "./dist/index.js"
},
"./*": "./dist/*",
"./browser/*": null,
@@ -39,7 +38,7 @@
"@ckeditor/ckeditor5-dev-build-tools": "43.1.0",
"@ckeditor/ckeditor5-inspector": ">=4.1.0",
"@ckeditor/ckeditor5-package-tools": "^4.0.0",
"@typescript-eslint/eslint-plugin": "~8.41.0",
"@typescript-eslint/eslint-plugin": "~8.42.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitest/browser": "^3.0.5",
"@vitest/coverage-istanbul": "^3.0.5",

View File

@@ -1,11 +1,11 @@
import './augmentation.js';
export { default as Mermaid } from './mermaid.js';
import infoIcon from './../theme/icons/info.svg?raw';
import insertMermaidIcon from './../theme/icons/insert.svg?raw';
import previewModeIcon from './../theme/icons/preview-mode.svg?raw';
import splitModeIcon from './../theme/icons/split-mode.svg?raw';
import sourceModeIcon from './../theme/icons/source-mode.svg?raw';
import infoIcon from './../theme/icons/info.svg';
import insertMermaidIcon from './../theme/icons/insert.svg';
import previewModeIcon from './../theme/icons/preview-mode.svg';
import splitModeIcon from './../theme/icons/split-mode.svg';
import sourceModeIcon from './../theme/icons/source-mode.svg';
import "../theme/mermaid.css";
export const icons = {

View File

@@ -2,11 +2,11 @@
* @module mermaid/mermaidui
*/
import insertMermaidIcon from '../theme/icons/insert.svg?raw';
import previewModeIcon from '../theme/icons/preview-mode.svg?raw';
import splitModeIcon from '../theme/icons/split-mode.svg?raw';
import sourceModeIcon from '../theme/icons/source-mode.svg?raw';
import infoIcon from '../theme/icons/info.svg?raw';
import insertMermaidIcon from '../theme/icons/insert.svg';
import previewModeIcon from '../theme/icons/preview-mode.svg';
import splitModeIcon from '../theme/icons/split-mode.svg';
import sourceModeIcon from '../theme/icons/source-mode.svg';
import infoIcon from '../theme/icons/info.svg';
import { ButtonView, Editor, ModelElement, Locale, Observable, Plugin } from 'ckeditor5';
import InsertMermaidCommand from './commands/insertMermaidCommand.js';

View File

@@ -1,11 +1,11 @@
import { Mermaid as MermaidDll, icons } from '../src/index.js';
import Mermaid from '../src/mermaid.js';
import infoIcon from './../theme/icons/info.svg?raw';
import insertMermaidIcon from './../theme/icons/insert.svg?raw';
import previewModeIcon from './../theme/icons/preview-mode.svg?raw';
import splitModeIcon from './../theme/icons/split-mode.svg?raw';
import sourceModeIcon from './../theme/icons/source-mode.svg?raw';
import infoIcon from './../theme/icons/info.svg';
import insertMermaidIcon from './../theme/icons/insert.svg';
import previewModeIcon from './../theme/icons/preview-mode.svg';
import splitModeIcon from './../theme/icons/split-mode.svg';
import sourceModeIcon from './../theme/icons/source-mode.svg';
import { describe, it } from 'vitest';
import { expect } from 'vitest';

View File

@@ -1,4 +1,4 @@
declare module '*.svg?raw' {
declare module '*.svg' {
const content: string;
export default content;
}

View File

@@ -4,7 +4,6 @@
"version": "45.0.0",
"private": true,
"type": "module",
"source": "./src/index.ts",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -21,10 +21,10 @@ import { Admonition } from "@triliumnext/ckeditor5-admonition";
import { Footnotes } from "@triliumnext/ckeditor5-footnotes";
import { Math, AutoformatMath } from "@triliumnext/ckeditor5-math";
// import "@triliumnext/ckeditor5-mermaid/index.css";
// import "@triliumnext/ckeditor5-admonition/index.css";
// import "@triliumnext/ckeditor5-footnotes/index.css";
// import "@triliumnext/ckeditor5-math/index.css";
import "@triliumnext/ckeditor5-mermaid/index.css";
import "@triliumnext/ckeditor5-admonition/index.css";
import "@triliumnext/ckeditor5-footnotes/index.css";
import "@triliumnext/ckeditor5-math/index.css";
import CodeBlockToolbar from "./plugins/code_block_toolbar.js";
import CodeBlockLanguageDropdown from "./plugins/code_block_language_dropdown.js";
import MoveBlockUpDownPlugin from "./plugins/move_block_updown.js";

View File

@@ -1,4 +1,4 @@
import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg?raw';
import cancelIcon from '@ckeditor/ckeditor5-core/theme/icons/cancel.svg';
import { ButtonView, Locale, toUnit, View } from 'ckeditor5';
const toPx = toUnit('%');

View File

@@ -30,7 +30,7 @@
"@codemirror/lang-xml": "6.1.0",
"@codemirror/legacy-modes": "6.5.1",
"@codemirror/search": "6.5.11",
"@codemirror/view": "6.38.1",
"@codemirror/view": "6.38.2",
"@fsegurai/codemirror-theme-abcdef": "6.2.2",
"@fsegurai/codemirror-theme-abyss": "6.2.2",
"@fsegurai/codemirror-theme-android-studio": "6.2.2",

View File

@@ -3,7 +3,6 @@
"version": "0.0.1",
"private": true,
"type": "module",
"source": "./src/index.ts",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@@ -1,4 +1,4 @@
import hljs from "highlight.js";
import hljs from "../node_modules/highlight.js/es/core.js";
import { normalizeMimeTypeForCKEditor, type MimeType } from "@triliumnext/commons";
import syntaxDefinitions from "./syntax_highlighting.js";
import { type Theme } from "./themes.js";

894
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,26 +5,22 @@
"emitDeclarationOnly": true,
"importHelpers": true,
"isolatedModules": true,
"lib": [
"ES2023"
],
"lib": ["ES2023"],
"module": "nodenext",
"moduleResolution": "nodenext",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": false,
"noImplicitOverride": false,
"noImplicitReturns": false,
"noUnusedLocals": false,
"noImplicitAny": false, // TODO: Re-enable it at some point.
"noImplicitOverride": false, // TODO: Re-enable it at some point.
"noImplicitReturns": false, // TODO: Re-enable it at some point.
"noUnusedLocals": false, // TODO: Re-enable it at some point.
"skipLibCheck": true,
"strict": true,
"target": "es2022",
"customConditions": [
"development"
],
"verbatimModuleSyntax": false,
"customConditions": ["development"],
"verbatimModuleSyntax": false, // TODO: Re-enable it when migrating back to ESM.
"resolveJsonModule": true,
"downlevelIteration": true,
"esModuleInterop": true
"esModuleInterop": true,
}
}