Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
#+TITLE: nextlearn #+DESCRIPTION: Learn anything. Fight distraction. #+DATE: <2026-06-04 Thu> - [[#description][Description]] - [[#screenshots][Screenshots]] - [[#features][Features]] - [[#tech-stack][Tech Stack]] - [[#architecture][Architecture]] - [[#design-philosophy-mostly-offline][Design philosophy mostly offline]] - [[#data-sources][Data Sources]] - [[#project-structure][Project Structure]] - [[#key-services][Key Services]] - [[#deck-file-format][Deck File Format]] - [[#how-to-use][How to Use]] - [[#link--image-syntax-reference][Link & Image Syntax Reference]] - [[#quote--block-rendering][Quote & Block Rendering]] - [[#keyboard-shortcuts-or-key-bindings][Keyboard Shortcuts or Key Bindings]] - [[#gemini-prompts][Gemini Prompts]] - [[#tag-inference-prompt][Tag Inference Prompt]] - [[#mcq-generation-prompt][MCQ Generation Prompt]] - [[#basic-flashcard-prompt][Basic Flashcard Prompt]] - [[#cloze-flashcard-prompt][Cloze Flashcard Prompt]] - [[#latex-and-math-rendering][LaTeX and Math Rendering]] * Description :PROPERTIES: :CUSTOM_ID: description :END: [[file:screenshots/nextlearn-logo.png]] nextlearn is a distraction-free desktop app for learning anything through bite-sized decks. You write decks as =.md= files with YAML frontmatter, =.org= frontmatter / inline metadata, or plain =.txt= files in =~/nextlearn/decks/= (and subdirectories) by default, the app reads them, and you flip through pages like a slideshow. Built with Avalonia UI + EF Core SQLite. nextlearn is a keyboard-centric complete study gear for =.md=, =.org=, and =.txt= files --- not just a flashcard app. Create decks manually or with AI, study in a minimal distraction-free UI, search decks instantly, export to Anki, and more. This repo contains only =~nextlearn.Desktop= --- the offline Avalonia UI desktop app. The companion website (for browsing/downloading decks) is a separate project. * Screenshots :PROPERTIES: :CUSTOM_ID: screenshots :END: For visual walkthroughs of features, see the [[file:screenshots.org][Screenshots Gallery]]. * Installation ** Download pre-built binary (Linux & macOS only) CI builds single-file executables for Linux and macOS. Windows is not currently supported (Windows requires WebView2 while nextlearn uses the system WebView engine available on Linux; the author lacks a Windows machine to consistently test). Download from the [[https://github.com/megamind1230/testing-nextlearn/actions][Actions tab]] → click the latest green run → scroll to "Artifacts": - =NextLearn-Linux= → =NextLearn.Desktop= (=chmod +x=, then =./NextLearn.Desktop=) - =NextLearn-macOS= → =NextLearn.Desktop= (Apple Silicon) Requires glibc ≥ 2.39 (Ubuntu 24.04+, Debian 12+, Fedora 39+). Older distros or Having issues? → build from source below. ** Build from source Prerequisites: [[https://dotnet.microsoft.com/en-us/download/dotnet/10.0][.NET 10.0 SDK]] #+begin_src sh git clone https://github.com/megamind1230/testing-nextlearn cd testing-nextlearn dotnet build NextLearn.Desktop/NextLearn.Desktop.csproj -c Release dotnet run --project NextLearn.Desktop #+end_src To produce a portable single-file binary: #+begin_src sh dotnet publish NextLearn.Desktop/NextLearn.Desktop.csproj \ -c Release \ -r linux-x64 # or osx-arm64 / osx-x64 \ --self-contained true \ -p:PublishSingleFile=true \ -o publish #+end_src The =publish/= folder contains the executable. Copy and run anywhere. * Features :PROPERTIES: :CUSTOM_ID: features :END: | Feature | Status | Note | |--------------------------------------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Fully offline | ✅ Done | No network calls ever | | File-based decks (.md / .org / .txt) | ✅ Done | YAML frontmatter + =#= / =##= headings for .md/.org; 25-line chunk pages for .txt. Obsidian-style vault: all files under decks root (recursive). FileName = relative vault path (=subdir/deck.md=), =[[]]= wiki links resolves across subdirectories | | Frontmatter metadata | ✅ Done | =title:=, =description:=, =tags:= in --- block | | Org #+TITLE:/#+DESCRIPTION: | ✅ Done | =#+TITLE: / #+DESCRIPTION: / #+TAGS:= in .org | | Tag system from frontmatter | ✅ Done | =tags: health, morning, sleep= | | #tag search (incremental) | ✅ Done | =#st= matches =study= (prefix match) | | Regex search toggle | ✅ Done | Regex checkbox switches title/desc/name + tags to regex | | Search by title/desc/filename | ✅ Done | Plain text tokens search all three | | file:/title:/desc:/tags: search prefixes | ✅ Done | =file:baka= → FileName only; =tags:new, math= → multi-tag; =""= for phrase matching in =file:/title:/desc:= | | Page-by-page learning | ✅ Done | Prev/Next with keyboard shortcuts | | Section breadcrumb | ✅ Done | Shows =# Section → ## Page= with heading markers | | Rendered markdown content | ✅ Done | NativeWebView (HTML rendering via data URI) | | Code syntax highlighting | ✅ Done | highlight.js embedded, 52 langs w/ alias support | | Table rendering (pipe tables) | ✅ Done | Markdown + Org table syntax → HTML | | .org file headings (=**=) | ✅ Done | Parser regex matches =*=/=**= and =#=/=##= | | .org code blocks (#+BEGIN_SRC) | ✅ Done | =#+BEGIN_SRC lang / #+END_SRC= → | | Inline code protection | ✅ Done | =[text](url)= inside backticks renders literally | | Code-block-aware page splits | ✅ Done | =#= inside code blocks don't trigger page breaks | | Deterministic deck identity | ✅ Done | SHA256 of file path → stable GUID | | Dark/light theme | ✅ Done | Live switch in Settings → Theme; dark/light color dictionaries via ThemeHelper.cs | | Selection color | ✅ Done | Blue bg (#2563EB) + white text in WebView | | vim-style j/k scroll | ✅ Done | J/K scrolls home deck list, study box content | | h/l horizontal scroll | ✅ Done | H/L scrolls study box content left/right | | Inline search (no popup) | ✅ Done | =/= focuses bar, Esc unfocuses | | Sidebar (☰ → settings, info) | ✅ Done | Backdrop click + Esc to close, scrollbar when content overflows | | Settings overlay page | ✅ Done | ⚙ in sidebar, placeholder content | | Marketplace | ✅ Done | Placeholder panel --- coming soon | | Get Plugins | ✅ Done | Opens =https://github.com/megamind1230= in browser (would be active on future releases) | | Stable progress across restarts | ✅ Done | DeckFileIdentity + DeckService save | | Auto-refresh on file change | ✅ Done | FileSystemWatcher watches decks dir | | Streak tracking & daily log | ✅ Done | Wall-clock minutes, daily activity | | Heatmap overlay (full-page) | ✅ Done | Snake layout 90° CCW, 6-level orange, green today | | Keyboard shortcuts | ✅ Done | Vim / Emacs / Custom profiles, multi-key chords, command palette | | Settings (sidebar → ⚙) | ✅ Done | Theme, font, decks/mcqs/flashcards path, keybindings profile, free Gemini API key, save/reset | | Theme presets / custom themes | ❌ Not yet | Planned for future updates | | Tag cloud / tag filter | ❌ Not yet | Planned (P27) | | Spaced repetition algorithm | ❌ Not yet | Basic 24h review only | | Image rendering (markdown) | ✅ Done | =![]()= and =![[path]]= → inline image in WebView | | Go to page (Ctrl+G) | ✅ Done | Jump to any page by number during study | | Text zoom (Ctrl+Shift++/-) | ✅ Done | Font-only zoom, keeps component layout | | Image rendering (org) | ✅ Done | =![[file:path]] / ![[file:path][alt]]= | | Image overlay toolbar | ✅ Done | ◀ ▶ nav, zoom slider, invert, open in viewer | | Archive / Pin decks | ✅ Done | Rename-based: =.md= / =+.md=, stable progress IDs; "Learn" button on pinned cards | | Open decks folder | ✅ Done | File manager fallback (17 binaries) → xdg-open | | Clickable links (http/https) | ✅ Done | =[text](url) / [[url]] / [[url][text]]= → | | Bare URL auto-linking | ✅ Done | =https://...= auto-linked in both .md and .org | | Non-http URLs shown as-is | ✅ Done | =[text](local/path)= → raw text, not broken link | | Org /.../ placeholder protect | ✅ Done | Prevents italic regex from corrupting file paths | | Quote blocks (> syntax) | ✅ Done | Renders =>= lines as | | Org block rendering (#+BEGIN_*) | ✅ Done | #+BEGIN_QUOTE etc → monospace pre with dark bg | | Falcon Eye (built-in TOC) | ✅ Done | Settings checkbox --- TOC page at start of each deck; clickable entries navigate to exact page. Persisted in settings.yaml | | Todo checkboxes ([ ], [x], [-], [~]) | ✅ Done | Colored spans: unchecked (empty box), checked (green ✓), in-progress (amber −) | | Documentation ? icon | ✅ Done | Sidebar button + F1 shortcut → opens GitHub README in browser | | Copy button on code/quote blocks | ✅ Done | Hover to copy code/quote content to clipboard, cross-platform | | Smart code block width | ✅ Done | Short code shrink-wraps; long code scrolls at viewport cap | | LaTeX math rendering (KaTeX) | ✅ Done | Inline =$..$= / =\(..\)= and display =$$..$$= / =\[..\]= w/ auto-render | | AI Tag Inference (Google Gemini) | ✅ Done | Open side bar → Tag Inference panel (or profile shortcut: =T= Vim, =C-c t= Emacs, =Ctrl+Shift+T= VS Code). Select a deck → auto-suggest 2--15 tags via Gemini API (free tier). Diff preview before applying. Supports 7 tag formats. Frontmatter health check auto-fills missing =title=/=desc=/=tags=. | | Anki Flashcard Generation (Gemini) | ✅ Done | Two buttons (Basic/Cloze), dedicated prompts, mode-aware parsing (tab-separated vs {{c1::}}), saves as .basic.txt / .cloze.txt for Anki import | | MCQ Quiz (Google Gemini) | ✅ Done | Three-tab panel (Generate / Take Quiz / Quiz Logs). AI-generate =.mcq= files via Gemini, interactive quiz in dedicated WebView (2×2 option grid), scored review. Open shortcut: =E= / =Ctrl+Q= Vim, =C-c q= Emacs, =Ctrl+Shift+Q= VS Code | | Focus Timer (Pomodoro) | ✅ Done | Sidebar panel with three tabs (Timer / Tasks / Log). Pomodoro-style work/break timer with configurable durations, persistent todo list (`focus-timer.yaml`), session history (YAML). Sound notification at session end. Todos support add/edit/delete/toggle. | Make sure to review the AI-generated contents, shape it and edit it the way you like. Sometimes you would be limited by the free AI API limits, but this doesn't mean that this is the only way you can add (tags / MCQ quizzes / flashcards). In fact, you can create your own or with other AI tools. As long as you put them in the proper folder with the proper format, you should be fine 😉. * Tech Stack :PROPERTIES: :CUSTOM_ID: tech-stack :END: - .NET 10.0, Avalonia UI 11.3.12 (cross-platform desktop) - CommunityToolkit.Mvvm 8.2.1 (source-generated MVVM) - Entity Framework Core 8.0.0 + SQLite - Serilog (file + console logging to =~/nextlearn/log.txt=) - NativeWebView 0.1.0-alpha.3 + NativeWebView.Platform.Linux (WebView HTML rendering) - YamlDotNet 18.0.0 (YAML serialization for settings + keybindings config) - highlight.js 11.11.1 (syntax highlighting, 52 languages w/ alias support, embedded as Assets/custom-highlight.js) - Linux: requires =libwpewebkit-2.0=, =libwpe-1.0=, =libWPEBackend-fdo-1.0= - Wayland: auto-detects =XDG_SESSION_TYPE=wayland=, unsets =WAYLAND_DISPLAY= (GLFW skip) + forces X11 backend via =UseSkia().UseX11()= - Markdown files as deck source (YAML frontmatter + =#= headings) * Architecture :PROPERTIES: :CUSTOM_ID: architecture :END: ** Design philosophy mostly offline :PROPERTIES: :CUSTOM_ID: design-philosophy-mostly-offline :END: The desktop app is fully offline except for the optional AI Tag Inference feature, which requires an internet connection and a free Google Gemini API key. All other features (deck reading, search, study, heatmap, export) work completely offline with no network calls. ** Data Sources :PROPERTIES: :CUSTOM_ID: data-sources :END: - ~/.config/nextlearn/nextlearn.db --- users, progress, activity - =~/nextlearn/decks/*.md, *.org, or *.txt= --- deck content with YAML frontmatter (.md/.org) or as plain text (.txt); recursive --- all subdirectories scanned - =~/.config/nextlearn/focus-timer.yaml= --- Focus Timer persistent data (todos + session log) ** Project Structure :PROPERTIES: :CUSTOM_ID: project-structure :END: | Path | Purpose | |---------------------------------+---------------------------------------| | =nextlearn.Desktop/= | Avalonia UI desktop app (C#, .NET 10) | | =nextlearn.Desktop/Models/= | Deck, Page, Flashcard, User, FocusTimerData, etc. | | =nextlearn.Desktop/Data/= | AppDbContext (SQLite) | | =nextlearn.Desktop/Services/= | Business logic layer | | =nextlearn.Desktop/ViewModels/= | MVVM view models (incl. FocusTimer) | | =nextlearn.Desktop/Views/= | MainWindow.axaml (single window) | | =nextlearn.Desktop/Controls/= | FocusTimerPanel, TagInferencePanel, Sidebar, Settings | ** Key Services :PROPERTIES: :CUSTOM_ID: key-services :END: | Service | Role | |-------------------------+------------------------------------------------------------------------------| | ThemeHelper | Runtime dark/light color dictionaries, ApplyTheme() for live switching | | DeckFileParser | Static parser: frontmatter + heading (.md/.org) or line-chunk (.txt) → Deck | | DeckFileIdentity | SHA256 of path → deterministic GUID | | DeckService | DB layer: decks, pages, progress tracking | | DeckFileService | File ops: archive/pin/rename decks | | UserService | Current user, daily activity, streak (computed from study minutes) | | SettingsService | Persists theme/font/paths to =.yaml= | | HtmlContentBuilder | Static HTML builder from Page model | | FalconEyeBuilder | Generates table-of-contents HTML from Deck | | MarkdownInlineRenderer | Strategy for .md inline rendering | | OrgInlineRenderer | Strategy for .org inline rendering | | TagInferenceService | Gemini API client: model discovery, retry, tag parsing | | TagInferenceResult | AI tag suggestion result model | | DeckFileWriter | Tag write-back (7 formats) + frontmatter health check (.md/.org) | | FlashcardService | Gemini API client: model fallback chain, retry, mode-aware TSV/cloze parsing | | FlashcardGenerationMode | Enum: Basic / Cloze flashcard generation mode | | FlashcardResult | AI flashcard generation result model | | McqFileParser | Parses =.mcq= files into question blocks | | McqFileService | CRUD for MCQ files | | McqQuizHtmlBuilder | Builds interactive quiz HTML (2×2 grid, timer, scoring) | | McqGenerationService | Gemini API client for MCQ generation | | McqResultLogger | Logs quiz results to =.mcq.result= files | | KeyBindingService | Keyboard shortcut profiles (Vim/Emacs/VS Code/Custom) | | KeyboardHandler | Routes key events to actions based on context + profile | | KeyboardActionKind | Enum of all keyboard actions | | WebViewBridge | WebView↔native communication (key bridge, URL clicks, image overlay) | | HtmlContentService | HTML enrichment service (KaTeX, highlight.js, copy buttons) | | FocusTimerViewModel | Pomodoro timer logic, todo CRUD, YAML persistence (focus-timer.yaml) | * Deck File Format :PROPERTIES: :CUSTOM_ID: deck-file-format :END: All =.md=, =.org=, and =.txt= files under =~/nextlearn/decks/= (and any subdirectory) are first-class decks. FileName includes the relative subdirectory path (e.g. =science/physics.md=), used for stable identity, archive/pin rename, and search. The parser reads YAML frontmatter and section headings for =.md= / =.org=, or splits on line count for =.txt=: ** With .md frontmatter (recommended) :PROPERTIES: :CUSTOM_ID: with-frontmatter-recommended :END: #+begin_src text --- title: Wake Up Properly description: Fix morning face puffiness with these simple changes. tags: health, morning, sleep, face, self-care --- # Section Title Content here... ## Page Title Page content here... ## Another Page More content... #+end_src The =tags:= field in YAML frontmatter supports multiple formats --- the app auto-detects which format is used and preserves it when writing changes back: #+begin_src text # Comma-separated list: tags: health, morning, sleep # Double-quoted string: tags: "health, morning, sleep" # Single-quoted string: tags: 'health, morning, sleep' # YAML inline array (brackets): tags: [health, morning, sleep] # YAML block list (indented with dashes): tags: - health - morning - sleep #+end_src ** .txt file support :PROPERTIES: :CUSTOM_ID: txt-file-support :END: Plain =.txt= files are read as decks without any frontmatter or heading parsing: | Property | Behaviour | |---------------+-----------------------------------------------------| | Title | First non-empty line of the file | | Description | Second non-empty line (truncated to 200 chars) | | Tags | Empty (no frontmatter) — excluded from tag inference| | Page splitting| 25 lines per page, each page ≈ 25 readable lines | | Page title | First non-empty line of that 25-line chunk | | Readability | Sentences auto-split onto separate lines after . ? !| | Rendering | Plain text (no markdown/org syntax parsing) | #+begin_src text Introduction to Machine Learning Machine learning is a subset of artificial intelligence. There are three main types of machine learning. ... #+end_src Each =.txt= file is split into pages of 25 lines. Sentences are automatically broken onto separate lines after terminal punctuation (=.=, =?=, =!=) to improve readability per page. ** .org file metadata (recommended) :PROPERTIES: :CUSTOM_ID: org-file-metadata :END: #+begin_src text ,#+TITLE: My Deck ,#+DESCRIPTION: Description text here. ,#+TAGS: tag1, tag2 ,* Section ,** Page One Content #+end_src The =#+TAGS:= field supports multiple formats --- the app auto-detects and preserves them: #+begin_src text # Comma-separated: ,#+TAGS: tag1, tag2 # Colon-separated (flat): ,#+TAGS: :tag1:tag2: # Colon-separated (hierarchy — double colon → /): ,#+TAGS: :parent::child:topic: #+end_src When metadata is absent, the study page header falls back to the disk file name (e.g. =my-deck.org=). The deck list card shows the first content line as title and 2nd+3rd lines as description. ** No frontmatter (fallback) :PROPERTIES: :CUSTOM_ID: no-frontmatter-fallback :END: #+begin_src markdown # Deck Title (becomes the deck title) First non-empty line after title (becomes description) ## Page One Content ## Page Two Content #+end_src ** Heading rules :PROPERTIES: :CUSTOM_ID: heading-rules :END: - =# Section= → sets section context (shown in breadcrumb: =DeckTitle → Section=) - =## Page= → creates a page with =SectionTitle= from nearest parent =#= - No =##= headings → =#= headings become pages directly (legacy) ** .mcq file format (MCQ Quiz) :PROPERTIES: :CUSTOM_ID: mcq-file-format-mcq-quiz :END: MCQ quiz files use the =.mcq= extension (=fileName.md.mcq=) and are valid Markdown internally. Questions are separated by =~=---=~=, with each block containing a =## Question N= heading, =A./B./C./D.= options, =**Answer:**=, and =**Explanation:**=: #+begin_src markdown --- title: Algebra Quiz source: algebra.md generated: 2026-06-30 --- ## Question 1 What is 2 + 2? A. 3 B. 4 C. 5 D. 6 ,**Answer:** B ,**Explanation:** 2 + 2 = 4. Basic arithmetic. --- ## Question 2 Simplify: 3x + 2x A. 5x B. 6x C. x D. 5x² ,**Answer:** A ,**Explanation:** 3x + 2x = (3+2)x = 5x. Combine like terms. #+end_src Multi-line question/explanation content (code fences, LaTeX, lists, blockquotes) is supported --- the quiz WebView renders block-level constructs via =HtmlContentBuilder.RenderBlock= and KaTeX/highlight.js enrichment. * How to Use :PROPERTIES: :CUSTOM_ID: how-to-use :END: 1. Create a =.md / .org= file in =~/nextlearn/decks/= (or any subdirectory) with YAML frontmatter: #+begin_src markdown --- title: My Deck description: What this deck is about tags: topic1, topic2, keyword --- #+end_src 2. Add sections and pages: #+begin_src markdown # Section Name ## Page One Content for this page... #+end_src 3. Run the app --- decks appear on the home view 4. Search decks using space-separated tokens (AND logic --- all must match): =baka= → search across Title + Description + FileName (any field) =file:baka= → FileName only =title:chapter= → Title only =desc:derivative= → Description only =#math= → tag prefix match (=#ma= matches =math=) =tags:math, phys= → tag match with comma-separated values (trimmed, both required) 🔑 The filter prefix (=file:=, =title:=, =desc:=, =description:=, =tags:=) must be directly followed by the search term with **no space** after the colon. =file:calc= works; =file: calc= treats =file:= as an empty filter and =calc= as a separate generic token. Use =""= for phrase matching in =file:=, =title:=, =desc:=, =description:=: =file:"calc notes"= matches filenames containing =calc notes= as one phrase. Enable ☐ Regex for regex patterns across all matching. 5. Click a deck or press N to start learning 6. Navigate pages with ← → or N / P keys 7. Press Q / D to go home (progress auto-saved) * Link & Image Syntax Reference :PROPERTIES: :CUSTOM_ID: link-image-syntax-reference :END: Every link and image syntax variant and how the app handles each: ** Markdown (.md) syntax :PROPERTIES: :CUSTOM_ID: markdown-.md-syntax :END: | Syntax | Type | Renders as | Rule | |-----------------------------------+-------+---------------------------------+-----------------------------------| | =[text](http://url)= | Link | =<a data-href="url">text</a>= | http/https only | | =[text](local/path)= | Link | =[text](local/path)= (raw text) | Non-http → as-is | | =[[http://url]]= | Link | =<a data-href="url">url</a>= | Wiki-style, http/https only | | =[[local/text]]= | Link | =[[local/text]]= (raw text) | Non-http → as-is | | == | Image | =<img class="inline-image">= | Always renders image | | == | Image | =<img>= (title stripped) | Title via =""= or ="= | | =![[path]]= | Image | =<img class="inline-image">= | Obsidian-style image | | =[](url)= | Mixed | =<img>= inner, outer raw | Link regex skips =[...](= | | =https://bare.url= | Link | =<a data-href="url">url</a>= | Auto-linked in both md & org | | =`[text](url)`= | Code | =<code>[text](url)</code>= | Protected by code span extraction | | =- [x] Done / TODO / DONE= | Meta | Colored spans | Checkboxes + inline keywords | | =- [~] In progress / - [-] Doing= | Meta | Amber in-progress span | Amber border + minus sign | ** Org (.org) syntax :PROPERTIES: :CUSTOM_ID: org-.org-syntax :END: | Syntax | Type | Renders as | Rule | |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+---------------------------------+-----------------------------------| | =[[http://url][text]]= | Link | =<a data-href="url">text</a>= | http/https only | | =[[http://url]]= | Link | =<a data-href="url">url</a>= | http/https only | | =[[file:path][alt]]= | Text | =[[file:path][alt]]= (raw text) | No =!= → as-is | | =[[file:path]]= | Text | =[[file:path]]= (raw text) | No =!= → as-is | | =[[local/target][text]]= | Text | =[[local/target][text]]= (raw) | Non-http → as-is with brackets | | =[[local/target]]= | Text | =[[local/target]]= (raw) | Non-http → as-is with brackets | | =![[http://url][text]]= | Link | =<a data-href="url">text</a>= | http overrides =!= | | =![[http://url]]= | Link | =<a data-href="url">url</a>= | http overrides =!= | | =![[file:path][alt]]= | Image | =<img class="inline-image">= | Strip =file:=, alt preserved | | =![[file:path]]= | Image | =<img class="inline-image">= | Strip =file:=, no alt | | =![[path][alt]]= | Image | =<img class="inline-image">= | No =file:=, path used directly | | =![[path]]= | Image | =<img class="inline-image">= | No =file:=, path used directly | | =[](img.png)= | Image | =<img>= | Markdown-style image (same as md) | | =[text](http://url)= | Link | =<a>= | Markdown-style link (same as md) | | =[text](local/path)= | Link | =[text](local/path)= (raw text) | Non-http → as-is | | =/italic/ / *bold* /=code`=| Format |= / / =| Inline formatting | |=[[...]] / ![[...]]=in path | Protect | Placeholder before formatting | Prevents / italic path corruption | |=https://bare.url=| Link |=url` | Auto-linked | | | Local path links (e.g. =[text](local/path)= / =[[local/target]]=) are also checked against known decks --- if the path matches an existing =.md= or =.org= file, they render as =<a data-decklink="...">= navigable links instead of raw text. ** Image resolution rules :PROPERTIES: :CUSTOM_ID: image-resolution-rules :END: All image paths resolve relative to =$DECKS_DIR= (=~/nextlearn/decks/=): | Syntax | Resolved path | |------------------------------+-------------------------------------------| | == | =$DECKS_DIR/img.png= | | == | =$DECKS_DIR/subdir/img.png= | | =![[file:./sub/img.png]]= | =$DECKS_DIR/sub/img.png= (strips =file:=) | | =http://example.com/cat.png= | =<a>= link (not an image) | | | | Path traversal (e.g. =../../etc/passwd=) is blocked by the path-traversal guard in =RenderImageTag() — renders as ~image path mis-referenced= error. ** Image error messages :PROPERTIES: :CUSTOM_ID: image-error-messages :END: | Condition | Display | |----------------------------+---------------------------------------| | =imageDir= not configured | =image folder not configured: path= | | Path escapes =$DECKS_DIR= | =image path mis-referenced: path= | | File does not exist | =image not found: path= | | File read/embed failure | =image not found: path= (catch block) | | URL passed to image syntax | =<a data-href="url">alt</a>= | ** Supported image types :PROPERTIES: :CUSTOM_ID: supported-image-types :END: Only the following formats are guaranteed to render inline and preview in the floating image window: | Format | Inline render | Overlay preview | Inversion | |------------------+---------------+-----------------+-----------| | =.png= | ✅ | ✅ | ✅ | | =.jpg= / =.jpeg= | ✅ | ✅ | ✅ | | =.gif= | ✅ | ✅ | ✅ | | =.webp= | ✅ | ✅ | ✅ | | =.bmp= | ✅ | ✅ | ✅ | Other formats (=.ico=, =.tiff=, =.svg=, =.avif=, =.heic=, etc.) are not guaranteed to render correctly. =.svg= works inline (browser renders it) but fails in the overlay (=Avalonia.Media.Imaging.Bitmap= cannot decode SVG). Unrecognized extensions fall back to =application/octet-stream= in the WebView, producing a broken image icon. Images that fail to load (missing file, corrupt, etc.) are automatically skipped when using ◀▶ / N/P navigation --- only successfully rendered images appear in the cycle. ** Deck links / wiki links (navigable cross-references) :PROPERTIES: :CUSTOM_ID: deck-links-wiki-links-navigable-cross-references :END: Decks can link to each other using any of these formats: | Format | Example | Notes | |----------------------------------+------------------------------------+----------------------------------------| | Bare wiki (Obsidian) | =[[mathematics]]= | Tries =.md=, =.org= fallback | | Alias (Obsidian) | =[[physics|Physics notes]]= | Uses =Physics notes= as link text | | Org-style file link | =[[file:index.md][Back to index]]= | Works in =.md= files too | | Standard markdown | =[Chemistry](nested/chemistry.md)= | Also with extension fallback | | Cross-format wiki (no extension) | =[[physics]]= | Resolves to =physics.org= if no _{.md} | All formats work in both =.md= and =.org= files. When a link target has no file extension (e.g. =[[mathematics]]=), the app tries =.md= first, then =.org=. If neither exists, the app also tries =+target.md= (pinned) and =target.md= (archived) before giving up. Clicking a link to an archived deck shows an "Unarchive and open?" prompt before navigating. Prefix any wiki-link with =!= to render it as plain text (suppresses the navigation prompt). This does /not/ affect image embeds --- =![[image.png]]= still embeds the image. | Example | Behavior | |-------------------------------+---------------------------------------------| | =[[mathematics]]= | Clickable → links to =mathematics.md= | | =[[physics|Physics notes]]= | Clickable → links to =physics= (.md → .org) | | =[[file:chemistry.md][Chem]]= | Clickable "Chem" → =chemistry.md= | | =![[mathematics]]= | Plain text =![[mathematics]]= (not a link) | | =[Text](nested/deep/biology)= | Clickable → =biology.md= or =biology.org= | | =~file:~= without extension | =[[file:geometry]]= | NextLearn is forgiving --- all link formats let you omit the file extension. The app searches for =.md= first, then =.org=, so =[[geometry]]= and =[[file:geometry]]= both resolve to whichever exists. * Quote & Block Rendering :PROPERTIES: :CUSTOM_ID: quote-block-rendering :END: ** Blockquotes (=>=) --- .md and .org :PROPERTIES: :CUSTOM_ID: blockquotes-.md-and-.org :END: Lines starting with =>= render as a styled blockquote with a blue left border: #+begin_src markdown > This is a quote. > It can span multiple lines. > > Empty lines separate quote blocks. #+end_src ** Org general blocks (=#+BEGIN_\*_= / =#+END_\*_=) --- .org only :PROPERTIES: :CUSTOM_ID: org-general-blocks-begin__-end__-.org-only :END: Any =#+BEGIN_\*_= / =#+END_\*_= block (except =#+BEGIN_SRC_=) renders as monospace text with a dark background. Content is shown as-is (no inline formatting). #+begin_src org ,#+BEGIN_QUOTE This is an org-mode quote block. It renders as monospace with a dark background. ,#+END_QUOTE #+end_src Supported block types: =QUOTE=, =EXAMPLE=, =VERSE=, =CENTER=, and any other =#+BEGIN_<type> / #+END_<type>= pair. * Keyboard Shortcuts or Key Bindings :PROPERTIES: :CUSTOM_ID: keyboard-shortcuts-or-key-bindings :END: nextlearn supports four keybinding profiles: =Vim= (default), =Emacs=, =VS Code=, and =Custom=. Switch between them in Settings → Key Bindings. The Vim preset ships with the app. You can also find the complete mapping below for reference or manual setup. ** Command Palette :PROPERTIES: :CUSTOM_ID: command-palette :END: Press =:= (Vim) or =M-x= (Alt+X, Emacs) to open the command palette --- a bottom-bar overlay that shows all available commands with their current keyboard shortcuts. Type any part of the command name to live-filter the list. Press =Enter= to execute the selected command, or =Esc= to close. The palette displays commands from all profiles, but only the currently active profile's shortcuts are shown as key hints. ** Multi-key chords :PROPERTIES: :CUSTOM_ID: multi-key-chords :END: Both profiles support multi-key chords (e.g. =C-x p=, =C-c s=, =g then i=). When you press a chord prefix, a pending indicator appears at the top-center of the window showing the keys pressed so far. If you don't complete the chord within 500ms, it cancels automatically. Available chord prefixes: - =C-x= (Emacs) --- followed by =p= (pinned), =a= (archived), =h= (heatmap) - =C-c= (Emacs) --- followed by =o= (open decks folder), =m= (marketplace), =s= (sidebar) - =g= (Vim) --- followed by =i= (focus and clear search bar) - =C-g= (Emacs only) --- cancels the pending chord and closes current overlay (like Esc) ** Cross-Profile (same across all presets) :PROPERTIES: :CUSTOM_ID: cross-profile-same-across-all-presets :END: - [X] Ctrl+Shift+= / Ctrl+Shift+- / Ctrl+Shift+0 --- Zoom text in/out/reset - [X] Emacs --- Home search, Study page, Heatmap - [X] Vim --- Home search, Study page, Heatmap - [X] VSCode --- Home search, Study page, Heatmap - [X] Ctrl+= / Ctrl+- / Ctrl+0 --- Zoom image in/out/reset - [X] Emacs --- Image Overlay - [X] Vim --- Image Overlay - [X] VSCode --- Image Overlay - [X] Shift+N / Shift+P --- Next/Previous image - [X] Emacs --- Image Overlay - [X] Vim --- Image Overlay - [X] VSCode --- Image Overlay - [X] Esc --- Cancel focus / close panels - [X] Emacs --- Search, Pinned, Archived, Heatmap, Flashcards, Tags, Study, Settings, MCQ (quiz+tabs) - [X] Vim --- Search, Settings, Pinned, Archived, Heatmap, Flashcards, Tags, Study, MCQ (quiz+tabs) - [X] VSCode --- Search, Settings, Pinned, Archived, Heatmap, Flashcards, Tags, Study, MCQ (quiz+tabs) - [X] ↑ / ↓ / ← / → --- Scroll content - [X] Emacs --- Home search, Study page, Flashcards, Tags, MCQ (quiz+tabs) - [X] Vim --- Home search, Study page, Flashcards, Tags, MCQ (quiz+tabs) - [X] VSCode --- Home search, Study page, Flashcards, Tags, MCQ (quiz+tabs) - [X] Alt+← / Alt+→ --- Previous/Next page - [X] Emacs --- Study page, MCQ quiz - [X] Vim --- Study page, MCQ quiz - [X] VSCode --- Study page, MCQ quiz - [X] F1 --- Open documentation - [X] Emacs --- Search, Study, Pinned, Archived, Flashcards, Tags, Heatmap, MCQ (quiz+tabs), Settings - [X] Vim --- Search, Study, Pinned, Archived, Flashcards, Tags, Heatmap, MCQ (quiz+tabs), Settings - [X] VSCode --- Search, Study, Pinned, Archived, Flashcards, Tags, Heatmap, MCQ (quiz+tabs), Settings - [X] Ctrl+Alt+Shift+O --- Reveal current deck in file manager - [X] Emacs - [X] Vim - [X] VSCode ** Vim preset (default) :PROPERTIES: :CUSTOM_ID: vim-preset-default :END: *** Learning :PROPERTIES: :CUSTOM_ID: learning :END: - [X] N --- Next page - [X] P --- Previous page - [X] J --- Scroll content down 40px - [X] K --- Scroll content up 40px - [X] H --- Scroll content left 40px - [X] L --- Scroll content right 40px - [X] Q / D --- Exit to home - [X] Ctrl+G --- Go to page (page-number dialog) *** Home :PROPERTIES: :CUSTOM_ID: home :END: - [X] J --- Scroll deck list down 40px - [X] K --- Scroll deck list up 40px - [X] / --- Focus search bar - [X] g then i --- Focus + clear search bar - [X] Q / D --- Go home *** Global :PROPERTIES: :CUSTOM_ID: global :END: - [X] Ctrl+, --- Open settings - [X] ? / Shift+/ --- Toggle shortcuts handbook - [X] T --- Open Tag Inference panel - [X] Ctrl+T --- Open Tag Inference panel (alt) - [X] E / Ctrl+Q --- Open MCQ Quiz - [X] Ctrl+E --- Open MCQ Quiz (alt) - [X] Ctrl+P --- Show pinned decks - [X] Ctrl+A --- Show archived decks - [X] Ctrl+H --- Show heatmap - [X] Ctrl+M --- Navigate to marketplace - [X] Ctrl+F --- Open Flashcard panel - [X] S --- Toggle sidebar - [X] Ctrl+O --- Open decks folder - [X] : --- Open command palette ** Emacs preset :PROPERTIES: :CUSTOM_ID: emacs-preset :END: *** Learning :PROPERTIES: :CUSTOM_ID: learning-1 :END: - [X] C-n --- Next page - [X] C-p --- Previous page - [X] C-v --- Scroll content down - [X] M-v --- Scroll content up - [X] C-b --- Scroll content left - [X] C-f --- Scroll content right - [X] C-x q / C-x d --- Exit to home - [X] C-x g --- Go to page (page-number dialog) *** Home :PROPERTIES: :CUSTOM_ID: home-1 :END: - [X] C-v --- Scroll deck list down - [X] M-v --- Scroll deck list up - [X] C-s --- Focus search bar - [X] C-x i --- Focus + clear search bar - [X] C-x q / C-x d --- Go home - [X] C-c o --- Open decks folder - [X] C-c m --- Open marketplace *** Global :PROPERTIES: :CUSTOM_ID: global-1 :END: - [X] C-c C-s --- Open settings - [X] C-h ? --- Toggle shortcuts handbook - [X] C-c t --- Open Tag Inference panel - [X] C-c q --- Open MCQ Quiz - [X] C-c p --- Show pinned decks - [X] C-c a --- Show archived decks - [X] C-c h --- Show heatmap - [X] C-c s --- Toggle sidebar - [X] C-c f --- Open Flashcard panel - [X] M-x --- Open command palette - [X] C-g --- Cancel chord / close overlay (Esc) =o= is not bound in Emacs (Vim-only). ** VSCode preset :PROPERTIES: :CUSTOM_ID: vscode-preset :END: *** Learning :PROPERTIES: :CUSTOM_ID: learning-2 :END: - [X] N / → --- Next page - [X] P / ← --- Previous page - [X] J --- Scroll content down 40px - [X] K --- Scroll content up 40px - [X] H --- Scroll content left 40px - [X] L --- Scroll content right 40px - [X] Q / D / Ctrl+W --- Exit to home / close deck - [X] Ctrl+G --- Go to page (page-number dialog) *** Home :PROPERTIES: :CUSTOM_ID: home-2 :END: - [X] J --- Scroll deck list down 40px - [X] K --- Scroll deck list up 40px - [X] / --- Focus search bar - [X] Ctrl+F --- Focus search bar - [X] g then i --- Focus + clear search bar - [X] Q / D --- Go home *** Global :PROPERTIES: :CUSTOM_ID: global-2 :END: - [X] Ctrl+, --- Open settings - [X] ? / Shift+/ --- Toggle shortcuts handbook - [X] Ctrl+K then Ctrl+S --- Toggle shortcuts handbook - [X] Ctrl+Shift+T --- Open Tag Inference panel - [X] Ctrl+Shift+Q --- Open MCQ Quiz - [X] Ctrl+B --- Toggle sidebar - [X] Ctrl+O --- Open decks folder - [X] : --- Open command palette - [X] Ctrl+P --- Open command palette - [X] Ctrl+Shift+P --- Show pinned decks - [X] Ctrl+Shift+A --- Show archived decks - [X] Ctrl+Shift+H --- Show heatmap - [X] Ctrl+Shift+M --- Open marketplace - [X] Ctrl+Shift+F --- Open Flashcard panel To use a custom config, unhide the template file written to the app's config directory: #+begin_src sh mv ~/.config/nextlearn/keybindings.yaml` `/.config/nextlearn/keybindings.yaml $EDITOR ~/.config/nextlearn/keybindings.yaml #+end_src Then select =Custom= in Settings → Key Bindings and click Save. ** Binding format reference :PROPERTIES: :CUSTOM_ID: binding-format-reference :END: | Field | Required | Description | |-------------+----------+---------------------------------------------------------------------| | =action= | yes | One of the =KeyboardActionKind= enum values (see below) | | =key= | yes | Avalonia Key enum name; inline =#= comments explain the real key | | =modifiers= | yes | =Control=, =Shift=, =Alt=, =Control+Shift=, or empty =""= | | =chords= | no | List of ={ key, modifiers }= for multi-key sequences (e.g. =C-x p=) | | =context= | no | =Learning=, =Home=, =ImageOverlay=, =McqQuiz=, or omit for global | | =textBox= | no | Set =true= to allow in text input fields (default false) | | =_comment= | no | Description shown in the Shortcuts Handbook | Available =action= values: - =NextPage= / =PreviousPage= --- flip deck pages during study - =ScrollDown= / =ScrollUp= / =ScrollLeft= / =ScrollRight= --- scroll WebView content - =NavigateHome= --- exit study or settings back to deck list (closes all overlays first) - =OpenSettings= / =CloseSettings= --- toggle settings panel - =ExitSettingsHome= --- close settings and navigate home - =ZoomTextIn= / =ZoomTextOut= / =ResetTextZoom= --- font scaling - =ZoomIn= / =ZoomOut= / =ResetZoom= --- image overlay zoom - =NextImage= / =PreviousImage= --- cycle images in overlay - =FocusSearchBar= --- focus the search bar on home screen - =FocusSearchWithClear= --- focus and clear search bar (g then i chord) - =ScrollDeckListDown= / =ScrollDeckListUp= --- scroll the deck list - =ToggleShortcutsHandbook= --- show/hide this handbook (auto-synced from KeyBindingService, stays up to date with new bindings) - =OpenGoToPage= --- jump to a page number during study - =OpenDocumentation= --- open GitHub README in browser - =ZoomHeatmapIn= / =ZoomHeatmapOut= / =ZoomHeatmapReset= --- heatmap zoom - =ToggleSidebar= --- toggle sidebar panel - =OpenDecksFolder= --- open decks folder in file manager - =OpenCurrentDeckFolder= --- reveal current deck in file manager (Ctrl+Alt+Shift+O) - =ShowPinnedView= --- show pinned decks - =ShowArchivedView= --- show archived decks - =ShowHeatmap= --- show study streak heatmap - =NavigateToMarketplace= --- navigate to marketplace - =OpenCommandPalette= --- open command palette (: / M-x) - =CloseCommandPalette= --- close command palette (Esc) - =OpenTagInference= --- open Tag Inference panel (=T= Vim / =C-c t= Emacs / =Ctrl+Shift+T= VS Code) - =CloseTagInference= --- close Tag Inference panel (Esc) - =OpenMcqQuiz= --- open MCQ Quiz panel (=E= Vim / =C-c q= Emacs / =Ctrl+Shift+Q= VS Code) - =CloseMcqQuiz= --- close MCQ Quiz panel (Esc) The following are handled internally and are not configurable: - =Esc=: closes overlays in priority order (CommandPalette → GoToPage → Handbook → ImageOverlay → ArchivedView → PinnedView → TagInference → McqQuiz → Heatmap → Marketplace → Settings → Sidebar → FlashcardPanel → clear focus) - =C-g= (Emacs only): same as Esc --- cancels pending chord and closes current overlay - =:= (Vim) / =M-x= (Emacs): open command palette (hard-coded in KeyboardHandler before binding table lookup, so it works even in text fields) ** Notes :PROPERTIES: :CUSTOM_ID: notes :END: - ⚠ OpenDecksFolder and OpenCurrentDeckFolder are experimental --- file-manager detection varies by OS and desktop environment #+begin_quote notice that any shortcut including =d= inside quiz UI shouldn't trigger, cuz the quiz UI prioritizes the =d= for option selection, NOT urgent, but we can solve later #+end_quote * Gemini Prompts :PROPERTIES: :CUSTOM_ID: gemini-prompts :END: The prompts below are copied directly from the app's source code. If you are having issues with the Gemini API (rate limits, model availability, cost), you can paste them into any Gemini-compatible client (=google-gemini= CLI, AI Studio, etc.) with your deck content to get the same output. ** Tag Inference Prompt :PROPERTIES: :CUSTOM_ID: tag-inference-prompt :END: Source: =Services/TagInferenceService.cs= (method =InferTagsAsync=) #+begin_src text You are a tag inference AI. Given this deck content and its existing tags, suggest 2-15 additional tags that capture the core concepts, topics, semantics, mnemonics, and potential aliases. Existing tags: {existingTags} Deck content: {content} Return a JSON array of strings only, e.g. ["tag1", "tag2"]. #+end_src Replace ={existingTags}= with the deck's current comma-separated tags (or "none") and ={content}= with the full deck text. ** MCQ Generation Prompt :PROPERTIES: :CUSTOM_ID: mcq-generation-prompt :END: Source: =Services/McqGenerationService.cs= (method =BuildPrompt=) #+begin_src text You are an educator creating multiple-choice questions from study material. Given the deck content below, create {questionCount} questions. Format each question EXACTLY like this: ## Question 1 {question text} A. {option A} B. {option B} C. {option C} D. {option D} **Answer:** A **Explanation:** {why this is correct} --- ## Question 2 ... Rules: - Each question must have exactly 4 options (A, B, C, D) - Make sure exactly one answer is correct - Distractors should be plausible but clearly wrong - Use --- on its own line as a separator between questions - Do NOT use any markdown code fences - Cover EVERY section of the content --- do not skip any part - Generate exactly {questionCount} questions Deck content: --- {content} --- #+end_src Replace ={questionCount}= with the desired number of questions and ={content}= with the deck text. ** Basic Flashcard Prompt :PROPERTIES: :CUSTOM_ID: basic-flashcard-prompt :END: Source: =Services/FlashcardService.cs= (method =BuildBasicPrompt=) #+begin_src text You are an Anki flashcard generator. Convert the following study material into BASIC flashcards in TSV format. BASIC flashcards are simple question -> answer pairs (one per line, tab-separated). Use these for: - Vocabulary: term TAB translation (e.g. "hello bonjour") - Definitions: term TAB definition (e.g. "Function A reusable block of code that performs a specific task") - Q&A: question TAB answer Rules: - One card per line, tab-separated (term TAB definition/answer) - Return ONLY the TSV lines --- no markdown, no code fences, no explanations - Cover EVERY section of the content --- do not skip any part - Generate as many cards as needed to cover all material Deck content: --- {content} --- #+end_src Replace ={content}= with the deck text. Save the output to a =.basic.txt= file and import into Anki as "Basic" note type. ** Cloze Flashcard Prompt :PROPERTIES: :CUSTOM_ID: cloze-flashcard-prompt :END: Source: =Services/FlashcardService.cs= (method =BuildClozePrompt=) #+begin_src text You are an Anki flashcard generator. Convert the following study material into CLOZE deletion flashcards. CLOZE cards use {{c1::term}} to create fill-in-the-blank sentences. Use these for: - Definitions: "A {{c1::function}} is a reusable block of code that performs a specific task." - Key concepts: "Binary search has a time complexity of {{c1::O(log n)}}." - Fill-in-the-blank for any term, definition, or concept being explained Format: One sentence per line, each sentence containing exactly one {{c1::term}} blank. Rules: - Every line MUST contain {{c1::}} syntax --- one blank per card - Do NOT use tab characters --- the entire line is the cloze text - Return ONLY the cloze lines --- no markdown, no code fences, no explanations - Cover EVERY section of the content --- do not skip any part - Generate as many cards as needed to cover all material Deck content: --- {content} --- #+end_src Replace ={content}= with the deck text. Save the output to a =.cloze.txt= file and import into Anki as "Cloze" note type. * LaTeX and Math Rendering :PROPERTIES: :CUSTOM_ID: latex-and-math-rendering :END: nextlearn renders LaTeX math expressions using KaTeX (bundled offline, no CDN). Math is always-on --- no frontmatter toggle needed. ** Supported delimiters :PROPERTIES: :CUSTOM_ID: supported-delimiters :END: | Delimiter pair | Display mode | Example | |----------------+--------------+--------------------------------| | =$...$= | Inline | =$x^2 + y^2 = z^2$= | | =\(...\)= | Inline | =\(E = mc^2\)= | | =$$...$$= | Display | =$$\int_0^1 x^2 \, dx$$= | | =\[...\]= | Display | =\[\sum_{n=1}^\infty n^{-2}\]= | ** Code takes priority :PROPERTIES: :CUSTOM_ID: code-takes-priority :END: Inline code (backticks in =.md=, ==...= / ~...~= in =.org=) always takes priority over math. This means you can safely use dollar signs inside code spans without triggering LaTeX: - =`$x^2 + y^2 = z^2$`= → renders as literal code, not math - =`$5`= for coffee → renders the literal text =$5=, avoids math mode ** Dollar signs in plain text :PROPERTIES: :CUSTOM_ID: dollar-signs-in-plain-text :END: A lone dollar sign followed by a number (e.g. =$5=, =$100=) may be interpreted as math delimiters. To display them literally, wrap in backticks: - ~~ =\$5= for coffee ~~ → renders as =$5 for coffee= - ~~ =\$100= - =\$200= ~~ → renders as =$100 - $200= This works because the inline code renderer runs before the math extractor, so backtick-protected content is never scanned for delimiters. ** Code blocks :PROPERTIES: :CUSTOM_ID: code-blocks :END: Math inside fenced code blocks (=```=) or org source blocks (=#+BEGIN_SRC / #+END_SRC=) is never rendered as math --- it stays as literal text with syntax highlighting. ** Copy button :PROPERTIES: :CUSTOM_ID: copy-button :END: Math display blocks (=$$..$$ / \[..\]=) include a hover-to-copy button. Copying grabs the raw LaTeX source (from the =data-latex= attribute), not the rendered HTML. ** Equation numbers :PROPERTIES: :CUSTOM_ID: equation-numbers :END: The =align=, =equation=, =gather= and similar environments render equation numbers (=(1)=, =(2)=, ...) on the right side. Display blocks reserve =3em= right padding so numbers don't overlap the equation content.