Merge pull request #213 from gamosoft/features/drawings

Features/drawings
This commit is contained in:
Guillermo Villar 2026-04-24 16:30:50 +02:00 committed by GitHub
commit b15271ddcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 1651 additions and 505 deletions

View File

@ -64,6 +64,7 @@ NoteDiscovery is a **lightweight, self-hosted note-taking application** that put
- 🧮 **Math Support** - LaTeX/MathJax for beautiful equations
- 📄 **HTML Export & Print** - Export notes as standalone HTML or print
- 🕸️ **Graph View** - Interactive visualization of connected notes
- ✏️ **Drawing editor** - In-app sketches as `drawing-*.png` next to your notes — see [documentation/DRAWING.md](documentation/DRAWING.md)
- ⭐ **Favorites** - Star your most-used notes for instant access
- 📑 **Outline Panel** - Navigate headings with click-to-jump TOC
- 🤖 **AI Assistant Ready** - MCP integration for Claude, Cursor & more
@ -240,6 +241,7 @@ Want to learn more?
- 🎨 **[THEMES.md](documentation/THEMES.md)** - Theme customization and creating custom themes
- ✨ **[FEATURES.md](documentation/FEATURES.md)** - Complete feature list and keyboard shortcuts
- ✏️ **[DRAWING.md](documentation/DRAWING.md)** - Built-in drawing editor (`drawing-*.png`), save behavior, and API notes
- 🏷️ **[TAGS.md](documentation/TAGS.md)** - Organize notes with tags and combined filtering
- 📋 **[TEMPLATES.md](documentation/TEMPLATES.md)** - Create notes from reusable templates with dynamic placeholders
- 🧮 **[MATHJAX.md](documentation/MATHJAX.md)** - LaTeX/Math notation examples and syntax reference

View File

@ -49,7 +49,7 @@ def get_media_as_base64(media_path: Path) -> Optional[Tuple[str, str]]:
def get_image_as_base64(image_path: Path) -> Optional[str]:
"""Read an image file and return it as a base64 data URL."""
result = get_media_as_base64(image_path)
if result and result[1] == 'image':
if result and result[1] in ('image', 'drawing'):
return result[0]
return None

View File

@ -625,11 +625,72 @@ async def get_media(media_path: str):
raise HTTPException(status_code=500, detail=safe_error_message(e, "Failed to load media file"))
@api_router.put("/media/{media_path:path}", tags=["Media"])
@limiter.limit("120/minute")
async def put_media(media_path: str, request: Request):
"""
Overwrite an existing media file in place (used for saving drawing PNGs).
Only files named drawing-*.png are accepted to avoid accidental overwrites.
"""
try:
from backend.utils import ALL_MEDIA_EXTENSIONS
notes_dir = config['storage']['notes_dir']
full_path = Path(notes_dir) / media_path
if not validate_path_security(notes_dir, full_path):
raise HTTPException(status_code=403, detail="Access denied")
name_lower = full_path.name.lower()
if not (name_lower.startswith('drawing-') and name_lower.endswith('.png')):
raise HTTPException(
status_code=400,
detail="Only drawing PNG files (drawing-*.png) can be updated in place",
)
if full_path.suffix.lower() not in ALL_MEDIA_EXTENSIONS:
raise HTTPException(status_code=400, detail="Not an allowed media file")
if not full_path.exists() or not full_path.is_file():
raise HTTPException(status_code=404, detail="File not found")
body = await request.body()
max_size = UPLOAD_MAX_IMAGE_MB * 1024 * 1024
if len(body) > max_size:
raise HTTPException(
status_code=400,
detail=f"File too large. Maximum size: {UPLOAD_MAX_IMAGE_MB}MB",
)
# Reject non-PNG payloads (defense in depth; path already restricts to .png)
if len(body) < 8 or body[:8] != b"\x89PNG\r\n\x1a\n":
raise HTTPException(status_code=400, detail="Body must be a valid PNG image")
try:
with open(full_path, 'wb') as f:
f.write(body)
except OSError as e:
raise HTTPException(status_code=500, detail=safe_error_message(e, "Failed to save file"))
return {"success": True, "path": media_path}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=safe_error_message(e, "Failed to update media file"))
@api_router.post("/upload-media", tags=["Media"])
@limiter.limit("20/minute")
async def upload_media(request: Request, file: UploadFile = File(...), note_path: str = Form(...)):
async def upload_media(
request: Request,
file: UploadFile = File(...),
note_path: str = Form(""),
content_folder: str = Form(""),
next_to_notes: str = Form(""),
):
"""
Upload a media file (image, audio, video, PDF) and save it to the attachments directory.
Upload a media file (image, audio, video, PDF) and save it to the attachments directory,
or (when next_to_notes=1) save a new drawing PNG next to markdown notes in content_folder.
Returns the relative path for markdown linking.
"""
try:
@ -677,6 +738,32 @@ async def upload_media(request: Request, file: UploadFile = File(...), note_path
detail=f"File too large. Maximum size for {media_type or 'this type'}: {max_size // (1024*1024)}MB. Uploaded: {len(file_data) / 1024 / 1024:.2f}MB"
)
if (next_to_notes or "").strip() == "1":
is_png = file.content_type in ("image/png",) or (file.filename and file.filename.lower().endswith(".png"))
if not is_png:
raise HTTPException(
status_code=400,
detail="next_to_notes requires a PNG file",
)
file_path = save_uploaded_image(
config["storage"]["notes_dir"],
"",
file.filename or "drawing.png",
file_data,
sibling_folder=content_folder or "",
)
if not file_path:
raise HTTPException(status_code=500, detail="Failed to save drawing")
out_name = Path(file_path).name
media_type = get_media_type(out_name) or "drawing"
return {
"success": True,
"path": file_path,
"filename": out_name,
"type": media_type,
"message": "Drawing created",
}
# Save the file (reusing image save function - it works for any file)
file_path = save_uploaded_image(
config['storage']['notes_dir'],

View File

@ -11,10 +11,56 @@ from datetime import datetime, timezone
from typing import Optional, Dict, Any
import threading
from .utils import validate_path_security
# Thread lock for safe concurrent access
_lock = threading.Lock()
def _token_references_accessible_file(storage_dir: str, rel_path: Any) -> bool:
"""
Return True if rel_path is a string that resolves to a regular file under storage_dir
and passes the same path boundary check used elsewhere in the app.
"""
if not rel_path or not isinstance(rel_path, str):
return False
try:
full = (Path(storage_dir) / rel_path).resolve()
except (OSError, ValueError, RuntimeError):
return False
if not validate_path_security(str(storage_dir), full):
return False
return full.is_file()
def _prune_inaccessible_unsafe(data_dir: str) -> int:
"""
Remove share tokens whose stored path is missing or not under the notes directory.
Call only while holding _lock. Returns the number of tokens removed.
"""
tokens = load_tokens(data_dir)
if not tokens:
return 0
kept: Dict[str, Dict[str, Any]] = {}
for token, info in tokens.items():
path = info.get("path")
if _token_references_accessible_file(data_dir, path):
kept[token] = info
removed = len(tokens) - len(kept)
if removed and not save_tokens(data_dir, kept):
return 0
return removed
def prune_inaccessible_share_tokens(data_dir: str) -> int:
"""
Remove entries whose path no longer resolves to a file under the storage root.
Called automatically after create/revoke share; may also be used from tests or one-off maintenance.
"""
with _lock:
return _prune_inaccessible_unsafe(data_dir)
def generate_token(length: int = 16) -> str:
"""Generate a URL-safe random token."""
# Use alphanumeric + underscore/hyphen (URL-safe)
@ -76,8 +122,9 @@ def create_share_token(data_dir: str, note_path: str, theme: str = "light") -> O
# Check if note already has a token
for token, info in tokens.items():
if info.get('path') == note_path:
_prune_inaccessible_unsafe(data_dir)
return token
# Generate new token
token = generate_token()
@ -93,7 +140,9 @@ def create_share_token(data_dir: str, note_path: str, theme: str = "light") -> O
}
if save_tokens(data_dir, tokens):
_prune_inaccessible_unsafe(data_dir)
return token
_prune_inaccessible_unsafe(data_dir)
return None
@ -177,8 +226,13 @@ def revoke_share_token(data_dir: str, note_path: str) -> bool:
if token_to_remove:
del tokens[token_to_remove]
return save_tokens(data_dir, tokens)
if not save_tokens(data_dir, tokens):
_prune_inaccessible_unsafe(data_dir)
return False
_prune_inaccessible_unsafe(data_dir)
return True
_prune_inaccessible_unsafe(data_dir)
return False

View File

@ -604,54 +604,67 @@ def get_attachment_dir(notes_dir: str, note_path: str) -> Path:
return Path(notes_dir) / folder / "_attachments"
def save_uploaded_image(notes_dir: str, note_path: str, filename: str, file_data: bytes) -> Optional[str]:
def save_uploaded_image(
notes_dir: str,
note_path: str,
filename: str,
file_data: bytes,
*,
sibling_folder: Optional[str] = None,
) -> Optional[str]:
"""
Save an uploaded image to the appropriate attachments directory.
Returns the relative path to the image if successful, None otherwise.
Args:
notes_dir: Base notes directory
note_path: Path of the note the image is being uploaded to
filename: Original filename
file_data: Binary file data
Returns:
Relative path to the saved image, or None if failed
Save uploaded media under the vault.
Default (sibling_folder is None): store in ``_attachments`` next to the note implied by
``note_path`` (drag/drop, paste, etc.).
If ``sibling_folder`` is set (including ``""`` for vault root): store ``drawing-{timestamp}.png``
in that folder next to ``.md`` files used for new drawings from the + menu.
Returns a relative path from ``notes_dir``, or None on failure.
"""
# Sanitize filename
base = Path(notes_dir)
if sibling_folder is not None:
cf = (sibling_folder or "").strip().replace("\\", "/")
segments = [p for p in cf.split("/") if p and p != "."]
if any(p == ".." for p in segments):
return None
dest_dir = base.joinpath(*segments) if segments else base
if not validate_path_security(notes_dir, dest_dir / ".nd_probe"):
return None
try:
dest_dir.mkdir(parents=True, exist_ok=True)
except OSError:
return None
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
full_path = dest_dir / f"drawing-{timestamp}.png"
if not validate_path_security(notes_dir, full_path):
return None
try:
with open(full_path, "wb") as f:
f.write(file_data)
return str(full_path.relative_to(base).as_posix())
except OSError as e:
print(f"Error saving image: {e}")
return None
sanitized_name = sanitize_filename(filename)
# Get extension
ext = Path(sanitized_name).suffix
name_without_ext = Path(sanitized_name).stem
# Add timestamp to prevent collisions
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
final_filename = f"{name_without_ext}-{timestamp}{ext}"
# Get attachments directory
attachments_dir = get_attachment_dir(notes_dir, note_path)
# Create directory if it doesn't exist
attachments_dir.mkdir(parents=True, exist_ok=True)
# Full path to save the image
full_path = attachments_dir / final_filename
# Security check
if not validate_path_security(notes_dir, full_path):
print(f"Security: Attempted to save image outside notes directory: {full_path}")
return None
try:
# Write the file
with open(full_path, 'wb') as f:
with open(full_path, "wb") as f:
f.write(file_data)
# Return relative path from notes_dir
relative_path = full_path.relative_to(Path(notes_dir))
return str(relative_path.as_posix())
except Exception as e:
return str(full_path.relative_to(base).as_posix())
except OSError as e:
print(f"Error saving image: {e}")
return None
@ -671,8 +684,13 @@ ALL_MEDIA_EXTENSIONS = set().union(*MEDIA_EXTENSIONS.values())
def get_media_type(filename: str) -> Optional[str]:
"""
Determine the media type based on file extension.
Returns: 'image', 'audio', 'video', 'document', or None if not a media file.
Returns: 'image', 'audio', 'video', 'document', 'drawing', or None if not a media file.
Drawings are PNG files stored like images but named drawing-*.png (editable canvas in the app).
"""
name_lower = Path(filename).name.lower()
if name_lower.startswith('drawing-') and name_lower.endswith('.png'):
return 'drawing'
ext = Path(filename).suffix.lower()
for media_type, extensions in MEDIA_EXTENSIONS.items():
if ext in extensions:

View File

@ -7,35 +7,45 @@
<!-- Primary Meta Tags -->
<title>NoteDiscovery - Your Self-Hosted Knowledge Base</title>
<meta name="title" content="NoteDiscovery - Your Self-Hosted Knowledge Base">
<meta name="description" content="A lightweight, privacy-focused Markdown note-taking application with AI assistant integration (MCP), wikilinks, graph view, LaTeX math, Mermaid diagrams, tags, templates, and code highlighting. Works with Claude, Cursor, and other AI tools. Self-hosted, free, and open source.">
<meta name="keywords" content="note taking, markdown editor, self-hosted, knowledge base, open source, privacy, notes app, docker, second brain, obsidian alternative, notion, evernote, onenote, latex, mermaid, diagrams, math equations, templates, tags, yaml frontmatter, AI assistant, MCP, model context protocol, Claude, Cursor, AI notes, AI knowledge base, AI-powered notes">
<meta name="description" content="Self-hosted Markdown knowledge base with AI assistant integration (MCP) for Claude & Cursor, wikilinks, graph view, drawing editor, LaTeX, Mermaid — free and open source.">
<meta name="keywords" content="markdown notes, self-hosted, knowledge base, MCP, Claude, Cursor, obsidian alternative, open source">
<meta name="author" content="Gamosoft">
<meta name="robots" content="index, follow">
<meta name="robots" content="index, follow, max-image-preview:large">
<link rel="canonical" href="https://www.notediscovery.com">
<!-- Preconnect / DNS prefetch for performance -->
<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin>
<link rel="preconnect" href="https://www.google-analytics.com" crossorigin>
<link rel="dns-prefetch" href="https://ko-fi.com">
<link rel="dns-prefetch" href="https://www.pikapods.com">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://www.notediscovery.com">
<meta property="og:title" content="NoteDiscovery - Your Self-Hosted Knowledge Base">
<meta property="og:description" content="AI-powered note-taking with MCP integration for Claude, Cursor & more. LaTeX math, Mermaid diagrams, graph view, wikilinks. Self-hosted, free, and open source.">
<meta property="og:description" content="AI-powered note-taking with MCP integration for Claude, Cursor & more. Wikilinks, graph view, drawing editor, LaTeX, Mermaid. Self-hosted, free, open source.">
<meta property="og:image" content="https://www.notediscovery.com/og-image.png">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="NoteDiscovery — self-hosted Markdown knowledge base">
<meta property="og:site_name" content="NoteDiscovery">
<meta property="og:locale" content="en_US">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@gamosoft">
<meta name="twitter:creator" content="@gamosoft">
<meta name="twitter:url" content="https://www.notediscovery.com">
<meta name="twitter:title" content="NoteDiscovery - Your Self-Hosted Knowledge Base">
<meta name="twitter:description" content="AI-powered note-taking with MCP integration for Claude & Cursor. LaTeX, Mermaid diagrams, graph view. Self-hosted, free, open source.">
<meta name="twitter:description" content="AI-powered note-taking with MCP for Claude & Cursor. Wikilinks, graph view, drawing editor, LaTeX, Mermaid. Self-hosted, free, open source.">
<meta name="twitter:image" content="https://www.notediscovery.com/og-image.png">
<meta name="twitter:image:alt" content="NoteDiscovery - Self-hosted note-taking application interface">
<meta name="twitter:image:alt" content="NoteDiscovery — self-hosted Markdown knowledge base">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="apple-touch-icon" href="logo.svg">
<!-- Theme Color -->
<meta name="theme-color" content="#667eea">
<style>
@ -353,7 +363,7 @@
animation: fadeInScale 0.6s ease-out forwards;
}
/* Stagger animation delays for feature cards (5 rows × 3 cols = 15 cards) */
/* Stagger animation delays for feature cards (15 cards) */
.feature.animated:nth-child(1) { animation-delay: 0s; }
.feature.animated:nth-child(2) { animation-delay: 0.1s; }
.feature.animated:nth-child(3) { animation-delay: 0.2s; }
@ -689,7 +699,7 @@
<div class="hero animate-on-scroll">
<div class="hero-content">
<h2>Take Control of Your Notes</h2>
<p>A lightweight, privacy-focused note-taking application with powerful features: AI assistant integration (MCP), LaTeX math equations, Mermaid diagrams, smart tags, custom templates, code highlighting, and more. Write and organize your notes with a beautiful, modern interface all running on your own server.</p>
<p>A lightweight, privacy-focused note-taking application with powerful features: AI assistant integration (MCP), LaTeX math equations, Mermaid diagrams, an embedded drawing editor for quick sketches, smart tags, custom templates, code highlighting, and more. Write and organize your notes with a beautiful, modern interface all running on your own server.</p>
<div class="cta-buttons">
<a href="https://gamosoft-notediscovery-demo.hf.space" class="cta-button demo" target="_blank" rel="noopener">Try Live Demo</a>
<a href="https://github.com/gamosoft/NoteDiscovery" class="cta-button" target="_blank" rel="noopener">Get Started on GitHub →</a>
@ -708,104 +718,104 @@
<h2>See It in Action</h2>
<p>A clean, intuitive interface designed for productivity and focus</p>
<div class="screenshot-container">
<div class="carousel">
<div class="carousel" role="region" aria-roledescription="carousel" aria-label="Product screenshots">
<div class="carousel-track" id="carouselTrack">
<!-- Slide 1: Main Interface -->
<div class="carousel-slide">
<div class="screenshot-frame">
<div class="screenshot-window-bar">
<div class="screenshot-dots">
<div class="screenshot-dots" aria-hidden="true">
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
</div>
<span class="screenshot-title">Editor View • Main Interface</span>
</div>
<img src="carousel-1.jpg" alt="NoteDiscovery Main Interface" loading="lazy">
<img src="carousel-1.jpg" alt="NoteDiscovery editor showing Markdown preview and sidebar" width="1600" height="1000" fetchpriority="high" decoding="async">
</div>
</div>
<!-- Slide 2: Search -->
<div class="carousel-slide">
<div class="screenshot-frame">
<div class="screenshot-window-bar">
<div class="screenshot-dots">
<div class="screenshot-dots" aria-hidden="true">
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
</div>
<span class="screenshot-title">Smart Search • Find Anything Instantly</span>
</div>
<img src="carousel-2.jpg" alt="NoteDiscovery Smart Search" loading="lazy">
<img src="carousel-2.jpg" alt="NoteDiscovery smart full-text search results" width="1600" height="1000" loading="lazy" decoding="async">
</div>
</div>
<!-- Slide 3: Tag Filter -->
<div class="carousel-slide">
<div class="screenshot-frame">
<div class="screenshot-window-bar">
<div class="screenshot-dots">
<div class="screenshot-dots" aria-hidden="true">
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
</div>
<span class="screenshot-title">Tag Filtering • Organize by Topics</span>
</div>
<img src="carousel-3.jpg" alt="NoteDiscovery Tag Filtering" loading="lazy">
<img src="carousel-3.jpg" alt="NoteDiscovery tag filtering panel" width="1600" height="1000" loading="lazy" decoding="async">
</div>
</div>
<!-- Slide 4: Outline -->
<div class="carousel-slide">
<div class="screenshot-frame">
<div class="screenshot-window-bar">
<div class="screenshot-dots">
<div class="screenshot-dots" aria-hidden="true">
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
</div>
<span class="screenshot-title">Outline Panel • Navigate Headings</span>
</div>
<img src="carousel-4.jpg" alt="NoteDiscovery Outline Panel" loading="lazy">
<img src="carousel-4.jpg" alt="NoteDiscovery outline panel for heading navigation" width="1600" height="1000" loading="lazy" decoding="async">
</div>
</div>
<!-- Slide 5: Card View -->
<div class="carousel-slide">
<div class="screenshot-frame">
<div class="screenshot-window-bar">
<div class="screenshot-dots">
<div class="screenshot-dots" aria-hidden="true">
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
</div>
<span class="screenshot-title">Card View • Visual Organization</span>
</div>
<img src="carousel-5.jpg" alt="NoteDiscovery Card View" loading="lazy">
<img src="carousel-5.jpg" alt="NoteDiscovery card view homepage with recent notes" width="1600" height="1000" loading="lazy" decoding="async">
</div>
</div>
<!-- Slide 6: Graph View -->
<div class="carousel-slide">
<div class="screenshot-frame">
<div class="screenshot-window-bar">
<div class="screenshot-dots">
<div class="screenshot-dots" aria-hidden="true">
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
</div>
<span class="screenshot-title">Graph View • Visualize Connections</span>
</div>
<img src="carousel-6.jpg" alt="NoteDiscovery Graph View" loading="lazy">
<img src="carousel-6.jpg" alt="NoteDiscovery graph view of linked notes" width="1600" height="1000" loading="lazy" decoding="async">
</div>
</div>
<!-- Slide 7: Mobile Support -->
<div class="carousel-slide">
<div class="screenshot-frame">
<div class="screenshot-window-bar">
<div class="screenshot-dots">
<div class="screenshot-dots" aria-hidden="true">
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
<div class="screenshot-dot"></div>
</div>
<span class="screenshot-title">Mobile Ready • Notes On The Go</span>
</div>
<img src="carousel-7.png" alt="NoteDiscovery Mobile Support" loading="lazy">
<img src="carousel-7.png" alt="NoteDiscovery responsive mobile interface" width="800" height="1400" loading="lazy" decoding="async">
</div>
</div>
</div>
@ -858,6 +868,12 @@
<p>Create flowcharts, mind maps, and diagrams with Mermaid syntax.</p>
</div>
<div class="feature animate-on-scroll">
<div class="feature-icon">✏️</div>
<h3>Drawing editor</h3>
<p>Sketch and annotate directly in the browser—PNG files saved alongside your notes with autosave, shapes, eraser, eyedropper, and undo/redo.</p>
</div>
<div class="feature animate-on-scroll">
<div class="feature-icon">💻</div>
<h3>Code Highlighting</h3>
@ -894,12 +910,6 @@
<p>Several built-in themes with dark/light modes and full customization.</p>
</div>
<div class="feature animate-on-scroll">
<div class="feature-icon"></div>
<h3>Favorites</h3>
<p>Star your most-used notes for instant access from the sidebar.</p>
</div>
<div class="feature animate-on-scroll">
<div class="feature-icon">📑</div>
<h3>Outline Panel</h3>
@ -999,7 +1009,7 @@
<span class="emoji">🌐</span>
<div class="text">
<strong>Public Sharing</strong>
Share notes via link, even with auth enabled
Share notes via link (works with auth on) and manage them from a dedicated Shared panel in the sidebar
</div>
</div>
@ -1191,27 +1201,104 @@
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "NoteDiscovery",
"description": "A lightweight, AI-powered Markdown note-taking application with MCP integration for Claude, Cursor and other AI assistants. Features LaTeX math, Mermaid diagrams, graph view, and code highlighting. Self-hosted Obsidian and Evernote alternative. Free and open source.",
"alternateName": "NoteDiscovery - Your Self-Hosted Knowledge Base",
"description": "A lightweight, AI-powered Markdown note-taking application with MCP integration for Claude, Cursor and other AI assistants. Features wikilinks, graph view, drawing editor, LaTeX math, Mermaid diagrams, tags, templates, and code highlighting. Self-hosted Obsidian/Evernote alternative. Free and open source.",
"url": "https://www.notediscovery.com",
"image": "https://www.notediscovery.com/og-image.png",
"screenshot": [
"https://www.notediscovery.com/carousel-1.jpg",
"https://www.notediscovery.com/carousel-2.jpg",
"https://www.notediscovery.com/carousel-3.jpg",
"https://www.notediscovery.com/carousel-4.jpg",
"https://www.notediscovery.com/carousel-5.jpg",
"https://www.notediscovery.com/carousel-6.jpg",
"https://www.notediscovery.com/carousel-7.png"
],
"applicationCategory": "ProductivityApplication",
"applicationSubCategory": "NoteTakingApplication",
"operatingSystem": "Linux, Windows, macOS",
"featureList": "AI assistant integration, MCP (Model Context Protocol), Claude integration, Cursor integration, Markdown editor, Wikilinks, Graph view, LaTeX math equations, Mermaid diagrams, Code syntax highlighting, Dark mode, Plugin system, Internal links, Properties panel, HTML export, Full-text search, Self-hosted, Obsidian alternative, Evernote alternative, Notion alternative, Second brain, AI-powered notes",
"browserRequirements": "Requires a modern browser with JavaScript enabled",
"softwareVersion": "0.22.1",
"datePublished": "2025-01-15",
"dateModified": "2026-04-24",
"license": "https://opensource.org/licenses/MIT",
"downloadUrl": "https://github.com/gamosoft/NoteDiscovery",
"codeRepository": "https://github.com/gamosoft/NoteDiscovery",
"isAccessibleForFree": true,
"featureList": "AI assistant integration, MCP (Model Context Protocol), Claude integration, Cursor integration, Markdown editor, Wikilinks, Graph view, Drawing editor, LaTeX math equations, Mermaid diagrams, Code syntax highlighting, Dark mode, Plugin system, Internal links, Properties panel, HTML export, Full-text search, Public sharing with dedicated sidebar panel, Self-hosted, Obsidian alternative, Evernote alternative, Notion alternative, Second brain, AI-powered notes",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
"priceCurrency": "USD",
"availability": "https://schema.org/InStock"
},
"author": {
"@type": "Person",
"name": "Gamosoft"
"name": "Gamosoft",
"url": "https://github.com/gamosoft"
},
"softwareVersion": "1.0",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "5",
"ratingCount": "1"
"publisher": {
"@type": "Organization",
"name": "Gamosoft",
"url": "https://github.com/gamosoft",
"logo": {
"@type": "ImageObject",
"url": "https://www.notediscovery.com/logo.png",
"width": 512,
"height": 512
}
}
}
</script>
<!-- FAQ Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Is NoteDiscovery really free?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. NoteDiscovery is free and open source under the MIT license. There are no paid tiers, no accounts, and no telemetry. You self-host it on your own server."
}
},
{
"@type": "Question",
"name": "Does it work with AI tools like Claude or Cursor?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. NoteDiscovery ships with a built-in MCP (Model Context Protocol) server, so AI assistants like Claude Desktop and Cursor can read and edit your notes directly, without any copy-pasting back and forth."
}
},
{
"@type": "Question",
"name": "Do my notes ever leave my computer?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. Everything stays on the server you choose. There is no cloud sync and no third-party storage. The AI integration runs locally over MCP, and your notes are never sent anywhere unless you explicitly publish a share link."
}
},
{
"@type": "Question",
"name": "Can I move my notes in or out easily?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Notes are plain Markdown files in normal folders. You can drop an existing Obsidian vault straight in, or open the same files in any other Markdown editor. There is no proprietary database to escape from."
}
},
{
"@type": "Question",
"name": "Do I need to be a developer to install it?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Not really. The recommended setup is a single Docker command, and there is a one-click PikaPods option if you would rather not touch the command line at all."
}
}
]
}
</script>
</body>
</html>

BIN
docs/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -2,7 +2,7 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://www.notediscovery.com</loc>
<lastmod>2025-11-09</lastmod>
<lastmod>2026-04-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>

View File

@ -214,6 +214,31 @@ curl http://localhost:8000/api/media/folder/_attachments/image-20240417093343.pn
- The file exists and is a valid media format
- The requesting user is authenticated (if auth is enabled)
### Update drawing (PNG in place)
```http
PUT /api/media/{media_path}
Content-Type: image/png
```
Overwrites an **existing** file in the vault. The server only accepts targets whose filename matches **`drawing-*.png`** (lowercase `.png`). The body must be a valid **PNG** (magic bytes are checked). Used by the in-app drawing editor when saving.
**Requirements:**
- File must already exist (create new drawings via **New drawing** / `POST /api/upload-media` with `next_to_notes`, not via PUT).
- Path must stay within the notes directory (same rules as GET).
**Response:**
```json
{ "success": true, "path": "folder/drawing-20260101120000.png" }
```
**Example:**
```bash
curl -X PUT http://localhost:8000/api/media/myproject/drawing-20260101120000.png \
-H "Content-Type: image/png" \
--data-binary @sketch.png
```
### Upload Media
```http
POST /api/upload-media

29
documentation/DRAWING.md Normal file
View File

@ -0,0 +1,29 @@
# Drawing editor
NoteDiscovery includes a **built-in drawing editor** for sketching and annotating directly in the app. Drawings are stored as ordinary **PNG files** in your vault, so they work like any other image: previews, export, and backups behave the same.
## Creating a drawing
Use the **+ New** menu in the sidebar and choose **New drawing**. The app creates a file named `drawing-{timestamp}.png` next to your notes (in the folder you pick), then opens it in the drawing viewer.
## Editor overview
- **Tools** — Freehand pencil, straight line, rectangle, and ellipse; **eraser** (paints with the canvas background color); **eyedropper** to sample a color from the canvas.
- **Color & stroke width** — Color picker and width slider appear on the same toolbar as the tools.
- **Undo / redo** — Use the main toolbar buttons or **Ctrl+Z** / **Ctrl+Y** (same shortcuts as the note editor; they apply to strokes while a drawing is open).
- **Clear** — Replaces the current session with a **blank white image** and schedules a save (see [FEATURES.md](FEATURES.md) for the exact behavior and confirmation).
- **Saving** — Changes are saved automatically after you finish a stroke (debounced), and you can press **Ctrl+S** (Cmd+S on Mac) to save the PNG immediately.
## Files on disk
- Only files whose names match **`drawing-*.png`** are opened in the drawing editor; other PNGs open as normal images.
- The saved PNGs **pixel dimensions** match the drawing pane at save time (layout and device pixel ratio), similar to a screenshot of the canvas area.
## API
To update an existing drawing from automation, use **PUT `/api/media/{path}`** with a **PNG body**; the server only allows in-place updates for `drawing-*.png` paths. See [API.md](API.md#update-drawing-png-in-place).
## See also
- [FEATURES.md](FEATURES.md) — Full feature list and keyboard shortcuts
- [API.md](API.md) — Media endpoints

View File

@ -15,6 +15,7 @@
- **Public Sharing** - Share notes via token-based URLs with optional QR code for mobile (see [SHARING.md](SHARING.md))
### Media Support
- **Drawing editor** — In-app **`drawing-*.png`** sketches next to your notes ([overview](#drawing-editor)); full guide: **[DRAWING.md](DRAWING.md)**
- **Drag & drop upload** - Drop files from your file system directly into the editor
- **Clipboard paste** - Paste images from clipboard with Ctrl+V
- **Images** - JPG, PNG, GIF, WebP (default max 10MB, configurable)
@ -42,6 +43,16 @@
- **Theme-aware** - Export uses your current theme for consistent appearance
- **Full rendering** - MathJax equations, Mermaid diagrams, and syntax highlighting included
## ✏️ Drawing editor
Sketch beside your notes without leaving NoteDiscovery. **+ New → New drawing** creates a **`drawing-{timestamp}.png`** next to your markdown files; those open in the drawing viewer, other images open as usual.
- **Tools** — Pencil, lines, rectangles, ellipses, eraser, eyedropper, clear; color and stroke width on the toolbar; undo/redo while you work.
- **Saving** — Autosave and **Ctrl+S** / **Cmd+S** like the rest of the app.
- **Files** — Plain PNGs in your vault—link them in notes and back them up with everything else.
For file naming, API notes, and more: **[DRAWING.md](DRAWING.md)**
## 🔗 Linking & Discovery
### Graph View
@ -311,11 +322,11 @@ date: {{date}}
| Windows/Linux | Mac | Action |
|---------------|-----|--------|
| `Ctrl+Alt+P` | `Cmd+Option+P` | Quick Switcher (jump to any note) |
| `Ctrl+S` | `Cmd+S` | Save note |
| `Ctrl+S` | `Cmd+S` | Save note (or **save drawing PNG** when a `drawing-*.png` is open) |
| `Ctrl+Alt+N` | `Cmd+Option+N` | New note |
| `Ctrl+Alt+F` | `Cmd+Option+F` | New folder |
| `Ctrl+Z` | `Cmd+Z` | Undo |
| `Ctrl+Y` or `Ctrl+Shift+Z` | `Cmd+Y` or `Cmd+Shift+Z` | Redo |
| `Ctrl+Z` | `Cmd+Z` | Undo (note edits, or **drawing strokes** when a drawing is open) |
| `Ctrl+Y` or `Ctrl+Shift+Z` | `Cmd+Y` or `Cmd+Shift+Z` | Redo (note edits, or **drawing strokes** when a drawing is open) |
| `Ctrl+Alt+Z` | `Cmd+Option+Z` | Toggle Zen Mode |
| `Esc` | `Esc` | Exit Zen Mode |
| `F3` | `F3` | Next search match |

File diff suppressed because it is too large Load Diff

View File

@ -1521,6 +1521,26 @@
x-text="backlinks.length > 9 ? '9+' : backlinks.length"
></span>
</button>
<!-- Shared notes (public links) -->
<button
class="icon-rail-btn relative"
:class="{'active': activePanel === 'shared'}"
@click="activePanel = 'shared'"
:title="t('share.panel_title')"
:aria-label="t('share.panel_title')"
>
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path>
</svg>
<span
x-show="_sharedNotePathsList.length > 0"
x-cloak
class="absolute -top-1 -right-1 w-4 h-4 text-[9px] font-bold rounded-full flex items-center justify-center"
style="background-color: var(--accent-primary); color: white;"
x-text="_sharedNotePathsList.length > 9 ? '9+' : _sharedNotePathsList.length"
></span>
</button>
<div class="icon-rail-spacer"></div>
@ -1992,6 +2012,48 @@
</div>
<!-- END BACKLINKS PANEL -->
<!-- ==================== SHARED NOTES PANEL ==================== -->
<div x-show="activePanel === 'shared'" x-transition:enter="transition ease-out duration-150" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="flex flex-col h-full">
<div class="flex-shrink-0 px-3 py-2 border-b flex items-center justify-between" style="border-color: var(--border-primary);">
<span class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-tertiary);" x-text="t('share.panel_title')"></span>
<span class="text-xs px-1.5 py-0.5 rounded" style="background-color: var(--bg-tertiary); color: var(--text-tertiary);" x-text="_sharedNotePathsList.length"></span>
</div>
<div class="flex-1 overflow-y-auto custom-scrollbar">
<div class="py-2">
<template x-if="_sharedNotePathsList.length > 0">
<div class="px-2">
<template x-for="item in getSharedPanelItems()" :key="item.path">
<div
@click="openItem(item.path, 'note')"
class="hover-accent px-2 py-1.5 text-sm cursor-pointer mb-1 rounded"
:style="currentNote === item.path ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'"
>
<div class="flex items-start gap-2 mb-0.5">
<svg class="w-4 h-4 flex-shrink-0 mt-0.5" style="color: var(--accent-primary); opacity: 0.9;" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path>
</svg>
<div class="min-w-0 flex-1">
<div class="font-medium truncate" x-text="item.name" :title="item.name"></div>
<div class="text-xs truncate" style="color: var(--text-tertiary);" x-text="item.folder" :title="item.folder"></div>
</div>
</div>
</div>
</template>
</div>
</template>
<template x-if="_sharedNotePathsList.length === 0">
<div class="text-center py-6 px-3">
<svg class="w-12 h-12 mx-auto mb-3 opacity-40" style="color: var(--text-tertiary);" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"></path>
</svg>
<p class="text-sm" style="color: var(--text-tertiary);" x-text="t('share.no_shared')"></p>
</div>
</template>
</div>
</div>
</div>
<!-- END SHARED NOTES PANEL -->
<!-- ==================== SETTINGS PANEL ==================== -->
<template x-if="activePanel === 'settings'">
<div class="flex flex-col h-full">
@ -2408,7 +2470,7 @@
style="position: absolute; top: 8px; right: 8px; opacity: 0; transition: opacity 0.2s; color: var(--error); padding: 4px; border-radius: 4px; background-color: var(--bg-secondary);"
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='var(--bg-secondary)'"
:title="note.type !== 'note' ? t('toolbar.delete_image') : t('toolbar.delete_note')"
:title="note.type === 'note' ? t('toolbar.delete_note') : (note.type === 'drawing' ? t('toolbar.delete_drawing') : t('toolbar.delete_image'))"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
@ -2467,12 +2529,12 @@
class="text-xl font-semibold border-none focus:outline-none focus:ring-2 rounded px-3 py-1.5"
:style="'background-color: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-primary); min-width: 150px; max-width: 300px;' + (!currentNote ? ' cursor: default; opacity: 0.9;' : '')"
>
<!-- Undo Button -->
<!-- Undo Button (note history or drawing strokes when viewing a drawing) -->
<button
@click="undo()"
:disabled="undoHistory.length <= 1"
@click="currentMediaType === 'drawing' ? drawingUndo() : undo()"
:disabled="currentMediaType === 'drawing' ? drawingOps.length === 0 : undoHistory.length <= 1"
class="p-2 rounded-lg"
:style="undoHistory.length <= 1 ? 'color: var(--text-tertiary); opacity: 0.4; cursor: not-allowed;' : 'color: var(--text-secondary);'"
:style="(currentMediaType === 'drawing' ? drawingOps.length === 0 : undoHistory.length <= 1) ? 'color: var(--text-tertiary); opacity: 0.4; cursor: not-allowed;' : 'color: var(--text-secondary);'"
onmouseover="if(!this.disabled) this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
:title="t('toolbar.undo')"
@ -2484,10 +2546,10 @@
<!-- Redo Button -->
<button
@click="redo()"
:disabled="redoHistory.length === 0"
@click="currentMediaType === 'drawing' ? drawingRedo() : redo()"
:disabled="currentMediaType === 'drawing' ? drawingRedoStack.length === 0 : redoHistory.length === 0"
class="p-2 rounded-lg"
:style="redoHistory.length === 0 ? 'color: var(--text-tertiary); opacity: 0.4; cursor: not-allowed;' : 'color: var(--text-secondary);'"
:style="(currentMediaType === 'drawing' ? drawingRedoStack.length === 0 : redoHistory.length === 0) ? 'color: var(--text-tertiary); opacity: 0.4; cursor: not-allowed;' : 'color: var(--text-secondary);'"
onmouseover="if(!this.disabled) this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
:title="t('toolbar.redo')"
@ -2504,7 +2566,7 @@
style="color: var(--error);"
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
:title="currentMedia ? t('toolbar.delete_image') : t('toolbar.delete_note')"
:title="currentMedia ? (currentMediaType === 'drawing' ? t('toolbar.delete_drawing') : t('toolbar.delete_image')) : t('toolbar.delete_note')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
@ -2654,6 +2716,130 @@
</div>
</div>
<!-- Drawing tools (below main toolbar; undo/redo use icons above) -->
<div
x-show="currentMedia && currentMediaType === 'drawing'"
class="px-4 py-2 flex flex-wrap gap-2 items-center border-b flex-shrink-0 zen-hide"
style="background-color: var(--bg-secondary); border-color: var(--border-primary);"
x-cloak
>
<button
type="button"
class="p-2 rounded-lg border transition-colors"
:style="drawingTool === 'freehand' ? 'background-color: var(--accent-primary); color: white; border-color: var(--accent-primary);' : 'background-color: var(--bg-tertiary); color: var(--text-secondary); border-color: var(--border-primary);'"
@click="drawingTool = 'freehand'"
:title="t('drawing.tool_freehand')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
</button>
<button
type="button"
class="p-2 rounded-lg border transition-colors"
:style="drawingTool === 'line' ? 'background-color: var(--accent-primary); color: white; border-color: var(--accent-primary);' : 'background-color: var(--bg-tertiary); color: var(--text-secondary); border-color: var(--border-primary);'"
@click="drawingTool = 'line'"
:title="t('drawing.tool_line')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 20L20 4" />
</svg>
</button>
<button
type="button"
class="p-2 rounded-lg border transition-colors"
:style="drawingTool === 'rect' ? 'background-color: var(--accent-primary); color: white; border-color: var(--accent-primary);' : 'background-color: var(--bg-tertiary); color: var(--text-secondary); border-color: var(--border-primary);'"
@click="drawingTool = 'rect'"
:title="t('drawing.tool_rect')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a2 2 0 012-2h12a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V5z" />
</svg>
</button>
<button
type="button"
class="p-2 rounded-lg border transition-colors"
:style="drawingTool === 'ellipse' ? 'background-color: var(--accent-primary); color: white; border-color: var(--accent-primary);' : 'background-color: var(--bg-tertiary); color: var(--text-secondary); border-color: var(--border-primary);'"
@click="drawingTool = 'ellipse'"
:title="t('drawing.tool_ellipse')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
<button
type="button"
class="p-2 rounded-lg border transition-colors"
:style="drawingTool === 'eraser' ? 'background-color: var(--accent-primary); color: white; border-color: var(--accent-primary);' : 'background-color: var(--bg-tertiary); color: var(--text-secondary); border-color: var(--border-primary);'"
@click="drawingTool = 'eraser'"
:title="t('drawing.tool_eraser')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21M22 21H7M5 11l9 9" />
</svg>
</button>
<button
type="button"
class="p-2 rounded-lg border transition-colors"
:style="drawingTool === 'eyedropper' ? 'background-color: var(--accent-primary); color: white; border-color: var(--accent-primary);' : 'background-color: var(--bg-tertiary); color: var(--text-secondary); border-color: var(--border-primary);'"
@click="drawingTool = 'eyedropper'"
:title="t('drawing.tool_eyedropper')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15 11.25 1.425 1.425c.671.671.671 1.757 0 2.428l-1.06 1.06c-.671.671-1.757.671-2.428 0l-1.425-1.425m6-6 1.425-1.425c.671-.671 1.757-.671 2.428 0l1.06 1.06c.671.671.671 1.757 0 2.428L21 15.75m-6-6L8.25 3.75a2.121 2.121 0 0 0-3 3L12 15.75l3-3m-3-3-6 6" />
</svg>
</button>
<button
type="button"
class="p-2 rounded-lg border transition-colors"
:class="!drawingClearEnabled() ? 'opacity-40 cursor-not-allowed' : ''"
:style="!drawingClearEnabled()
? 'border-color: var(--border-primary); color: var(--text-secondary);'
: 'border-color: var(--border-primary); color: var(--text-secondary); background-color: var(--bg-tertiary);'"
:disabled="!drawingClearEnabled()"
@click="drawingClear()"
:title="t('drawing.clear_hint')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
<label
class="flex h-9 items-center gap-2 rounded-lg border px-2 shrink-0 cursor-pointer"
style="border-color: var(--border-primary); background-color: var(--bg-tertiary);"
:title="t('drawing.color')"
>
<span class="text-xs select-none" style="color: var(--text-secondary);" x-text="t('drawing.color')"></span>
<input
type="color"
x-model="drawingColor"
class="h-7 w-8 shrink-0 cursor-pointer rounded border p-0"
style="border-color: var(--border-primary);"
:aria-label="t('drawing.color')"
/>
</label>
<div
class="flex h-9 min-w-0 shrink items-center gap-2 rounded-lg border px-2"
style="border-color: var(--border-primary); background-color: var(--bg-tertiary);"
:title="t('drawing.width')"
>
<span class="text-xs whitespace-nowrap select-none shrink-0" style="color: var(--text-secondary);" x-text="t('drawing.width')"></span>
<input
type="range"
min="1"
max="32"
x-model.number="drawingLineWidth"
class="w-24 md:w-36 align-middle min-w-0 flex-1"
style="accent-color: var(--accent-primary);"
:aria-label="t('drawing.width')"
:aria-valuemin="1"
:aria-valuemax="32"
:aria-valuenow="drawingLineWidth"
/>
<span class="text-xs tabular-nums shrink-0 w-5 text-right" style="color: var(--text-secondary);" x-text="drawingLineWidth"></span>
</div>
</div>
<!-- Editor/Preview/Graph -->
<div class="flex-1 flex relative" style="min-height: 0;">
<!-- Editor -->
@ -2875,8 +3061,9 @@
<!-- Media Viewer (images, audio, video, PDF) -->
<template x-if="currentMedia">
<div
class="flex-1 flex items-center justify-center overflow-auto"
style="background-color: var(--bg-primary); min-height: 0;"
class="flex-1 flex overflow-auto min-h-0"
:class="currentMediaType === 'drawing' ? 'items-stretch justify-stretch' : 'items-center justify-center'"
style="background-color: var(--bg-primary);"
>
<!-- Image -->
<template x-if="currentMediaType === 'image'">
@ -2921,6 +3108,32 @@
:title="currentMedia.split('/').pop()"
></iframe>
</template>
<!-- Drawing (PNG named drawing-*.png); tools row is in header area above -->
<template x-if="currentMediaType === 'drawing'">
<div
class="flex flex-col flex-1 w-full min-w-0 min-h-0 self-stretch"
:key="currentMedia"
>
<div
x-ref="drawingCanvasWrap"
class="flex-1 w-full min-h-0 min-w-0 flex items-center justify-center"
style="min-height: 0; background-color: var(--bg-secondary);"
>
<canvas
x-ref="drawingCanvas"
class="drawing-surface block"
:style="drawingTool === 'eyedropper'
? 'touch-action: none; cursor: copy; background: #fff;'
: 'touch-action: none; cursor: crosshair; background: #fff;'"
@pointerdown.prevent="drawingPointerDown($event)"
@pointermove.prevent="drawingPointerMove($event)"
@pointerup.prevent="drawingPointerUp($event)"
@pointercancel.prevent="drawingPointerUp($event)"
></canvas>
</div>
</div>
</template>
</div>
</template>
</div>
@ -3115,6 +3328,16 @@
<span class="text-lg">📄</span>
<span x-text="t('sidebar.new_from_template')"></span>
</button>
<button
@click="createNewDrawing()"
class="w-full px-4 py-2.5 text-sm text-left flex items-center gap-3 transition-colors"
style="color: var(--text-primary);"
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
onmouseout="this.style.backgroundColor='transparent'"
>
<span class="text-lg">✏️</span>
<span x-text="t('sidebar.new_drawing')"></span>
</button>
</div>
</div>

View File

@ -4,11 +4,9 @@
"name": "Deutsch",
"flag": "🇩🇪"
},
"app": {
"tagline": "Deine selbstgehostete Wissensdatenbank"
},
"common": {
"save": "Speichern",
"cancel": "Abbrechen",
@ -28,12 +26,10 @@
"copy_to_clipboard": "In Zwischenablage kopieren",
"failed": "{{action}} fehlgeschlagen. Bitte versuche es erneut."
},
"toast": {
"region_label": "Benachrichtigungen",
"dismiss": "Schließen"
},
"sidebar": {
"title": "DATEIEN",
"favorites_title": "Favoriten",
@ -42,6 +38,7 @@
"new_note": "Neue Notiz",
"new_folder": "Neuer Ordner",
"new_from_template": "Neu aus Vorlage",
"new_drawing": "Neue Zeichnung",
"rename_folder": "Ordner umbenennen",
"delete_folder": "Ordner löschen",
"search_placeholder": "Notizen durchsuchen...",
@ -72,7 +69,6 @@
"settings_title": "EINSTELLUNGEN",
"filtered_notes": "Gefilterte Notizen"
},
"editor": {
"placeholder": "Schreibe in Markdown...",
"drop_hint": "💡 Hier ablegen, um an Cursorposition einzufügen...",
@ -85,7 +81,6 @@
"hours_ago": "vor {{count}}h",
"days_ago": "vor {{count}}T"
},
"notes": {
"confirm_delete": "\"{{name}}\" löschen?",
"already_exists": "Eine Notiz mit dem Namen \"{{name}}\" existiert bereits an diesem Ort.\nBitte wähle einen anderen Namen.",
@ -101,7 +96,6 @@
"no_content": "Kein Inhalt zum Exportieren",
"open_first": "Bitte öffne zuerst eine Notiz, bevor du Bilder hochlädst."
},
"folders": {
"confirm_delete": "⚠️ WARNUNG ⚠️\n\nBist du sicher, dass du den Ordner \"{{name}}\" löschen möchtest?\n\nDies LÖSCHT DAUERHAFT:\n• Alle Notizen in diesem Ordner\n• Alle Unterordner und deren Inhalte\n\nDiese Aktion kann NICHT rückgängig gemacht werden!",
"already_exists": "Ein Ordner mit dem Namen \"{{name}}\" existiert bereits an diesem Ort.\nBitte wähle einen anderen Namen.",
@ -118,26 +112,24 @@
"cannot_move_into_self": "Ordner kann nicht in sich selbst oder einen Unterordner verschoben werden.",
"empty": "leer"
},
"toolbar": {
"undo": "Rückgängig (Strg+Z)",
"redo": "Wiederholen (Strg+Y)",
"delete_note": "Notiz löschen",
"delete_image": "Bild löschen",
"delete_drawing": "Zeichnung löschen",
"export_html": "Als HTML exportieren",
"print_preview": "Druckvorschau",
"copy_link": "Link in Zwischenablage kopieren",
"add_favorite": "Zu Favoriten hinzufügen",
"remove_favorite": "Aus Favoriten entfernen"
},
"zen_mode": {
"title": "Zen-Modus",
"tooltip": "Zen-Modus (Strg+Alt+Z)",
"exit": "Zen-Modus beenden",
"exit_hint": "Zen-Modus beenden (Esc)"
},
"share": {
"button_tooltip": "Notiz teilen",
"modal_title": "Notiz teilen",
@ -150,9 +142,10 @@
"error_creating": "Fehler beim Erstellen des Freigabelinks: {{error}}",
"error_revoking": "Fehler beim Widerrufen des Freigabelinks: {{error}}",
"show_qr": "QR-Code anzeigen",
"hide_qr": "QR-Code ausblenden"
"hide_qr": "QR-Code ausblenden",
"panel_title": "Geteilt",
"no_shared": "Keine geteilten Notizen"
},
"quick_switcher": {
"placeholder": "Notizen suchen...",
"no_results": "Keine Ergebnisse",
@ -161,7 +154,6 @@
"open": "öffnen",
"close": "schließen"
},
"tags": {
"title": "Tags",
"no_tags": "Keine Tags gefunden",
@ -170,13 +162,11 @@
"clear_all": "Tag-Filter löschen",
"filter_by": "Nach {{tag}} filtern ({{count}} Notizen)"
},
"outline": {
"title": "Gliederung",
"no_headings": "Keine Überschriften gefunden",
"hint": "Überschriften mit # hinzufügen"
},
"backlinks": {
"title": "Rückverweise",
"no_backlinks": "Keine Rückverweise gefunden",
@ -184,21 +174,18 @@
"select_note": "Wählen Sie eine Notiz um Rückverweise zu sehen",
"more_refs": "mehr"
},
"stats": {
"words": "Wörter",
"reading_time": "~{{minutes}} Min. Lesezeit",
"links": "{{count}} Links",
"click_details": "▼ Klicken für Details"
},
"graph": {
"title": "Graphansicht",
"empty": "Keine Verbindungen anzuzeigen",
"markdown_links": "Markdown-Links",
"click_hint": "Klick: auswählen • Doppelklick: öffnen"
},
"templates": {
"title": "Vorlagen",
"select": "Vorlage auswählen...",
@ -212,11 +199,9 @@
"available_placeholders": "Verfügbare Platzhalter",
"create_note": "Notiz erstellen"
},
"export": {
"failed": "HTML-Export fehlgeschlagen: {{error}}"
},
"login": {
"title": "Anmelden",
"tagline": "Deine selbstgehostete Wissensdatenbank",
@ -225,20 +210,31 @@
"footer": "🔒 Sicher & Selbstgehostet",
"error_incorrect_password": "Falsches Passwort. Bitte versuche es erneut."
},
"drawing": {
"tool_freehand": "Stift — Freihand",
"tool_line": "Gerade Linie",
"tool_rect": "Rechteck",
"tool_ellipse": "Kreis",
"color": "Farbe",
"width": "Strichstärke",
"tool_eraser": "Radierer (malt mit Hintergrundfarbe)",
"tool_eyedropper": "Pipette — Farbe vom Bild aufnehmen",
"clear": "Leeren",
"clear_hint": "Durch leeres Bild ersetzen",
"clear_title": "Durch leere Leinwand ersetzen?",
"clear_confirm": "Die gespeicherte PNG-Datei und alle Striche in dieser Sitzung werden durch ein weißes Bild ersetzt. Dies kann nicht rückgängig gemacht werden."
},
"media": {
"confirm_delete": "\"{{name}}\" löschen?",
"upload_failed": "Datei-Upload fehlgeschlagen",
"no_valid_files": "Keine gültigen Dateien gefunden. Unterstützt: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Verschieben der Notiz fehlgeschlagen.",
"failed_folder": "Verschieben des Ordners fehlgeschlagen.",
"failed_media": "Verschieben der Mediendatei fehlgeschlagen."
},
"search": {
"previous": "Vorheriger (Umschalt+F3)",
"next": "Nächster (F3)",
@ -249,22 +245,18 @@
"no_results": "Keine Notizen enthalten \"{{query}}\"",
"searching": "Suche nach \"{{query}}\"..."
},
"theme": {
"title": "Design"
},
"language": {
"title": "Sprache"
},
"syntax_highlight": {
"title": "Syntax-Highlighting",
"description": "Markdown-Syntax im Editor einfärben",
"enable": "Syntax-Highlighting aktivieren",
"disable": "Syntax-Highlighting deaktivieren"
},
"settings": {
"account": "Konto",
"logout": "Abmelden",
@ -275,7 +267,6 @@
"tab_inserts_tab": "Tab-Taste fügt Tab ein",
"tab_inserts_tab_desc": "Tab drücken, um ein Tabulatorzeichen einzufügen statt den Fokus zu wechseln"
},
"homepage": {
"title": "Startseite",
"welcome": "Willkommen bei NoteDiscovery",
@ -288,7 +279,6 @@
"folder_singular": "Ordner",
"folder_plural": "Ordner"
},
"format": {
"bold": "Fett",
"italic": "Kursiv",
@ -304,23 +294,19 @@
"checkbox": "Kontrollkästchen",
"table": "Tabelle"
},
"validation": {
"forbidden_chars": "Der Name enthält unzulässige Zeichen: {{chars}}",
"reserved_name": "Dieser Name ist vom Betriebssystem reserviert.",
"invalid_dot": "Der Name darf nicht nur ein Punkt sein.",
"trailing_dot_space": "Der Name darf nicht mit einem Punkt oder Leerzeichen enden."
},
"support": {
"enjoying_demo": "Unterstütze dieses Projekt",
"deploy_own": "Eigene Instanz bereitstellen",
"thank_you": "Danke für deine Unterstützung! 💚"
},
"demo": {
"title": "DEMO-MODUS",
"warning": "Inhalte werden täglich zurückgesetzt. Änderungen können von anderen Benutzern überschrieben werden."
}
}

View File

@ -4,11 +4,9 @@
"name": "English",
"flag": "🇬🇧"
},
"app": {
"tagline": "Your Self-Hosted Knowledge Base"
},
"common": {
"save": "Save",
"cancel": "Cancel",
@ -28,12 +26,10 @@
"copy_to_clipboard": "Copy to clipboard",
"failed": "Failed to {{action}}. Please try again."
},
"toast": {
"region_label": "Notifications",
"dismiss": "Dismiss"
},
"sidebar": {
"title": "FILES",
"favorites_title": "Favourites",
@ -42,6 +38,7 @@
"new_note": "New Note",
"new_folder": "New Folder",
"new_from_template": "New from Template",
"new_drawing": "New Drawing",
"rename_folder": "Rename folder",
"delete_folder": "Delete folder",
"search_placeholder": "Search notes...",
@ -71,7 +68,6 @@
"settings_title": "SETTINGS",
"filtered_notes": "Filtered Notes"
},
"editor": {
"placeholder": "Start writing in markdown...",
"drop_hint": "💡 Drop here to insert at cursor position...",
@ -84,7 +80,6 @@
"hours_ago": "{{count}}h ago",
"days_ago": "{{count}}d ago"
},
"notes": {
"confirm_delete": "Delete \"{{name}}\"?",
"already_exists": "A note named \"{{name}}\" already exists in this location.\nPlease choose a different name.",
@ -100,7 +95,6 @@
"no_content": "No note content to export",
"open_first": "Please open a note first before uploading images."
},
"folders": {
"confirm_delete": "⚠️ WARNING ⚠️\n\nAre you sure you want to delete the folder \"{{name}}\"?\n\nThis will PERMANENTLY delete:\n• All notes inside this folder\n• All subfolders and their contents\n\nThis action CANNOT be undone!",
"already_exists": "A folder named \"{{name}}\" already exists in this location.\nPlease choose a different name.",
@ -117,26 +111,24 @@
"cannot_move_into_self": "Cannot move folder into itself or its subfolder.",
"empty": "empty"
},
"toolbar": {
"undo": "Undo (Ctrl+Z)",
"redo": "Redo (Ctrl+Y)",
"delete_note": "Delete note",
"delete_image": "Delete image",
"delete_drawing": "Delete drawing",
"export_html": "Export as HTML",
"print_preview": "Print preview",
"copy_link": "Copy link to clipboard",
"add_favorite": "Add to favourites",
"remove_favorite": "Remove from favourites"
},
"zen_mode": {
"title": "Zen Mode",
"tooltip": "Zen Mode (Ctrl+Alt+Z)",
"exit": "Exit Zen Mode",
"exit_hint": "Exit Zen Mode (Esc)"
},
"share": {
"button_tooltip": "Share note",
"modal_title": "Share Note",
@ -149,9 +141,10 @@
"error_creating": "Failed to create share link: {{error}}",
"error_revoking": "Failed to revoke share link: {{error}}",
"show_qr": "Show QR Code",
"hide_qr": "Hide QR Code"
"hide_qr": "Hide QR Code",
"panel_title": "Shared",
"no_shared": "No shared notes"
},
"quick_switcher": {
"placeholder": "Type to search notes...",
"no_results": "No matching notes",
@ -160,7 +153,6 @@
"open": "open",
"close": "close"
},
"tags": {
"title": "Tags",
"no_tags": "No tags found",
@ -169,13 +161,11 @@
"clear_all": "Clear tag filters",
"filter_by": "Filter by {{tag}} ({{count}} notes)"
},
"outline": {
"title": "Outline",
"no_headings": "No headings found",
"hint": "Add headings using # syntax"
},
"backlinks": {
"title": "Backlinks",
"no_backlinks": "No backlinks found",
@ -183,21 +173,18 @@
"select_note": "Select a note to see backlinks",
"more_refs": "more"
},
"stats": {
"words": "words",
"reading_time": "~{{minutes}}m read",
"links": "{{count}} links",
"click_details": "▼ Click for details"
},
"graph": {
"title": "Graph View",
"empty": "No connections to display",
"markdown_links": "Markdown links",
"click_hint": "Click: select • Double-click: open"
},
"templates": {
"title": "Templates",
"select": "Select a template...",
@ -211,11 +198,9 @@
"available_placeholders": "Available placeholders",
"create_note": "Create Note"
},
"export": {
"failed": "Failed to export HTML: {{error}}"
},
"login": {
"title": "Login",
"tagline": "Your Self-Hosted Knowledge Base",
@ -224,20 +209,31 @@
"footer": "🔒 Secure & Self-Hosted",
"error_incorrect_password": "Incorrect password. Please try again."
},
"drawing": {
"tool_freehand": "Pencil — freehand drawing",
"tool_line": "Straight line",
"tool_rect": "Rectangle",
"tool_ellipse": "Circle",
"color": "Colour",
"width": "Stroke width",
"tool_eraser": "Eraser (paints with background colour)",
"tool_eyedropper": "Eyedropper — pick a colour from the canvas",
"clear": "Clear",
"clear_hint": "Replace with a blank image",
"clear_title": "Replace with a blank canvas?",
"clear_confirm": "The saved PNG and all strokes in this session will be replaced with a blank white image. You cannot undo this."
},
"media": {
"confirm_delete": "Delete \"{{name}}\"?",
"upload_failed": "Failed to upload file",
"no_valid_files": "No valid files found. Supported: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Failed to move note.",
"failed_folder": "Failed to move folder.",
"failed_media": "Failed to move media file."
},
"search": {
"previous": "Previous (Shift+F3)",
"next": "Next (F3)",
@ -248,22 +244,18 @@
"no_results": "No notes contain \"{{query}}\"",
"searching": "Searching for \"{{query}}\"..."
},
"theme": {
"title": "Theme"
},
"language": {
"title": "Language"
},
"syntax_highlight": {
"title": "Editor Syntax Highlight",
"description": "Colourise markdown syntax in editor",
"enable": "Enable syntax highlighting",
"disable": "Disable syntax highlighting"
},
"settings": {
"account": "Account",
"logout": "Logout",
@ -274,7 +266,6 @@
"tab_inserts_tab": "Tab key inserts tab",
"tab_inserts_tab_desc": "Press Tab to insert a tab character instead of changing focus"
},
"homepage": {
"title": "Home",
"welcome": "Welcome to NoteDiscovery",
@ -287,7 +278,6 @@
"folder_singular": "folder",
"folder_plural": "folders"
},
"format": {
"bold": "Bold",
"italic": "Italic",
@ -303,23 +293,19 @@
"checkbox": "Checkbox",
"table": "Table"
},
"validation": {
"forbidden_chars": "Name contains forbidden characters: {{chars}}",
"reserved_name": "This name is reserved by the operating system.",
"invalid_dot": "Name cannot be just a dot.",
"trailing_dot_space": "Name cannot end with a dot or space."
},
"support": {
"enjoying_demo": "Support this project",
"deploy_own": "Deploy your own instance",
"thank_you": "Thank you for your support! 💚"
},
"demo": {
"title": "DEMO MODE",
"warning": "Contents reset daily. Changes may be overwritten by other users."
}
}

View File

@ -4,11 +4,9 @@
"name": "English",
"flag": "🇺🇸"
},
"app": {
"tagline": "Your Self-Hosted Knowledge Base"
},
"common": {
"save": "Save",
"cancel": "Cancel",
@ -28,12 +26,10 @@
"copy_to_clipboard": "Copy to clipboard",
"failed": "Failed to {{action}}. Please try again."
},
"toast": {
"region_label": "Notifications",
"dismiss": "Dismiss"
},
"sidebar": {
"title": "FILES",
"favorites_title": "Favorites",
@ -42,6 +38,7 @@
"new_note": "New Note",
"new_folder": "New Folder",
"new_from_template": "New from Template",
"new_drawing": "New Drawing",
"rename_folder": "Rename folder",
"delete_folder": "Delete folder",
"search_placeholder": "Search notes...",
@ -72,7 +69,6 @@
"settings_title": "SETTINGS",
"filtered_notes": "Filtered Notes"
},
"editor": {
"placeholder": "Start writing in markdown...",
"drop_hint": "💡 Drop here to insert at cursor position...",
@ -85,7 +81,6 @@
"hours_ago": "{{count}}h ago",
"days_ago": "{{count}}d ago"
},
"notes": {
"confirm_delete": "Delete \"{{name}}\"?",
"already_exists": "A note named \"{{name}}\" already exists in this location.\nPlease choose a different name.",
@ -101,7 +96,6 @@
"no_content": "No note content to export",
"open_first": "Please open a note first before uploading images."
},
"folders": {
"confirm_delete": "⚠️ WARNING ⚠️\n\nAre you sure you want to delete the folder \"{{name}}\"?\n\nThis will PERMANENTLY delete:\n• All notes inside this folder\n• All subfolders and their contents\n\nThis action CANNOT be undone!",
"already_exists": "A folder named \"{{name}}\" already exists in this location.\nPlease choose a different name.",
@ -118,26 +112,24 @@
"cannot_move_into_self": "Cannot move folder into itself or its subfolder.",
"empty": "empty"
},
"toolbar": {
"undo": "Undo (Ctrl+Z)",
"redo": "Redo (Ctrl+Y)",
"delete_note": "Delete note",
"delete_image": "Delete image",
"delete_drawing": "Delete drawing",
"export_html": "Export as HTML",
"print_preview": "Print preview",
"copy_link": "Copy link to clipboard",
"add_favorite": "Add to favorites",
"remove_favorite": "Remove from favorites"
},
"zen_mode": {
"title": "Zen Mode",
"tooltip": "Zen Mode (Ctrl+Alt+Z)",
"exit": "Exit Zen Mode",
"exit_hint": "Exit Zen Mode (Esc)"
},
"share": {
"button_tooltip": "Share note",
"modal_title": "Share Note",
@ -150,9 +142,10 @@
"error_creating": "Failed to create share link: {{error}}",
"error_revoking": "Failed to revoke share link: {{error}}",
"show_qr": "Show QR Code",
"hide_qr": "Hide QR Code"
"hide_qr": "Hide QR Code",
"panel_title": "Shared",
"no_shared": "No shared notes"
},
"quick_switcher": {
"placeholder": "Type to search notes...",
"no_results": "No matching notes",
@ -161,7 +154,6 @@
"open": "open",
"close": "close"
},
"tags": {
"title": "Tags",
"no_tags": "No tags found",
@ -170,13 +162,11 @@
"clear_all": "Clear tag filters",
"filter_by": "Filter by {{tag}} ({{count}} notes)"
},
"outline": {
"title": "Outline",
"no_headings": "No headings found",
"hint": "Add headings using # syntax"
},
"backlinks": {
"title": "Backlinks",
"no_backlinks": "No backlinks found",
@ -184,21 +174,18 @@
"select_note": "Select a note to see backlinks",
"more_refs": "more"
},
"stats": {
"words": "words",
"reading_time": "~{{minutes}}m read",
"links": "{{count}} links",
"click_details": "▼ Click for details"
},
"graph": {
"title": "Graph View",
"empty": "No connections to display",
"markdown_links": "Markdown links",
"click_hint": "Click: select • Double-click: open"
},
"templates": {
"title": "Templates",
"select": "Select a template...",
@ -212,11 +199,9 @@
"available_placeholders": "Available placeholders",
"create_note": "Create Note"
},
"export": {
"failed": "Failed to export HTML: {{error}}"
},
"login": {
"title": "Login",
"tagline": "Your Self-Hosted Knowledge Base",
@ -225,20 +210,31 @@
"footer": "🔒 Secure & Self-Hosted",
"error_incorrect_password": "Incorrect password. Please try again."
},
"drawing": {
"tool_freehand": "Pencil — freehand drawing",
"tool_line": "Straight line",
"tool_rect": "Rectangle",
"tool_ellipse": "Circle",
"color": "Color",
"width": "Stroke width",
"tool_eraser": "Eraser (paints with background color)",
"tool_eyedropper": "Eyedropper — pick a color from the canvas",
"clear": "Clear",
"clear_hint": "Replace with a blank image",
"clear_title": "Replace with a blank canvas?",
"clear_confirm": "The saved PNG and all strokes in this session will be replaced with a blank white image. You cannot undo this."
},
"media": {
"confirm_delete": "Delete \"{{name}}\"?",
"upload_failed": "Failed to upload file",
"no_valid_files": "No valid files found. Supported: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Failed to move note.",
"failed_folder": "Failed to move folder.",
"failed_media": "Failed to move media file."
},
"search": {
"previous": "Previous (Shift+F3)",
"next": "Next (F3)",
@ -249,22 +245,18 @@
"no_results": "No notes contain \"{{query}}\"",
"searching": "Searching for \"{{query}}\"..."
},
"theme": {
"title": "Theme"
},
"language": {
"title": "Language"
},
"syntax_highlight": {
"title": "Editor Syntax Highlight",
"description": "Colorize markdown syntax in editor",
"enable": "Enable syntax highlighting",
"disable": "Disable syntax highlighting"
},
"settings": {
"account": "Account",
"logout": "Logout",
@ -275,7 +267,6 @@
"tab_inserts_tab": "Tab key inserts tab",
"tab_inserts_tab_desc": "Press Tab to insert a tab character instead of changing focus"
},
"homepage": {
"title": "Home",
"welcome": "Welcome to NoteDiscovery",
@ -288,7 +279,6 @@
"folder_singular": "folder",
"folder_plural": "folders"
},
"format": {
"bold": "Bold",
"italic": "Italic",
@ -304,23 +294,19 @@
"checkbox": "Checkbox",
"table": "Table"
},
"validation": {
"forbidden_chars": "Name contains forbidden characters: {{chars}}",
"reserved_name": "This name is reserved by the operating system.",
"invalid_dot": "Name cannot be just a dot.",
"trailing_dot_space": "Name cannot end with a dot or space."
},
"support": {
"enjoying_demo": "Support this project",
"deploy_own": "Deploy your own instance",
"thank_you": "Thank you for your support! 💚"
},
"demo": {
"title": "DEMO MODE",
"warning": "Contents reset daily. Changes may be overwritten by other users."
}
}

View File

@ -4,11 +4,9 @@
"name": "Español",
"flag": "🇪🇸"
},
"app": {
"tagline": "Tu Base de Conocimientos Autoalojada"
},
"common": {
"save": "Guardar",
"cancel": "Cancelar",
@ -28,12 +26,10 @@
"copy_to_clipboard": "Copiar al portapapeles",
"failed": "Error al {{action}}. Por favor, inténtalo de nuevo."
},
"toast": {
"region_label": "Notificaciones",
"dismiss": "Cerrar"
},
"sidebar": {
"title": "ARCHIVOS",
"favorites_title": "Favoritos",
@ -42,6 +38,7 @@
"new_note": "Nueva Nota",
"new_folder": "Nueva Carpeta",
"new_from_template": "Nueva desde Plantilla",
"new_drawing": "Nuevo dibujo",
"rename_folder": "Renombrar carpeta",
"delete_folder": "Eliminar carpeta",
"search_placeholder": "Buscar notas...",
@ -72,7 +69,6 @@
"settings_title": "CONFIGURACIÓN",
"filtered_notes": "Notas Filtradas"
},
"editor": {
"placeholder": "Empieza a escribir en markdown...",
"drop_hint": "💡 Suelta aquí para insertar en la posición del cursor...",
@ -85,7 +81,6 @@
"hours_ago": "hace {{count}}h",
"days_ago": "hace {{count}}d"
},
"notes": {
"confirm_delete": "¿Eliminar \"{{name}}\"?",
"already_exists": "Ya existe una nota llamada \"{{name}}\" en esta ubicación.\nPor favor, elige un nombre diferente.",
@ -101,7 +96,6 @@
"no_content": "No hay contenido para exportar",
"open_first": "Por favor, abre una nota antes de subir imágenes."
},
"folders": {
"confirm_delete": "⚠️ ADVERTENCIA ⚠️\n\n¿Estás seguro de que quieres eliminar la carpeta \"{{name}}\"?\n\nEsto eliminará PERMANENTEMENTE:\n• Todas las notas dentro de esta carpeta\n• Todas las subcarpetas y su contenido\n\n¡Esta acción NO se puede deshacer!",
"already_exists": "Ya existe una carpeta llamada \"{{name}}\" en esta ubicación.\nPor favor, elige un nombre diferente.",
@ -118,26 +112,24 @@
"cannot_move_into_self": "No se puede mover una carpeta dentro de sí misma o de una subcarpeta.",
"empty": "vacía"
},
"toolbar": {
"undo": "Deshacer (Ctrl+Z)",
"redo": "Rehacer (Ctrl+Y)",
"delete_note": "Eliminar nota",
"delete_image": "Eliminar imagen",
"delete_drawing": "Eliminar dibujo",
"export_html": "Exportar como HTML",
"print_preview": "Vista previa de impresión",
"copy_link": "Copiar enlace al portapapeles",
"add_favorite": "Añadir a favoritos",
"remove_favorite": "Quitar de favoritos"
},
"zen_mode": {
"title": "Modo Zen",
"tooltip": "Modo Zen (Ctrl+Alt+Z)",
"exit": "Salir del Modo Zen",
"exit_hint": "Salir del Modo Zen (Esc)"
},
"share": {
"button_tooltip": "Compartir nota",
"modal_title": "Compartir Nota",
@ -150,9 +142,10 @@
"error_creating": "Error al crear el enlace: {{error}}",
"error_revoking": "Error al revocar el enlace: {{error}}",
"show_qr": "Mostrar código QR",
"hide_qr": "Ocultar código QR"
"hide_qr": "Ocultar código QR",
"panel_title": "Compartidas",
"no_shared": "No hay notas compartidas"
},
"quick_switcher": {
"placeholder": "Escribe para buscar notas...",
"no_results": "Sin resultados",
@ -161,7 +154,6 @@
"open": "abrir",
"close": "cerrar"
},
"tags": {
"title": "Etiquetas",
"no_tags": "No se encontraron etiquetas",
@ -170,13 +162,11 @@
"clear_all": "Limpiar filtros de etiquetas",
"filter_by": "Filtrar por {{tag}} ({{count}} notas)"
},
"outline": {
"title": "Esquema",
"no_headings": "No se encontraron encabezados",
"hint": "Añade encabezados usando sintaxis #"
},
"backlinks": {
"title": "Backlinks",
"no_backlinks": "No se encontraron backlinks",
@ -184,21 +174,18 @@
"select_note": "Selecciona una nota para ver backlinks",
"more_refs": "más"
},
"stats": {
"words": "palabras",
"reading_time": "~{{minutes}}m de lectura",
"links": "{{count}} enlaces",
"click_details": "▼ Clic para detalles"
},
"graph": {
"title": "Vista de Grafo",
"empty": "No hay conexiones para mostrar",
"markdown_links": "Enlaces Markdown",
"click_hint": "Clic: seleccionar • Doble clic: abrir"
},
"templates": {
"title": "Plantillas",
"select": "Selecciona una plantilla...",
@ -212,11 +199,9 @@
"available_placeholders": "Marcadores disponibles",
"create_note": "Crear Nota"
},
"export": {
"failed": "Error al exportar HTML: {{error}}"
},
"login": {
"title": "Iniciar sesión",
"tagline": "Tu Base de Conocimientos Autoalojada",
@ -225,20 +210,31 @@
"footer": "🔒 Seguro y Autoalojado",
"error_incorrect_password": "Contraseña incorrecta. Por favor, inténtalo de nuevo."
},
"drawing": {
"tool_freehand": "Lápiz — trazo libre",
"tool_line": "Línea recta",
"tool_rect": "Rectángulo",
"tool_ellipse": "Círculo",
"color": "Color",
"width": "Grosor del trazo",
"tool_eraser": "Borrador (pinta con el color de fondo)",
"tool_eyedropper": "Cuentagotas — tomar color del lienzo",
"clear": "Borrar",
"clear_hint": "Sustituir por imagen en blanco",
"clear_title": "¿Sustituir por lienzo en blanco?",
"clear_confirm": "El PNG guardado y todos los trazos de esta sesión se sustituirán por una imagen en blanco. No se puede deshacer."
},
"media": {
"confirm_delete": "¿Eliminar \"{{name}}\"?",
"upload_failed": "Error al subir archivo",
"no_valid_files": "No se encontraron archivos válidos. Soporta: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Error al mover la nota.",
"failed_folder": "Error al mover la carpeta.",
"failed_media": "Error al mover el archivo multimedia."
},
"search": {
"previous": "Anterior (Shift+F3)",
"next": "Siguiente (F3)",
@ -249,22 +245,18 @@
"no_results": "Ninguna nota contiene \"{{query}}\"",
"searching": "Buscando \"{{query}}\"..."
},
"theme": {
"title": "Tema"
},
"language": {
"title": "Idioma"
},
"syntax_highlight": {
"title": "Resaltado de Sintaxis del Editor",
"description": "Colorear sintaxis markdown en el editor",
"enable": "Activar resaltado de sintaxis",
"disable": "Desactivar resaltado de sintaxis"
},
"settings": {
"account": "Cuenta",
"logout": "Cerrar sesión",
@ -275,7 +267,6 @@
"tab_inserts_tab": "Tabulador inserta tabulación",
"tab_inserts_tab_desc": "Pulsa Tab para insertar un tabulador en lugar de cambiar el foco"
},
"homepage": {
"title": "Inicio",
"welcome": "Bienvenido a NoteDiscovery",
@ -288,7 +279,6 @@
"folder_singular": "carpeta",
"folder_plural": "carpetas"
},
"format": {
"bold": "Negrita",
"italic": "Cursiva",
@ -304,23 +294,19 @@
"checkbox": "Casilla de verificación",
"table": "Tabla"
},
"validation": {
"forbidden_chars": "El nombre contiene caracteres no permitidos: {{chars}}",
"reserved_name": "Este nombre está reservado por el sistema operativo.",
"invalid_dot": "El nombre no puede ser solo un punto.",
"trailing_dot_space": "El nombre no puede terminar con un punto o espacio."
},
"support": {
"enjoying_demo": "Apoya este proyecto",
"deploy_own": "Despliega tu propia instancia",
"thank_you": "¡Gracias por tu apoyo! 💚"
},
"demo": {
"title": "MODO DEMO",
"warning": "El contenido se reinicia diariamente. Los cambios pueden ser sobrescritos por otros usuarios."
}
}

View File

@ -4,11 +4,9 @@
"name": "Français",
"flag": "🇫🇷"
},
"app": {
"tagline": "Votre Base de Connaissances Auto-Hébergée"
},
"common": {
"save": "Enregistrer",
"cancel": "Annuler",
@ -28,12 +26,10 @@
"copy_to_clipboard": "Copier dans le presse-papiers",
"failed": "Échec de {{action}}. Veuillez réessayer."
},
"toast": {
"region_label": "Notifications",
"dismiss": "Fermer"
},
"sidebar": {
"title": "FICHIERS",
"favorites_title": "Favoris",
@ -42,6 +38,7 @@
"new_note": "Nouvelle Note",
"new_folder": "Nouveau Dossier",
"new_from_template": "Nouveau depuis Modèle",
"new_drawing": "Nouveau dessin",
"rename_folder": "Renommer le dossier",
"delete_folder": "Supprimer le dossier",
"search_placeholder": "Rechercher des notes...",
@ -72,7 +69,6 @@
"settings_title": "PARAMÈTRES",
"filtered_notes": "Notes Filtrées"
},
"editor": {
"placeholder": "Commencez à écrire en markdown...",
"drop_hint": "💡 Déposez ici pour insérer à la position du curseur...",
@ -85,7 +81,6 @@
"hours_ago": "il y a {{count}}h",
"days_ago": "il y a {{count}}j"
},
"notes": {
"confirm_delete": "Supprimer \"{{name}}\" ?",
"already_exists": "Une note nommée \"{{name}}\" existe déjà à cet emplacement.\nVeuillez choisir un autre nom.",
@ -101,7 +96,6 @@
"no_content": "Aucun contenu à exporter",
"open_first": "Veuillez d'abord ouvrir une note avant de télécharger des images."
},
"folders": {
"confirm_delete": "⚠️ ATTENTION ⚠️\n\nÊtes-vous sûr de vouloir supprimer le dossier \"{{name}}\" ?\n\nCeci supprimera DÉFINITIVEMENT :\n• Toutes les notes dans ce dossier\n• Tous les sous-dossiers et leur contenu\n\nCette action est IRRÉVERSIBLE !",
"already_exists": "Un dossier nommé \"{{name}}\" existe déjà à cet emplacement.\nVeuillez choisir un autre nom.",
@ -118,26 +112,24 @@
"cannot_move_into_self": "Impossible de déplacer un dossier dans lui-même ou dans un sous-dossier.",
"empty": "vide"
},
"toolbar": {
"undo": "Annuler (Ctrl+Z)",
"redo": "Rétablir (Ctrl+Y)",
"delete_note": "Supprimer la note",
"delete_image": "Supprimer l'image",
"delete_drawing": "Supprimer le dessin",
"export_html": "Exporter en HTML",
"print_preview": "Aperçu avant impression",
"copy_link": "Copier le lien dans le presse-papiers",
"add_favorite": "Ajouter aux favoris",
"remove_favorite": "Retirer des favoris"
},
"zen_mode": {
"title": "Mode Zen",
"tooltip": "Mode Zen (Ctrl+Alt+Z)",
"exit": "Quitter le Mode Zen",
"exit_hint": "Quitter le Mode Zen (Échap)"
},
"share": {
"button_tooltip": "Partager la note",
"modal_title": "Partager la Note",
@ -150,9 +142,10 @@
"error_creating": "Échec de la création du lien : {{error}}",
"error_revoking": "Échec de la révocation du lien : {{error}}",
"show_qr": "Afficher le code QR",
"hide_qr": "Masquer le code QR"
"hide_qr": "Masquer le code QR",
"panel_title": "Partagées",
"no_shared": "Aucune note partagée"
},
"quick_switcher": {
"placeholder": "Rechercher des notes...",
"no_results": "Aucun résultat",
@ -161,7 +154,6 @@
"open": "ouvrir",
"close": "fermer"
},
"tags": {
"title": "Étiquettes",
"no_tags": "Aucune étiquette trouvée",
@ -170,13 +162,11 @@
"clear_all": "Effacer les filtres d'étiquettes",
"filter_by": "Filtrer par {{tag}} ({{count}} notes)"
},
"outline": {
"title": "Plan",
"no_headings": "Aucun titre trouvé",
"hint": "Ajoutez des titres avec la syntaxe #"
},
"backlinks": {
"title": "Rétroliens",
"no_backlinks": "Aucun rétrolien trouvé",
@ -184,21 +174,18 @@
"select_note": "Sélectionnez une note pour voir les rétroliens",
"more_refs": "de plus"
},
"stats": {
"words": "mots",
"reading_time": "~{{minutes}}m de lecture",
"links": "{{count}} liens",
"click_details": "▼ Cliquez pour les détails"
},
"graph": {
"title": "Vue Graphique",
"empty": "Aucune connexion à afficher",
"markdown_links": "Liens Markdown",
"click_hint": "Clic : sélectionner • Double-clic : ouvrir"
},
"templates": {
"title": "Modèles",
"select": "Sélectionner un modèle...",
@ -212,11 +199,9 @@
"available_placeholders": "Variables disponibles",
"create_note": "Créer la Note"
},
"export": {
"failed": "Échec de l'export HTML : {{error}}"
},
"login": {
"title": "Connexion",
"tagline": "Votre Base de Connaissances Auto-Hébergée",
@ -225,20 +210,31 @@
"footer": "🔒 Sécurisé et Auto-Hébergé",
"error_incorrect_password": "Mot de passe incorrect. Veuillez réessayer."
},
"drawing": {
"tool_freehand": "Crayon — main levée",
"tool_line": "Ligne droite",
"tool_rect": "Rectangle",
"tool_ellipse": "Cercle",
"color": "Couleur",
"width": "Épaisseur du trait",
"tool_eraser": "Gomme (peint avec la couleur de fond)",
"tool_eyedropper": "Pipette — choisir une couleur sur le canevas",
"clear": "Effacer",
"clear_hint": "Remplacer par une image vide",
"clear_title": "Remplacer par un canevas vide ?",
"clear_confirm": "Le PNG enregistré et tous les traits de cette session seront remplacés par une image blanche vide. Cette action est irréversible."
},
"media": {
"confirm_delete": "Supprimer \"{{name}}\" ?",
"upload_failed": "Échec du téléchargement du fichier",
"no_valid_files": "Aucun fichier valide trouvé. Supporté : JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Échec du déplacement de la note.",
"failed_folder": "Échec du déplacement du dossier.",
"failed_media": "Échec du déplacement du fichier multimédia."
},
"search": {
"previous": "Précédent (Maj+F3)",
"next": "Suivant (F3)",
@ -249,22 +245,18 @@
"no_results": "Aucune note ne contient \"{{query}}\"",
"searching": "Recherche de \"{{query}}\"..."
},
"theme": {
"title": "Thème"
},
"language": {
"title": "Langue"
},
"syntax_highlight": {
"title": "Coloration Syntaxique de l'Éditeur",
"description": "Coloriser la syntaxe markdown dans l'éditeur",
"enable": "Activer la coloration syntaxique",
"disable": "Désactiver la coloration syntaxique"
},
"settings": {
"account": "Compte",
"logout": "Déconnexion",
@ -275,7 +267,6 @@
"tab_inserts_tab": "Tab insère une tabulation",
"tab_inserts_tab_desc": "Appuyez sur Tab pour insérer une tabulation au lieu de changer le focus"
},
"homepage": {
"title": "Accueil",
"welcome": "Bienvenue sur NoteDiscovery",
@ -288,7 +279,6 @@
"folder_singular": "dossier",
"folder_plural": "dossiers"
},
"format": {
"bold": "Gras",
"italic": "Italique",
@ -304,23 +294,19 @@
"checkbox": "Case à cocher",
"table": "Tableau"
},
"validation": {
"forbidden_chars": "Le nom contient des caractères interdits : {{chars}}",
"reserved_name": "Ce nom est réservé par le système d'exploitation.",
"invalid_dot": "Le nom ne peut pas être juste un point.",
"trailing_dot_space": "Le nom ne peut pas se terminer par un point ou un espace."
},
"support": {
"enjoying_demo": "Soutenez ce projet",
"deploy_own": "Déployez votre propre instance",
"thank_you": "Merci pour votre soutien ! 💚"
},
"demo": {
"title": "MODE DÉMO",
"warning": "Le contenu est réinitialisé quotidiennement. Les modifications peuvent être écrasées par d'autres utilisateurs."
}
}

View File

@ -4,11 +4,9 @@
"name": "Magyar",
"flag": "🇭🇺"
},
"app": {
"tagline": "A Saját, Önálló Tudásbázisod"
},
"common": {
"save": "Mentés",
"cancel": "Mégse",
@ -28,12 +26,10 @@
"copy_to_clipboard": "Másolás",
"failed": "Hiba a(z) {{action}} során. Kérlek, próbáld újra."
},
"toast": {
"region_label": "Értesítések",
"dismiss": "Bezárás"
},
"sidebar": {
"title": "FÁJLOK",
"favorites_title": "Kedvencek",
@ -42,6 +38,7 @@
"new_note": "Új Jegyzet",
"new_folder": "Új Mappa",
"new_from_template": "Új Sablonból",
"new_drawing": "Új rajz",
"rename_folder": "Mappa átnevezése",
"delete_folder": "Mappa törlése",
"search_placeholder": "Jegyzetek keresése...",
@ -72,7 +69,6 @@
"settings_title": "BEÁLLÍTÁSOK",
"filtered_notes": "Szűrt jegyzetek"
},
"editor": {
"placeholder": "Kezdj el írni markdown-ban...",
"drop_hint": "💡 Beszúrás ide...",
@ -85,7 +81,6 @@
"hours_ago": "{{count}} órája",
"days_ago": "{{count}} napja"
},
"notes": {
"confirm_delete": "Biztos törlöd a következőt: \"{{name}}\"?",
"already_exists": "A(z) \"{{name}}\" nevű jegyzet már létezik.\nKérlek válassz egy másik nevet.",
@ -101,7 +96,6 @@
"no_content": "Nincs exportálható jegyzettartalom",
"open_first": "Kérlek előbb nyiss meg egy jegyzetet, mielőtt képeket töltenél fel."
},
"folders": {
"confirm_delete": "⚠️ FIGYELMEZTETÉS ⚠️\n\nBiztosan törölni szeretnéd a(z) \"{{name}}\" mappát?\n\nEz VÉGLEGESEN törli a következőket:\n• A mappában található összes jegyzet\n• Az összes almappa és azok tartalma \n\nEz a művelet NEM vonható vissza!",
"already_exists": "A(z) \"{{name}}\" nevű mappa már létezik.\nKérlek válassz egy másik nevet.",
@ -118,26 +112,24 @@
"cannot_move_into_self": "A mappa nem mozgatható saját magába vagy bármelyik almappájába.",
"empty": "üres"
},
"toolbar": {
"undo": "Visszavon (Ctrl+Z)",
"redo": "Helyrehoz (Ctrl+Y)",
"delete_note": "Jegyzet törlése",
"delete_image": "Kép törlése",
"delete_drawing": "Rajz törlése",
"export_html": "Exportálás HTML-ként",
"print_preview": "Nyomtatási előnézet",
"copy_link": "Link másolása",
"add_favorite": "Hozzáadás a kedvencekhez",
"remove_favorite": "Eltávolítás a kedvencek közül"
},
"zen_mode": {
"title": "Zen Mód",
"tooltip": "Zen Mód (Ctrl+Alt+Z)",
"exit": "Kilépés a Zen Módból",
"exit_hint": "Kilépés a Zen Módból (Esc)"
},
"share": {
"button_tooltip": "Jegyzet megosztása",
"modal_title": "Jegyzet Megosztása",
@ -150,9 +142,10 @@
"error_creating": "Hiba történt a megosztási link létrehozásánál: {{error}}",
"error_revoking": "Nemsikerült visszavonni a megosztási linket: {{error}}",
"show_qr": "QR-Kód Megjelenítése",
"hide_qr": "QR-Kód Elrejtése"
"hide_qr": "QR-Kód Elrejtése",
"panel_title": "Megosztott",
"no_shared": "Nincs megosztott jegyzet"
},
"quick_switcher": {
"placeholder": "Írj a jegyzetek kereséséhez...",
"no_results": "Nincs találat",
@ -161,7 +154,6 @@
"open": "nyit",
"close": "csuk"
},
"tags": {
"title": "Címkék",
"no_tags": "Nem találhatók címkék",
@ -170,13 +162,11 @@
"clear_all": "Szűrők törlése",
"filter_by": "Szűrés {{tag}} ({{count}} jegyzetek)"
},
"outline": {
"title": "Fejlécek",
"no_headings": "Nincsenek fejlécek",
"hint": "Fejléc hozzáadása a következővel: # "
},
"backlinks": {
"title": "Bejövő linkek",
"no_backlinks": "Nincsenek bejövő linkek",
@ -184,21 +174,18 @@
"select_note": "Válassz ki egy jegyzetet a bejövő linkek megtekintéséhez",
"more_refs": "továbbiak"
},
"stats": {
"words": "szavak",
"reading_time": "~{{minutes}} percnyi olvasás",
"links": "{{count}} hivatkozások",
"click_details": "▼ Kattints a részletekért"
},
"graph": {
"title": "Grafikon nézet",
"empty": "Nincsenek megjeleníthető kapcsolatok",
"markdown_links": "Markdown hivatkozások",
"click_hint": "Kattints: kiválasztás • Dupla kattintás: megnyitás"
},
"templates": {
"title": "Sablonok",
"select": "Válassz egy sablont...",
@ -212,11 +199,9 @@
"available_placeholders": "Elérhető helyőrzők",
"create_note": "Jegyzet létrehozása"
},
"export": {
"failed": "Hiba a HTML exportálása során: {{error}}"
},
"login": {
"title": "Bejelentkezés",
"tagline": "A Saját, Önálló Tudásbázisod",
@ -225,20 +210,31 @@
"footer": "🔒 Biztonságos & Saját Kiszolgálású",
"error_incorrect_password": "Helytelen jelszó. Próbáld újra."
},
"drawing": {
"tool_freehand": "Ceruza — szabadkézi",
"tool_line": "Egyenes vonal",
"tool_rect": "Téglalap",
"tool_ellipse": "Kör",
"color": "Szín",
"width": "Vonalvastagság",
"tool_eraser": "Radír (háttérszínnel fest)",
"tool_eyedropper": "Pipetta — szín mintavétele a vászonról",
"clear": "Törlés",
"clear_hint": "Üres képre cserélés",
"clear_title": "Üres vászonra cserélés?",
"clear_confirm": "A mentett PNG és az ebben a munkamenetben rajzolt vonalak fehér üres képre cserélődnek. Ez nem vonható vissza."
},
"media": {
"confirm_delete": "Biztos törlöd a következőt: \"{{name}}\"?",
"upload_failed": "Hiba a fájl feltöltése során",
"no_valid_files": "Nem található érvényes fájl. Támogatott formátumok: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Hiba a jegyzet mozgatásánál.",
"failed_folder": "Hiba a mappa mozgatásánál.",
"failed_media": "Hiba a médiafájl mozgatásánál."
},
"search": {
"previous": "Előző (Shift+F3)",
"next": "Következő (F3)",
@ -249,22 +245,18 @@
"no_results": "Nincs jegyzet amely tartalmazza a következőt: \"{{query}}\"",
"searching": "\"{{query}}\" keresése..."
},
"theme": {
"title": "Téma"
},
"language": {
"title": "Nyelv"
},
"syntax_highlight": {
"title": "Szintaxisok kiemelése",
"description": "Markdown szintaxisok színezése",
"enable": "Szintaxiskiemelések engedélyezése",
"disable": "Szintaxiskiemelések letiltása"
},
"settings": {
"account": "Fiók",
"logout": "Kijelentkezés",
@ -275,7 +267,6 @@
"tab_inserts_tab": "Tab billentyű több helyet hagy a szövegben",
"tab_inserts_tab_desc": "Nyomd meg a Tab-ot, hogy több helyet hagyj a szövegben a fókuszváltás helyett"
},
"homepage": {
"title": "Főoldal",
"welcome": "Üdvözöllek a NoteDiscovery-ben",
@ -288,7 +279,6 @@
"folder_singular": "mappa",
"folder_plural": "mappa"
},
"format": {
"bold": "Félkövér",
"italic": "Dőlt",
@ -304,20 +294,17 @@
"checkbox": "Jelölőnégyzet",
"table": "Táblázat"
},
"validation": {
"forbidden_chars": "A név tiltott karaktereket tartalmaz: {{chars}}",
"reserved_name": "Ezt a nevet az operációs rendszer tartja fenn.",
"invalid_dot": "A név nem állhat csak egy pontból.",
"trailing_dot_space": "A név nem végződhet ponttal vagy szóközzel."
},
"support": {
"enjoying_demo": "Támogasd ezt a projektet",
"deploy_own": "Saját példány telepítése",
"thank_you": "Köszönjük a támogatásodat! 💚"
},
"demo": {
"title": "DEMÓ MÓD",
"warning": "A tartalom naponta visszaáll. A módosításokat más felhasználók felülírhatják."

View File

@ -4,11 +4,9 @@
"name": "Italiano",
"flag": "🇮🇹"
},
"app": {
"tagline": "La tua base di conoscenza personale"
},
"common": {
"save": "Salva",
"cancel": "Annulla",
@ -28,12 +26,10 @@
"copy_to_clipboard": "Copia negli appunti",
"failed": "Impossibile {{action}}. Riprova."
},
"toast": {
"region_label": "Notifiche",
"dismiss": "Chiudi"
},
"sidebar": {
"title": "FILE",
"favorites_title": "Preferiti",
@ -42,6 +38,7 @@
"new_note": "Nuova Nota",
"new_folder": "Nuova Cartella",
"new_from_template": "Nuovo da Modello",
"new_drawing": "Nuovo disegno",
"rename_folder": "Rinomina cartella",
"delete_folder": "Elimina cartella",
"search_placeholder": "Cerca note...",
@ -71,7 +68,6 @@
"settings_title": "IMPOSTAZIONI",
"filtered_notes": "Note Filtrate"
},
"editor": {
"placeholder": "Inizia a scrivere in markdown...",
"drop_hint": "💡 Rilascia qui per inserire alla posizione del cursore...",
@ -84,7 +80,6 @@
"hours_ago": "{{count}}h fa",
"days_ago": "{{count}}g fa"
},
"notes": {
"confirm_delete": "Eliminare \"{{name}}\"?",
"already_exists": "Una nota chiamata \"{{name}}\" esiste già in questa posizione.\nScegli un nome diverso.",
@ -100,7 +95,6 @@
"no_content": "Nessun contenuto da esportare",
"open_first": "Apri prima una nota per caricare immagini."
},
"folders": {
"confirm_delete": "⚠️ ATTENZIONE ⚠️\n\nSei sicuro di voler eliminare la cartella \"{{name}}\"?\n\nQuesto eliminerà PERMANENTEMENTE:\n• Tutte le note in questa cartella\n• Tutte le sottocartelle e i loro contenuti\n\nQuesta azione NON PUÒ essere annullata!",
"already_exists": "Una cartella chiamata \"{{name}}\" esiste già in questa posizione.\nScegli un nome diverso.",
@ -117,26 +111,24 @@
"cannot_move_into_self": "Impossibile spostare la cartella in se stessa o in una sottocartella.",
"empty": "vuota"
},
"toolbar": {
"undo": "Annulla (Ctrl+Z)",
"redo": "Ripeti (Ctrl+Y)",
"delete_note": "Elimina nota",
"delete_image": "Elimina immagine",
"delete_drawing": "Elimina disegno",
"export_html": "Esporta come HTML",
"print_preview": "Anteprima di stampa",
"copy_link": "Copia link negli appunti",
"add_favorite": "Aggiungi ai preferiti",
"remove_favorite": "Rimuovi dai preferiti"
},
"zen_mode": {
"title": "Modalità Zen",
"tooltip": "Modalità Zen (Ctrl+Alt+Z)",
"exit": "Esci dalla Modalità Zen",
"exit_hint": "Esci dalla Modalità Zen (Esc)"
},
"share": {
"button_tooltip": "Condividi nota",
"modal_title": "Condividi Nota",
@ -149,9 +141,10 @@
"error_creating": "Impossibile creare il link: {{error}}",
"error_revoking": "Impossibile revocare il link: {{error}}",
"show_qr": "Mostra codice QR",
"hide_qr": "Nascondi codice QR"
"hide_qr": "Nascondi codice QR",
"panel_title": "Condivise",
"no_shared": "Nessuna nota condivisa"
},
"quick_switcher": {
"placeholder": "Cerca note...",
"no_results": "Nessun risultato",
@ -160,7 +153,6 @@
"open": "apri",
"close": "chiudi"
},
"tags": {
"title": "Tag",
"no_tags": "Nessun tag trovato",
@ -169,13 +161,11 @@
"clear_all": "Cancella filtri tag",
"filter_by": "Filtra per {{tag}} ({{count}} note)"
},
"outline": {
"title": "Indice",
"no_headings": "Nessun titolo trovato",
"hint": "Aggiungi titoli usando la sintassi #"
},
"backlinks": {
"title": "Backlink",
"no_backlinks": "Nessun backlink trovato",
@ -183,21 +173,18 @@
"select_note": "Seleziona una nota per vedere i backlink",
"more_refs": "altri"
},
"stats": {
"words": "parole",
"reading_time": "~{{minutes}}m lettura",
"links": "{{count}} link",
"click_details": "▼ Clicca per dettagli"
},
"graph": {
"title": "Grafo",
"empty": "Nessuna connessione da visualizzare",
"markdown_links": "Link markdown",
"click_hint": "Clic: seleziona • Doppio clic: apri"
},
"templates": {
"title": "Modelli",
"select": "Seleziona un modello...",
@ -211,11 +198,9 @@
"available_placeholders": "Segnaposto disponibili",
"create_note": "Crea Nota"
},
"export": {
"failed": "Esportazione HTML fallita: {{error}}"
},
"login": {
"title": "Accesso",
"tagline": "La tua base di conoscenza personale",
@ -224,20 +209,31 @@
"footer": "🔒 Sicuro e ospitato localmente",
"error_incorrect_password": "Password errata. Riprova."
},
"drawing": {
"tool_freehand": "Matita — mano libera",
"tool_line": "Linea retta",
"tool_rect": "Rettangolo",
"tool_ellipse": "Cerchio",
"color": "Colore",
"width": "Spessore tratto",
"tool_eraser": "Gomma (disegna col colore di sfondo)",
"tool_eyedropper": "Contagocce — prendi un colore dal canvas",
"clear": "Svuota",
"clear_hint": "Sostituisci con immagine vuota",
"clear_title": "Sostituire con tela bianca?",
"clear_confirm": "Il PNG salvato e tutti i tratti di questa sessione saranno sostituiti da un'immagine bianca vuota. Non è possibile annullare."
},
"media": {
"confirm_delete": "Eliminare \"{{name}}\"?",
"upload_failed": "Caricamento file fallito",
"no_valid_files": "Nessun file valido trovato. Supportati: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Impossibile spostare la nota.",
"failed_folder": "Impossibile spostare la cartella.",
"failed_media": "Impossibile spostare il file multimediale."
},
"search": {
"previous": "Precedente (Shift+F3)",
"next": "Successivo (F3)",
@ -248,22 +244,18 @@
"no_results": "Nessuna nota contiene \"{{query}}\"",
"searching": "Ricerca di \"{{query}}\"..."
},
"theme": {
"title": "Tema"
},
"language": {
"title": "Lingua"
},
"syntax_highlight": {
"title": "Evidenziazione Sintassi Editor",
"description": "Colora la sintassi markdown nell'editor",
"enable": "Attiva evidenziazione sintassi",
"disable": "Disattiva evidenziazione sintassi"
},
"settings": {
"account": "Account",
"logout": "Esci",
@ -274,7 +266,6 @@
"tab_inserts_tab": "Tab inserisce tabulazione",
"tab_inserts_tab_desc": "Premi Tab per inserire una tabulazione invece di cambiare focus"
},
"homepage": {
"title": "Home",
"welcome": "Benvenuto in NoteDiscovery",
@ -287,7 +278,6 @@
"folder_singular": "cartella",
"folder_plural": "cartelle"
},
"format": {
"bold": "Grassetto",
"italic": "Corsivo",
@ -303,20 +293,17 @@
"checkbox": "Casella di controllo",
"table": "Tabella"
},
"validation": {
"forbidden_chars": "Il nome contiene caratteri non consentiti: {{chars}}",
"reserved_name": "Questo nome è riservato dal sistema operativo.",
"invalid_dot": "Il nome non può essere solo un punto.",
"trailing_dot_space": "Il nome non può terminare con un punto o uno spazio."
},
"support": {
"enjoying_demo": "Supporta questo progetto",
"deploy_own": "Crea la tua istanza",
"thank_you": "Grazie per il tuo supporto! 💚"
},
"demo": {
"title": "MODALITÀ DEMO",
"warning": "I contenuti vengono ripristinati ogni giorno. Le modifiche potrebbero essere sovrascritte da altri utenti."

View File

@ -4,11 +4,9 @@
"name": "日本語",
"flag": "🇯🇵"
},
"app": {
"tagline": "セルフホスト型ナレッジベース"
},
"common": {
"save": "保存",
"cancel": "キャンセル",
@ -28,12 +26,10 @@
"copy_to_clipboard": "クリップボードにコピー",
"failed": "{{action}}に失敗しました。もう一度お試しください。"
},
"toast": {
"region_label": "通知",
"dismiss": "閉じる"
},
"sidebar": {
"title": "ファイル",
"favorites_title": "お気に入り",
@ -42,6 +38,7 @@
"new_note": "新規ノート",
"new_folder": "新規フォルダ",
"new_from_template": "テンプレートから作成",
"new_drawing": "新規お絵かき",
"rename_folder": "フォルダの名前を変更",
"delete_folder": "フォルダを削除",
"search_placeholder": "ノートを検索...",
@ -71,7 +68,6 @@
"settings_title": "設定",
"filtered_notes": "フィルター済みノート"
},
"editor": {
"placeholder": "マークダウンで書き始める...",
"drop_hint": "💡 ここにドロップしてカーソル位置に挿入...",
@ -84,7 +80,6 @@
"hours_ago": "{{count}}時間前",
"days_ago": "{{count}}日前"
},
"notes": {
"confirm_delete": "「{{name}}」を削除しますか?",
"already_exists": "「{{name}}」という名前のノートは既に存在します。\n別の名前を選択してください。",
@ -100,7 +95,6 @@
"no_content": "エクスポートするノートの内容がありません",
"open_first": "画像をアップロードする前にノートを開いてください。"
},
"folders": {
"confirm_delete": "⚠️ 警告 ⚠️\n\n本当にフォルダ「{{name}}」を削除しますか?\n\n以下が完全に削除されます:\n• このフォルダ内のすべてのノート\n• すべてのサブフォルダとその内容\n\nこの操作は元に戻せません",
"already_exists": "「{{name}}」という名前のフォルダは既に存在します。\n別の名前を選択してください。",
@ -117,26 +111,24 @@
"cannot_move_into_self": "フォルダを自身またはそのサブフォルダに移動できません。",
"empty": "空"
},
"toolbar": {
"undo": "元に戻す (Ctrl+Z)",
"redo": "やり直す (Ctrl+Y)",
"delete_note": "ノートを削除",
"delete_image": "画像を削除",
"delete_drawing": "お絵かきを削除",
"export_html": "HTMLとしてエクスポート",
"print_preview": "印刷プレビュー",
"copy_link": "リンクをクリップボードにコピー",
"add_favorite": "お気に入りに追加",
"remove_favorite": "お気に入りから削除"
},
"zen_mode": {
"title": "集中モード",
"tooltip": "集中モード (Ctrl+Alt+Z)",
"exit": "集中モードを終了",
"exit_hint": "集中モードを終了 (Esc)"
},
"share": {
"button_tooltip": "ノートを共有",
"modal_title": "ノートを共有",
@ -149,9 +141,10 @@
"error_creating": "共有リンクの作成に失敗しました: {{error}}",
"error_revoking": "共有リンクの取り消しに失敗しました: {{error}}",
"show_qr": "QRコードを表示",
"hide_qr": "QRコードを非表示"
"hide_qr": "QRコードを非表示",
"panel_title": "共有",
"no_shared": "共有中のノートはありません"
},
"quick_switcher": {
"placeholder": "ノートを検索...",
"no_results": "結果なし",
@ -160,7 +153,6 @@
"open": "開く",
"close": "閉じる"
},
"tags": {
"title": "タグ",
"no_tags": "タグが見つかりません",
@ -169,13 +161,11 @@
"clear_all": "タグフィルターをクリア",
"filter_by": "{{tag}}でフィルター({{count}}件)"
},
"outline": {
"title": "アウトライン",
"no_headings": "見出しが見つかりません",
"hint": "# 記法で見出しを追加"
},
"backlinks": {
"title": "バックリンク",
"no_backlinks": "バックリンクが見つかりません",
@ -183,21 +173,18 @@
"select_note": "バックリンクを表示するノートを選択",
"more_refs": "その他"
},
"stats": {
"words": "語",
"reading_time": "約{{minutes}}分で読了",
"links": "{{count}}件のリンク",
"click_details": "▼ 詳細を表示"
},
"graph": {
"title": "グラフ",
"empty": "表示する接続がありません",
"markdown_links": "マークダウンリンク",
"click_hint": "クリック: 選択 • ダブルクリック: 開く"
},
"templates": {
"title": "テンプレート",
"select": "テンプレートを選択...",
@ -211,11 +198,9 @@
"available_placeholders": "利用可能なプレースホルダー",
"create_note": "ノートを作成"
},
"export": {
"failed": "HTMLエクスポートに失敗しました: {{error}}"
},
"login": {
"title": "ログイン",
"tagline": "セルフホスト型ナレッジベース",
@ -224,20 +209,31 @@
"footer": "🔒 安全なセルフホスト",
"error_incorrect_password": "パスワードが正しくありません。もう一度お試しください。"
},
"drawing": {
"tool_freehand": "鉛筆(フリーハンド)",
"tool_line": "直線",
"tool_rect": "長方形",
"tool_ellipse": "円",
"color": "色",
"width": "線の太さ",
"tool_eraser": "消しゴム(背景色で塗る)",
"tool_eyedropper": "スポイト — キャンバスから色を取得",
"clear": "クリア",
"clear_hint": "白紙の画像に置き換え",
"clear_title": "白紙のキャンバスに置き換えますか?",
"clear_confirm": "保存済みのPNGとこの作業中の線は、白い空白画像に置き換えられます。元に戻せません。"
},
"media": {
"confirm_delete": "「{{name}}」を削除しますか?",
"upload_failed": "ファイルのアップロードに失敗しました",
"no_valid_files": "有効なファイルが見つかりません。対応: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "ノートの移動に失敗しました。",
"failed_folder": "フォルダの移動に失敗しました。",
"failed_media": "メディアファイルの移動に失敗しました。"
},
"search": {
"previous": "前へ (Shift+F3)",
"next": "次へ (F3)",
@ -248,22 +244,18 @@
"no_results": "「{{query}}」を含むノートはありません",
"searching": "「{{query}}」を検索中..."
},
"theme": {
"title": "テーマ"
},
"language": {
"title": "言語"
},
"syntax_highlight": {
"title": "エディタのシンタックスハイライト",
"description": "エディタでマークダウン構文を色分け表示",
"enable": "シンタックスハイライトを有効化",
"disable": "シンタックスハイライトを無効化"
},
"settings": {
"account": "アカウント",
"logout": "ログアウト",
@ -274,7 +266,6 @@
"tab_inserts_tab": "Tabキーでタブを挿入",
"tab_inserts_tab_desc": "Tabキーでフォーカス移動ではなくタブ文字を挿入"
},
"homepage": {
"title": "ホーム",
"welcome": "NoteDiscoveryへようこそ",
@ -287,7 +278,6 @@
"folder_singular": "フォルダ",
"folder_plural": "フォルダ"
},
"format": {
"bold": "太字",
"italic": "斜体",
@ -303,20 +293,17 @@
"checkbox": "チェックボックス",
"table": "表"
},
"validation": {
"forbidden_chars": "名前に使用できない文字が含まれています: {{chars}}",
"reserved_name": "この名前はオペレーティングシステムで予約されています。",
"invalid_dot": "名前をドットだけにすることはできません。",
"trailing_dot_space": "名前の末尾にドットやスペースは使用できません。"
},
"support": {
"enjoying_demo": "このプロジェクトを支援",
"deploy_own": "自分のインスタンスをデプロイ",
"thank_you": "ご支援ありがとうございます!💚"
},
"demo": {
"title": "デモモード",
"warning": "コンテンツは毎日リセットされます。変更は他のユーザーによって上書きされる場合があります。"

View File

@ -4,11 +4,9 @@
"name": "Русский",
"flag": "🇷🇺"
},
"app": {
"tagline": "Ваша локальная база знаний"
},
"common": {
"save": "Сохранить",
"cancel": "Отмена",
@ -28,12 +26,10 @@
"copy_to_clipboard": "Копировать в буфер обмена",
"failed": "Не удалось {{action}}. Попробуйте снова."
},
"toast": {
"region_label": "Уведомления",
"dismiss": "Закрыть"
},
"sidebar": {
"title": "ФАЙЛЫ",
"favorites_title": "Избранное",
@ -42,6 +38,7 @@
"new_note": "Новая заметка",
"new_folder": "Новая папка",
"new_from_template": "Из шаблона",
"new_drawing": "Новый рисунок",
"rename_folder": "Переименовать папку",
"delete_folder": "Удалить папку",
"search_placeholder": "Поиск заметок...",
@ -71,7 +68,6 @@
"settings_title": "НАСТРОЙКИ",
"filtered_notes": "Отфильтрованные заметки"
},
"editor": {
"placeholder": "Начните писать в markdown...",
"drop_hint": "💡 Отпустите здесь для вставки в позицию курсора...",
@ -84,7 +80,6 @@
"hours_ago": "{{count}} ч. назад",
"days_ago": "{{count}} дн. назад"
},
"notes": {
"confirm_delete": "Удалить «{{name}}»?",
"already_exists": "Заметка с именем «{{name}}» уже существует.\nВыберите другое имя.",
@ -100,7 +95,6 @@
"no_content": "Нет содержимого для экспорта",
"open_first": "Сначала откройте заметку для загрузки изображений."
},
"folders": {
"confirm_delete": "⚠️ ВНИМАНИЕ ⚠️\n\nВы уверены, что хотите удалить папку «{{name}}»?\n\nБудет БЕЗВОЗВРАТНО удалено:\n• Все заметки в этой папке\n• Все подпапки и их содержимое\n\nЭто действие НЕЛЬЗЯ отменить!",
"already_exists": "Папка с именем «{{name}}» уже существует.\nВыберите другое имя.",
@ -117,26 +111,24 @@
"cannot_move_into_self": "Нельзя переместить папку в себя или в подпапку.",
"empty": "пусто"
},
"toolbar": {
"undo": "Отменить (Ctrl+Z)",
"redo": "Повторить (Ctrl+Y)",
"delete_note": "Удалить заметку",
"delete_image": "Удалить изображение",
"delete_drawing": "Удалить рисунок",
"export_html": "Экспорт в HTML",
"print_preview": "Предварительный просмотр печати",
"copy_link": "Копировать ссылку",
"add_favorite": "Добавить в избранное",
"remove_favorite": "Удалить из избранного"
},
"zen_mode": {
"title": "Режим концентрации",
"tooltip": "Режим концентрации (Ctrl+Alt+Z)",
"exit": "Выйти из режима концентрации",
"exit_hint": "Выйти из режима концентрации (Esc)"
},
"share": {
"button_tooltip": "Поделиться заметкой",
"modal_title": "Поделиться заметкой",
@ -149,9 +141,10 @@
"error_creating": "Не удалось создать ссылку: {{error}}",
"error_revoking": "Не удалось отозвать ссылку: {{error}}",
"show_qr": "Показать QR-код",
"hide_qr": "Скрыть QR-код"
"hide_qr": "Скрыть QR-код",
"panel_title": "Опубликовано",
"no_shared": "Нет опубликованных заметок"
},
"quick_switcher": {
"placeholder": "Поиск заметок...",
"no_results": "Ничего не найдено",
@ -160,7 +153,6 @@
"open": "открыть",
"close": "закрыть"
},
"tags": {
"title": "Теги",
"no_tags": "Теги не найдены",
@ -169,13 +161,11 @@
"clear_all": "Сбросить фильтры тегов",
"filter_by": "Фильтр по {{tag}} ({{count}} заметок)"
},
"outline": {
"title": "Содержание",
"no_headings": "Заголовки не найдены",
"hint": "Добавьте заголовки с помощью #"
},
"backlinks": {
"title": "Обратные ссылки",
"no_backlinks": "Обратные ссылки не найдены",
@ -183,21 +173,18 @@
"select_note": "Выберите заметку, чтобы увидеть обратные ссылки",
"more_refs": "ещё"
},
"stats": {
"words": "слов",
"reading_time": "~{{minutes}} мин. чтения",
"links": "{{count}} ссылок",
"click_details": "▼ Нажмите для подробностей"
},
"graph": {
"title": "Граф связей",
"empty": "Нет связей для отображения",
"markdown_links": "Markdown-ссылки",
"click_hint": "Клик: выбрать • Двойной клик: открыть"
},
"templates": {
"title": "Шаблоны",
"select": "Выберите шаблон...",
@ -211,11 +198,9 @@
"available_placeholders": "Доступные плейсхолдеры",
"create_note": "Создать заметку"
},
"export": {
"failed": "Ошибка экспорта HTML: {{error}}"
},
"login": {
"title": "Вход",
"tagline": "Ваша локальная база знаний",
@ -224,20 +209,31 @@
"footer": "🔒 Безопасно и локально",
"error_incorrect_password": "Неверный пароль. Попробуйте снова."
},
"drawing": {
"tool_freehand": "Карандаш — от руки",
"tool_line": "Прямая линия",
"tool_rect": "Прямоугольник",
"tool_ellipse": "Круг",
"color": "Цвет",
"width": "Толщина линии",
"tool_eraser": "Ластик (рисует цветом фона)",
"tool_eyedropper": "Пипетка — взять цвет с холста",
"clear": "Очистить",
"clear_hint": "Заменить пустым изображением",
"clear_title": "Заменить пустым холстом?",
"clear_confirm": "Сохранённый PNG и все штрихи этого сеанса будут заменены пустым белым изображением. Это действие нельзя отменить."
},
"media": {
"confirm_delete": "Удалить «{{name}}»?",
"upload_failed": "Не удалось загрузить файл",
"no_valid_files": "Не найдено подходящих файлов. Поддерживает: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Не удалось переместить заметку.",
"failed_folder": "Не удалось переместить папку.",
"failed_media": "Не удалось переместить медиафайл."
},
"search": {
"previous": "Предыдущий (Shift+F3)",
"next": "Следующий (F3)",
@ -248,22 +244,18 @@
"no_results": "Нет заметок с \"{{query}}\"",
"searching": "Поиск \"{{query}}\"..."
},
"theme": {
"title": "Тема"
},
"language": {
"title": "Язык"
},
"syntax_highlight": {
"title": "Подсветка синтаксиса",
"description": "Подсветка markdown-синтаксиса в редакторе",
"enable": "Включить подсветку синтаксиса",
"disable": "Выключить подсветку синтаксиса"
},
"settings": {
"account": "Аккаунт",
"logout": "Выйти",
@ -274,7 +266,6 @@
"tab_inserts_tab": "Tab вставляет табуляцию",
"tab_inserts_tab_desc": "Нажмите Tab для вставки табуляции вместо переключения фокуса"
},
"homepage": {
"title": "Главная",
"welcome": "Добро пожаловать в NoteDiscovery",
@ -287,7 +278,6 @@
"folder_singular": "папка",
"folder_plural": "папок"
},
"format": {
"bold": "Жирный",
"italic": "Курсив",
@ -303,20 +293,17 @@
"checkbox": "Чекбокс",
"table": "Таблица"
},
"validation": {
"forbidden_chars": "Имя содержит запрещённые символы: {{chars}}",
"reserved_name": "Это имя зарезервировано операционной системой.",
"invalid_dot": "Имя не может состоять только из точки.",
"trailing_dot_space": "Имя не может заканчиваться точкой или пробелом."
},
"support": {
"enjoying_demo": "Поддержать проект",
"deploy_own": "Развернуть свой экземпляр",
"thank_you": "Спасибо за поддержку! 💚"
},
"demo": {
"title": "ДЕМО-РЕЖИМ",
"warning": "Содержимое сбрасывается ежедневно. Изменения могут быть перезаписаны другими пользователями."

View File

@ -4,11 +4,9 @@
"name": "Slovenščina",
"flag": "🇸🇮"
},
"app": {
"tagline": "Vaša lastna baza znanja"
},
"common": {
"save": "Shrani",
"cancel": "Prekliči",
@ -28,12 +26,10 @@
"copy_to_clipboard": "Kopiraj v odložišče",
"failed": "Napaka pri {{action}}. Prosimo, poskusite znova."
},
"toast": {
"region_label": "Obvestila",
"dismiss": "Zapri"
},
"sidebar": {
"title": "DATOTEKE",
"favorites_title": "Priljubljene",
@ -42,6 +38,7 @@
"new_note": "Nov zapis",
"new_folder": "Nova mapa",
"new_from_template": "Novo iz predloge",
"new_drawing": "Nov risba",
"rename_folder": "Preimenuj mapo",
"delete_folder": "Izbriši mapo",
"search_placeholder": "Išči zapiske...",
@ -71,7 +68,6 @@
"settings_title": "NASTAVITVE",
"filtered_notes": "Filtrirani zapiski"
},
"editor": {
"placeholder": "Začnite pisati v markdownu...",
"drop_hint": "💡 Spustite tukaj za vstavljanje na mesto kazalca...",
@ -84,7 +80,6 @@
"hours_ago": "pred {{count}} h",
"days_ago": "pred {{count}} d"
},
"notes": {
"confirm_delete": "Izbrišem \"{{name}}\"?",
"already_exists": "Zapis z imenom \"{{name}}\" na tej lokaciji že obstaja.\nProsimo, izberite drugo ime.",
@ -100,7 +95,6 @@
"no_content": "Ni vsebine za izvoz",
"open_first": "Prosimo, najprej odprite zapis, preden naložite slike."
},
"folders": {
"confirm_delete": "⚠️ OPOZORILO ⚠️\n\nAli ste prepričani, da želite izbrisati mapo \"{{name}}\"?\n\nTo bo TRAJNO izbrisalo:\n• Vse zapiske v tej mapi\n• Vse podmape in njihovo vsebino\n\nTe akcije NI mogoče preklicati!",
"already_exists": "Mapa z imenom \"{{name}}\" na tej lokaciji že obstaja.\nProsimo, izberite drugo ime.",
@ -117,26 +111,24 @@
"cannot_move_into_self": "Mape ni mogoče premakniti vase ali v njeno podmapo.",
"empty": "prazno"
},
"toolbar": {
"undo": "Razveljavi (Ctrl+Z)",
"redo": "Ponovi (Ctrl+Y)",
"delete_note": "Izbriši zapis",
"delete_image": "Izbriši sliko",
"delete_drawing": "Izbriši risbo",
"export_html": "Izvozi kot HTML",
"print_preview": "Predogled tiskanja",
"copy_link": "Kopiraj povezavo v odložišče",
"add_favorite": "Dodaj med priljubljene",
"remove_favorite": "Odstrani iz priljubljenih"
},
"zen_mode": {
"title": "Zen način",
"tooltip": "Zen način (Ctrl+Alt+Z)",
"exit": "Izhod iz zen načina",
"exit_hint": "Izhod iz zen načina (Esc)"
},
"share": {
"button_tooltip": "Deli zapis",
"modal_title": "Deli zapis",
@ -149,9 +141,10 @@
"error_creating": "Napaka pri ustvarjanju povezave: {{error}}",
"error_revoking": "Napaka pri preklicu povezave: {{error}}",
"show_qr": "Prikaži QR kodo",
"hide_qr": "Skrij QR kodo"
"hide_qr": "Skrij QR kodo",
"panel_title": "V skupni rabi",
"no_shared": "Ni deljenih zapisov"
},
"quick_switcher": {
"placeholder": "Išči beležke...",
"no_results": "Ni rezultatov",
@ -160,7 +153,6 @@
"open": "odpri",
"close": "zapri"
},
"tags": {
"title": "Oznake",
"no_tags": "Ni najdenih oznak",
@ -169,13 +161,11 @@
"clear_all": "Počisti filtre oznak",
"filter_by": "Filtriraj po {{tag}} ({{count}} zapiskov)"
},
"outline": {
"title": "Oris",
"no_headings": "Ni najdenih naslovov",
"hint": "Dodajte naslove s sintakso #"
},
"backlinks": {
"title": "Povratne povezave",
"no_backlinks": "Ni najdenih povratnih povezav",
@ -183,21 +173,18 @@
"select_note": "Izberite beležko za ogled povratnih povezav",
"more_refs": "več"
},
"stats": {
"words": "besed",
"reading_time": "~{{minutes}} min branja",
"links": "{{count}} povezav",
"click_details": "▼ Kliknite za podrobnosti"
},
"graph": {
"title": "Grafični prikaz",
"empty": "Ni povezav za prikaz",
"markdown_links": "Markdown povezave",
"click_hint": "Klik: izberi • Dvojni klik: odpri"
},
"templates": {
"title": "Predloge",
"select": "Izberite predlogo...",
@ -211,11 +198,9 @@
"available_placeholders": "Razpoložljivi gradniki",
"create_note": "Ustvari zapis"
},
"export": {
"failed": "Izvoz v HTML ni uspel: {{error}}"
},
"login": {
"title": "Prijava",
"tagline": "Vaša lastna baza znanja",
@ -224,20 +209,31 @@
"footer": "🔒 Varno in samostojno gostovano",
"error_incorrect_password": "Napačno geslo. Prosimo, poskusite znova."
},
"drawing": {
"tool_freehand": "Svinčnik — prostoročno",
"tool_line": "Ravna črta",
"tool_rect": "Pravokotnik",
"tool_ellipse": "Krog",
"color": "Barva",
"width": "Debelina črte",
"tool_eraser": "Radirka (riše z barvo ozadja)",
"tool_eyedropper": "Kapalka — vzemi barvo s platna",
"clear": "Počisti",
"clear_hint": "Zamenjaj s prazno sliko",
"clear_title": "Zamenjaj s praznim platnom?",
"clear_confirm": "Shranjena datoteka PNG in vse poteze v tej seji bodo zamenjane s prazno belo sliko. Tega ni mogoče razveljaviti."
},
"media": {
"confirm_delete": "Izbrišem \"{{name}}\"?",
"upload_failed": "Nalaganje datoteke ni uspelo",
"no_valid_files": "Ni najdenih veljavnih datotek. Podprto: JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "Napaka pri premikanju zapisa.",
"failed_folder": "Napaka pri premikanju mape.",
"failed_media": "Napaka pri premikanju medijske datoteke."
},
"search": {
"previous": "Prejšnje (Shift+F3)",
"next": "Naslednje (F3)",
@ -248,22 +244,18 @@
"no_results": "Nobena beležka ne vsebuje \"{{query}}\"",
"searching": "Iskanje \"{{query}}\"..."
},
"theme": {
"title": "Tema"
},
"language": {
"title": "Jezik"
},
"syntax_highlight": {
"title": "Poudarjanje sintakse v urejevalniku",
"description": "Obarvaj markdown sintakso v urejevalniku",
"enable": "Omogoči poudarjanje sintakse",
"disable": "Onemogoči poudarjanje sintakse"
},
"settings": {
"account": "Račun",
"logout": "Odjava",
@ -274,7 +266,6 @@
"tab_inserts_tab": "Tipka Tab vstavi tabulator",
"tab_inserts_tab_desc": "Pritisnite Tab za vstavljanje tabulatorja namesto premikanja fokusa"
},
"homepage": {
"title": "Domov",
"welcome": "Dobrodošli v NoteDiscovery",
@ -287,7 +278,6 @@
"folder_singular": "mapa",
"folder_plural": "mape"
},
"format": {
"bold": "Krepko",
"italic": "Ležeče",
@ -303,22 +293,19 @@
"checkbox": "Potrditveno polje",
"table": "Tabela"
},
"validation": {
"forbidden_chars": "Ime vsebuje prepovedane znake: {{chars}}",
"reserved_name": "To ime je rezervirano s strani operacijskega sistema.",
"invalid_dot": "Ime ne more biti le pika.",
"trailing_dot_space": "Ime se ne more končati s piko ali presledkom."
},
"support": {
"enjoying_demo": "Podprite ta projekt",
"deploy_own": "Namestite svojo instanco",
"thank_you": "Hvala za podporo! 💚"
},
"demo": {
"title": "DEMO NAČIN",
"warning": "Vsebina se dnevno ponastavi. Spremembe lahko prepišejo drugi uporabniki."
}
}
}

View File

@ -4,11 +4,9 @@
"name": "Chinese (Simplified)",
"flag": "🇨🇳"
},
"app": {
"tagline": "您的自托管知识库"
},
"common": {
"save": "保存",
"cancel": "取消",
@ -28,12 +26,10 @@
"copy_to_clipboard": "复制到剪贴板",
"failed": "{{action}} 失败。请重试。"
},
"toast": {
"region_label": "通知",
"dismiss": "关闭"
},
"sidebar": {
"title": "文件",
"favorites_title": "收藏夹",
@ -42,6 +38,7 @@
"new_note": "新建笔记",
"new_folder": "新建文件夹",
"new_from_template": "从模板新建",
"new_drawing": "新建绘图",
"rename_folder": "重命名文件夹",
"delete_folder": "删除文件夹",
"search_placeholder": "搜索笔记...",
@ -71,7 +68,6 @@
"settings_title": "设置",
"filtered_notes": "筛选的笔记"
},
"editor": {
"placeholder": "开始用 Markdown 写作...",
"drop_hint": "💡 拖放到此处以插入到光标位置...",
@ -84,7 +80,6 @@
"hours_ago": "{{count}}小时前",
"days_ago": "{{count}}天前"
},
"notes": {
"confirm_delete": "删除 \"{{name}}\"",
"already_exists": "此位置已存在名为 \"{{name}}\" 的笔记。\n请选择一个不同的名称。",
@ -100,7 +95,6 @@
"no_content": "没有可导出的笔记内容",
"open_first": "请先打开一篇笔记,然后再上传图片。"
},
"folders": {
"confirm_delete": "⚠️ 警告 ⚠️\n\n您确定要删除文件夹 \"{{name}}\" 吗?\n\n这将永久删除\n• 此文件夹内的所有笔记\n• 所有子文件夹及其内容\n\n此操作无法撤销",
"already_exists": "此位置已存在名为 \"{{name}}\" 的文件夹。\n请选择一个不同的名称。",
@ -117,26 +111,24 @@
"cannot_move_into_self": "无法将文件夹移动到其自身或其子文件夹中。",
"empty": "空"
},
"toolbar": {
"undo": "撤销 (Ctrl+Z)",
"redo": "重做 (Ctrl+Y)",
"delete_note": "删除笔记",
"delete_image": "删除图片",
"delete_drawing": "删除绘图",
"export_html": "导出为 HTML",
"print_preview": "打印预览",
"copy_link": "复制链接到剪贴板",
"add_favorite": "添加到收藏夹",
"remove_favorite": "从收藏夹移除"
},
"zen_mode": {
"title": "禅模式",
"tooltip": "禅模式 (Ctrl+Alt+Z)",
"exit": "退出禅模式",
"exit_hint": "退出禅模式 (Esc)"
},
"share": {
"button_tooltip": "分享笔记",
"modal_title": "分享笔记",
@ -149,9 +141,10 @@
"error_creating": "创建分享链接失败:{{error}}",
"error_revoking": "撤销分享链接失败:{{error}}",
"show_qr": "显示二维码",
"hide_qr": "隐藏二维码"
"hide_qr": "隐藏二维码",
"panel_title": "已分享",
"no_shared": "暂无已分享的笔记"
},
"quick_switcher": {
"placeholder": "搜索笔记...",
"no_results": "无匹配结果",
@ -160,7 +153,6 @@
"open": "打开",
"close": "关闭"
},
"tags": {
"title": "标签",
"no_tags": "未找到标签",
@ -169,13 +161,11 @@
"clear_all": "清除所有标签过滤器",
"filter_by": "按 {{tag}} 过滤 ({{count}} 条笔记)"
},
"outline": {
"title": "大纲",
"no_headings": "未找到标题",
"hint": "使用 # 语法添加标题"
},
"backlinks": {
"title": "反向链接",
"no_backlinks": "未找到反向链接",
@ -183,21 +173,18 @@
"select_note": "选择一个笔记以查看反向链接",
"more_refs": "更多"
},
"stats": {
"words": "字数",
"reading_time": "~{{minutes}}分钟阅读",
"links": "{{count}} 个链接",
"click_details": "▼ 点击查看详情"
},
"graph": {
"title": "图谱",
"empty": "无连接可显示",
"markdown_links": "Markdown 链接",
"click_hint": "点击:选择 • 双击:打开"
},
"templates": {
"title": "模板",
"select": "选择一个模板...",
@ -211,11 +198,9 @@
"available_placeholders": "可用占位符",
"create_note": "创建笔记"
},
"export": {
"failed": "导出 HTML 失败:{{error}}"
},
"login": {
"title": "登录",
"tagline": "您的自托管知识库",
@ -224,20 +209,31 @@
"footer": "🔒 安全且自托管",
"error_incorrect_password": "密码错误。请重试。"
},
"drawing": {
"tool_freehand": "铅笔 — 自由绘制",
"tool_line": "直线",
"tool_rect": "矩形",
"tool_ellipse": "圆形",
"color": "颜色",
"width": "线条粗细",
"tool_eraser": "橡皮擦(使用背景色绘制)",
"tool_eyedropper": "吸管 — 从画布取色",
"clear": "清除",
"clear_hint": "替换为空白图像",
"clear_title": "替换为空白画布?",
"clear_confirm": "已保存的 PNG 和本会话中的笔划都将被替换为空白白色图像,且无法撤销。"
},
"media": {
"confirm_delete": "删除 \"{{name}}\"",
"upload_failed": "上传文件失败",
"no_valid_files": "未找到有效文件。支持JPG, PNG, GIF, WebP, MP3, MP4, PDF",
"formats": "JPG, PNG, GIF, WebP, MP3, MP4, PDF"
},
"move": {
"failed_note": "移动笔记失败。",
"failed_folder": "移动文件夹失败。",
"failed_media": "移动媒体文件失败。"
},
"search": {
"previous": "上一个 (Shift+F3)",
"next": "下一个 (F3)",
@ -248,22 +244,18 @@
"no_results": "没有笔记包含\"{{query}}\"",
"searching": "正在搜索\"{{query}}\"..."
},
"theme": {
"title": "主题"
},
"language": {
"title": "语言"
},
"syntax_highlight": {
"title": "编辑器语法高亮",
"description": "在编辑器中为 Markdown 语法着色",
"enable": "启用语法高亮",
"disable": "禁用语法高亮"
},
"settings": {
"account": "账户",
"logout": "登出",
@ -274,7 +266,6 @@
"tab_inserts_tab": "Tab键插入制表符",
"tab_inserts_tab_desc": "按Tab键插入制表符而不是切换焦点"
},
"homepage": {
"title": "首页",
"welcome": "欢迎使用 NoteDiscovery",
@ -287,7 +278,6 @@
"folder_singular": "文件夹",
"folder_plural": "文件夹"
},
"format": {
"bold": "粗体",
"italic": "斜体",
@ -303,23 +293,19 @@
"checkbox": "复选框",
"table": "表格"
},
"validation": {
"forbidden_chars": "名称包含禁止的字符:{{chars}}",
"reserved_name": "此名称被操作系统保留。",
"invalid_dot": "名称不能只是一个点。",
"trailing_dot_space": "名称不能以点或空格结尾。"
},
"support": {
"enjoying_demo": "支持此项目",
"deploy_own": "部署您自己的实例",
"thank_you": "感谢您的支持!💚"
},
"demo": {
"title": "演示模式",
"warning": "内容每日重置。您的更改可能被其他用户覆盖。"
}
}