Skip to main content

Plugin API

SyncFolds Class

The main plugin class that extends Obsidian’s Plugin base class.
export default class SyncFolds extends Plugin {
    settings: SyncFoldSettings
    private debounceTimer: number | null
    private originalSetItem: typeof Storage.prototype.setItem
    private originalRemoveItem: typeof Storage.prototype.removeItem
    private cachedFolds: FoldStateData
}

Properties

settings
SyncFoldSettings
required
Current plugin settings including the enableSync flag and serialized fold data.
debounceTimer
number | null
Internal timer ID for the debounced save operation. null when no save is pending.
originalSetItem
typeof Storage.prototype.setItem
Reference to the original localStorage.setItem method before interception.
originalRemoveItem
typeof Storage.prototype.removeItem
Reference to the original localStorage.removeItem method before interception.
cachedFolds
FoldStateData
In-memory cache of the current fold states for efficient change detection.

Public Methods

onExternalSettingsChange()

Called when the plugin’s data.json file is modified externally (e.g., by a file sync service). This method synchronizes the external changes back to localStorage.
public async onExternalSettingsChange(): Promise<void>
This method is typically called by Obsidian’s plugin system or by file sync integrations that detect changes to the plugin’s data file.
Behavior:
  1. Loads the updated settings from disk
  2. Compares cached fold states with newly loaded states
  3. Removes localStorage entries for deleted folds
  4. Updates localStorage entries for modified folds
  5. Updates the internal cache
Example Usage:
// In a file sync integration or external watcher
const plugin = app.plugins.getPlugin('sync-folds')
if (plugin) {
    await plugin.onExternalSettingsChange()
}
Source Reference: main.ts:73-107

Lifecycle Methods

onload()

Initializes the plugin when Obsidian loads it. This is called automatically by Obsidian.
async onload(): Promise<void>
Initialization Steps:
  1. Load settings from data.json
  2. Check if sync is enabled
  3. Intercept localStorage operations
  4. Import or export initial fold states
  5. Register commands
  6. Initialize cache
Source Reference: main.ts:13-57

onunload()

Cleans up resources when the plugin is disabled or Obsidian closes.
onunload(): void
Cleanup Steps:
  1. Restore original localStorage methods
  2. Cancel any pending debounced saves
  3. Clear internal state
Source Reference: main.ts:109-116

Private Methods

loadSettings()

Loads plugin settings from disk.
async loadSettings(): Promise<void>
Source Reference: main.ts:118-124

saveSettings()

Persists current settings to disk.
async saveSettings(): Promise<void>
Source Reference: main.ts:126-128

getFoldsObject()

Parses the JSON-serialized fold data into a FoldStateData object.
private getFoldsObject(): FoldStateData
return
FoldStateData
Parsed fold state data, or an empty object if parsing fails.
Source Reference: main.ts:59-66

setFoldsObject()

Serializes a FoldStateData object to JSON and stores it in settings.
private setFoldsObject(folds: FoldStateData): void
folds
FoldStateData
required
The fold state data to serialize and store.
Source Reference: main.ts:69-71

interceptLocalStorage()

Intercepts localStorage.setItem and localStorage.removeItem to capture fold state changes.
interceptLocalStorage(): void
Behavior:
  • Wraps native localStorage methods with custom logic
  • Filters for keys matching the pattern {appId}-note-fold-{filePath}
  • Triggers debounced sync for matching keys
Source Reference: main.ts:130-159

restoreLocalStorage()

Restores the original localStorage methods, removing the plugin’s interception.
restoreLocalStorage(): void
Source Reference: main.ts:161-168

debouncedSyncFile()

Debounces fold state changes to prevent excessive disk writes.
debouncedSyncFile(filePath: string, value: string | null): void
filePath
string
required
The path of the file whose fold state changed.
value
string | null
required
The new fold state as a JSON string, or null if the fold was removed.
Debounce Delay: 150ms Source Reference: main.ts:170-182

exportFoldsToFile()

Exports all fold states from localStorage to the plugin’s settings file.
private async exportFoldsToFile(): Promise<void>
Behavior:
  1. Scans localStorage for all keys matching {appId}-note-fold-*
  2. Parses each fold state
  3. Builds a FoldStateData object
  4. Saves to settings.folds
  5. Updates the cache
Source Reference: main.ts:184-217

importFoldsToStorage()

Imports fold states from the plugin’s settings file to localStorage.
private importFoldsToStorage(): void
Behavior:
  1. Reads settings.folds
  2. Parses the JSON data
  3. Writes each fold state to localStorage using the {appId}-note-fold-{filePath} key pattern
  4. Updates the cache
Source Reference: main.ts:219-240

upsertFoldStateForFile()

Updates or removes a single file’s fold state in the settings.
private async upsertFoldStateForFile(filePath: string, value: string | null): Promise<void>
filePath
string
required
The file path whose fold state should be updated.
value
string | null
required
The new fold state as JSON, or null to remove the fold state.
Source Reference: main.ts:242-268

Type Definitions

SyncFoldSettings

Configuration interface for the plugin.
export interface SyncFoldSettings {
    enableSync: boolean
    folds: string
}
enableSync
boolean
default:"true"
Whether fold synchronization is enabled. When false, the plugin does not intercept localStorage or sync fold states.
folds
string
default:"\"{}\""
JSON-serialized FoldStateData object containing all fold states.
Default Settings:
export const DEFAULT_SETTINGS: SyncFoldSettings = {
    enableSync: true,
    folds: '{}'
}
Source Reference: settings.ts:1-9

FoldStateData

Top-level data structure mapping file paths to their fold states.
export interface FoldStateData {
    [filePath: string]: FoldedProperties
}
[filePath]
FoldedProperties
The fold properties for a specific file. The key is the file’s vault-relative path (e.g., "Daily Notes/2024-03-15.md").
Example:
{
  "Daily Notes/2024-03-15.md": {
    "folds": [
      { "from": 5, "to": 12 },
      { "from": 20, "to": 25 }
    ],
    "lines": 100
  },
  "Projects/README.md": {
    "folds": [
      { "from": 0, "to": 3 }
    ],
    "lines": 50
  }
}
Source Reference: types.ts:11-13

FoldedProperties

Contains all fold information for a single file.
export interface FoldedProperties {
    folds: Fold[]
    lines: number
}
folds
Fold[]
required
Array of folded regions in the file. Each element represents a contiguous folded section.
lines
number
required
Total number of lines in the document. Used by Obsidian for validation and rendering.
Source Reference: types.ts:6-9

Fold

Represents a single folded region in a document.
export interface Fold {
    from: number
    to: number
}
from
number
required
Zero-indexed starting line number of the fold.
to
number
required
Zero-indexed ending line number of the fold (inclusive).
Example:
{
  "from": 5,
  "to": 12
}
This represents a fold from line 5 to line 12 (lines 5-12 are hidden when collapsed). Source Reference: types.ts:1-4

Commands

The plugin registers two commands that users can invoke from the command palette.

export-fold-states

Manually exports all fold states from localStorage to the plugin settings file. Command ID: export-fold-states
Command Name: “Export folds from local storage”
Usage:
  1. Open command palette (Ctrl/Cmd + P)
  2. Type “Export folds”
  3. Select “Sync Folds: Export folds from local storage”
  4. A notice confirms: “Fold states saved to settings”
When to Use:
  • Before manually syncing the vault to another device
  • To force a save of current fold states
  • After recovering from a corrupted settings file
Implementation:
this.addCommand({
    id: 'export-fold-states',
    name: 'Export folds from local storage',
    callback: async () => {
        await this.exportFoldsToFile()
        new Notice('Fold states saved to settings')
    }
})
Source Reference: main.ts:38-45

import-fold-states

Manually imports fold states from the plugin settings file to localStorage. Command ID: import-fold-states
Command Name: “Import folds into local storage”
Usage:
  1. Open command palette (Ctrl/Cmd + P)
  2. Type “Import folds”
  3. Select “Sync Folds: Import folds into local storage”
  4. A notice confirms: “Fold states applied from settings”
When to Use:
  • After syncing the vault from another device
  • To restore fold states from a backup
  • When folds appear out of sync between devices
Implementation:
this.addCommand({
    id: 'import-fold-states',
    name: 'Import folds into local storage',
    callback: async () => {
        this.importFoldsToStorage()
        new Notice('Fold states applied from settings')
    }
})
Source Reference: main.ts:47-54

Usage Examples

Accessing Plugin Instance

// From another plugin or script
const app = this.app  // or window.app
const syncFoldsPlugin = app.plugins.getPlugin('sync-folds')

if (syncFoldsPlugin) {
    console.log('Sync Folds is enabled')
    console.log('Current settings:', syncFoldsPlugin.settings)
}

Reading Fold States

// Get the current fold states
const plugin = app.plugins.getPlugin('sync-folds')
if (plugin) {
    const foldData: FoldStateData = JSON.parse(plugin.settings.folds)
    
    // Check if a specific file has folds
    const filePath = 'Daily Notes/2024-03-15.md'
    if (foldData[filePath]) {
        console.log('Folds for', filePath, ':', foldData[filePath].folds)
    }
}

Triggering Manual Sync

// Force export of current folds
const plugin = app.plugins.getPlugin('sync-folds')
if (plugin) {
    // Call the private method via the command
    app.commands.executeCommandById('sync-folds:export-fold-states')
}

Monitoring Fold Changes

// Watch for changes to the plugin's data file
const plugin = app.plugins.getPlugin('sync-folds')
if (plugin) {
    // Register a vault modify event
    this.registerEvent(
        app.vault.on('modify', async (file) => {
            if (file.path === '.obsidian/plugins/sync-folds/data.json') {
                console.log('Fold settings changed')
                await plugin.onExternalSettingsChange()
            }
        })
    )
}

Programmatically Enabling/Disabling Sync

const plugin = app.plugins.getPlugin('sync-folds')
if (plugin) {
    // Disable sync
    plugin.settings.enableSync = false
    await plugin.saveSettings()
    
    // Re-enable sync
    plugin.settings.enableSync = true
    await plugin.saveSettings()
    
    // Note: This requires a plugin reload to take effect
    await app.plugins.disablePlugin('sync-folds')
    await app.plugins.enablePlugin('sync-folds')
}

Logging API

The plugin includes a conditional logging system for debugging.
export const log = (...args: unknown[]) => {
    if (DEBUG) {
        console.debug('[SyncFolds]', ...args)
    }
}
args
unknown[]
required
Arguments to log. These are passed directly to console.debug().
Enabling Debug Logs: The DEBUG constant is set at build time. To enable logging:
  1. Modify esbuild.config.mjs to set DEBUG: 'true'
  2. Rebuild the plugin: npm run build
  3. Open the developer console in Obsidian (Ctrl/Cmd + Shift + I)
  4. Filter console output by [SyncFolds]
Example Output:
[SyncFolds] Plugin loaded with settings: { enableSync: true, folds: '{}' }
[SyncFolds] Intercepting localStorage
[SyncFolds] No folds in settings: exporting from localStorage
[SyncFolds] Fold state changed: Daily Notes/2024-03-15.md
[SyncFolds] Debouncing file sync (150ms) for: Daily Notes/2024-03-15.md
[SyncFolds] Executing debounced file sync for: Daily Notes/2024-03-15.md
Source Reference: log.ts:1-7

Version Information

Plugin ID: sync-folds
Plugin Name: Sync Folds
Current Version: 1.0.0
Minimum Obsidian Version: 0.15.0
Desktop Only: No (mobile compatible)
Source Reference: manifest.json:1-13

Build docs developers (and LLMs) love