Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Mindmap NextGen
https://github.com/james-tindal/obsidian-mindmap-nextgen
Admin
Obsidian plugin to view your notes as mindmaps using Markmap, offering features like screenshot
...
Tokens:
6,334
Snippets:
35
Trust Score:
7.9
Update:
4 months ago
Context
Skills
Chat
Benchmark
94.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Mindmap NextGen Mindmap NextGen is an Obsidian plugin that transforms Markdown notes into interactive mindmaps using the Markmap visualization library. It enables users to view their hierarchical note structures as dynamic, zoomable mindmaps with support for wikilinks, embeds, LaTeX, code highlighting, and checkboxes. The plugin integrates seamlessly into Obsidian's workspace, providing both dedicated tab views and inline code block rendering. The plugin offers flexible configuration through a three-tier settings system (global, file-level, and code-block-level), multiple coloring strategies for visual organization, and advanced features including screenshot export, customizable styling, and reactive rendering. Built on a functional reactive programming architecture using Callbag streams, it provides smooth integration with Obsidian's API while maintaining high performance and extensibility. ## APIs and Key Functions ### Plugin Entry Point Initialize the plugin and lazy-load main functionality. ```typescript import { Plugin } from 'obsidian' export const __entry = {} as { plugin: Plugin } export default class extends Plugin { onload() { __entry.plugin = this import('./main') } } ``` ### Global Settings Configuration Define and manage plugin-wide settings with automatic persistence. ```typescript import { globalSettings, defaultSettings, settingsLoaded } from 'src/settings/filesystem' // Wait for settings to load await settingsLoaded // Access current settings console.log(globalSettings.coloring) // 'depth' | 'branch' | 'single' console.log(globalSettings.initialExpandLevel) // -1 for fully expanded // Modify settings (automatically saves) globalSettings.depth1Color = '#ff0000' globalSettings.animationDuration = 1000 globalSettings.spacingHorizontal = 100 // Default settings structure const defaults = { splitDirection: 'horizontal', nodeMinHeight: 16, lineHeight: '1em', spacingVertical: 5, spacingHorizontal: 80, paddingX: 8, initialExpandLevel: -1, colorFreezeLevel: 0, animationDuration: 500, maxWidth: 0, highlight: true, coloring: 'depth', depth1Color: '#cb4b16', depth2Color: '#6c71c4', depth3Color: '#859900', defaultColor: '#b58900', depth1Thickness: '3', depth2Thickness: '1.5', depth3Thickness: '1', defaultThickness: '1', screenshotBgColor: '#002b36', screenshotBgStyle: 'color', screenshotTextColor: '#fdf6e3', screenshotTextColorEnabled: false, titleAsRootNode: true } ``` ### File-Level Settings (Frontmatter) Override global settings on a per-file basis using YAML frontmatter. ```markdown --- markmap: coloring: branch depth1Color: "#ff0000" initialExpandLevel: 2 spacingHorizontal: 120 animationDuration: 1000 titleAsRootNode: false highlight: true screenshotBgColor: "#ffffff" --- # My Mindmap Root ## First Branch ### Sub-item 1 ### Sub-item 2 ## Second Branch ### Another item ``` ### Code Block Rendering Render mindmaps inline within markdown documents with optional per-block settings. ````markdown ```markmap # Project Planning ## Phase 1: Research - Market analysis - Competitor study - User interviews ## Phase 2: Development - Frontend implementation - Backend API - Database design ## Phase 3: Testing - Unit tests - Integration tests - User acceptance --- markmap: height: 300 coloring: depth depth1Color: "#e74c3c" depth2Color: "#3498db" initialExpandLevel: 2 ``` ```` ### Transform Markdown to Mindmap Convert markdown text into a Markmap node tree with internal link parsing. ```typescript import { transformMarkdown } from 'src/rendering/renderer-common' const markdown = ` # Root Node ## Child 1 - Item A with [[internal-link]] - Item B ## Child 2 - [[another-link|Display Text]] ` const rootNode = transformMarkdown(markdown) // Returns INode tree structure with parsed wikilinks // Links converted to: <a href="link">Display Text</a> ``` ### Create and Configure Markmap Instance Initialize a Markmap visualization with customizable options. ```typescript import { createMarkmap, getOptions } from 'src/rendering/renderer-common' const container = document.querySelector('.my-mindmap-container') // Create without toolbar const { svg, markmap } = createMarkmap({ parent: container, toolbar: false }) // Create with toolbar const { svg, markmap, toolbar } = createMarkmap({ parent: container, toolbar: true }) // Configure and render const settings = { coloring: 'depth', depth1Color: '#e74c3c', depth2Color: '#3498db', depth3Color: '#2ecc71', defaultColor: '#95a5a6', initialExpandLevel: 2, animationDuration: 500, spacingHorizontal: 80, spacingVertical: 5 } const options = getOptions(settings) const rootNode = transformMarkdown(markdown) await markmap.setData(rootNode, options) markmap.fit() // Fit to viewport ``` ### Code Block Handler Track and manage all mindmap code blocks in documents. ```typescript import { codeBlockCreated, getCodeBlocksByPath } from 'src/new/codeBlockHandler' import Callbag from 'src/utilities/callbag' // Subscribe to new code blocks Callbag.subscribe(codeBlockCreated, (codeBlock) => { console.log('New code block:', codeBlock.file.path) console.log('Markdown content:', codeBlock.markdown) console.log('Container element:', codeBlock.containerEl) }) // Get all code blocks in a specific file const filePath = 'notes/project-plan.md' const codeBlocks = getCodeBlocksByPath(filePath) codeBlocks.forEach(block => { const sectionInfo = block.getSectionInfo() console.log(`Block at lines ${sectionInfo.lineStart}-${sectionInfo.lineEnd}`) }) ``` ### Settings Merging Strategy Implement three-tier settings inheritance: Global → File → Code Block. ```typescript import { splitMarkdown } from 'src/rendering/renderer-common' import { globalSettings } from 'src/settings/filesystem' // Get file-level settings from frontmatter const fileContent = editor.getValue() const { settings: fileSettings } = splitMarkdown('file', fileContent) // Get code block settings const codeBlockMarkdown = ` # Mindmap --- markmap: height: 250 coloring: branch ` const { settings: codeBlockSettings, body } = splitMarkdown('codeBlock', codeBlockMarkdown) // Merge settings (later overrides earlier) const mergedSettings = { ...globalSettings, ...fileSettings, ...codeBlockSettings } console.log(mergedSettings.coloring) // 'branch' (from code block) console.log(mergedSettings.depth1Color) // from file or global console.log(mergedSettings.height) // 250 (code block only) ``` ### Internal Link Parsing Convert Obsidian wikilinks to clickable HTML anchors within mindmaps. ```typescript import { parseInternalLinks } from 'src/internal-links/parse-internal-links' const node = { content: 'Check [[documentation]] and [[guides|User Guides]]', children: [ { content: 'Sub-item with [[another-link]]', children: [] } ] } parseInternalLinks(node) // node.content becomes: // 'Check <a href="documentation">documentation</a> and <a href="guides">User Guides</a>' // Recursively processes all children ``` ### Custom Embed Support Embed markdown files within mindmap nodes using custom syntax. ```typescript // In markdown: // ![[embedded-file.md]] import { embedPlugin } from 'src/embeds/embeds' // Plugin automatically registers markdown-it rule // Converts: ![[file.md]] → <mmng-embed linkText="file.md"></mmng-embed> // Custom element resolves file path and renders content // Example usage in mindmap markdown: const markdown = ` # Project Structure ## Documentation ![[README.md]] ## Guides ![[user-guide.md]] ![[developer-guide.md#installation]] ` ``` ### Mindmap View Management Create and manage dedicated mindmap tab views. ```typescript import MindmapView from 'src/views/view' import { leafManager } from 'src/views/leaf-manager' import { getActiveFile } from 'src/views/get-active-file' // Create unpinned view (follows active file) leafManager.new('unpinned') // Create pinned view for specific file const file = app.vault.getAbstractFileByPath('notes/project.md') if (file instanceof TFile) { leafManager.new(file) } // Access view instance const instances = MindmapView.instances instances.forEach(view => { console.log('Display text:', view.getDisplayText()) view.render(file) // Render specific file view.renderer.collapseAll() // Collapse all nodes view.renderer.takeScreenshot() // Export as PNG }) // Reveal existing view leafManager.reveal('unpinned') // Close view leafManager.close('unpinned') ``` ### Reactive Event Streams Subscribe to workspace events using Callbag functional reactive programming. ```typescript import Callbag from 'src/utilities/callbag' import { fileOpen, fileChanged, layoutChange, isDarkMode, commandOpenUnpinned } from 'src/core/events' // File opened Callbag.subscribe(fileOpen, (file) => { console.log('Opened file:', file?.path) }) // File content changed (debounced 300ms) Callbag.subscribe(fileChanged, ({ editor, info }) => { console.log('Editor changed:', info.file.path) const content = editor.getValue() }) // Workspace layout changed Callbag.subscribe(layoutChange, () => { console.log('Layout updated') }) // Dark mode toggled Callbag.subscribe(isDarkMode, (isDark) => { console.log('Dark mode:', isDark) document.body.classList[isDark ? 'add' : 'remove']('markmap-dark') }) // Command executed Callbag.subscribe(commandOpenUnpinned, () => { console.log('Open unpinned mindmap command triggered') }) ``` ### Custom Callbag Operators Create reactive streams from Obsidian events with type-safe operators. ```typescript import { fromObsidianEvent, fromCommand } from 'src/utilities/callbag' // Create stream from Obsidian event const fileDeleted$ = fromObsidianEvent(app.vault, 'delete') .unary() // Take only first argument const fileCreated$ = fromObsidianEvent(app.vault, 'create') .object('file') // Map to { file: TAbstractFile } const metadataChange$ = fromObsidianEvent(app.metadataCache, 'changed') .object('file', 'data', 'cache') // Maps to { file: TFile, data: string, cache: CachedMetadata } // Create command that emits when executed const myCommand$ = fromCommand( 'mindmapnextgen:custom-command', 'Custom Command Name' ) Callbag.subscribe(myCommand$, () => { console.log('Command executed') }) ``` ### Screenshot Export Export mindmap as PNG with customizable colors. ```typescript import { takeScreenshot } from 'src/rendering/screenshot' import type { Markmap } from 'markmap-view' const markmap: Markmap // Existing markmap instance // Use theme colors (current light/dark mode) await takeScreenshot(markmap, { background: getComputedStyle(document.body).backgroundColor, text: getComputedStyle(document.body).color }) // Use custom colors await takeScreenshot(markmap, { background: '#ffffff', text: '#000000' }) // Use transparent background await takeScreenshot(markmap, { background: 'transparent', text: '#2c3e50' }) // Screenshot automatically copied to clipboard ``` ### Drag and Drop Resize Create resizable mindmap containers with drag handles. ```typescript import { dragAndDrop } from 'src/utilities/callbag' import Callbag from 'src/utilities/callbag' const resizeHandle = document.createElement('hr') resizeHandle.classList.add('workspace-leaf-resize-handle') container.prepend(resizeHandle) const drag$ = dragAndDrop(resizeHandle) let currentHeight = 300 Callbag.subscribe(drag$, (drag) => { // drag: { start, current, changeFromStart, changeFromPrevious } currentHeight += drag.changeFromPrevious.y svg.style.height = currentHeight + 'px' }) // Save height on mouse up Callbag.subscribe(fromEvent(document, 'mouseup'), () => { console.log('Final height:', currentHeight) // Update frontmatter or settings }) ``` ### Settings Change Listeners React to specific setting changes without re-rendering entire view. ```typescript import { settingChanges } from 'src/settings/filesystem' // Listen to single setting const unsubscribe = settingChanges.listen('coloring', (newValue) => { console.log('Coloring changed to:', newValue) // Re-render mindmaps with new coloring strategy }) // Listen to multiple settings settingChanges.listen('animationDuration', (duration) => { console.log('Animation duration:', duration) }) settingChanges.listen('depth1Color', (color) => { console.log('Depth 1 color:', color) }) // Unsubscribe when done unsubscribe() ``` ### Layout Serialization Save and restore workspace layout with pinned mindmap views. ```typescript import { layoutManager } from 'src/views/layout-manager' import { layout } from 'src/settings/filesystem' // Save current layout layoutManager.serialise() // Stores to plugin data: { version: '2.0', settings: {...}, layout: [...] } // On plugin load, restore layout await layoutManager.deserialise() // Recreates all pinned mindmap tabs in correct positions // Access raw layout data const currentLayout = layout.load() console.log('Saved layout:', currentLayout) // Manually update layout layout.save([ { type: 'tabs', mindmaps: [ { file: 'notes/project.md', pinned: true } ] } ]) ``` ### Coloring Strategies Apply different coloring algorithms to mindmap branches. ```typescript import { depthColoring } from 'src/rendering/renderer-common' // Depth-based coloring const depthColorFn = depthColoring({ coloring: 'depth', depth1Color: '#e74c3c', depth2Color: '#3498db', depth3Color: '#2ecc71', defaultColor: '#95a5a6' }) const node = { state: { depth: 2 } } const color = depthColorFn(node) // '#3498db' // Branch coloring (random per branch) const options = { coloring: 'branch', colorFreezeLevel: 2 // Stop color changes after depth 2 } // Single color (uniform) const singleColorOptions = { coloring: 'single', defaultColor: '#34495e' } ``` ### Translation Support Access localized strings based on user's Obsidian language. ```typescript import { strings } from 'src/translation' // Command names console.log(strings.commands.unpinned) // "Open as unpinned mindmap" console.log(strings.commands.pinned) // "Open as pinned mindmap" // Menu items console.log(strings.menu.copyScreenshot) // "Copy screenshot" console.log(strings.menu.collapseAll) // "Collapse all" console.log(strings.menu.pin.pin) // "Pin" console.log(strings.menu.pin.unpin) // "Unpin" console.log(strings.menu.toolbar.show) // "Show" console.log(strings.menu.toolbar.hide) // "Hide" // Settings UI console.log(strings.settings.level.global) // "Global Settings" console.log(strings.settings.level.file) // "File Settings" console.log(strings.settings.level.codeBlock) // "Code Block Settings" // Supported languages: en, zh-CN, zh-TW // Auto-detects from Obsidian locale ``` ### Custom Subject/Stream Pattern Create imperative push-based streams for event emission. ```typescript import Callbag from 'src/utilities/callbag' const { source, push } = Callbag.subject<string>() // Subscribe to stream Callbag.subscribe(source, (value) => { console.log('Received:', value) }) // Push values push('Hello') push.next('World') // Handle errors push.error(new Error('Something went wrong')) // Complete stream push.complete() // Use with reactive operators Callbag.pipe( source, Callbag.filter(x => x.length > 5), Callbag.map(x => x.toUpperCase()), Callbag.subscribe(console.log) ) ``` ### Dynamic Style Management Inject and update CSS dynamically based on settings. ```typescript import { globalStyle } from 'src/rendering/style-tools' // Register style that updates on setting changes globalStyle.add( ['depth1Thickness', 'depth2Thickness'], // Triggers (settings) => ` .markmap-node[data-depth="1"] { stroke-width: ${settings.depth1Thickness}px; } .markmap-node[data-depth="2"] { stroke-width: ${settings.depth2Thickness}px; } ` ) // Styles auto-update when settings change globalSettings.depth1Thickness = '5' // CSS automatically regenerated and applied ``` ### Utility Functions Helper functions for common operations. ```typescript import { assert, notNullish, isObjectEmpty, nextTick } from 'src/utilities/utilities' // Type-safe assertions const file = app.vault.getFileByPath('notes/project.md') assert(notNullish, file, 'File not found') // Now TypeScript knows file is not null // Check empty objects const settings = {} if (isObjectEmpty(settings)) { console.log('No custom settings') } // Wait for next event loop tick await nextTick() console.log('Executed after current call stack') // Custom Set with functional methods import { Set } from 'src/utilities/set' const numbers = new Set([1, 2, 3, 4, 5]) const evens = numbers.filter(n => n % 2 === 0) // Set { 2, 4 } const doubled = numbers.map(n => n * 2) // Set { 2, 4, 6, 8, 10 } const found = numbers.find(n => n > 3) // 4 ``` ## Summary and Integration Mindmap NextGen serves as a powerful visualization layer for Obsidian notes, enabling users to transform hierarchical markdown structures into interactive mindmaps. Primary use cases include project planning and brainstorming, knowledge base visualization, document outlining, meeting notes organization, and hierarchical data representation. The plugin's flexible three-tier settings system allows customization at global, file, and code-block levels, while features like internal linking, embeds, and screenshot export enhance note-taking workflows. Integration patterns leverage Obsidian's plugin API through event subscriptions, workspace management, and markdown post-processing. Developers can extend functionality by subscribing to Callbag event streams, creating custom coloring strategies, implementing new markdown-it plugins for embed support, or building UI components using the settings framework. The reactive architecture based on functional programming principles ensures efficient rendering and state management, making it suitable for both simple note visualization and complex knowledge management systems. The plugin's modular design allows easy customization of rendering pipeline, settings persistence, view management, and link handling behaviors.
Mindmap NextGen (james-tindal/obsidian-mindmap-nextgen) | Context7