Overview
Inkdown uses an event system to notify plugins of changes and user actions. Subscribe to events to react to file changes, editor updates, and more.
// Subscribe to file creation
this.registerEvent(
this.app.workspace.onFileCreate((file) => {
console.log('File created:', file.path);
})
);
Events Base Class
Many Inkdown managers extend the Events class.
abstract class Events {
on(name: string, callback: (...data: unknown[]) => void, ctx?: unknown): EventRef;
off(name: string, callback: (...data: unknown[]) => void): void;
offref(ref: EventRef): void;
trigger(name: string, ...data: unknown[]): void;
}
type EventRef = () => void;
Registration
Using Plugin.registerEvent()
registerEvent
(eventRef: EventRef) => void
Register an event listener that’s automatically cleaned up when the plugin unloads.
export default class MyPlugin extends Plugin {
async onload() {
// Automatic cleanup
this.registerEvent(
this.app.workspace.onFileCreate((file) => {
console.log('File created:', file.path);
})
);
}
}
Always use registerEvent() to ensure listeners are cleaned up when your plugin unloads.
Manual Event Handling
If you need to manually manage events:
const eventRef = this.app.workspace.onFileCreate((file) => {
console.log('File created:', file.path);
});
// Later, to unsubscribe:
eventRef();
Workspace Events
onFileCreate()
onFileCreate
(callback: (file: TFile) => void) => EventRef
Fired when a file is created.
this.registerEvent(
this.app.workspace.onFileCreate((file) => {
console.log('New file:', file.path);
this.showNotice(`Created: ${file.basename}`);
// Initialize file with template
if (file.extension === 'md') {
this.initializeFile(file);
}
})
);
onFileModify()
onFileModify
(callback: (file: TFile) => void) => EventRef
Fired when a file is modified.
this.registerEvent(
this.app.workspace.onFileModify((file) => {
console.log('File modified:', file.path);
// Update cache
this.invalidateCache(file.path);
// Auto-save after modification
this.scheduleAutoSave(file);
})
);
onFileDelete()
onFileDelete
(callback: (file: TAbstractFile) => void) => EventRef
Fired when a file or folder is deleted.
this.registerEvent(
this.app.workspace.onFileDelete((file) => {
console.log('Deleted:', file.path);
// Clean up references
this.removeFromIndex(file.path);
// Update UI
this.refreshView();
})
);
onFileRename()
onFileRename
(callback: (file: TFile, oldPath: string) => void) => EventRef
Fired when a file is renamed or moved.
this.registerEvent(
this.app.workspace.onFileRename((file, oldPath) => {
console.log(`Renamed: ${oldPath} -> ${file.path}`);
// Update links
this.updateLinks(oldPath, file.path);
// Update bookmarks
this.updateBookmarks(oldPath, file.path);
})
);
Workspace UI Events
onActiveFileChange()
onActiveFileChange
(callback: (file: TFile | null) => void) => EventRef
Fired when the active file changes.
this.registerEvent(
this.app.workspaceUI.onActiveFileChange((file) => {
if (file) {
console.log('Active file:', file.path);
this.updateStatusBar(file);
} else {
console.log('No active file');
this.clearStatusBar();
}
})
);
onFileOpen()
onFileOpen
(callback: (file: TFile) => void) => EventRef
Fired when a file is opened.
this.registerEvent(
this.app.workspaceUI.onFileOpen((file) => {
console.log('File opened:', file.path);
this.addToRecentFiles(file.path);
this.preloadRelatedFiles(file);
})
);
onChanged()
onChanged
(callback: (file: TFile, data: string, cache: CachedMetadata) => void) => EventRef
Fired when file metadata is updated.
this.registerEvent(
this.app.metadataCache.onChanged((file, data, cache) => {
console.log('Metadata updated:', file.path);
console.log('Frontmatter:', cache.frontmatter);
console.log('Links:', cache.links);
console.log('Tags:', cache.tags);
// Update search index
this.updateSearchIndex(file, cache);
})
);
Manager Events
Managers like BookmarkManager and TagManager emit generic events:
BookmarkManager
this.registerEvent(
this.app.bookmarkManager.on('change', () => {
console.log('Bookmarks changed');
this.refreshBookmarkView();
})
);
TagManager
this.registerEvent(
this.app.tagManager.on('change', () => {
console.log('Tags changed');
this.refreshTagView();
})
);
Plugin Manager (via Plugins)
const unsubscribe = this.app.plugins.onPluginChange(
(pluginId, changeType) => {
console.log(`${pluginId}: ${changeType}`);
if (changeType === 'enabled') {
console.log(`${pluginId} was enabled`);
} else if (changeType === 'disabled') {
console.log(`${pluginId} was disabled`);
} else if (changeType === 'settings-changed') {
console.log(`${pluginId} settings changed`);
}
}
);
// Manual cleanup (if not using registerEvent)
// unsubscribe();
DOM Events
registerDomEvent()
registerDomEvent
(el: HTMLElement, type: string, callback: Function) => void
Register DOM event listeners with automatic cleanup.
const button = document.createElement('button');
button.textContent = 'Click Me';
this.registerDomEvent(button, 'click', (evt: MouseEvent) => {
console.log('Button clicked');
this.showNotice('Button clicked!');
});
// Window events
this.registerDomEvent(window, 'resize', () => {
console.log('Window resized');
this.updateLayout();
});
// Document events
this.registerDomEvent(document, 'keydown', (evt: KeyboardEvent) => {
if (evt.key === 'Escape') {
this.closeModal();
}
});
Intervals and Timers
registerInterval()
Register an interval for automatic cleanup.
export default class MyPlugin extends Plugin {
async onload() {
// Auto-save every 30 seconds
this.registerInterval(
window.setInterval(() => {
this.autoSave();
}, 30000)
);
// Update status bar every second
this.registerInterval(
window.setInterval(() => {
this.updateStatusBar();
}, 1000)
);
}
}
Common Patterns
Track File Changes
export default class MyPlugin extends Plugin {
private modifiedFiles = new Set<string>();
async onload() {
this.registerEvent(
this.app.workspace.onFileModify((file) => {
this.modifiedFiles.add(file.path);
})
);
// Process modified files periodically
this.registerInterval(
window.setInterval(() => {
if (this.modifiedFiles.size > 0) {
this.processModifiedFiles(Array.from(this.modifiedFiles));
this.modifiedFiles.clear();
}
}, 5000)
);
}
async processModifiedFiles(paths: string[]) {
console.log('Processing modified files:', paths);
// Process files...
}
}
Debounce Events
export default class MyPlugin extends Plugin {
private saveTimeout: number | null = null;
async onload() {
this.registerEvent(
this.app.workspace.onFileModify((file) => {
this.debouncedSave(file);
})
);
}
debouncedSave(file: TFile) {
if (this.saveTimeout !== null) {
clearTimeout(this.saveTimeout);
}
this.saveTimeout = window.setTimeout(() => {
this.performSave(file);
this.saveTimeout = null;
}, 2000);
}
async performSave(file: TFile) {
console.log('Saving:', file.path);
// Perform save...
}
}
React to Active File
export default class MyPlugin extends Plugin {
private currentFile: TFile | null = null;
async onload() {
this.registerEvent(
this.app.workspaceUI.onActiveFileChange((file) => {
this.onActiveFileChange(file);
})
);
}
onActiveFileChange(file: TFile | null) {
// Clean up previous file
if (this.currentFile) {
this.cleanupFile(this.currentFile);
}
// Set up new file
this.currentFile = file;
if (file) {
this.setupFile(file);
}
}
setupFile(file: TFile) {
console.log('Setting up:', file.path);
// Initialize for this file
}
cleanupFile(file: TFile) {
console.log('Cleaning up:', file.path);
// Clean up resources
}
}
export default class MyPlugin extends Plugin {
private cache = new Map<string, CachedMetadata>();
async onload() {
this.registerEvent(
this.app.metadataCache.onChanged((file, data, cache) => {
this.cache.set(file.path, cache);
this.updateView(file, cache);
})
);
}
updateView(file: TFile, cache: CachedMetadata) {
// Check for specific changes
if (cache.frontmatter?.status === 'published') {
this.showNotice(`${file.basename} is published`);
}
// Update based on tags
if (cache.tags?.some(t => t.tag === '#important')) {
this.addToImportantList(file);
}
}
}
Best Practices
Always Use registerEvent()
// ✅ Good - automatic cleanup
this.registerEvent(
this.app.workspace.onFileCreate((file) => {
console.log('Created:', file.path);
})
);
// ❌ Bad - manual cleanup required
const ref = this.app.workspace.onFileCreate((file) => {
console.log('Created:', file.path);
});
// Must remember to call ref() in onunload!
Handle Null Values
// ✅ Good
this.registerEvent(
this.app.workspaceUI.onActiveFileChange((file) => {
if (file) {
this.updateForFile(file);
} else {
this.clearView();
}
})
);
// ❌ Bad - assumes file exists
this.registerEvent(
this.app.workspaceUI.onActiveFileChange((file) => {
this.updateForFile(file); // May be null!
})
);
Avoid Heavy Operations in Event Handlers
// ✅ Good - debounce heavy operations
private updateTimeout: number | null = null;
this.registerEvent(
this.app.workspace.onFileModify((file) => {
if (this.updateTimeout) clearTimeout(this.updateTimeout);
this.updateTimeout = window.setTimeout(() => {
this.heavyOperation(file);
}, 500);
})
);
// ❌ Bad - heavy operation on every change
this.registerEvent(
this.app.workspace.onFileModify((file) => {
this.heavyOperation(file); // Called too often!
})
);
Clean Up Intervals
// ✅ Good - use registerInterval
this.registerInterval(
window.setInterval(() => {
this.update();
}, 1000)
);
// ❌ Bad - manual cleanup needed
const interval = window.setInterval(() => {
this.update();
}, 1000);
// Must clear in onunload!